Почему баг Cursor — это не обычный баг

Человек, который пишет то же самое изменение, держит в голове контекст, недоступный редактору. Он помнит, что панель настроек читает из кеша, который нужно инвалидировать; что кнопка загрузки неактивна, пока не выбран файл; что конкретный эндпоинт при отсутствии совпадений возвращает пустой массив, а не 404. Cursor исходит из файлов, которые вы открыли, и текста, который вы набрали. Всё, что за пределами этого окна, — догадка.

Поэтому дело не в кривом синтаксисе. Проблема в изменении, которое локально корректно и глобально неверно. Функция делает ровно то, что заявлено. Но вызывается она не в тот момент, или оставляет после себя кусок состояния, или рассчитывает на структуру, которую API перестал возвращать два спринта назад.

Юнит-тесты этого не ловят, потому что пишутся исходя из тех же предположений, что и код. Если и то и другое написал агент, тесты согласуются с кодом — и расходятся с вашим продуктом.

Откуда баги Cursor берутся на самом деле

Большая часть приходится на три области.

Состояние интерфейса

  • Форма отправляется, но список за ней не обновляется.

  • Модальное окно закрывается и оставляет блокировку прокрутки на body.

  • Кнопка остаётся активной, пока запрос уже выполняется, и двойной клик создаёт две записи.

Асинхронные сценарии

  • Экран отрисовывается до прихода данных и больше не перерисовывается.

  • Фоновая задача запускается, но её никто не дожидается, и следующий шаг читает устаревшие значения.

  • Ветка с ошибкой завершается молча, и пользователь видит сообщение об успехе там, где операция провалилась.

Границы интеграций

  • Тело запроса соответствует описанию типа, но не тому, что на самом деле принимает сервис.

  • Аутентификация считается выполненной, потому что она была выполнена в той сессии, которую видел агент.

  • Пагинация, пустые состояния и лимиты запросов обработаны в системе типов — и больше нигде.

Общее у всех этих случаев одно: увидеть их можно, только запустив приложение и поработав в нём. Чтение диффа не покажет ни одного из них — как и набор тестов, который ни разу не открывает браузер.

Как поймать баги Cursor до слияния

Проверка должна идти по развёрнутому приложению, причём работать с ним нужно так, как работал бы человек, а результат должен возвращаться в виде, пригодном для действий агента. Иначе баг найден, но чинить его всё равно вам.

Должны выполняться три условия. Ни одно из них не сложное, но стоит выпустить любое — и цикл начинает протекать.

Агент должен уметь проверять

Установка добавляет навык проверки прямо в кодинг-агента, поэтому он знает, как создавать, запускать и разбирать тесты, а не догадывается об этом по README. Она пишет файл с инструкциями туда, где ваш редактор его и так ищет, — то есть Cursor подхватывает его из .cursor/rules/ так же, как Claude Code читает собственный каталог навыков. Поддерживаются восемь редакторов — это важно в команде, где не все работают в одном и том же.

Терминал

npm install -g @testsprite/testsprite-cli
testsprite setup

То же самое можно сделать в дашборде TestSprite, если не хочется ничего ставить локально. Всё остальное, что умеет CLI, описано в репозитории CLI.

Проверка должна идти по развёрнутому приложению, а не по моку

Направьте запуск на окружение, где изменение уже работает. Агент открывает приложение, проходит сценарий как пользователь и сообщает, что произошло на самом деле, а не выполнилось ли утверждение в песочнице. Моки такого сигнала дать не могут — поэтому зелёные юнит-тесты и сломанная функциональность так спокойно уживаются вместе.

Падение должно возвращаться в том виде, с которым агент может работать

Именно здесь решается, замкнётся ли цикл. Если падение приходит скриншотом в дашборде, его должен прочитать, истолковать и превратить в промпт человек. Если оно приходит единым связным пакетом — что пытались сделать, как повело себя приложение и где именно возникло расхождение, — агент может взять его и сразу действовать. Следующая итерация начинается с фактов, а не с вашего пересказа фактов.

Сделайте так, чтобы никому не приходилось об этом помнить

Проверку, которую запускают руками, пропускают в тот самый день, когда все спешат, — то есть когда она нужнее всего. Автоматизировать её можно двумя способами, и выбор зависит от того, кто отвечает за ваш пайплайн.

  • GitHub App — это вебхук, который настраивается в дашборде TestSprite. Он слушает событие деплоя, которое ваш пайплайн уже генерирует, поэтому в репозитории ничего менять не нужно.

  • GitHub Actions встраивает этот шаг прямо в ваш workflow, а настраивается из терминала.

Саму схему работы стоит понимать, даже если вы никогда не заглянете в конфигурацию. Интеграция не собирает и не деплоит ваше приложение, не добавляет и не редактирует файлы workflow. Она слушает событие деплоя, которое ваш пайплайн уже генерирует, и воспринимает «новая сборка доступна по этому URL» как сигнал к запуску. Результаты возвращаются туда, где идёт работа, — комментарием к пул-реквесту или проверкой на коммите.

