Добавляем кнопку «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('						
Источник: https://habr.com/ru/articles/875448/


Интересные статьи

Интересные статьи

Каждый день мы пользуемся криптографическими схемами, не особо задумываясь об этом. Именно криптография обеспечивает защиту наших коммуникаций через интернет, включая все B2B, B2C и G2C взаимодействия...
В Евросоюзе почти готов закон, который будет делать безопасным искусственный интеллект. Государства предлагается ограничить в контроле над людьми с помощью ИИ, а корпорации — в извлечении прибыли. Тем...
Решения для больших компаний обычно должны выдерживать высокие нагрузки. Когда в штате много десятков тысяч человек, и значительная доля из них ежедневно пользуются ...
Привет, друзья! Меня зовут Петр, я представитель малого белорусского бизнеса со штатом чуть более 20 сотрудников. В данной статье хочу поделиться негативным опытом покупки 1С-Битрикс. ...
В 2019 году люди знакомятся с брендом, выбирают и, что самое главное, ПОКУПАЮТ через интернет. Сегодня практически у любого бизнеса есть свой сайт — от личных блогов, зарабатывающих на рекламе, до инт...