Как построить самопроверяющегося браузерного агента на Claude Code и MegaIndex Browser API
31 июля 2026
Автор: admin

Как построить самопроверяющегося браузерного агента на Claude Code и MegaIndex Browser API

Практическая схема, в которой Claude Code пишет и исправляет браузерную автоматизацию, MegaIndex BrowserAPI запускает ее в реальном браузере, а отдельный набор проверок определяет, выполнена задача или нет.

Claude Code умеет писать сценарии для Playwright, исправлять селекторы, добавлять повторные попытки и находить ошибки в коде.

Но рабочий код еще не означает, что браузерный сценарий действительно работает.

В удаленной сессии сайт может выглядеть иначе. Новый профиль может попасть на страницу входа. Селектор может найти скрытый элемент вместо видимого. Прокси может открыть другую региональную версию сайта. Скрипт может вернуть корректный JSON, но собрать не те данные.

Поэтому одного анализа кода для браузерной автоматизации недостаточно.

Нужен замкнутый цикл проверки:

Claude Code изменяет автоматизацию
↓
MegaIndex BrowserAPI запускает реальный браузерный сценарий
↓
Скрипт проверки собирает результат и артефакты
↓
Claude получает информацию об ошибке и исправляет код
↓
Работа завершается только после успешной проверки

В такой схеме Claude отвечает за реализацию и исправления, а решение об успешном выполнении принимает независимый проверяющий скрипт.



Чего не хватает обычному процессу разработки с Claude Code


Часто работа с Claude Code заканчивается сразу после изменения файлов и запуска локальных тестов.

Агент возвращает примерно такой отчет:

Обновлен сценарий авторизации.
Исправлена работа с селекторами.
Добавлены повторные попытки.
Все тесты пройдены.

Формально все может быть верно. Но реальный браузерный сценарий при этом способен завершиться ошибкой.

Например:

  • тесты запускались только на подготовленном HTML;
  • реальный сайт перенаправил браузер на страницу входа;
  • сценарий открылся с другим профилем;
  • пользовательский прокси не был применен;
  • селектор нашел элемент в DOM, но элемент оказался скрытым;
  • экстрактор вернул неполные записи;
  • после ошибки браузерная сессия осталась открытой;
  • тест изменили так, чтобы он принимал неправильное поведение.

Поэтому успешный результат должен определяться отдельно от кода, который выполняет задачу.

В этой архитектуре обязанности распределяются следующим образом:

КомпонентОтветственность
Claude CodeИзучает репозиторий, изменяет код и исправляет найденные ошибки
MegaIndex BrowserAPIЗапускает сценарий в удаленной браузерной сессии
Playwright или PuppeteerУправляет браузером, навигацией, действиями и извлечением данных
Скрипт проверкиПроверяет страницу, полученные данные, артефакты и закрытие сессии
Stop hookНе дает Claude завершить работу, пока обязательные проверки не пройдены


Основной принцип:

Claude может выбирать способ исправления, но не должен сам решать, считать ли результат успешным.

Сначала опишите браузерный контракт


До начала работы нужно определить, что именно считается успешным выполнением задачи.

Формулировка «Открой страницу товаров и собери данные» слишком расплывчата. Она не определяет, какая страница должна открыться, какие данные нужны и как проверить их правильность.

Браузерный контракт должен содержать:

  • начальный URL;
  • используемый браузерный профиль;
  • требования к прокси;
  • признаки успешной загрузки страницы;
  • последовательность действий;
  • структуру результата;
  • минимальное количество записей;
  • обязательные поля;
  • правила закрытия сессии;
  • артефакты, которые нужно сохранить.

Пример контракта:

Сценарий: извлечение товаров из авторизованной сессии

Входные данные:
- целевой URL;
- необязательный ID браузерного профиля;
- необязательный пользовательский прокси;
- ожидаемое имя учетной записи.

Успешный результат:
- соединение с MegaIndex BrowserAPI установлено;
- целевая страница открылась без перенаправления на форму входа;
- на странице отображается ожидаемое имя учетной записи;
- найдено не менее пяти карточек товаров;
- у каждого товара есть название, URL, цена и идентификатор;
- сохранены JSON-результат, скриншот и HTML;
- браузерная сессия закрыта независимо от результата.

