Числа были зашиты в код и подобраны на лёгком синтетическом стенде. На тяжёлом
прикладном решении тот же resetState честно идёт дольше — и упирался бы в чужой
дефолт без возможности его поднять.
- `deadlines: {...}` в webtest.config.mjs переопределяет любой бюджет поштучно.
Неизвестный ключ или неположительное значение — ошибка до старта прогона:
опечатка в имени означала бы, что переопределение молча не действует.
- Пробой resetState теперь ПРЕРЫВАЕТ контекст, а не пишет строку и едет дальше.
После неудавшегося сброса состояние UI неизвестно, и переиспользование слота
утекало бы грязным состоянием в следующий тест — худший исход плохо подобранного
бюджета: тихий дрейф вместо видимой ошибки. Теперь слишком тесный бюджет стоит
перезапуска контекста, но никогда — неверного результата теста.
Проверено: опечатка ключа → внятная ошибка с перечнем допустимых; deadlines
{resetState:1} → пробой виден строкой, контекст прерван, следующий тест зелёный.
check.mjs 6/6.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Фикстура лежит в репозитории, но README о ней не упоминал — а её нельзя
использовать «по интуиции»: ожидаемый результат `1 passed, 1 failed` с кодом
выхода 1, где красный тест означает успех.
Записано: как запускать, какие два условия читать в выводе, когда гонять
(правки пути очистки и жизненного цикла, обновление Playwright — механика
стоит на замеренном поведении библиотеки), чего она стоит (лицензия +
перезапуск браузера в tab-режиме) и чего НЕ ловит (молчаливую поломку logout,
если 1С уйдёт на куки; полумёртвый CDP).
Заодно: коды выхода 2/3 из --global-timeout и актуальный счёт тестов (21→25).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Прогон вставал намертво на зависшем Playwright-действии (~29 мин, без движения),
при --format=allure отчёт терялся целиком — результаты писались только в конце.
Promise.race с таймером был и раньше, но не лечил: race не отменяет t.fn (промис
отменить нельзя), а весь путь после него шёл без единого таймаута. Паттерн
`try { await x } catch {}` ловит reject, но не «никогда не завершится» — а
page.evaluate не имеет таймаута в принципе, поэтому заблокированный JS-поток
рендерера вешал раннер навсегда.
Что сделано:
1. Прерывание. При таймауте движок опрашивает контекст (probeContext) и при
вердикте hang/browser-dead уничтожает зависшее: abortContext закрывает
страницу с runBeforeUnload:false, повисший await отваливается «Target closed»,
следующий тест поднимает контекст лениво. Зависший тест не ретраится.
2. Диагноз в отчёте. hang (браузер жив, рендерер не отвечает) против slow
(просто не уложился → поднять timeout). Разделитель — асимметрия пробников:
вызов в browser-процесс отвечает за 1 мс при мёртвом рендерере, evaluate — нет.
3. Лицензии. Штатный logout идёт fetch-ем изнутри страницы и на зависшей
странице невозможен. abortContext шлёт POST /e1cib/logout из Node: замерено —
сеанс опознаётся seanceId в URL, кук у клиента нет вообще, после запроса
клиент пишет «сеанс был завершен». Каскад: node → page → соседняя страница.
4. Дедлайны на весь путь очистки (deadline.mjs) — пробой печатается строкой,
молча зависнуть больше нельзя. Попутно: disconnect слал logout по зависшей
странице дважды (модульный `page` после multi-context ветки).
5. Инкрементальный Allure: результат теста пишется сразу по его завершении —
зависание больше не уничтожает уже собранное.
6. --global-timeout: потолок на прогон, работает и внутри зависшего теста
(неразрешённый промис не блокирует event loop). Коды выхода: 2 — потолок
сработал, 3 — зависло само сворачивание. Внешний watchdog больше не нужен.
Проверено вживую на стенде: фикстура tests/web-test/_hang (заблокированный
JS-поток) — падение за 17.6с с verdict: hang вместо вечного зависания,
следующий тест зелёный, оба результата в allure-results; --global-timeout
срабатывает посреди зависания и сохраняет отчёт. Полный регресс 25 тестов:
пробоев дедлайнов ноль.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Раннер регресса теперь держит пул 1С-сеансов сам, вместо накопления контекстов
между тестами и ручного закрытия в хуках. Три необязательных поля webtest.config.mjs
(без них поведение прежнее):
- maxContexts — потолок одновременно живых сеансов (null = без лимита);
- contextPolicy — 'reuse' (держать открытыми в пределах лимита) | 'strict'
(закрывать non-pinned контексты теста сразу после него);
- pinnedContexts — не вытесняются LRU (default = [defaultContext]; [] делает
default вытесняемым на тесном стенде).
Перед setup каждого теста LRU-вытеснение освобождает слот под нужды теста;
уже открытые нужные контексты переиспользуются. Default больше не вечно-pinned.
Исчерпание пула даёт внятную ошибку вместо маскирующего «Browser not connected».
- new: cli/test-runner/context-pool.mjs — чистый планировщик planEviction + LRU.
- cli/commands/test.mjs (v1.4): парсинг/валидация полей, вытеснение с фолбэк-парковкой
на нужный контекст (нельзя закрыть единственный активный), strict-закрытие, LRU-трекинг.
- Доки: regression-spec (§7/§8/глоссарий), regression-guide (рецепт), regress.md.
- Регресс дай-фудит фичу: 14-multi-context-routing роутит в 3-й контекст c,
15-multi-context-handover проверяет вытеснение c на границе; конфиг maxContexts:2.
Юнит-краёв планировщика — в debug/ (gitignored). Live-регресс: 25/25 зелёных.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Спека §1, regress.md, README приведены в соответствие новому контракту:
сигнатура `test <dir|file>...`, несколько путей (дедуп + сортировка), флаг
--url=, заметка про резолв webtest.config.mjs/_hooks.mjs от каталога первого пути.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Команда `test` приведена к поведению тест-раннеров (jest/pytest/playwright):
человеческий отчёт со сводкой в последней строке идёт в stdout, а машинный
JSON/JUnit — опционально через `--report=-` (Unix-конвенция `-` = stdout),
при этом прогресс уезжает в stderr. Убран безусловный дамп JSON в stdout,
из-за которого `test … | tail` хоронил сводку под отчётом.
- test.mjs: writer выбирается по режиму (--report=- → stderr-прогресс);
развилка `-` в обеих ветках записи (json и junit), чтобы не плодить файл "-";
валидация: --report=- несовместимо с --format=allure (каталог, не поток).
- util.mjs: строка --report=- в справке.
- Документация (spec/guide/regress/README) приведена к фактическому
английскому выводу и описывает матрицу потоков stdout/stderr.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
clickElement как последний fallback (без table) фокусирует одноимённое
поле ввода, не меняя значение — возвращает focused:{field,id,ok}.
Закрывает пробел: клавиши F4/Shift+F4 требовали сфокусированного поля,
но штатного примитива фокуса не было.
- dom/forms.mjs: резолв input.editInput/textarea по имени/заголовку
последним шагом findClickTargetScript; имена полей в available
- click-form.mjs: focusFormField (клик по инпуту + isInputFocused → ok)
- click.mjs: ветка диспетчера kind === field
- SKILL.md + docs/web-test-guide.md: focused в extras, пример focus→F4
- tests: 19-focus-field.test.mjs (focus/F4/регресс/негатив)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>