Compare commits

...

2 Commits

Author SHA1 Message Date
e77e4a2ab7 docs(ET-008): analyst artifacts - BRD, TRZ, AC, TestPlan
All checks were successful
CI / lint (push) Successful in 4s
CI / test (push) Successful in 6s
CI / build (push) Successful in 1s
2026-05-31 19:51:53 +03:00
9d5e2e18c5 docs: init ET-008 business request
All checks were successful
CI / lint (push) Successful in 4s
CI / test (push) Successful in 6s
CI / build (push) Successful in 2s
2026-05-31 19:39:58 +03:00
6 changed files with 1188 additions and 0 deletions

View 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. Тёмная и светлая темы: маркер использует одинаковые цвета в обеих
темах (тёмный фон + светлый текст), читаемость гарантируется
собственными цветами, а не наследованием от темы.

View 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-прогона важнее, чем визуальная
эстетика маркера.

View 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 / БД.

View 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-конфигов не изменена
```

View 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*"

View 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"