mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-10 16:14:54 +03:00
feat(web-test): M7.1+M7.2 — ctx.testInfo + проброс custom-полей контекстов
- ctx.testInfo (name/file/filePath/tags/timeout/attempt/maxAttempts/param/contexts/primaryContext)
выставляется перед каждой попыткой, доступен в beforeEach/test/afterEach
- ctx.testResult (status/duration/attempts/error/steps) доступен в afterEach
- run.mjs:411 spread полного contextSpec (был whitelist {url, isolation});
CLI --url override сохраняет custom-поля через merge
- webtest.config.mjs: displayName для a/b
- spec §3 — подраздел «Метаданные теста», §6 — availability testInfo/testResult,
§7 — рекомендация латинский ID + кириллический displayName
- Full regression 18/18 ✓ (9m 9.8s)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
// web-test run v1.11 — CLI runner for 1C web client automation
|
||||
// web-test run v1.12 — CLI runner for 1C web client automation
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
/**
|
||||
* CLI runner for 1C web client automation.
|
||||
@@ -408,10 +408,10 @@ async function cmdTest(rawArgs) {
|
||||
const defaultIsolation = config.isolation || 'tab';
|
||||
if (config.contexts && typeof config.contexts === 'object' && Object.keys(config.contexts).length) {
|
||||
for (const [n, spec] of Object.entries(config.contexts)) {
|
||||
contextSpecs[n] = { url: spec.url, isolation: spec.isolation };
|
||||
contextSpecs[n] = { ...spec };
|
||||
}
|
||||
defaultContextName = config.defaultContext || Object.keys(config.contexts)[0];
|
||||
if (url) contextSpecs[defaultContextName] = { url }; // CLI override of default
|
||||
if (url) contextSpecs[defaultContextName] = { ...contextSpecs[defaultContextName], url }; // CLI override of default url (preserve custom fields)
|
||||
} else {
|
||||
const fallbackUrl = url || config.url;
|
||||
if (!fallbackUrl) die('No URL provided and no webtest.config.mjs found');
|
||||
@@ -572,6 +572,23 @@ async function cmdTest(rawArgs) {
|
||||
let stepIdx = 0;
|
||||
const t0 = Date.now();
|
||||
|
||||
// testInfo — declarative metadata about the current test, visible
|
||||
// to test body and hooks (beforeEach/afterEach). Overwritten on
|
||||
// each attempt and each test (no delete, mirrors ctx.log/step lifecycle).
|
||||
ctx.testInfo = {
|
||||
name: t.name,
|
||||
file: basename(t.file),
|
||||
filePath: t.file,
|
||||
tags: t.tags,
|
||||
timeout: t.timeout,
|
||||
attempt,
|
||||
maxAttempts,
|
||||
param: t.param,
|
||||
contexts: Object.fromEntries(testContextNames.map(n => [n, contextSpecs[n]])),
|
||||
primaryContext: testContextNames[0],
|
||||
};
|
||||
ctx.testResult = null; // set right before afterEach
|
||||
|
||||
let videoFile = null;
|
||||
if (opts.record) {
|
||||
videoFile = resolve(reportDir, `${testIdx}-${slugify(t.name)}.mp4`);
|
||||
@@ -631,6 +648,8 @@ async function cmdTest(rawArgs) {
|
||||
|
||||
// per-test teardown
|
||||
if (t.teardown) try { await t.teardown(ctx); } catch {}
|
||||
// Expose testResult to afterEach (preliminary — full testResult assembled below).
|
||||
ctx.testResult = { status: 'passed', duration: elapsed(t0), attempts: attempt, error: null, steps };
|
||||
// afterEach
|
||||
if (hooks.afterEach) try { await hooks.afterEach(ctx); } catch {}
|
||||
// Built-in state reset across all contexts the test used
|
||||
@@ -661,6 +680,9 @@ async function cmdTest(rawArgs) {
|
||||
|
||||
// per-test teardown (always)
|
||||
if (t.teardown) try { await t.teardown(ctx); } catch {}
|
||||
// Expose preliminary testResult to afterEach (final testResult assembled below).
|
||||
const errInfo = { message: e.message, step: e.onecError?.step, screenshot: shotFile };
|
||||
ctx.testResult = { status: 'failed', duration: elapsed(t0), attempts: attempt, error: errInfo, steps };
|
||||
// afterEach (always)
|
||||
if (hooks.afterEach) try { await hooks.afterEach(ctx); } catch {}
|
||||
// Built-in state reset across all contexts the test used
|
||||
|
||||
@@ -159,6 +159,50 @@ export default async function({ кладовщик, менеджер, step }) {
|
||||
- `assert.*` -- хелперы утверждений (см. раздел 5)
|
||||
- `log(...args)` -- добавить в вывод теста
|
||||
|
||||
### Метаданные теста (`ctx.testInfo`)
|
||||
|
||||
Декларативная информация о текущем тесте. Раннер выставляет `ctx.testInfo`
|
||||
перед каждой попыткой (до `beforeEach`), хук и тело теста могут читать.
|
||||
Не предназначено для мутации.
|
||||
|
||||
```js
|
||||
ctx.testInfo = {
|
||||
name, // 'Навигация по разделам' (с подставленными params)
|
||||
file, // '01-navigation.test.mjs' (basename)
|
||||
filePath, // '01-navigation.test.mjs' (relative к testDir)
|
||||
tags, // ['nav', 'smoke']
|
||||
timeout, // 60000 (ms)
|
||||
attempt, // 1..maxAttempts (1-based)
|
||||
maxAttempts, // 1 + retry
|
||||
param, // { ... } | undefined (для export const params)
|
||||
contexts: { // объект, всегда 1+ ключей; зеркалит config.contexts
|
||||
a: { url, isolation, ...customFields },
|
||||
b: { ... },
|
||||
},
|
||||
primaryContext, // 'a' — имя контекста, активного на входе в тест
|
||||
// (= t.context для single, t.contexts[0] для multi)
|
||||
}
|
||||
```
|
||||
|
||||
Доступ к специфике контекста: `testInfo.contexts[testInfo.primaryContext].displayName`.
|
||||
`primaryContext` — декларация теста, не зависит от runtime-состояния
|
||||
`getActiveContext()` (которое может меняться внутри теста).
|
||||
|
||||
### Результат теста в afterEach (`ctx.testResult`)
|
||||
|
||||
Только в `afterEach`. До запуска теста — `null`. После — заполняется
|
||||
раннером перед вызовом хука:
|
||||
|
||||
```js
|
||||
ctx.testResult = {
|
||||
status, // 'passed' | 'failed'
|
||||
duration, // ms
|
||||
attempts, // фактически выполнено попыток (1..maxAttempts)
|
||||
error, // { message, step?, screenshot? } | null
|
||||
steps, // массив step-результатов
|
||||
}
|
||||
```
|
||||
|
||||
### Мульти-контекст
|
||||
|
||||
При `export const contexts = ['a', 'b']`:
|
||||
@@ -282,8 +326,9 @@ assert.noErrors(state, msg)
|
||||
**Тестовый уровень** (с контекстом браузера):
|
||||
- `beforeAll(ctx)` -- после подключения, перед первым тестом
|
||||
- `afterAll(ctx)` -- после последнего теста, до отключения
|
||||
- `beforeEach(ctx)` -- перед каждым тестом
|
||||
- `afterEach(ctx)` -- после каждого теста
|
||||
- `beforeEach(ctx)` -- перед каждым тестом. На входе уже доступен `ctx.testInfo` (см. §3).
|
||||
- `afterEach(ctx)` -- после каждого теста. Дополнительно доступен `ctx.testResult`
|
||||
с результатом завершившегося теста (status/duration/error/...).
|
||||
|
||||
### Порядок выполнения
|
||||
|
||||
@@ -377,13 +422,16 @@ URL должен быть передан через CLI.
|
||||
|
||||
```js
|
||||
export default {
|
||||
// Контексты: именованные URL для разных пользователей/ролей
|
||||
// Контексты: именованные URL для разных пользователей/ролей.
|
||||
// Рекомендация: латинский ID контекста (`clerk`, `manager`) + кириллический
|
||||
// `displayName` для UI/слайдов. Любые custom-поля пробрасываются как есть
|
||||
// и доступны хукам через `ctx.testInfo.contexts[name]` (см. §3).
|
||||
contexts: {
|
||||
кладовщик: { url: 'http://localhost/app-clerk/ru_RU' },
|
||||
менеджер: { url: 'http://localhost/app-manager/ru_RU' },
|
||||
админ: { url: 'http://localhost/app-admin/ru_RU' },
|
||||
clerk: { url: 'http://localhost/app-clerk/ru_RU', displayName: 'Кладовщик' },
|
||||
manager: { url: 'http://localhost/app-manager/ru_RU', displayName: 'Менеджер' },
|
||||
admin: { url: 'http://localhost/app-admin/ru_RU', displayName: 'Админ' },
|
||||
},
|
||||
defaultContext: 'кладовщик',
|
||||
defaultContext: 'clerk',
|
||||
|
||||
// Значения по умолчанию (переопределяются флагами CLI)
|
||||
timeout: 30000,
|
||||
@@ -393,6 +441,10 @@ export default {
|
||||
};
|
||||
```
|
||||
|
||||
Кириллица в ID контекстов работает, но смешанный регистр затрудняет ergonomics
|
||||
(`testInfo.contexts.кладовщик.displayName` vs `testInfo.contexts.clerk.displayName`).
|
||||
Рекомендуем разделять технический ID и человекочитаемое имя.
|
||||
|
||||
**Упрощённая форма** (один контекст, без именованных):
|
||||
|
||||
```js
|
||||
|
||||
@@ -7,8 +7,11 @@
|
||||
// конфликтовать с ручной разведкой и работать поверх отдельного Apache на :9191.
|
||||
export default {
|
||||
contexts: {
|
||||
a: { url: 'http://localhost:9191/webtest-runner/ru_RU' },
|
||||
b: { url: 'http://localhost:9191/webtest-runner/ru_RU' },
|
||||
// `displayName` — человекочитаемое имя контекста, видно хукам через
|
||||
// testInfo.contexts[name].displayName (например для showTitleSlide).
|
||||
// Custom-поля любого типа пробрасываются как есть.
|
||||
a: { url: 'http://localhost:9191/webtest-runner/ru_RU', displayName: 'Пользователь A' },
|
||||
b: { url: 'http://localhost:9191/webtest-runner/ru_RU', displayName: 'Пользователь B' },
|
||||
},
|
||||
defaultContext: 'a',
|
||||
// isolation: 'tab' (default) — persistent context, tabs in one window, 1С extension loads.
|
||||
|
||||
Reference in New Issue
Block a user