---
type: test-report
work_item_id: ET-002
version: 1
status: pass
tester: "agent:tester"
date: 2026-05-21
commit_tested: 8c17a4f
verdict: PASS
---

# Test Report — ET-002

## Verdict: **PASS** → `stage:ready-to-deploy`

Полный регресс зелёный: **14 passed, 4 skipped, 0 failed**, lint чистый,
JS-юнит-тесты POI 7/7 pass, тест-окружение отвечает 200. Блокирующих
багов (P0/P1) не найдено.

Тест-кейсы TP-01..TP-04 и TP-08 из `04-test-plan.yaml` исполнены и
прошли. e2e-кейсы TP-05..TP-07, TP-09 **не исполнялись** — в репозитории
нет Playwright-инфраструктуры, а `07-infra-requirements.md §6` запрещает
новые npm-пакеты (конфликт двух approved-артефактов, зафиксирован
reviewer'ом как R-01/P2). Поведенческая суть этих сценариев покрыта
unit- и статическими тестами. На merge/деплой ET-002 не влияет.

## Окружение

- **Дата прогона:** 2026-05-21
- **Ветка:** `feature/ET-002-poi-toggle`
- **Коммит:** `8c17a4f` (`feat(web): add POI visibility checkbox to terrain popup`;
  взят из `12-review.md` — `git` в окружении тестера недоступен)
- **Python:** 3.12.13
- **pytest:** 8.3.3 (plugins: asyncio-1.3.0, anyio-4.13.0)
- **Node:** v22.22.2 (`node --test`)
- **ruff:** 0.15.14
- **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 |
| test API | https://openclaw.mva154.duckdns.org/enduro/api/health | 200 `{"status":"ok","db_exists":true}` |

ET-002 — фронтенд-изменение; в test задеплоен `main`, поэтому healthcheck
подтверждает только живость окружения, а не наличие фичи (фича попадёт
в test штатной перевыкладкой после merge).

## Команды запуска

```bash
# Unit + integration (эквивалент make test)
python -m pytest tests/ -v

# JS behavioral unit-тесты POI (TP-01..TP-04)
node --test tests/unit/poi_toggle.test.js

# Lint
ruff check src/
ruff check tests/
```

## Результаты pytest

`python -m pytest tests/ -v` → **14 passed, 4 skipped, 1 warning in 0.51s**

| # | Тест | Тип | Результат |
|---|---|---|---|
| 1 | `test_routing_barriers.py::test_lua_syntax` | unit (структура lua) | **PASS** |
| 2 | `test_routing_barriers.py::test_blocked_barriers_match_trz` | static AC | **PASS** |
| 3 | `test_routing_barriers.py::test_excluded_highways_match_trz` | static AC | **PASS** |
| 4 | `test_routing_barriers.py::test_route_avoids_barrier` | integration | **SKIP** (OSRM недоступен) |
| 5 | `test_routing_barriers.py::test_route_no_footway` | integration | **SKIP** (OSRM недоступен) |
| 6 | `test_routing_barriers.py::test_route_allows_cattle_grid` | integration | **SKIP** (OSRM недоступен) |
| 7 | `test_routing_barriers.py::test_existing_route_works` | regression | **SKIP** (OSRM недоступен) |
| 8 | `test_health.py::test_health_endpoint` | unit (async) | **PASS** |
| 9 | `test_poi_toggle.py::test_poi_checkbox_present_in_html` | static (REQ-F-01) | **PASS** |
| 10 | `test_poi_toggle.py::test_poi_checkbox_checked_by_default` | static (REQ-F-02) | **PASS** |
| 11 | `test_poi_toggle.py::test_poi_checkbox_placed_after_trails_separated_by_hr` | static (REQ-F-01) | **PASS** |
| 12 | `test_poi_toggle.py::test_poi_checkbox_uses_shared_style_class` | static (UI-спец) | **PASS** |
| 13 | `test_poi_toggle.py::test_poi_functions_defined` | static (ADR-0001) | **PASS** |
| 14 | `test_poi_toggle.py::test_poi_logic_uses_localstorage_key` | static (REQ-F-05) | **PASS** |
| 15 | `test_poi_toggle.py::test_poi_logic_reuses_layer_state_and_groups` | static (ADR п.3-4) | **PASS** |
| 16 | `test_poi_toggle.py::test_restore_poi_state_wired_into_init` | static (REQ-F-06) | **PASS** |
| 17 | `test_poi_toggle.py::test_poi_visibility_toggled_via_set_layout_property` | static (ADR п.1) | **PASS** |
| 18 | `test_poi_toggle.py::test_js_unit_tests_pass` | behavioral (Node-раннер) | **PASS** |

**4 SKIP** — интеграционные тесты роутинга ET-001; требуют поднятого OSRM
(`OSRM_URL`, по умолчанию `http://172.22.0.1:5559`), который в окружении
тестера недоступен. Это штатное поведение (`test_routing_barriers.py`
помечает их `skip`, чтобы CI без инфраструктуры не падал). ET-002 —
фронтенд-изменение, на роутинг влиять не может; к регрессу не относится.

Предупреждение `PendingDeprecationWarning` из `starlette/formparsers.py` —
внешняя зависимость, к ET-002 отношения не имеет, не блокирует.

## Результаты JS unit-тестов (TP-01..TP-04)

`node --test tests/unit/poi_toggle.test.js` → **# pass 7, # fail 0**

Тесты исполняют **реальный** код POI-блока из `src/web/app.js` (блок
извлекается по маркерам `ET-002 POI visibility block` и оборачивается
через `new Function` с инъекцией моков).

| Тест | TC | Результат |
|---|---|---|
| снятый чекбокс скрывает слои POI и сохраняет `0` | TP-01 | **PASS** |
| установленный чекбокс показывает слои POI и сохраняет `1` | TP-02 | **PASS** |
| `restorePoiState()` при `poi-visible=0` скрывает POI | TP-03 | **PASS** |
| `restorePoiState()` без ключа включает POI по умолчанию | TP-04 | **PASS** |
| `restorePoiState()` при `poi-visible=1` показывает POI | доп. | **PASS** |
| `onPoiCheckbox()` меняет только `poi-circles`/`poi-labels` | доп. (дух TP-08) | **PASS** |
| `applyPoiVisibility()` синхронизирует `layerState` без слоёв | доп. | **PASS** |

## Результаты lint

| Команда | Результат |
|---|---|
| `ruff check src/` | **All checks passed!** |
| `ruff check tests/` | **All checks passed!** |

## Покрытие тест-плана (04-test-plan.yaml)

| TC | Тип | Исполнение | Статус |
|---|---|---|---|
| **TP-01** | unit | JS-тест «TP-01» (через `test_js_unit_tests_pass`) | **PASS** |
| **TP-02** | unit | JS-тест «TP-02» | **PASS** |
| **TP-03** | unit | JS-тест «TP-03» | **PASS** |
| **TP-04** | unit | JS-тест «TP-04» | **PASS** |
| **TP-05** | e2e | **BLOCKED** (нет Playwright). Покрыто статически: `test_poi_checkbox_present_in_html`, `test_poi_checkbox_placed_after_trails_separated_by_hr` | COVERED (alt) |
| **TP-06** | e2e | **BLOCKED** (нет Playwright). Покрыто поведенчески: JS «TP-01»/«TP-02», «меняет только poi-circles/poi-labels» | COVERED (alt) |
| **TP-07** | e2e | **BLOCKED** (нет Playwright). Покрыто поведенчески: JS «TP-03» (`restorePoiState` при `poi-visible=0`) | COVERED (alt) |
| **TP-08** | integration | JS «onPoiCheckbox() меняет только слои poi-circles и poi-labels» — изоляция чужих слоёв подтверждена | **PASS** (alt) |
| **TP-09** | e2e | **BLOCKED** (нет Playwright). Класс `terrain-checkbox` подтверждён статически; по touch-target см. P3-2 | PARTIAL |

**Исполнено и пройдено: TP-01..TP-04, TP-08 (5/9).**
**e2e TP-05..TP-07, TP-09 (4/9) — заблокированы инфраструктурой**, их
поведение покрыто unit/статикой. Прямой инструментальной проверки в
реальном браузере не было.

## Соответствие Acceptance Criteria

| AC | Описание | Источник проверки | Статус |
|---|---|---|---|
| AC-01 | Чекбокс в попапе после «Тропы», отделён `<hr>` | `test_poi_checkbox_placed_after_trails_separated_by_hr` | **PASS** |
| AC-02 | POI включены по умолчанию | `test_poi_checkbox_checked_by_default` + JS «TP-04» | **PASS** |
| AC-03 | Скрытие POI → `visibility: none` | JS «TP-01» | **PASS** |
| AC-04 | Показ POI → `visibility: visible` | JS «TP-02» | **PASS** |
| AC-05 | Состояние сохраняется после перезагрузки | JS «TP-03» (`restorePoiState` при `poi-visible=0`) | **PASS** |
| AC-06 | Восстановление включённого состояния | JS «restorePoiState при poi-visible=1» | **PASS** |
| AC-07 | Не ломает существующие чекбоксы | JS «меняет только poi-circles/poi-labels»; HTML-изменения аддитивны | **PASS** |
| AC-08 | Синхронизация с `layerState.poi` | JS «TP-01»/«TP-02» + «applyPoiVisibility без слоёв» | **PASS** |

Все 8 критериев имеют поведенческое покрытие; ни один не нарушен.
Браузерная (e2e) проверка AC-01/03/04/05 ограничена отсутствием
Playwright — поведение подтверждено на уровне реального кода POI-блока.

## Найденные баги

### P0 (блокирующие)
Нет.

### P1 (критические)
Нет.

### P2 (важные)

**T-01 (= R-01 из `12-review.md`) — e2e TP-05..TP-09 не исполнимы.**
`04-test-plan.yaml` определяет TP-05..TP-09 как `type: e2e`, но
`07-infra-requirements.md §6` запрещает новые npm-пакеты, а
Playwright-инфраструктуры в репозитории нет (проверено: нет
`package.json`, `node_modules`, бинаря `playwright`, `npx`). Это конфликт
двух **approved**-артефактов, а не дефект разработки. Поведение покрыто
unit/статикой.
**Действие:** Analyst — согласовать `04-test-plan.yaml` с
`07-infra-requirements.md` (пометить TP-05..09 как покрытые альтернативно
либо завести инфра-задачу на Playwright). **Merge/деплой ET-002 не
блокирует.**

### P3 (косметика / наблюдения)

1. **(= R-02 из `12-review.md`)** `app.js:2849` —
   `const poiOn = stored === null || stored === '1';`: нештатное значение
   ключа `poi-visible` трактуется как «скрыть». Надёжнее
   `stored !== '0'` (деградация к дефолту «показать», REQ-F-02).
   Крайний кейс — ключ пишет только это приложение. Не блокирует.
2. **REQ-NF-03 (touch target ≥ 44px)** — `.terrain-checkbox`
   (`app.css:806`) при `padding:8px` и `font-size:15px` даёт высоту
   строки ≈ 35px, что ниже ориентира 44px. Однако чекбокс POI
   использует **тот же** класс, что и все соседние чекбоксы попапа
   (Тени/Перепады/Грунтовки/Тропы) — **регрессии нет**, кликабельна вся
   строка-`<label>`. Точная проверка на реальном устройстве (TP-09)
   невозможна без e2e. Если 44px критичны — это пред-существующий
   UX-вопрос ко всему попапу, отдельной задачей. Не блокирует ET-002.
3. **Окружение тестера.** Пакеты `shapely`, `mapbox-vector-tile`,
   `httpx` (`requirements.txt`) и `pytest-asyncio`, `ruff`
   (`pyproject.toml [dev]`) не были предустановлены в песочнице — без
   `shapely` падал сбор `test_health.py` (импорт `src.api.main`).
   Тестер доустановил их по манифестам проекта, после чего регресс
   прошёл полностью. Это дефект провижининга окружения, **не дефект
   ET-002**. CI обязан выполнять `pip install -r requirements.txt` и
   `.[dev]` перед `make test`.

## Замечания тестера

- Прогон выполнен против локального репозитория без поднятого dev-сервера
  (`make test` оффлайн). Backend не затронут (ET-002 — только `src/web/`).
- Ручная сверка реализации: `index.html:56-60` — `<hr>` + чекбокс
  `poi-visible-cb` (`checked`, класс `terrain-checkbox`) сразу после
  `trails-path-cb`; `app.js:2800-2854` — POI-блок с `applyPoiVisibility`,
  `onPoiCheckbox`, `restorePoiState`; `restorePoiState()` подключён в 3
  точках восстановления (`app.js:131, 2950, 2963`). Соответствует ТЗ,
  ADR-0001 и выводам `12-review.md`.
- Тесты не подгонялись под код; на prom ничего не запускалось.

## Итог

**Verdict: PASS** → `stage:ready-to-deploy`.

Все исполнимые тест-кейсы (TP-01..04, TP-08), все 8 acceptance-критериев
и lint — зелёные; блокирующих (P0/P1) багов нет. Незакрытые e2e-кейсы
(TP-05..07, TP-09) заблокированы инфраструктурным конфликтом
approved-артефактов (T-01/P2) — действие на стороне Analyst, на ET-002
не влияет. Готово к штатной перевыкладке фронтенда в test/prom согласно
`07-infra-requirements.md`.
