22 KiB
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:
Скрипт должен:
- Принимать аргументы:
node run_tests.js <test-cases.md> <output-dir> - Парсить тест-кейсы через
parse_testcases.js - Запускать Chromium через playwright-core с правильным
LD_LIBRARY_PATH - Для каждого теста:
- Установить viewport (desktop: 1280×720, mobile: 375×812)
- Выполнить шаги: navigate, wait, click, scroll, screenshot
- Сохранить скриншоты в
<output-dir>/screenshots/
- Сформировать 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— парсит markdownscripts/run_tests.js— запускает тесты, делает скриншотыscripts/package.json+node_modules/— playwright-core установленexamples/TEST_CASES_EXAMPLE.md— пример тестовSKILL.md— полные инструкции- Тест на реальном URL — скриншот не пустой
Создано: 2026-05-12 | Автор ТЗ: Стрим | Исполнитель: Dev-агент