Полезность зависит от двух решений, и оба — вопрос здравого смысла, а не настроек.

  • Какой момент считать готовностью. Выбирайте событие, которое срабатывает после того, как деплой стал доступен, а не в момент старта сборки. Слишком раннее событие направит запуск на URL, который ещё не поднялся, и все тесты упадут по причине, никак не связанной с вашим кодом. Это самая частая ошибка при настройке.

  • Проверка рекомендательная или обязательная. Комментарий к пул-реквесту информирует. Обязательная проверка блокирует слияние. Начните с рекомендательного режима на неделю, посмотрите, что она ловит, и тогда решайте. Сделать её обязательной с первого дня, пока ей ещё никто не доверяет, — верный способ добиться того, что полезную проверку отключат.

Практическое замечание, если ваши превью живут на площадке вроде Vercel или Netlify: у каждого хостинга свои правила именования URL превью, поэтому интеграция принимает шаблон, а не фиксированный адрес, и подставляет ветку или пул-реквест при каждом запуске.

Как это соотносится с вашими текущими тестами

Это поведенческая проверка работающего продукта, поэтому она дополняет быстрые локальные юнит-тесты, а не заменяет их. Оставьте юнит-тесты для логики, а этой проверке отдайте то, что они по своей природе увидеть не могут: работает ли функциональность, когда ею пользуется реальный человек.

Замечание о более общей закономерности

Всё это не специфично для Cursor. Тот же разрыв возникает с любым ассистентом, который пишет код быстрее, чем человек успевает его читать, — поэтому шаг проверки стоит выстроить один раз и переиспользовать во всех редакторах. В открытом рейтинге, где агенты собирали одно и то же приложение, самая дешёвая модель из всех выдала наиболее корректный результат именно с таким циклом проверки — вдвое дешевле самого дорогого участника. Вывод не в том, что одна модель лучше другой. Вывод в том, что модель с работающим сигналом обратной связи обыгрывает более сильную модель без него.

Где TestSprite встраивается в цикл работы с Cursor

TestSprite — это половина, отвечающая за проверку. Cursor пишет изменение; TestSprite открывает развёрнутое приложение, проходит сценарий так, как это сделал бы пользователь, и сообщает, что произошло на самом деле. Ни один не пытается делать работу другого, и в этом разделении весь смысл: автор изменения — неподходящая сторона для его подтверждения.

Для команды, которая выкатывает код, написанный Cursor, отсюда следует три вещи. Покрытие перестаёт зависеть от того, найдёт ли кто-то время писать тесты: кейсы генерируются по вашему продукту и уточняются обычным языком. Косметические редизайны перестают красить набор тестов в красный, потому что шаг — это намерение, а не путь по DOM. А падение возвращается единым пакетом, с которым агент может работать, так что следующая итерация начинается с фактов, а не с вашего их пересказа.

В первый же день вы получаете сценарии, сломать которые было бы стыдно: они прогоняются на каждом пул-реквесте, а результат называет конкретное расхождение. Чего сервис не делает — так это не проверяет ваш код и не заменяет быстрые локальные юнит-тесты; он работает рядом с тем и другим.

Почему тесты, написанные Cursor, проходят, хотя функциональность сломана?

Потому что тесты и код выросли из одних и тех же предположений. Если агент решил, что эндпоинт возвращает пустой массив, и написал вокруг этого и обработчик, и тест, они согласуются друг с другом. Разрешить спор может только запуск настоящего приложения против настоящего сервиса.

Нужна ли команда QA, чтобы это настроить?

Нет. Вся настройка — это одна команда CLI и установка GitHub App. Всё рассчитано на команды, где результаты тестов смотрят только разработчики.

Нужен ли доступ к моему исходному коду?

Проверка идёт по развёрнутому приложению через его интерфейс. Право на запись в GitHub нужно для того, чтобы публиковать результаты в пул-реквестах и коммитах, а не чтобы пушить код или менять файлы workflow.

Что будет с тестами, если интерфейс изменили намеренно?

Тест, привязанный к бизнес-сценарию, а не к конкретному элементу, переживает редизайн, который сам сценарий не меняет. Если же меняется сам сценарий, это новое поведение: нужен новый или обновлённый кейс, и агент может сгенерировать его по изменению.

Можно ли запускать это на ветке, не затрагивая основной набор тестов?

Тесты принадлежат приложению, а не ветке в git, поэтому набор всегда один, канонический. Выберите триггер по пул-реквесту, если нужны проверки по веткам, и триггер по push, если нужно проверять одно общее окружение после каждого слияния.

Коротко

Хватит читать дифф. Запустите приложение.

Баги Cursor прячутся в состоянии, таймингах и на границах интеграций — ровно там, куда не дотягиваются ни чтение кода глазами, ни тест на моках. Отдайте проверку в руки агента, направьте её на развёрнутую сборку и пусть пул-реквест сам скажет вам, работает ли изменение, ещё до того, как кто-то его вольёт.