Добавляем кнопку «Fix with AI» в отчёты Playwright

от автора

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

Ниже поделюсь способом, как можно оптимизировать процесс исправления Playwright тестов с помощью AI, добавив прямо в HTML-отчёт вот такую кнопку:

Fix with AI

Fix with AI

Поехали!

План

Подход состоит из трёх шагов:

  1. Определить упавший тест

  2. Сгенерировать промпт для исправления с релевантным контекстом:

    • сообщение об ошибке

    • фрагмент кода теста

    • ARIA-spanshot страницы

  3. Прикрепить этот промпт к 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('🤖 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»:

Кнопка "Fix with AI"

Кнопка «Fix with AI»

Раскрываем вложение и копируем промпт кнопкой в правом верхнем углу:

Копируем промпт

Копируем промпт

Вставляем промпт в ChatGPT и получаем решение:

Рекомендации от ChatGPT

Рекомендации от 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 даёт более лаконичные ответы. Можно просто скопировать полученный код и вставить его обратно в тест:

Более лаконичный ответ ChatGPT

Более лаконичный ответ 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 определил, что текст ссылки отличается и предложил изменить локатор:

ChatGPT исправляет текст ссылки

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:

ChatGPT находит корректную ссылку

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-агенту и отображала бы результат.

Вариант расположения такой кнопки:

Место для кнопки "Fix with AI" в Playwright VS Code extension

Место для кнопки «Fix with AI» в Playwright VS Code extension

2. Улучшения в HTML-отчёте

Аналогично, в HTML-отчёте можно было бы добавить кнопку, которая посылает запрос к AI-модели и показывает предложенный фикс прямо в браузере. Это удобно для тех, кто работает в основном с тест-репортам и не использует IDE.

Вот вариант расположения кнопки «Fix with AI» в отчёте:

Место для кнопки "Fix with AI" в HTML-отчёте

Место для кнопки «Fix with AI» в HTML-отчёте

К сожалению, на текущий момент HTML-отчёт Playwright не поддерживает такую кастомизацию. Но запросы на эти фичи есть:

Интеграция «Fix with AI» в ваш проект

Я создал репозиторий на GitHub с полностью рабочим примером «Fix with AI». Там можно запустить тесты, посмотреть промпты в отчёте, и применить их для исправления ошибок.

Также, можно добавить «Fix with AI» в ваш проект:

  1. Убедитесь, что используете Playwright 1.49 или новее

  2. Скопируйте файл fix-with-ai.ts в папку с вашими тестами

  3. Добавьте фикстуру:

    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 }], }); 
  4. Запустите тесты и откройте HTML-отчёт. Если тест упадёт, вы увидите вложение «Fix with AI».

После этого просто копируете промпт и вставляете его в ChatGPT или Copilot. Или можете запустить режим Copilot Edits, чтобы автоматически применить изменения к коду.


Буду рад услышать ваш фидбэк и идеи, как можно улучшить процесс «Fix with AI».

Спасибо за внимание и зелёных тестов с AI ❤️


ссылка на оригинал статьи https://habr.com/ru/articles/875448/


Комментарии

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

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