Ошибка:
- открылась страница входа;
- обнаружена страница проверки;
- имя учетной записи не совпадает;
- найдено меньше пяти товаров;
- отсутствует обязательное поле;
- браузерная сессия не закрыта.

Такой контракт нельзя незаметно изменить во время разработки. Claude получает конкретную задачу, а проверяющий скрипт — четкие условия приемки.

Не объединяйте выполнение и проверку


Основной браузерный сценарий и приемочная проверка должны находиться в разных модулях.

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

Например, сценарий считает страницу успешной по наличию элемента, а проверка использует тот же селектор. Если этот элемент присутствует и на странице входа, обе части ошибочно вернут успешный результат.

Рабочую структуру можно организовать так:

src/
├── browser/
│   ├── connect-browser.js
│   ├── create-session.js
│   └── close-session.js
├── workflows/
│   └── collect-products.js
├── validation/
│   └── validate-products.js
└── artifacts/
    └── save-run-artifacts.js

scripts/
└── verify-product-workflow.mjs

Основной workflow выполняет действия в браузере.

Отдельный verification-скрипт запускает workflow, получает результат и сравнивает его с контрактом.

Это важно, потому что успешная навигация еще не означает, что открыта нужная страница. Браузер мог загрузить:

  • форму входа;
  • страницу с отказом в доступе;
  • региональное перенаправление;
  • пустой список результатов;
  • страницу проверки;
  • частично загруженный интерфейс.

Проверяющий скрипт должен уметь распознавать такие состояния и отклонять их.

Подключение проверки к MegaIndex BrowserAPI


Параметры подключения лучше передавать через переменные окружения.

Claude должен знать названия переменных, но не их содержимое. WebSocket-адрес, cookies, токены и пароли не должны попадать в промпты, логи или репозиторий.

Установите Playwright Core:

npm install --save-dev playwright-core

Создайте файл scripts/verify-product-workflow.mjs:

import fs from "node:fs/promises";
import process from "node:process";
import { chromium } from "playwright-core";

const browserEndpoint =
  process.env.MEGAINDEX_BROWSER_WS_ENDPOINT;

const targetUrl =
  process.env.BROWSER_TEST_URL;

const expectedAccount =
  process.env.BROWSER_EXPECTED_ACCOUNT;

const artifactDirectory =
  process.env.BROWSER_ARTIFACT_DIRECTORY ?? "artifacts";

const minimumProducts = Number.parseInt(
  process.env.BROWSER_MINIMUM_PRODUCTS ?? "5",
  10,
);

if (!browserEndpoint) {
  throw new Error(
    "MEGAINDEX_BROWSER_WS_ENDPOINT is required",
  );
}

if (!targetUrl) {
  throw new Error(
    "BROWSER_TEST_URL is required",
  );
}

if (!expectedAccount) {
  throw new Error(
    "BROWSER_EXPECTED_ACCOUNT is required",
  );
}

if (
  !Number.isInteger(minimumProducts) ||
  minimumProducts < 1
) {
  throw new Error(
    "BROWSER_MINIMUM_PRODUCTS must be a positive integer",
  );
}

await fs.mkdir(artifactDirectory, {
  recursive: true,
});

const resultPath =
  `${artifactDirectory}/browser-result.json`;

const screenshotPath =
  `${artifactDirectory}/browser-page.png`;

const pageHtmlPath =
  `${artifactDirectory}/browser-page.html`;

let browser;
let page;

const startedAt = new Date().toISOString();

