diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..1c29004 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "playwright": { + "command": "/home/artem/.nvm/versions/node/v22.14.0/bin/npx", + "args": ["@playwright/mcp@latest", "--browser", "chromium", "--headless", "--viewport-size", "1440x900"], + "env": { + "PATH": "/home/artem/.nvm/versions/node/v22.14.0/bin:/usr/local/bin:/usr/bin:/bin" + } + } + } +} diff --git a/.roo/docs/mcp-browser-verification.md b/.roo/docs/mcp-browser-verification.md new file mode 100644 index 0000000..8b5cc50 --- /dev/null +++ b/.roo/docs/mcp-browser-verification.md @@ -0,0 +1,101 @@ +# MCP Browser Verification Guide + +## Что это + +Playwright MCP server (`@playwright/mcp`) предоставляет инструменты для взаимодействия с браузером +через Model Context Protocol. Позволяет агенту (Zoo Code) открывать страницы, делать snapshot DOM, +кликать, заполнять формы, делать скриншоты — без написания тест-кода. + +## Конфигурация + +- **Сервер**: `web-browser` в `mcp_settings.json` (Zoo Code global storage) +- **Браузер**: Chromium (headless) +- **Node**: 22.14.0 (nvm, absolute path) +- **Пакет**: `@playwright/mcp@latest` (npm) + +## Workflow: UI Verification после изменения + +### 1. Запуск dev-сервера + +```bash +yarn dev +# → http://localhost:5173 +``` + +### 2. Проверка публичных страниц (без auth) + +| Рут | Что проверять | +|-----|---------------| +| `/explore` | Feed загружен, карточки тренировок рендерятся | +| `/public/workouts/:id` | Детали тренировки: chart, map, данные | +| `/routes` | Список маршрутов | +| `/404` | Страница 404 для неизвестных маршрутов | + +### 3. Проверка auth-страниц (с моком auth) + +```js +// browser_evaluate: установить мок-токен и перезагрузить +() => { + localStorage.setItem('token', 'mock-jwt-token'); + localStorage.setItem('user', JSON.stringify({ id: 1, name: 'Test User', email: 'test@test.ru' })); + localStorage.setItem('profile', JSON.stringify({ name: 'Test', avatar: null })); + location.reload(); +} +``` + +После reload: + +| Рут | Что проверять | +|-----|---------------| +| `/workouts` | Список пользовательских тренировок | +| `/workouts/:id` | Detail: chart (power, HR, elevation), map polyline | +| `/preferences` | Профиль, настройки | +| `/workouts/upload` | Форма загрузки FIT/GPX | + +### 4. Инструменты MCP (ключевые) + +| Инструмент | Назначение | +|-----------|-----------| +| `browser_navigate` | Перейти по URL | +| `browser_snapshot` | Получить accessibility tree (структура DOM) | +| `browser_take_screenshot` | Скриншот (PNG) | +| `browser_click` | Клик по элементу (по ref из snapshot) | +| `browser_fill_form` | Заполнить форму | +| `browser_evaluate` | Выполнить JS на странице | +| `browser_network_requests` | Список HTTP-запросов (проверить API) | +| `browser_wait_for` | Дождаться появления текста | +| `browser_console_messages` | Console errors/warnings | + +### 5. Типовой сценарий проверки (example) + +``` +1. browser_navigate → http://localhost:5173/explore +2. browser_wait_for → "Тренировки" (заголовок) +3. browser_snapshot → проверить структуру feed +4. browser_take_screenshot → визуал +5. browser_evaluate → mock auth (localStorage) +6. browser_navigate → http://localhost:5173/workouts +7. browser_snapshot → проверить сайдбар (authRoutes), список +8. browser_network_requests → убедиться что API /api/v0/workouts вызван +``` + +## Ограничения + +- **API**: dev-сервер проксирует API на `HOST` (production). Для изолированной проверки + нужно либо: + - Использовать production API (read-only запросы — ок) + - Поднять локальный mock-сервер (axios interceptors) +- **Yandex Maps**: API key hardcoded, работает в headless, но рендер может отличаться +- **Chart.js**: Canvas — в snapshot не виден, только через screenshot +- **SSR**: MCP проверяет SPA (client-side). SSR — отдельно через `curl` или `browser_navigate` на SSR-порт + +## Acceptance Criteria для Coder-задач + +Каждая Coder-задача с UI-изменениями должна включать: + +```markdown +### Acceptance Criteria +1. `yarn lint` → 0 ошибок +2. `yarn build` → 0 ошибок (vue-tsc --noEmit) +3. MCP browser: [конкретный сценарий проверки — рут, что ожидать в DOM] +``` diff --git a/.roo/memory-bank/activeContext.md b/.roo/memory-bank/activeContext.md index 69e40cf..f5d7c8d 100644 --- a/.roo/memory-bank/activeContext.md +++ b/.roo/memory-bank/activeContext.md @@ -2,11 +2,44 @@ ## Task State -- task_id: TASK-MCP-SETUP -- status: success +- task_id: TASK-TEST-PUBLIC +- status: in_progress - parent_task: — -- summary: **Настроить Playwright MCP для UI-верификации через браузер. Конфиг Zoo Code обновлён (Node 22), Chromium установлен.** -- next task: — +- summary: **Написать Playwright integration тесты для фронтенда в `../integration/tests/`. 3 spec файла: auth-flows ✅, public-pages, auth-protected.** +- next task: TASK-TEST-PROTECTED → Verify + +## ✅ CODER SUCCESS REPORT — TASK-TEST-AUTH + +- `../integration/tests/auth-flows.spec.ts` создан: 4 теста (login success, login failure, signup+auto-login, logout) +- Self-seed via `POST /api/v0/signup`, Vuestic selectors (`input[type=email]`, `input[type=password]`) +- Verified: `npx playwright test tests/auth-flows.spec.ts` → **4 passed (19.2s)** + +## TASK-TEST-PUBLIC: public-pages.spec.ts + +**Цель:** Playwright spec для auth-сценариев (signup, login success/error, logout, validation). + +**Файл для создания:** `../integration/tests/auth-flows.spec.ts` + +**Конвенции (см. SKILL.md):** +- Self-seed user via `POST /api/v0/signup` in `beforeAll` +- Selectors: `input[type=email]`, `input[type=password]`, `getByRole('button', { name: 'Вход' })` +- Vuestic floating labels → select by input type, NOT placeholder +- Auth mock via `page.evaluate(() => localStorage.setItem(...))` +- Do NOT call `expect()` inside `page.evaluate()` +- Base URLs: `FRONTEND` (5173), `BACKEND` (8000) +- `freshPage()` helper: goto → clear localStorage → goto again + +**Тесты в spec:** +1. **Signup**: `POST /api/v0/signup` → returns token, user, profile; navigate to `/auth/signup` → fill form → submit → redirect to `/explore` +2. **Login success**: pre-seed user via API → UI login → token in localStorage → redirect to `/explore` +3. **Login failure**: wrong password → error toast appears, stays on `/auth/login` +4. **Logout**: logged in → open profile dropdown → «Выход» → redirect to login, token cleared + +**Acceptance Criteria:** +1. Spec file created at `../integration/tests/auth-flows.spec.ts` +2. All tests self-contained (self-seed data) +3. Follows existing `sidebar-auth-switch.spec.ts` patterns +4. `npx playwright test tests/auth-flows.spec.ts` passes against integration stack ## TASK-MCP-SETUP: Playwright MCP configuration diff --git a/.roo/memory-bank/progress.md b/.roo/memory-bank/progress.md index a877148..a5213d3 100644 --- a/.roo/memory-bank/progress.md +++ b/.roo/memory-bank/progress.md @@ -1,5 +1,21 @@ # Progress — Strava Frontend +## 2026-09-24 — UI-testing skill + mandatory rule + +- `.roo/skills/ui-testing/SKILL.md` — how to test UI via (A) MCP browser ad-hoc and (B) the Playwright integration stand: run commands, spec conventions, selectors, gotchas. +- `.roo/rules/ui-testing.md` — MANDATORY rule: after any `src/` change affecting a visible route/interaction the task is not done until `yarn lint` + `yarn build` + browser verification (MCP ad-hoc for quick/visual, Playwright spec for must-not-regress) + green `npx playwright test`. Includes run cheat-sheet and required reporting format. + +## 2026-09-24 — Integration UI-test stand (Playwright + docker-compose) + +- Created `../integration/` (sibling of `frontend/`, `backend/`): + - `docker-compose.yml` — 3 services: `db` (postgres:15, host:5433, schema `strava` via `db-init/01-schema.sql`), `backend` (python:3.12, `pip install -r requirements.txt` + `alembic upgrade head` + `python -m app.web.__main__`, port 8000, `DB_URI=postgresql+asyncpg://svcuser:svcpass@db:5432/svc`), `frontend` (node:20-alpine, `yarn install --frozen-lockfile --ignore-scripts` + `yarn dev --host 0.0.0.0 --port 5173`, env `VITE_HOST=http://localhost:8000`, `HUSKY=0`). + - `package.json` + `playwright.config.ts` (`@playwright/test` ^1.54, baseURL `FRONTEND_URL||localhost:5173`, `API_URL`). + - `tests/sidebar-auth-switch.spec.ts` — TASK-AUTH-NAV regression: publicRoutes (no «Тренировки») → UI login (`/auth/login`, `input[type=email]` + `input[type=password]` + button «Вход») → authRoutes («Тренировки» appears, no reload) → UI logout (profile dropdown → «Выход») → publicRoutes. Self-seeds a unique user via `POST /api/v0/signup` in `beforeAll`. + - `README.md` — run instructions (`yarn up`, wait for backend, `yarn test`). +- Frontend change: `src/main.ts` `HOST = import.meta.env.VITE_HOST || "https://cycle-rider.ru"` (production unaffected — env unset → falls back to prod host). +- Verified: full stack up (frontend 200, backend 200 on `/api/v0/html_test/login`, db healthy); `npx playwright test tests/sidebar-auth-switch.spec.ts` → **1 passed (9.9s)** on Node 22 (nvm `~/.nvm/versions/node/v22.14.0/bin`). +- Gotchas: (1) npm install in frontend container fails on `@unhead/vue` peer-dep (wants vite>=6, project vite 5) → must use **yarn** (`yarn.lock`), and `node:20-alpine` already ships yarn (do NOT `npm i -g yarn` → EEXIST); (2) backend health path is `/api/v0/html_test/login` (router_public prefix `/api/v0`, no `/auth`); (3) Vuestic `VaInput` uses floating labels → no usable placeholder, select login fields by `input[type=email]` / `input[type=password]`; (4) Playwright/MCP need Node ≥20 (system node is 18) → run via nvm Node 22. + ## 2026-09-24 — TASK-AUTH-NAV-2: реактивные маршруты в сайдбаре и хлебных крошках - `NavigationRoutes.ts`: named exports `authRoutes`/`publicRoutes`, удалён module-level `localStorage.getItem("token")` тернарник. diff --git a/.roo/rules/ui-testing.md b/.roo/rules/ui-testing.md new file mode 100644 index 0000000..d8db520 --- /dev/null +++ b/.roo/rules/ui-testing.md @@ -0,0 +1,63 @@ +# UI Testing Rule (mandatory after frontend changes) + +> Procedure + commands: see the **`ui-testing`** skill (`.roo/skills/ui-testing/SKILL.md`). +> This rule defines the **when** (obligation) and the **gate** (definition of done). + +## When this rule applies + +Any change under `src/` that affects a **visible route or interaction**: +pages, components, auth flows, sidebar/breadcrumbs, forms, charts, maps, i18n +labels. Pure backend-only or docs-only changes are exempt. + +## The gate (Definition of Done) + +A frontend task is **not done** until ALL of the following pass. Do not claim +completion otherwise. + +1. `yarn lint` (frontend) → 0 errors +2. `yarn build` (frontend) → 0 errors (`vue-tsc --noEmit`) +3. **Browser verification** — the changed behavior is actually exercised in a + real browser (this is the non-negotiable step the user requires): + - **Quick change / visual only** → **MCP browser** ad-hoc check: + `browser_navigate` the affected route → `browser_snapshot` (assert the + structure) → `browser_take_screenshot` → `browser_console_messages` + (no app errors). + - **Behavior that must not regress** → a **Playwright integration spec** in + `../integration/tests/` that passes against the docker-compose stack. +4. If a Playwright spec was touched or added: `npx playwright test` green + (Node ≥20, via nvm Node 22). + +## How to run (cheat-sheet) + +```bash +# Playwright integration stack (repeatable / regression) +cd ../integration +export PATH="$HOME/.nvm/versions/node/v22.14.0/bin:$PATH" # Playwright needs Node ≥20 +yarn up # docker compose up -d --build +until curl -sf http://localhost:8000/api/v0/html_test/login >/dev/null; do sleep 2; done +npx playwright test # all +npx playwright test tests/.spec.ts # one +yarn down # stop (keep DB) +``` + +MCP browser (ad-hoc) needs the app reachable: `yarn dev` → `:5173`, or the +integration stack (`yarn up`) so the SPA talks to the real backend at `:8000`. + +## Reporting + +When finishing a task, state explicitly in the completion message: +- lint/build result +- which browser verification was performed (MCP ad-hoc vs Playwright spec) and + the outcome (pass/fail) +- for Playwright: the spec name and that it is green + +## Gotchas (must follow) + +- Frontend deps install with **yarn**, never npm (peer-dep conflict on + `@unhead/vue` / vite). +- Vuestic inputs have **floating labels → empty placeholder**: select by + `input[type=email]` / `input[type=password]`, not by placeholder. +- Sidebar = `