Хотел протестировать веб-приложение через AI — за три дня собрал свой инструмент

от автора

Задача была простая: протестировать два веб-приложения перед деплоем. Next.js-портфолио и SaaS-чат — accessibility, консольные ошибки, отзывчивость на мобильных. Рутина.

Открыл Claude Code, подключил Playwright MCP, написал «протестируй приложение». Агент начал работать, делать скриншоты, проверять элементы. На 51-м снапшоте /compact сработал. Текстовый контекст был заполнен на 18%. Я не понял что произошло.

Через час разбирательств я нашёл невидимый image-лимит. Через три часа — понял, что Playwright MCP сжигает в 50 раз больше токенов чем CLI на том же workflow. Через три дня — у меня был рабочий инструмент, который уже тестируют реальные пользователи.

Эта статья — про путь от «хочу просто протестировать» до open-source инструмента, и про архитектурные проблемы, которые заставили его собрать.

Проблема первая: MCP сжигает токены как не в себя

В ноябре 2025 Pramod Dutta опубликовал анализ, который разошёлся по всему AI-testing сообществу: Playwright MCP сжигает ~114k токенов на один тест. Бенчмарк Özal на GitHub Microsoft показывает, что workflow verify для e-commerce упирается в ~1.5M токенов через MCP. Playwright CLI? Те же ~27k.

Асимметрия 50–60×. Причина архитектурная: MCP держит LLM в browser-loop’е на каждом действии — навигация, клик, ожидание, скриншот, анализ, повтор. Отлично для исследования незнакомого интерфейса. Катастрофически дорого для повторного прогона того же сценария.

Microsoft в собственном README обновил рекомендацию: для coding-агентов — CLI + Skills, а не MCP. Официальная документация Test Agents теперь предлагает триплет Planner / Generator / Healer как основную архитектуру — не «агент сидит в MCP всю сессию».

Вывод: фикс — не «использовать меньше Playwright MCP». Фикс — разделить исследование и воспроизведение по фазам.

Проблема вторая: невидимый image-лимит

Пока я разбирался с токенами, нашёл проблему хуже. В Claude Code есть второй контекстный лимит — бюджет на inline-image блоки. Примерно 50–100 блоков за сессию. Без счётчика. Без предупреждения.

Каждый Playwright:browser_take_screenshot возвращает один image-блок в контекст. 50 скриншотов — и ты использовал 0.4% текстового бюджета и 100% image-бюджета. /compact срабатывает при пустом на 80% текстовом контексте. Агент теряет всё что не было сохранено на диск.

Инструмент

Что возвращает

Стоимость

browser_take_screenshot

Изображение inline

1 блок

Read на .png

Изображение inline

1 блок

browser_snapshot

ARIA-дерево как текст

0

toHaveScreenshot() через CLI

diff% как JSON

0

Я попробовал три «очевидных» фикса:

«Просто меньше скриншотов» — дисциплина не выживает 30+ ходов в реальной исследовательской сессии. Счётчика нет — ты видишь только /compact.

Мягкое правило в CLAUDE.md — «никогда не делай скриншоты, используй ARIA». Работает ходов 30. Когда агент застревает на модалке — тянется за скриншотом и тут же рационализирует нарушение правила.

context: fork во frontmatter скилла — официально документированный фикс. На Claude Code 2.1.x под Windows не парсится. Скилл просто не появляется в списке. 90 минут отладки — потом сдался.

Что работает: субагенты (Task tool) имеют изолированный image-бюджет. Всё что субагент читает, не учитывается в родительском контексте. Проверил эмпирически: субагент прочитал 6 PNG, вернул 6 текстовых описаний — счётчик родительского чата не вырос.

День третий: рабочий инструмент

К концу третьего дня у меня был skill для Claude Code, построенный вокруг одного архитектурного инварианта: родительский чат никогда не получает изображение.

Четыре паттерна это обеспечивают:

Паттерн A (90% работы): исследование через ARIA-дерево. browser_snapshot возвращает accessibility tree как текст — те же данные о локаторах что и скриншот, но в текстовом виде. Стоимость в image-бюджете: ноль.

Паттерн B (3–5 раз за прогон): когда vision действительно нужен — pixel-diff сработал, нужна визуальная проверка layout’а — субагент читает ОДНО изображение и возвращает ОДНУ текстовую строку. Субагент сжигает свой бюджет, родительский чат чист.

Паттерн C: встроенный toHaveScreenshot() возвращает diff% как JSON через npx playwright test. Текст по всему пайплайну. Vision-токены жгутся только если diff реально сработал — и даже тогда через Паттерн B.

Паттерн D: скриншоты на диске (артефакты Playwright, кэш MCP) стоят ноль пока их не прочитали явно. Файл на диске ≠ файл в контексте.

Поток работы: первый прогон → субагент через Playwright MCP ходит по приложению через ARIA-снапшоты, генерирует *.spec.ts. Каждый последующий прогон → npx playwright test напрямую — детерминированно, ~ноль расхода токенов. Поверх — fingerprinting багов с SHA-256 ключами и классификация между прогонами: new / regression / persisting / fixed.

