Хуки (beforeEach, afterEach)
Урок: Хуки в Playwright
Введение
Представь, что ты каждый день начинаешь работу с одного и того же: включаешь компьютер, открываешь нужные программы, заходишь в рабочие сервисы.
И в конце дня делаешь примерно одно и то же: сохраняешь изменения, закрываешь приложения, выключаешь компьютер.
Теперь представь, что тебе приходится делать это вручную перед каждым действием. Это было бы неудобно и утомительно.
В тестах ситуация похожая. Часто перед каждым тестом нужно:
- открыть страницу;
- авторизоваться;
- подготовить данные.
А после теста:
- очистить состояние;
- закрыть ресурсы.
Чтобы не писать это в каждом тесте, в Playwright существуют хуки.
Хуки — это специальные функции, которые выполняются до или после тестов автоматически.
Официальная справка по API: test (beforeEach / afterEach / beforeAll / afterAll). Как хуки сочетаются с воркерами и параллельным запуском — в разделе Parallelism.
Что такое хуки
Хуки — это функции, которые выполняются в определённые моменты жизненного цикла теста.
В Playwright есть четыре основных хука:
test.beforeAll— один раз на процесс воркера перед всеми тестами в области, к которой привязан хук;test.afterAll— один раз на воркер после всех этих тестов;test.beforeEach— перед каждым тестом;test.afterEach— после каждого теста.
Если зарегистрировано несколько хуков одного типа, они выполняются в порядке регистрации. У каждого хука может быть необязательное имя (первая строка-аргумент) — так проще читать отчёты и трейсы.
Пример:
import { test } from '@playwright/test';
test.beforeEach('Стартовая страница', async ({ page }) => {
await page.goto('https://example.com');
});
test('пример', async ({ page }) => {
await page.getByRole('button', { name: 'Submit' }).click();
});
Здесь:
- перед каждым тестом автоматически открывается страница;
- сам тест становится короче и чище.
beforeEach — выполняется перед каждым тестом
Это самый часто используемый хук.
test.beforeEach('Стартовая страница', async ({ page }) => {
await page.goto('https://example.com');
});
Что происходит:
- перед каждым тестом открывается страница;
- не нужно дублировать этот код в каждом тесте.
Это удобно, когда:
- есть общий стартовый сценарий;
- нужно одинаковое состояние перед тестами.
afterEach — выполняется после каждого теста
Этот хук используется для очистки или логирования.
test.afterEach(async ({ page }) => {
console.log('Тест завершён');
});
Можно использовать для:
- очистки данных;
- логирования;
- сбора информации о тесте.
beforeAll — один раз на воркер перед тестами в группе
test.beforeAll(async () => {
console.log('Запуск тестового набора');
});
Важно из документации Playwright: beforeAll / afterAll выполняются один раз на worker process для тестов в своей области (файл или test.describe), а не «строго один раз на всю матрицу CI». При параллельных тестах в одном файле каждый тест может оказаться в своём воркере — тогда beforeAll отработает для каждого такого воркера отдельно.
Если воркер перезапускается (например, после падения теста с ретраями), хуки beforeAll / afterAll в новом воркере выполняются снова — см. workers and failures.
Типичные сценарии:
- тяжёлая подготовка, которую не хочется повторять перед каждым тестом (с осторожностью при параллелизме);
- общий ресурс в рамках одного воркера.
afterAll — один раз на воркер после тестов в группе
test.afterAll(async () => {
console.log('Все тесты завершены');
});
Используется для:
- освобождения ресурсов, созданных в
beforeAll; - завершения процессов;
- финального логирования.
Порядок нескольких afterAll — тоже по регистрации.
Область хуков: test.describe
Хуки, объявленные внутри test.describe('…', () => { … }), действуют только на тесты этой группы. Так можно разделить разный сетап для разных наборов кейсов — см. примеры с группами в Annotations.
test.describe('Корзина', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/cart');
});
test('пустая корзина', async ({ page }) => {
// ...
});
});
Параллельные тесты и общее состояние
Если в файле включён параллельный режим (test.describe.configure({ mode: 'parallel' }) или fullyParallel в конфиге), тесты из одного файла могут идти в разных воркерах. Тогда нельзя безопасно полагаться на общую глобальную переменную между тестами — каждый тест получает свой набор хуков и фикстур. Для зависимых друг от друга тестов в документации описан режим serial и пример с page, созданным в beforeAll — но его рекомендуют избегать, если можно сделать тесты изолированными.
test.info() в хуках
Внутри хука удобно смотреть метаданные текущего прогона:
test.afterEach(async ({ page }) => {
const info = test.info();
console.log(`Завершён тест: ${info.title}, статус: ${info.status}`);
});
Также доступны workerIndex, parallelIndex и др. — см. TestInfo.
Таймауты из хуков
Таймаут теста общий с телом теста и с beforeEach / afterEach. При необходимости его можно увеличить из хука:
test.beforeEach(async ({ page }, testInfo) => {
await page.goto('/app');
test.setTimeout(testInfo.timeout + 30_000);
});
Для beforeAll / afterAll таймаут задаётся отдельно для самого хука: test.setTimeout(60_000) внутри хука — см. test.setTimeout. В beforeAll / afterAll нельзя вызывать test.slow() — вместо этого вызывают test.setTimeout(...).
Пример полного сценария с хуками
import { test, expect } from '@playwright/test';
test.beforeEach(async ({ page }) => {
await page.goto('https://example.com/login');
});
test('логин', async ({ page }) => {
await page.getByLabel('Email').fill('test@mail.com');
await page.getByLabel('Пароль').fill('1234');
await page.getByRole('button', { name: 'Войти' }).click();
await expect(page.getByText('Добро пожаловать')).toBeVisible();
});
test('ошибка логина', async ({ page }) => {
await page.getByLabel('Email').fill('wrong@mail.com');
await page.getByLabel('Пароль').fill('wrong');
await page.getByRole('button', { name: 'Войти' }).click();
await expect(page.getByText('Ошибка')).toBeVisible();
});
Что здесь важно:
beforeEachубирает дублирование;- тесты становятся чище;
- логика сосредоточена в одном месте.
Почему хуки важны
Без хуков тесты выглядят так:
await page.goto('/login');
await page.goto('/login');
await page.goto('/login');
Повторение кода:
- усложняет поддержку;
- увеличивает вероятность ошибок;
- делает тесты длиннее.
С хуками:
- код централизован;
- изменения вносятся в одном месте;
- тесты проще читать.
Когда использовать разные хуки
Важно понимать разницу:
beforeEach— когда нужно одинаковое состояние перед каждым тестом;beforeAll— когда достаточно выполнить действие один раз;afterEach— когда нужно очистить после каждого теста;afterAll— когда нужно завершить всё в конце.
Например:
- авторизация →
beforeEach; - запуск сервера →
beforeAll.
Частые ошибки
Ошибка 1 — слишком много логики в хуках
test.beforeEach(async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('test');
await page.getByLabel('Пароль').fill('1234');
await page.getByRole('button', { name: 'Войти' }).click();
});
Иногда это делает тесты менее гибкими.
Ошибка 2 — скрытая логика
Когда тест не показывает, что происходит, потому что всё спрятано в хуках.
Важно сохранять баланс.
Где это используется на практике
Хуки используются почти в каждом проекте:
- подготовка данных;
- авторизация;
- открытие страниц;
- очистка состояния;
- логирование.
Особенно важны:
- в больших тестовых наборах;
- при CI/CD;
- при параллельных тестах.
Итоговое понимание
Хуки — это способ автоматизировать повторяющиеся действия вокруг тестов.
Ключевая идея в том, что они позволяют:
- убрать дублирование;
- сделать тесты чище;
- централизовать логику.
С точки зрения Playwright важно помнить: beforeAll / afterAll привязаны к воркеру, порядок нескольких хуков — по регистрации, область задаётся describe, а при параллельном запуске нужно не делить глобальное состояние между тестами без явной изоляции.
Хорошее использование хуков делает тесты понятными, короткими и удобными для поддержки. Хуки должны помогать читать тест, а не скрывать его смысл — подробности и краевые случаи см. в документации test и параллелизме.