Как сделать отчёты Playwright понятными, а падения — полезными

от автора

Автоматизация тестирования — это не только про стабильность и скорость, но и про понимание причин падений. Одно дело — увидеть красный индикатор в 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); });

Если тест падает, причина может быть в отсутствии элемента, неудачном клике или таймауте. Чтобы понять, что именно произошло, нужно:

  1. включить встроенный HTML-отчёт в файле playwright.config.js;

  2. настроить создание скриншотов при падении.

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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *