auto-sync: 2026-06-02 21:00:01
This commit is contained in:
422
tasks/orchestrator/proposal_v1/09_ui_testing.md
Normal file
422
tasks/orchestrator/proposal_v1/09_ui_testing.md
Normal file
@@ -0,0 +1,422 @@
|
||||
# 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:** в 2025–2026 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 укладываются в 5–10 минут.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user