/chat/completions), репозиторий на GitHub с ActionsСхема, которую собираем: раннер тестов (Playwright для веба, Appium для мобильных) → ИИ-слой (модель по API получает цель и разметку экрана, возвращает шаги) → кэш проверенных шагов в JSON → CI. Модель предлагает шаги, но выполняет их обычный раннер — это ключевое ограничение безопасности.

ANDROID_HOME/JAVA_HOME сверьте с документацией appium.io.kvm).Чек-лист перед стартом:
node -v и npm -v выводят версии без ошибокecho $ANDROID_HOME (Linux/macOS) или echo $env:ANDROID_HOME (PowerShell) не пустойadb devices видит эмуляторЧто сделать: создать каталог проекта, установить Playwright, прогнать демо-тест и зафиксировать структуру папок.
Зачем: ИИ-слой не заменяет раннер, а надстраивается над ним. Пока обычный тест не проходит, генерировать шаги бессмысленно — вы не отличите ошибку модели от сломанного окружения.
Как проверить: демо-тест зелёный, структура каталогов создана.
mkdir ai-e2e && cd ai-e2e
npm init -y
npm init playwright@latest
npx playwright test
Установщик создаст playwright.config.ts, tests/example.spec.ts и папку tests/. В конце прогона вы увидите строку вида 1 passed — это базовая линия.
Структура, к которой приводим проект:
ai-e2e/
├── tests/web/login.spec.ts
├── tests/mobile/android-login.spec.ts
├── ai/generate.mjs # вызов модели
├── ai-steps.json # кэш проверенных шагов
├── artifacts/ # DOM и скриншоты для модели
└── playwright.config.ts
В playwright.config.ts сразу задайте две вещи: use: { baseURL: 'https://staging.<ваш-домен>' } и retries: 1 в CI. baseURL подставляется в page.goto('/login'), поэтому в тестах не будет абсолютных адресов — при переезде на другой стенд правится одна строка.
Пример: npx playwright test --headed открывает браузер и показывает, как тест кликает по форме. Это ваш эталон поведения — с ним потом сравниваются шаги от модели.

Что сделать: написать скрипт, который отправляет модели цель и HTML-разметку страницы, получает JSON-массив шагов и сохраняет его в ai-steps.json. Для мобильных — тот же скрипт, но вместо HTML подаётся XML-дамп экрана из Appium.
Зачем: модель нестабильна между вызовами: один и тот же запрос может дать разные селекторы. Кэш фиксирует один проверенный вариант, тесты становятся воспроизводимыми, а расход токенов — предсказуемым.
Как проверить: скрипт печатает число шагов, ai-steps.json содержит непустой массив, повторный запуск с тем же файлом не обращается к API (в логе строка про кэш).
Сначала сохраните разметку страницы:
mkdir -p artifacts
npx playwright open https://staging.<ваш-домен>/login
В открывшемся окне Playwright Inspector нажмите «Record» и скопируйте HTML нужного экрана в artifacts/dom.html. Для Android:
adb shell uiautomator dump /sdcard/window_dump.xml
adb pull /sdcard/window_dump.xml artifacts/dom.xml
Скрипт вызова модели ai/generate.mjs:
import fs from 'node:fs';
const BASE_URL = process.env.AI_BASE_URL; // например https://<эндпоинт-провайдера>/v1
const API_KEY = process.env.AI_API_KEY;
const MODEL = process.env.AI_MODEL; // имя модели — сверьтесь с документацией провайдера
const goal = process.argv[2];
const dom = fs.readFileSync(process.argv[3], 'utf8').slice(0, 40_000); // обрезаем: контекст модели ограничен
const cache = 'ai-steps.json';
if (fs.existsSync(cache)) {
const saved = JSON.parse(fs.readFileSync(cache, 'utf8'));
if (saved.goal === goal) { console.log('Кэш найден: ai-steps.json, вызов API пропущен'); process.exit(0); }
}
const res = await fetch(`${BASE_URL}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${API_KEY}` },
body: JSON.stringify({
model: MODEL,
messages: [
{ role: 'system', content: 'Верни только JSON-массив шагов: [{"action":"fill","testid":"login-email","value":"..."}]. Без пояснений и markdown.' },
{ role: 'user', content: `Цель: ${goal}\nРазметка:\n${dom}` }
]
})
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
const data = await res.json();
const steps = JSON.parse(data.choices[0].message.content); // упадёт, если модель добавила текст вокруг JSON
fs.writeFileSync(cache, JSON.stringify({ goal, steps }, null, 2));
console.log(`Шагов сгенерировано: ${steps.length}`);
Запуск:
export AI_BASE_URL="https://<эндпоинт-провайдера>/v1"
export AI_API_KEY="<ваш-ключ>"
export AI_MODEL="<имя-модели>"
node ai/generate.mjs "войти под пользователем demo" artifacts/dom.html
Ожидаемый вывод: Шагов сгенерировано: 4. В ai-steps.json появится {"goal": "...", "steps": [...]}.
Дальше шаги выполняет обычный тест: читает JSON и вызывает page.getByTestId(step.testid).fill(step.value). Модель ничего не запускает сама — это защита от того, чтобы сгенерированный шаг не ушёл в продакшен. Требуйте от модели только data-testid: селекторы по тексту и XPath ломаются при смене вёрстки или языка.
Если ваш провайдер разворачивает модели в Google Cloud, базовый URL и способ выпуска ключа берите из гайда Как подключить модели OpenAI через Google Cloud.
Для мобильных поднимите Appium и драйвер:
npm i -D appium
npx appium driver install uiautomator2
npx appium
Пример: node ai/generate.mjs "войти под пользователем demo" artifacts/dom.xml — тот же скрипт для Android, потому что формат ответа модели не зависит от платформы. Меняется только источник разметки.

