analyst(ET): auto-commit from analyst run_id=25
This commit is contained in:
7
docs/work-items/ET-007/00-business-request.md
Normal file
7
docs/work-items/ET-007/00-business-request.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Business Request: ET-005: Спутниковая карта (Схема/Спутник)
|
||||
|
||||
Work Item ID: ET-007
|
||||
|
||||
## Description
|
||||
|
||||
TBD
|
||||
80
docs/work-items/ET-008/00-business-request.md
Normal file
80
docs/work-items/ET-008/00-business-request.md
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
type: business-request
|
||||
work_item_id: ET-008
|
||||
title: "Smoke test analyst integration"
|
||||
created_at: 2026-05-31
|
||||
source: pipeline-smoke
|
||||
requester: claude-bot
|
||||
synthetic: true
|
||||
---
|
||||
|
||||
# Бизнес-запрос — ET-008 (Smoke test analyst integration)
|
||||
|
||||
## Контекст
|
||||
|
||||
Это **smoke-работа** для проверки интеграции аналитика в пайплайн
|
||||
`analyst → architect → coder → tester`. Реального заказчика нет;
|
||||
запрос синтезирован, чтобы проверить, что:
|
||||
|
||||
1. Аналитик умеет создать полный пакет артефактов
|
||||
(BRD / TRZ / AC / test-plan / UI test cases) без ручного вмешательства.
|
||||
2. Архитектор может декомпозировать ТЗ в исполнимый план.
|
||||
3. Кодер может реализовать минимальное изменение по плану.
|
||||
4. Тестировщик может прогнать функциональные и Playwright-тесты,
|
||||
и автоматически закрыть задачу.
|
||||
|
||||
В отличие от ET-007 (dry-run по реальной фиче), ET-008 — это **smoke**:
|
||||
скоуп ещё уже, никакого нового UX-функционала для пользователя нет,
|
||||
только технический маркер, видимый Playwright-у.
|
||||
|
||||
Требования к синтетическому скоупу (жёстче, чем у ET-007):
|
||||
|
||||
- Изменение исключительно во фронтенде (`src/web/index.html`,
|
||||
`src/web/app.css`). JavaScript **не трогаем**.
|
||||
- Не ломает существующий функционал: карта, темы, роутинг, GPX, рельеф
|
||||
(hillshade/TRI), POI, разведка, линейка, единицы измерения, поиск,
|
||||
переключатели слоёв.
|
||||
- Виден Playwright-у при специальном условии (`?smoke=et-008` в URL),
|
||||
но **невидим** обычному пользователю при чистой загрузке.
|
||||
- Тривиально откатывается: одна `<div>` в `index.html` + один CSS-блок.
|
||||
- Не зависит от сети, БД, времени, пользовательских действий.
|
||||
|
||||
## Исходная формулировка
|
||||
|
||||
> Нужен невидимый по умолчанию DOM-маркер, который сигнализирует, что
|
||||
> текущая сборка прошла полный конвейер аналитик → архитектор → кодер →
|
||||
> тестировщик. Маркер должен появляться в углу карты только когда в
|
||||
> URL присутствует параметр `?smoke=et-008` (или хеш `#smoke=et-008`).
|
||||
> Это нужно автоматическим тестам пайплайна — они проверяют, что
|
||||
> deploy на test содержит сборку правильного work-item, и что фронтенд
|
||||
> отвечает на разметку без падений.
|
||||
|
||||
## Уточнения (приняты по умолчанию для smoke)
|
||||
|
||||
1. Видимый идентификатор маркера: `#pipeline-smoke`.
|
||||
2. Текст маркера (на русском): «ET-008 ✓».
|
||||
3. Позиция: левый нижний угол экрана (не конфликтует с
|
||||
`#map-controls-r` справа, с `bottom-sheet` снизу-по центру, с
|
||||
`.maplibregl-ctrl-attrib` справа-внизу). Конкретно — `left: 8px;
|
||||
bottom: 8px`.
|
||||
4. Размер: 11px шрифт, нижний регистр, полупрозрачный фон, нейтральный
|
||||
тёмно-серый цвет; не должен закрывать ничего важного даже если
|
||||
как-то стал видимым случайно.
|
||||
5. Условие отображения: маркер присутствует в DOM **всегда**, но имеет
|
||||
`display: none` по умолчанию. Видим становится, когда у `<body>`
|
||||
есть класс `smoke-mode`. Класс ставится автоматически инлайн-скриптом
|
||||
в `<head>`, если `location.search` содержит `smoke=et-008` ИЛИ
|
||||
`location.hash` содержит `smoke=et-008`.
|
||||
6. Не использовать `localStorage`. Маркер ничего не сохраняет —
|
||||
только реагирует на URL текущего хита.
|
||||
7. Доступность: `aria-hidden="true"`, `role="presentation"` — маркер
|
||||
технический, не должен попадать в screen reader.
|
||||
8. Семантика body-класса: имя `smoke-mode` зарезервировано **только**
|
||||
за этим work item. Если в будущем понадобятся аналогичные маркеры
|
||||
для других work items — расширяем семантику тем же классом, но с
|
||||
дополнительными data-атрибутами.
|
||||
9. Backend / БД / тайл-эндпоинты / OSRM / app.js / units.js / gpx.js
|
||||
**не затрагиваются**.
|
||||
10. Тёмная и светлая темы: маркер использует одинаковые цвета в обеих
|
||||
темах (тёмный фон + светлый текст), читаемость гарантируется
|
||||
собственными цветами, а не наследованием от темы.
|
||||
120
docs/work-items/ET-008/01-brd.md
Normal file
120
docs/work-items/ET-008/01-brd.md
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
type: brd
|
||||
work_item_id: ET-008
|
||||
title: "BRD: Smoke test analyst integration"
|
||||
version: 1
|
||||
status: draft
|
||||
created_at: 2026-05-31
|
||||
updated_at: 2026-05-31
|
||||
authors:
|
||||
- "agent:analyst"
|
||||
---
|
||||
|
||||
# BRD — ET-008: Smoke test analyst integration
|
||||
|
||||
## 1. Цель
|
||||
|
||||
Подтвердить, что цепочка агентов `analyst → architect → coder → tester`
|
||||
работоспособна **end-to-end**: аналитик выдаёт валидные артефакты,
|
||||
архитектор по ним строит план, кодер минимально его реализует,
|
||||
тестировщик прогоняет автотесты (включая Playwright) и автоматически
|
||||
закрывает задачу.
|
||||
|
||||
Полезной фичи для конечного пользователя нет — это намеренно
|
||||
технический smoke. Минимальное изменение во фронтенде нужно лишь как
|
||||
«отпечаток сборки», который Playwright увидит и подтвердит.
|
||||
|
||||
## 2. Контекст
|
||||
|
||||
- Веб-приложение: MapLibre GL JS 4.7 + vanilla JS, без фреймворка.
|
||||
- Базовая страница: `src/web/index.html`; стили — `src/web/app.css`;
|
||||
логика — `src/web/app.js` (НЕ ТРОГАТЬ в рамках ET-008).
|
||||
- ET-007 (Спутниковая карта) уже использовал такую же idea-pipeline
|
||||
для dry-run; ET-008 — следующая итерация, ещё минимальнее: ноль JS,
|
||||
ноль внешних зависимостей, ноль localStorage.
|
||||
- Имеется работающая Playwright-инфра (`tests/web/e2e/`), которая
|
||||
умеет открывать тест-окружение и снимать скриншоты.
|
||||
|
||||
## 3. Scope
|
||||
|
||||
### In scope
|
||||
|
||||
| # | Функция |
|
||||
|------|------------------------------------------------------------------------------------------------------|
|
||||
| F-01 | DOM-элемент `#pipeline-smoke` с текстом «ET-008 ✓» в `src/web/index.html` |
|
||||
| F-02 | CSS-правила для `#pipeline-smoke` в `src/web/app.css` (скрыт по умолчанию) |
|
||||
| F-03 | CSS-правила для `body.smoke-mode #pipeline-smoke` (видим в smoke-режиме) |
|
||||
| F-04 | Инлайн-скрипт в `<head>` `src/web/index.html`, добавляющий `smoke-mode` к `<body>` по URL-условию |
|
||||
| F-05 | Условие активации: `location.search.includes('smoke=et-008')` ИЛИ `location.hash.includes('smoke=et-008')` |
|
||||
| F-06 | По умолчанию (без параметра) маркер физически в DOM, но `display:none` |
|
||||
| F-07 | Маркер `aria-hidden="true"` / `role="presentation"` — не попадает в screen reader |
|
||||
| F-08 | Позиционирование: `position: fixed; left: 8px; bottom: 8px; z-index` ниже sheets/popup'ов |
|
||||
| F-09 | Совместимость с тёмной и светлой темами — собственные цвета, не наследует темовые переменные |
|
||||
|
||||
### Out of scope
|
||||
|
||||
- JavaScript-логика в `src/web/app.js`, `units.js`, `gpx.js`.
|
||||
- Любой backend / БД / OSRM / тайлы.
|
||||
- Сохранение состояния (localStorage / sessionStorage / cookies).
|
||||
- Видимость по нажатию кнопки / shortcut'у клавиатуры / тапу — только URL.
|
||||
- Локализация (текст «ET-008 ✓» одинаков для всех языков).
|
||||
- Анимации появления / скрытия.
|
||||
- Управление через query API (REST/IPC).
|
||||
- Мобильный layout-tuning (маркер одинаков на desktop и mobile).
|
||||
- Любые другие work item identifiers, кроме `et-008`.
|
||||
|
||||
## 4. Метрики успеха
|
||||
|
||||
| Метрика | Критерий |
|
||||
|-----------------------------------------------|-----------------------------------------------------------------------------------------|
|
||||
| Наличие маркера в DOM | На любой загрузке `document.querySelector('#pipeline-smoke')` не null |
|
||||
| Скрытость по умолчанию | На `?` (без параметра) `getComputedStyle(...).display === 'none'` |
|
||||
| Видимость в smoke-режиме | На `?smoke=et-008` или `#smoke=et-008` маркер визуально различим в левом нижнем углу |
|
||||
| Контент маркера | `textContent.trim() === 'ET-008 ✓'` |
|
||||
| Корректность ARIA | `aria-hidden="true"`, `role="presentation"` |
|
||||
| Отсутствие конфликтов | Маркер не перекрывает `#map-controls-r`, sheets, `.maplibregl-ctrl-attrib` |
|
||||
| Стабильность тем | После `toggleTheme()` маркер остаётся видим/скрыт согласно своему правилу |
|
||||
| Отсутствие регрессий | Все существующие E2E (ET-001..ET-007) остаются зелёными |
|
||||
| Время от клика до отображения | После загрузки страницы маркер показан **до** `DOMContentLoaded` end (инлайн-скрипт) |
|
||||
| Отсутствие сетевых запросов | ET-008 не порождает ни одного нового HTTP-запроса |
|
||||
| Откатываемость | Полный откат — 3 диффа (HTML head + HTML body + CSS блок), 1 коммит |
|
||||
|
||||
## 5. Риски
|
||||
|
||||
| Риск | Вероятность | Влияние | Митигация |
|
||||
|---------------------------------------------------------------------|-------------|---------|------------------------------------------------------------------------------------|
|
||||
| Инлайн-скрипт в `<head>` ломает CSP | Низкая | Среднее | CSP не настроен сейчас. Если когда-то будет — переключить на data-атрибут + CSS-only|
|
||||
| `#pipeline-smoke` перекрывает важный UI (например `.maplibregl-ctrl-scale`) | Низкая | Низкое | `z-index: 1` — ниже всех плавающих контролов; позиция фиксирована подальше |
|
||||
| Маркер случайно засветился пользователю (например shared link с `?smoke=et-008`) | Низкая | Низкое | Текст нейтральный, не раскрывает ничего внутреннего; стиль ненавязчивый |
|
||||
| Body-класс `smoke-mode` конфликтует с будущим классом темы | Низкая | Низкое | Префикс `smoke-` зарезервирован; в проекте сейчас нет похожих имён |
|
||||
| ARIA-атрибуты «protect» сломаются при mutation observer на body | Низкая | Низкое | `aria-hidden` ставится статически в HTML, не из JS |
|
||||
| Маркер ломает Playwright-снимки других тестов | Низкая | Низкое | По умолчанию `display:none` — на скриншоте не виден, BB-rect = 0 |
|
||||
| Лишний div снижает производительность DOM | Очень низкая| Очень низкое| 1 элемент, без подписок, без обработчиков |
|
||||
| Старые браузеры не поддерживают `includes` на строках | Очень низкая| Низкое | `String.prototype.includes` — ES2015, поддержано во всех целевых браузерах |
|
||||
|
||||
## 6. Зависимости
|
||||
|
||||
- **Внешние сервисы**: нет.
|
||||
- **Внутренние**: только `src/web/index.html`, `src/web/app.css`.
|
||||
- Не зависит от ET-005 / ET-006 / ET-007.
|
||||
- Не блокирует ни одну фазу (PH-1..PH-9).
|
||||
- Не вносит изменений в `app.js`, `units.js`, `gpx.js`.
|
||||
- Не меняет `style.json`, `style-dark.json`.
|
||||
- Backend, БД, OSRM, миграции, контейнеры — не затрагиваются.
|
||||
- CI/CD — не меняется. Только новые E2E-тесты для самого маркера в
|
||||
`tests/web/e2e/pipeline-smoke.spec.ts`.
|
||||
|
||||
## 7. Критерии smoke-успеха (для пайплайна)
|
||||
|
||||
Эта работа считается завершённой, когда:
|
||||
|
||||
1. Все 5 артефактов аналитика (`00`..`04b`) присутствуют и валидны.
|
||||
2. Архитектор выдаёт минимум один план в `05-architecture.md` или
|
||||
эквивалент.
|
||||
3. Кодер вносит изменения только в `src/web/index.html` +
|
||||
`src/web/app.css` (по diff'у видно).
|
||||
4. Все тесты UT/IT/E2E зелёные на test-окружении.
|
||||
5. Tester автоматически закрывает задачу.
|
||||
|
||||
Само наличие зелёного smoke-прогона важнее, чем визуальная
|
||||
эстетика маркера.
|
||||
281
docs/work-items/ET-008/02-trz.md
Normal file
281
docs/work-items/ET-008/02-trz.md
Normal file
@@ -0,0 +1,281 @@
|
||||
---
|
||||
type: trz
|
||||
work_item_id: ET-008
|
||||
title: "ТЗ: Smoke test analyst integration"
|
||||
version: 1
|
||||
status: draft
|
||||
created_at: 2026-05-31
|
||||
updated_at: 2026-05-31
|
||||
authors:
|
||||
- "agent:analyst"
|
||||
depends_on:
|
||||
- "ET-008/01-brd.md"
|
||||
---
|
||||
|
||||
# Техническое задание — ET-008: Smoke test analyst integration
|
||||
|
||||
## 1. Общая архитектура изменений
|
||||
|
||||
Изменение строго фронтовое и максимально минимальное. Затрагиваются
|
||||
**ровно два файла**:
|
||||
|
||||
- `src/web/index.html`
|
||||
- В `<head>` добавить инлайн-скрипт, который выставляет
|
||||
`document.body.classList.add('smoke-mode')`, если в URL присутствует
|
||||
`smoke=et-008` (в `search` или `hash`).
|
||||
- В `<body>` (рядом с `<div id="map">` или сразу перед `<!-- Scripts -->`)
|
||||
добавить элемент `<div id="pipeline-smoke" aria-hidden="true"
|
||||
role="presentation">ET-008 ✓</div>`.
|
||||
- `src/web/app.css`
|
||||
- В конец файла добавить блок с правилами для `#pipeline-smoke`.
|
||||
|
||||
**Запрещено** трогать: `src/web/app.js`, `src/web/units.js`,
|
||||
`src/web/gpx.js`, `src/web/style.json`, `src/web/style-dark.json`,
|
||||
`src/api/**`, `tests/api/**`, миграции, Docker-конфигурацию.
|
||||
|
||||
## 2. Функциональные требования
|
||||
|
||||
### REQ-F-01. DOM-маркер `#pipeline-smoke`
|
||||
|
||||
В `src/web/index.html` добавить элемент:
|
||||
|
||||
```html
|
||||
<div id="pipeline-smoke" aria-hidden="true" role="presentation">ET-008 ✓</div>
|
||||
```
|
||||
|
||||
Размещение: после `<div id="map"></div>` (т.е. внутри `<body>`,
|
||||
сразу после карты), но **до** `<!-- Scripts -->`. Конкретное место —
|
||||
после блока `<div id="no-data-warning">` или эквивалентного места,
|
||||
где сейчас лежат «свободно плавающие» элементы (`#ruler-toast`,
|
||||
`#app-toast`).
|
||||
|
||||
Текст узла: ровно `ET-008 ✓` (без кавычек, с одним пробелом перед
|
||||
галочкой, U+2713 CHECK MARK).
|
||||
|
||||
### REQ-F-02. Инлайн-скрипт активации в `<head>`
|
||||
|
||||
В `<head>` `src/web/index.html`, **после** `<link rel="stylesheet" href="app.css">`
|
||||
и **до** закрывающего `</head>`, добавить:
|
||||
|
||||
```html
|
||||
<script>
|
||||
// ET-008: smoke marker activation. Pure URL-based, no storage, no I/O.
|
||||
(function () {
|
||||
try {
|
||||
var s = (location.search || '') + ' ' + (location.hash || '');
|
||||
if (s.indexOf('smoke=et-008') !== -1) {
|
||||
document.documentElement.classList.add('smoke-mode');
|
||||
}
|
||||
} catch (e) { /* swallow — smoke is non-critical */ }
|
||||
})();
|
||||
</script>
|
||||
```
|
||||
|
||||
Замечания:
|
||||
- Скрипт ставит класс на `<html>` (`documentElement`), а не на
|
||||
`<body>`, потому что на момент выполнения (в `<head>`) `<body>`
|
||||
ещё не существует. Эквивалентный селектор для CSS —
|
||||
`html.smoke-mode #pipeline-smoke`. Это требование к CSS-блоку
|
||||
(см. REQ-F-04).
|
||||
- `try/catch` обязателен — если по какой-то причине `location`
|
||||
не доступен (тест с jsdom без URL), скрипт не должен ронять
|
||||
страницу.
|
||||
- Скрипт **синхронный**, не модуль, без `defer`/`async` — он
|
||||
должен сработать до того, как CSS попытается применить
|
||||
`display:none` к видимому в smoke маркеру (избежать FOUC).
|
||||
|
||||
### REQ-F-03. CSS базовое состояние (скрыт)
|
||||
|
||||
В конец `src/web/app.css` добавить:
|
||||
|
||||
```css
|
||||
/* ── ET-008: pipeline smoke marker ───────────────────────────
|
||||
По умолчанию полностью скрыт. Включается классом .smoke-mode
|
||||
на <html>, который ставит инлайн-скрипт в <head>, если
|
||||
в URL присутствует ?smoke=et-008 или #smoke=et-008.
|
||||
──────────────────────────────────────────────────────────── */
|
||||
#pipeline-smoke {
|
||||
display: none;
|
||||
}
|
||||
```
|
||||
|
||||
### REQ-F-04. CSS активное состояние (видим)
|
||||
|
||||
Сразу после REQ-F-03 в `src/web/app.css`:
|
||||
|
||||
```css
|
||||
html.smoke-mode #pipeline-smoke {
|
||||
display: inline-block;
|
||||
position: fixed;
|
||||
left: 8px;
|
||||
bottom: 8px;
|
||||
z-index: 1;
|
||||
padding: 2px 6px;
|
||||
font: 11px/1.4 system-ui, -apple-system, sans-serif;
|
||||
color: #e7e7e7;
|
||||
background: rgba(20, 20, 28, 0.7);
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
```
|
||||
|
||||
Значения цвета и фона **не зависят** от темы — это намеренно, чтобы
|
||||
маркер был стабильно читаем и в светлой, и в тёмной теме без отдельных
|
||||
правил `body.theme-dark`.
|
||||
|
||||
### REQ-F-05. Условие активации
|
||||
|
||||
Маркер показывается, если **любое** из условий выполнено:
|
||||
|
||||
- `location.search` (часть URL после `?`) содержит подстроку
|
||||
`smoke=et-008`;
|
||||
- `location.hash` (часть URL после `#`) содержит подстроку
|
||||
`smoke=et-008`.
|
||||
|
||||
Сравнение **точное**, чувствительно к регистру. Любое отклонение
|
||||
(`smoke=ET-008`, `smoke=et_008`, `smoke=et-007`, `smoketest=et-008`)
|
||||
**не** активирует маркер.
|
||||
|
||||
### REQ-F-06. Не-конфликтность с темами
|
||||
|
||||
После `toggleTheme()` маркер:
|
||||
- если был видим (smoke-mode) — остаётся видимым с теми же цветами;
|
||||
- если был скрыт (no smoke-mode) — остаётся скрытым.
|
||||
|
||||
То есть body-класс `theme-dark` не влияет на отображение
|
||||
`#pipeline-smoke`.
|
||||
|
||||
### REQ-F-07. Не-конфликтность с layout
|
||||
|
||||
`z-index: 1` ниже, чем у `#map-controls-r` (фактический z-index у
|
||||
существующих контролов — выше). Маркер не должен:
|
||||
- перекрывать `#map-controls-r` (top-right);
|
||||
- перекрывать `.maplibregl-ctrl-attrib` (bottom-right);
|
||||
- мешать `#toolbar` на мобильном (нижняя панель);
|
||||
- перекрывать любой `.bottom-sheet`, когда тот открыт.
|
||||
|
||||
В частности на мобильном бутылочное горлышко — `#toolbar` снизу.
|
||||
Маркер в `left: 8px; bottom: 8px` помещается слева, тулбар обычно
|
||||
центрирован/растянут, так что небольшой риск перекрытия допустим,
|
||||
но визуально не критичен (маркер маленький и полупрозрачный).
|
||||
|
||||
### REQ-F-08. ARIA
|
||||
|
||||
- `aria-hidden="true"` — обязательно.
|
||||
- `role="presentation"` — обязательно.
|
||||
- Никаких подсказок (`title`), `aria-label`, фокусируемых элементов
|
||||
внутри.
|
||||
|
||||
### REQ-F-09. Отсутствие сетевых эффектов
|
||||
|
||||
Реализация не должна:
|
||||
- добавлять `<link>` / `<script src="...">` / `<img>` / `<iframe>`;
|
||||
- обращаться к `fetch` / `XMLHttpRequest` / `navigator.sendBeacon`;
|
||||
- использовать `localStorage` / `sessionStorage` / `cookie` /
|
||||
`IndexedDB` / `caches`.
|
||||
|
||||
### REQ-F-10. Совместимость с Playwright `route(...)`
|
||||
|
||||
Playwright-тесты ET-008 имеют право ассертить отсутствие новых
|
||||
сетевых запросов после добавления маркера (например, через
|
||||
`page.on('request', ...)`). Любая сетевая активность из ET-008 —
|
||||
дефект.
|
||||
|
||||
## 3. Нефункциональные требования
|
||||
|
||||
### REQ-NF-01. Производительность
|
||||
|
||||
- Дополнительный DOM-узел — 1 (`<div>`).
|
||||
- Дополнительный CSS-блок — ≤ 20 строк.
|
||||
- Дополнительный JS — инлайн ≤ 10 строк, выполняется **один раз**
|
||||
до парсинга `<body>`.
|
||||
- Никаких подписок на события, MutationObserver, requestAnimationFrame.
|
||||
|
||||
### REQ-NF-02. Совместимость браузеров
|
||||
|
||||
Те же, что у основного приложения:
|
||||
- Chrome ≥ 110, Firefox ≥ 110, Safari ≥ 16, мобильные Chrome/Safari
|
||||
iOS ≥ 16.
|
||||
- `String.prototype.indexOf` — ES1, поддержано всегда.
|
||||
|
||||
### REQ-NF-03. Логирование
|
||||
|
||||
В нормальной работе скрипт ничего не пишет в консоль.
|
||||
В исключительной — молча проглатывает (`try/catch` без `console.warn`).
|
||||
Это смягчение для тестов, которые ассертят «no console.error».
|
||||
|
||||
### REQ-NF-04. Локализация
|
||||
|
||||
Текст `ET-008 ✓` не локализуется. Это технический идентификатор,
|
||||
одинаковый во всех языках.
|
||||
|
||||
### REQ-NF-05. Безопасность
|
||||
|
||||
- Никаких пользовательских данных в маркере.
|
||||
- Никакого `innerHTML` / `eval` / `Function(...)`.
|
||||
- Никаких внешних URL.
|
||||
|
||||
## 4. Структура данных
|
||||
|
||||
### REQ-D-01. URL-параметры
|
||||
|
||||
| Параметр | Тип | Значение | Эффект |
|
||||
|-----------------------|----------|---------------------------|-----------------------------------------|
|
||||
| `?smoke=et-008` | query | строго `et-008` | `html.classList.add('smoke-mode')` |
|
||||
| `#smoke=et-008` | fragment | строго `et-008` | то же самое |
|
||||
| (нет параметра) | — | — | маркер `display:none` |
|
||||
| `?smoke=...` иное | query | любое отличное от `et-008`| маркер `display:none` |
|
||||
| `#smoke=...` иное | fragment | любое отличное от `et-008`| маркер `display:none` |
|
||||
|
||||
### REQ-D-02. Классы CSS
|
||||
|
||||
| Класс | Селектор где | Назначение |
|
||||
|----------------------|-------------------------------|-------------------------------------|
|
||||
| `smoke-mode` | на `<html>` | флаг видимости маркера |
|
||||
|
||||
## 5. UI / UX контракт
|
||||
|
||||
| Элемент | Местоположение | Поведение |
|
||||
|---------------------|-----------------------------------------------------------------|------------------------------------------------------------------------|
|
||||
| `#pipeline-smoke` | `<body>`, после `#map`, до `<!-- Scripts -->` | По умолчанию `display:none`. В smoke-режиме — fixed left:8 bottom:8 |
|
||||
| Содержимое | Текстовый узел `ET-008 ✓` | Не меняется в рантайме |
|
||||
| Фон | `rgba(20,20,28,0.7)` | Полупрозрачный, чтобы видеть карту под ним |
|
||||
| Текст | `#e7e7e7`, 11px | Контраст ≥ 4.5:1 над тёмным фоном маркера |
|
||||
| Курсор / фокус | `pointer-events: none`, не фокусируется | Не мешает кликам по карте под ним |
|
||||
|
||||
## 6. Тестируемость
|
||||
|
||||
- **Unit**: проверка наличия CSS-блока и DOM-узла через парсинг файлов
|
||||
(без запуска браузера).
|
||||
- **Integration**: запуск страницы в jsdom с поддельным `location`,
|
||||
проверка добавления класса.
|
||||
- **E2E (Playwright)**: загрузка test-окружения с `?smoke=et-008`
|
||||
и без, проверка видимости/скрытости, отсутствия сетевых запросов,
|
||||
отсутствия console.error.
|
||||
- **UI (Playwright скриншоты)**: см. `04b-ui-test-cases.md`.
|
||||
|
||||
## 7. Совместимость и миграции
|
||||
|
||||
- БД: миграций нет.
|
||||
- API: новых эндпоинтов нет.
|
||||
- Конфиги: новых ENV не добавляем.
|
||||
- Существующие work item: не затрагиваются (только статические файлы,
|
||||
никаких JS-символов).
|
||||
- CI: новые тесты добавляются как файл
|
||||
`tests/web/e2e/pipeline-smoke.spec.ts`, существующая команда
|
||||
`make test` подхватывает их без изменения конфигов.
|
||||
|
||||
## 8. Откат
|
||||
|
||||
В случае проблем:
|
||||
1. В `src/web/index.html` удалить инлайн-`<script>` из `<head>`
|
||||
(REQ-F-02).
|
||||
2. В `src/web/index.html` удалить `<div id="pipeline-smoke">…</div>`
|
||||
из `<body>` (REQ-F-01).
|
||||
3. В `src/web/app.css` удалить блок с правилами для `#pipeline-smoke`
|
||||
(REQ-F-03 и REQ-F-04).
|
||||
|
||||
Полный откат — один коммит, без побочных эффектов и без изменения
|
||||
JS / API / БД.
|
||||
177
docs/work-items/ET-008/03-acceptance-criteria.md
Normal file
177
docs/work-items/ET-008/03-acceptance-criteria.md
Normal file
@@ -0,0 +1,177 @@
|
||||
---
|
||||
type: acceptance-criteria
|
||||
work_item_id: ET-008
|
||||
title: "AC: Smoke test analyst integration"
|
||||
version: 1
|
||||
status: draft
|
||||
created_at: 2026-05-31
|
||||
updated_at: 2026-05-31
|
||||
authors:
|
||||
- "agent:analyst"
|
||||
depends_on:
|
||||
- "ET-008/01-brd.md"
|
||||
- "ET-008/02-trz.md"
|
||||
---
|
||||
|
||||
# Критерии приёмки — ET-008: Smoke test analyst integration
|
||||
|
||||
Формат: Gherkin. Сценарии — в браузере приложения, на test-окружении
|
||||
`https://openclaw.mva154.duckdns.org/enduro/`, если не указано иное.
|
||||
|
||||
## AC-01. Маркер присутствует в DOM при любой загрузке
|
||||
|
||||
```
|
||||
Given пользователь открывает приложение в браузере без query-параметров
|
||||
When страница полностью загружена
|
||||
Then в DOM существует элемент с id="pipeline-smoke"
|
||||
And его textContent.trim() == "ET-008 ✓"
|
||||
And у элемента aria-hidden="true"
|
||||
And у элемента role="presentation"
|
||||
```
|
||||
|
||||
## AC-02. Маркер скрыт по умолчанию
|
||||
|
||||
```
|
||||
Given пользователь открывает приложение без параметра smoke
|
||||
When страница загружена
|
||||
Then getComputedStyle(#pipeline-smoke).display == "none"
|
||||
And элемент не виден визуально (boundingClientRect.height == 0)
|
||||
```
|
||||
|
||||
## AC-03. Маркер виден при ?smoke=et-008 в query
|
||||
|
||||
```
|
||||
Given пользователь открывает приложение с URL "...?smoke=et-008"
|
||||
When страница загружена
|
||||
Then у элемента <html> присутствует класс "smoke-mode"
|
||||
And getComputedStyle(#pipeline-smoke).display == "inline-block"
|
||||
And маркер визуально расположен в левом нижнем углу
|
||||
(left ≈ 8px, bottom ≈ 8px от края viewport)
|
||||
```
|
||||
|
||||
## AC-04. Маркер виден при #smoke=et-008 в hash
|
||||
|
||||
```
|
||||
Given пользователь открывает приложение с URL "...#smoke=et-008"
|
||||
When страница загружена
|
||||
Then у элемента <html> присутствует класс "smoke-mode"
|
||||
And маркер визуально виден в левом нижнем углу
|
||||
```
|
||||
|
||||
## AC-05. Маркер скрыт при неверном значении параметра
|
||||
|
||||
```
|
||||
Given пользователь открывает приложение с URL "...?smoke=et-007"
|
||||
When страница загружена
|
||||
Then у элемента <html> класса "smoke-mode" НЕТ
|
||||
And getComputedStyle(#pipeline-smoke).display == "none"
|
||||
```
|
||||
|
||||
## AC-06. Маркер скрыт при неверном имени параметра
|
||||
|
||||
```
|
||||
Given пользователь открывает приложение с URL "...?smoketest=et-008"
|
||||
When страница загружена
|
||||
Then у элемента <html> класса "smoke-mode" НЕТ
|
||||
And маркер не виден
|
||||
```
|
||||
|
||||
## AC-07. Корректность ARIA
|
||||
|
||||
```
|
||||
Given маркер видим (?smoke=et-008)
|
||||
When ассистивная технология сканирует страницу
|
||||
Then узел #pipeline-smoke не попадает в accessibility tree
|
||||
(aria-hidden="true")
|
||||
And screen reader не озвучивает "ET-008 ✓"
|
||||
```
|
||||
|
||||
## AC-08. Совместимость с переключением темы
|
||||
|
||||
```
|
||||
Given маркер видим (?smoke=et-008) в светлой теме
|
||||
When пользователь нажимает #btn-theme (включение тёмной темы)
|
||||
Then тема приложения меняется (body.theme-dark)
|
||||
And маркер #pipeline-smoke остаётся видимым
|
||||
And его background и color не изменились
|
||||
And обратное переключение темы не скрывает маркер
|
||||
```
|
||||
|
||||
## AC-09. Маркер не перекрывает важные элементы UI
|
||||
|
||||
```
|
||||
Given маркер видим
|
||||
When пользователь смотрит на интерфейс
|
||||
Then маркер не перекрывает #map-controls-r
|
||||
And не перекрывает .maplibregl-ctrl-attrib
|
||||
And при открытии bottom-sheet маркер либо остаётся под sheet,
|
||||
либо вообще исчезает за ним (sheet z-index выше)
|
||||
And клики «сквозь» маркер по карте проходят
|
||||
(pointer-events: none на маркере)
|
||||
```
|
||||
|
||||
## AC-10. Отсутствие сетевых запросов от ET-008
|
||||
|
||||
```
|
||||
Given Playwright перехватывает все запросы
|
||||
When страница загружена с ?smoke=et-008
|
||||
Then ни одного НОВОГО HTTP-запроса не порождено логикой ET-008
|
||||
(только базовые тайлы, app.js, units.js, gpx.js, app.css)
|
||||
And в частности нет запросов к /api/smoke/* или подобному
|
||||
```
|
||||
|
||||
## AC-11. Отсутствие console.error
|
||||
|
||||
```
|
||||
Given включён сбор console-сообщений
|
||||
When страница загружена с ?smoke=et-008 (или без)
|
||||
Then в течение 10 секунд после load нет ни одного console.error
|
||||
And console.warn от ET-008 (наш try/catch) не выстреливает
|
||||
в нормальной работе
|
||||
```
|
||||
|
||||
## AC-12. Маркер показывается ДО DOMContentLoaded
|
||||
|
||||
```
|
||||
Given пользователь грузит страницу с ?smoke=et-008 в медленной сети
|
||||
When браузер ещё парсит <body>
|
||||
Then инлайн-скрипт в <head> уже выставил html.smoke-mode
|
||||
And нет FOUC (маркер не мигает «скрыт → видим» после загрузки)
|
||||
```
|
||||
|
||||
## AC-13. Не ломает существующий функционал
|
||||
|
||||
```
|
||||
Given изменения ET-008 применены
|
||||
When пользователь использует
|
||||
роутинг (Маршрут), связку, красивый, разведку, линейку,
|
||||
поиск, метку, GPX, единицы измерения (км/мили),
|
||||
переключение темы, тени рельефа, переключение POI
|
||||
Then все режимы работают как до изменений
|
||||
And нет регрессий в существующих E2E (ET-001..ET-007)
|
||||
And нет ошибок в console.error при базовом сценарии
|
||||
```
|
||||
|
||||
## AC-14. Откатываемость
|
||||
|
||||
```
|
||||
Given изменения ET-008 применены и работают
|
||||
When все упомянутые в TRZ §8 правки откатываются
|
||||
Then приложение возвращается к состоянию до ET-008
|
||||
And ни один тест из существующего набора не падает
|
||||
And в DOM нет осиротевших элементов
|
||||
And в CSS нет неиспользуемых правил для #pipeline-smoke
|
||||
```
|
||||
|
||||
## AC-15. Изоляция диффа
|
||||
|
||||
```
|
||||
Given коммит реализации ET-008
|
||||
When анализируется его diff
|
||||
Then изменены только два файла:
|
||||
- src/web/index.html
|
||||
- src/web/app.css
|
||||
And app.js / units.js / gpx.js / style.json / style-dark.json
|
||||
не затронуты
|
||||
And ни одна строка backend, миграций, Docker-конфигов не изменена
|
||||
```
|
||||
300
docs/work-items/ET-008/04-test-plan.yaml
Normal file
300
docs/work-items/ET-008/04-test-plan.yaml
Normal file
@@ -0,0 +1,300 @@
|
||||
---
|
||||
type: test-plan
|
||||
work_item_id: ET-008
|
||||
title: "Test Plan: Smoke test analyst integration"
|
||||
version: 1
|
||||
status: draft
|
||||
created_at: 2026-05-31
|
||||
updated_at: 2026-05-31
|
||||
authors:
|
||||
- "agent:analyst"
|
||||
depends_on:
|
||||
- "ET-008/02-trz.md"
|
||||
- "ET-008/03-acceptance-criteria.md"
|
||||
---
|
||||
|
||||
# План функциональных тестов — ET-008
|
||||
|
||||
scope:
|
||||
- src/web/index.html
|
||||
- src/web/app.css
|
||||
|
||||
out_of_scope:
|
||||
- src/web/app.js
|
||||
- src/web/units.js
|
||||
- src/web/gpx.js
|
||||
- src/web/style.json
|
||||
- src/web/style-dark.json
|
||||
- src/api/**
|
||||
- migrations/**
|
||||
- tests/api/**
|
||||
|
||||
test_suites:
|
||||
|
||||
- id: unit
|
||||
name: "Unit-тесты статической структуры файлов"
|
||||
runner: "node:test (без браузера)"
|
||||
location: "tests/web/unit/pipeline-smoke.test.js"
|
||||
cases:
|
||||
|
||||
- id: UT-01
|
||||
title: "index.html содержит инлайн-скрипт активации smoke-mode"
|
||||
ac_refs: [AC-03, AC-04, AC-12]
|
||||
given:
|
||||
- "файл src/web/index.html прочитан как строка"
|
||||
when:
|
||||
- "ищется подстрока 'smoke=et-008'"
|
||||
- "ищется подстрока \"classList.add('smoke-mode')\""
|
||||
then:
|
||||
- "обе подстроки найдены"
|
||||
- "скрипт расположен внутри <head> (по offset раньше </head>)"
|
||||
|
||||
- id: UT-02
|
||||
title: "index.html содержит элемент #pipeline-smoke"
|
||||
ac_refs: [AC-01]
|
||||
given:
|
||||
- "файл src/web/index.html прочитан как строка"
|
||||
when:
|
||||
- "ищется регулярка /id=\"pipeline-smoke\"/"
|
||||
- "ищется текст 'ET-008 ✓'"
|
||||
- "ищется atrribute aria-hidden=\"true\""
|
||||
- "ищется attribute role=\"presentation\""
|
||||
then:
|
||||
- "все четыре поиска успешны"
|
||||
- "элемент расположен внутри <body>, до <!-- Scripts -->"
|
||||
|
||||
- id: UT-03
|
||||
title: "app.css содержит правила для #pipeline-smoke"
|
||||
ac_refs: [AC-02, AC-03]
|
||||
given:
|
||||
- "файл src/web/app.css прочитан как строка"
|
||||
when:
|
||||
- "ищется селектор '#pipeline-smoke'"
|
||||
- "ищется селектор 'html.smoke-mode #pipeline-smoke'"
|
||||
- "ищется свойство 'display: none' для первого селектора"
|
||||
- "ищется свойство 'position: fixed' для второго селектора"
|
||||
then:
|
||||
- "все четыре поиска успешны"
|
||||
|
||||
- id: UT-04
|
||||
title: "Запрещённые файлы не изменены (whitelist diff)"
|
||||
ac_refs: [AC-15]
|
||||
given:
|
||||
- "git diff feature/ET-008-... main"
|
||||
when:
|
||||
- "анализируется список изменённых файлов"
|
||||
then:
|
||||
- "ни один из {app.js, units.js, gpx.js, style.json, style-dark.json} не в diff"
|
||||
- "ни один из {src/api/**, migrations/**, Dockerfile, docker-compose*.yml} не в diff"
|
||||
- "в diff присутствуют только {src/web/index.html, src/web/app.css, docs/work-items/ET-008/**, tests/web/**/pipeline-smoke*}"
|
||||
|
||||
- id: integration
|
||||
name: "Интеграционные тесты в jsdom"
|
||||
runner: "node:test + jsdom"
|
||||
location: "tests/web/integration/pipeline-smoke.test.js"
|
||||
cases:
|
||||
|
||||
- id: IT-01
|
||||
title: "Без smoke-параметра — класс не ставится"
|
||||
ac_refs: [AC-02, AC-05, AC-06]
|
||||
given:
|
||||
- "jsdom инициализирован с URL https://localhost/enduro/"
|
||||
- "загружен src/web/index.html (инлайн-скрипт исполнится синхронно)"
|
||||
when:
|
||||
- "проверяется document.documentElement.classList"
|
||||
then:
|
||||
- "класса 'smoke-mode' нет"
|
||||
|
||||
- id: IT-02
|
||||
title: "С ?smoke=et-008 — класс ставится"
|
||||
ac_refs: [AC-03]
|
||||
given:
|
||||
- "jsdom инициализирован с URL https://localhost/enduro/?smoke=et-008"
|
||||
when:
|
||||
- "загружен src/web/index.html"
|
||||
then:
|
||||
- "document.documentElement.classList.contains('smoke-mode') == true"
|
||||
|
||||
- id: IT-03
|
||||
title: "С #smoke=et-008 в hash — класс ставится"
|
||||
ac_refs: [AC-04]
|
||||
given:
|
||||
- "jsdom инициализирован с URL https://localhost/enduro/#smoke=et-008"
|
||||
when:
|
||||
- "загружен src/web/index.html"
|
||||
then:
|
||||
- "document.documentElement.classList.contains('smoke-mode') == true"
|
||||
|
||||
- id: IT-04
|
||||
title: "С ?smoke=et-007 — класс не ставится"
|
||||
ac_refs: [AC-05]
|
||||
given:
|
||||
- "jsdom URL .../?smoke=et-007"
|
||||
when:
|
||||
- "загружен index.html"
|
||||
then:
|
||||
- "классa 'smoke-mode' нет"
|
||||
|
||||
- id: IT-05
|
||||
title: "С ?smoketest=et-008 — класс не ставится"
|
||||
ac_refs: [AC-06]
|
||||
given:
|
||||
- "jsdom URL .../?smoketest=et-008"
|
||||
when:
|
||||
- "загружен index.html"
|
||||
then:
|
||||
- "класса 'smoke-mode' нет"
|
||||
|
||||
- id: IT-06
|
||||
title: "Регистр имеет значение: ?smoke=ET-008 не активирует"
|
||||
ac_refs: [AC-05]
|
||||
given:
|
||||
- "jsdom URL .../?smoke=ET-008"
|
||||
when:
|
||||
- "загружен index.html"
|
||||
then:
|
||||
- "класса 'smoke-mode' нет"
|
||||
|
||||
- id: e2e
|
||||
name: "E2E (Playwright) — функциональные сценарии"
|
||||
runner: "playwright"
|
||||
location: "tests/web/e2e/pipeline-smoke.spec.ts"
|
||||
base_url: "https://openclaw.mva154.duckdns.org/enduro/"
|
||||
notes:
|
||||
- "UI-визуальные тесты вынесены в 04b-ui-test-cases.md."
|
||||
- "Эти тесты проверяют DOM-состояние и сетевые/console-эффекты, без скриншот-сравнений."
|
||||
cases:
|
||||
|
||||
- id: E2E-01
|
||||
title: "Маркер скрыт при чистой загрузке"
|
||||
ac_refs: [AC-01, AC-02]
|
||||
steps:
|
||||
- "navigate base_url"
|
||||
- "wait map idle (4000 ms)"
|
||||
expect:
|
||||
- "selector #pipeline-smoke exists"
|
||||
- "evaluate: document.querySelector('#pipeline-smoke').textContent.trim() == 'ET-008 ✓'"
|
||||
- "evaluate: getComputedStyle(document.querySelector('#pipeline-smoke')).display == 'none'"
|
||||
- "evaluate: !document.documentElement.classList.contains('smoke-mode')"
|
||||
|
||||
- id: E2E-02
|
||||
title: "Маркер видим при ?smoke=et-008"
|
||||
ac_refs: [AC-03]
|
||||
steps:
|
||||
- "navigate base_url + '?smoke=et-008'"
|
||||
- "wait map idle (4000 ms)"
|
||||
expect:
|
||||
- "evaluate: document.documentElement.classList.contains('smoke-mode')"
|
||||
- "evaluate: getComputedStyle(document.querySelector('#pipeline-smoke')).display == 'inline-block'"
|
||||
- "boundingClientRect(#pipeline-smoke).height > 0"
|
||||
|
||||
- id: E2E-03
|
||||
title: "Маркер видим при #smoke=et-008"
|
||||
ac_refs: [AC-04]
|
||||
steps:
|
||||
- "navigate base_url + '#smoke=et-008'"
|
||||
- "wait map idle (4000 ms)"
|
||||
expect:
|
||||
- "evaluate: document.documentElement.classList.contains('smoke-mode')"
|
||||
- "evaluate: getComputedStyle(document.querySelector('#pipeline-smoke')).display == 'inline-block'"
|
||||
|
||||
- id: E2E-04
|
||||
title: "Маркер скрыт при неверном значении (?smoke=et-007)"
|
||||
ac_refs: [AC-05]
|
||||
steps:
|
||||
- "navigate base_url + '?smoke=et-007'"
|
||||
- "wait map idle (4000 ms)"
|
||||
expect:
|
||||
- "evaluate: !document.documentElement.classList.contains('smoke-mode')"
|
||||
- "evaluate: getComputedStyle(document.querySelector('#pipeline-smoke')).display == 'none'"
|
||||
|
||||
- id: E2E-05
|
||||
title: "Совместимость с переключением темы"
|
||||
ac_refs: [AC-08]
|
||||
steps:
|
||||
- "navigate base_url + '?smoke=et-008'"
|
||||
- "wait map idle (4000 ms)"
|
||||
- "click #btn-theme"
|
||||
- "wait 1500 ms"
|
||||
- "click #btn-theme"
|
||||
- "wait 1500 ms"
|
||||
expect:
|
||||
- "после каждого переключения темы getComputedStyle(#pipeline-smoke).display == 'inline-block'"
|
||||
- "background-color и color #pipeline-smoke не зависят от темы"
|
||||
|
||||
- id: E2E-06
|
||||
title: "Отсутствие новых сетевых запросов"
|
||||
ac_refs: [AC-10]
|
||||
steps:
|
||||
- "register page.on('request') accumulator"
|
||||
- "navigate base_url + '?smoke=et-008'"
|
||||
- "wait map idle (4000 ms)"
|
||||
expect:
|
||||
- "среди собранных URL нет /api/smoke, /api/et-008, /smoke/*"
|
||||
- "количество запросов сопоставимо с базовой загрузкой без ET-008 (± 5%)"
|
||||
|
||||
- id: E2E-07
|
||||
title: "Отсутствие console.error / console.warn от ET-008"
|
||||
ac_refs: [AC-11]
|
||||
steps:
|
||||
- "register page.on('console')"
|
||||
- "navigate base_url + '?smoke=et-008'"
|
||||
- "wait 10000 ms"
|
||||
expect:
|
||||
- "0 console.error сообщений за время теста"
|
||||
- "0 console.warn сообщений, чей text содержит 'ET-008' или 'smoke'"
|
||||
|
||||
- id: E2E-08
|
||||
title: "ARIA-атрибуты выставлены корректно"
|
||||
ac_refs: [AC-07]
|
||||
steps:
|
||||
- "navigate base_url + '?smoke=et-008'"
|
||||
- "wait 4000 ms"
|
||||
expect:
|
||||
- "getAttribute(#pipeline-smoke, 'aria-hidden') == 'true'"
|
||||
- "getAttribute(#pipeline-smoke, 'role') == 'presentation'"
|
||||
|
||||
- id: E2E-09
|
||||
title: "Клики проходят сквозь маркер"
|
||||
ac_refs: [AC-09]
|
||||
steps:
|
||||
- "navigate base_url + '?smoke=et-008'"
|
||||
- "wait 4000 ms"
|
||||
- "позиционировать курсор на координаты левого нижнего угла (10, 90vh)"
|
||||
- "click"
|
||||
expect:
|
||||
- "клик зарегистрирован картой (например, map.on('click') триггерится)"
|
||||
- "маркер не получил focus, не вызвал событий"
|
||||
|
||||
- id: E2E-10
|
||||
title: "Не ломает существующий набор E2E"
|
||||
ac_refs: [AC-13]
|
||||
steps:
|
||||
- "запустить полный набор tests/web/e2e/**"
|
||||
expect:
|
||||
- "все ранее зелёные тесты остаются зелёными"
|
||||
|
||||
coverage_matrix:
|
||||
AC-01: [UT-02, E2E-01]
|
||||
AC-02: [UT-03, IT-01, E2E-01]
|
||||
AC-03: [UT-01, UT-03, IT-02, E2E-02]
|
||||
AC-04: [UT-01, IT-03, E2E-03]
|
||||
AC-05: [IT-04, IT-06, E2E-04]
|
||||
AC-06: [IT-05]
|
||||
AC-07: [UT-02, E2E-08]
|
||||
AC-08: [E2E-05]
|
||||
AC-09: [E2E-09]
|
||||
AC-10: [E2E-06]
|
||||
AC-11: [E2E-07]
|
||||
AC-12: [UT-01]
|
||||
AC-13: [E2E-10]
|
||||
AC-14: "manual (revert + rerun E2E-10)"
|
||||
AC-15: [UT-04]
|
||||
|
||||
exit_criteria:
|
||||
- "Все unit-тесты UT-01..UT-04 зелёные"
|
||||
- "Все integration-тесты IT-01..IT-06 зелёные"
|
||||
- "Все E2E E2E-01..E2E-10 зелёные на test-окружении"
|
||||
- "Существующий набор UI/E2E не имеет регрессий"
|
||||
- "Покрытие AC ≥ 14 из 15 автотестами (AC-14 — ручная проверка отката)"
|
||||
- "git diff показывает изменения только в src/web/index.html, src/web/app.css, docs/work-items/ET-008/**, tests/web/**/pipeline-smoke*"
|
||||
230
docs/work-items/ET-008/04b-ui-test-cases.md
Normal file
230
docs/work-items/ET-008/04b-ui-test-cases.md
Normal file
@@ -0,0 +1,230 @@
|
||||
---
|
||||
type: ui-test-cases
|
||||
work_item_id: ET-008
|
||||
title: "UI Test Cases: Smoke test analyst integration"
|
||||
version: 1
|
||||
status: draft
|
||||
created_at: 2026-05-31
|
||||
updated_at: 2026-05-31
|
||||
authors:
|
||||
- "agent:analyst"
|
||||
depends_on:
|
||||
- "ET-008/02-trz.md"
|
||||
- "ET-008/03-acceptance-criteria.md"
|
||||
---
|
||||
|
||||
# UI Test Cases — ET-008: Smoke test analyst integration
|
||||
|
||||
Playwright-сценарии для визуального тестирования. Базовый URL для всех
|
||||
кейсов: `https://openclaw.mva154.duckdns.org/enduro/` (с добавлением
|
||||
параметра smoke там, где это указано в шагах).
|
||||
|
||||
Селекторы взяты из текущего `src/web/index.html` и проектируемой
|
||||
разметки нового маркера (`#pipeline-smoke`).
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-01 — Маркер скрыт при чистой загрузке (desktop)
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. check-visual: "В левом нижнем углу карты НЕТ маркера 'ET-008 ✓'. Привычный UI не изменился: компас, GPX, локация, рельеф, тема — сверху-справа; нижний #toolbar центрирован"
|
||||
4. screenshot: "et008-tc01-default-no-marker-desktop"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-02 — Маркер скрыт при чистой загрузке (mobile)
|
||||
|
||||
тип: ui
|
||||
viewport: mobile
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. check-visual: "В левом нижнем углу НЕТ маркера. Тулбар снизу и кнопки справа выглядят как обычно"
|
||||
4. screenshot: "et008-tc02-default-no-marker-mobile"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-03 — Маркер виден при ?smoke=et-008 (desktop)
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/?smoke=et-008
|
||||
2. wait: 4000
|
||||
3. check-visual: "В левом нижнем углу появился маленький полупрозрачный лейбл с текстом 'ET-008 ✓'. Высота ~16-20px, шрифт мелкий, фон тёмно-серый полупрозрачный"
|
||||
4. screenshot: "et008-tc03-smoke-marker-visible-desktop"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-04 — Маркер виден при #smoke=et-008 (desktop)
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/#smoke=et-008
|
||||
2. wait: 4000
|
||||
3. check-visual: "Маркер 'ET-008 ✓' в левом нижнем углу присутствует, как и в TC-UI-03"
|
||||
4. screenshot: "et008-tc04-smoke-marker-via-hash"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-05 — Маркер виден на мобильном
|
||||
|
||||
тип: ui
|
||||
viewport: mobile
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/?smoke=et-008
|
||||
2. wait: 4000
|
||||
3. check-visual: "Маркер виден в левом нижнем углу, не перекрывает #toolbar (тулбар центрирован, маркер слева). Размер маркера такой же, как на desktop"
|
||||
4. screenshot: "et008-tc05-smoke-marker-mobile"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-06 — Маркер скрыт при неверном значении
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/?smoke=et-007
|
||||
2. wait: 4000
|
||||
3. check-visual: "В левом нижнем углу НЕТ маркера 'ET-008 ✓' (значение параметра не наш)"
|
||||
4. screenshot: "et008-tc06-wrong-value-hidden"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-07 — Маркер в светлой теме
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/?smoke=et-008
|
||||
2. wait: 4000
|
||||
3. click: "#btn-theme"
|
||||
4. wait: 2000
|
||||
5. check-visual: "Тема стала светлой (карта и панели светлые); маркер 'ET-008 ✓' в левом нижнем углу остался с тёмным полупрозрачным фоном, текст светлый — читается"
|
||||
6. screenshot: "et008-tc07-smoke-marker-light-theme"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-08 — Маркер в тёмной теме
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/?smoke=et-008
|
||||
2. wait: 4000
|
||||
3. check-visual: "Дефолтная тёмная тема: маркер 'ET-008 ✓' читается на тёмной карте за счёт собственного фона и контрастного текста"
|
||||
4. screenshot: "et008-tc08-smoke-marker-dark-theme"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-09 — Не конфликтует с атрибуцией MapLibre
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/?smoke=et-008
|
||||
2. wait: 4000
|
||||
3. check-visual: "Маркер слева внизу, атрибуция MapLibre справа внизу — они не перекрываются и не сливаются. Между ними чистая карта"
|
||||
4. screenshot: "et008-tc09-no-overlap-with-attrib"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-10 — Не конфликтует с открытым sheet
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/?smoke=et-008
|
||||
2. wait: 4000
|
||||
3. click: "#tb-route"
|
||||
4. wait: 1500
|
||||
5. check-visual: "Открылся bottom-sheet 'Маршрут'. Маркер либо полностью скрыт за sheet, либо виден только если sheet занимает не весь низ — никаких 'торчащих углов' маркера поверх sheet"
|
||||
6. screenshot: "et008-tc10-sheet-overlap-route"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-11 — Не ломает кнопки правой панели
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/?smoke=et-008
|
||||
2. wait: 4000
|
||||
3. check-visual: "Правая панель кнопок #map-controls-r (компас, GPX, локация, рельеф, тема) в прежнем виде; маркер слева внизу её не касается"
|
||||
4. screenshot: "et008-tc11-right-controls-intact"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-12 — Не ломает GPX-панель
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/?smoke=et-008
|
||||
2. wait: 4000
|
||||
3. click: "#tb-gpx"
|
||||
4. wait: 1500
|
||||
5. check-visual: "Bottom-sheet #sheet-gpx открыт, подсказка 'Нажми кнопку загрузки GPX...' читается. Маркер либо скрыт за sheet, либо виден слева и не пересекается с подсказкой"
|
||||
6. screenshot: "et008-tc12-gpx-sheet-with-smoke"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-13 — Не ломает поиск
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/?smoke=et-008
|
||||
2. wait: 4000
|
||||
3. click: "#tb-search"
|
||||
4. wait: 1500
|
||||
5. check-visual: "Поисковая панель/sheet открыт; маркер ET-008 слева внизу не мешает вводу и подсказкам"
|
||||
6. screenshot: "et008-tc13-search-with-smoke"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-14 — Маркер виден при первом кадре (no FOUC)
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/?smoke=et-008
|
||||
2. wait: 500
|
||||
3. screenshot: "et008-tc14-early-frame"
|
||||
4. wait: 4000
|
||||
5. screenshot: "et008-tc14-late-frame"
|
||||
6. check-visual: "На обоих скриншотах (~500ms и ~4500ms) маркер ET-008 ✓ присутствует и стабильно расположен; никакого мигания между ранним и поздним кадром"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-15 — Регрессия: переключение темы без smoke (маркер всё ещё скрыт)
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. click: "#btn-theme"
|
||||
4. wait: 2000
|
||||
5. check-visual: "Тема переключилась; маркера 'ET-008 ✓' нет ни в одной из тем (поскольку smoke-параметра в URL не было)"
|
||||
6. screenshot: "et008-tc15-no-smoke-after-theme-toggle"
|
||||
88
docs/work-items/ET-009/00-business-request.md
Normal file
88
docs/work-items/ET-009/00-business-request.md
Normal file
@@ -0,0 +1,88 @@
|
||||
---
|
||||
type: business-request
|
||||
work_item_id: ET-009
|
||||
title: "Final verification test — шкала масштаба на карте (dry-run пайплайна)"
|
||||
created_at: 2026-05-31
|
||||
source: pipeline-dry-run
|
||||
requester: claude-bot
|
||||
synthetic: true
|
||||
---
|
||||
|
||||
# Бизнес-запрос — ET-009 (Final verification test)
|
||||
|
||||
## Контекст
|
||||
|
||||
Это **финальная dry-run верификация** пайплайна
|
||||
`analyst → architect → coder → tester` после слияния PR #9
|
||||
(«feat: add UI/visual testing to pipeline»). Реальный заказчик
|
||||
отсутствует; запрос синтезирован агентом-аналитиком, чтобы прогнать
|
||||
через весь конвейер минимально достаточную задачу с UI-составляющей
|
||||
и проверить, что новая UI-тестовая ветка работает на «зелёной» задаче.
|
||||
|
||||
ET-007 (синтетический dry-run) проверял пайплайн на средней по размеру
|
||||
задаче (новая подложка, переключатель, persistence, атрибуция).
|
||||
ET-009 намеренно делает следующий шаг назад — **минимум кода, максимум
|
||||
тестируемости**: одна строчка `map.addControl(new
|
||||
maplibregl.ScaleControl(...))` плюс реакция на событие `unitchange` из
|
||||
существующего модуля `Units` (ET-005).
|
||||
|
||||
Требования к синтетическому скоупу:
|
||||
|
||||
- Изменение изолировано во фронтенде (`src/web/`); backend и БД не
|
||||
затрагиваются.
|
||||
- Не ломает существующий функционал: карта, темы, роутинг, GPX,
|
||||
рельеф (hillshade/TRI), POI, разведка, линейка, единицы измерения,
|
||||
поиск, GPX-uploader, satellite-toggle (если ET-007 будет смержена
|
||||
к моменту начала ET-009 — он не зависит, но и не конфликтует).
|
||||
- Видно глазом — есть что проверить Playwright-ом (горизонтальная
|
||||
шкала в нижнем левом углу карты, переключающая km↔mi синхронно с
|
||||
кнопкой единиц измерения).
|
||||
- Тривиально откатывается: одна правка в `src/web/app.js`
|
||||
(3–5 строк) и опционально пара строк CSS.
|
||||
|
||||
## Исходная формулировка
|
||||
|
||||
> На карте сейчас нет визуальной шкалы масштаба. Хочется простую
|
||||
> горизонтальную линейку в углу карты, чтобы при планировании
|
||||
> маршрута можно было «на глаз» оценить расстояние без линейки и без
|
||||
> переключения в режим расчёта. Шкала должна автоматически
|
||||
> подстраиваться под текущий выбор единиц (км/мили) — он уже есть в
|
||||
> терреин-попапе (ET-005). Никаких настроек, никаких кнопок — просто
|
||||
> всегда видимый элемент в углу.
|
||||
|
||||
## Уточнения (приняты по умолчанию для dry-run)
|
||||
|
||||
1. Контрол: встроенный `maplibregl.ScaleControl` — он же используется
|
||||
в туториалах MapLibre, не требует внешних зависимостей.
|
||||
2. Расположение: **bottom-left** угла карты (левый нижний). Это
|
||||
единственный угол карты, который сейчас свободен:
|
||||
- top-left занят `NavigationControl` + `FullscreenControl`
|
||||
(`src/web/app.js:1403-1404`);
|
||||
- top-right занят правой кнопочной панелью `#map-controls-r`;
|
||||
- bottom-right занят `AttributionControl` (compact).
|
||||
3. Единицы: при первой загрузке шкала использует
|
||||
`Units.getUnit()` → `'metric'` (для `'km'`) или `'imperial'`
|
||||
(для `'mi'`). При событии `unitchange` (диспатчится из
|
||||
`units.js:108-114`) шкала перестраивается в новой единице.
|
||||
4. Максимальная ширина: 100px (дефолт MapLibre).
|
||||
5. Тема: цвет границы и текста шкалы должны быть читаемы на обеих
|
||||
темах (день/ночь). При необходимости — лёгкий CSS-override в
|
||||
`app.css` через переменные `var(--text)` / `var(--surface)`.
|
||||
6. Совместимость с переключением темы (`map.setStyle()`): контролы
|
||||
MapLibre переживают `setStyle()` автоматически — никаких ручных
|
||||
действий внутри `rebuildMapOverlays()` не требуется. Однако
|
||||
обработчик `unitchange` должен оставаться рабочим (он навешан на
|
||||
`document`, не на `map`, поэтому переживёт).
|
||||
7. Backend / БД / тайл-эндпоинты / OSRM не затрагиваются.
|
||||
8. Доступность: шкала — декоративный элемент (`aria-hidden="true"` по
|
||||
умолчанию у `maplibregl-ctrl-scale`). Дополнительно ничего делать
|
||||
не нужно.
|
||||
|
||||
## Не входит в скоуп
|
||||
|
||||
- Кликабельность шкалы / переключение единиц по клику в неё.
|
||||
- Вертикальная шкала, линейка с делениями.
|
||||
- Сохранение настройки «показывать шкалу» — она всегда видна.
|
||||
- Локализация подписей шкалы (используем стандартные MapLibre:
|
||||
`km` / `m` / `mi` / `ft` — английские, как в библиотеке).
|
||||
- Backend-эндпоинт настроек пользователя.
|
||||
@@ -1,7 +1,103 @@
|
||||
# Business Request: E2E final test analyst sync
|
||||
---
|
||||
type: business-request
|
||||
work_item_id: ET-010
|
||||
title: "Final analyst-sync test — координаты центра карты в scale-zoom-bar (dry-run)"
|
||||
created_at: 2026-05-31
|
||||
source: pipeline-dry-run
|
||||
requester: claude-bot
|
||||
synthetic: true
|
||||
---
|
||||
|
||||
Work Item ID: ET-010
|
||||
# Бизнес-запрос — ET-010 (Final analyst-sync test)
|
||||
|
||||
## Description
|
||||
## Контекст
|
||||
|
||||
TBD
|
||||
Это **финальная dry-run верификация** пайплайна
|
||||
`analyst → architect → coder → tester` после слияния PR #9
|
||||
(«feat: add UI/visual testing to pipeline»). Реальный заказчик
|
||||
отсутствует; запрос синтезирован агентом-аналитиком, чтобы прогнать
|
||||
через весь конвейер минимально достаточную задачу с UI-составляющей
|
||||
и проверить, что:
|
||||
|
||||
1. Полный набор артефактов аналитика (BRD/ТЗ/AC/test-plan/UI-test-cases)
|
||||
создаётся, валидируется и синхронизируется в работу.
|
||||
2. UI-тестовая ветка пайплайна (`04b-ui-test-cases.md` → Playwright)
|
||||
стабильно работает на «зелёной» задаче.
|
||||
3. Артефакты не конфликтуют параллельно с ET-009 (шкала масштаба в
|
||||
`bottom-left`).
|
||||
|
||||
ET-007 проверял пайплайн на средней задаче (новая подложка,
|
||||
переключатель, persistence, атрибуция). ET-009 — на минимальной
|
||||
(MapLibre `ScaleControl` + реакция на `unitchange`). ET-010 — ещё один
|
||||
точечный шаг: **расширение существующего custom-overlay
|
||||
`#scale-zoom-bar`** на ещё одну строку с координатами центра карты,
|
||||
обновляющимися на `map.on('move')`.
|
||||
|
||||
Требования к синтетическому скоупу:
|
||||
|
||||
- Изменение изолировано во фронтенде (`src/web/`); backend и БД не
|
||||
затрагиваются.
|
||||
- Не ломает существующий функционал: карта, темы, роутинг, GPX,
|
||||
рельеф (hillshade/TRI), POI, разведка, линейка, единицы измерения,
|
||||
поиск, GPX-uploader.
|
||||
- Не конфликтует с ET-009: ET-010 трогает существующий
|
||||
`#scale-zoom-bar` (top-right), ET-009 добавляет нативный
|
||||
`ScaleControl` (bottom-left). Зон пересечения нет.
|
||||
- Видно глазом — есть что проверить Playwright-ом (новый текстовый
|
||||
блок «55.500, 40.500» в верхнем правом углу, обновляющийся при
|
||||
перемещении/зуме карты).
|
||||
- Тривиально откатывается: одна правка в `src/web/app.js` (5–10 строк)
|
||||
плюс 3–5 строк CSS.
|
||||
|
||||
## Исходная формулировка
|
||||
|
||||
> При планировании эндуро-маршрута часто нужно «снять координаты»
|
||||
> точки на карте — например, чтобы переслать её в чат или вбить в
|
||||
> навигатор. Сейчас координаты можно увидеть только в адресной
|
||||
> строке (URL hash `#zoom/lat/lon`), это неудобно. Хочется компактный
|
||||
> индикатор широты/долготы центра карты — чтобы при наведении/центровке
|
||||
> карты на нужную точку можно было сразу прочитать её координаты.
|
||||
> Никаких настроек, никаких кнопок — просто всегда видимый ненавязчивый
|
||||
> текст рядом со шкалой и индикатором зума.
|
||||
|
||||
## Уточнения (приняты по умолчанию для dry-run)
|
||||
|
||||
1. Что показываем: **широта и долгота центра карты** (`map.getCenter()`),
|
||||
а не координаты курсора. Курсор не работает на тач-устройствах,
|
||||
а пользователь и так центрирует карту на нужной точке.
|
||||
2. Источник событий: `map.on('move', updateCenterCoords)`. Этот же
|
||||
паттерн уже используется для `scale-zoom-bar` (`map.on('move')`
|
||||
и `map.on('zoom')` обновляют шкалу и `z7`).
|
||||
3. Местоположение: **внутри существующего `#scale-zoom-bar`** (top-right),
|
||||
третьим под-элементом `.szb-coords` после `.szb-scale` и `.szb-zoom`.
|
||||
Новый DOM-элемент создаётся той же функцией, что создаёт scale-zoom-bar
|
||||
(`initMap`), порядок строго `scale → zoom → coords`.
|
||||
4. Формат координат: `"55.500, 40.500"` — широта, запятая+пробел,
|
||||
долгота. Три знака после точки (≈ 100 м точности на широте 55°).
|
||||
Без подписей «N/E/S/W» — это технический индикатор для пилота.
|
||||
Отрицательные координаты — со знаком минус.
|
||||
5. Округление: `toFixed(3)`. При значениях |x| < 1 показываем
|
||||
`0.000` (а не `.000`).
|
||||
6. Тема: цвет текста — `var(--text)` (как у `.szb-label` и `.szb-zoom`).
|
||||
Фон — тот же `var(--surface)` (наследуется от родителя `.szb-*`).
|
||||
7. Доступность: декоративный текст. `aria-hidden="true"`. Голосовому
|
||||
ассистенту читать координаты бессмысленно (их шесть знаков
|
||||
меняются почти при каждом движении).
|
||||
8. Производительность: на `map.on('move')` срабатывает уже
|
||||
`updateScaleZoom()`. Координаты обновляются **в той же функции**
|
||||
(одна точка обновления, один pass по DOM). Никакого debouncing не
|
||||
требуется (текст коротенький, перерасчёт O(1)).
|
||||
9. localStorage: ничего не сохраняем — координаты вычисляются «на
|
||||
лету» из текущего центра карты.
|
||||
10. Backend / БД / тайл-эндпоинты / OSRM не затрагиваются.
|
||||
|
||||
## Не входит в скоуп
|
||||
|
||||
- Координаты курсора при `mousemove` (тач не работает).
|
||||
- Копирование координат по клику в clipboard.
|
||||
- Переключение формата (десятичные ↔ DMS).
|
||||
- Локализация (`N/E/S/W`) — оставляем чистые числа.
|
||||
- Сохранение «показывать/скрывать координаты» — они всегда видны.
|
||||
- Отображение высоты центра карты (это PH-8 Elevation Profile).
|
||||
- Поведение в режиме линейки — координаты центра карты продолжают
|
||||
обновляться, индикатор линейки `#ruler-info` не трогаем.
|
||||
|
||||
123
docs/work-items/ET-010/01-brd.md
Normal file
123
docs/work-items/ET-010/01-brd.md
Normal file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
type: brd
|
||||
work_item_id: ET-010
|
||||
title: "BRD: Координаты центра карты в scale-zoom-bar"
|
||||
version: 1
|
||||
status: draft
|
||||
created_at: 2026-05-31
|
||||
updated_at: 2026-05-31
|
||||
authors:
|
||||
- "agent:analyst"
|
||||
---
|
||||
|
||||
# BRD — ET-010: Координаты центра карты в scale-zoom-bar
|
||||
|
||||
## 1. Цель
|
||||
|
||||
Дать пилоту-эндуристу возможность мгновенно «снять» координаты любой
|
||||
точки маршрута, центрировав на ней карту — без открытия адресной
|
||||
строки браузера, без копирования из URL-хеша и без переключения в
|
||||
сторонние навигаторы.
|
||||
|
||||
Дополнительно — эта работа служит **end-to-end final dry-run**
|
||||
проверкой пайплайна `analyst → architect → coder → tester` после
|
||||
слияния PR #9 (UI/visual testing). Цели dry-run:
|
||||
|
||||
- Подтвердить, что полный пакет артефактов аналитика
|
||||
(BRD + ТЗ + AC + test-plan + UI-test-cases) собирается,
|
||||
валидируется и синхронизируется в pipeline без ручного
|
||||
вмешательства.
|
||||
- Подтвердить, что UI-составляющая (Playwright + screenshot)
|
||||
стабильна на «зелёной» минимальной задаче.
|
||||
- Подтвердить, что параллельные dry-run работы (ET-009 / ET-010) не
|
||||
конфликтуют по зонам ответственности.
|
||||
|
||||
## 2. Контекст
|
||||
|
||||
- Веб-приложение: MapLibre GL JS 4.7 + vanilla JS, без фреймворка.
|
||||
- Базовая страница: `src/web/index.html`; стили — `src/web/app.css`;
|
||||
логика — `src/web/app.js`.
|
||||
- В `src/web/app.js:1405-1409` уже создаётся custom-overlay
|
||||
`#scale-zoom-bar` (top-right) с двумя суб-элементами:
|
||||
- `.szb-scale` — горизонтальная шкала с подписью «30 km»;
|
||||
- `.szb-zoom` — зум-индикатор «z7».
|
||||
Обновление обоих делает `updateScaleZoom()`, навешанная на
|
||||
`map.on('move')` и `map.on('zoom')`.
|
||||
- Координаты центра карты уже доступны через `map.getCenter()` →
|
||||
`{ lng, lat }`.
|
||||
- Параллельная задача ET-009 добавляет нативный
|
||||
`maplibregl.ScaleControl` в `bottom-left`. Не пересекается с ET-010.
|
||||
- Темы (день/ночь): переключение реализовано через `switchMapStyle()`
|
||||
→ `map.setStyle()` → `rebuildMapOverlays()`. Кастомный
|
||||
`#scale-zoom-bar` существует независимо от стиля MapLibre и не
|
||||
затирается при `setStyle()`.
|
||||
- Backend и БД не затрагиваются.
|
||||
|
||||
## 3. Scope
|
||||
|
||||
### In scope
|
||||
|
||||
| # | Функция |
|
||||
|------|-------------------------------------------------------------------------------|
|
||||
| F-01 | Новый суб-элемент `.szb-coords` внутри `#scale-zoom-bar` |
|
||||
| F-02 | Текст элемента — `"<lat>, <lon>"` с тремя знаками после запятой |
|
||||
| F-03 | Обновление координат происходит в существующей `updateScaleZoom()` |
|
||||
| F-04 | Координаты обновляются на `map.on('move')` (включая зум) |
|
||||
| F-05 | Цвет / шрифт / выравнивание — совместимы с соседними `.szb-*` на обеих темах |
|
||||
| F-06 | Поведение при `setStyle()` (тема): `#scale-zoom-bar` не пересоздаётся, текст остаётся |
|
||||
| F-07 | Доступность: `.szb-coords` имеет `aria-hidden="true"` (декоративный элемент) |
|
||||
| F-08 | Координаты обновляются и в десктопном, и в мобильном viewport |
|
||||
|
||||
### Out of scope
|
||||
|
||||
- Координаты курсора при `mousemove` (тач не работает).
|
||||
- Копирование координат по клику в clipboard.
|
||||
- Форматы координат, отличные от десятичных (DMS, MGRS).
|
||||
- Локализация (буквы N/E/S/W) — показываем чистые числа.
|
||||
- Высота центра карты (это PH-8 Elevation Profile).
|
||||
- Сохранение «показывать/скрывать координаты» в `localStorage`.
|
||||
- Изменение поведения индикатора линейки `#ruler-info`.
|
||||
- Backend-эндпоинт настроек пользователя.
|
||||
|
||||
## 4. Метрики успеха
|
||||
|
||||
| Метрика | Критерий |
|
||||
|-----------------------------------------------|-------------------------------------------------------------------------------------------|
|
||||
| Видимость индикатора | Элемент `.szb-coords` присутствует в DOM на каждой загрузке |
|
||||
| Дефолтное значение | После загрузки на дефолтной позиции (`55.5, 40.5`) текст — `"55.500, 40.500"` |
|
||||
| Реакция на panning | После `map.panTo([41.0, 56.0])` текст становится `"56.000, 41.000"` (± 0.001) |
|
||||
| Реакция на зум | При зум-ин/зум-аут центр карты тот же → координаты не меняются (а вот `.szb-zoom` меняется) |
|
||||
| Формат | Всегда три знака после точки, разделитель `", "`, без `N/E/S/W` |
|
||||
| Отрицательные координаты | Лондон (`51.5, -0.1`) показывается как `"51.500, -0.100"` |
|
||||
| Совместимость с темой | После переключения темы (`#btn-theme`) текст продолжает обновляться, цвет читаем |
|
||||
| Совместимость с overlay-ями | После загрузки GPX / построения маршрута / переключения рельефа `.szb-coords` обновляется |
|
||||
| Производительность | На пять последовательных `panBy` подряд UI не «лагает», нет ошибок в `console.error` |
|
||||
| Не ломает существующий функционал | Шкала и зум-индикатор продолжают работать как раньше |
|
||||
| Доступность | `.szb-coords` имеет `aria-hidden="true"`; скринридер не зачитывает координаты |
|
||||
|
||||
## 5. Риски
|
||||
|
||||
| Риск | Вероятность | Влияние | Митигация |
|
||||
|---------------------------------------------------------------------|-------------|---------|------------------------------------------------------------------------------------|
|
||||
| Конфликт с ET-009 (`ScaleControl` в `bottom-left`) | Низкая | Низкое | Разные зоны экрана; никаких общих DOM-узлов |
|
||||
| `#scale-zoom-bar` пропадает при `setStyle()` | Низкая | Низкое | Элемент уже устойчив к `setStyle()` (существующий код); проверяется AC-05 |
|
||||
| Текст координат «прыгает» по ширине (3 → 4 знака) | Средняя | Низкое | Фиксируем `toFixed(3)` всегда — ширина строки стабильна |
|
||||
| На мобильном `#scale-zoom-bar` становится узким, координаты не помещаются | Средняя | Среднее | Используем `white-space: nowrap` и flex-обёртку; см. ТЗ §3 UI/UX |
|
||||
| Слишком частые `map.on('move')` вызывают перерисовку DOM | Низкая | Низкое | Обновляем DOM только если значение текста реально изменилось |
|
||||
| Скринридер начинает зачитывать обновляющиеся координаты | Низкая | Среднее | `aria-hidden="true"` на `.szb-coords` |
|
||||
| Отрицательные координаты округляются неверно (`-0.0005 → "-0.000"`) | Низкая | Низкое | Используем `Number.parseFloat(x.toFixed(3))` или принимаем `"-0.000"` как корректно |
|
||||
|
||||
## 6. Зависимости
|
||||
|
||||
- **Внешние**: нет. Никаких новых сетевых запросов, API-ключей или
|
||||
внешних сервисов.
|
||||
- **Внутренние**: использует существующую функцию `updateScaleZoom()` и
|
||||
существующий DOM-узел `#scale-zoom-bar` (`src/web/app.js:1405-...`).
|
||||
- **Параллельные work item**:
|
||||
- ET-007 (satellite map) — не затрагивает `#scale-zoom-bar`,
|
||||
разные файлы (`style*.json`, `index.html`, новые функции). Конфликта нет.
|
||||
- ET-009 (`ScaleControl` в `bottom-left`) — другой DOM-узел, другой
|
||||
угол карты. Конфликта нет.
|
||||
- **Файлы**: только `src/web/app.js` (расширение `updateScaleZoom()`
|
||||
и создание `.szb-coords`) и `src/web/app.css` (стили `.szb-coords`).
|
||||
- Backend, БД, OSRM, миграции, контейнеры — не затрагиваются.
|
||||
219
docs/work-items/ET-010/02-trz.md
Normal file
219
docs/work-items/ET-010/02-trz.md
Normal file
@@ -0,0 +1,219 @@
|
||||
---
|
||||
type: trz
|
||||
work_item_id: ET-010
|
||||
title: "ТЗ: Координаты центра карты в scale-zoom-bar"
|
||||
version: 1
|
||||
status: draft
|
||||
created_at: 2026-05-31
|
||||
updated_at: 2026-05-31
|
||||
authors:
|
||||
- "agent:analyst"
|
||||
depends_on:
|
||||
- "ET-010/01-brd.md"
|
||||
---
|
||||
|
||||
# Техническое задание — ET-010: Координаты центра карты
|
||||
|
||||
## 1. Общая архитектура изменений
|
||||
|
||||
Изменение чисто фронтовое. Никакого нового кода в `src/api/`,
|
||||
миграций или Docker-настроек не вносится. Затрагиваются **ровно два
|
||||
файла**:
|
||||
|
||||
- `src/web/app.js` — расширение существующей функции
|
||||
`updateScaleZoom()` (около строки 1411) и инициализация
|
||||
суб-элемента `.szb-coords` рядом с `.szb-scale` / `.szb-zoom`.
|
||||
- `src/web/app.css` — стили `.szb-coords` (по образцу `.szb-zoom`).
|
||||
|
||||
`src/web/index.html` **не трогаем**: `#scale-zoom-bar` создаётся
|
||||
динамически в `initMap()`, в HTML его нет.
|
||||
|
||||
## 2. Функциональные требования
|
||||
|
||||
### REQ-F-01. Суб-элемент `.szb-coords`
|
||||
|
||||
В `initMap()` (`src/web/app.js`, рядом со строкой 1408), при сборке
|
||||
`#scale-zoom-bar`, добавить третий блок:
|
||||
|
||||
```html
|
||||
<div class="szb-scale">…</div>
|
||||
<div class="szb-zoom">z7</div>
|
||||
<div class="szb-coords" aria-hidden="true">55.500, 40.500</div>
|
||||
```
|
||||
|
||||
Порядок строго `scale → zoom → coords` (слева направо в flex-строке).
|
||||
Стартовое значение — текущий центр карты после `new maplibregl.Map(...)`.
|
||||
|
||||
### REQ-F-02. Формат координат
|
||||
|
||||
```
|
||||
"<lat>, <lon>"
|
||||
```
|
||||
|
||||
Где:
|
||||
- `lat` = `map.getCenter().lat.toFixed(3)` (три знака после точки)
|
||||
- `lon` = `map.getCenter().lng.toFixed(3)` (три знака после точки)
|
||||
- Разделитель — запятая + один пробел (`", "`)
|
||||
- Знак минус сохраняется (`"-0.100"`)
|
||||
- Без подписей N/E/S/W
|
||||
|
||||
Никаких других форматов не поддерживается.
|
||||
|
||||
### REQ-F-03. Обновление в `updateScaleZoom()`
|
||||
|
||||
Внутри существующей `updateScaleZoom()` после блока обновления
|
||||
`zoomEl` (строка 1457) добавить:
|
||||
|
||||
```js
|
||||
const coordsEl = scaleZoomBar.querySelector('.szb-coords');
|
||||
if (coordsEl) {
|
||||
const c = map.getCenter();
|
||||
const next = `${c.lat.toFixed(3)}, ${c.lng.toFixed(3)}`;
|
||||
if (coordsEl.textContent !== next) {
|
||||
coordsEl.textContent = next;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Условие `if (coordsEl.textContent !== next)` обязательно — оно
|
||||
исключает лишнюю работу с DOM при `move`-событиях, которые не сдвинули
|
||||
центр карты заметно (например, конец инерции).
|
||||
|
||||
### REQ-F-04. Подключение к событиям
|
||||
|
||||
Никаких новых обработчиков навешивать **не нужно**. Существующие
|
||||
`map.on('zoom', updateScaleZoom)` и `map.on('move', updateScaleZoom)`
|
||||
(строки 1463-1464) автоматически вызывают `updateScaleZoom()` при
|
||||
любом изменении вьюпорта, включая `panTo`, `flyTo`, `easeTo`, `zoomIn`,
|
||||
`zoomOut`, перетаскивание мышью, pinch-zoom на тач-устройстве и
|
||||
программный `map.jumpTo(...)`.
|
||||
|
||||
### REQ-F-05. Поведение при `setStyle()` (тема)
|
||||
|
||||
`#scale-zoom-bar` создаётся через `document.getElementById('map').appendChild(...)`
|
||||
и не принадлежит DOM MapLibre — `setStyle()` его не затрагивает. После
|
||||
переключения темы `updateScaleZoom()` вызывается следующим же
|
||||
`map.on('move')` или `map.on('zoom')` — и текст координат корректно
|
||||
обновляется. Дополнительных вызовов в `rebuildMapOverlays()` не нужно.
|
||||
|
||||
### REQ-F-06. Стили `.szb-coords`
|
||||
|
||||
В `src/web/app.css` после блока `.szb-zoom { … }` (после строки 926)
|
||||
добавить:
|
||||
|
||||
```css
|
||||
.szb-coords {
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
color: rgba(255,255,255,0.85);
|
||||
text-shadow: 0 0 3px rgba(0,0,0,0.8), 0 1px 2px rgba(0,0,0,0.6);
|
||||
white-space: nowrap;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
```
|
||||
|
||||
Семейство шрифта наследуется от `body`. `tabular-nums` гарантирует,
|
||||
что строка не «прыгает» по ширине при изменении цифр.
|
||||
|
||||
### REQ-F-07. Доступность
|
||||
|
||||
- `.szb-coords` получает атрибут `aria-hidden="true"` сразу при
|
||||
создании (см. REQ-F-01).
|
||||
- Никакого `tabindex` элемент не получает.
|
||||
- Не вмешивается в фокусную последовательность и не озвучивается
|
||||
скринридером.
|
||||
|
||||
### REQ-F-08. Мобильный viewport
|
||||
|
||||
В существующем `#scale-zoom-bar { display: flex; gap: 6px; }` нет
|
||||
переноса строк — все три суб-элемента остаются в одну линию. Если на
|
||||
очень узких экранах (< 360 px) общая ширина превысит свободное место,
|
||||
браузер обрежет правый край `.szb-coords` (с `nowrap`) — это
|
||||
ожидаемое поведение dry-run, не требующее отдельной обработки.
|
||||
|
||||
В рамках ET-010 **не вводим** медиа-правил для скрытия `.szb-coords`
|
||||
на мобильном. Если регрессия по верстке всё же обнаружится — fix
|
||||
поедет отдельной задачей.
|
||||
|
||||
## 3. Нефункциональные требования
|
||||
|
||||
### REQ-NF-01. Производительность
|
||||
|
||||
- `updateScaleZoom()` вызывается синхронно из `map.on('move')` — это
|
||||
уже факт текущего кода. Добавляемая работа: один `getCenter()`,
|
||||
два `toFixed(3)`, одна конкатенация, одно сравнение, опционально
|
||||
одно присваивание `textContent`. Все операции — O(1).
|
||||
- Никаких новых `requestAnimationFrame` / `setTimeout` / `debounce`
|
||||
не вводим.
|
||||
- Бюджет: суммарная работа на `move` не должна расти больше чем на
|
||||
+0.5 ms в среднем кадре (ориентир — `performance.now()` в DevTools).
|
||||
|
||||
### REQ-NF-02. Совместимость браузеров
|
||||
|
||||
- Поддержка: Chrome ≥ 110, Firefox ≥ 110, Safari ≥ 16, мобильные
|
||||
Chrome/Safari iOS ≥ 16 (как у текущего приложения).
|
||||
- `Number.prototype.toFixed`, `Array.prototype.querySelector`,
|
||||
template-strings — поддержаны во всех целевых браузерах с 2017 года.
|
||||
|
||||
### REQ-NF-03. Логирование
|
||||
|
||||
- В нормальной работе никакого вывода в `console.*`.
|
||||
- Никаких новых уровней логирования не вводим.
|
||||
|
||||
### REQ-NF-04. Совместимость с другими work item
|
||||
|
||||
- Не пересекается с ET-007 (satellite map): тот трогает
|
||||
`style*.json`, `index.html`, `app.css`, добавляет источник
|
||||
`sat-raster`. ET-010 не трогает ни источники, ни слои MapLibre.
|
||||
- Не пересекается с ET-009 (ScaleControl bottom-left): тот добавляет
|
||||
нативный MapLibre control в `bottom-left`. ET-010 расширяет
|
||||
custom-overlay `#scale-zoom-bar` в `top-right`. Разные DOM-узлы.
|
||||
|
||||
## 4. Структура данных
|
||||
|
||||
Никаких новых ключей `localStorage`. Никаких новых полей в
|
||||
`layerState`. Состояние существует только в DOM (`textContent`
|
||||
элемента `.szb-coords`) и пересчитывается из `map.getCenter()` при
|
||||
каждом вызове `updateScaleZoom()`.
|
||||
|
||||
## 5. UI / UX контракт
|
||||
|
||||
| Элемент | Местоположение | Поведение |
|
||||
|------------------|-----------------------------------------------------------|--------------------------------------------------------------------------------------|
|
||||
| `#scale-zoom-bar`| `top-right` карты (absolute, top ≈ 8 px, right 12 px) | Контейнер — не меняется визуально |
|
||||
| `.szb-scale` | левый элемент flex-строки | Шкала «30 km» — не меняется |
|
||||
| `.szb-zoom` | средний элемент flex-строки | «z7» — не меняется |
|
||||
| `.szb-coords` | **правый элемент flex-строки** | «55.500, 40.500» — обновляется на `move`/`zoom` |
|
||||
| Шрифт `.szb-coords` | `10px` / `500` / белый с тенью | Идентичен `.szb-label`, чуть мельче `.szb-zoom` |
|
||||
| `aria-hidden` | `"true"` на `.szb-coords` | Скринридер игнорирует элемент |
|
||||
|
||||
## 6. Тестируемость
|
||||
|
||||
- В `tests/web/`: unit-тест функции форматирования (extract `formatCenterCoords(lat, lng)`
|
||||
если оркестратор хочет — допустимо и инлайн-форматирование без extract).
|
||||
- Integration-тест (jsdom): проверка, что после имитации
|
||||
`updateScaleZoom()` `.szb-coords.textContent` соответствует
|
||||
ожидаемой строке.
|
||||
- E2E (Playwright) — см. `04-test-plan.yaml` и `04b-ui-test-cases.md`.
|
||||
|
||||
## 7. Совместимость и миграции
|
||||
|
||||
- БД: миграций нет.
|
||||
- API: новых эндпоинтов нет.
|
||||
- Конфиги: новых ENV не добавляем.
|
||||
- Существующие work item: ET-005 (units), ET-006 (gpx), ET-007 (satellite),
|
||||
ET-009 (scale control) не затрагиваются.
|
||||
- Бизнес-конфиги, скрипты сборки, CI — не меняются.
|
||||
|
||||
## 8. Откат
|
||||
|
||||
В случае проблем:
|
||||
|
||||
1. В `src/web/app.js` удалить из `initMap()` строку создания
|
||||
`.szb-coords` (часть `scaleZoomBar.innerHTML = '<div…><div class="szb-coords"…></div>'`).
|
||||
2. Удалить из `updateScaleZoom()` блок обновления `coordsEl`
|
||||
(5 строк).
|
||||
3. В `src/web/app.css` удалить блок `.szb-coords { … }`.
|
||||
|
||||
Полный откат — один коммит, без побочных эффектов. Существующие шкала
|
||||
и зум-индикатор продолжат работать как раньше.
|
||||
186
docs/work-items/ET-010/03-acceptance-criteria.md
Normal file
186
docs/work-items/ET-010/03-acceptance-criteria.md
Normal file
@@ -0,0 +1,186 @@
|
||||
---
|
||||
type: acceptance-criteria
|
||||
work_item_id: ET-010
|
||||
title: "AC: Координаты центра карты в scale-zoom-bar"
|
||||
version: 1
|
||||
status: draft
|
||||
created_at: 2026-05-31
|
||||
updated_at: 2026-05-31
|
||||
authors:
|
||||
- "agent:analyst"
|
||||
depends_on:
|
||||
- "ET-010/01-brd.md"
|
||||
- "ET-010/02-trz.md"
|
||||
---
|
||||
|
||||
# Критерии приёмки — ET-010: Координаты центра карты
|
||||
|
||||
Формат: Gherkin. Все сценарии — в браузере приложения (десктоп и
|
||||
мобильный viewport, если не указано иное).
|
||||
|
||||
## AC-01. Элемент `.szb-coords` присутствует после загрузки
|
||||
|
||||
```
|
||||
Given пользователь открывает приложение в браузере с чистым localStorage
|
||||
When страница загружена и карта инициализирована (map.on('load'))
|
||||
Then в DOM существует элемент #scale-zoom-bar .szb-coords
|
||||
And элемент имеет атрибут aria-hidden="true"
|
||||
And элемент находится в #scale-zoom-bar после .szb-zoom
|
||||
And элемент содержит текст вида "<число>.<три цифры>, <число>.<три цифры>"
|
||||
```
|
||||
|
||||
## AC-02. Дефолтные координаты совпадают с центром карты
|
||||
|
||||
```
|
||||
Given карта инициализируется с center: [40.5, 55.5] (lng, lat)
|
||||
When страница загружена и updateScaleZoom() отработал
|
||||
Then .szb-coords.textContent == "55.500, 40.500"
|
||||
```
|
||||
|
||||
Уточнение: при `hash: true` URL-фрагмент может переопределить центр —
|
||||
тогда сравнение идёт с фактическим `map.getCenter()` после применения
|
||||
hash.
|
||||
|
||||
## AC-03. Реакция на программный pan
|
||||
|
||||
```
|
||||
Given карта в произвольном положении
|
||||
When из консоли вызывается map.panTo([41.0, 56.0])
|
||||
And карта закончила анимацию (map.once('moveend'))
|
||||
Then .szb-coords.textContent == "56.000, 41.000" (± допуск 0.001 на координату)
|
||||
```
|
||||
|
||||
## AC-04. Реакция на пользовательский drag
|
||||
|
||||
```
|
||||
Given карта в произвольном положении X1
|
||||
When пользователь перетаскивает карту мышью на 200 px влево
|
||||
And отпускает кнопку
|
||||
Then .szb-coords.textContent изменился (новое значение ≠ X1)
|
||||
And формат строки соответствует "<lat>, <lon>" с тремя знаками после точки
|
||||
```
|
||||
|
||||
## AC-05. Реакция на zoom без pan
|
||||
|
||||
```
|
||||
Given карта в позиции (55.000, 40.000) с зумом z7
|
||||
When пользователь зумит в (без панорамирования)
|
||||
Then .szb-zoom.textContent изменился (z7 → z8 или больше)
|
||||
And .szb-coords.textContent НЕ изменился (центр карты тот же)
|
||||
```
|
||||
|
||||
## AC-06. Отрицательные координаты
|
||||
|
||||
```
|
||||
Given карта установлена в Лондон (map.jumpTo({center: [-0.1, 51.5], zoom: 10}))
|
||||
Then .szb-coords.textContent == "51.500, -0.100"
|
||||
And знак минус виден перед нулём
|
||||
```
|
||||
|
||||
## AC-07. Координаты вне Европы
|
||||
|
||||
```
|
||||
Given карта установлена в Сидней (map.jumpTo({center: [151.2, -33.9], zoom: 10}))
|
||||
Then .szb-coords.textContent == "-33.900, 151.200"
|
||||
And положительная долгота показывается без знака
|
||||
And отрицательная широта показывается со знаком
|
||||
```
|
||||
|
||||
## AC-08. Совместимость с переключением темы
|
||||
|
||||
```
|
||||
Given пользователь находится в светлой теме
|
||||
And координаты в .szb-coords обновляются корректно
|
||||
When пользователь переключает тему через #btn-theme
|
||||
Then тема меняется (body.theme-dark)
|
||||
And #scale-zoom-bar и .szb-coords остаются в DOM
|
||||
And после первого же map.on('move') .szb-coords пересчитывается
|
||||
And цвет текста читаем на новой подложке (контраст ≥ 4.5:1 на тёмном/светлом фоне за счёт text-shadow)
|
||||
```
|
||||
|
||||
## AC-09. Совместимость с переключением единиц (ET-005)
|
||||
|
||||
```
|
||||
Given пользователь переключает единицы измерения с «км» на «мили»
|
||||
When срабатывает событие unitchange и updateScaleZoom() переотрисовывает .szb-scale
|
||||
Then .szb-coords.textContent НЕ изменился
|
||||
And формат координат не зависит от выбранной единицы (всегда десятичные градусы)
|
||||
```
|
||||
|
||||
## AC-10. Совместимость с GPX-треком (ET-006)
|
||||
|
||||
```
|
||||
Given пользователь загрузил GPX-файл и трек отрисовался на карте
|
||||
When карта автоматически центрировалась на bbox трека (fitBounds)
|
||||
Then .szb-coords.textContent отражает новый центр карты
|
||||
And GPX-полилиния остаётся видимой
|
||||
And статистика трека в #sheet-gpx остаётся доступной
|
||||
```
|
||||
|
||||
## AC-11. Совместимость с активным маршрутом
|
||||
|
||||
```
|
||||
Given пользователь построил маршрут (есть 2+ waypoint, отрисован вариант)
|
||||
When пользователь перетаскивает карту
|
||||
Then .szb-coords.textContent обновляется
|
||||
And маршрутные линии остаются видимыми
|
||||
And waypoint-маркеры на месте
|
||||
```
|
||||
|
||||
## AC-12. Производительность под нагрузкой
|
||||
|
||||
```
|
||||
Given карта в покое
|
||||
When пользователь делает 10 быстрых map.panBy(...) подряд
|
||||
Then нет ошибок в console.error
|
||||
And UI остаётся отзывчивым (нет visible freeze > 100 ms)
|
||||
And итоговое значение .szb-coords совпадает с map.getCenter() после последнего panBy
|
||||
```
|
||||
|
||||
## AC-13. Доступность
|
||||
|
||||
```
|
||||
Given пользователь использует скринридер (NVDA / VoiceOver — manual check)
|
||||
When фокус перемещается по UI приложения через Tab
|
||||
Then скринридер НЕ зачитывает содержимое .szb-coords
|
||||
And .szb-coords не получает focus (нет tabindex)
|
||||
And фокусная последовательность вокруг #map-controls-r не меняется
|
||||
```
|
||||
|
||||
## AC-14. Не ломает существующий функционал
|
||||
|
||||
```
|
||||
Given изменения ET-010 применены
|
||||
When пользователь использует
|
||||
роутинг (Маршрут), связку, красивый маршрут, разведку, линейку,
|
||||
поиск, метку, GPX, единицы измерения (км/мили),
|
||||
переключение темы, hillshade/TRI, POI
|
||||
Then все режимы работают как до изменений
|
||||
And нет регрессий в существующих UI-тестах (TC-UI-*)
|
||||
And нет ошибок в console.error при базовом сценарии
|
||||
And шкала .szb-scale и индикатор .szb-zoom продолжают обновляться
|
||||
```
|
||||
|
||||
## AC-15. Откатываемость
|
||||
|
||||
```
|
||||
Given изменения ET-010 применены и работают
|
||||
When все упомянутые в TRZ §8 правки откатываются
|
||||
Then приложение возвращается к состоянию до ET-010
|
||||
And элемент .szb-coords отсутствует в DOM
|
||||
And шкала и зум-индикатор работают корректно
|
||||
And ни один тест из существующего набора не падает
|
||||
And в DOM нет осиротевших элементов или ссылок на удалённые символы
|
||||
```
|
||||
|
||||
## AC-16. Не конфликтует с ET-009
|
||||
|
||||
```
|
||||
Given в ту же сборку случайно попали изменения ET-009 (ScaleControl bottom-left)
|
||||
When страница загружена
|
||||
Then в DOM присутствуют ОБА индикатора:
|
||||
.szb-coords в #scale-zoom-bar (top-right) — от ET-010
|
||||
.maplibregl-ctrl-scale в bottom-left — от ET-009
|
||||
And они не перекрываются визуально
|
||||
And оба обновляются синхронно с состоянием карты
|
||||
```
|
||||
333
docs/work-items/ET-010/04-test-plan.yaml
Normal file
333
docs/work-items/ET-010/04-test-plan.yaml
Normal file
@@ -0,0 +1,333 @@
|
||||
---
|
||||
type: test-plan
|
||||
work_item_id: ET-010
|
||||
title: "Test Plan: Координаты центра карты в scale-zoom-bar"
|
||||
version: 1
|
||||
status: draft
|
||||
created_at: 2026-05-31
|
||||
updated_at: 2026-05-31
|
||||
authors:
|
||||
- "agent:analyst"
|
||||
depends_on:
|
||||
- "ET-010/02-trz.md"
|
||||
- "ET-010/03-acceptance-criteria.md"
|
||||
---
|
||||
|
||||
# План функциональных тестов — ET-010
|
||||
|
||||
scope:
|
||||
- src/web/app.js
|
||||
- src/web/app.css
|
||||
|
||||
out_of_scope:
|
||||
- src/api/**
|
||||
- migrations/**
|
||||
- tests/api/**
|
||||
- src/web/index.html
|
||||
- src/web/style.json
|
||||
- src/web/style-dark.json
|
||||
|
||||
test_suites:
|
||||
|
||||
- id: unit
|
||||
name: "Unit-тесты форматирования координат"
|
||||
runner: "vitest или node:test"
|
||||
location: "tests/web/unit/center-coords.test.js"
|
||||
cases:
|
||||
|
||||
- id: UT-01
|
||||
title: "Формат: положительные координаты, целые градусы"
|
||||
ac_refs: [AC-02, AC-03]
|
||||
given:
|
||||
- "lat = 55.5, lng = 40.5"
|
||||
when:
|
||||
- "вызван formatCenterCoords(lat, lng) или inline-эквивалент"
|
||||
then:
|
||||
- "результат == '55.500, 40.500'"
|
||||
|
||||
- id: UT-02
|
||||
title: "Формат: отрицательные координаты"
|
||||
ac_refs: [AC-06]
|
||||
given:
|
||||
- "lat = 51.5, lng = -0.1"
|
||||
when:
|
||||
- "вызван formatCenterCoords(lat, lng)"
|
||||
then:
|
||||
- "результат == '51.500, -0.100'"
|
||||
|
||||
- id: UT-03
|
||||
title: "Формат: lat отрицателен, lng положителен (южное полушарие)"
|
||||
ac_refs: [AC-07]
|
||||
given:
|
||||
- "lat = -33.9, lng = 151.2"
|
||||
when:
|
||||
- "вызван formatCenterCoords(lat, lng)"
|
||||
then:
|
||||
- "результат == '-33.900, 151.200'"
|
||||
|
||||
- id: UT-04
|
||||
title: "Округление до трёх знаков"
|
||||
ac_refs: [AC-02]
|
||||
given:
|
||||
- "lat = 55.50049, lng = 40.49951"
|
||||
when:
|
||||
- "вызван formatCenterCoords(lat, lng)"
|
||||
then:
|
||||
- "результат == '55.500, 40.500'"
|
||||
|
||||
- id: UT-05
|
||||
title: "Околонулевые значения"
|
||||
ac_refs: [AC-06]
|
||||
given:
|
||||
- "lat = 0, lng = 0"
|
||||
when:
|
||||
- "вызван formatCenterCoords(lat, lng)"
|
||||
then:
|
||||
- "результат == '0.000, 0.000'"
|
||||
|
||||
- id: UT-06
|
||||
title: "Близкие к нулю отрицательные значения"
|
||||
ac_refs: [AC-06]
|
||||
given:
|
||||
- "lat = -0.0001, lng = -0.0005"
|
||||
when:
|
||||
- "вызван formatCenterCoords(lat, lng)"
|
||||
then:
|
||||
- "результат содержит знак минус (например '-0.000, -0.001') либо без знака на чистом нуле — допускаем оба варианта; главное — формат '<lat>, <lng>' с тремя знаками"
|
||||
|
||||
- id: integration
|
||||
name: "Интеграционные тесты updateScaleZoom (jsdom)"
|
||||
runner: "vitest + jsdom"
|
||||
location: "tests/web/integration/szb-coords.test.js"
|
||||
cases:
|
||||
|
||||
- id: IT-01
|
||||
title: "После initMap элемент .szb-coords создан с aria-hidden"
|
||||
ac_refs: [AC-01]
|
||||
given:
|
||||
- "загружен src/web/app.js в jsdom"
|
||||
- "мок window.maplibregl с MapStub (center: [40.5, 55.5])"
|
||||
when:
|
||||
- "вызван initMap()"
|
||||
then:
|
||||
- "document.querySelector('#scale-zoom-bar .szb-coords') существует"
|
||||
- "элемент имеет aria-hidden='true'"
|
||||
- "textContent соответствует регексу /^-?\\d+\\.\\d{3}, -?\\d+\\.\\d{3}$/"
|
||||
|
||||
- id: IT-02
|
||||
title: "Порядок суб-элементов: scale → zoom → coords"
|
||||
ac_refs: [AC-01]
|
||||
given:
|
||||
- "тот же setup"
|
||||
when:
|
||||
- "querySelectorAll('#scale-zoom-bar > *')"
|
||||
then:
|
||||
- "первый элемент имеет класс .szb-scale"
|
||||
- "второй элемент имеет класс .szb-zoom"
|
||||
- "третий элемент имеет класс .szb-coords"
|
||||
|
||||
- id: IT-03
|
||||
title: "updateScaleZoom после программного pan обновляет textContent"
|
||||
ac_refs: [AC-03]
|
||||
given:
|
||||
- "MapStub.center = {lng: 41.0, lat: 56.0}"
|
||||
when:
|
||||
- "вызван updateScaleZoom() напрямую"
|
||||
then:
|
||||
- ".szb-coords.textContent == '56.000, 41.000'"
|
||||
|
||||
- id: IT-04
|
||||
title: "Идемпотентность: повторный вызов с тем же центром не перезаписывает DOM"
|
||||
ac_refs: [REQ-NF-01]
|
||||
given:
|
||||
- "MapStub.center неизменён"
|
||||
- "Сохранён MutationObserver на .szb-coords"
|
||||
when:
|
||||
- "updateScaleZoom() вызван 5 раз подряд"
|
||||
then:
|
||||
- "MutationObserver зафиксировал ≤ 1 запись об изменении textContent"
|
||||
|
||||
- id: IT-05
|
||||
title: "Шкала и зум продолжают обновляться вместе с координатами"
|
||||
ac_refs: [AC-14]
|
||||
given:
|
||||
- "MapStub.zoom = 10, center = {lng: 0, lat: 0}"
|
||||
when:
|
||||
- "обновляется MapStub: zoom = 12; вызван updateScaleZoom()"
|
||||
then:
|
||||
- ".szb-zoom.textContent == 'z12'"
|
||||
- ".szb-scale текст обновился (label содержит цифры и единицу)"
|
||||
- ".szb-coords.textContent == '0.000, 0.000'"
|
||||
|
||||
- id: e2e
|
||||
name: "E2E (Playwright) — функциональные сценарии"
|
||||
runner: "playwright"
|
||||
location: "tests/web/e2e/center-coords.spec.ts"
|
||||
base_url: "https://openclaw.mva154.duckdns.org/enduro/"
|
||||
notes:
|
||||
- "UI-визуальные тесты вынесены в 04b-ui-test-cases.md."
|
||||
- "Эти тесты проверяют DOM-состояние и значения, без скриншот-сравнений."
|
||||
cases:
|
||||
|
||||
- id: E2E-01
|
||||
title: "Дефолтное состояние: .szb-coords присутствует"
|
||||
ac_refs: [AC-01, AC-02]
|
||||
steps:
|
||||
- "navigate base_url"
|
||||
- "evaluate: localStorage.clear()"
|
||||
- "reload page"
|
||||
- "wait map idle (3000 ms)"
|
||||
expect:
|
||||
- "selector #scale-zoom-bar .szb-coords exists"
|
||||
- ".szb-coords attr aria-hidden == 'true'"
|
||||
- "evaluate: .szb-coords.textContent matches /^-?\\d+\\.\\d{3}, -?\\d+\\.\\d{3}$/"
|
||||
|
||||
- id: E2E-02
|
||||
title: "Программный panTo обновляет координаты"
|
||||
ac_refs: [AC-03]
|
||||
steps:
|
||||
- "navigate base_url"
|
||||
- "wait map idle"
|
||||
- "evaluate: window._map.jumpTo({center: [41.0, 56.0], zoom: 8})"
|
||||
- "wait 500 ms"
|
||||
expect:
|
||||
- "evaluate: document.querySelector('#scale-zoom-bar .szb-coords').textContent == '56.000, 41.000'"
|
||||
|
||||
- id: E2E-03
|
||||
title: "Пользовательский drag обновляет координаты"
|
||||
ac_refs: [AC-04]
|
||||
steps:
|
||||
- "navigate base_url"
|
||||
- "wait map idle"
|
||||
- "evaluate: const before = document.querySelector('.szb-coords').textContent; window.__before = before"
|
||||
- "mouse.move в центр карты"
|
||||
- "mouse.down"
|
||||
- "mouse.move 200 px влево"
|
||||
- "mouse.up"
|
||||
- "wait 500 ms"
|
||||
expect:
|
||||
- "evaluate: document.querySelector('.szb-coords').textContent !== window.__before"
|
||||
- "evaluate: /^-?\\d+\\.\\d{3}, -?\\d+\\.\\d{3}$/.test(document.querySelector('.szb-coords').textContent)"
|
||||
|
||||
- id: E2E-04
|
||||
title: "Зум не сдвигает координаты центра"
|
||||
ac_refs: [AC-05]
|
||||
steps:
|
||||
- "navigate base_url"
|
||||
- "evaluate: window._map.jumpTo({center: [40, 55], zoom: 7})"
|
||||
- "wait 500 ms"
|
||||
- "evaluate: window.__coordsBefore = document.querySelector('.szb-coords').textContent; window.__zoomBefore = document.querySelector('.szb-zoom').textContent"
|
||||
- "evaluate: window._map.zoomTo(10)"
|
||||
- "wait 1000 ms"
|
||||
expect:
|
||||
- "evaluate: document.querySelector('.szb-coords').textContent == window.__coordsBefore"
|
||||
- "evaluate: document.querySelector('.szb-zoom').textContent !== window.__zoomBefore"
|
||||
|
||||
- id: E2E-05
|
||||
title: "Отрицательные координаты (Лондон)"
|
||||
ac_refs: [AC-06]
|
||||
steps:
|
||||
- "navigate base_url"
|
||||
- "evaluate: window._map.jumpTo({center: [-0.1, 51.5], zoom: 10})"
|
||||
- "wait 500 ms"
|
||||
expect:
|
||||
- "evaluate: document.querySelector('.szb-coords').textContent == '51.500, -0.100'"
|
||||
|
||||
- id: E2E-06
|
||||
title: "Южное полушарие (Сидней)"
|
||||
ac_refs: [AC-07]
|
||||
steps:
|
||||
- "navigate base_url"
|
||||
- "evaluate: window._map.jumpTo({center: [151.2, -33.9], zoom: 10})"
|
||||
- "wait 500 ms"
|
||||
expect:
|
||||
- "evaluate: document.querySelector('.szb-coords').textContent == '-33.900, 151.200'"
|
||||
|
||||
- id: E2E-07
|
||||
title: "Совместимость с переключением темы"
|
||||
ac_refs: [AC-08]
|
||||
steps:
|
||||
- "navigate base_url"
|
||||
- "evaluate: window._map.jumpTo({center: [40, 55], zoom: 7})"
|
||||
- "wait 500 ms"
|
||||
- "evaluate: window.__before = document.querySelector('.szb-coords').textContent"
|
||||
- "click #btn-theme"
|
||||
- "wait 2000 ms (style.load)"
|
||||
- "evaluate: window._map.panBy([10, 0])"
|
||||
- "wait 500 ms"
|
||||
expect:
|
||||
- "selector #scale-zoom-bar .szb-coords exists"
|
||||
- "evaluate: document.querySelector('.szb-coords').textContent !== window.__before"
|
||||
|
||||
- id: E2E-08
|
||||
title: "Совместимость с переключением единиц (ET-005)"
|
||||
ac_refs: [AC-09]
|
||||
steps:
|
||||
- "navigate base_url"
|
||||
- "wait map idle"
|
||||
- "evaluate: window.__coordsBefore = document.querySelector('.szb-coords').textContent"
|
||||
- "click #terrain-toggle"
|
||||
- "wait 500 ms"
|
||||
- "click #unit-btn-mi"
|
||||
- "wait 500 ms"
|
||||
expect:
|
||||
- "evaluate: document.querySelector('.szb-coords').textContent == window.__coordsBefore"
|
||||
- "evaluate: /mi/.test(document.querySelector('.szb-label').textContent)"
|
||||
|
||||
- id: E2E-09
|
||||
title: "Производительность: 10 быстрых panBy без ошибок"
|
||||
ac_refs: [AC-12]
|
||||
steps:
|
||||
- "navigate base_url"
|
||||
- "wait map idle"
|
||||
- "evaluate: for (let i=0;i<10;i++) window._map.panBy([50, 50], {duration: 0})"
|
||||
- "wait 1000 ms"
|
||||
expect:
|
||||
- "no console.error captured"
|
||||
- "evaluate: /^-?\\d+\\.\\d{3}, -?\\d+\\.\\d{3}$/.test(document.querySelector('.szb-coords').textContent)"
|
||||
|
||||
- id: E2E-10
|
||||
title: "Не ломает существующий набор UI-тестов"
|
||||
ac_refs: [AC-14]
|
||||
steps:
|
||||
- "запустить существующий набор E2E из tests/web/e2e/**"
|
||||
expect:
|
||||
- "все ранее зелёные тесты остаются зелёными"
|
||||
|
||||
- id: E2E-11
|
||||
title: "Параллель с ET-009 не вызывает конфликта (опционально)"
|
||||
ac_refs: [AC-16]
|
||||
notes: "Запускать только если ET-009 уже смерженa в main"
|
||||
steps:
|
||||
- "navigate base_url"
|
||||
- "wait map idle"
|
||||
expect:
|
||||
- "selector #scale-zoom-bar .szb-coords exists"
|
||||
- "selector .maplibregl-ctrl-scale exists"
|
||||
- "BoundingBox(.szb-coords) и BoundingBox(.maplibregl-ctrl-scale) не пересекаются"
|
||||
|
||||
coverage_matrix:
|
||||
AC-01: [IT-01, IT-02, E2E-01]
|
||||
AC-02: [UT-01, UT-04, UT-05, E2E-01]
|
||||
AC-03: [IT-03, E2E-02]
|
||||
AC-04: [E2E-03]
|
||||
AC-05: [E2E-04]
|
||||
AC-06: [UT-02, UT-06, E2E-05]
|
||||
AC-07: [UT-03, E2E-06]
|
||||
AC-08: [E2E-07]
|
||||
AC-09: [E2E-08]
|
||||
AC-10: [E2E-10]
|
||||
AC-11: [E2E-10]
|
||||
AC-12: [IT-04, E2E-09]
|
||||
AC-13: "manual"
|
||||
AC-14: [IT-05, E2E-10]
|
||||
AC-15: "manual"
|
||||
AC-16: [E2E-11]
|
||||
|
||||
exit_criteria:
|
||||
- "Все unit-тесты UT-01..UT-06 зелёные"
|
||||
- "Все integration-тесты IT-01..IT-05 зелёные"
|
||||
- "Все E2E E2E-01..E2E-10 зелёные на test-окружении"
|
||||
- "E2E-11 пропускается до мержа ET-009 — не блокирует"
|
||||
- "Существующий набор UI/E2E не имеет регрессий"
|
||||
- "Покрытие AC ≥ 14 из 16 автотестами (AC-13 и AC-15 — ручная проверка)"
|
||||
305
docs/work-items/ET-010/04b-ui-test-cases.md
Normal file
305
docs/work-items/ET-010/04b-ui-test-cases.md
Normal file
@@ -0,0 +1,305 @@
|
||||
---
|
||||
type: ui-test-cases
|
||||
work_item_id: ET-010
|
||||
title: "UI Test Cases: Координаты центра карты в scale-zoom-bar"
|
||||
version: 1
|
||||
status: draft
|
||||
created_at: 2026-05-31
|
||||
updated_at: 2026-05-31
|
||||
authors:
|
||||
- "agent:analyst"
|
||||
depends_on:
|
||||
- "ET-010/02-trz.md"
|
||||
- "ET-010/03-acceptance-criteria.md"
|
||||
---
|
||||
|
||||
# UI Test Cases — ET-010: Координаты центра карты
|
||||
|
||||
Playwright-сценарии для визуального тестирования. Базовый URL для всех
|
||||
кейсов: `https://openclaw.mva154.duckdns.org/enduro/`.
|
||||
|
||||
Селекторы взяты из текущего `src/web/index.html` и предполагаемой
|
||||
динамической разметки `#scale-zoom-bar` (создаётся в `initMap()`,
|
||||
суб-элементы — `.szb-scale`, `.szb-zoom`, новый `.szb-coords`).
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-01 — Элемент `.szb-coords` присутствует и читаем (desktop)
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. check-visual: "В верхнем правом углу карты, в одну строку со шкалой и индикатором зума, виден текст вида '55.500, 40.500'; шрифт идентичен соседним .szb-label и .szb-zoom; цвет белый с тенью"
|
||||
4. screenshot: "et010-tc01-coords-desktop"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-02 — Элемент `.szb-coords` на мобильном
|
||||
|
||||
тип: ui
|
||||
viewport: mobile
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. check-visual: "В верхнем правом углу карты виден `#scale-zoom-bar` со шкалой, зумом и координатами; все три элемента в одну строку, не наезжают на правую кнопочную панель `#map-controls-r`"
|
||||
4. screenshot: "et010-tc02-coords-mobile"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-03 — Дефолтные координаты совпадают с центром карты
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. check-visual: "Текст `.szb-coords` показывает координаты дефолтного центра карты России (55.5, 40.5) — что-то близкое к '55.500, 40.500' (если URL hash не переопределил)"
|
||||
4. screenshot: "et010-tc03-default-coords"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-04 — Координаты обновляются при программном panTo
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. screenshot: "et010-tc04-before-pan"
|
||||
4. evaluate: "window._map.jumpTo({center: [37.6173, 55.7558], zoom: 10}) // Moscow"
|
||||
5. wait: 2000
|
||||
6. check-visual: "Координаты в `.szb-coords` обновились на '55.756, 37.617' (± 0.001); шкала и зум-индикатор пересчитались соответственно"
|
||||
7. screenshot: "et010-tc04-after-pan-moscow"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-05 — Координаты обновляются при пользовательском drag
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. screenshot: "et010-tc05-before-drag"
|
||||
4. mouse-drag: from "center-of-#map" to "center-minus-200px-x"
|
||||
5. wait: 1000
|
||||
6. check-visual: "Координаты в `.szb-coords` изменились по сравнению с шагом 3; формат '<lat>, <lon>' с тремя знаками после точки сохранён; никаких лишних символов"
|
||||
7. screenshot: "et010-tc05-after-drag"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-06 — Координаты не меняются при чистом зуме
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. evaluate: "window._map.jumpTo({center: [40, 55], zoom: 7})"
|
||||
3. wait: 2000
|
||||
4. screenshot: "et010-tc06-before-zoom"
|
||||
5. evaluate: "window._map.zoomTo(10, {duration: 0})"
|
||||
6. wait: 1500
|
||||
7. check-visual: "В `.szb-zoom` значение увеличилось (z7 → z10); в `.szb-coords` остался прежний текст ('55.000, 40.000'); шкала ('.szb-scale') пересчиталась под новый зум"
|
||||
8. screenshot: "et010-tc06-after-zoom"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-07 — Отрицательные координаты (Лондон)
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. evaluate: "window._map.jumpTo({center: [-0.1276, 51.5074], zoom: 10})"
|
||||
3. wait: 2000
|
||||
4. check-visual: "Текст `.szb-coords` показывает '51.507, -0.128' — со знаком минус перед долготой, три знака после точки"
|
||||
5. screenshot: "et010-tc07-london"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-08 — Южное полушарие (Сидней)
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. evaluate: "window._map.jumpTo({center: [151.2093, -33.8688], zoom: 10})"
|
||||
3. wait: 2000
|
||||
4. check-visual: "Текст `.szb-coords` показывает '-33.869, 151.209' — отрицательная широта, положительная долгота без знака"
|
||||
5. screenshot: "et010-tc08-sydney"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-09 — Совместимость с тёмной темой
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. check-visual: "Стартовая тема — тёмная (`body.theme-dark`); `.szb-coords` читаем поверх тёмной OSM-подложки благодаря text-shadow"
|
||||
4. screenshot: "et010-tc09-coords-dark-theme"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-10 — Совместимость со светлой темой
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. click: "#btn-theme"
|
||||
4. wait: 3000
|
||||
5. check-visual: "Тема стала светлой; `.szb-coords` по-прежнему читаем; цвет текста не изменился (остался белым с тенью), за счёт `text-shadow` контраст сохраняется на светлом фоне"
|
||||
6. screenshot: "et010-tc10-coords-light-theme"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-11 — Совместимость с переключением единиц измерения
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. click: "#terrain-toggle"
|
||||
4. wait: 500
|
||||
5. click: "#unit-btn-mi"
|
||||
6. wait: 1500
|
||||
7. check-visual: "В `.szb-label` единица изменилась на 'mi'; в `.szb-coords` формат и значения координат остались прежними (десятичные градусы)"
|
||||
8. screenshot: "et010-tc11-coords-with-mi-units"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-12 — Совместимость с активным маршрутом
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. click: "#tb-route"
|
||||
4. wait: 1500
|
||||
5. click: "#map"
|
||||
6. wait: 2500
|
||||
7. scroll: 200
|
||||
8. click: "#map"
|
||||
9. wait: 4000
|
||||
10. check-visual: "На карте отрисована маршрутная линия и две точки waypoint; `.szb-coords` в верхнем правом углу обновлён к новому центру карты; шкала и зум-индикатор корректны; mini-route-sheet с метриками виден"
|
||||
11. screenshot: "et010-tc12-coords-with-route"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-13 — Совместимость с GPX-треком
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. click: "#tb-gpx"
|
||||
4. wait: 1500
|
||||
5. check-visual: "Bottom-sheet `#sheet-gpx` открыт; `.szb-coords` остаётся видимым над листом и продолжает отражать текущий центр карты"
|
||||
6. screenshot: "et010-tc13-coords-with-gpx-sheet"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-14 — Совместимость с hillshade
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. evaluate: "window._map.jumpTo({center: [40.5, 55.5], zoom: 11})"
|
||||
4. wait: 2000
|
||||
5. click: "#terrain-toggle"
|
||||
6. wait: 500
|
||||
7. click: "#terrain-hillshade-cb"
|
||||
8. wait: 3000
|
||||
9. check-visual: "Поверх карты видны тени рельефа; `.szb-coords` по-прежнему читаемый, не перекрыт растром hillshade (находится в overlay-DOM, не на canvas)"
|
||||
10. screenshot: "et010-tc14-coords-with-hillshade"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-15 — Стабильная ширина строки `.szb-coords`
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. evaluate: "window._map.jumpTo({center: [0.000, 0.000], zoom: 5})"
|
||||
4. wait: 1000
|
||||
5. screenshot: "et010-tc15a-zero-coords"
|
||||
6. evaluate: "window._map.jumpTo({center: [-179.999, -89.999], zoom: 5})"
|
||||
7. wait: 1000
|
||||
8. check-visual: "Ширина блока `.szb-coords` визуально стабильна между шагами 5 и 7 (за счёт `font-variant-numeric: tabular-nums`); шкала и зум-индикатор не «прыгают» влево/вправо"
|
||||
9. screenshot: "et010-tc15b-edge-coords"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-16 — Координаты на мобильном после drag
|
||||
|
||||
тип: ui
|
||||
viewport: mobile
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. screenshot: "et010-tc16-before-touch-drag"
|
||||
4. touch-drag: from "center-of-#map" to "center-plus-150px-y"
|
||||
5. wait: 1500
|
||||
6. check-visual: "`.szb-coords` обновился после тач-перетаскивания; шкала и зум-индикатор пересчитались; `#toolbar` снизу не перекрыл `#scale-zoom-bar`"
|
||||
7. screenshot: "et010-tc16-after-touch-drag"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-17 — Регрессия: `.szb-coords` не перекрывает кнопки `#map-controls-r`
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. check-visual: "`#scale-zoom-bar` находится строго над кнопками `#map-controls-r`; нет visible overlap; кнопки компас/GPX-upload/locate/рельеф/тема кликабельны"
|
||||
4. click: "#terrain-toggle"
|
||||
5. wait: 500
|
||||
6. check-visual: "Открылся `#terrain-popup`; `.szb-coords` остаётся видимым; popup не перекрывает координаты сверху"
|
||||
7. screenshot: "et010-tc17-no-overlap"
|
||||
|
||||
---
|
||||
|
||||
### TC-UI-18 — Параллель с ET-009 (опциональный сценарий)
|
||||
|
||||
тип: ui
|
||||
viewport: desktop
|
||||
условия: "выполнять только если ET-009 уже смержен в main; иначе пропустить"
|
||||
|
||||
шаги:
|
||||
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
|
||||
2. wait: 4000
|
||||
3. check-visual: "В верхнем правом углу — `.szb-coords` (ET-010); в нижнем левом углу — нативный `.maplibregl-ctrl-scale` (ET-009); элементы не перекрываются и не конфликтуют; оба обновляются при `panTo`"
|
||||
4. evaluate: "window._map.jumpTo({center: [37.6173, 55.7558], zoom: 13})"
|
||||
5. wait: 2000
|
||||
6. check-visual: "После panTo оба индикатора обновились синхронно; на скриншоте видны и `.szb-coords '55.756, 37.617'`, и нативная шкала MapLibre с подписью в км/милях"
|
||||
7. screenshot: "et010-tc18-coexistence-with-et009"
|
||||
Reference in New Issue
Block a user