Паттерн issues-коллектора

Каждый сгенерированный spec собирает все мягкие проверки в один массив:

test('home page baseline', async ({ page }) => {  const consoleErrors: string[] = [];  const failedRequests: string[] = [];  const issues: string[] = [];  page.on('pageerror', (e) => consoleErrors.push(`pageerror: ${e.message}`));  page.on('console', (m) => m.type() === 'error' && consoleErrors.push(`console: ${m.text()}`));  page.on('response', (r) => r.status() >= 400 && failedRequests.push(`${r.status()} ${r.url()}`));  await page.goto('/');  const a11y = await new AxeBuilder({ page })    .withTags(['wcag2a','wcag2aa','wcag21aa','wcag22aa']).analyze();  a11y.violations.forEach((v) =>    issues.push(`a11y[${v.impact}] ${v.id}: ${v.help} (${v.nodes.length}x nodes)`));  // overflow, heading hierarchy, touch targets, html-lang — всё в issues[]  expect(issues, `${issues.length} issues found:\n  - ${issues.join('\n  - ')}`).toEqual([]);  expect(consoleErrors).toEqual([]);  expect(failedRequests).toEqual([]);});

Когда тест падает — ты получаешь все проблемы в одном сообщении, а не первую попавшуюся. Пост-обработка парсит результат и генерирует по одной баг-записи на каждый issue с стабильным отпечатком для сравнения между прогонами.

Что тестируется из коробки

Каждый spec включает: слушатели консольных ошибок (подключаются до page.goto(), с фильтром шума для GTM/Stripe/Sentry/Next.js/Supabase/ResizeObserver), axe-core WCAG аудит (теги wcag2a через wcag22aa), иерархию заголовков (прыжки h1 → h3), размеры touch-target (WCAG 2.5.8 AA = 24×24 CSS px), горизонтальное переполнение, наличие html lang. Visual regression через встроенный toHaveScreenshot() без внешних зависимостей.

Severity назначается автоматически из axe impact и класса ошибки, с тремя механизмами override: [severity:S0] inline в коллекторе, в имени теста, или // @severity: S0 комментарием перед test().

Честная картина рынка

Octomind 30 апреля 2026 опубликовал прощальное письмо. Платные сервисы AI-тестирования которые ещё на месте — QA Wolf (типичные контракты $60–250k/год), Mabl, BrowserStack AI — продают реальную ценность: облачный параллелизм, человеческую проверку, SOC 2, SLA.

Мой инструмент не конкурирует с этим. Нет управляемого облака, нет человеческой проверки, нет сертификации. Для соло-разработчиков и маленьких команд уже на Claude Code с нулевым QA-бюджетом — это рабочая замена при $0/мес. Для команды на 50 человек с cross-browser nightly regression — нет, и притворяться иначе нечестно.

Честная группа сравнения — бесплатный OSS-tier: нативный playwright init-agents --loop=claude от Microsoft (триплет Planner/Generator/Healer) и Magnitude. Отличия моего инструмента: встроенный axe-core + console + network аудит, fingerprinting багов с классификацией между прогонами, маппинг на трекеры Linear/GitHub/Jira. Ничего из этого свободные альтернативы не делают.

Что я намеренно не делал

Никакого self-healing. QA-сообщество весь последний год критикует self-healing как маркетинг — задокументированный failure mode: healer выбирает похожий-но-неправильный элемент, тест зеленеет, баг уходит в продакшн. Инструмент предпочитает красный тест ложнозелёному.

Никакого облака. Тесты остаются в репозитории. Отчёты — в файловой системе. Если npm-пакет завтра исчезнет — suite продолжит работать.

Никаких обещаний «AI напишет все тесты». Это дополнение к инженерному суждению, не замена. Особенно хорош на скучных 80%: a11y, console, network, responsive, regression diffs.

Результаты на реальных приложениях

Оба приложения публичные на GitHub — это не синтетические бенчмарки.

Статический Next.js-портфолио, mobile viewport. Найдено 4 реальных бага, 0 ложных срабатываний: axe-core color-contrast — 8 элементов не проходят WCAG 1.4.3 AA (S1), два touch-target под 24×24 px (S2), прыжок заголовков h1→h3 на странице проектов (S2). Image-бюджет в родительском чате: ноль.

Voice-first AI SaaS-чат (Next.js + FastAPI + Supabase + WebSocket). 11 спеков на login, чат, перевод, TTS, настройки, библиотеку фраз, сценарный режим, статистику, logout. 10 из 10 прошли после 4 итераций, ~12 минут от установки до первого зелёного assertion’а. Прогон выявил 6 проблем, которые стали фиксами версии 0.2.0.

Установка

npx webtest-orch@beta install

Создаёте .env.test с TEST_BASE_URL в проекте, перезапускаете Claude Code, говорите «протестируй приложение». Инструмент сам определит аутентифицированный vs публичный сайт, соберёт Playwright + axe-core, прогонит первый исследовательский pass, запишет отчёт.

Версия 0.3.1-beta, 113 тестов, CI на Linux/macOS/Windows. MIT.

Полезные ссылки

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