mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-14 09:54:56 +03:00
docs(web-test): полный sync спеки + contexts[] в testResult
spec.md v0.2 (последний sync 2026-05-13): §1 CLI: добавлены --report-dir и `--` separator в таблицу флагов. §1 «Режим выполнения»: убрана несуществующая «группировка по контексту», описана реальная алфавитная модель + lazy ensureContext. §2 пример multi-context: latin ID контекстов вместо кириллицы (clerk/manager) + showcase closeContext в финальном шаге. §3 список API расширен: контексты (createContext/closeContext/setActive/ listContexts/hasContext/getActiveContext), overlay-helpers (hideTitleSlide/ hideImage/setHighlight/isHighlightMode), error-helpers (dismissPendingErrors/ fetchErrorStack). §6 пример _hooks.mjs: убран mock-навигация в beforeAll, добавлены примеры afterOpenContext/beforeCloseContext, afterEach показывает testResult. §8 переписан раздел «Реализация в browser.mjs» (мульти-контекст уже live) + новая таблица режимов изоляции tab/window. §9 JSON example: поле "context" → "contexts": [...] (массив). §10: убрано упоминание несуществующего verbose-режима. §13 «Параметризация»: убран статус «будущее», описана реальная семантика T6 (template name, param 2-м аргументом, testInfo.param). §14 buildContext: переписан под done-состояние + scoped-вариант. §16 каталог тест-кейсов: 13 → 19 файлов (multi-context, recording, errors-stack, tree-form, misc, hooks). §17 дорожная карта: 10 → 18 пунктов, M4–M8 включены. run.mjs: - testResult получил поле contexts: [...names] во всех ветках (passed/failed/skipped/context-setup-failed). Раннер передаёт declaredContexts из единой точки до if(skip), чтобы skip-результаты тоже несли структурную информацию. Регресс 19/19 ✓ (9m 8.7s) после --rebuild-stand. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -571,25 +571,29 @@ async function cmdTest(rawArgs) {
|
||||
let testIdx = 0;
|
||||
for (const t of filtered) {
|
||||
testIdx++;
|
||||
// Declared contexts — нужны и в skip-ветке, и в основной, чтобы все
|
||||
// testResult-записи в отчёте всегда содержали `contexts` поле.
|
||||
const declaredContexts = t.contexts && t.contexts.length
|
||||
? t.contexts
|
||||
: [t.context || defaultContextName];
|
||||
|
||||
if (t.skip) {
|
||||
const reason = typeof t.skip === 'string' ? t.skip : '';
|
||||
W.write(` \u25CB ${t.name}${reason ? ` (skip: ${reason})` : ' (skip)'}\n`);
|
||||
results.push({ name: t.name, file: t.file, tags: t.tags, status: 'skipped', duration: 0, attempts: 0, steps: [], output: '', error: null, screenshot: null });
|
||||
results.push({ name: t.name, file: t.file, tags: t.tags, contexts: declaredContexts, status: 'skipped', duration: 0, attempts: 0, steps: [], output: '', error: null, screenshot: null });
|
||||
skipCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resolve test's contexts: multi (t.contexts) or single (t.context || default).
|
||||
// Lazy-create them and set active to the primary one.
|
||||
const testContextNames = t.contexts && t.contexts.length
|
||||
? t.contexts
|
||||
: [t.context || defaultContextName];
|
||||
const testContextNames = declaredContexts;
|
||||
try {
|
||||
for (const cn of testContextNames) await ensureContext(cn);
|
||||
await browser.setActiveContext(testContextNames[0]);
|
||||
} catch (e) {
|
||||
W.write(` ✗ ${t.name} (context setup failed: ${e.message})\n`);
|
||||
results.push({ name: t.name, file: t.file, tags: t.tags, status: 'failed', duration: 0, attempts: 0, steps: [], output: '', error: { message: e.message }, screenshot: null });
|
||||
results.push({ name: t.name, file: t.file, tags: t.tags, contexts: declaredContexts, status: 'failed', duration: 0, attempts: 0, steps: [], output: '', error: { message: e.message }, screenshot: null });
|
||||
failCount++;
|
||||
if (opts.bail) break;
|
||||
continue;
|
||||
@@ -697,7 +701,7 @@ async function cmdTest(rawArgs) {
|
||||
try { await browser.stopRecording(); } catch {}
|
||||
}
|
||||
const dur = elapsed(t0);
|
||||
testResult = { name: t.name, file: t.file, tags: t.tags, status: 'passed', duration: dur, attempts: attempt, start: t0, stop: Date.now(), steps, output: output.join('\n'), error: null, screenshot: null, video: videoFile };
|
||||
testResult = { name: t.name, file: t.file, tags: t.tags, contexts: testContextNames, status: 'passed', duration: dur, attempts: attempt, start: t0, stop: Date.now(), steps, output: output.join('\n'), error: null, screenshot: null, video: videoFile };
|
||||
lastError = null;
|
||||
break;
|
||||
|
||||
@@ -731,7 +735,7 @@ async function cmdTest(rawArgs) {
|
||||
}
|
||||
lastError = { message: e.message, step: e.onecError?.step, screenshot: shotFile };
|
||||
const dur = elapsed(t0);
|
||||
testResult = { name: t.name, file: t.file, tags: t.tags, status: 'failed', duration: dur, attempts: attempt, start: t0, stop: Date.now(), steps, output: output.join('\n'), error: lastError, screenshot: shotFile, video: videoFile };
|
||||
testResult = { name: t.name, file: t.file, tags: t.tags, contexts: testContextNames, status: 'failed', duration: dur, attempts: attempt, start: t0, stop: Date.now(), steps, output: output.join('\n'), error: lastError, screenshot: shotFile, video: videoFile };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+167
-104
@@ -1,7 +1,7 @@
|
||||
# web-test runner: спецификация
|
||||
|
||||
Версия: 0.1 (черновик)
|
||||
Дата: 2026-04-05
|
||||
Версия: 0.2
|
||||
Дата: 2026-05-13 (последний sync)
|
||||
|
||||
## Обзор
|
||||
|
||||
@@ -30,26 +30,29 @@ node run.mjs test [url] <dir|file> [флаги]
|
||||
| `--bail` | false | Остановиться при первом падении |
|
||||
| `--retry=N` | 0 | Повторить упавшие тесты N раз |
|
||||
| `--timeout=ms` | 30000 | Таймаут на тест (мс) |
|
||||
| `--report=path` | (нет) | Записать JSON-отчёт в файл |
|
||||
| `--format=fmt` | json | Формат отчёта: `json`, `allure`, `junit` |
|
||||
| `--report-dir=path` | (нет) | Каталог для результатов Allure |
|
||||
| `--report=path` | (нет) | Записать JSON-отчёт в файл (или XML для `--format=junit`) |
|
||||
| `--format=fmt` | json | Формат отчёта: `json` / `allure` / `junit` |
|
||||
| `--report-dir=path` | dirname(report) / testDir | Каталог для скриншотов, видео, Allure-результатов |
|
||||
| `--screenshot=strategy` | on-failure | `on-failure` / `every-step` / `off` |
|
||||
| `--record` | false | Записывать видео для каждого теста |
|
||||
| `--record` | false | Записывать видео для каждого теста (mp4 в `--report-dir`) |
|
||||
| `-- <hookArgs...>` | -- | Всё после `--` пробрасывается в `_hooks.mjs` как `hookArgs` (см. §6.1) |
|
||||
|
||||
URL необязателен, если в каталоге тестов есть `webtest.config.mjs`.
|
||||
URL необязателен, если в каталоге тестов есть `webtest.config.mjs`. CLI URL переопределяет URL дефолтного контекста.
|
||||
|
||||
### Режим выполнения
|
||||
|
||||
In-process (не через HTTP). Раннер:
|
||||
1. Загружает конфиг (если есть)
|
||||
2. Обнаруживает файлы `*.test.mjs`
|
||||
3. Импортирует каждый модуль, извлекает метаданные
|
||||
4. Фильтрует по тегам/grep/only
|
||||
5. Группирует по контексту, сортирует по алфавиту внутри группы
|
||||
6. Запускает браузер (`chromium.launch()`)
|
||||
7. Создаёт BrowserContext + page для каждого используемого контекста (лениво)
|
||||
8. Выполняет тесты последовательно, переключая активный контекст
|
||||
9. Закрывает все контексты и браузер, выводит результаты
|
||||
1. Загружает конфиг (если есть).
|
||||
2. Обнаруживает файлы `*.test.mjs`, читает каждый, извлекает метаданные.
|
||||
3. Фильтрует по `--tags`/`--grep`/`only`. Параметризованные тесты разворачиваются.
|
||||
4. Запускает браузер и default-контекст (`chromium.launch()` или `launchPersistentContext`
|
||||
в зависимости от `isolation`).
|
||||
5. Тесты выполняются последовательно **в алфавитном порядке имён файлов**
|
||||
(внутри файла — в порядке экспорта).
|
||||
6. Для каждого теста: лениво создаёт нужные BrowserContext-ы (`ensureContext`),
|
||||
переключает активный, прогоняет хуки и тело, делает встроенный reset.
|
||||
7. По завершении: финальный teardown контекстов с `beforeCloseContext`-хуками,
|
||||
`disconnect()`, `cleanup()`.
|
||||
|
||||
---
|
||||
|
||||
@@ -104,26 +107,33 @@ export default async function({ navigateSection, openCommand, clickElement,
|
||||
|
||||
### Пример: мульти-контекстный процессный тест
|
||||
|
||||
Рекомендация: латинский ID контекста + кириллический `displayName` в
|
||||
`webtest.config.mjs.contexts.<id>.displayName` (см. §7).
|
||||
|
||||
```js
|
||||
export const name = 'Согласование приходной накладной';
|
||||
export const contexts = ['кладовщик', 'менеджер'];
|
||||
export const contexts = ['clerk', 'manager'];
|
||||
export const tags = ['process'];
|
||||
|
||||
export default async function({ кладовщик, менеджер, step }) {
|
||||
export default async function({ clerk, manager, step }) {
|
||||
|
||||
await step('Кладовщик создаёт накладную', async () => {
|
||||
await кладовщик.navigateSection('Склад');
|
||||
await кладовщик.openCommand('Приходные накладные');
|
||||
await кладовщик.clickElement('Создать');
|
||||
await кладовщик.fillFields({ 'Контрагент': 'ООО Поставщик' });
|
||||
await кладовщик.clickElement('Записать');
|
||||
await clerk.navigateSection('Склад');
|
||||
await clerk.openCommand('Приходные накладные');
|
||||
await clerk.clickElement('Создать');
|
||||
await clerk.fillFields({ 'Контрагент': 'ООО Поставщик' });
|
||||
await clerk.clickElement('Записать');
|
||||
});
|
||||
|
||||
await step('Менеджер утверждает', async () => {
|
||||
await менеджер.navigateSection('Согласование');
|
||||
await менеджер.openCommand('На утверждении');
|
||||
await менеджер.clickElement('ООО Поставщик', { dblclick: true });
|
||||
await менеджер.clickElement('Утвердить');
|
||||
await manager.navigateSection('Согласование');
|
||||
await manager.openCommand('На утверждении');
|
||||
await manager.clickElement('ООО Поставщик', { dblclick: true });
|
||||
await manager.clickElement('Утвердить');
|
||||
});
|
||||
|
||||
await step('Освобождаем контекст clerk', async () => {
|
||||
await manager.closeContext('clerk'); // освободить лицензию 1С
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -149,8 +159,12 @@ export default async function({ кладовщик, менеджер, step }) {
|
||||
**Таблицы:** `readTable`, `readSpreadsheet`, `fillTableRow`, `deleteTableRow`
|
||||
**Поля:** `fillFields`, `fillField`, `selectValue`
|
||||
**Действия:** `clickElement`, `closeForm`, `filterList`, `unfilterList`
|
||||
**Ошибки:** `dismissPendingErrors`, `fetchErrorStack`
|
||||
**Контексты:** `createContext`, `setActiveContext`, `closeContext`, `listContexts`,
|
||||
`hasContext`, `getActiveContext`
|
||||
**Запись:** `startRecording`, `stopRecording`, `isRecording`, `addNarration`, `getCaptions`
|
||||
**Презентация:** `showCaption`, `hideCaption`, `highlight`, `unhighlight`, `showTitleSlide`, `showImage`
|
||||
**Презентация:** `showCaption`, `hideCaption`, `showTitleSlide`, `hideTitleSlide`,
|
||||
`showImage`, `hideImage`, `highlight`, `unhighlight`, `setHighlight`, `isHighlightMode`
|
||||
**Утилиты:** `screenshot`, `wait`, `getPage`, `getSession`
|
||||
|
||||
### Тестовые утилиты
|
||||
@@ -401,12 +415,26 @@ export async function cleanup({ log }) {
|
||||
execSync('powershell.exe -File scripts/unpublish.ps1');
|
||||
}
|
||||
|
||||
export async function beforeAll({ navigateSection }) {
|
||||
await navigateSection('Склад');
|
||||
export async function beforeAll(ctx) {
|
||||
// По умолчанию 1С после входа уже показывает дефолтную секцию — навигация
|
||||
// в beforeAll обычно не нужна. Хук удобен для счётчиков, телеметрии,
|
||||
// общего setup'а который должен случиться один раз для всего прогона.
|
||||
}
|
||||
|
||||
export async function afterEach({ closeForm }) {
|
||||
// пользовательская очистка после теста (необязательно, встроенный сброс тоже сработает)
|
||||
export async function afterEach(ctx) {
|
||||
// Доступен ctx.testResult — { status, duration, attempts, error, steps }.
|
||||
// Встроенный сброс состояния выполняется ПОСЛЕ afterEach автоматически.
|
||||
}
|
||||
|
||||
export async function afterOpenContext(ctx, name, spec) {
|
||||
// Контекст name создан. spec — config.contexts[name]. Удобно для
|
||||
// persistent DOM-overlay'я с displayName (видно в видео какая вкладка к
|
||||
// какому пользователю относится).
|
||||
}
|
||||
|
||||
export async function beforeCloseContext(ctx, name, spec) {
|
||||
// Контекст name вот-вот закроется. Срабатывает и при ctx.closeContext
|
||||
// из теста, и в финальном teardown раннера.
|
||||
}
|
||||
```
|
||||
|
||||
@@ -508,15 +536,20 @@ export const context = 'кладовщик'; // необязательно, и
|
||||
export default async function({ clickElement, fillFields, ... }) { }
|
||||
```
|
||||
|
||||
### Группировка по контексту
|
||||
### Порядок выполнения и переключение контекста
|
||||
|
||||
Раннер группирует тесты по значению `context`:
|
||||
1. Собрать все тесты, определить набор уникальных контекстов
|
||||
2. Создать BrowserContext + page для каждого используемого контекста
|
||||
3. Для каждой группы тестов: переключить активный context, выполнить тесты
|
||||
4. Внутри группы тесты выполняются по алфавиту
|
||||
Раннер НЕ группирует тесты по контексту. Порядок выполнения — алфавитный
|
||||
по именам файлов (плюс порядок экспорта внутри файла). Для каждого теста:
|
||||
1. Через `ensureContext(name)` создаются BrowserContext-ы, упомянутые в
|
||||
`t.context` / `t.contexts` (если ещё не созданы).
|
||||
2. `setActiveContext(testContextNames[0])` — активный контекст = первый
|
||||
объявленный (для single — `t.context || defaultContext`, для multi —
|
||||
`t.contexts[0]`).
|
||||
3. После теста встроенный сброс пробегает по всем использованным контекстам.
|
||||
|
||||
Контексты создаются лениво (при первом обращении) и живут до конца прогона.
|
||||
Контексты живут между тестами: переключение через `setActiveContext` —
|
||||
дешёвое, новый login не требуется. Закрываются явно (`closeContext`) или
|
||||
финальным teardown'ом перед `disconnect()`.
|
||||
|
||||
### Мульти-контекст (процессные тесты)
|
||||
|
||||
@@ -550,18 +583,34 @@ await step('Кладовщик проверяет статус', async () => {
|
||||
});
|
||||
```
|
||||
|
||||
### Влияние на browser.mjs
|
||||
### Реализация в browser.mjs
|
||||
|
||||
Текущий browser.mjs хранит `page`, `browser`, `session` как module-level переменные.
|
||||
Для мульти-контекста необходимо:
|
||||
- Уметь создавать несколько `BrowserContext` + `page` в одном `browser`
|
||||
- Хранить карту контекстов `{ name → { context, page, session } }`
|
||||
- Переключать текущий `page` при смене активного контекста
|
||||
- API-функции раб��тают с текущим активным `page`
|
||||
`browser.mjs` хранит активный слот в module-level `page`/`browser`/`sessionPrefix`/`seanceId`,
|
||||
зеркалит его из Map `contexts: Map<name, slot>`. Переключение между слотами:
|
||||
`_saveActiveSlot()` сохраняет module-level → slot, `_activateSlot(name)`
|
||||
загружает slot → module-level. Это держит API-функции (`clickElement`,
|
||||
`fillFields` и т.д.) plain — они работают с текущим активным `page`,
|
||||
не зная про множественность контекстов.
|
||||
|
||||
Это промежуточный шаг к полному `createContext()` из Фазы 3 роадмапа,
|
||||
но значительно проще -- не требует рефакторинга всех функций browser.mjs,
|
||||
только управление текущим page.
|
||||
Публичный контекстный API:
|
||||
- `createContext(name, url, { isolation, extensionPath })` — создаёт BrowserContext
|
||||
и navigate'ит на URL.
|
||||
- `setActiveContext(name)` — переключает активный слот, при активной записи
|
||||
flush'ит хвост старой страницы и переподключает screencast к новой.
|
||||
- `closeContext(name)` — logout + close (page для `tab`, BrowserContext для
|
||||
`window`), удаляет из реестра. Throw если `name === active`.
|
||||
- `listContexts()` / `hasContext(name)` / `getActiveContext()` — read-only.
|
||||
|
||||
### Режимы изоляции
|
||||
|
||||
`isolation` (per-context или config-level):
|
||||
|
||||
| Режим | Реализация | Окна | Cookies | 1С-расширение |
|
||||
|-------|-----------|------|---------|---------------|
|
||||
| `'tab'` (default) | `launchPersistentContext` + `newPage()` per context | 1 окно, N вкладок | shared by path | загружается надёжно |
|
||||
| `'window'` | `chromium.launch()` + `newContext()` per context | N окон | полная изоляция | может не загружаться |
|
||||
|
||||
Смешивать режимы в одном прогоне нельзя — `createContext` бросает явную ошибку.
|
||||
|
||||
---
|
||||
|
||||
@@ -587,7 +636,7 @@ await step('Кладовщик проверяет статус', async () => {
|
||||
"name": "CRUD справочника Контрагенты",
|
||||
"file": "02-catalog-crud.test.mjs",
|
||||
"tags": ["smoke", "crud"],
|
||||
"context": "кладовщик",
|
||||
"contexts": ["clerk"],
|
||||
"status": "passed",
|
||||
"duration": 12.3,
|
||||
"attempts": 1,
|
||||
@@ -608,7 +657,7 @@ await step('Кладовщик проверяет статус', async () => {
|
||||
"name": "Обязательное поле",
|
||||
"file": "10-validation.test.mjs",
|
||||
"tags": ["validation"],
|
||||
"context": "кладовщик",
|
||||
"contexts": ["clerk"],
|
||||
"status": "failed",
|
||||
"duration": 8.1,
|
||||
"attempts": 2,
|
||||
@@ -711,7 +760,9 @@ web-test -- http://localhost/app/ru_RU
|
||||
23 passed, 1 failed, 1 skipped (2m 0.5s)
|
||||
```
|
||||
|
||||
Шаги показываются для упавших тестов (всегда) и для успешных (в verbose-режиме).
|
||||
Для passed-тестов выводится одна строка `✓ name (duration)`. Шаги печатаются
|
||||
только для упавших — после строки `✗`, с отступом, плюс сообщение ошибки и
|
||||
путь к скриншоту. Полная картина по шагам — в JSON-отчёте (`--report=...`).
|
||||
|
||||
---
|
||||
|
||||
@@ -760,9 +811,7 @@ async function resetState(ctx) {
|
||||
|
||||
---
|
||||
|
||||
## 13. Параметризация (будущее)
|
||||
|
||||
Формат зарезервирован, реализация отложена.
|
||||
## 13. Параметризация
|
||||
|
||||
```js
|
||||
export const name = 'Заполнение поля {type}';
|
||||
@@ -780,33 +829,33 @@ export default async function({ fillFields, getFormState, assert }, { type, fiel
|
||||
}
|
||||
```
|
||||
|
||||
В отчётах каждый набор параметров отображается как отдельный тест:
|
||||
- "Заполнение поля String"
|
||||
- "Заполнение поля Number"
|
||||
- "Заполнение поля Date"
|
||||
- "Заполнение поля Boolean"
|
||||
Параметры разворачиваются в отдельные тесты на этапе discovery. Имя
|
||||
формируется подстановкой через шаблон `{key}` в `mod.name`; если шаблона
|
||||
нет — суффикс `[index]`. Тест получает `param` вторым аргументом
|
||||
(`default(ctx, param)`). В отчётах каждый набор — отдельная запись со
|
||||
своим `name` и `param` в testInfo. `ctx.testInfo.param` доступен в теле
|
||||
теста и хуках.
|
||||
|
||||
---
|
||||
|
||||
## 14. buildContext() -- рефакторинг executeScript
|
||||
## 14. buildContext()
|
||||
|
||||
Извлечь из `executeScript()` в `run.mjs` (строки 104-214):
|
||||
Общая фабрика контекста, используется и `executeScript()` (для `exec`/`run`/`start`),
|
||||
и `cmdTest()` (для `test`).
|
||||
|
||||
**Что извлечь:**
|
||||
- Сбор всех экспортов `browser.*` в объект
|
||||
- Обёртка ACTION_FNS авто-обнаружением ошибок (проверка модальных/всплывающих после каждого вызова)
|
||||
- Захват скриншота до того, как `fetchErrorStack` закроет модальное окно ошибки
|
||||
- Вызов `fetchErrorStack` для модальных ошибок
|
||||
- Заглушки `noRecord` для функций записи/озвучки
|
||||
**Что делает:**
|
||||
- Собирает все экспорты `browser.*` в плоский объект.
|
||||
- Оборачивает ACTION_FNS авто-обнаружением 1С-ошибок: после каждого вызова
|
||||
проверяет `state.errors.modal`/`balloon`, делает скриншот ДО того, как
|
||||
`fetchErrorStack` закроет модалку, вызывает `fetchErrorStack` для modal-ошибок,
|
||||
бросает исключение со структурированным `err.onecError = { step, args, errors, formState, stack, screenshot }`.
|
||||
- Подмешивает заглушки `noRecord` (для функций записи/озвучки в exec-режиме).
|
||||
|
||||
**Сигнатура новой функции:**
|
||||
```js
|
||||
function buildContext({ noRecord = false } = {}) -> object
|
||||
```
|
||||
**Сигнатура:** `function buildContext({ noRecord = false } = {}) -> object`
|
||||
|
||||
**Использование после рефакторинга:**
|
||||
- `executeScript()` вызывает `buildContext()` + `new AsyncFunction(...)` (поведение не меняется)
|
||||
- `cmdTest()` вызывает `buildContext()` + `import()` + `mod.default(ctx)` (новое поведение)
|
||||
**Scoped-вариант** (`buildScopedContext(name)`): тот же `buildContext()`,
|
||||
но каждый вызов функции префиксится `await browser.setActiveContext(name)`.
|
||||
Используется для мульти-контекстных тестов (`ctx.a`/`ctx.b`).
|
||||
|
||||
---
|
||||
|
||||
@@ -854,39 +903,53 @@ function buildContext({ noRecord = false } = {}) -> object
|
||||
|
||||
## 16. Каталог тест-кейсов
|
||||
|
||||
Расположение: `tests/web-test/`
|
||||
Расположение: `tests/web-test/`. По состоянию на 2026-05-13: 19 файлов.
|
||||
|
||||
| # | Файл | Теги | Покрытие API |
|
||||
|---|------|------|-------------|
|
||||
| 01 | navigation.test.mjs | nav, smoke | navigateSection, getPageState, getSections, getCommands |
|
||||
| 02 | catalog-crud.test.mjs | crud, catalog, smoke | openCommand, fillFields, clickElement, closeForm, readTable, getFormState |
|
||||
| 03 | field-types.test.mjs | fields | fillFields (строка, число, дата, булево, перечисление) на Номенклатуре |
|
||||
| 04 | reference-field.test.mjs | fields, select | selectValue на ПриходнаяНакладная.Контрагент |
|
||||
| 05 | table-operations.test.mjs | table, smoke | readTable, fillTableRow, deleteTableRow |
|
||||
| 06 | document-workflow.test.mjs | doc, smoke | Создание документа, заполнение шапки + ТЧ, проведение, отмена |
|
||||
| 07 | tabs.test.mjs | tabs | switchTab на форме Номенклатуры |
|
||||
| 08 | hierarchy.test.mjs | hierarchy | clickElement с expand/collapse на Номенклатуре |
|
||||
| 09 | filter-list.test.mjs | filter | filterList, unfilterList, расширенный фильтр по полю |
|
||||
| 10 | validation.test.mjs | validation | Ошибка обязательного поля, подтверждение при закрытии |
|
||||
| 11 | report.test.mjs | report | Открыть отчёт, задать параметры, сформировать, readSpreadsheet |
|
||||
| 12 | form-state.test.mjs | state | getFormState: поля, кнопки, таблицы |
|
||||
| 13 | screenshots.test.mjs | util | screenshot(), wait() |
|
||||
| # | Файл | Теги | Покрытие |
|
||||
|---|------|------|----------|
|
||||
| 00 | hooks.test.mjs | hooks, smoke | индикатор порядка beforeAll/beforeEach/afterEach + testInfo + afterOpenContext |
|
||||
| 01 | navigation.test.mjs | nav, smoke | navigateSection, getPageState, navigateLink, switchTab, errors |
|
||||
| 02 | crud.test.mjs | crud, smoke | openCommand, fillFields, clickElement, closeForm, save-confirm flow |
|
||||
| 03 | fillfields.test.mjs | fields | text/checkbox/date/dropdown/reference/radio/clear + composite + direct-edit-form |
|
||||
| 04 | selectvalue.test.mjs | fields, select | dropdown / форма выбора / auto-history / clear |
|
||||
| 05 | table.test.mjs | table, smoke | fillTableRow/deleteTableRow/tab-loop/checkbox/clear |
|
||||
| 06 | document.test.mjs | doc, smoke | создание+проведение документа |
|
||||
| 07 | tabs.test.mjs | tabs | switchTab + errors |
|
||||
| 08 | hierarchy.test.mjs | hierarchy | groups expand + tree-grid view-mode switch |
|
||||
| 09 | filter.test.mjs | filter | simple-search/advanced-column/exact/date/reference/unfilter-all/unfilter-specific |
|
||||
| 10 | validation.test.mjs | validation | сообщения + exception modal (fetchErrorStack Path 1) |
|
||||
| 11 | report.test.mjs | report | DCS form + быстрый фильтр + readSpreadsheet + drill-down |
|
||||
| 12 | formstate.test.mjs | state | fields/buttons/tables/openForms/subordinate-nav/platformDialogs |
|
||||
| 13 | misc.test.mjs | misc | openFile EPF + security confirm |
|
||||
| 14 | errors-stack.test.mjs | errors | fetchErrorStack Path 1 + dismiss-modal + dismiss-platform |
|
||||
| 14 | multi-context-routing.test.mjs | multi-context | single test → non-default context |
|
||||
| 15 | multi-context-handover.test.mjs | multi-context | ctx.a creates → ctx.b sees → closeContext(b) + edge throw |
|
||||
| 15 | recording.test.mjs | record | startRecording/stopRecording/captions/narration/overlays |
|
||||
| 16 | tree-form.test.mjs | tree, table | FormDataTree edit (ДеревоНоменклатуры) |
|
||||
|
||||
~30 тест-кейсов, покрывающих все основные области API browser.mjs.
|
||||
Полный регресс — **19/19** (~9 минут на warm-стенде).
|
||||
|
||||
---
|
||||
|
||||
## 17. Дорожная карта реализации
|
||||
|
||||
| # | Задача | Результат | Зависимости | Статус |
|
||||
|---|--------|-----------|-------------|--------|
|
||||
| 1 | Архитектурная спецификация | `docs/web-test-runner-spec.md` (этот файл) | -- | done 2026-04-05 |
|
||||
| 2 | Рефакторинг buildContext() | run.mjs: извлечение из executeScript | спека | done 2026-04-05 |
|
||||
| 3 | Ядро cmdTest() | run.mjs: обнаружение, импорт, выполнение, консольный вывод, JSON-отчёт | #2 | done 2026-04-05 |
|
||||
| 4 | Утверждения + обёртка step() | run.mjs: assert.*, step(name, fn) | #3 | done 2026-04-05 |
|
||||
| 5 | Хуки (prepare/cleanup + before/after) | run.mjs: поддержка _hooks.mjs | #3 | done 2026-04-05 |
|
||||
| 6 | Файл конфигурации + контексты | run.mjs: webtest.config.mjs, BrowserContext'ы, маршрутизация | #3 | config done, BrowserContext pending |
|
||||
| 7 | Форматы отчётов (Allure, JUnit) | run.mjs: --format=allure/junit | #3 | -- |
|
||||
| 8 | Синтетическая конфигурация | integration/build-webtest-config.test.mjs | спека | done 2026-04-05 |
|
||||
| 9 | Smoke-тесты (01-06) | tests/web-test/01-06*.test.mjs | #3, #8 | -- |
|
||||
| 10 | Остальные тесты (07-13) | tests/web-test/07-13*.test.mjs | #9 | -- |
|
||||
| # | Задача | Результат | Статус |
|
||||
|---|--------|-----------|--------|
|
||||
| 1 | Архитектурная спецификация | `docs/web-test-runner-spec.md` (этот файл) | done 2026-04-05 |
|
||||
| 2 | Рефакторинг buildContext() | run.mjs: извлечение из executeScript | done 2026-04-05 |
|
||||
| 3 | Ядро cmdTest() | run.mjs: обнаружение, импорт, выполнение, JSON-отчёт | done 2026-04-05 |
|
||||
| 4 | Утверждения + обёртка step() | run.mjs: assert.*, step(name, fn) | done 2026-04-05 |
|
||||
| 5 | Хуки (prepare/cleanup + before/after) | run.mjs: поддержка `_hooks.mjs` | done 2026-04-05 |
|
||||
| 6 | Файл конфигурации + BrowserContext-ы | webtest.config.mjs, мульти-контекст | done 2026-05-10 (T4 + T4.5/4.6) |
|
||||
| 7 | Форматы отчётов (Allure, JUnit) | --format=allure/junit | done 2026-05-03 (T2/T3) |
|
||||
| 8 | Синтетическая конфигурация | `build-webtest-config.test.mjs` | done 2026-04-05 + M1 расширения 2026-05-01 |
|
||||
| 9 | Smoke-тесты P0 (~18 кейсов) | `tests/web-test/01-12*.test.mjs` | done 2026-05-04 (M2) |
|
||||
| 10 | Регресс P1 (~15 кейсов) | расширение 02/03/04/05/09/12 | done 2026-05-10 (M3) |
|
||||
| 11 | M4: расширенный регресс P2 | validation/errors/recording/hierarchy/openFile | done 2026-05-11 |
|
||||
| 12 | M5-pre: расширение синтетики | tree-form, composite, textEdit, history, unfilter | done 2026-05-12 |
|
||||
| 13 | M6: автономный стенд через `_hooks.mjs` | prepare(): config-rebuild/data-reload/EPF + smart Apache | done 2026-05-12 (MVP) |
|
||||
| 14 | M7.1/M7.2: ctx.testInfo + custom-поля контекстов | спека §3 + run.mjs | done 2026-05-13 |
|
||||
| 15 | M7.3: Headless-режим | `--headless` CLI + config | deferred (1С-specific блокеры в headless) |
|
||||
| 16 | M7.4: 4 testlevel-хука + индикатор | `_hooks.mjs` v0.3 + 00-hooks.test.mjs | done 2026-05-13 |
|
||||
| 17 | M7.5: title slide bonus | `beforeEach` под isRecording() | done 2026-05-13 |
|
||||
| 18 | M8: per-context lifecycle | closeContext + afterOpenContext/beforeCloseContext | done 2026-05-13 |
|
||||
|
||||
Reference in New Issue
Block a user