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

Желаю удачи в автоматизации и красивых падений! ?

Комментарии (3)


  1. Mad_man
    25.08.2025 11:14

    Статья - огонь! Всё по делу.
    У себя на проекте АТ применяю аналогичные практики, с LLM хотел сделать что-то аналогичное, но не доходили руки. Пожалуй, переиспользую твою реализацию на досуге! =)

    На скринах не увидел, как твой декоратор позволяет "логировать аргументы" в отчёте

    > Адаптация декоратора позволила не только оборачивать методы, но и передавать читабельное описание, а также логировать аргументы. 

    Было бы супер увидеть, как это выглядит


  1. 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
    magic-steps-report


  1. 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