# 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** Создать скрипт проверки окружения: ```javascript #!/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** Проверить: ```bash 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: ```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): ```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 ```bash node parse_testcases.js /path/to/TEST_CASES.md ``` - [ ] **2.2** Проверить на примере: ```bash 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`: ```json { "name": "ui-test-runner", "version": "1.0.0", "private": true, "dependencies": { "playwright-core": "^1.52.0" } } ``` - [ ] **3.2** Установить зависимости: ```bash cd /home/node/.openclaw/workspace/skills/ui-test/scripts && npm install ``` - [ ] **3.3** Создать `run_tests.js`: Скрипт должен: 1. Принимать аргументы: `node run_tests.js ` 2. Парсить тест-кейсы через `parse_testcases.js` 3. Запускать Chromium через playwright-core с правильным `LD_LIBRARY_PATH` 4. Для каждого теста: - Установить viewport (desktop: 1280×720, mobile: 375×812) - Выполнить шаги: navigate, wait, click, scroll, screenshot - Сохранить скриншоты в `/screenshots/` 5. Сформировать JSON-отчёт: `/results.json` Ключевые моменты реализации: ```javascript 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`: ```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: ```bash 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: ```markdown # 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 с полными инструкциями: ```markdown --- 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-агент*