auto-sync: 2026-06-02 21:00:01

This commit is contained in:
Stream
2026-06-02 21:00:01 +03:00
parent 124f6ae20b
commit 7315b66ffa
34 changed files with 9 additions and 2 deletions

View 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:** в 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.