Files
enduro-trails/docs/work-items/ET-005/13-test-report.md
claude-bot 3f6e7ae284
All checks were successful
CI / lint (push) Successful in 4s
CI / test (push) Successful in 4s
CI / lint (pull_request) Successful in 4s
CI / test (pull_request) Successful in 5s
CI / build (push) Successful in 3s
CI / build (pull_request) Successful in 1s
tester(ET): auto-commit from tester run_id=12
2026-05-21 21:29:11 +00:00

248 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: test-report
work_item_id: ET-005
version: 1
status: pass
tester: "agent:tester"
date: 2026-05-21
commit_tested: 2fe5cfe
verdict: PASS
---
# Test Report — ET-005
## Verdict: **PASS** → `stage:ready-to-deploy`
Полный регресс зелёный: **pytest 31 passed, 4 skipped, 0 failed**;
JS-юнит-тесты `units.js` **20/20 pass**; **e2e Playwright TP-01…TP-05
6/6 pass** (0 JS-ошибок на странице); lint чистый; тест-окружение
отвечает 200. Блокирующих багов (P0/P1) не найдено.
Весь тест-план `04-test-plan.yaml` (TP-01…TP-05) исполнен **в реальном
браузере** — в отличие от ET-002, e2e не блокирован. Все 4 acceptance-
критерия покрыты и не нарушены.
## Окружение
- **Дата прогона:** 2026-05-21
- **Ветка:** `feature/ET-005-`
- **Код-коммит:** `2fe5cfe` (`feat(web): переключатель единиц измерения
расстояний (км/мили)`; источник — `12-review.md`)
- **HEAD:** `d32ad8f` (`reviewer(ET): auto-commit ...`) — поверх кода
только артефакт ревью, изменений кода нет; тестировалось рабочее дерево
- **Python:** 3.12.13
- **pytest:** 8.3.3 (plugins: asyncio-1.3.0, anyio-4.13.0)
- **Node:** v22.22.2 (`node --test`)
- **Playwright:** 1.60.0, Chromium headless (chromium-headless-shell v1223)
- **ruff:** установлен по `pyproject.toml [dev]`
- **test-env:** https://openclaw.mva154.duckdns.org/enduro/ → HTTP 200
## Healthcheck
| Среда | URL | Код |
|---|---|---|
| local dev | http://localhost:5556/health | connection refused (dev не поднят — ОК, прогон оффлайн) |
| test | https://openclaw.mva154.duckdns.org/enduro/ | 200 |
ET-005 — фронтенд-изменение. В test задеплоен предыдущий код, поэтому
healthcheck подтверждает только живость окружения; фича попадёт в test
штатной перевыкладкой `src/web/` после merge (см.
`07-infra-requirements.md §7`). e2e-прогон выполнен против **кода ветки**,
поднятого локально статическим сервером (`python -m http.server` →
`src/web/`), не против test-окружения. На prom ничего не запускалось.
## Команды запуска
```bash
# Unit + integration (эквивалент make test)
python -m pytest tests/ -v
# JS behavioral unit-тесты units.js (TP-01..TP-04, AC-2, AC-3)
node --test tests/unit/units.test.js
# e2e (TP-01..TP-05): локальная раздача src/web + Playwright/Chromium
python -m http.server 8777 --directory src/web &
python /tmp/et005_e2e.py
# Lint
ruff check src/ tests/
```
## Результаты pytest
`python -m pytest tests/ -v` → **31 passed, 4 skipped, 1 warning in 0.62s**
| Файл | Тестов | PASS | SKIP |
|---|---|---|---|
| `integration/test_routing_barriers.py` | 7 | 3 | 4 |
| `unit/test_health.py` | 1 | 1 | 0 |
| `unit/test_poi_toggle.py` (ET-002, регресс) | 10 | 10 | 0 |
| `unit/test_unit_toggle.py` (**ET-005**) | 17 | 17 | 0 |
| **Итого** | **35** | **31** | **4** |
**ET-005 — `test_unit_toggle.py` (17/17 PASS):**
| Тест | Покрывает | Результат |
|---|---|---|
| `test_units_module_exists` | наличие `src/web/units.js` (ADR-0001 п.2) | **PASS** |
| `test_units_module_public_api` | контракт `window.Units` (ADR-0001 п.3) | **PASS** |
| `test_units_module_constants` | `KM_TO_MI=0.621371`, `distance_unit`, дефолт `km` | **PASS** |
| `test_units_module_exports_for_browser_and_node` | `window.Units` + `module.exports` | **PASS** |
| `test_unit_toggle_present_in_html` | кнопка km/mi в попапе (ФТ-1, AC-1) | **PASS** |
| `test_unit_toggle_reuses_seg_control_component` | переиспользование `.seg-control` (R8) | **PASS** |
| `test_units_js_loaded_before_app_js` | порядок скриптов (R7, ADR-0001 п.2) | **PASS** |
| `test_unit_toggle_has_styles` | стили `.terrain-unit-row` (AC-4) | **PASS** |
| `test_app_js_unit_functions_defined` | `onUnitToggle`/`syncUnitToggleUI`/`onUnitChange` | **PASS** |
| `test_app_js_has_et005_block_markers` | блок-маркеры `>>> ET-005 ... >>>` | **PASS** |
| `test_app_js_single_unitchange_subscription` | ровно одна подписка `unitchange` (ADR п.6) | **PASS** |
| `test_app_js_uses_centralized_formatter` | форматирование через `Units.formatDistance` | **PASS** |
| `test_app_js_distance_helpers_delegate_to_units` | хелперы делегируют в `units.js` (R1) | **PASS** |
| `test_app_js_scale_bar_is_unit_aware` | scale-bar учитывает единицу (R3) | **PASS** |
| `test_app_js_gpx_export_stays_metric` | GPX-экспорт остаётся метрическим (R6) | **PASS** |
| `test_app_js_restores_unit_choice_on_load` | восстановление выбора при загрузке (AC-3) | **PASS** |
| `test_js_unit_tests_pass` | запуск `units.test.js` через Node-раннер | **PASS** |
**4 SKIP** — интеграционные тесты роутинга ET-001
(`test_routing_barriers.py::test_route_*`); требуют поднятого OSRM,
недоступного в окружении тестера (штатный `skip`, чтобы CI без
инфраструктуры не падал). ET-005 — фронтенд-изменение, на роутинг не
влияет; к регрессу не относится.
Предупреждение `PytestDeprecationWarning` (`asyncio_default_fixture_loop_scope`)
— внешняя зависимость `pytest-asyncio`, к ET-005 отношения не имеет, не
блокирует.
## Результаты JS unit-тестов `units.js`
`node --test tests/unit/units.test.js` → **# tests 20, # pass 20, # fail 0**
Тесты исполняют **реальный** `src/web/units.js` (сброс `require.cache` +
инъекция моков `window`/`document`/`localStorage` перед каждым тестом).
Покрыты TP-01…TP-04, AC-2, AC-3, граница 1000 м, недоступный
`localStorage`, валидация `setUnit()`, публикация неймспейса, единый
разделитель «запятая» (R4), точность по умолчанию.
## Результаты e2e (Playwright / Chromium) — TP-01…TP-05
Прогон в headless-Chromium против кода ветки, поднятого локально
(`src/web/` через `http.server`). Взаимодействие — через **реальные
DOM-клики** по кнопкам попапа (`onUnitToggle` срабатывает по inline
`onclick`). Пересчёт видимых расстояний верифицирован на живой
масштабной линейке карты (`#scale-zoom-bar`), которую перерисовывает
оркестратор `onUnitChange()`.
| TC | Сценарий | Факт | Результат |
|---|---|---|---|
| **TP-01** | дефолт после очистки `localStorage` | `getUnit()='km'`, кнопка «км» `.active`, «мили» нет, `localStorage`=пусто, `formatDistance(12345)='12,3 км'` | **PASS** |
| **TP-02** | переключение в мили | `getUnit()='mi'`, «мили» `.active`, `localStorage='mi'`, `formatDistance(12345)='7,7 ми'`, scale-bar `'55 km'→'35 mi'` | **PASS** |
| **TP-03** | persistence после reload | после перезагрузки `getUnit()='mi'`, «мили» `.active` | **PASS** |
| **TP-04** | возврат в км | `getUnit()='km'`, «км» `.active`, `localStorage='km'`, scale-bar снова `'55 km'` | **PASS** |
| **TP-05** | mobile responsive 375px | обе кнопки видимы и в пределах вьюпорта (km `x=166 w=57`, mi `x=226 w=57`), клик переключает | **PASS** |
| NFR-perf | переключение < 100 мс | клик + пересчёт всех поверхностей = **0,5 мс** | **PASS** |
**Итог e2e: 6/6 PASS.** На странице **не зафиксировано ни одной
JS-ошибки** (`pageerror` за весь прогон — none).
## Покрытие тест-плана (04-test-plan.yaml)
| TC | Тип | Исполнение | Статус |
|---|---|---|---|
| **TP-01** | e2e | Playwright + JS-тест `units.test.js` | **PASS** |
| **TP-02** | e2e | Playwright (scale-bar `km→mi`) + JS-тест | **PASS** |
| **TP-03** | e2e | Playwright (reload) + JS-тест | **PASS** |
| **TP-04** | e2e | Playwright (scale-bar `mi→km`) + JS-тест | **PASS** |
| **TP-05** | e2e | Playwright, viewport 375×667 | **PASS** |
**Исполнено и пройдено: 5/5 тест-кейсов.**
## Соответствие Acceptance Criteria
| AC | Описание | Источник проверки | Статус |
|---|---|---|---|
| **AC-1** | Кнопка km/mi в панели, показывает выбор, клик переключает | e2e TP-01 (км active по умолчанию), TP-02/TP-04 (клик переключает класс `.active`), `test_unit_toggle_present_in_html` | **PASS** |
| **AC-2** | Пересчёт всех расстояний, коэф. 0.621371, округление до 1 знака | e2e TP-02 (scale-bar `55 km→35 mi`, `formatDistance(12345)=12,3 км→7,7 ми`), `units.test.js` (`KM_TO_MI`, точность 1 знак), `test_units_module_constants` | **PASS** |
| **AC-3** | Сохранение/восстановление из `localStorage`, дефолт km | e2e TP-01 (дефолт km, `localStorage` пуст), TP-02 (`localStorage='mi'`), TP-03 (выживает reload) | **PASS** |
| **AC-4** | Кнопка не перекрывает элементы, mobile, переключение < 100мс | e2e TP-05 (375px, кнопки в пределах вьюпорта, кликабельны), NFR-perf (0,5 мс ≪ 100 мс), `test_unit_toggle_has_styles` | **PASS** |
Все 4 критерия имеют поведенческое покрытие в реальном браузере; ни один
не нарушен. Коэффициент `0.621371` и округление до 1 знака подтверждены
и unit-тестами на реальном `units.js`, и e2e-конвертацией.
## Найденные баги
### P0 (блокирующие)
Нет.
### P1 (критические)
Нет.
### P2 (важные)
**T-01 (= R-01 из `12-review.md`) — переключение единиц сбрасывает выбор
варианта связки.** В режиме связки `onUnitChange()` вызывает
`renderLinkCards(linkRoutes)`, которая всегда подсвечивает «Вариант 1»;
выбранный пользователем вариант 2/3 теряется при каждом переключении
км/мили. Дефект **унаследован из ревью** (зафиксирован reviewer'ом как
R-01/P2). **Тестером в этом прогоне не воспроизводился инструментально** —
режим связки требует построенного маршрута и поднятого OSRM, недоступного
в окружении (см. 4 SKIP). Расстояния при этом пересчитываются корректно,
ФТ-3 ТЗ формально выполнено; дефект ограничен UX режима связки.
**P2 — merge/деплой не блокирует.** Действие: dev — поправить в этом же
PR (ввести `activeLinkIdx`) либо осознанно вынести в техдолг.
### P3 (косметика / наблюдения)
1. **(= R-02 из `12-review.md`)** Масштабная линейка в режиме «mi»
использует латиницу и точку (`'0.5 mi'`) вместо запятой и русских
подписей `units.js` (`'0,5 ми'`). Это **пред-существующее** поведение
scale-bar (в режиме «km» и раньше было `'30 km'`), ET-005 лишь
расширил тот же стиль на мили — регрессии нет. e2e подтвердил: scale-bar
корректно меняет суффикс `km↔mi`. Косметика, не блокирует.
2. **(= R-03 из `12-review.md`)** Слой `app.js` (оркестратор,
unit-aware ветка scale-bar) в репозитории покрыт только статикой.
В этом прогоне пробел закрыт **e2e**: оркестратор проверен на живой
масштабной линейке (TP-02/TP-04). Перерисовка карточек маршрута/связки
через `onUnitChange()` инструментально не покрыта (нет OSRM); поведение
подтверждено `units.test.js` на реальном коде + статикой `test_app_js_*`.
Техдолг на DOM/MapLibre-харнесс для `app.js` остаётся.
3. **Окружение тестера.** Пакеты `shapely`, `mapbox-vector-tile`
(`requirements.txt`) и `pytest-asyncio`, `ruff` (`pyproject.toml [dev]`)
не были предустановлены в песочнице — без `shapely` падал сбор
`test_health.py` (импорт `src.api.main`). Тестер доустановил их по
манифестам проекта. Это дефект провижининга окружения, **не дефект
ET-005**. CI обязан выполнять `pip install -r requirements.txt` и
`.[dev]` перед `make test`.
## Замечания тестера
- **e2e-инструментарий.** Playwright + Chromium установлены **только в
песочницу тестера** для исполнения e2e. В артефакты проекта
(`requirements.txt`, `pyproject.toml`, `package.json`) ничего **не
добавлено** — ограничение `07-infra-requirements.md §6` («новые
npm/Python пакеты — Нет», касается production/build-зависимостей) **не
нарушено**. e2e-тесты `04-test-plan.yaml` (TP-01…TP-05) явно ожидаются
`07-infra-requirements.md §9`; здесь они исполнены без изменения кода и
тестов проекта. Скрипт прогона — временный, в репозиторий не коммитится.
- Ручная сверка реализации: `index.html:62-69` — сегментированный
переключатель `#unit-seg` (кнопки `unit-btn-km`/`unit-btn-mi`) в попапе
`#terrain-popup` после чекбокса POI, отделён `<hr>`; `index.html:415` —
`units.js` подключён строго перед `app.js`; `app.js:2877-2948` — блок
ET-005 (`onUnitToggle`, `syncUnitToggleUI`, оркестратор `onUnitChange`);
`app.js:2437-2438` — восстановление выбора и единственная подписка
`unitchange` в `DOMContentLoaded`. Соответствует ТЗ, ADR-0001 и выводам
`12-review.md`.
- Тесты не подгонялись под код; продакшн-код не правился; на prom ничего
не запускалось.
## Итог
**Verdict: PASS** → `stage:ready-to-deploy`.
Весь тест-план (TP-01…TP-05) исполнен в реальном браузере и пройден;
все 4 acceptance-критерия зелёные; pytest-регресс (31 passed) и lint
чистые; JS-ошибок на странице нет; нефункциональное требование
«пересчёт < 100 мс» выполнено с запасом (0,5 мс). Блокирующих (P0/P1)
багов нет. Унаследованный из ревью T-01/P2 (сброс варианта связки)
деплой не блокирует — решение по нему за dev. Готово к штатной
перевыкладке фронтенда согласно `07-infra-requirements.md §7`.