Files
wiki/tasks/ui-test-skill/DEV_TASK.md
2026-05-13 00:20:05 +03:00

22 KiB
Raw Blame History

DEV TASK: UI/UX Auto-Testing Skill

Статус: Ready for dev
Проект: ui-test-skill
BRD: tasks/ui-test-skill/BRD.md


Цель

Скилл для автономного UI/UX тестирования веб-приложений: запуск Chromium через Playwright, выполнение сценариев, скриншоты, анализ через vision-модель, отчёт.

Архитектура

Playwright (Node.js) запускает Chromium headless shell напрямую (без OpenClaw browser plugin). Скрипт-раннер читает тест-кейсы из markdown, выполняет шаги, делает скриншоты. Стрим анализирует скриншоты через image tool и формирует отчёт.

SKILL.md (инструкции для Стрим)
    ↓
scripts/run_tests.js (раннер) ← TEST_CASES_*.md
    ↓
Playwright + Chromium headless shell
    ↓
screenshots/ → Стрим анализирует через image tool
    ↓
report.md (результат)

Стек / Зависимости

  • Node.js (уже есть: v24.14.0)
  • Playwright (уже установлен: npx playwright)
  • Chromium headless shell (уже скачан: ~/.cache/ms-playwright/chromium_headless_shell-1223/)
  • Системные .so библиотеки (уже извлечены: ~/chromium-libs/libs/)

Инфраструктура

Параметр Значение
Workspace /home/node/.openclaw/workspace/skills/ui-test/
Chromium ~/.cache/ms-playwright/chromium_headless_shell-1223/chrome-headless-shell-linux64/chrome-headless-shell
LD_LIBRARY_PATH ~/chromium-libs/libs/usr/lib/x86_64-linux-gnu:~/chromium-libs/libs/lib/x86_64-linux-gnu
Screenshots tasks/{project}/reports/screenshots/
Reports tasks/{project}/reports/

Файловая карта

Действие Файл Ответственность
Создать skills/ui-test/SKILL.md Инструкции для Стрим
Создать skills/ui-test/scripts/run_tests.js Раннер тестов (Playwright)
Создать skills/ui-test/scripts/parse_testcases.js Парсер markdown тест-кейсов
Создать skills/ui-test/scripts/package.json Зависимости (playwright)
Создать skills/ui-test/scripts/health_check.js Проверка готовности окружения
Создать skills/ui-test/examples/TEST_CASES_EXAMPLE.md Пример формата тест-кейсов

Задачи

Task 1: Health check скрипт

Файлы:

  • Создать: skills/ui-test/scripts/health_check.js

Шаги:

  • 1.1 Создать скрипт проверки окружения:
#!/usr/bin/env node
// health_check.js — проверяет что всё готово для UI-тестирования

const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

const CHROMIUM_PATH = path.join(
  process.env.HOME,
  '.cache/ms-playwright/chromium_headless_shell-1223/chrome-headless-shell-linux64/chrome-headless-shell'
);

const LIB_PATH = path.join(process.env.HOME, 'chromium-libs/libs/usr/lib/x86_64-linux-gnu');

const checks = [];

// 1. Chromium binary exists
if (fs.existsSync(CHROMIUM_PATH)) {
  checks.push({ name: 'Chromium binary', status: 'OK' });
} else {
  checks.push({ name: 'Chromium binary', status: 'FAIL', detail: `Not found: ${CHROMIUM_PATH}` });
}

// 2. Libraries exist
if (fs.existsSync(LIB_PATH)) {
  const libs = fs.readdirSync(LIB_PATH).filter(f => f.endsWith('.so') || f.includes('.so.'));
  checks.push({ name: 'Shared libraries', status: libs.length > 20 ? 'OK' : 'FAIL', detail: `${libs.length} .so files` });
} else {
  checks.push({ name: 'Shared libraries', status: 'FAIL', detail: `Not found: ${LIB_PATH}` });
}

