Урок 14. Агент пишет тесты сам
API-хелперы, Skill и автоматические проверки
Темы урока
API Arrange и тестовые ручки POST/DELETE, сессионная cookie, вынос подготовки и очистки в helper, уникальные данные, Skill и PROJECT.md, hooks, lint и десятикратный прогон
Видео урока
Конспект урока
Главное за урок
В этом уроке мы прошли путь от повторяющегося кода в тестах до локального AI-агента, который пишет тесты по правилам конкретного проекта. Сначала вместе с ИИ вынесли подготовку и удаление пользователей в API-хелперы, проверили результат, а уже затем зафиксировали рабочие договорённости в Skill и добавили автоматическую проверку перед отправкой кода.
Главная мысль: агент становится полезным не из-за длинного промпта, а когда получает понятные источники истины, границы ответственности и измеримые критерии готовности.
Зачем подготовку данных выносить из UI
Если сценарий проверяет поиск, бронирование или профиль, регистрация пользователя через браузер — это всего лишь подготовка данных. Она:
- удлиняет тест;
- добавляет лишние UI-шаги и локаторы;
- делает падение менее понятным;
- повышает риск флаков.
Для такой подготовки лучше использовать API. Но если цель теста — проверить саму форму регистрации, её валидацию или редирект после отправки, UI убирать нельзя: в этом случае регистрация и есть проверяемое поведение.
Удобное правило:
API создаёт предусловия, UI проверяет пользовательский сценарий.
Контракт тестовых ручек PomidorQA
На эфире мы использовали один служебный адрес:
/api/pomidorqa/test/accounts
Метод определяет действие:
| Задача | Метод | Тело запроса | Успешный статус | Результат |
|---|---|---|---|---|
| Создать пользователя | POST |
{ name, email, password } |
201 |
Возвращает { id, name, email } и сохраняет сессионную cookie |
| Удалить пользователя | DELETE |
Не требуется | 200 |
Удаляет текущий тестовый аккаунт и связанные с ним данные |
Ручка принимает только тестовые email. Фабрика makeUser создаёт адреса на @example.com, поэтому случайно удалить обычного пользователя через неё нельзя.
При удалении сервер определяет аккаунт по сессионной cookie. Поэтому DELETE нужно отправлять через тот же APIRequestContext, которым выполнялся POST. Передавать id или email в запрос удаления не нужно.
Один контекст — одна сессия
В Playwright у каждого BrowserContext есть связанный context.request. Cookies этого API-контекста и браузерной сессии общие. Это позволяет создать пользователя через API, а затем открыть страницу уже авторизованным пользователем:
const context = await browser.newContext();
const page = await context.newPage();
const user = makeUser("host", Date.now());
await registerUserViaApi(context.request, user);
await page.goto("/pomidorqa");
Для удаления передаём тот же контекст:
await deleteUserViaApi(context.request);
Если зарегистрировать пользователя через отдельный глобальный request, а страницу открыть в новом браузерном контексте, авторизация сама туда не попадёт. Если вызвать DELETE из другого контекста, сервер не увидит нужную cookie и не поймёт, какой аккаунт удалять.
Что вынесли в user helper
В tests/helpers/user.ts собраны операции, которые не являются смыслом отдельного тестового сценария:
- типы
TestUserиRegisteredParticipant; - фабрика уникальных данных
makeUser; - UI-регистрация
registerUserдля тестов самой формы; - API-регистрация
registerUserViaApiдля Arrange; - API-удаление
deleteUserViaApi; - общая очистка нескольких контекстов
cleanupUsersViaApi.
Уникальные данные
export function makeUser(role: string, runId: number): TestUser {
return {
name: `${role} Автотест`,
email: `${role}-${runId}@example.com`,
password: "testpass123",
};
}
Уникальный runId не даёт параллельным и повторным прогонам бороться за один email. Роль в имени помогает понять, кому принадлежали данные: например, host, guest или guest2.
Создание через API
export async function registerUserViaApi(
request: APIRequestContext,
user: TestUser
): Promise<RegisteredParticipant> {
const response = await request.post("/api/pomidorqa/test/accounts", {
data: user,
});
if (response.status() !== 201) {
throw new Error(
`Регистрация ${user.email} не удалась: ${response.status()} ${await response.text()}`
);
}
return response.json();
}
Хелпер проверяет HTTP-статус сразу на границе взаимодействия с API. Если подготовка не удалась, тест получает понятную ошибку с телом ответа, а не падает позже на отсутствующей карточке или кнопке.
Удаление через API
export async function deleteUserViaApi(
request: APIRequestContext
): Promise<void> {
const response = await request.delete("/api/pomidorqa/test/accounts");
if (response.status() !== 200) {
throw new Error(
`Удаление аккаунта не удалось: ${response.status()} ${await response.text()}`
);
}
}
Удаление каскадно очищает аккаунт, навыки, свободные слоты и бронирования. Благодаря этому следующий прогон начинает работу с чистого состояния.
Очистка должна сработать даже после падения
Если контекст создаётся внутри одного теста, используйте try/finally:
const context = await browser.newContext();
try {
const user = makeUser("host", Date.now());
await registerUserViaApi(context.request, user);
// Шаги и проверки сценария
} finally {
await deleteUserViaApi(context.request);
await context.close();
}
Если тест создаёт несколько контекстов или набор тестов хранит их в общем массиве, удобнее выполнять очистку в afterEach:
test.afterEach(async () => {
await cleanupUsersViaApi(contexts);
contexts = [];
});
afterEach и finally решают одну задачу: не оставлять данные и ресурсы после упавшей проверки. Выбирайте вариант по структуре теста, а не смешивайте оба без необходимости.
Как мы выносили код в хелпер
- Нашли повторяющиеся UI-шаги создания пользователя и очистки.
- Отделили проверяемое поведение от подготовки данных.
- Зафиксировали контракт
POSTиDELETE: адрес, тело, статусы и cookie. - Описали типы входных данных и ответа.
- Перенесли API-вызовы в маленькие функции с понятными именами.
- Заменили подготовительные UI-шаги в тестах вызовом
registerUserViaApi. - Оставили
registerUserдля сценариев, которые проверяют регистрацию через интерфейс. - Добавили гарантированную очистку в
finallyилиafterEach. - Запустили линтер, целевой тест и повторный прогон.
Такой рефакторинг лучше делать небольшими шагами. После каждого шага проще увидеть, где изменилось поведение, и откатить только неудачную часть.
Из правил проекта — в локального агента
После того как хелперы и тесты заработали, мы перенесли проверенные договорённости в .cursor/skills/ai-avtomatizator/SKILL.md. Это важно: сначала получаем рабочий пример и понимаем правила, затем учим им агента.
В проекте файлы выполняют разные роли:
| Файл | Зачем нужен |
|---|---|
CODEX.md |
Общие обязательные правила репозитория |
SKILL.md |
Когда запускать специализированного агента и по какому процессу он работает |
PROJECT.md |
Карта тестового проекта: папки, готовые хелперы, Page Object и команды |
.cursor/hooks.json |
Подключение автоматических действий к событиям |
pre-push-check.mjs |
Проверка, которая запускается перед отправкой изменений |
Наш агент должен:
- выбрать подходящий уровень теста: unit, API или E2E;
- сначала найти готовые helpers и Page Object;
- для E2E проверить локаторы по реальному DOM;
- не использовать
waitForTimeout,force,pauseиonly; - запустить линтер;
- прогнать изменённый тест 10 раз и считать любое падение проблемой.
Hook — это страховочная сетка, а не замена проверки поведения. Зелёный линтер подтверждает соблюдение части правил кода, но не доказывает, что сценарий тестирует нужное бизнес-требование.
Как установить файлы агента
- Скачайте архив из блока ниже.
- Распакуйте его в корень
pomidorqa-course-testsс сохранением скрытой папки.cursor. - Проверьте, что появился файл
.cursor/skills/ai-avtomatizator/SKILL.md. - Откройте
PROJECT.mdвнутри Skill и сверяйте его с актуальной структурой своего проекта. - Попросите агента изменить конкретный тест, затем обязательно прочитайте diff и выполните проверки сами.
Проверка результата
Минимальный gate для изменённого теста:
npx eslint <изменённые-файлы-в-tests>
npm run lint
npx playwright test --project=<unit|api|e2e> <путь-к-spec> --repeat-each=10
Если один из десяти прогонов упал, тест пока нельзя считать стабильным. Нужно найти причину, исправить её и начать серию из десяти прогонов заново, а не скрывать проблему через retry.
Частые ошибки
- Убирать UI-регистрацию из теста, который должен проверять именно регистрацию.
- Создавать пользователя одним
APIRequestContext, а удалять другим. - Проверять только
response.ok()и терять точное ожидание контракта201или200. - Генерировать один и тот же email для всех запусков.
- Очищать данные только в конце успешного сценария, а не в
finallyилиafterEach. - Копировать ответ ИИ, не читая diff и не понимая тест.
- Считать зелёный lint доказательством правильного поведения.
- Маскировать нестабильность ожиданиями по времени или повторами упавшего теста.
Файлы урока
Ключевые тезисы для теста
- API Arrange ускоряет подготовку данных, если UI регистрации не является предметом проверки.
- HTTP-метод — часть контракта:
POSTсоздаёт аккаунт,DELETEудаляет его. - Сессионная cookie живёт в контексте, поэтому создание, браузерные шаги и удаление должны использовать связанный контекст.
- Хелпер прячет повторяемую техническую механику, но не смысл сценария и его assertions.
finallyиafterEachгарантируют очистку после успешного и упавшего теста.- Skill хранит рабочий процесс специализированного агента, а
PROJECT.md— карту конкретного проекта. - Готовность теста подтверждается не обещанием агента, а измеримыми проверками: lint и стабильный повторный прогон.
Полезные ссылки
Домашнее задание
Индивидуальная проверка ДЗ — на Boosty
Конспект, видео и тест открыты всем. Текст задания и проверка работы — по подписке.
Индивидуальная проверка ДЗ на Boosty