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);
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('? Fix with AI', { body: prompt });
Теперь, если тест упадёт, в HTML-отчёте появится кнопка «? Fix with AI».
Тестирование промпта
Для тестирования промпта я написал простой сценарий, который проверяет ссылку Get started на домашней странице Playwright:
test('get started link', async ({ page }) => {
await page.goto('https://playwright.dev');
await page.getByRole('link', { name: 'Get started' }).click();
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});
Убеждаемся, что тест проходит:
$ npx playwright test
Running 1 test using 1 worker
1 passed (1.9s)
Далее я намеренно буду вносить в тест ошибки, эмулируя изменения UI, и смотреть, как с ними справится AI.
Проверка 1: Изменяем роль link на button
Допустим, разработчик изменил тип элемента Get Started со ссылки на кнопку. Эмулируем это в тесте:
test('get started link', async ({ page }) => {
await page.goto('https://playwright.dev');
- await page.getByRole('link', { name: 'Get started' }).click();
+ await page.getByRole('button', { name: 'Get started' }).click();
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});
Теперь тест падает, и в HTML-отчёте мы видим вложение «? Fix with AI»:
Раскрываем вложение и копируем промпт кнопкой в правом верхнем углу:
Вставляем промпт в ChatGPT и получаем решение:
ChatGPT подсказывает, что роль button
неверная, и нужно вернуть link
. После правки тест снова зелёный! ?
Улучшение промпта
ChatGPT даёт развёрнутый ответ, но в реальной работе удобнее получать код-дифф и минимум дополнительного текста. После многих экспериментов, я остановился на таком шаблоне промпта:
You are an expert in Playwright testing.
Fix the error in the Playwright test "{title}".
- Provide response as a diff highlighted code snippet.
- Strictly rely on the ARIA snapshot of the page.
- Avoid adding any new code.
- Avoid adding comments to the code.
- Avoid changing the test logic.
- Use only role-based locators: getByRole, getByLabel, etc.
- For 'heading' role try to adjust the level first.
- Add a concise note about applied changes.
- If the test may be correct and there is a bug in the page, note it.
{error}
Code snippet of the failing test:
{snippet}
ARIA snapshot of the page:
{ariaSnapshot}
С таким промптом ChatGPT даёт более лаконичные ответы. Можно просто скопировать полученный код и вставить его обратно в тест:
Проверка 2: Изменение текста ссылки
Изменение текста элементов — довольно частая ситуация при активной разработке. Пусть ссылка Get started
стала Get involved
:
test('get started link', async ({ page }) => {
await page.goto('https://playwright.dev');
- await page.getByRole('link', { name: 'Get started' }).click();
+ await page.getByRole('link', { name: 'Get involved' }).click();
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});
Тест падает. По ARIA-snapshot ChatGPT определил, что текст ссылки отличается и предложил изменить локатор:
Важно отличать ошибку в тесте и реальную ошибку в UI, обнаруженную тестом. Поэтому я всегда смотрю дифф, прежде чем копировать исправленный код
Проверка 3: Убираем имя ссылки
Если Playwright локатор находит несколько элементов на странице, то чаще всего тест падает, т.к. непонятно какой элемент использовать для проверок. Эмулируем эту ситуацию, убрав name
у локатора ссылки:
test('get started link', async ({ page }) => {
await page.goto('https://playwright.dev');
- await page.getByRole('link', { name: 'Get started' }).click();
+ await page.getByRole('link').click();
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});
Тест падает с такой ошибкой:
Error: locator.click: Error: strict mode violation:
getByRole('link') resolved to 39 elements:
Вот что предлагает ChatGPT:
Анализ кода теста и ARIA-snapshot помогает AI «понять», какая именно из 39 ссылок на странице должна использовать в тесте.
Также ChatGPT предложил задать уровень заголовка Installation
, чтобы сделать локатор надёжнее.
В итоге, проверки показывают, что подход с AI-подсказками имеет право на жизнь.
Использование Copilot Edits
В примерах выше приходится вручную копировать ответ ChatGPT обратно в IDE. Можно упростить эту схему с помощью GitHub Copilot. Если вставить промпт в окно Copilot Edits в VS Code, то Copilot предложит изменения прямо в вашем редакторе, и вы сможете сразу их применить.
Ниже видео, как это выглядит на практике:
Что можно улучшить?
В процессе написания статьи, у меня появилось несколько идей, как можно усовершенствовать данный подход.
1. Кнопка «Fix with AI» в Playwright VS Code Extension
Сейчас, чтобы исправить тест, нужно вручную копировать промпт. Было бы здорово, если бы в плагине Playwright для VS Code появилась кнопка «Fix with AI». По нажатию она бы автоматически отправляла промпт заданному AI-агенту и отображала бы результат.
Вариант расположения такой кнопки:
2. Улучшения в HTML-отчёте
Аналогично, в HTML-отчёте можно было бы добавить кнопку, которая посылает запрос к AI-модели и показывает предложенный фикс прямо в браузере. Это удобно для тех, кто работает в основном с тест-репортам и не использует IDE.
Вот вариант расположения кнопки "Fix with AI" в отчёте:
К сожалению, на текущий момент HTML-отчёт Playwright не поддерживает такую кастомизацию. Но запросы на эти фичи есть:
Интеграция «Fix with AI» в ваш проект
Я создал репозиторий на GitHub с полностью рабочим примером «Fix with AI». Там можно запустить тесты, посмотреть промпты в отчёте, и применить их для исправления ошибок.
Также, можно добавить «Fix with AI» в ваш проект:
Убедитесь, что используете Playwright 1.49 или новее
Скопируйте файл
fix-with-ai.ts
в папку с вашими тестами-
Добавьте фикстуру:
import { test as base } from '@playwright/test'; import { attachFixWithAI } from './fix-with-ai'; export const test = base.extend<{ fixWithAI: void }>({ fixWithAI: [async ({ page }, use, testInfo) => { await use(); await attachFixWithAI(page, testInfo); }, { scope: 'test', auto: true }], });
Запустите тесты и откройте HTML-отчёт. Если тест упадёт, вы увидите вложение «Fix with AI».
После этого просто копируете промпт и вставляете его в ChatGPT или Copilot. Или можете запустить режим Copilot Edits, чтобы автоматически применить изменения к коду.
Буду рад услышать ваш фидбэк и идеи, как можно улучшить процесс «Fix with AI».
Спасибо за внимание и зелёных тестов с AI ❤️