К списку уроков
Блок 7 · ИИ-агент-автоматизатор

Урок 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 решают одну задачу: не оставлять данные и ресурсы после упавшей проверки. Выбирайте вариант по структуре теста, а не смешивайте оба без необходимости.

Как мы выносили код в хелпер

  1. Нашли повторяющиеся UI-шаги создания пользователя и очистки.
  2. Отделили проверяемое поведение от подготовки данных.
  3. Зафиксировали контракт POST и DELETE: адрес, тело, статусы и cookie.
  4. Описали типы входных данных и ответа.
  5. Перенесли API-вызовы в маленькие функции с понятными именами.
  6. Заменили подготовительные UI-шаги в тестах вызовом registerUserViaApi.
  7. Оставили registerUser для сценариев, которые проверяют регистрацию через интерфейс.
  8. Добавили гарантированную очистку в finally или afterEach.
  9. Запустили линтер, целевой тест и повторный прогон.

Такой рефакторинг лучше делать небольшими шагами. После каждого шага проще увидеть, где изменилось поведение, и откатить только неудачную часть.

Из правил проекта — в локального агента

После того как хелперы и тесты заработали, мы перенесли проверенные договорённости в .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 — это страховочная сетка, а не замена проверки поведения. Зелёный линтер подтверждает соблюдение части правил кода, но не доказывает, что сценарий тестирует нужное бизнес-требование.

Как установить файлы агента

  1. Скачайте архив из блока ниже.
  2. Распакуйте его в корень pomidorqa-course-tests с сохранением скрытой папки .cursor.
  3. Проверьте, что появился файл .cursor/skills/ai-avtomatizator/SKILL.md.
  4. Откройте PROJECT.md внутри Skill и сверяйте его с актуальной структурой своего проекта.
  5. Попросите агента изменить конкретный тест, затем обязательно прочитайте 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 доказательством правильного поведения.
  • Маскировать нестабильность ожиданиями по времени или повторами упавшего теста.

Файлы урока

Ключевые тезисы для теста

  1. API Arrange ускоряет подготовку данных, если UI регистрации не является предметом проверки.
  2. HTTP-метод — часть контракта: POST создаёт аккаунт, DELETE удаляет его.
  3. Сессионная cookie живёт в контексте, поэтому создание, браузерные шаги и удаление должны использовать связанный контекст.
  4. Хелпер прячет повторяемую техническую механику, но не смысл сценария и его assertions.
  5. finally и afterEach гарантируют очистку после успешного и упавшего теста.
  6. Skill хранит рабочий процесс специализированного агента, а PROJECT.md — карту конкретного проекта.
  7. Готовность теста подтверждается не обещанием агента, а измеримыми проверками: lint и стабильный повторный прогон.

Полезные ссылки

Домашнее задание

Индивидуальная проверка ДЗ — на Boosty

Конспект, видео и тест открыты всем. Текст задания и проверка работы — по подписке.

Индивидуальная проверка ДЗ на Boosty
База для QA — вопросы на собеседование, тренажёры и материалы