К списку уроков
Блок 4 · Playwright — AAA-структура теста

Урок 7. Arrange — подготовка перед автотестом

Независимость тестов

Темы урока

AAA как тест-кейс в коде, что класть в Arrange и что нельзя, фабрика vs хелпер vs фикстура, beforeEach / beforeAll / afterEach, независимость и атомарность, антипаттерны общего аккаунта

Видео урока

Пройти тест по уроку

Конспект урока

Конспект — Урок 7: Один упал — остальные красные. Как так?

Главное за урок

AAA пришёл из юнит-тестов: Arrange (сцена) → Act (шаги) → Assert (ожидаемый). Это тот же тест-кейс, только в коде: предусловия / шаги / ожидаемый результат.

Первая A нужна, чтобы Act не врал. Без своего мира тест проверяет «как получилось»: общий аккаунт, чужой навык, протухшая дата, кука с прошлого прогона.

Свой пользователь на каждый тест — через beforeEach. beforeAll с одним аккаунтом на файл связывает пачку: «у меня локально прошло».

AAA = тест-кейс в коде

Концепцию сформулировали разработчики для юнит-тестов. На собеседованиях спрашивают.

Буква В тест-кейсе В автотесте
Arrange Предусловия Данные, навигация, регистрация, логин
Act Шаги click / fill / отправка — Урок 8
Assert Ожидаемый результат expect — Урок 9

booking-flow из прошлых уроков — учебный флоу «пройти путь глазами». Для независимости удобнее несколько коротких test() с одной мыслью каждый.

История с эфира: один стендовый аккаунт на двух локалях. Каждый тест один — зелёный. Друг за другом — красный: первая локаль прибила куку, вторая не логинилась. Виновата не «флаковость Playwright», а общая сцена.


Что класть в Arrange — и что нельзя

Arrange готовит сцену до проверяемого действия.

Можно

Тип Примеры на PomidorQA
Данные Имя, email, пароль, дата слота — из фабрики
Навигация goto на регистрацию, затем на /pomidorqa/profile
Предусловия Зарегистрирован, залогинен, навыков ещё нет
Контекст браузера Свой page на тест (Playwright даёт сам)

Данные должны переживать повторный прогон: уникальный email, живая дата (Date.now() + смещение), а не user@test.com и не 2026-08-17.

Пароль в учебном стенде можно оставить статичным (testpass123): на регистрацию он не претендует. Email — нет: второй прогон с тем же адресом не зарегистрируется.

Нельзя

Что Почему
Само проверяемое действие Хук добавил навык — тест «навык добавился» проверяет хук
expect про фичу Даже «очень хочется сразу»: это Assert
Чужие / вечные данные «В каталоге уже кто-то с Playwright»
Контракт на порядок тестов Тест B живёт, только если A уже отработал
Общий аккаунт в beforeAll Первый тест пачкает профиль — второй красный

Регистрация в хелпере — серая зона. Если тест про регистрацию, шаги остаются в Act. Если тест про профиль после регистрации — регистрация это предусловие, её выносят. expect URL после регистрации в хелпере на курсе пока оставляем как дымовую страховку; по-хорошему проверка регистрации живёт в отдельном тесте.

UI-регистрация в каждом тесте медленная. На проде её часто уносят на API-слой (дернули ручку — получили пользователя, браузер не открывали). Пока у нас UI — нормально для курса. Суть та же: подготовка не должна быть тем, что вы утверждаете.


Фабрики, хелперы, датасеты

Три роли, которые на эфире путали.

Фабрика — на вход параметры, на выход готовый объект. Ничего не кликает.

function makeUser(role: string, runId: number): TestUser {
  return {
    name: `${role} Автотест`,
    email: `${role}-${runId}@example.com`,
    password: "testpass123",
  };
}

Какие бывают:

Вид Что возвращает Пример
Фабрика данных Объект тестовых данных makeUser, позже makeSlot с датой «завтра»
Фабрика локаторов Locator (page) => page.getByLabel("Имя")
Фабрика сущности через API Созданный на бэке объект Регистрация ручкой, не формой — позже

Хелпер — выполняет действие. registerUser(page, user) не создаёт объект пользователя (это сделала фабрика), он проводит его по UI.

Датасет — набор готовых данных в отдельной папке/файле (JSON, фикстуры данных). Удобно, когда вариаций много. Мина та же, что у хардкода: статичный email и протухшая дата. Датасеты с уникальными полями из фабрики — ок; вечный qa@test.com на весь набор — нет.

Хардкод не запрещён навсегда. Запрещено хардкодить то, что должно быть уникальным между прогонами и параллельными воркерами.


Фикстуры: хук vs { page }

На эфире «фикстурой» называли подготовку в beforeEach. В Playwright слово fixture ещё и официальное.

{ page } в сигнатуре теста — это встроенная фикстура: Playwright сам открыл браузер, отдал страницу, после теста закрыл. Вы её не создаёте руками.

Хук (beforeEach) Фикстура Playwright
Как выглядит test.beforeEach(async ({ page }) => { ... }) async ({ page, user }) => { ... }
Что даёт Побочный эффект «сделай до теста» Значение, которое впрыскивают в тест
Уборка afterEach Код после use(...) — сам
Когда Учебный минимум, ДЗ-7 Свои фикстуры — когда один и тот же Arrange копируют в десять файлов