Что сделать: запустить набор локально три раза подряд и один раз в GitHub Actions, проверить отчёт и код возврата.
Зачем: ИИ-тесты склонны к «миганию» (flaky). Три прогона подряд отделяют стабильный тест от случайно зелёного.
Как проверить: все три прогона дают passed, echo $? возвращает 0, отчёт открывается в браузере.
npx playwright test --reporter=html
echo $?
Успех: в терминале 3 passed, код 0, файл playwright-report/index.html создан и открывается командой npx playwright show-report.
Второй прогон должен пройти без обращения к модели — в логе Кэш найден: ai-steps.json, вызов API пропущен. Если вызов повторяется, значит goal в тесте не совпадает с сохранённым (лишний пробел, другая формулировка).
CI-конфиг .github/workflows/ai-e2e.yml:
name: ai-e2e
on: [push, pull_request]
jobs:
web:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npx playwright test
env:
AI_BASE_URL: ${{ secrets.AI_BASE_URL }}
AI_API_KEY: ${{ secrets.AI_API_KEY }}
AI_MODEL: ${{ secrets.AI_MODEL }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
Секреты добавьте в Settings → Secrets and variables → Actions. Артефакт с отчётом сохраняется даже при падении — по нему видно, на каком шаге тест остановился.
Проблема. Тест падает на клике по кнопке входа, хотя вручную всё работает
Error: locator.click: Timeout 30000ms exceeded.<button> и <a> с одинаковой подписью) либо элемент появляется с задержкой.ai-steps.json, добавьте в разметку data-testid="login-submit" и перегенерируйте шаги. В системный промпт добавьте строку «разрешены только data-testid». Проверьте результат: npx playwright test --debug — Inspector подсветит найденный элемент.Проблема. Локально работает, в CI падает на первом же вызове модели
HTTP 401: {"error":{"message":"Incorrect API key provided"}}AI_API_KEY не проброшена в job или названа иначе; ключ лежит в .env, которого нет на раннере.env: и добавьте в job шаг диагностики без вывода значения: run: node -e "console.log(Boolean(process.env.AI_API_KEY))" — должно напечатать true.Проблема. Мобильный тест не стартует
An unknown server-side error occurred while processing the command. Original error: Could not find a connected Android device.adb не видит устройство.adb devices. В выводе должна быть строка emulator-5554 device (слово device, не offline). Затем перезапустите npx appium.Вы получили связку «раннер + ИИ-слой + кэш шагов + CI», где модель только предлагает шаги, а выполняет их Playwright или Appium. Дальше расширяйте кэш на новые сценарии и переносите мобильные тесты в отдельный job с запуском эмулятора на раннере.
Нужен ли ИИ-тестировщику доступ к продакшену?
Нет. Работайте со staging-стендом и тестовой учётной записью: сгенерированные шаги могут содержать деструктивные действия, а откатить их на реальных данных нечем.
Можно ли использовать гайд без ИИ-модели?
Да. Шаги 1 и 3 и весь CI-конфиг работают с обычными тестами; ai/generate.mjs — опциональный слой, кэш в ai-steps.json можно заполнять вручную.
Сколько стоит прогон?
Зависит от провайдера и модели — тарифы сверьте с документацией вашего провайдера. Расход снижает кэш: при совпадении goal вызов API не выполняется.
Подходит ли схема для iOS?
Да, через Appium с XCUITest-драйвером, но только на macOS с установленным Xcode. Разметку экрана получают тем же способом и подают в тот же скрипт.