Автоматизация тестирования — это не только про стабильность и скорость, но и про понимание причин падений. Одно дело — увидеть красный индикатор в CI, и совсем другое — быстро разобраться, что именно пошло не так.
В этой статье — практические приёмы, которые помогут сделать Playwright-тесты не просто «зелёными», а по-настоящему надёжными и понятными. Разберём, как:
-
подключить минимальный набор инструментов для диагностики;
-
улучшить читаемость отчётов;
-
использовать кастомные ассерты для более информативных ошибок;
-
выбрать подходящие репортеры под разные роли в команде;
-
и даже применить AI для автоматического анализа падений.
Материал будет полезен QA-инженерам и автоматизаторам, работающим с Playwright, а также разработчикам, тимлидам и менеджерам, которым важно получать прозрачную картину состояния автотестов.
Быстрый старт: HTML-репорт, скриншоты и trace
Давайте возьмём пример и на нём разберём, что у нас может пойти не так. Для начала нужно создать задачу, заполнить форму и проверить, что задача создалась успешно.
test('Успешное создание новой задачи', async ({app: {formTasksPage}}) => { const formValue: Task = generateTasks(); await formTasksPage.open(urls.tasks.create); // await formTasksPage.fillForm<Task>(formValue); // await formTasksPage.save(); // await formTasksPage.messages.expectMessages(NoticeMessages.created); });
Если тест падает, причина может быть в отсутствии элемента, неудачном клике или таймауте. Чтобы понять, что именно произошло, нужно:
-
включить встроенный HTML-отчёт в файле playwright.config.js;
-
настроить создание скриншотов при падении.
export default defineConfig({ reporter: 'html', use: { screenshot: 'only-on-failure', } })
Так при падении теста появится:
-
HTML-отчёт с подробным описанием выполнения;
-
скриншот последнего состояния страницы;
-
trace-файл с записью всех действий.
Базовая диагностика готова, но этого недостаточно.
Детальная отладка с Playwright Trace Viewer
Trace Viewer предоставляет расширенные возможности для отладки и позволяет:
-
просматривать каждый шаг теста;
-
видеть скриншоты до, во время и после действия;
-
анализировать таймлайн событий в любой момент;
-
отслеживать HTTP-запросы и ответы, даже те, что сделаны через request;
-
сохранять всё, что было в консоли браузера во время выполнения теста;
-
открыть текущее состояние DOM-дерева на любом шаге;
-
измерять время выполнения шагов.
Для включения Trace Viewer достаточно указать его при ретрае.
export default defineConfig({ reporter: 'html', use: { screenshot: 'only-on-failure', trace: 'retry-with-trace', } })
С этими настройками отчёт о падениях откроется в браузере, а также появится trace-файл и скриншот, что значительно упрощает анализ.


Если не хватает информации, можно воспользоваться функцией debug:
DEBUG=pw:api npx playwright test
Читаемые шаги: test.step и автоматизация описаний
Чтобы отчёты стали ещё понятнее, нужно разбить тест на логические шаги и использовать test.step():
test('Успешное создание новой задачи', async ({ app: { formTasksPage } }) => { const formValue: Task = generateTasks(); await test.step('Открываем форму создания', async () => { await formTasksPage.open(urls.tasks.create); }); await test.step('Заполняем и сохраняем форму на создание задачи', async () => { await formTasksPage.fillForm<Task>(formValue); await formTasksPage.save(); }); await test.step('Проверяем, что задача создалась', async () => { await formTasksPage.messages.expectMessages(NoticeMessages.created); }); });
Такой подход делает отчёт более читаемым: видно, на каком шаге произошёл сбой. Однако оборачивать каждый метод вручную — это сплошные повторения кода.

Чтобы спастись от повторений, можно создать декоратор, который автоматически добавляет шаги с понятными названиями и параметрами.
function boxedStep(target: Function, context: ClassMethodDecoratorContext) { return function replacementMethod(...args: any) { const name = this.constructor.name + ' ' + (context.name as string); return test.step(name, async () => { return await target.call(this, ...args); }, { box: true }); // Note the "box" option here. }; } class LoginPage { constructor(readonly page: Page) {} @boxedStep async login() { // .... } }
У меня, как и у многих инженеров, есть свой «сниппет», который кочует из проекта в проект. Адаптация декоратора позволила не только оборачивать методы, но и передавать читабельное описание, а также логировать аргументы.
Пример использования:
export class BaseFormPage extends BasePage { @step('Заполняем форму значениями $0') async fillForm<T>(values: T) { for (const [keyForm, valueField] of Object.entries(values)) { await this.form[keyForm].fill(valueField); } } @step('Проверяем видимость элементов формы') async checkAllForm() { for (const [_, locatorField] of Object.entries(this.form)) { await locatorField.expectVisible(); } } }
Сам декоратор:
const step = (stepNameTemplate: string) => (target: (this: any, ...args: any[]) => Promise<any>) => { async function replacementMethod(this: any, ...args: any[]): Promise<any> { const stringArgs = args.map(arg => { if (typeof arg === 'object') return JSON.stringify(arg); return arg; }); const fullNameStep = getFullNameStep(stepNameTemplate, stringArgs); return test.step(fullNameStep, async () => target.call(this, ...args), { box: true }); } return replacementMethod; };
Тогда получится функция парсинга названий степов:
const getFullNameStep = (stepNameTemplate: string, args: string[]) => { const countInsertArgumentsInStep = stepNameTemplate.match(/\$\d+|\$/g); if (countInsertArgumentsInStep) { return countInsertArgumentsInStep.reduce((acc, el) => { if (el === '$') return acc.replace('$', args[0]); const numArgs = Number(el.replace('$', '')); acc = acc.replace(el, args[numArgs]); return acc; }, stepNameTemplate); } return stepNameTemplate; };
Весь код моего декоратора можно посмотреть на GitHub. Будет здорово, если он вам пригодится — напишите об этом в комментариях.
С таким подходом в отчёте будут отображаться не только названия шагов, но и значения, с которыми они выполнялись.

Ассерты с объяснением: не просто expected true
Шаги помогают понять, где упал тест. Теперь нужно понять, почему.
Вместо сухих сообщений:

Можно добавить объяснения:
const res = await this.api.usersApi.createUser(data); expect(res.status, '[POST] /api/users Пользователь не создан').toBe(200);

Можно пойти дальше и расширить expect, чтобы проверять статус-код с подробным сообщением:
export const expect = baseExpect.extend({ async toHaveStatusCode<T>(received: ApiResponse<T>, expectedStatus: number) { const assertionName = 'toHaveStatusCode'; const statusCode = received.status; const pass = statusCode === expectedStatus; const errorMessage = ` Ожидаемый статус-код: ${expectedStatus} Полученный статус-код: ${statusCode} URL: ${received.response.url()} Метод: ${received.request.method} Тело запроса: ${received.request.data} Тело ответа: ${JSON.stringify(await received.data) || 'Тело отсутствует'} `; if (!pass) { return { message: () => errorMessage, pass: false, }; } return { name: assertionName, message: () => `Ответ вернул ожидаемый статус-код: ${statusCode}`, pass: true, }; }, });
Теперь наш тест выглядит чище, а сообщение об ошибке содержит всю нужную информацию:

Репорты под каждую роль: от разработчика до менеджера
Разным участникам команды нужны разные отчёты.
Для разработчиков: Dot-отчёт
Разработчикам важно быстро понять: зеленый свет или красный. Для этого идеально подходит быстрый Dot-отчётдля отслеживания прогресса:
export default defineConfig({ reporter: 'dot', });
Для менеджеров: кастомные репортеры и уведомления
Менеджерам важно знать, какие тесты упали и сколько их. Им нужны понятные ошибки без технических деталей. А также чтобы отчёты приходили быстро, например, в Telegram или Slack. Playwright позволяет писать свои кастомные репортеры:
import { Reporter, TestCase, TestResult, FullResult, FullConfig, Suite } from "@playwright/test/reporter"; export default class MyReporter implements Reporter { private introMessage = ""; private failsMessage = ""; private passed = 0; private failed = 0; private skipped = 0; onBegin(config: FullConfig, suite: Suite) { this.introMessage = `- Общее количество тестов: ${suite.allTests().length}`; } onTestEnd(test: TestCase, result: TestResult) { switch (result.status) { case "failed": case "timedOut": this.addFailMessage(`❌ ${test.title} failed\n> ${result.error?.message || "Unknown error"}`); this.failed++; break; case "skipped": this.addFailMessage(`⚠️ ${test.title} skipped`); this.skipped++; break; case "passed": this.passed++; break; } } async onEnd(result: FullResult) { const message = this.buildMessage(); console.log(message); // метод отправки в телеграмм/слак/матермост } private addFailMessage(message: string) { this.failsMessage += `\n${message}`; } private buildMessage(): string { const summary = ` 📦 **Результаты прогона тестов**: - ✅ Успешно пройдено: ${this.passed} - ❌ Провалено: ${this.failed} - ⏩ Пропущено: ${this.skipped} `.trim(); const failureDetails = this.failsMessage ? `\n### ❌ Проваленные тесты\n${this.failsMessage}` : "\n👍 Все тесты успешно пройдены!"; return ` --- ${this.introMessage} --- ${summary} `.trim().replace(/ {4}/g, ''); } }
Такие краткие отчёты менеджеры могут получать прямо в мессенджер:

Для QA-инженеров: HTML-отчёты с трейсами и скриншотами
Нам нужны все подробности. Поэтому для QA-инженеров следует включить:
-
HTML-отчёт — обязательно;
-
Allure-отчёт — если пользуетесь.
-
JSON-отчёт или JUnit-отчёт — для машиночитаемого формата в CI/CD.
export default defineConfig({ reporter: [ ['dot'], ['html'], ['./reporter-custom.ts'], ['json', { outputFile: 'results.json' }], ], });
AI в помощь: автоматический разбор падений
Playwright добавил кнопку «Copy prompt» в HTML-отчёт, которая генерирует промпт для LLM с частью кода и контекстом падения, если вы, как и я, пользуетесь нейросетями для облегчения работы.

Этот промт хранится в attachments. У меня появилась идея: собрать все уникальные ошибки, отправить их в LLM и получить готовые решения.
Как это сделать:
1. Собрать информацию об упавших тестах в onTestEnd, включая аттачменты с промптами.
async onTestEnd(test: TestCase, result: TestResult) { if (result.status === 'failed') { const errorMessage = result.error?.message; const promptAttachments = this.formatPrompt(result); this._failedTests.push({ testTitle: test.title, errorMessage, attachments: promptAttachments, }); } }
2. С помощью Map или Set сгруппировать только уникальные промпты, чтобы не отправлять одно и то же 100 раз.
async onEnd() { if (this._failedTests.length === 0) { console.log('Все тесты прошли успешно!'); return; } const map = new Map( this._failedTests.map(test => [ test.errorMessage, test.attachments[0]?.body.toString("utf8") ]) ); const uniquePrompts = map.entries(); for (const [error, prompt] of uniquePrompts) { const responseLLM = await llm.sendMessage(prompt); // отправляем куда нам удобно console.log(error, responseLLM); } }
3. Отправить эти промпты в выбранную LLM (например, GigaChat API).
async onEnd() { if (this._failedTests.length === 0) { console.log('Все тесты прошли успешно!'); return; } const map = new Map( this._failedTests.map(test => [ test.errorMessage, test.attachments[0]?.body.toString("utf8") ]) ); const uniquePrompts = map.entries(); for (const [error, prompt] of uniquePrompts) { const responseLLM = await llm.sendMessage(prompt); // отправляем куда нам удобно console.log(error, responseLLM); } }
4. LLM возвратит объяснение ошибки и, возможно, предложит код решения.
Такой подход можно встроить в CI, например, через GitHub Actions. Это экономит время и помогает быстрее устранять проблемы.

Итог
Хороший автотест — это не только тот, что проходит, но и тот, что падает понятно. Использование trace, шагов, кастомных сообщений и репортов делает отладку проще, а отчёты — полезнее для всей команды. Надеюсь, эти приёмы помогут сделать ваши Playwright-тесты стабильнее, а отчёты — читаемыми и информативными.
Желаю удачи в автоматизации и красивых падений! 🚀
ссылка на оригинал статьи https://habr.com/ru/articles/935882/
Добавить комментарий