// 3. Chromium launches
try {
  const env = { ...process.env, LD_LIBRARY_PATH: `${LIB_PATH}:${path.join(process.env.HOME, 'chromium-libs/libs/lib/x86_64-linux-gnu')}` };
  execSync(`"${CHROMIUM_PATH}" --headless --disable-gpu --no-sandbox --dump-dom about:blank`, { env, timeout: 10000, stdio: 'pipe' });
  checks.push({ name: 'Chromium launches', status: 'OK' });
} catch (e) {
  checks.push({ name: 'Chromium launches', status: 'FAIL', detail: e.message.slice(0, 100) });
}

// 4. Playwright available
try {
  require('playwright-core');
  checks.push({ name: 'Playwright module', status: 'OK' });
} catch {
  checks.push({ name: 'Playwright module', status: 'FAIL', detail: 'npm install playwright-core needed' });
}

// Output
const allOk = checks.every(c => c.status === 'OK');
console.log(allOk ? '✅ All checks passed' : '❌ Some checks failed');
checks.forEach(c => {
  const icon = c.status === 'OK' ? '✓' : '✗';
  console.log(`  ${icon} ${c.name}: ${c.status}${c.detail ? ' — ' + c.detail : ''}`);
});
process.exit(allOk ? 0 : 1);
  • 1.2 Проверить:
cd /home/node/.openclaw/workspace/skills/ui-test/scripts && node health_check.js
# Ожидаемый результат: ✅ All checks passed

Критерий готовности: Скрипт запускается, все 4 проверки зелёные.


Task 2: Парсер тест-кейсов

Файлы:

  • Создать: skills/ui-test/scripts/parse_testcases.js

Шаги:

  • 2.1 Создать парсер markdown → JSON:

Формат входного markdown:

### TC-XX — Название теста
**Тип:** ui
**Viewport:** desktop | mobile | both
**URL:** https://example.com/

**Шаги:**
1. navigate: {url}
2. wait: 3000
3. click: "#button-id"
4. screenshot: "step-name"
5. check-visual: "Описание что проверяем"

**Визуальные критерии:**
- Нет пустых белых/чёрных областей
- Текст читаем

Формат выхода (JSON):

[
  {
    "id": "TC-XX",
    "name": "Название теста",
    "type": "ui",
    "viewport": "both",
    "url": "https://example.com/",
    "steps": [
      { "action": "navigate", "value": "{url}" },
      { "action": "wait", "value": 3000 },
      { "action": "click", "value": "#button-id" },
      { "action": "screenshot", "value": "step-name" },
      { "action": "check-visual", "value": "Описание что проверяем" }
    ],
    "criteria": ["Нет пустых белых/чёрных областей", "Текст читаем"]
  }
]

Парсер должен:

  • Читать файл markdown
  • Извлекать только тест-кейсы с **Тип:** ui
  • Игнорировать тесты без типа ui (они для curl/grep)
  • Выводить JSON в stdout
node parse_testcases.js /path/to/TEST_CASES.md
  • 2.2 Проверить на примере:
node parse_testcases.js ../examples/TEST_CASES_EXAMPLE.md | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); console.log(d.length + ' tests parsed')"
# Ожидаемый результат: N tests parsed (N > 0)

Критерий готовности: Парсер корректно извлекает UI тест-кейсы из markdown в JSON.


Task 3: Раннер тестов (основной скрипт)

Файлы:

  • Создать: skills/ui-test/scripts/run_tests.js
  • Создать: skills/ui-test/scripts/package.json

Шаги:

  • 3.1 Создать package.json:
{
  "name": "ui-test-runner",
  "version": "1.0.0",
  "private": true,
  "dependencies": {
    "playwright-core": "^1.52.0"
  }
}
  • 3.2 Установить зависимости:
cd /home/node/.openclaw/workspace/skills/ui-test/scripts && npm install
  • 3.3 Создать run_tests.js:

Скрипт должен:

  1. Принимать аргументы: node run_tests.js <test-cases.md> <output-dir>
  2. Парсить тест-кейсы через parse_testcases.js
  3. Запускать Chromium через playwright-core с правильным LD_LIBRARY_PATH
  4. Для каждого теста:
    • Установить viewport (desktop: 1280×720, mobile: 375×812)
    • Выполнить шаги: navigate, wait, click, scroll, screenshot
    • Сохранить скриншоты в <output-dir>/screenshots/
  5. Сформировать JSON-отчёт: <output-dir>/results.json

