Три теста написать легко. Пятьдесят — другой разговор. Без структуры e2e-набор быстро превращается в кашу из скопированного кода: в каждом тесте одинаковый логин, одни и те же локаторы разбросаны по десяткам файлов, а одна смена вёрстки ломает половину тестов сразу. Playwright решает это двумя инструментами — fixtures и page object model.
Изоляция по умолчанию
Сначала важный фундамент: каждый тест в Playwright работает в своём изолированном контексте браузера. Встроенная фикстура page — это свежие cookies, пустое хранилище, никаких следов от предыдущего теста. Тесты не должны зависеть друг от друга.
Это важно, потому что зависимый тест — ненадёжный тест. Если тест «создать заказ» работает только после теста «войти в систему», любой сбой в первом ломает весь хвост. Изоляция — главное, что держит набор стабильным.
Изоляция в браузере работает автоматически. Внешнее состояние (база данных, файлы) — ваша ответственность; о нём отдельно в статье данные и аутентификация.
Дублирование setup: откуда берётся и как победить
Типичная картина в молодом e2e-наборе:
test("видит свои заказы", async ({ page }) => {
await page.goto("/login");
await page.getByLabel("Email").fill("user@example.com");
await page.getByLabel("Пароль").fill("secret");
await page.getByRole("button", { name: "Войти" }).click();
// ... теперь сам тест
});
test("может изменить профиль", async ({ page }) => {
await page.goto("/login");
await page.getByLabel("Email").fill("user@example.com");
await page.getByLabel("Пароль").fill("secret");
await page.getByRole("button", { name: "Войти" }).click();
// ... теперь сам тест
});
Логин одинаковый — в каждом тесте. Это плохо: при смене формы авторизации придётся менять в десятках мест. Для этого существуют кастомные fixtures.
Fixtures: выносим общий setup один раз
Fixture (фикстура) — это именованный кусок setup-кода, который Playwright запускает перед тестом (и завершает после). Встроенных фикстур несколько: page, browser, context. Свои создают через test.extend.
import { test as base, type Page } from "@playwright/test";
type Fixtures = { authedPage: Page };
export const test = base.extend<Fixtures>({
authedPage: async ({ page }, use) => {
await page.goto("/login");
await page.getByLabel("Email").fill("user@example.com");
await page.getByLabel("Пароль").fill("secret");
await page.getByRole("button", { name: "Войти" }).click();
await use(page); // <-- здесь запускается тест
// после use() — место для очистки, если нужна
},
});
Теперь тесты выглядят так:
test("видит свои заказы", async ({ authedPage }) => {
await authedPage.goto("/orders");
await expect(authedPage.getByText("Мои заказы")).toBeVisible();
});
test("может изменить профиль", async ({ authedPage }) => {
await authedPage.goto("/profile");
await expect(authedPage.getByLabel("Имя")).toBeVisible();
});
Логин описан один раз, тесты читаются чисто. Меняется форма логина — правите в одном месте.
Фикстуры можно составлять: одна фикстура может зависеть от другой. Playwright разрешает зависимости сам.
Авторизация через storageState
Вход через настоящий UI в каждом тесте — медленно. Быстрее войти один раз, сохранить состояние браузера в файл (storageState) и восстанавливать его в тестах без нового логина. Это отдельная тема, подробно — в данные и аутентификация.
Page object model: прячем локаторы за понятными методами
Другая проблема роста набора: локаторы расползаются по всем тестам. Изменился data-testid кнопки — ищите по всем файлам.
Page object — это класс, который описывает одну страницу (или её часть): какие элементы на ней есть и какие действия можно совершить. Тест работает с методами класса, а не с локаторами напрямую.
class CheckoutPage {
constructor(private readonly page: Page) {}
async open() {
await this.page.goto("/checkout");
}
async fillCard(number: string) {
await this.page.getByLabel("Номер карты").fill(number);
}
async submit() {
await this.page.getByRole("button", { name: "Оплатить" }).click();
}
get successMessage() {
return this.page.getByText("Заказ оформлен");
}
}
test("оформляет заказ", async ({ page }) => {
const checkout = new CheckoutPage(page);
await checkout.open();
await checkout.fillCard("4242 4242 4242 4242");
await checkout.submit();
await expect(checkout.successMessage).toBeVisible();
});
Тест читается как сценарий, а не как набор технических команд. Изменилась вёрстка страницы оплаты — правите CheckoutPage, остальные тесты не трогаете.
Когда вводить fixtures, когда page objects
Не нужно строить всё сразу. Хороший ориентир:
Fixtures — когда один и тот же setup встречается в нескольких тестах. Даже двух хватит, чтобы вынести.
Page objects — когда несколько тестов работают с одной страницей и локаторы начинают повторяться.
Для первых одного-двух тестов обе абстракции могут быть лишними. Начинайте просто, добавляйте структуру по мере роста — не наоборот.
Коротко
- Каждый тест в Playwright запускается в изолированном контексте браузера — тесты не должны зависеть друг от друга.
- Кастомные fixtures создают через
test.extend: код доuse()— подготовка, после — очистка. - Fixtures убирают дублирование setup: логин, создание данных, любая повторяющаяся подготовка — один раз, не в каждом тесте.
- Page object прячет локаторы и действия страницы за понятными методами; тест читается как сценарий.
- Меняется вёрстка — правите page object, а не десятки тестов.
- Вводите структуру по мере роста набора, не авансом.
Что почитать дальше
- Локаторы в Playwright — как правильно находить элементы.
- Данные и аутентификация — storageState и управление внешним состоянием.
- Утверждения и ожидание — как делать проверки устойчивыми.