try {
  browser = await chromium.connectOverCDP(
    browserEndpoint,
  );

  const context =
    browser.contexts()[0] ??
    await browser.newContext();

  page =
    context.pages()[0] ??
    await context.newPage();

  const response = await page.goto(
    targetUrl,
    {
      waitUntil: "domcontentloaded",
      timeout: 60000,
    },
  );

  if (!response) {
    throw new Error(
      "Navigation returned no HTTP response",
    );
  }

  if (!response.ok()) {
    throw new Error(
      `Navigation failed with HTTP status ${response.status()}`,
    );
  }

  await page
    .locator("[data-account-name]")
    .waitFor({
      state: "visible",
      timeout: 30000,
    });

  const accountName = (
    await page
      .locator("[data-account-name]")
      .first()
      .textContent()
  )?.trim() ?? "";

  if (accountName !== expectedAccount) {
    throw new Error(
      `Unexpected account. Expected "${expectedAccount}", received "${accountName}"`,
    );
  }

  const loginFormVisible = await page
    .locator(
      "form[action*='login'], input[type='password']",
    )
    .first()
    .isVisible()
    .catch(() => false);

  if (loginFormVisible) {
    throw new Error(
      "The browser was redirected to a login page",
    );
  }

  const challengeVisible = await page
    .locator(
      "[data-captcha], iframe[src*='captcha'], iframe[src*='challenge']",
    )
    .first()
    .isVisible()
    .catch(() => false);

  if (challengeVisible) {
    throw new Error(
      "A challenge page was detected instead of the target content",
    );
  }

  const products = await page
    .locator("[data-product]")
    .evaluateAll((elements) =>
      elements.map((element) => ({
        title:
          element
            .querySelector("[data-title]")
            ?.textContent
            ?.trim() ?? "",
        price:
          element
            .querySelector("[data-price]")
            ?.textContent
            ?.trim() ?? "",
        url:
          element
            .querySelector("a")
            ?.href ?? "",
        id:
          element.getAttribute(
            "data-product-id",
          ) ?? "",
      })),
    );

  if (products.length < minimumProducts) {
    throw new Error(
      `Expected at least ${minimumProducts} products, received ${products.length}`,
    );
  }

  const invalidProduct = products.find(
    (product) =>
      !product.title ||
      !product.price ||
      !product.url ||
      !product.id,
  );

  if (invalidProduct) {
    throw new Error(
      `Invalid product record: ${JSON.stringify(invalidProduct)}`,
    );
  }

  await page.screenshot({
    path: screenshotPath,
    fullPage: true,
  });

  await fs.writeFile(
    pageHtmlPath,
    await page.content(),
    "utf8",
  );

  const result = {
    passed: true,
    startedAt,
    finishedAt: new Date().toISOString(),
    requestedUrl: targetUrl,
    finalUrl: page.url(),
    title: await page.title(),
    httpStatus: response.status(),
    accountName,
    productCount: products.length,
    products,
    artifacts: {
      screenshotPath,
      pageHtmlPath,
    },
  };

  await fs.writeFile(
    resultPath,
    `${JSON.stringify(result, null, 2)}\n`,
    "utf8",
  );

  console.log(
    JSON.stringify(result),
  );
} catch (error) {
  if (page) {
    await page
      .screenshot({
        path: screenshotPath,
        fullPage: true,
      })
      .catch(() => undefined);

    await fs
      .writeFile(
        pageHtmlPath,
        await page.content(),
        "utf8",
      )
      .catch(() => undefined);
  }

  const result = {
    passed: false,
    startedAt,
    finishedAt: new Date().toISOString(),
    error:
      error instanceof Error
        ? error.message
        : String(error),
    finalUrl: page?.url() ?? null,
    artifacts: {
      screenshotPath,
      pageHtmlPath,
    },
  };

  await fs.writeFile(
    resultPath,
    `${JSON.stringify(result, null, 2)}\n`,
    "utf8",
  );

  console.error(
    JSON.stringify(result),
  );

  process.exitCode = 1;
} finally {
  await browser
    ?.close()
    .catch(() => undefined);
}

Скрипт проверяет не только соединение с браузером.

Он контролирует:

  • HTTP-статус ответа;
  • открытую учетную запись;
  • отсутствие формы входа;
  • отсутствие страницы проверки;
  • минимальное количество товаров;
  • наличие обязательных полей;
  • сохранение скриншота и HTML;
  • закрытие браузера в блоке finally.

Селекторы в примере условные. Их нужно заменить на селекторы конкретного сайта, сохранив сам принцип проверки.

Сохраняйте артефакты каждого запуска


Одного сообщения об ошибке обычно недостаточно.

