19 KiB
09. Стратегия UI-тестирования
Назначение: ваш отдельный пункт «нужно добиться полного тестирования от агентов включая тестирования UI» развернут в конкретный план: какие виды UI-тестов, на каком инструменте, как агент-Tester их запускает, как обновляются baseline'ы, как обрабатываются flaky.
Простым языком
UI-тестирование — самая тонкая часть автоматизации, потому что:
- интерфейс зависит от шрифтов, рендеринга, времени, асинхронности;
- результат «работает или нет» — частично визуальный (выглядит правильно), частично функциональный (нажал кнопку — событие произошло), частично доступный (слепой пользователь сможет пройти).
Поэтому UI-тесты делятся на четыре уровня, каждый отвечает за свой аспект:
- Компонентные тесты — проверяют, что отдельный кусочек UI ведёт себя правильно (вне браузера или в jsdom).
- E2E-тесты — реальный браузер, реальные клики; проверяют сценарии пользователя из Acceptance Criteria.
- Visual regression — сравнение скриншотов «было / стало». Защищает от случайных визуальных регрессий.
- 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 всех уровней. Пример:
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-тестами
Запуск регресса
# 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-теста
-
Tester-агент получает stack-trace и/или скриншот failing-теста.
-
Анализирует: это баг кода или проблема теста?
- Если код не делает то, что в ТЗ — баг кода. Заводится Plane issue с шаблоном (
bug:found-by-qa), привязка к Work Item, лейблback-to:dev. - Если тест неверно описывает ожидание — это проблема теста, заводится отдельная задача
tech-debt:fix-flaky-test-X, TC помечаетсяquarantined, не блокирует релиз (с оговоркой: квота на quarantined ≤ 5% от тестов).
- Если код не делает то, что в ТЗ — баг кода. Заводится Plane issue с шаблоном (
-
Если 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 для критичных экранов.
Минимальный шаблон теста:
// 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. Конфигурация:
// 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 на ключевых страницах. Конфигурация:
{
"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 или фича обрабатывает пользовательский ввод.
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-тестов
# .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 — тест, который при одном и том же коде иногда проходит, иногда падает.
- Detection: CI runner ведёт счётчик. Если тест в течение 30 дней падал и проходил на одном и том же SHA — он flaky.
- 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, не блокирует релиз».
- Помечает TC в test-plan:
- Quarantined тесты запускаются в CI, но падение не блокирует merge. Сводка в test-report.
- Лимит карантина: ≤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.