Тяжёлые DOM-блоки row-fill (15–60 строк) вынесены в 8 именованных
функций dom/grid-edit.mjs. row-fill стал плоским orchestrator-ом.
Новое в dom/grid-edit.mjs:
- sortFieldKeysByColindexScript — сортировка keys по colindex (Tab-нав)
- findCellCoordsByFieldsScript — клетка по first matching header (multi)
- findNextCellCoordsByKeyScript — клетка по single key + no-space fuzzy
- findCheckboxAtPointScript — checkbox info по координатам elementFromPoint
- findRowCommitClickCoordsScript — клик OTHER row для commit-edit
- getGridEditCheckScript — { inEdit, tag?, hint? } диагностика
- readActiveGridCellScript — активная клетка (id, fullName, headerText)
- getElementCenterCoordsByIdScript — центр по id (дедуп 2 копий)
Метрики row-fill: 971 → 793 LOC (−178, S6 alone), inline page.evaluate
10 → 1 (значительно ниже плановой цели ≤10). Все табличные suite
зелёные (05/06/08/10/16), полный регресс зелёный (Checkpoint-2).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Новое в dom/grid.mjs (все принимают опциональный gridSelector):
- countGridRowsScript — кол-во .gridLine в body
- isTreeGridScript — тип grid'а (есть .gridBoxTree)
- findGridHeadCenterCoordsScript — центр .gridHead для commit-клика
- getSelectedOrLastRowIndexScript — selected row index, fallback на последний
Также:
- isInputFocusedInGrid wrapper (S1) применён в add-row "ready" поллинге
- isNotInListCloudVisibleScript (S3) применён вместо локального notInList
- clickShowAllInNotInListCloudScript — новая в dom/forms.mjs (клик
"Показать все" в "нет в списке" cloud popup через dispatchEvent)
Метрики row-fill: 1041 → 971 LOC (−70), evaluates 17 → 10. Регресс
05/08/16/10 — зелёный.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Новое в dom/edd.mjs:
- readEddScript — {visible, items:[{name,x,y}]}
- isEddVisibleScript — boolean, лёгкая проверка
- clickEddItemViaDispatchScript — клик по name через dispatchEvent (bypass
div.surface); fuzzy с NBSP/ё/bracket-strip
- clickShowAllInEddScript — "Показать все" в footer
Wrappers в helpers.mjs: readEdd, isEddVisible, clickEddItemViaDispatch,
clickShowAllInEdd. row-fill clickEddItem унифицирован с select-value
вариантом (NBSP/ё normalization теперь работает и для табличных строк).
Метрики: select-value 880 → 827 LOC (−53), row-fill 1065 → 1041 LOC
(−24); evaluates row-fill 20 → 17, select-value 7 → 5. Полный регресс
зелёный.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Все 17 inline page.evaluate в engine/table/filter.mjs вынесены в
именованные dom-генераторы. Engine-модуль стал чистым orchestrator-ом.
Новое в dom/forms.mjs (shared с будущим S3 select-value):
- findSearchInputScript(formNum) — поиск SearchString/ПоискаСтроки input
- findNamedButtonScript(text) — кнопка a.press по innerText (Найти, OK)
- findCompareTypeRadioScript(form, idx) — радио CompareType#N#radio
- isFormVisibleScript(form) — есть ли видимые элементы form{N}
Новое в dom/filter.mjs:
- findFirstGridCellCoordsScript — координаты первой клетки грида
- findColumnFirstCellCoordsScript — клетка по имени колонки (fuzzy header
match с needDlb-fallback)
- readFieldSelectorInfoScript — FieldSelector value + DLB coords
- pickFieldInSelectorDropdownScript — выбор поля в FieldSelector DLB-edd
- readFilterDialogInfoScript — Pattern id+value+isDate+isRef
- findFilterBadgeCloseScript — × badge по имени поля
- findFirstFilterBadgeCloseScript — × первого видимого badge (для clear-all)
Попутно: добавлен импорт readSubmenuScript (был pre-existing broken
import в Еще-fallback ветке Alt+F).
Метрики filter.mjs: 390 → 256 LOC (−134, −34%), inline page.evaluate
17 → 0. Регресс 09-filter / 02-crud / 05-table — зелёный.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SpreadsheetDocument (отчёты, печатные формы) — другой домен, чем form-grid
(табличные части документов, списки). Раньше лежал внутри table/, что
было обманчиво.
engine/table/spreadsheet.mjs → engine/spreadsheet/spreadsheet.mjs
Структура engine/:
core/ плумбинг движка (state, wait, errors, session, click, ...)
forms/ работа с формами (fill, close, select-value, state)
nav/ навигация
table/ form-grid (grid, row-fill, filter, grid-toggle)
spreadsheet/ SpreadsheetDocument
recording/ запись + overlays
В будущем при росте spreadsheet можно распилить — engine/spreadsheet/cells.mjs,
engine/spreadsheet/scroll.mjs и т.д. без переименований.
11-report регресс зелёный.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
getFormState — высокоуровневая операция «прочитать состояние формы»,
семантически в forms/ ближе чем в core/ (foundational плумбинг движка).
engine/core/form-state.mjs → engine/forms/state.mjs
Все 11 importer'ов обновлены. Внутри state.mjs пути исправлены:
'./state.mjs' → '../core/state.mjs', './errors.mjs' → '../core/errors.mjs'.
03-fillfields регресс зелёный.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
После полной чистки cycle-импортов в E.13 остались комментарии типа
"getFormState still in browser.mjs", которые больше не верны (он переехал
в engine/core/form-state.mjs). Сметаем устаревшие "moved to / lives in
browser.mjs" комментарии в 8 файлах.
Дополнительно в engine/table/spreadsheet.mjs:
- убраны неиспользуемые импорты readTableScript, resolveGridScript, normYo
(остались с тех пор, как readTable жил в этом файле — до этапа D.12
rename'а в grid.mjs)
- заголовочный комментарий обновлён (без упоминания readTable)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
readTable читает form-grid (.gridLine/.gridBody — табличные части на форме,
списки), а не SpreadsheetDocument. Имя файла table/spreadsheet.mjs было
обманчиво. Разделяем домены:
table/grid.mjs ← readTable (form-grid операции, готово
для fillTableRow + deleteTableRow в D.12)
table/spreadsheet.mjs ← readSpreadsheet + cell helpers (только
SpreadsheetDocument — отчёты, печатные формы)
Поведение 1-в-1. browser.mjs re-export обновлён.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
clickSpreadsheetCell вызывает getFormState в конце (для drill-down формы),
но import не был добавлен при экстракции на C.11. ReferenceError в
11-report drill-down. Импортируем из browser.mjs (циклически).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
table/spreadsheet.mjs (~580 LOC):
- readTable, readSpreadsheet
- scanSpreadsheetCells, buildSpreadsheetMapping (private)
- scrollSpreadsheetToCell, clickSpreadsheetCell, findSpreadsheetCellByText
table/filter.mjs (~390 LOC): filterList, unfilterList
После C.11 + C.10 clean-up:
- core/click.mjs импортит clickSpreadsheetCell/findSpreadsheetCellByText
напрямую из table/spreadsheet.mjs (а не из browser.mjs)
- browser.mjs больше не реэкспортирует эти два — публичный API
остаётся 56 экспортов как до рефакторинга
- Добавил import { clickElement } в browser.mjs для внутренних вызовов
из fillTableRow/deleteTableRow
browser.mjs: 2470 → 1532 LOC (≈75% от исходных 6293).
05-table + 09-filter регресс зелёный.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
clickElement (~300 LOC) перенесён единым блоком в core/click.mjs.
Поведение 1-в-1. Внутри остаётся всё ветвление (spreadsheet, submenu,
gridGroup/Parent, gridTreeNode, gridRow, tab, button) — разнос на
forms/click-form.mjs + nav/click-popup.mjs + finer table-toggle
отложен на E.13 для безопасности.
clickSpreadsheetCell + findSpreadsheetCellByText временно exported из
browser.mjs (нужны core/click.mjs). На C.11 они переедут в
table/spreadsheet.mjs, экспорт из browser.mjs можно будет убрать.
browser.mjs: 2768 → 2470 LOC.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
forms/fill.mjs (~140 LOC): fillFields, fillField
forms/close.mjs (~50 LOC): closeForm
clickElement остаётся в browser.mjs до C.10.
Допиленные импорты после первого прохода:
- fill.mjs: readFormScript, normYo (из dom/state — забыл при экстракции)
- close.mjs: recorder (используется для паузы 500ms при confirmation
во время записи)
03-fillfields регресс зелёный.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Перенос selectValue + helpers из browser.mjs (~960 LOC):
- scanGridRows, dblclickAndVerify, advancedSearchInline
- pickFromSelectionForm, isTypeDialog, pickFromTypeDialog (экспортируются —
вызываются из fillFields/fillTableRow в browser.mjs)
- fillReferenceField (экспортируется — вызывается из fillFields)
- selectValue
Двумя слайсами вокруг fillFields/fillField/clickElement/closeForm, которые
остаются в browser.mjs до этапов C.9/C.10.
browser.mjs: 4095 → 2933 LOC. 56 публичных экспортов.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. В selectValue локальный const detectNewForm = () => ... объявлялся ниже
composite-type ветки, которая его вызывала → TDZ ReferenceError "Cannot
access 'detectNewForm' before initialization". Хелпер поднят в начало
функции, дубликат-объявление убрано.
2. clipboardWarnLogged читается в restoreClipboard (line 92), но не был
в списке импортов из core/state.mjs (импортировался только setter).
ReferenceError срабатывал только когда clipboard.read() возвращал
ошибку — в первом A-регрессе ветка не активировалась случайно.
Регресс 18/19 (одна flake в 11-report — readSpreadsheet timing).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В clickElement две ветки (gridGroup/gridParent + gridTreeNode) имели
почти идентичные page.evaluate-блоки: найти gridLine под target.y,
получить иконку-разворачивалку, вернуть её центр + isExpanded.
table/grid-toggle.mjs:
- getGridToggleIcon(target, formNum, { iconSelector, isExpandedExpr })
- shouldClickToggle(iconInfo, expand, toggle)
Поведение 1-в-1. Селекторы и isExpanded-критерий передаются параметрами:
- groups: '.gridListH, .gridListV' + icon.classList.contains('gridListV')
- trees: '.gridBoxImg [tree="true"]' + bg.includes('gx=0')
Экономия ~30 LOC дублей.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
core/helpers.mjs: returnFormState(extras) — стандартный хвост action-функций:
getFormState + Object.assign(extras) + checkForErrors → state.errors. Унифицирует
~15 hand-written копий и закрывает R1/R2/R3 (state.errors теперь добавляется
автоматически у любого пользователя хелпера).
В этом коммите конвертированы только 2 простейших P1-сайта (openCommand,
второй handle в navigateLink) — без extras между getFormState и err-проверкой.
Остальные 30+ сайтов сложнее (state.X между, разные return-shape, wrapped
fillFields) — будут мигрированы органически при переносе clickElement/
selectValue/closeForm в forms/* на этапе C.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В fillReferenceField было два места с одинаковым page.evaluate-скриптом
чтения #editDropDown (DLB-popup перед paste и autocomplete после Ctrl+V).
core/helpers.mjs: readEdd() → { visible, items?: [{ name, x, y }] }.
selectValue использует свой clickEddItem через dispatchEvent (bypass div.surface) —
оставлен как есть, специфика API там сильно отличается.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В fillReferenceField, selectValue и fillTableRow была одна и та же логика:
сканировать DOM на наличие элемента с id="form{N}_*" где N > prevFormNum.
Две вариации: strict (только visible interactive — input.editInput/a.press)
и broad (любой [id], учитывает type-dialogs с пустыми button-id).
core/helpers.mjs: detectNewForm(prevFormNum, { strict }) → number|null.
Внутри функций оставлены тонкие локальные обёртки (для совместимости
с уже использующейся сигнатурой без аргументов) — будут убраны на C.8/D.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В selectValue было 4 одинаковых блока поиска input-элемента поля
по имени (form{N}_{name} либо form{N}_{name}_i0 для refs):
clear-ветка, composite-type-ветка, F4-fallback, "last resort" F4.
core/helpers.mjs: findFieldInputId(formNum, fieldName) → string|null.
~30 LOC дублей убрано, поведение 1-в-1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В fillReferenceField, clickElement и DLB-ветке selectValue был один и тот же
паттерн: page.click → catch 'intercepts pointer events' → force-click →
catch снова → Escape + retry. Три копии (плюс одна с dismissPendingErrors).
core/helpers.mjs (новый): safeClick(selector, { timeout, dismissErrors }).
Экономия ~60 LOC дублей. Поведение 1-в-1 (dismissErrors:true только в
fillReferenceField — там единственное место, где исходно было).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
После A.3 эти helpers стали приватными в core/errors.mjs, но getFormState
(browser.mjs:408) и closeForm (browser.mjs:2168) их по-прежнему вызывают —
ловили ReferenceError на каждое действие. Делаем их экспортируемыми
и импортируем в browser.mjs. Имя с подчёркиванием сохраняется до этапа E.13
(финальная чистка). Регресс 19/19.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Перенос session-функций из browser.mjs (~380 LOC):
- connect, disconnect, attach, detach, getSession
- createContext, setActiveContext, listContexts, getActiveContext,
hasContext, closeContext
- findExtension (приватная)
- _logoutSlot, _saveActiveSlot, _activateSlot, _attachSessionListeners
(приватные multi-context хелперы)
Session-модуль зависит от core/state, core/errors (closeModals),
recording/capture (stopRecording) и циклически от browser.mjs
(getPageState — переедет в nav/navigation.mjs на этапе C.7).
ESM live-binding делает цикл безопасным: getPageState вызывается
только внутри async функций, а не на этапе загрузки модуля.
browser.mjs: 4251 LOC, 56 публичных экспортов. Завершает Чекпоинт A.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
core/wait.mjs (123 LOC):
- waitForStable: smart DOM-stability polling
- waitForCondition: JS-expression polling
- startNetworkMonitor: CDP network-activity monitor
core/errors.mjs (336 LOC):
- closeModals, dismissPendingErrors, checkForErrors, fetchErrorStack
- Платформенные диалоги: _detectPlatformDialogs, _closePlatformDialogs
- _parseErrorStack, _fetchStackViaReport, _fetchStackViaHamburger (приватные)
browser.mjs импортирует их для внутреннего использования и re-export'ит
только fetchErrorStack (исходно публичный). Остальные функции остаются
приватными — публичный API не меняется (56 экспортов).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Состояние движка (browser, page, sessionPrefix, seanceId, recorder, контексты,
константы, normYo, isConnected/ensureConnected/getPage) переехало в
core/state.mjs. Импортируется как live-binding; присваивания в browser.mjs
конвертированы в setX(...) — ESM imports read-only.
Публичный API не меняется (56 экспортов). Регресс 19/19 зелёный.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Тесты активно используют OS clipboard (`writeText` + Ctrl+V — единственный
способ добиться trusted-paste для autocomplete справочников и кириллицы).
При локальном запуске это перетирало пользовательский буфер. Теперь:
- `pasteText(text, {confirm, postDelay})` в browser.mjs делает узкое окно
save → writeText → confirm-key → restore вокруг каждой пасты (~ms).
- Save/restore через `navigator.clipboard.read()`/`write()` — все MIME
(текст, картинка, HTML), blob'ы стэшатся на `window` без CDP-сериализации.
- 14 callsites переведены на helper.
- При failure save'а (CF_HDROP из Проводника не виден через web-API) restore
явно очищает буфер, чтобы тестовое значение не протекало.
- Опт-аут: CLI `--no-preserve-clipboard`, env `WEB_TEST_PRESERVE_CLIPBOARD=0`,
`preserveClipboard: false` в `webtest.config.mjs`.
Регресс tests/web-test — 6 прогонов 19/19 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
--timeout=<ms> / --timeout-min=<n> и WEB_TEST_EXEC_TIMEOUT_MS вместо
захардкоженных 30 мин; сообщение об ошибке строится из фактического
значения. Закрывает кейс длинных записей видео с addNarration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В тест-обёртке ACTION_FN при 1С-исключении на throw-ed Error
вешалась полная структура (step, args, errors, formState, stack,
screenshot), но при сборке отчёта движок брал из неё только
{message, step, screenshot} — остальные поля терялись. Платформенный
стек 1С, ради которого делается fetchErrorStack, в JSON-отчёт не
попадал; в Allure statusDetails.trace писался только log()-вывод
теста.
Что поменялось:
- errInfo собирается один раз после teardown (раньше был дубликат на
732 и 745), используется и для ctx.testResult (afterEach), и для
lastError, и для итоговой записи в results[].
- В errInfo добавлено поле onecError: e.onecError — структура с
stack.entries[{location, code}], formState, args, errors доезжает
до JSON-отчёта без обрезания.
- writeAllure склеивает statusDetails.trace из tr.output + (если есть)
onecError.stack.raw под разделителем "--- 1C stack ---". В Allure UI
платформенный стек теперь виден прямо в карточке упавшего теста.
Обратная совместимость: для падений без 1С-исключения (assertion,
навигация и т.п.) e.onecError === undefined → JSON.stringify его
выкидывает, форма записи { message, screenshot } сохраняется в
точности.
Проверено вручную на стенде tests/web-test/ — падающий тест с
ВызватьИсключение, JSON и Allure оба содержат полный stack.entries и
формированный trace.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
run.mjs:
- syncAllureExtras(testDir, reportDir) копирует все файлы из
<testDir>/_allure/ в reportDir перед генерацией отчёта. Underscore
в имени параллелен _hooks.mjs (инфра, не тест) — discovery его
пропускает.
- Вызов после writeAllure при --format=allure.
tests/web-test/_allure/categories.json — 7 правил классификации падений
по нашему 1С-домену:
1. License pool exhausted (1C) — известный multi-context flake.
2. 1C application error (modal) — exception modal через fetchErrorStack.
3. Section panel icon-only — деградация состояния стенда.
4. Navigation lookup miss — navigateSection/openCommand/navigateLink/switchTab.
5. Element not found — clickElement/fillField/selectValue/closeForm/fillTableRow/deleteTableRow.
6. Test timeout — Timeout (Nms) от раннера.
7. Assertion failure — наши createAssertions + 1С-specific (formHasField/tableHasRow/noErrors).
spec §9: раздел «Доп. файлы Allure через <testDir>/_allure/» с таблицей
поддерживаемых типов (categories.json / environment.properties /
executor.json) и минимальным примером.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
run.mjs:
- buildSeverityIndex(config) — валидация config.severity (inverted map
«уровень → [теги]») при загрузке: ключи только из blocker|critical|
normal|minor|trivial, теги не дублируются между bucket'ами,
defaultSeverity тоже валидируется. fail-fast через die.
- resolveSeverity(t, severityIndex):
1. mod.severity если задан и валидный — выигрывает.
2. max-rank среди тегов (стандартные имена severity или маппинг).
3. config.defaultSeverity или 'normal'.
Rank: blocker(5) > critical(4) > normal(3) > minor(2) > trivial(1).
Max-wins инвариантен к порядку тегов.
- writeAllure: добавлены labels suite (= dirname(t.file) или 'root') +
severity. Тег `tag` остался как раньше.
- testResult пробрасывает t.severity (для passed/failed веток).
- SEVERITY_RANK/LEVELS объявлены в модульной шапке (top-level await на
cmdTest начинается до конца тела модуля, TDZ-аккуратность).
webtest.config.mjs: severity policy для нашего сьюта (smoke +
multi-context → critical, recording → minor, defaultSeverity = normal).
spec.md §7: раздел про severity-policy в конфиге с валидацией.
spec.md §9: «Авто-эмиссия label-ов» — tag/suite/severity + правила резолва.
Регресс 19/19 ✓ (9m 7.6s). Распределение по уровням после исправления
'record' → 'recording' в маппинге: 13 critical / 5 normal / 1 minor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
browser.mjs:
- + closeContext(name): logout slot + close page (tab) или context (window),
удаление из реестра. Throw если name неактивен (рулило: nicht den aktiven
closen, recorder always attached к active → invariant простой).
- _logoutSlot(slot, waitMs) — извлечён из disconnect, переиспользуется в
closeContext.
run.mjs:
- ensureContext() после createContext вызывает hooks.afterOpenContext(ctx, name, spec).
- wrapCloseContextHook() оборачивает ctx.closeContext (и каждую scoped-обёртку)
чтобы перед browser.closeContext fir'ить hooks.beforeCloseContext.
- Финальный teardown в finally: для всех живых контекстов кроме первого
(survivor) — beforeCloseContext + closeContext; для survivor только хук,
его закрывает disconnect().
_hooks.mjs v0.5:
- afterOpenContext инжектит persistent DOM-badge с displayName в правый
верхний угол page — в записанном видео всегда видно, какой контекст.
- beforeCloseContext counter-only.
- _state расширен полями afterOpenContext / beforeCloseContext.
15-multi-context-handover.test.mjs:
- +2 шага: closeContext('b') после handover, попытка closeContext(active)
ловится throw'ом с проверкой message.
00-hooks.test.mjs:
- +1 ассерт: afterOpenContext >= 1 (default уже создан), beforeCloseContext === 0
в теле первого теста.
spec §6:
- Раздел «Контекстный уровень» (afterOpenContext / beforeCloseContext + правила closeContext).
- ASCII-диаграмма порядка хуков обновлена с per-context lifecycle.
Регресс 19/19 ✓ (9m 16.8s).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В CLI раннера всё после `--` собирается в массив hookArgs и
передаётся в инфра-хуки prepare/cleanup без интерпретации со
стороны раннера. Сигнатура расширена до { hookArgs, log, config }:
log — структурированный вывод раннера, config — разобранный
webtest.config.mjs. Шаблон «всё после `--` принадлежит вложенному
инструменту» — стандартная shell-конвенция (npm, cargo, pytest).
Спека §6 обновлена под новую сигнатуру, §6.1 закрепляет контракт
`--` ↔ hookArgs с примером. Help-строка раннера упоминает
разделитель.
run.mjs:
1. resetState проверял `if (!state.form) break`. form === 0 (фоновая
форма 1С, которую detectForm может вернуть) рассматривался как
"форм нет" → cleanup прерывался, форма оставалась → следующий тест
получал грязное состояние. Замена на `state.form == null` корректно
различает null (desktop) и 0 (реальная фоновая форма).
2. Error screenshot в catch-блоке cmdTest снимался ПОСЛЕ resetState,
который уже закрывал все формы → скрин показывал пустой рабочий
стол вместо места падения. Перенёс снимок в начало catch (до
teardown/afterEach/resetState).
Эффекты:
- 15-multi-context-handover теперь стабильно проходит в полном прогоне
(раньше падал когда предыдущий тест оставлял form=0).
- 04-selectvalue/direct-form остался pre-existing flake (история
выбора 1С после 03 — отдельная задача в синтетике).
- Скриншоты падения теперь показывают реальный UI на момент исключения.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
run.mjs v1.10: cmdTest больше не передаёт noRecord:true в buildContext.
Тестам доступен полный API browser.mjs (showCaption, hideCaption,
startRecording, stopRecording, addNarration).
Изначальный стуб с noRecord:true прятал showCaption/hideCaption тестов
вместе с recording-функциями. Это блокировало визуальные оверлеи в
мульти-контекстных тестах: a.showCaption() тихо превращался в no-op,
баннер никогда не отображался даже под --record.
Smart wait внутри showCaption и так гейтится на наличие recorder
(`if (recorder && ...)`), поэтому без --record тесты остаются быстрыми
(никаких 2-секундных пауз на каждый вызов).
startRecording/stopRecording/addNarration теперь тоже доступны тестам.
При попытке вызвать startRecording в момент активной runner-записи
browser.startRecording бросает "Already recording" — loud failure
лучше silent no-op.
Регресс: 15-multi-context-handover один проходит за 19.9s. Полный
прогон 10/12 (04 и 15 флапают независимо в последовательности).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
browser.mjs v1.12 + run.mjs v1.9: createContext принимает isolation параметр.
По умолчанию 'tab' — все контексты живут в одном launchPersistentContext, каждый
слот получает свою Page (вкладку). Преимущества: 1С extension грузится
надёжно (через --load-extension в persistent profile), один процесс Chromium,
дешёвая память. Cookies делятся между вкладками, но скоупятся по URL-path —
для модели «разные пользователи через разные vrd-публикации» это естественно
и достаточно.
isolation: 'window' (opt-in) — старый путь chromium.launch() + newContext():
полная изоляция cookies, отдельный BrowserContext (и окно) на каждый слот,
но extension может не подняться. Использовать когда нужна изоляция auth
внутри одного URL.
Смешивать режимы в одном прогоне нельзя — createContext бросает явную
ошибку (первый createContext устанавливает activeMode, остальные обязаны
совпадать).
Конфиг tests/web-test/webtest.config.mjs: добавлен комментарий с описанием
обоих режимов. По умолчанию tab — синтетика и наши smoke-тесты идут им.
Live: 11/12 в полном прогоне (default tab) + 3/3 sanity-check в window mode
(01-navigation + 14 + 15). Видеозапись из T4.5 работает в обоих режимах.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
browser.mjs v1.11: recorder стал глобальным (не per-slot) — один ffmpeg,
один mp4 на тест с любым числом переключений контекста.
Frame state (lastFrameBuf/lastFrameTime/handler) переехал в поля recorder.
Добавлен recorder._attachPage(targetPage) — стопает старый CDP screencast,
заводит новый на нужной странице, route'ит фреймы в тот же ffmpeg pipe.
setActiveContext: при активной записи делает _flushFrames (замораживает
хвост уходящего окна), затем _attachPage(page) после _activateSlot. Видео
получается непрерывным с плавным сюжетом — пока активен a, видно a; пока
активен b, видно b.
_saveActiveSlot/_activateSlot больше не трогают recorder/lastCaptions/
lastRecordingDuration — recorder следует за активной страницей через
_attachPage, не через slot mirror.
disconnect: убрал leftover из T4.1, который пытался итерировать slot.recorder.
Live: 15-multi-context-handover с --record → 17.84s mp4, 446 кадров @ 25fps,
извлечённые кадры показывают переключение между окнами a (1920x1042) и
b (982x546). Полный регресс 11/12 (04-selectvalue — pre-existing flake).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
browser.mjs v1.10: createContext/setActiveContext/listContexts/getActiveContext/
hasContext. Несколько изолированных BrowserContext в одном Chromium-процессе через
chromium.launch() + newContext(). Module-level page/sessionPrefix/seanceId/recorder
зеркалят активный слот (атомарный своп через _saveActiveSlot/_activateSlot).
connect() оставлен для exec/run/start без изменений (launchPersistentContext).
run.mjs v1.8: ensureContext(name) + ленивое создание. Single-routing через
export const context = 'name'. Multi через export const contexts = ['a','b'] +
buildScopedContext(name) строит ctx.a/ctx.b — каждое действие префиксится
setActiveContext. Reset state после теста по всем активным контекстам.
Конфиг tests/web-test/webtest.config.mjs: два контекста a/b на одну webtest
публикацию (изолированные cookies через newContext).
Smoke-тесты:
- 14-multi-context-routing.test.mjs — single routing в b (2.6s)
- 15-multi-context-handover.test.mjs — ctx.a создаёт Контрагента, ctx.b в
независимой сессии видит запись через filterList, ctx.a cleanup (14.5s, 4/4)
Live: 11/12 в полном прогоне. 04-selectvalue/direct-form флапает —
pre-existing, воспроизводится на baseline 95e4674 (03→04 sequence).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>