Например:

Selector timeout after 30000 ms

По такому сообщению непонятно, что открылось в браузере: страница входа, пустой интерфейс, региональная версия сайта или страница проверки.

Поэтому после каждого запуска стоит сохранять набор артефактов.

АртефактЧто он показывает
JSON-результатСтатус запуска, URL, извлеченные данные и текст ошибки
СкриншотФактическое содержимое страницы в удаленном браузере
HTML-снимокDOM страницы после завершения сценария
TraceПереходы, действия, запросы и время выполнения
Лог консолиОшибки клиентского JavaScript
Сводка сетевых запросовСтатусы запросов и отсутствие нужных ответов


После этого Claude сможет проверить не только текст исключения, но и:

  • конечный URL;
  • заголовок страницы;
  • скриншот;
  • HTML;
  • сетевые ошибки;
  • состояние авторизации;
  • полученные записи.

Так исправления основываются на фактическом состоянии браузера, а не на предположениях.

Разделяйте ошибки по типам


Повторный запуск подходит не для каждой ошибки.

Сначала нужно определить категорию сбоя, а затем выбирать способ исправления.

Тип ошибкиПримерЧто проверять
ПодключениеНе удалось подключиться к WebSocket BrowserAPIДоступность endpoint и ограниченные повторные попытки
НавигацияСтраница не загрузилась за установленное времяОтвет сервера, перенаправления и условие готовности
АвторизацияБраузер вернулся на страницу входаПрофиль, cookies и сохранение сессии
Состояние страницыНе найден контрольный элементСкриншот и HTML до изменения селекторов
Извлечение данныхУ товара отсутствует цена или идентификаторDOM, селекторы и правила валидации
Страница проверкиВместо контента открылась challenge-страницаСостояние сессии и признаки целевой страницы
Закрытие сессииУдаленный браузер остался открытУправление ресурсами и блок finally
Бизнес-проверкаПолучено слишком мало записейПагинация, фильтры и полнота загрузки


Повторные попытки уместны при временных проблемах соединения или загрузки.

Они бесполезны, если:

  • открыта другая учетная запись;
  • селектор указывает на неправильный элемент;
  • загружена страница проверки;
  • результат не соответствует схеме;
  • повторное действие может создать дубликат.

В таких случаях автоматический retry только маскирует проблему.

Создайте отдельный skill для браузерной проверки


Полную инструкцию по проверке не нужно вставлять в каждый запрос к Claude.

Ее можно сохранить в отдельном skill и использовать после изменений, связанных с браузером.

Создайте файл:

.claude/skills/verify-browser-workflow/SKILL.md

Содержимое:

---
name: verify-browser-workflow
description: Использовать после изменений в подключении к MegaIndex BrowserAPI, навигации Playwright или Puppeteer, браузерных профилях, прокси, сессиях, селекторах, извлечении данных, повторных попытках и браузерных тестах.
---

# Проверка браузерного сценария

1. Изучи текущий git diff.
2. Определи, какие браузерные сценарии затронуты.
3. Проверь изменения в управлении сессией, выборе профиля, приоритете прокси и закрытии браузера.
4. Запусти модульные тесты, линтер и проверку типов.
5. Запусти затронутый сценарий через MegaIndex BrowserAPI.
6. Изучи JSON-результат, скриншот и сохраненный HTML.
7. Подтверди:
   - открыта ожидаемая страница;
   - состояние авторизации корректно;
   - страница входа, проверки или ошибки не принята за целевой контент;
   - записи соответствуют контракту результата;
   - браузерная сессия закрыта;
   - тесты не были удалены, пропущены или ослаблены.
8. Укажи выполненные команды, коды завершения, созданные артефакты, изменения тестов и итоговый результат.

Skill задает порядок действий, но не заставляет Claude выполнить их полностью.

Для обязательной проверки нужен Stop hook.

Запретите завершение работы при неудачной проверке


Stop hook запускается, когда Claude пытается закончить текущую задачу.

Если хотя бы одна обязательная команда завершается ошибкой, hook должен вернуть блокирующий код.

Создайте файл:

.claude/hooks/verify-browser-before-stop.sh

