mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-11 00:14:56 +03:00
eb87be5c04
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>
75 lines
3.4 KiB
JavaScript
75 lines
3.4 KiB
JavaScript
export const name = 'Multi-context: ctx.a creates, ctx.b sees the new record';
|
|
export const tags = ['multi-context'];
|
|
export const contexts = ['a', 'b'];
|
|
export const timeout = 120000;
|
|
|
|
export default async function({ a, b, assert, step, log }) {
|
|
|
|
const unique = 'MultiCtx-' + Date.now();
|
|
|
|
await step('a: открыть Контрагенты, создать новую запись', async () => {
|
|
await a.navigateSection('Склад');
|
|
await a.openCommand('Контрагенты');
|
|
await a.clickElement('Создать');
|
|
await a.fillField('Наименование', unique);
|
|
await a.clickElement('Записать и закрыть');
|
|
log(`a created: ${unique}`);
|
|
});
|
|
|
|
await step('b: открыть Контрагенты в независимой сессии', async () => {
|
|
await b.navigateSection('Склад');
|
|
const state = await b.openCommand('Контрагенты');
|
|
assert.ok(state.form != null, 'Список должен открыться в b');
|
|
});
|
|
|
|
await step('b: найти запись через filterList', async () => {
|
|
await b.filterList(unique);
|
|
const t = await b.readTable();
|
|
log(`b: total=${t.total} rows=${t.rows?.length}`);
|
|
assert.tableHasRow(t, r => r['Наименование'] === unique);
|
|
await b.unfilterList();
|
|
await b.closeForm();
|
|
});
|
|
|
|
await step('a: cleanup — удалить запись', async () => {
|
|
// a's list view is still open from step 1's "Записать и закрыть" returning to list
|
|
await a.filterList(unique);
|
|
await a.clickElement(unique);
|
|
const page = await a.getPage();
|
|
await page.keyboard.press('Delete');
|
|
// confirmation dialog → Yes
|
|
await a.clickElement('Да');
|
|
await a.unfilterList();
|
|
await a.closeForm();
|
|
log('a deleted');
|
|
});
|
|
|
|
await step('a: освободить контекст b через closeContext', async () => {
|
|
// M8: handover завершён, b больше не нужен — освобождаем лицензию.
|
|
// scoped-обёртка `a.closeContext('b')` сначала setActiveContext('a'),
|
|
// потом browser.closeContext('b') → 'b' уже неактивен → success.
|
|
const before = await a.listContexts();
|
|
assert.includes(before, 'b', 'b должен быть в списке до closeContext');
|
|
await a.closeContext('b');
|
|
const after = await a.listContexts();
|
|
log(`contexts: before=[${before.join(',')}] after=[${after.join(',')}]`);
|
|
assert.ok(!after.includes('b'), `b должен исчезнуть, но contexts=[${after.join(',')}]`);
|
|
assert.includes(after, 'a', 'a должен остаться');
|
|
});
|
|
|
|
await step('a: closeContext активного контекста бросает', async () => {
|
|
// M8 invariant: нельзя закрыть active. scoped a.closeContext('a') сначала
|
|
// setActiveContext('a'), потом browser.closeContext('a') — 'a' активен → throw.
|
|
let caught = null;
|
|
try {
|
|
await a.closeContext('a');
|
|
} catch (e) {
|
|
caught = e;
|
|
}
|
|
assert.ok(caught, 'closeContext(active) должен бросить, но не бросил');
|
|
assert.match(caught.message, /cannot close the active context/,
|
|
`ожидался текст "cannot close the active context", получено: ${caught.message}`);
|
|
log(`thrown as expected: ${caught.message.split('\n')[0]}`);
|
|
});
|
|
}
|