Sprint 17 — onboarding-контур: 4-шаг wizard, контекстный help, in-app
feedback, admin self-diagnostic, /whats-new из CHANGELOG.md.
Ключевые цифры:
- Wizard: 4 шага + skip каждого, 7 e2e тестов ✓ за 20 секунд.
- Diagnostic: 7 параллельных проверок, ~80ms на stage.
- Bundle impact: initial +4 KB gzip (только FeedbackWidget +
HelpTooltip + EmptyStateWithDemo в основном bundle; страницы lazy).
- Regression-suite: 35 → 42 flows + 60 → 66 visual snapshots.
Backend (новые endpoint'ы):
- /api/admin/diagnostic/run — 7 параллельных проверок (DB, SMTP,
MinIO, Hangfire, диск, сертификаты, бэкап). Task.WhenAll, ~80ms.
- /api/feedback — POST {category, message}, email на FromEmail +
Telegram (если SupportTelegram:* настроены). Rate-limit 5/час.
- /api/whats-new — парсер CHANGELOG.md, возвращает {buildVersion,
items}. Dockerfile.api копирует CHANGELOG.md в content-root +
пишет VERSION из GIT_SHA build-arg.
Frontend:
- /onboarding-wizard — 4-step builder, состояние в useState,
localStorage.fm.wizardCompleted после завершения.
- <HelpTooltip topic="key"/> — popover на каждой странице, mapping
src/lib/help-topics.ts (13 keys).
- /help — knowledge base, 7 markdown topics через import.meta.glob,
mini-renderer без heavy deps, fuzzy search.
- /whats-new — список из /api/whats-new, иконки по типу (feat/fix).
- /admin/diagnostic — Admin/SuperAdmin only, 🟢/🟡/🔴 индикаторы.
- <FeedbackWidget> в sidebar footer + ссылки на /help и /whats-new.
- <EmptyStateWithDemo> placeholder для будущих видео-демо.
scripts/generate-changelog.sh — git log feat:/fix: за 90 дней
→ CHANGELOG.md (307 строк сгенерировано).
Wizard UX-screenshots в docs/sprint17-screenshots/ (6 PNG: 4 шага +
help + diagnostic).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
67 lines
2.9 KiB
TypeScript
67 lines
2.9 KiB
TypeScript
/**
|
||
* Sprint 17: визуальные screenshots каждого шага wizard'а для отчёта.
|
||
*
|
||
* Шаги 1-4 captured как `wizard-step-N.png`. Используется один build()
|
||
* на весь файл — caпture'ы быстрые.
|
||
*/
|
||
import { expect, test } from '@playwright/test'
|
||
import { OrgFactory, type BuiltOrg } from '../factories/OrgFactory.js'
|
||
import { attachSession } from '../lib/ui.js'
|
||
|
||
let built: BuiltOrg
|
||
|
||
test.beforeAll(async () => {
|
||
built = await OrgFactory.for('wiz-shots').build()
|
||
})
|
||
|
||
test('wizard step 1 (магазин)', async ({ page }) => {
|
||
await attachSession(page, built.session, '/onboarding-wizard')
|
||
await page.waitForLoadState('networkidle')
|
||
await expect(page).toHaveScreenshot('wizard-step-1.png')
|
||
})
|
||
|
||
test('wizard step 2 (товар)', async ({ page }) => {
|
||
await attachSession(page, built.session, '/onboarding-wizard')
|
||
await page.waitForLoadState('networkidle')
|
||
await page.getByRole('button', { name: 'Пропустить', exact: true }).first().click()
|
||
await page.waitForLoadState('networkidle')
|
||
await expect(page).toHaveScreenshot('wizard-step-2.png')
|
||
})
|
||
|
||
test('wizard step 3 (сотрудник)', async ({ page }) => {
|
||
await attachSession(page, built.session, '/onboarding-wizard')
|
||
await page.waitForLoadState('networkidle')
|
||
await page.getByRole('button', { name: 'Пропустить', exact: true }).first().click()
|
||
await page.waitForLoadState('networkidle')
|
||
await page.getByRole('button', { name: 'Пропустить', exact: true }).first().click()
|
||
await page.waitForLoadState('networkidle')
|
||
await expect(page).toHaveScreenshot('wizard-step-3.png')
|
||
})
|
||
|
||
test('wizard step 4 (demo-данные)', async ({ page }) => {
|
||
await attachSession(page, built.session, '/onboarding-wizard')
|
||
await page.waitForLoadState('networkidle')
|
||
await page.getByRole('button', { name: 'Пропустить', exact: true }).first().click()
|
||
await page.waitForLoadState('networkidle')
|
||
await page.getByRole('button', { name: 'Пропустить', exact: true }).first().click()
|
||
await page.waitForLoadState('networkidle')
|
||
await page.getByRole('button', { name: /Сделать позже/i }).click()
|
||
await page.waitForLoadState('networkidle')
|
||
await expect(page).toHaveScreenshot('wizard-step-4.png')
|
||
})
|
||
|
||
test('help page', async ({ page }) => {
|
||
await attachSession(page, built.session, '/help')
|
||
await page.waitForLoadState('networkidle')
|
||
await expect(page).toHaveScreenshot('help-page.png')
|
||
})
|
||
|
||
test('admin diagnostic page', async ({ page }) => {
|
||
await attachSession(page, built.session, '/admin/diagnostic')
|
||
await page.waitForLoadState('networkidle')
|
||
// Запускаем check'и для screenshot'a с реальным результатом.
|
||
await page.getByRole('button', { name: /Запустить/i }).click()
|
||
await page.waitForTimeout(2000)
|
||
await expect(page).toHaveScreenshot('admin-diagnostic.png', { fullPage: true })
|
||
})
|