Утро, кофе, открываешь GitLab, а CI красный. Классика. Лезешь в отчёт, а там портянка на 5 экранов и TimeoutError где-то в недрах клика по кнопке. Селектор нормальный, data-testid="checkout-submit". Чтобы понять, что именно сломалось (может отвалилась база или фронт не отрисовал кнопку, возможно, юзер тупит), нужно лезть в код теста и дебажить глазами.
Бесит нереально. Ты или твой коллега тратите время не на фикс багов, а на расследование.
Классический POM: Как мы жили раньше?
Обычно все начинается красиво. Вы пишете чистый Page Object.
import { Page } from '@playwright/test';
export class CartPage {
// Добавляем конструктор
constructor(private readonly page: Page) {}
async clickCheckout() {
// Атомарное действие. Никакой логики, только клик.
await this.page.getByTestId('checkout-submit').click();
}
}
В коде все красиво. Вызовы методов, чистота, порядок. А в отчете - ад.
Как по такому списку понять контекст за 5 секунд? Это нереально. Разработчик лезет в код теста, читает его весь, матерится, восстанавливает контекст в голове. Время уходит.
Почему просто пихать test.step в тесты - это путь в ад?
Сначала вам посоветуют: "Да просто используй test.step в коде, чего паришься?". Не делайте так. На 3-х тестах это прокатит. На 100 вы закопаете проект.
-
Копипаста убьет все. Цепочка "Авторизация -> Корзина" будет в большинстве файлов. Поменялась логика логина? Поздравляю, нужно править 50 файлов руками.
-
Ад поддержки. Добавили в чекаут галочку "Согласен с офертой"? Идите и вставляйте
awaitpage.click(...)в сотню мест. -
Код превратится в кашу. Тест на 10 строк раздуется до 50 из-за постоянных
await test.step(...). Вы просто потеряете суть проверки за этим шумом.
Решение: Слой Flows (Бизнес-сценарии)
Выход один: нужна прослойка. Слой между "тупыми" страницами (POM) и тестами (Flows). Flow - это дирижер. Он вообще не знает про селекторы (.click(), .fill()). Он знает только про бизнес-процесс.
import { test, expect } from '@playwright/test';
import { CartPage, PaymentPage, OrderData } from './types';
export class CheckoutFlow {
// Dependency Injection: Flow принимает готовые инстансы страниц
constructor(private cartPage: CartPage, private paymentPage: PaymentPage) {}
@Step('Пользователь оформляет заказ')
async completePurchase(orderData: OrderData) {
await test.step('Переход к оплате', async () => {
await this.cartPage.clickCheckout();
// Бизнес-проверка: перешли ли мы на нужную страницу?
await expect(this.paymentPage.form).toBeVisible();
});
await test.step('Заполнение платежных данных', async () => {
// Данные (orderData) приходят извне: из фабрик или фикстур.
// Никакого хардкода карт или имен внутри Flow!
await this.paymentPage.fillDetails(orderData.card);
await this.paymentPage.submit();
});
}
}
Что в отчете?
Все. Теперь там действия юзера, а не браузера.
Тест упал? Разраб смотрит отчет. 30 секунд и он понял, на каком шаге бизнес-логики затык.
Как мы это закодили (никакой магии)?
Чтобы не писать тонны бойлерплейта (и не сойти с ума), внедрили две вещи:
-
Dependency Injection (DI). Используем фикстуры Playwright как DI-контейнер. Flows регистрируем один раз в конфиге. Playwright сам создает страницы, кидает их в конструктор Flow и отдает готовый объект в тест.
new CheckoutFlow(...)в каждом файле? Забудьте. -
Декоратор @Step. Та самая "магия". Оборачивает метод класса в
test.step, имя берет из декоратора. Код Flows остается чистым.
Как написать такой декоратор и не сломать
thisс типизацией, тема для отдельной статьи. Там хардкор, на пальцах не объяснишь.
Итого
Автотесты пишутся один раз, а читаются постоянно разными людьми. Ассерты отвечают на вопрос «что именно сломалось», а Flows — на вопрос «на каком бизнес‑шаге это произошло».
Хотите, чтобы разрабы и менеджеры не игнорировали ваши отчеты? Сделайте их читаемыми. Разделение на POM и Flows — это не просто «красивая архитектура», это инструмент, который окупается в первый же месяц за счёт того, что вы перестаете тратить часы на расшифровку красных крестиков.
Автор: playwright_no_hero