Ключевые моменты реализации:

const { chromium } = require('playwright-core');
const path = require('path');

const CHROMIUM_PATH = path.join(
  process.env.HOME,
  '.cache/ms-playwright/chromium_headless_shell-1223/chrome-headless-shell-linux64/chrome-headless-shell'
);

const LIB_PATH = [
  path.join(process.env.HOME, 'chromium-libs/libs/usr/lib/x86_64-linux-gnu'),
  path.join(process.env.HOME, 'chromium-libs/libs/lib/x86_64-linux-gnu')
].join(':');

async function launchBrowser(viewport) {
  const browser = await chromium.launch({
    executablePath: CHROMIUM_PATH,
    headless: true,
    args: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'],
    env: { ...process.env, LD_LIBRARY_PATH: LIB_PATH }
  });
  const context = await browser.newContext({
    viewport: viewport === 'mobile' 
      ? { width: 375, height: 812 } 
      : { width: 1280, height: 720 },
    deviceScaleFactor: viewport === 'mobile' ? 2 : 1,
    isMobile: viewport === 'mobile',
    userAgent: viewport === 'mobile' 
      ? 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15'
      : undefined
  });
  return { browser, context };
}

async function executeStep(page, step, screenshotDir, testId) {
  switch (step.action) {
    case 'navigate':
      await page.goto(step.value, { waitUntil: 'networkidle', timeout: 30000 });
      break;
    case 'wait':
      await page.waitForTimeout(parseInt(step.value));
      break;
    case 'click':
      await page.click(step.value, { timeout: 5000 });
      break;
    case 'scroll':
      await page.evaluate((px) => window.scrollBy(0, parseInt(px)), step.value);
      break;
    case 'screenshot':
      const filename = `${testId}-${step.value}.png`;
      await page.screenshot({ path: path.join(screenshotDir, filename), fullPage: false });
      return { screenshot: filename };
      break;
    case 'check-visual':
      // Скриншот для визуальной проверки (будет анализироваться Стрим через image tool)
      const checkFile = `${testId}-check-${Date.now()}.png`;
      await page.screenshot({ path: path.join(screenshotDir, checkFile), fullPage: false });
      return { screenshot: checkFile, checkDescription: step.value };
      break;
    case 'resize':
      const [w, h] = step.value.split('x').map(Number);
      await page.setViewportSize({ width: w, height: h });
      break;
  }
  return null;
}

Формат results.json:

{
  "timestamp": "2026-05-12T21:00:00Z",
  "testFile": "TEST_CASES_TERRAIN.md",
  "results": [
    {
      "id": "TC-07",
      "name": "Кнопка terrain",
      "viewport": "desktop",
      "status": "completed",
      "screenshots": ["TC-07-desktop-initial.png", "TC-07-desktop-after-click.png"],
      "checks": [
        { "description": "Попап виден", "screenshot": "TC-07-desktop-check-1715...png" }
      ],
      "error": null
    }
  ]
}
  • 3.4 Проверить на тестовом URL:
cd /home/node/.openclaw/workspace/skills/ui-test/scripts
node run_tests.js ../examples/TEST_CASES_EXAMPLE.md /tmp/ui-test-output
ls /tmp/ui-test-output/screenshots/
cat /tmp/ui-test-output/results.json | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); console.log(d.results.length + ' tests completed')"

Критерий готовности: Раннер запускает Chromium, выполняет шаги, сохраняет скриншоты и results.json.


Task 4: Пример тест-кейсов

Файлы:

  • Создать: skills/ui-test/examples/TEST_CASES_EXAMPLE.md

Шаги:

  • 4.1 Создать пример с 3 тест-кейсами для enduro-trails:
# UI Test Cases: Enduro Trails (Example)

### TC-UI-01 — Загрузка карты
**Тип:** ui
**Viewport:** both
**URL:** https://openclaw.mva154.duckdns.org/enduro/

