423 lines
19 KiB
Markdown
423 lines
19 KiB
Markdown
# 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.
|