feat(web-test): M8 — per-context lifecycle (closeContext + afterOpenContext/beforeCloseContext)

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>
This commit is contained in:
Nick Shirokov
2026-05-13 16:07:45 +03:00
co-authored by Claude Opus 4.7
parent 43ed9ba142
commit eb87be5c04
6 changed files with 226 additions and 30 deletions
+10
View File
@@ -42,6 +42,16 @@ export default async function ({ step, assert, log, testInfo }) {
assert.equal(typeof testInfo.primaryContext, 'string', 'primaryContext должен быть строкой');
});
await step('afterOpenContext отработал хотя бы для default', () => {
// Default контекст создаётся до beforeAll → afterOpenContext должен был
// отработать как минимум один раз. beforeCloseContext в теле первого
// теста ещё не вызывался (контексты живы).
assert.ok(_state.afterOpenContext >= 1,
`afterOpenContext=${_state.afterOpenContext}, ожидался >= 1 (default-контекст создан)`);
assert.equal(_state.beforeCloseContext, 0,
`beforeCloseContext=${_state.beforeCloseContext}, ожидался 0 (контексты ещё живы)`);
});
await step('afterEach для этого теста ещё не вызывался', () => {
// В теле теста afterEach НЕ должен быть вызван ни разу для текущего теста.
// Если 00-hooks запущен первым (что и ожидается), afterEach === 0.