#!/usr/bin/env bash

set -uo pipefail

FAILED=0

run_check() {
  local name="$1"
  shift

  echo "Running ${name}..."

  if "$@"; then
    echo "${name}: passed"
  else
    echo "${name}: failed" >&2
    FAILED=1
  fi
}

run_check \
  "unit tests" \
  npm test

run_check \
  "lint" \
  npm run lint

run_check \
  "type checking" \
  npm run typecheck

run_check \
  "MegaIndex BrowserAPI verification" \
  npm run verify:browser

if [[ "$FAILED" -ne 0 ]]; then
  echo "Browser acceptance checks failed. Inspect the artifacts and continue working." >&2
  exit 2
fi

echo "All browser acceptance checks passed."
exit 0

Подключите hook в файле:

.claude/settings.json

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/verify-browser-before-stop.sh",
            "timeout": 600
          }
        ]
      }
    ]
  }
}

Для блокировки используется код завершения 2.

Код 1 может просто сообщить об ошибке. Поэтому поведение Stop hook нужно отдельно проверить, искусственно вызвав ошибку в одной из команд.

Не перегружайте CLAUDE.md


В CLAUDE.md стоит оставлять только правила, которые относятся почти ко всем браузерным задачам проекта.

Например:

# Правила проекта MegaIndex BrowserAPI

- Используй существующий модуль подключения к BrowserAPI.
- Получай параметры подключения только из переменных окружения.
- Не выводи и не сохраняй WebSocket-адреса, cookies, токены и пароли прокси.
- Явно определяй, какой модуль отвечает за закрытие браузерной сессии.
- Закрывай удаленную сессию после успешного выполнения и после ошибки.
- Пользовательский прокси имеет приоритет над прокси браузерного профиля.
- Не повторяй действия, которые могут создать дублирующий внешний эффект.
- Не заменяй ожидание событий фиксированными задержками.
- Не принимай страницы входа, проверки и ошибки за целевой контент.
- Не удаляй, не пропускай и не ослабляй тесты ради успешного результата.
- Изменение браузерного кода считается завершенным только после удаленной проверки.

Селекторы конкретных сайтов, схемы данных и разбор отдельных ошибок лучше хранить в skills или справочных файлах.

Иначе CLAUDE.md быстро разрастается и перестает быть полезным.

Проведите финальную проверку в новой сессии


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

После успешного прохождения тестов полезно открыть новую сессию и передать ей только diff и артефакты.

Пример запроса:

Проведи независимую проверку текущего diff как специалист по браузерной автоматизации.

Не изменяй файлы.

Проверь:

1. Управление подключением к MegaIndex BrowserAPI.
2. Закрытие браузера после успешного выполнения и ошибки.
3. Приоритет браузерного профиля и пользовательского прокси.
4. Проверку состояния авторизации.
5. Ограничения повторных попыток.
6. Действия, которые нельзя безопасно повторять.
7. Обнаружение страниц входа, проверки и ошибки.
8. Валидацию извлеченных данных.
9. Изменения тестов, ослабляющие существующие гарантии.
10. Возможную утечку учетных данных.
11. Доказывают ли сохраненные артефакты выполнение браузерного контракта.

Верни только конкретные замечания с путями к файлам и объяснением причин.

Независимая проверка особенно полезна для сценариев, связанных с:

  • авторизацией;
  • платежами;
  • изменением учетной записи;
  • повторным использованием сессий;
  • маршрутизацией прокси;
  • массовым извлечением данных;
  • работой с несколькими сайтами.

Запуск проверки в CI


Тот же процесс можно использовать в CI без интерактивного интерфейса Claude Code.

Сначала запускаются обычные проверки:

set -euo pipefail

npm test
npm run lint
npm run typecheck
npm run verify:browser

После них можно выполнить независимый анализ diff:

claude -p \
  --permission-mode plan \
  --output-format json \
  --max-turns 5 \
  "Проверь текущий diff на наличие регрессий в BrowserAPI. Проверь управление сессиями, закрытие браузера, приоритет профиля и прокси, состояние авторизации, повторные попытки, валидацию извлеченных данных, ослабление тестов, утечки секретов и созданные артефакты." \
  > artifacts/claude-browser-review.json

