# Как подключить ИИ-тестировщик e2e к проекту: настройка автотестов для веб- и мобильных приложений

> AI Release · @ai_release1 · https://ai-release.org/guides/kak-podklyuchit-ii-testirovschik-e2e-k-proektu-nastroyk.html

## TL;DR
- Время выполнения: 60–90 минут (без установки Android Studio/Xcode; с ними — плюс 40–60 минут)
- Уровень сложности: Intermediate
- Понадобится: Node.js LTS + npm, Playwright, Appium + Android SDK и эмулятор (или Xcode на macOS для iOS), API-ключ модели с OpenAI-совместимым эндпоинтом (`/chat/completions`), репозиторий на GitHub с Actions

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

## Требования и подготовка
![Иллюстрация к разделу «Требования и подготовка»](https://ai-release.org/guides/img/kak-podklyuchit-ii-testirovschik-e2e-k-proektu-nastroyk-ru-1.jpg?v=1794300596)

- ОС: Linux/macOS для веба; для iOS-тестов — только macOS (требование Xcode).
- Node.js LTS и npm. Точную минимальную версию для вашей версии Playwright сверьте с документацией playwright.dev.
- Java JDK и Android SDK + эмулятор (AVD) — для Android-тестов. Версии и переменные `ANDROID_HOME`/`JAVA_HOME` сверьте с документацией appium.io.
- Свободное место: 3–5 ГБ (эмулятор + браузеры Playwright).
- API-ключ модели и базовый URL эндпоинта. Если провайдер — OpenAI-совместимый сервис, порядок получения ключа и настройки клиента разобран в гайде [Как получить API-ключ GPT-6 Astra и подключить модель](https://ai-release.org/guides/kak-poluchit-api-klyuch-gpt-6-astra-i-podklyuchit-model.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide).
- Доступ к staging-окружению приложения (не к продакшену) и тестовая учётная запись.
- Права на установку пакетов и запуск эмулятора (в Linux — членство в группе `kvm`).

Чек-лист перед стартом:

- [ ] `node -v` и `npm -v` выводят версии без ошибок
- [ ] `echo $ANDROID_HOME` (Linux/macOS) или `echo $env:ANDROID_HOME` (PowerShell) не пустой
- [ ] `adb devices` видит эмулятор
- [ ] тестовая учётка входит в staging вручную
- [ ] ключ модели лежит в переменной окружения, а не в файле репозитория

## Шаг 1. Развернуть раннер и снять базовую линию
**Что сделать:** создать каталог проекта, установить Playwright, прогнать демо-тест и зафиксировать структуру папок.

**Зачем:** ИИ-слой не заменяет раннер, а надстраивается над ним. Пока обычный тест не проходит, генерировать шаги бессмысленно — вы не отличите ошибку модели от сломанного окружения.

**Как проверить:** демо-тест зелёный, структура каталогов создана.

```bash
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` открывает браузер и показывает, как тест кликает по форме. Это ваш эталон поведения — с ним потом сравниваются шаги от модели.

## Шаг 2. Подключить ИИ-слой и кэш шагов
![Иллюстрация к разделу «Шаг 2. Подключить ИИ-слой и кэш шагов»](https://ai-release.org/guides/img/kak-podklyuchit-ii-testirovschik-e2e-k-proektu-nastroyk-ru-2.jpg?v=1794300601)

**Что сделать:** написать скрипт, который отправляет модели цель и HTML-разметку страницы, получает JSON-массив шагов и сохраняет его в `ai-steps.json`. Для мобильных — тот же скрипт, но вместо HTML подаётся XML-дамп экрана из Appium.

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

**Как проверить:** скрипт печатает число шагов, `ai-steps.json` содержит непустой массив, повторный запуск с тем же файлом не обращается к API (в логе строка про кэш).

Сначала сохраните разметку страницы:

```bash
mkdir -p artifacts
npx playwright open https://staging.<ваш-домен>/login
```

В открывшемся окне Playwright Inspector нажмите «Record» и скопируйте HTML нужного экрана в `artifacts/dom.html`. Для Android:

```bash
adb shell uiautomator dump /sdcard/window_dump.xml
adb pull /sdcard/window_dump.xml artifacts/dom.xml
```

Скрипт вызова модели `ai/generate.mjs`:

```js
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}`);
```

Запуск:

```bash
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](https://ai-release.org/guides/kak-podklyuchit-modeli-openai-cherez-google-cloud-posha.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide).

Для мобильных поднимите Appium и драйвер:

```bash
npm i -D appium
npx appium driver install uiautomator2
npx appium
```

**Пример:** `node ai/generate.mjs "войти под пользователем demo" artifacts/dom.xml` — тот же скрипт для Android, потому что формат ответа модели не зависит от платформы. Меняется только источник разметки.

## Шаг 3. Прогнать в CI и убедиться, что тесты воспроизводимы
![Иллюстрация к разделу «Шаг 3. Прогнать в CI и убедиться, что тесты воспроизводимы»](https://ai-release.org/guides/img/kak-podklyuchit-ii-testirovschik-e2e-k-proektu-nastroyk-ru-3.jpg?v=1794300607)

**Что сделать:** запустить набор локально три раза подряд и один раз в GitHub Actions, проверить отчёт и код возврата.

**Зачем:** ИИ-тесты склонны к «миганию» (flaky). Три прогона подряд отделяют стабильный тест от случайно зелёного.

**Как проверить:** все три прогона дают `passed`, `echo $?` возвращает `0`, отчёт открывается в браузере.

```bash
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`:

```yaml
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` не видит устройство.
- Решение: запустите AVD из Android Studio и проверьте `adb devices`. В выводе должна быть строка `emulator-5554	device` (слово `device`, не `offline`). Затем перезапустите `npx appium`.

## Резюме и следующие шаги
Вы получили связку «раннер + ИИ-слой + кэш шагов + CI», где модель только предлагает шаги, а выполняет их Playwright или Appium. Дальше расширяйте кэш на новые сценарии и переносите мобильные тесты в отдельный job с запуском эмулятора на раннере.

## FAQ
**Нужен ли ИИ-тестировщику доступ к продакшену?**
Нет. Работайте со staging-стендом и тестовой учётной записью: сгенерированные шаги могут содержать деструктивные действия, а откатить их на реальных данных нечем.

**Можно ли использовать гайд без ИИ-модели?**
Да. Шаги 1 и 3 и весь CI-конфиг работают с обычными тестами; `ai/generate.mjs` — опциональный слой, кэш в `ai-steps.json` можно заполнять вручную.

**Сколько стоит прогон?**
Зависит от провайдера и модели — тарифы сверьте с документацией вашего провайдера. Расход снижает кэш: при совпадении `goal` вызов API не выполняется.

**Подходит ли схема для iOS?**
Да, через Appium с XCUITest-драйвером, но только на macOS с установленным Xcode. Разметку экрана получают тем же способом и подают в тот же скрипт.
