# Sprint UI-deep — глубокое браузерное тестирование stage Цель: пройти `https://test.admin.food-market.kz` через **реальный Chromium** (Playwright Test) и найти UX-баги, которые axios-проверки не видят: console errors, network 5xx/4xx, layout breaks, missing loading states, проблемы responsive, отсутствие confirm/validation/disabled-state и multi-tenant утечки через URL. Старт: 2026-05-30. Исполнитель: Claude Opus 4.7 (автономный режим). ## Стек - `@playwright/test` runner — параллельные специ, trace-on-failure, screenshot-on-failure. - `otplib` — генерация TOTP-кодов для item 11 (2FA flow). - Все спецы лежат в `tests/e2e/scenarios/stage-ui-*.spec.ts`. - `tests/e2e/playwright.config.ts` — конфиг с `BASE`, `headless: true`, `screenshot: 'only-on-failure'`, `trace: 'retain-on-failure'`. ## Принципы - Каждый пункт = отдельный spec-файл (.spec.ts). - Каждый баг: воспроизвести в test() → починить код → `dotnet build` + локальные тесты → `~/deploy-stage.sh` → retest spec на стейдже зелёный → коммит фикса → коммит spec → `[x]` в этом доке. - НЕ трогать: `global.json`, прод-стек, POS WPF. ## Чек-лист - [x] **1. Signup → onboarding → первая работа** — `stage-ui-1-signup-flow.spec.ts` (5 specs ✓). Найден баг: ProductEditPage race на currencies — теперь disabled пока не подгрузились + canSave проверяет currencyId. Form-level error display переведён на `humanizeError()` — больше не «Request failed with status code 400». - [x] **2. Дашборд + навигация** — `stage-ui-2-nav.spec.ts` (4 ✓). 27 sidebar-страниц последовательно открыты в Chromium, 0 console-errors, 0 5xx. Активный пункт (aria-current="page") и labels проверены. - [x] **3. Каталог (товары) full CRUD** — `stage-ui-3-products-crud.spec.ts` (5 ✓). Найдены 2 бага: race на currencies (item 1) + ghost-404 toast после Delete (refetch на удалённый id из-за invalidate). Также Modal a11y улучшен. Image upload — через `setInputFiles()`, проверяем response code. - [x] **4. Контрагенты / Группы / Единицы / Типы цен** — `stage-ui-4-references-crud.spec.ts` (4 ✓). Контрагенты: modal CRUD с ConfirmDialog. Группы: create через UI. Типы цен: bootstrap + новая. Единицы — smoke. - [x] **5. Сотрудники + Роли** — `stage-ui-5-employees-roles.spec.ts` (3 ✓). 2 бага: 1) EmployeesPage save показывал «Request failed with status code 400» — фикс через humanizeError; 2) После create list не refetch'ался — фикс qc.invalidateQueries после direct api.post. - [x] **6. Приёмка (Supply)** — `stage-ui-6-supply.spec.ts` (3 ✓). Save disabled на пустом черновике, UI правильно показывает Posted после API post, остаток обновлён. **Найден P2 баг (known)**: Supply нет optimistic concurrency — 2 вкладки могут перезаписать друг друга (lost-update). Зафиксирован как known issue для будущего фикса. - [x] **7. RetailSale + CustomerReturn** — `stage-ui-7-retail-sale.spec.ts` (4 ✓). Oversell на Post возвращает понятное русское сообщение. Payment validation работает. Кнопка «Возврат» доступна на проведённом чеке. - [x] **8. Складские документы** — `stage-ui-8-inventory-docs.spec.ts` (5 ✓). Все 6 doc-форм рендерятся с правильным Submit state. Transfer ToStore фильтрует выбранный FromStore. Inventory CSV-import видна на draft. Enter Post через UI ✓. Demand oversell — понятный русский текст. - [x] **9. Отчёты — Sales/Stock/Profit/ABC** — `stage-ui-9-reports.spec.ts` (6 ✓). Все 4 отчёта рендерятся без console-errors. Sales CSV скачивается через `page.waitForEvent('download')`. Stock XLSX endpoint возвращает корректный MIME+body. - [x] **10. OrgAuditLog UI** — `stage-ui-10-audit-log.spec.ts` (2 ✓). После seed-demo записи видны, diff `
/` раскрывается. - [x] **11. 2FA flow** — `stage-ui-11-2fa.spec.ts` (4 ✓). API-only (UI 2FA не реализован пока). Минимальная TOTP-генерация (RFC 6238) на crypto.createHmac sha1 — без зависимостей. Enroll/Verify/Disable работают, status флипается. - [x] **12. Login edge** — `stage-ui-12-login-edge.spec.ts` (4 ✓). Неверный пароль показывает читаемую ошибку (не «Request failed»). Forgot-password flow + happy-path login → redirect. **Known issue**: за 10 попыток login не словили 429 — rate-limit либо отключён, либо окно длиннее 10 попыток. - [x] **13. Multi-tenant изоляция через URL** — `stage-ui-13-multitenant.spec.ts` (5 ✓). **P0 ПРОВЕРКА — изоляция HOLDS**. GET/PUT/DELETE товара A с токеном B → все 404/403. UI B на /products/{id-A} НЕ показывает имя A. Список B показывает EmptyState. - [x] **14. Mobile viewport 375x667** — `stage-ui-14-mobile.spec.ts` (5 ✓). Sidebar схлопывается на md, гамбургер виден, drawer открывается+закрывается, products list без horizontal overflow, ConfirmDialog влезает. ## Журнал ### 2026-05-30 — старт - Создан этот файл. Sprint 7 (UX-полировка) закрыт ранее — теперь смотрим уже на «улучшенный» UI и ищем оставшиеся дыры. - Подготовка: устанавливаю `@playwright/test`, `otplib`. Конфиг + helper'ы. ### 2026-05-30 — итог **59/59 спецификаций ✓** на `https://test.admin.food-market.kz` после последнего deploy-stage. **Найдено и починено (6 багов):** 1. **ProductEditPage race на currencies** — если юзер кликнул цену до загрузки справочника валют, в payload уходил `currencyId=''` → server 400 с криптичным JSON-validation. Фикс: MoneyInput disabled пока `!currencies.data`, canSave проверяет row.currencyId. 2. **Generic axios error в form-level error display** — пользователь видел «Request failed with status code 400» вместо реальной API-подсказки. Экспортировал `humanizeError()` из `@/lib/api`, применил в ProductEditPage и EmployeesPage. 3. **Modal a11y** — компонент `` не имел `role="dialog"` / `aria-modal` / `aria-labelledby`. Screen reader не определял диалог. Также добавил `aria-label="Закрыть"` на крестик. 4. **Ghost-404 toast после Delete товара** — ProductEditPage.remove делал `invalidateQueries({queryKey:['/api/catalog/products']})` до navigate; TanStack Query refetch'ил конкретно `['/api/catalog/products', id]` (тот что живёт на той же странице) → 404 → toast «Не найдено» поверх редиректа. Фикс: просто `navigate()`, без cache-touch. Refetch list при заходе на ProductsPage сам обновит. 5. **EmployeesPage save error** — тоже показывал «Request failed with status code 400». Через humanizeError. 6. **EmployeesPage create не обновлял list** — direct `api.post` без invalidateQueries (мутации с custom-response shape для generated password). Фикс: `await qc.invalidateQueries({queryKey:[URL]})` после успеха. **Known issues (documented, не блокирующие):** - **Supply lost-update**: нет optimistic concurrency. 2 вкладки → обе сохраняются успешно (HTTP 204), второй overwrite'ит первый. P2 для будущего sprint'а — добавить ETag или RowVersion. - **Login rate-limit**: за 10 попыток `/connect/token` подряд (с разными username) ни одна не получила 429. Либо rate-limit отключён, либо настроен слишком широко (>10/min). Стоит проверить configuration. **P0 проверка прошла:** multi-tenant изоляция работает. GET/PUT/DELETE товара A с токеном B → все 404/403. UI B на /products/{id-A} НЕ показывает имя A. **Покрытие 14 пунктов:** | # | Тема | specs | результат | |---|---|---|---| | 1 | Signup + first work | 5 | ✓ + 1 bug fixed | | 2 | Dashboard + navigation | 4 | ✓ (27 страниц без errors) | | 3 | Products CRUD | 5 | ✓ + 2 bugs fixed | | 4 | References CRUD | 4 | ✓ | | 5 | Employees + Roles | 3 | ✓ + 2 bugs fixed | | 6 | Supply UI | 3 | ✓ + 1 known issue | | 7 | RetailSale + CustomerReturn | 4 | ✓ | | 8 | Inventory documents | 5 | ✓ | | 9 | Reports + downloads | 6 | ✓ | | 10 | OrgAuditLog UI | 2 | ✓ | | 11 | 2FA flow (API-only) | 4 | ✓ | | 12 | Login edge cases | 4 | ✓ + 1 known issue | | 13 | Multi-tenant URL isolation (P0) | 5 | ✓ | | 14 | Mobile viewport 375x667 | 5 | ✓ | | **Σ** | | **59** | **59/59 ✓** |