Параметризация тестов
Урок: Параметризация тестов Playwright
Введение
Представь, что ты тестируешь форму логина на сайте. У тебя есть несколько сценариев: правильный логин и пароль, неправильный пароль, пустые поля, слишком короткий пароль. Самый простой путь — написать отдельный тест под каждый случай.
Но довольно быстро ты замечаешь проблему: тесты почти одинаковые. Отличаются только входные данные и ожидаемый результат. Код начинает дублироваться, становится длинным и сложным в поддержке.
Теперь представь, что ты можешь описать сам сценарий один раз, а набор входных данных просто «подставлять» в него. Это и есть параметризация.
В Playwright параметризация позволяет запускать один и тот же тест с разными данными. Это делает тесты короче, понятнее и гораздо удобнее в поддержке.
Давай разберёмся, как это работает.
Официальное руководство: Parameterize tests | Playwright. Там же — как не сломать хуки при forEach и как параметризовать проекты в конфиге.
Что такое параметризация и зачем она нужна
Параметризация — это способ запускать один тест с разными входными данными.
Вместо того чтобы писать так:
test('логин с валидными данными', async ({ page }) => {
// ...
});
test('логин с неверным паролем', async ({ page }) => {
// ...
});
test('логин с пустыми полями', async ({ page }) => {
// ...
});
мы можем описать один тест и передать в него разные наборы данных.
В документации выделяют два уровня:
- Параметризация тестов — несколько кейсов в одном файле (часто массив +
forEach/ цикл при регистрации тестов). - Параметризация проектов — одни и те же тесты прогоняются в разных проектах
playwright.configс разнымиuse/ опциями (как отдельные «конфигурации»).
Главная идея: мы отделяем логику теста от данных для теста.
Это даёт сразу несколько преимуществ:
- меньше дублирования кода;
- легче добавлять новые кейсы;
- тесты читаются как таблица сценариев;
- проще поддерживать при изменениях.
Простейшая параметризация через массив данных
Самый базовый способ — использовать массив и forEach.
Пример
import { test, expect } from '@playwright/test';
const testData = [
{ login: 'user1', password: 'correct', success: true },
{ login: 'user1', password: 'wrong', success: false },
{ login: '', password: '', success: false },
];
testData.forEach(({ login, password, success }) => {
test(`логин: ${login}, пароль: ${password}`, async ({ page }) => {
await page.goto('https://example.com/login');
await page.getByLabel('Логин').fill(login);
await page.getByLabel('Пароль').fill(password);
await page.getByRole('button', { name: 'Войти' }).click();
if (success) {
await expect(page).toHaveURL(/dashboard/);
} else {
await expect(page.getByRole('alert')).toBeVisible();
}
});
});
Что здесь происходит
- Мы создаём массив
testDataс разными сценариями. - Каждый объект — это отдельный тест-кейс.
forEachпроходит по массиву и создаёт тест для каждого набора данных.- Внутри теста используются значения из объекта.
Важный момент
test(`логин: ${login}, пароль: ${password}`, ...)
Название теста формируется динамически. Это помогает в отчётах — ты сразу видишь, какой кейс упал.
Как это работает внутри
Когда Playwright видит такой код:
testData.forEach(...)
он не выполняет тесты сразу. Он регистрирует тесты во время загрузки файла.
То есть:
- Node.js выполняет
forEach; - на каждой итерации создаётся новый
test(...); - Playwright сохраняет их как отдельные тесты.
В итоге ты получаешь несколько независимых тестов, а не один тест с циклом.
Это важно: каждый тест из параметризации — полноценный тест со своей изоляцией.
Хуки и forEach: где объявлять
Из документации:
beforeEach/afterEach/beforeAll/afterAll, которые должны выполниться один раз на файл для всех параметров, объявляют снаружиforEach. Иначе при регистрации хука внутри цикла он продублируется на каждую итерацию.- Если нужен свой
beforeEachна каждый набор данных (например, разныйgotoсnameв URL), оборачивают итерацию вtest.describe+ хуки внутри — см. второй пример в доке.
Улучшение читаемости: описание тест-кейсов
Когда данных становится больше, полезно делать их более выразительными.
Пример
const testData = [
{
name: 'валидный логин',
login: 'user1',
password: 'correct',
success: true,
},
{
name: 'неверный пароль',
login: 'user1',
password: 'wrong',
success: false,
},
];
Используем это в тесте:
testData.forEach(({ name, login, password, success }) => {
test(name, async ({ page }) => {
await page.goto('https://example.com/login');
await page.getByLabel('Логин').fill(login);
await page.getByLabel('Пароль').fill(password);
await page.getByRole('button', { name: 'Войти' }).click();
if (success) {
await expect(page).toHaveURL(/dashboard/);
} else {
await expect(page.getByRole('alert')).toBeVisible();
}
});
});
Почему это лучше
- Названия тестов становятся понятными;
- не нужно читать данные внутри теста, чтобы понять сценарий;
- отчёты становятся удобнее.
Параметризация с test.describe
Иногда нужно сгруппировать параметризованные тесты.
Пример
test.describe('Тесты логина', () => {
const testData = [
{ login: 'user1', password: 'correct', success: true },
{ login: 'user1', password: 'wrong', success: false },
];
testData.forEach(({ login, password, success }) => {
test(`логин: ${login}, пароль: ${password}`, async ({ page }) => {
await page.goto('https://example.com/login');
await page.getByLabel('Логин').fill(login);
await page.getByLabel('Пароль').fill(password);
await page.getByRole('button', { name: 'Войти' }).click();
if (success) {
await expect(page).toHaveURL(/dashboard/);
} else {
await expect(page.getByRole('alert')).toBeVisible();
}
});
});
});
Что даёт test.describe
- объединяет тесты в логическую группу;
- улучшает структуру отчётов;
- позволяет применять общие настройки (например,
beforeEach).
Параметризация на уровне проектов (playwright.config)
Один сценарий можно прогнать несколько раз с разными опциями, не дублируя файлы: вынеси параметр в кастомную опцию фикстур (test.extend с { option: true }) и задай разные значения в projects[].use в конфиге. В тесте параметр приходит как обычная фикстура рядом с page.
Идея на пальцах (полный код — в Parameterized Projects):
// my-test.js — один раз расширяешь test
import { test as base } from '@playwright/test';
export const test = base.extend({
person: ['Гость', { option: true }],
});
// example.spec.js
import { test } from './my-test';
test('приветствие', async ({ page, person }) => {
await page.goto(`/greet?name=${person}`);
await expect(page.getByRole('heading')).toContainText(person);
});
// playwright.config.js — два проекта = два значения person
import { defineConfig } from '@playwright/test';
export default defineConfig({
projects: [
{ name: 'alice', use: { person: 'Alice' } },
{ name: 'bob', use: { person: 'Bob' } },
],
});
Поведение кастомных опций в проектах менялось в 1.18 — см. release notes, если поддерживаешь старые конфиги.
Переменные окружения и .env
Данные и секреты удобно подставлять из окружения (process.env.USER_NAME в тесте или в defineConfig), а в CI — через USER_NAME=… npx playwright test. Для локальной разработки часто подключают dotenv в playwright.config и файл .env — см. Passing Environment Variables.
Генерация тестов из CSV
Раннер выполняется в Node.js, поэтому можно прочитать CSV с диска, распарсить (csv-parse и т.п.) и в цикле вызвать test('…', …) — пример в Create tests via a CSV file. Имена тестов должны оставаться уникальными.
Когда параметризация особенно полезна
Параметризация особенно хорошо работает в ситуациях:
- формы (логин, регистрация, фильтры);
- API-тесты с разными входными данными;
- проверка валидаций;
- тестирование ролей пользователей;
- тесты на разные конфигурации (браузеры, языки, регионы).
Например, если нужно проверить 10 вариантов неправильного ввода — параметризация превращает это из 10 тестов в один аккуратный блок.
Ограничения и важные нюансы
Параметризация — мощный инструмент, но важно понимать границы:
- Не стоит превращать один тест в «комбайн» на 50 кейсов — отчёты станут неудобными.
- Если логика тестов сильно различается — лучше разделить их.
- Данные должны быть простыми и понятными — иначе параметризация теряет смысл.
Главное правило: параметризуем только то, что действительно одинаково по логике.
Практический смысл
В реальной работе параметризация:
- ускоряет написание тестов;
- снижает количество ошибок из-за копипаста;
- упрощает поддержку при изменении UI или API;
- делает тесты более декларативными — ты описываешь сценарии, а не переписываешь код.
В командах это особенно важно: другие разработчики и тестировщики быстрее понимают тесты.
Итоговое понимание
Параметризация — это способ отделить данные от одинаковой логики и не плодить копипаст.
- на уровне файла — массив +
forEachпри регистрации тестов, уникальные имена, продуманные хуки; - на уровне конфига — проекты и кастомные опции фикстур;
- снаружи — переменные окружения / CSV для больших таблиц кейсов.
Если сценарии начинают расходиться по веткам — не насилуй одну мега-функцию: разнеси по тестам или файлам. Тогда параметризация остаётся читаемой и устойчивой в отчётах.