From f39a0d9c5ec00425c563f1a735131dc421224858 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 5 Apr 2026 14:47:26 +0300 Subject: [PATCH] =?UTF-8?q?docs(web-test):=20BrowserContext=20=D0=B2=D0=BC?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=BE=20sequential=20reconnect=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BC=D1=83=D0=BB=D1=8C=D1=82=D0=B8-=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D1=82=D0=B5=D0=BA=D1=81=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Один процесс браузера, несколько изолированных BrowserContext'ов. Мгновенное переключение между пользователями, состояние каждой сессии сохраняется. Не требует полного рефакторинга createContext(). Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/web-test-runner-spec.md | 105 +++++++++++++++++++++++++++-------- 1 file changed, 82 insertions(+), 23 deletions(-) diff --git a/docs/web-test-runner-spec.md b/docs/web-test-runner-spec.md index 54538f17..d7ccdbdb 100644 --- a/docs/web-test-runner-spec.md +++ b/docs/web-test-runner-spec.md @@ -46,9 +46,10 @@ In-process (не через HTTP). Раннер: 3. Импортирует каждый модуль, извлекает метаданные 4. Фильтрует по тегам/grep/only 5. Группирует по контексту, сортирует по алфавиту внутри группы -6. Подключается к 1С (`browser.connect(url)`) -7. Выполняет тесты последовательно -8. Отключается, выводит результаты +6. Запускает браузер (`chromium.launch()`) +7. Создаёт BrowserContext + page для каждого используемого контекста (лениво) +8. Выполняет тесты последовательно, переключая активный контекст +9. Закрывает все контексты и браузер, выводит результаты --- @@ -280,19 +281,21 @@ assert.noErrors(state, msg) ### Порядок выполнения ``` -prepare() // без браузера - browser.connect(url) - beforeAll(ctx) // браузер готов +prepare() // без браузера (восстановление БД, публикация) + browser.launch() // запуск процесса браузера + создание BrowserContext'ов // по одному на каждый используемый контекст + beforeAll(ctx) // браузер готов, контексты созданы beforeEach(ctx) - test.setup(ctx) // подготовка теста - test.default(ctx) // тело теста - test.teardown(ctx) // очистка теста (всегда) - afterEach(ctx) // всегда - [встроенный сброс] // всегда + test.setup(ctx) // подготовка теста + test.default(ctx) // тело теста + test.teardown(ctx) // очистка теста (всегда) + afterEach(ctx) // всегда + [встроенный сброс] // всегда (для каждого активного контекста) ...следующий тест... afterAll(ctx) - browser.disconnect() -cleanup() // без браузера + закрытие всех BrowserContext'ов + browser.close() +cleanup() // без браузера (удаление публикации) ``` ### Встроенный сброс состояния @@ -372,9 +375,30 @@ export default { ## 8. Контексты +### Механизм: Playwright BrowserContext + +Один процесс браузера (`chromium.launch()`), несколько изолированных контекстов. +Каждый контекст -- отдельная сессия (куки, авторизация, состояние страницы). + +``` +browser (один процесс chromium) + ├─ BrowserContext "кладовщик" → page → http://localhost/app-clerk/ru_RU + ├─ BrowserContext "менеджер" → page → http://localhost/app-mgr/ru_RU + └─ BrowserContext "админ" → page → http://localhost/app-admin/ru_RU +``` + +Преимущества: +- **Мгновенное переключение** между пользователями (смена активного `page`) +- **Состояние сохраняется** -- переключились на менеджера и обратно, у кладовщика + все формы остались открытыми, ничего не потеряно +- **Нет переподключений** -- каждая сессия живёт независимо +- **Один процесс** -- экономия ресурсов по сравнению с несколькими браузерами +- **Стандартный паттерн** Playwright для мульти-пользовательских сценариев + ### Одиночный контекст (по умолчанию) -Большинство тестов. Один браузер, один пользователь. Тест получает плоский контекст со всем API. +Большинство тестов. Один BrowserContext, один пользователь. +Тест получает плоский контекст со всем API. ```js export const context = 'кладовщик'; // необязательно, используется defaultContext @@ -383,10 +407,13 @@ export default async function({ clickElement, fillFields, ... }) { } ### Группировка по контексту -Раннер группирует тесты по значению `context`, минимизирует переподключения: -1. Собрать все тесты, сгруппировать по имени контекста -2. Для каждой группы: подключиться -> выполнить тесты -> отключиться -3. Внутри группы тесты выполняются по алфавиту +Раннер группирует тесты по значению `context`: +1. Собрать все тесты, определить набор уникальных контекстов +2. Создать BrowserContext + page для каждого используемого контекста +3. Для каждой группы тестов: переключить активный context, выполнить тесты +4. Внутри группы тесты выполняются по алфавиту + +Контексты создаются лениво (при первом обращении) и живут до конца прогона. ### Мульти-контекст (процессные тесты) @@ -395,11 +422,43 @@ export const contexts = ['кладовщик', 'менеджер']; export default async function({ кладовщик, менеджер, step, assert }) { } ``` -Каждый именованный контекст -- полноценный объект API. Тест оркестрирует переключение. +Каждый именованный контекст -- полноценный объект API со своим `page`. +Тест оркестрирует переключение между пользователями. +Состояние каждого пользователя сохраняется между переключениями: -**Этапы реализации:** -- Этап 1: последовательное переподключение (отключиться от одного URL, подключиться к другому) -- Этап 2: параллельные браузеры (после рефакторинга browser.mjs в `createContext()`) +```js +await step('Кладовщик создаёт документ', async () => { + await кладовщик.openCommand('Приходные накладные'); + await кладовщик.clickElement('Создать'); + await кладовщик.fillFields({ 'Контрагент': 'ООО Поставщик' }); + await кладовщик.clickElement('Записать'); + // кладовщик стоит на форме документа +}); + +await step('Менеджер утверждает', async () => { + await менеджер.navigateSection('Согласование'); + await менеджер.clickElement('Утвердить'); +}); + +await step('Кладовщик проверяет статус', async () => { + // страница кладовщика ТА ЖЕ -- форма открыта, навигация не нужна + const state = await кладовщик.getFormState(); + assert.equal(state.fields['Статус']?.value, 'Утверждён'); +}); +``` + +### Влияние на browser.mjs + +Текущий browser.mjs хранит `page`, `browser`, `session` как module-level переменные. +Для мульти-контекста необходимо: +- Уметь создавать несколько `BrowserContext` + `page` в одном `browser` +- Хранить карту контекстов `{ name → { context, page, session } }` +- Переключать текущий `page` при смене активного контекста +- API-функции раб��тают с текущим активным `page` + +Это промежуточный шаг к полному `createContext()` из Фазы 3 роадмапа, +но значительно проще -- не требует рефакторинга всех функций browser.mjs, +только управление текущим page. --- @@ -723,7 +782,7 @@ function buildContext({ noRecord = false } = {}) -> object | 3 | Ядро cmdTest() | run.mjs: обнаружение, импорт, выполнение, консольный вывод, JSON-отчёт | #2 | | 4 | Утверждения + обёртка step() | run.mjs: assert.*, step(name, fn) | #3 | | 5 | Хуки (prepare/cleanup + before/after) | run.mjs: поддержка _hooks.mjs | #3 | -| 6 | Файл конфигурации + контексты | run.mjs: webtest.config.mjs, маршрутизация контекстов | #3 | +| 6 | Файл конфигурации + контексты | run.mjs: webtest.config.mjs, BrowserContext'ы, маршрутизация | #3 | | 7 | Форматы отчётов (Allure, JUnit) | run.mjs: --format=allure/junit | #3 | | 8 | Синтетическая конфигурация | integration/build-webtest-config.test.mjs | спека | | 9 | Smoke-тесты (01-06) | tests/web-test/01-06*.test.mjs | #3, #8 |