**Шаги:**
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 3000
3. screenshot: "initial-load"
4. check-visual: "Карта загружена, тайлы отрисованы, нет белых пустот. Toolbar виден внизу."

**Визуальные критерии:**
- Карта занимает весь экран
- Нет белых/серых незагруженных областей
- Toolbar с кнопками виден

---

### TC-UI-02 — Переключение темы
**Тип:** ui
**Viewport:** desktop
**URL:** https://openclaw.mva154.duckdns.org/enduro/

**Шаги:**
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 3000
3. screenshot: "before-theme-switch"
4. click: "#theme-toggle"
5. wait: 2000
6. screenshot: "after-theme-switch"
7. check-visual: "Тема переключилась: фон карты изменился (светлый↔тёмный), кнопки toolbar сменили цвет"

**Визуальные критерии:**
- Скриншоты before/after визуально отличаются
- Карта не пустая после переключения
- Toolbar адаптировался к новой теме

---

### TC-UI-03 — Terrain попап (мобильный)
**Тип:** ui
**Viewport:** mobile
**URL:** https://openclaw.mva154.duckdns.org/enduro/

**Шаги:**
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 3000
3. click: "#terrain-btn"
4. wait: 1000
5. screenshot: "terrain-popup-mobile"
6. check-visual: "Попап terrain виден полностью, не обрезан снизу. Чекбоксы кликабельного размера (>44px)."

**Визуальные критерии:**
- Попап не выходит за границы экрана
- Чекбоксы достаточного размера для тапа
- Текст читаем

Критерий готовности: Файл существует, парсер корректно извлекает 3 теста.


Task 5: SKILL.md

Файлы:

  • Создать: skills/ui-test/SKILL.md

Шаги:

  • 5.1 Создать SKILL.md с полными инструкциями:
---
name: ui-test
description: "Автономное UI/UX тестирование веб-приложений через Playwright + vision analysis. Запуск тестов, скриншоты, визуальный анализ, отчёт."
---

# UI/UX Auto-Testing Skill

## Когда использовать

- После деплоя новой фичи — проверить что ничего не сломалось
- По запросу Славы — "проверь UI"
- Перед релизом фазы — регрессионный прогон всех тестов

## Порядок тестирования

1. Unit/integration тесты (curl/grep, API)
2. UI/UX тесты (этот скилл) — ПОСЛЕДНИМИ

## Быстрый старт

### 1. Проверить готовность

