End-to-end тесты обеспечивают надёжность приложения, но сами они часто превращаются в боль при поддержке. Даже небольшие изменения в UI могут их ломать, и в результате команда тратит много времени на отладку.
Ниже поделюсь способом, как можно оптимизировать процесс исправления Playwright тестов с помощью AI, добавив прямо в HTML-отчёт вот такую кнопку:

Поехали!
План
Подход состоит из трёх шагов:
Определить упавший тест
Сгенерировать промпт для исправления с релевантным контекстом:
сообщение об ошибке
фрагмент кода теста
ARIA-spanshot страницы
Прикрепить этот промпт к HTML-отчёту
Шаг 1: Определение упавшего теста
Определить упавший Playwright тест можно с помощью fixture. Если после выполнения теста в testInfo.error ошибка и тест не будет перезапущен - значит он окончательно упал и можно генерировать промпт.
Пример кода:
import { test as base } from '@playwright/test';
export const test = base.extend({
fixWithAI: [async ({ page }, use, testInfo) => {
await use();
const willBeRetried = testInfo.retry < testInfo.project.retries;
if (testInfo.error && !willBeRetried) {
// ... формируем промпт для исправления ошибки
// ... прикрепляем промпт к тесту
}
}, { scope: 'test', auto: true }],
});
Шаг 2: Генерируем промпт
Шаблон промпта
Начнём с простого варианта:
Fix the error in the Playwright test "{title}".
{error}
Code snippet of the failing test:
{snippet}
ARIA snapshot of the page:
{ariaSnapshot}
Далее нужно подставить в этот шаблон необходимые данные.
Сообщение об ошибке
В Playwright сообщение об ошибке лежит в testInfo.error.message. Но в нём содержатся ANSI-коды для вывода цветов в терминале (например, [2m, [22m). Для их очистки я использую функцию stripAnsiEscapes из исходников Playwright:
const clearedErrorMessage = stripAnsiEscapes(testInfo.error.message);
После очистки сообщение выглядит так:
TimeoutError: locator.click: Timeout 1000ms exceeded.
Call log:
- waiting for getByRole('button', { name: 'Get started' })
Это сообщение мы вставляем в промпт.
Фрагмент кода
Очень важно добавить в промпт фрагмент кода теста, чтобы AI мог предложить нужные правки. Playwright уже добавляет в отчёты вот такие сниппеты:
4 | test('get started link', async ({ page }) => {
5 | await page.goto('https://playwright.dev');
> 6 | await page.getByRole('button', { name: 'Get started' }).click();
| ^
7 | await expect(page.getByRole('heading', { level: 3, name: 'Installation' })).toBeVisible();
8 | });
Поискав в исходниках Playwright, я нашел место, где они формируются. В итоге собрал всю логику в отдельную функцию getCodeSnippet(), которая получает код теста по стектрейсу ошибки:
const snippet = getCodeSnippet(testInfo.error);
Полный код getCodeSnippet().
ARIA-snapshot
ARIA-snapshots были добавлены в Playwright 1.49. Они позволяют получить в yaml формате accessibility-tree любого элемента на странице. Пример ARIA-snapshot для навигационного меню домашней страницы Playwright:
- document:
- navigation "Main":
- link "Playwright logo Playwright":
- img "Playwright logo"
- text: Playwright
- link "Docs"
- link "API"
- button "Node.js"
- link "Community"
...
Хотя ARIA-snapshots в первую очередь предназначены для снэпшот-тестирования, они идеально подходят для AI промптов:
Маленький размер → меньше риск превысить лимит длины запроса
Меньше «шума» → меньше ненужного контекста
Использование ARIA ролей → AI генерирует надежные локаторы вида
getByRole,getByLabelи т.д.
Для исправления теста лучше передать ARIA-snapshot всей страницы целиком. Поэтому снимаем его у корневого <html> тега:
const ariaSnapshot = await page.locator('html').ariaSnapshot();
Собираем промпт
В итоге собираем все части промпта:
const errorMessage = stripAnsiEscapes(testInfo.error.message);
const snippet = getCodeSnippet(testInfo.error);
const ariaSnapshot = await page.locator('html').ariaSnapshot();
const prompt = promptTemplate
.replace('{title}', testInfo.title)
.replace('{error}', errorMessage)
.replace('{snippet}', snippet)
.replace('{ariaSnapshot}', ariaSnapshot);
Пример сгенерированного промпта:
Fix the error in the Playwright test "get started link".
TimeoutError: locator.click: Timeout 1000ms exceeded.
Call log:
- waiting for getByRole('button', { name: 'Get started' })
Code snippet of the failing test:
test('get started link', async ({ page }) => {
await page.goto('https://playwright.dev');
await page.getByRole('button', { name: 'Get started' }).click();
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});
ARIA snapshot of the page:
- document:
- region "Skip to main content":
- link "Skip to main content"
- navigation "Main":
- link "Playwright logo Playwright":
- img "Playwright logo"
- text: Playwright
...
Шаг 3: Прикрепляем промпт к отчёту
Готовый промпт добавляем в отчёт в виде вложения:
await testInfo.attach(' 