tester(ET): auto-commit from tester run_id=12
All checks were successful
All checks were successful
This commit is contained in:
247
docs/work-items/ET-005/13-test-report.md
Normal file
247
docs/work-items/ET-005/13-test-report.md
Normal file
@@ -0,0 +1,247 @@
|
||||
---
|
||||
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`.
|
||||
Reference in New Issue
Block a user