
Продолжение серии. Предыдущие части: Как я перестал бояться Claude Code, Почему AI-агент тратит 80% токенов на уже прочитанные файлы
Задача была простая: протестировать два веб-приложения перед деплоем. 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% текстовом контексте. Агент теряет всё что не было сохранено на диск.
|
Инструмент |
Что возвращает |
Стоимость |
|---|---|---|
|
|
Изображение inline |
1 блок |
|
|
Изображение inline |
1 блок |
|
|
ARIA-дерево как текст |
0 |
|
|
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.
Полезные ссылки
-
Playwright MCP: accessibility snapshots — почему текст лучше скриншотов для агентов
-
Playwright Test Agents (официальные доки) — архитектура Planner / Generator / Healer
-
Özal benchmark: MCP vs CLI token cost — данные из issue Microsoft
-
axe-core WCAG tags — справочник по тегам
-
Предыдущая статья: иерархический project-context — другая невидимая стоимость в агентных workflow’ах
ссылка на оригинал статьи https://habr.com/ru/articles/1030840/