\`\`\`bash
cd /home/node/.openclaw/workspace/skills/ui-test/scripts && node health_check.js
\`\`\`

Все проверки должны быть ✅. Если нет — см. раздел "Установка".

### 2. Запустить тесты

\`\`\`bash
cd /home/node/.openclaw/workspace/skills/ui-test/scripts
node run_tests.js /path/to/TEST_CASES.md /path/to/output-dir
\`\`\`

Пример для enduro-trails:
\`\`\`bash
node run_tests.js \
  /home/node/.openclaw/workspace/tasks/enduro-trails/TEST_CASES_UI.md \
  /home/node/.openclaw/workspace/tasks/enduro-trails/reports
\`\`\`

### 3. Проанализировать скриншоты

После запуска раннера — проанализировать скриншоты через `image` tool:

Для каждого check из `results.json`:
- Открыть скриншот через image tool
- Промпт: "Ты — QA-инженер. Проанализируй скриншот. {check.description}. Есть ли визуальные проблемы? Ответь: pass/fail + описание проблем если есть."
- Записать результат в отчёт

### 4. Сформировать отчёт

Создать `reports/ui-test-YYYY-MM-DD.md`:

\`\`\`markdown
# UI Test Report: {project}
**Дата:** {date}
**Тесты:** {total} | ✅ {passed} | ❌ {failed}

| # | Тест | Desktop | Mobile | Проблемы |
|---|------|---------|--------|----------|
| TC-XX | Название | ✅/❌ | ✅/❌ | описание |
\`\`\`

### 5. Отправить результат Славе

Всегда отправлять результат — даже если всё зелёное.

## Формат тест-кейсов

Файл: `TEST_CASES_UI.md` (или секция в общем TEST_CASES файле)

\`\`\`markdown
### TC-XX — Название
**Тип:** ui
**Viewport:** desktop | mobile | both
**URL:** https://...

**Шаги:**
1. navigate: {url}
2. wait: {ms}
3. click: "{selector}"
4. scroll: {pixels}
5. resize: {width}x{height}
6. screenshot: "{name}"
7. check-visual: "{описание проверки}"

**Визуальные критерии:**
- Критерий 1
- Критерий 2
\`\`\`

### Доступные действия

| Действие | Формат | Описание |
|----------|--------|----------|
| navigate | `navigate: {url}` | Перейти на URL |
| wait | `wait: {ms}` | Подождать N миллисекунд |
| click | `click: "{selector}"` | Кликнуть по CSS-селектору |
| scroll | `scroll: {pixels}` | Прокрутить вниз на N пикселей |
| resize | `resize: {w}x{h}` | Изменить viewport |
| screenshot | `screenshot: "{name}"` | Сделать скриншот |
| check-visual | `check-visual: "{desc}"` | Скриншот + пометка для vision-анализа |

## Установка (если health_check падает)

### Chromium headless shell
\`\`\`bash
npx --yes playwright install chromium
\`\`\`

### Системные библиотеки (без root)
\`\`\`bash
# Библиотеки уже извлечены в ~/chromium-libs/libs/
# Если нет — скачать deb-пакеты и извлечь через dpkg-deb:
mkdir -p ~/chromium-libs && cd ~/chromium-libs
# ... (список URL в BRD)
for deb in *.deb; do dpkg-deb -x "$deb" libs/; done
\`\`\`

### Node.js зависимости
\`\`\`bash
cd /home/node/.openclaw/workspace/skills/ui-test/scripts && npm install
\`\`\`

## Ограничения

- Chromium headless — нет GPU рендеринга (WebGL карты могут выглядеть иначе)
- Vision-анализ субъективен — false positives возможны
- MapLibre тайлы грузятся по сети — нужен wait 3-5 сек после navigate
- Мобильный viewport эмулирует размер, но не touch-события

Критерий готовности: SKILL.md содержит полные инструкции: быстрый старт, формат тестов, установка, ограничения.


Проверка (Acceptance)

# Проверка Команда / Действие Ожидаемый результат
1 Health check node health_check.js All checks passed
2 Парсер node parse_testcases.js examples/TEST_CASES_EXAMPLE.md Валидный JSON, 3 теста
3 Раннер desktop node run_tests.js examples/TEST_CASES_EXAMPLE.md /tmp/test1 screenshots/ содержит PNG файлы
4 Раннер mobile Тест с viewport: mobile Скриншот 375×812
5 results.json cat /tmp/test1/results.json Валидный JSON со статусами
6 Реальный URL Тест на openclaw.mva154.duckdns.org/enduro/ Скриншот с картой (не пустой)

Ограничения и контекст

  • ⚠️ Chromium запускается с LD_LIBRARY_PATH — обязательно передавать env в chromium.launch()
  • ⚠️ --no-sandbox обязателен (контейнер без root)
  • ⚠️ --disable-dev-shm-usage — /dev/shm маленький в контейнере
  • ⚠️ MapLibre карта грузит тайлы по сети — wait минимум 3000ms после navigate
  • ⚠️ Headless shell не поддерживает --headless=new — использовать просто headless: true
  • 🚫 НЕ использовать OpenClaw browser tool — работаем через Playwright напрямую
  • 🚫 НЕ устанавливать ничего через apt/sudo — всё уже есть

Деплой-чеклист

  • scripts/health_check.js — работает
  • scripts/parse_testcases.js — парсит markdown
  • scripts/run_tests.js — запускает тесты, делает скриншоты
  • scripts/package.json + node_modules/ — playwright-core установлен
  • examples/TEST_CASES_EXAMPLE.md — пример тестов
  • SKILL.md — полные инструкции
  • Тест на реальном URL — скриншот не пустой

Создано: 2026-05-12 | Автор ТЗ: Стрим | Исполнитель: Dev-агент