Автоматизация тестирования — это не только про стабильность и скорость, но и про понимание причин падений. Одно дело — увидеть красный индикатор в 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-тесты стабильнее, а отчёты — читаемыми и информативными.
Желаю удачи в автоматизации и красивых падений! ?
Комментарии (3)
vitalets
25.08.2025 11:14Полезная статья, спасибо!
В качестве альтернативы декоратору порекламирую своё решение, где шаги генерируются из комментариев в коде:test('Check home page', async ({ page }) => { // step: Open home page await page.goto('https://playwright.dev'); // step: Click "Get started" link await page.getByRole('link', { name: 'Get started' }).click(); // step: Check page title await expect(page).toHaveTitle('Installation | Playwright'); });
Отчет:
magic-steps-report
old-door
25.08.2025 11:14Тоже использую подобный декоратор для step и я в нем ещё реализовал генерацию описания, на основании названия класса + названия методов + параметров, а так же можно прописать название и вручную. В отчете выглядит примерно так:
Some page › some action: param-value
Но это подходит в случае, если тест отчетность нужна на английском.
___
Кстати, ещё можно отметить, что в 1.53.0 версии плейрайта добавили фичу, помогающей присвоить описание локаторов. Можно почитать об этом в релизнотах
Теперь и действия с локаторами по типу click, fill, clear и тд, будут иметь описание по дефолту.
Конечно, locator.desctibe пока что работает не очень хорошо, описание теряется, при вызове элемента по индексу, использованию locator.all(), first(), last() и в общем любой функции возвращающей локатор, но есть способы обойти это ограничение и получить улучшенную тест отчетность без дополнительных step
Mad_man
Статья - огонь! Всё по делу.
У себя на проекте АТ применяю аналогичные практики, с LLM хотел сделать что-то аналогичное, но не доходили руки. Пожалуй, переиспользую твою реализацию на досуге! =)
На скринах не увидел, как твой декоратор позволяет "логировать аргументы" в отчёте
> Адаптация декоратора позволила не только оборачивать методы, но и передавать читабельное описание, а также логировать аргументы.
Было бы супер увидеть, как это выглядит