Встроенные фикстуры, которые уже есть: page, context, browser, request.

Своя фикстура (знать, не писать в ДЗ-7):

import { test as base } from "@playwright/test";

export const test = base.extend<{ user: TestUser }>({
  user: async ({ page }, use) => {
    const user = makeUser("student", Date.now());
    await registerUser(page, user);
    await use(user);
    // сюда — teardown: удалить аккаунт, если появится ручка
  },
});

Два скоупа:

Скоуп Когда живёт Куда класть
test (по умолчанию) Один тест Пользователь, логин, грязные данные
worker На весь воркер Тяжёлое неизменяемое: один раз поднять мок-сервер

Правило то же, что у Arrange: в фикстуру — сцена. Не клик «Добавить навык», если тест как раз про добавление.


Хуки: beforeEach, beforeAll, afterEach

Хук — код, который раннер вызывает до или после теста. Не путать с хелпером: хелпер вы вызываете сами.

Хук Когда Куда на курсе
beforeEach Перед каждым test() Свой makeUser + регистрация + goto профиля
beforeAll Один раз на файл / describe Осторожно. Ок для неизменяемого. Опасно для аккаунта
afterEach После каждого теста Подместись: удалить аккаунт, закрыть лишнее
afterAll Один раз в конце Редко; та же ловушка «общее состояние»

page.close() в afterEach на курсе — учебный жест. Playwright и так изолирует page между тестами. Полезный teardown — удалить созданного пользователя, чтобы не засирать общий стенд. Ручки удаления пока нет — куратор чистит базу руками.

На одном из проектов с эфира beforeAll запретили контрактом: экспериментальные куки на весь сьют ломали чужие тесты, дебаг шёл часами. Это не «beforeAll всегда зло». На собеседовании лучше сказать: опасен для мутабельных данных и скрытых прекондишенов на тысяче строк, уместен для общего неизменяемого сетапа.


Независимость и атомарность

Независимость. Тест можно запустить один — зелёный. Порядок в файле не контракт. Общий пользователь, общий слот, «сначала тот тест» — связь, не экономия.

Классика с эфира: тест A добавляет навык, тест B ждёт пустой can-help-skills. B один — зелёный. A+B — красный.

Атомарность. Один test() — одна мысль: имя на месте или навыков нет или слотов нет. Простыня на 15 шагов: упал шаг 3 — хвост даже не бежит, причина тонет.


Антипаттерны Arrange

  1. Общий аккаунт и порядок как контракт. beforeAll + один registerUser на файл.
  2. Arrange спрятан внутри Act. Регистрация и goto посреди кликов — через неделю не видно, где сцена, где шаг.
  3. Хук уже делает Act. beforeEach жмёт «Добавить навык», тест проверяет чип.
  4. Хардкод уникального. Email, дата слота, имя хоста из каталога.
  5. Простыня. Один огромный тест вместо трёх коротких миров.

Пример структуры нового файла автотеста

Новый spec — не простыня booking-flow. Каркас: типы → фабрика → хелпер → describe → хук Arrange → короткие тесты.

ДЗ — новый файл tests/e2e/arrange-practice.spec.ts. booking-flow не трогаем. Ниже — схема, не готовое решение.

import { test, expect, type Page } from "@playwright/test";

type TestUser = {
  // поля тестового пользователя
};

function makeUser(/* роль, уникальный id */): TestUser {
  // фабрика: вернуть уникальные данные, без хардкода email
}

async function registerUser(page: Page, user: TestUser) {
  // хелпер подготовки: регистрация
  // сюда не класть проверяемое действие теста
}

test.describe("свой мир на каждый тест", () => {
  let user: TestUser;

  test.beforeEach(async ({ page }) => {
    // Arrange:
    // 1) новый пользователь из фабрики
    // 2) регистрация через хелпер
    // 3) переход на страницу, где будет проверка
  });

  test("мир 1: ...", async ({ page }) => {
    // Assert: одна мысль про сцену
  });

  test("мир 2: ...", async ({ page }) => {
    // Assert: другая мысль на той же сцене
  });
});

Прогони тесты по одному и пачкой. Порядок не должен влиять.


Как подойти к домашке

  1. Ветка hw7-<github-username>. booking-flow.spec.ts не трогать.
  2. Новый файл tests/e2e/arrange-practice.spec.ts по схеме выше: фабрика, хелпер, beforeEach, два коротких теста.
  3. Не хардкодь email / имя / дату. Не добавляй навык — это Act Урока 8.
  4. Оба теста зелёные по одному и вместе.

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

  • AAA — данные / действие / проверка; это предусловия / шаги / ожидаемый из тест-кейса.
  • Arrange — сцена до клика. Проверяемое действие и expect про фичу туда не кладут.
  • Фабрика возвращает объект; хелпер выполняет шаг; хук вызывает подготовку до теста.
  • { page } — встроенная фикстура Playwright, не «просто аргумент».
  • beforeEach — свой мир на тест; beforeAll с мутабельным аккаунтом связывает тесты.
  • Хардкод email/даты даёт ложнозелёный первый прогон и красный второй.
  • Один упал — остальные красные: чаще общий стейт, не баг продукта.
  • Teardown (afterEach / код после use в фикстуре) возвращает окружение к чистому состоянию; закрытие page раннер делает сам.

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

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

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

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