Статус CI должен определяться кодами завершения тестов и verification-скрипта.

Отчет Claude используется как дополнительная проверка. Он не заменяет тесты и не должен самостоятельно определять итоговый статус сборки.

Рекомендуемая структура проекта


project/
├── CLAUDE.md
├── .claude/
│   ├── settings.json
│   ├── hooks/
│   │   └── verify-browser-before-stop.sh
│   └── skills/
│       └── verify-browser-workflow/
│           ├── SKILL.md
│           └── reference.md
├── scripts/
│   └── verify-product-workflow.mjs
├── src/
│   ├── browser/
│   ├── workflows/
│   ├── validation/
│   └── artifacts/
├── tests/
├── artifacts/
└── package.json

Назначение файлов:

  • CLAUDE.md — постоянные правила проекта;
  • skills — инструкции для повторяемых задач;
  • hooks — обязательные проверки перед завершением работы;
  • scripts — реальные сценарии проверки через MegaIndex BrowserAPI;
  • artifacts — JSON, скриншоты, HTML и другие данные запуска;
  • tests — локальные тесты и проверки браузерных контрактов.

Промпт для настройки самопроверяющегося процесса


Изучи этот репозиторий и создай самопроверяющийся процесс для автоматизации на базе MegaIndex BrowserAPI.

Не раскрывай учетные данные.

1. Найди существующий код, связанный с:
   - подключением к BrowserAPI;
   - управлением браузерной сессией;
   - профилями;
   - прокси;
   - извлечением данных;
   - закрытием ресурсов.

2. Определи браузерный контракт для основного сценария:
   - входные данные;
   - ожидаемое состояние авторизации;
   - ожидаемое состояние страницы;
   - минимальный результат;
   - обязательные поля;
   - условия ошибки;
   - требования к закрытию сессии;
   - сохраняемые артефакты.

3. Создай verification-скрипт, который:
   - получает endpoint BrowserAPI из переменной окружения;
   - подключается по CDP;
   - запускает основной сценарий;
   - отклоняет страницы входа, проверки и ошибки;
   - проверяет извлеченные данные;
   - сохраняет JSON, скриншот и HTML;
   - закрывает браузер после успешного выполнения и после ошибки;
   - возвращает ненулевой код, если контракт не выполнен.

4. Создай skill Claude Code для изменений, затрагивающих:
   - подключения к BrowserAPI;
   - сессии;
   - профили;
   - прокси;
   - навигацию;
   - селекторы;
   - извлечение данных;
   - повторные попытки;
   - браузерные тесты.

5. Создай Stop hook, который запускает:
   - модульные тесты;
   - линтер;
   - проверку типов;
   - проверку BrowserAPI.

   Если хотя бы одна обязательная проверка завершилась ошибкой, hook должен вернуть код 2.

6. Проверь текущий diff и подтверди:
   - тесты не удалены, не пропущены и не ослаблены;
   - учетные данные не могут попасть в логи;
   - браузер закрывается при любом результате;
   - повторные попытки не повторяют действия с внешними побочными эффектами.

Верни:
- браузерный контракт;
- список измененных файлов;
- выполненные команды;
- созданные артефакты;
- результат проверки;
- нерешенные риски.

Результат


Без отдельной приемочной проверки Claude завершает задачу, когда код выглядит готовым и локальные тесты проходят.

После внедрения браузерного контракта процесс меняется:

Определить условия приемки
↓
Передать реализацию Claude
↓
Запустить сценарий через MegaIndex BrowserAPI
↓
Сохранить JSON, скриншот, HTML и результаты тестов
↓
Передать найденные ошибки обратно Claude
↓
Не разрешать завершение до успешной проверки
↓
Проверить итоговый diff в новой сессии

Ручная проверка по-прежнему остается частью процесса. Но теперь разработчик проверяет не отчет Claude о проделанной работе, а воспроизводимый запуск реального браузерного сценария с сохраненными доказательствами.

Обсуждение

Для добавления комментария, пожалуйста, авторизуйтесь