Files
wiki/tasks/multi-agent/proposal_v1/09_ui_testing.md
2026-05-15 00:50:01 +03:00

423 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 09. Стратегия UI-тестирования
**Назначение:** ваш отдельный пункт «нужно добиться полного тестирования от агентов включая тестирования UI» развернут в конкретный план: какие виды UI-тестов, на каком инструменте, как агент-Tester их запускает, как обновляются baseline'ы, как обрабатываются flaky.
---
## Простым языком
UI-тестирование — самая тонкая часть автоматизации, потому что:
- интерфейс зависит от шрифтов, рендеринга, времени, асинхронности;
- результат «работает или нет» — частично визуальный (выглядит правильно), частично функциональный (нажал кнопку — событие произошло), частично доступный (слепой пользователь сможет пройти).
Поэтому UI-тесты делятся на **четыре уровня**, каждый отвечает за свой аспект:
1. **Компонентные тесты** — проверяют, что отдельный кусочек UI ведёт себя правильно (вне браузера или в jsdom).
2. **E2E-тесты** — реальный браузер, реальные клики; проверяют сценарии пользователя из Acceptance Criteria.
3. **Visual regression** — сравнение скриншотов «было / стало». Защищает от случайных визуальных регрессий.
4. **A11y-тесты** — автопроверка доступности (контраст, ARIA, фокус, клавиатура).
Плюс две сопровождающие проверки: **производительность** (Lighthouse, p95 latency) и **безопасность** (ZAP baseline для UI).
Все эти тесты — обязательные ворота на QG-6. Без зелёного UI-теста задача с UI не уйдёт в деплой.
---
## Стек
| Уровень | Инструмент | Где живут тесты |
|---------|-----------|----------------|
| Компонентные | Vitest / Jest + Testing Library | `tests/components/*.test.{ts,tsx}` |
| E2E | **Playwright** (Chromium + Firefox + WebKit) | `tests/e2e/*.spec.ts` |
| Visual regression | Playwright `toHaveScreenshot` или Loki / Chromatic | `tests/e2e/*` + `tests/visual/baseline/` |
| A11y | `@axe-core/playwright` | внутри e2e тестов как доп. проверка |
| Performance | Lighthouse CI | `tests/perf/lighthouse.config.json` |
| Load | k6 / Locust | `tests/perf/load.js` |
| Security | OWASP ZAP baseline | `tests/security/zap.conf` |
> **Почему Playwright:** в 20252026 Playwright стал мейнстримом для UI-тестирования (быстрее Cypress, кросс-браузерный из коробки, отличный visual regression, удобный для агентов через MCP). У него есть официальный MCP-сервер, который агенту даёт прямой контроль над браузером.
---
## Test Plan: что именно тестируется
`04-test-plan.yaml` для UI-овой задачи содержит TC всех уровней. Пример:
```yaml
plane_id: PROJ-123
test_cases:
# === Component-level ===
- id: TC-1
title: "NoiseZoneToggle renders with default state"
type: unit
priority: P1
automation:
tool: vitest
file: tests/components/NoiseZoneToggle.test.tsx
coverage: [REQ-F-1]
# === E2E ===
- id: TC-2
title: "User toggles noise zones layer on map"
type: e2e
priority: P0
automation:
tool: playwright
file: tests/e2e/noise-zones-toggle.spec.ts
coverage: [REQ-F-1, AC-1]
browsers: [chromium, firefox, webkit]
- id: TC-3
title: "Mobile: noise zones legend collapses"
type: e2e
priority: P1
automation:
tool: playwright
file: tests/e2e/noise-zones-mobile.spec.ts
viewport: { width: 375, height: 667 }
coverage: [REQ-F-3]
# === Visual regression ===
- id: TC-4
title: "Map with noise zones — visual baseline"
type: visual
priority: P0
automation:
tool: playwright-visual
file: tests/e2e/noise-zones.spec.ts
snapshot: noise-zones-default
threshold: 0.01
coverage: [REQ-NF-UI-1]
# === A11y ===
- id: TC-5
title: "Noise zones panel — a11y AA compliance"
type: a11y
priority: P0
automation:
tool: axe-core
file: tests/e2e/noise-zones-a11y.spec.ts
rules: [wcag2a, wcag2aa]
coverage: [REQ-NF-A11Y-1]
# === Performance ===
- id: TC-6
title: "Map load time with noise zones"
type: performance
priority: P1
automation:
tool: lighthouse
url: https://${PREVIEW_HOST}/map?layer=noise
thresholds:
performance: 90
accessibility: 95
LCP_ms: 2500
coverage: [REQ-NF-PERF-1]
```
---
## Как агент-Tester работает с UI-тестами
### Запуск регресса
```bash
# 1. Проверяет, что preview-окружение здорово
curl -fsS $PREVIEW_URL/health || exit 1
# 2. Запускает все TC из test-plan
python scripts/run-test-plan.py \
--plan docs/work-items/$PLANE_ID/04-test-plan.yaml \
--preview-url $PREVIEW_URL \
--output docs/work-items/$PLANE_ID/13-test-report/
# Скрипт парсит test-plan, для каждого TC вызывает соответствующий runner:
# - vitest для unit
# - playwright test для e2e и visual
# - playwright + axe для a11y
# - lighthouse-ci для perf
```
### Через MCP (когда агент работает интерактивно)
Playwright MCP даёт прямой контроль:
```
agent: playwright_navigate({ url: "https://pr-142.preview.example.com/map" })
agent: playwright_click({ selector: "[data-testid='noise-toggle']" })
agent: playwright_wait_for({ selector: "[data-testid='noise-layer']" })
agent: playwright_screenshot({ path: "screenshots/tc-2-after-toggle.png" })
```
Полезно для интерактивной отладки или когда автотест есть, но требует расширенной диагностики.
### Обработка failing-теста
1. Tester-агент получает stack-trace и/или скриншот failing-теста.
2. Анализирует: это **баг кода** или **проблема теста**?
- Если код не делает то, что в ТЗбаг кода. Заводится Plane issue с шаблоном (`bug:found-by-qa`), привязка к Work Item, лейбл `back-to:dev`.
- Если тест неверно описывает ожидание — это **проблема теста**, заводится отдельная задача `tech-debt:fix-flaky-test-X`, TC помечается `quarantined`, **не блокирует** релиз (с оговоркой: квота на quarantined ≤ 5% от тестов).
3. Если flaky (3 попытки, 2 раза падает, 1 раз проходит) — TC автоматически помечается `flaky`, задача в Plane, не блокирует релиз.
---
## Visual regression: подход
**Стратегия:** использовать Playwright `toHaveScreenshot()` со снапшотами, хранящимися в `tests/visual/baseline/`. Снапшот — это PNG, версионируется в Git.
**Threshold по умолчанию:** 0.01 (1% pixel difference). Можно ужесточать на критичных экранах.
**Управление baseline'ами:**
- При **первом** запуске теста (новый снапшот) — Playwright автоматически создаёт baseline и фейлит тест. Developer/Designer обновляет baseline через `playwright test --update-snapshots` локально и коммитит.
- При **изменении дизайна** (намеренном) — Designer-агент обновляет baseline в своём этапе через Playwright MCP, кладёт новый PNG в `tests/visual/baseline/`. Diff приложен в комментарий PR.
- **Любой diff в visual regression** → CI красный. Никакого «авто-обновления baseline'а в CI» — только через явное человеческое или агентское действие.
**Что попадает в baseline:**
- Скриншоты ключевых экранов в desktop (1280×800) и mobile (375×667).
- На каждый ключевой компонент — отдельный визуальный тест в Playwright Component Testing.
- Не каждый чих — только то, на что в ТЗ есть UI-требование. Иначе baseline'ы становятся неуправляемыми.
**Что исключается:**
- Динамические элементы (timestamps, рандомные данные, видео, GIF) — маскируются через `mask: [page.locator('.timestamp')]`.
- Анимации — отключаются через `animations: 'disabled'`.
---
## A11y-тесты: подход
**Инструмент:** `@axe-core/playwright`. Запускается на каждом затронутом экране.
**Правила:** `wcag2a` + `wcag2aa` (по умолчанию). Опционально — `wcag2aaa` для критичных экранов.
**Минимальный шаблон теста:**
```typescript
// tests/e2e/noise-zones-a11y.spec.ts
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('Noise zones panel: WCAG AA', async ({ page }) => {
await page.goto('/map?layer=noise');
await page.locator('[data-testid="noise-toggle"]').click();
await page.waitForSelector('[data-testid="noise-layer"]');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.analyze();
expect(results.violations).toEqual([]);
});
```
**Что покрывается обязательно** (из чек-листа в `11-design/a11y.md`):
- Контраст ≥ 4.5:1 для текста, ≥ 3:1 для UI-элементов.
- Все интерактивные элементы доступны с клавиатуры (Tab/Shift-Tab, Enter, Space, Escape).
- Focus visible (focus ring или аналогичный индикатор).
- ARIA-роли для нестандартных компонентов.
- Alt-тексты для изображений.
- Lang-атрибут на `<html>`.
- `prefers-reduced-motion` уважается.
---
## Cross-browser
Playwright поддерживает **Chromium, Firefox, WebKit** в одном API. Конфигурация:
```typescript
// playwright.config.ts
projects: [
{ name: 'chromium', use: devices['Desktop Chrome'] },
{ name: 'firefox', use: devices['Desktop Firefox'] },
{ name: 'webkit', use: devices['Desktop Safari'] },
{ name: 'mobile', use: devices['iPhone 13'] },
]
```
**Принцип:** P0 e2e — на всех 4 проектах. P1 — на Chromium + один из (Firefox/WebKit) + mobile. P2/P3 — только Chromium.
**В CI:** все запуски параллельны через matrix-strategy, на одном раннере 4-CPU укладываются в 510 минут.
---
## Performance тесты
**Lighthouse CI** на ключевых страницах. Конфигурация:
```json
{
"ci": {
"collect": {
"url": ["http://localhost:3000/", "http://localhost:3000/map"],
"numberOfRuns": 3
},
"assert": {
"assertions": {
"categories:performance": ["error", { "minScore": 0.9 }],
"categories:accessibility": ["error", { "minScore": 0.95 }],
"first-contentful-paint": ["error", { "maxNumericValue": 2500 }],
"largest-contentful-paint": ["error", { "maxNumericValue": 4000 }],
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }]
}
}
}
}
```
**Load tests (k6 / Locust)** — для API-эндпоинтов с NFR по производительности. Запуск только при наличии REQ-NF-PERF в ТЗ. Не на каждый PR (медленно), а на nightly + перед мажорным релизом.
---
## Security baseline (UI)
**OWASP ZAP** baseline scan — пассивный (без brute-force) скан preview-URL. Включается, если в ТЗ есть REQ-NF-SEC или фича обрабатывает пользовательский ввод.
```bash
docker run --rm -v $(pwd)/tests/security:/zap/wrk \
ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py -t $PREVIEW_URL -g gen.conf -r zap-report.html
```
Алерты уровня High блокируют QG-6. Medium — issue в Plane, не блокируют.
**Дополнительно:** Trivy на собранный образ (контейнер) — на каждом CI; npm audit / pip-audit / cargo audit — на каждом CI.
---
## Где живут тесты в репозитории
```
tests/
├── components/ # vitest / jest, jsdom
│ └── NoiseZoneToggle.test.tsx
├── e2e/ # playwright
│ ├── noise-zones-toggle.spec.ts
│ ├── noise-zones-mobile.spec.ts
│ └── noise-zones-a11y.spec.ts
├── visual/
│ └── baseline/ # PNG снапшотов
│ ├── chromium-desktop/
│ ├── firefox-desktop/
│ ├── webkit-desktop/
│ └── chromium-mobile/
├── perf/
│ └── lighthouse.config.json
│ └── load.js # k6
├── security/
│ └── zap.conf
├── fixtures/ # сидируется в preview-окружение
│ ├── users.json
│ └── flights.csv
├── smoke/ # минимальный набор для smoke в test/prom
│ └── api-health.spec.ts
└── README.md # описание структуры тестов
```
---
## CI: пайплайн UI-тестов
```yaml
# .github/workflows/qg-test.yml
name: QG-6 Test
on:
pull_request:
types: [labeled]
jobs:
e2e:
if: github.event.label.name == 'stage:test'
runs-on: ubuntu-latest
strategy:
matrix:
project: [chromium, firefox, webkit, mobile]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test --project=${{ matrix.project }}
- if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-${{ matrix.project }}
path: |
test-results/
playwright-report/
visual:
needs: e2e
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npx playwright test --grep @visual
- run: ./scripts/visual-diff-summary.sh > docs/work-items/${PLANE_ID}/13-test-report/visual-diff.md
a11y:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npx playwright test --grep @a11y
perf:
runs-on: ubuntu-latest
steps:
- uses: treosh/lighthouse-ci-action@v11
with:
configPath: ./tests/perf/lighthouse.config.json
security-baseline:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'has-ui')
steps:
- run: ./scripts/zap-baseline.sh $PREVIEW_URL
generate-report:
needs: [e2e, visual, a11y, perf]
runs-on: ubuntu-latest
if: always()
steps:
- uses: actions/checkout@v4
- run: ./scripts/generate-test-report.py > docs/work-items/${PLANE_ID}/13-test-report.md
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "test(qa): test report for PROJ-123"
```
---
## Flaky tests: процедура
Flaky — тест, который при одном и том же коде иногда проходит, иногда падает.
1. Detection: CI runner ведёт счётчик. Если тест в течение 30 дней падал и проходил на одном и том же SHA — он flaky.
2. Tester-агент при обнаружении flaky:
- Помечает TC в test-plan: `quarantined: true, reason: flaky-N-times-in-7-days`.
- Заводит Plane issue `tech-debt:flaky-test-<id>` с историей запусков.
- В test-report указывает: «N TC quarantined, не блокирует релиз».
3. Quarantined тесты запускаются в CI, но падение не блокирует merge. Сводка в test-report.
4. **Лимит карантина:** ≤5% от общего числа тестов. При превышении — лейбл `escalation:test-quality` на проект, обязательное вмешательство Owner.
---
## Когда UI-тестов нет — что делать
Если задача не затрагивает UI (`ui_affected: false` в ТЗ), Designer-этап автозакрывается, **никаких UI-тестов не пишется**. Tester ограничивается unit/integration/perf/security.
---
## Антипаттерны UI-тестирования
- ❌ «Smoke test» для UI: только зайти на главную и проверить, что не упало. Это unit-тест на `<App />`, а не e2e.
- ❌ Тестировать через `data-testid`, расставленные **только для тестов**. Лучше — тестировать через ARIA-роли и видимый текст.
- ❌ Sleep'ы в e2e (`await page.waitForTimeout(2000)`). Использовать `waitForSelector`/`waitForLoadState`/`waitForResponse`.
- ❌ Хардкодить URL preview в тесте. Только через env-переменную `BASE_URL`.
- ❌ Запускать UI-тесты против test/prom. Только preview.
- ❌ Игнорировать визуальный diff «авось не страшно». Любой diff = либо обновить baseline (намеренно), либо вернуть в Dev.
- ❌ Тестировать на одном браузере. Минимум Chromium + WebKit (последний — приближение к Safari/iOS).
- ❌ Хранить скриншоты ≥1MB в Git. Использовать gzip / quality 80, либо вынести в LFS.
- ❌ Скрипт `playwright test --update-snapshots` на CI без явного флага. Только локально или через явный workflow.