mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-18 00:29:42 +03:00
fix(web-test): скриншоты падений доезжают до Allure-отчёта
Вложение «Screenshot on failure» весило 0 B у всех красных тестов — две независимых причины, маскирующие друг друга. 1. slugify сохранял кириллицу в именах артефактов. Allure CLI молча не находит вложение с не-ASCII именем: пишет "size": 0 без ссылки на файл (JAVA_OPTS с file.encoding/sun.jnu.encoding не помогает). Теперь slugify транслитерирует кириллицу и схлопывает остальное не-ASCII в дефис. Чинит и видео — оно использовало то же имя. 2. Скриншот 1С-ошибки писался в фиксированный <навык>/error-shot.png: вне reportDir (репортер аттачит по basename → мёртвая ссылка) и одним именем на весь прогон (каждый следующий тест перетирал предыдущий). exec-context получил setErrorShotDir + уникальные имена; раннер направляет их в reportDir. Дефолт для интерактивных exec/run не менялся. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
// web-test cli/commands/test v1.4 — regression test runner
|
||||
// web-test cli/commands/test v1.5 — regression test runner
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import { existsSync, writeFileSync, mkdirSync } from 'fs';
|
||||
import { existsSync, writeFileSync, mkdirSync, renameSync, copyFileSync, unlinkSync } from 'fs';
|
||||
import { resolve, dirname, basename, relative } from 'path';
|
||||
import * as browser from '../../browser.mjs';
|
||||
import { out, die, elapsed, slugify, formatDuration, interpolate, printSteps } from '../util.mjs';
|
||||
import { buildContext, buildScopedContext } from '../exec-context.mjs';
|
||||
import { buildContext, buildScopedContext, setErrorShotDir } from '../exec-context.mjs';
|
||||
import { createAssertions } from '../test-runner/assertions.mjs';
|
||||
import { buildSeverityIndex } from '../test-runner/severity.mjs';
|
||||
import { writeAllure, buildJUnit, syncAllureExtras } from '../test-runner/reporters.mjs';
|
||||
@@ -143,6 +143,10 @@ export async function cmdTest(rawArgs) {
|
||||
: (opts.report && !reportToStdout ? dirname(resolve(opts.report)) : testDir);
|
||||
if (opts.screenshot !== 'off') {
|
||||
try { mkdirSync(reportDir, { recursive: true }); } catch {}
|
||||
// 1C-error screenshots (taken inside the action wrapper) default to a single
|
||||
// fixed file at the skill root — outside reportDir and shared by every test.
|
||||
// Point them at reportDir so each failure keeps its own attachable file.
|
||||
setErrorShotDir(reportDir);
|
||||
}
|
||||
|
||||
// Discover test files
|
||||
@@ -432,6 +436,21 @@ export async function cmdTest(rawArgs) {
|
||||
shotFile = resolve(reportDir, `error-${testIdx}-${slugify(t.file.replace(/\.test\.mjs$/, ''))}.png`);
|
||||
writeFileSync(shotFile, png);
|
||||
} catch {}
|
||||
} else if (shotFile && dirname(resolve(shotFile)) !== reportDir) {
|
||||
// Shot came from a context built before setErrorShotDir (e.g. a server
|
||||
// session started earlier): reporters attach by basename, so anything
|
||||
// outside reportDir is a dead link. Move it in under a unique name.
|
||||
const dest = resolve(reportDir, `error-${testIdx}-${slugify(t.file.replace(/\.test\.mjs$/, ''))}.png`);
|
||||
try {
|
||||
renameSync(resolve(shotFile), dest);
|
||||
shotFile = dest;
|
||||
} catch {
|
||||
try {
|
||||
copyFileSync(resolve(shotFile), dest);
|
||||
try { unlinkSync(resolve(shotFile)); } catch {}
|
||||
shotFile = dest;
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
if (t.teardown) try { await t.teardown(ctx); } catch {}
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
// web-test cli/exec-context v1.0 — buildContext + executeScript для run/exec/test
|
||||
// web-test cli/exec-context v1.1 — buildContext + executeScript для run/exec/test
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import { resolve, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import * as browser from '../browser.mjs';
|
||||
import { elapsed } from './util.mjs';
|
||||
import { elapsed, slugify } from './util.mjs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const ERROR_SHOT_PATH = resolve(__dirname, '..', '..', 'error-shot.png');
|
||||
const DEFAULT_ERROR_SHOT_PATH = resolve(__dirname, '..', '..', 'error-shot.png');
|
||||
|
||||
// Where 1C-error screenshots go. The default is a single fixed file: fine for
|
||||
// interactive `exec`/`run` (last error wins, easy to find), wrong for a test run
|
||||
// where every test needs its own file inside reportDir. cmdTest overrides this.
|
||||
let errorShotDir = null;
|
||||
let errorShotSeq = 0;
|
||||
|
||||
export function setErrorShotDir(dir) {
|
||||
errorShotDir = dir;
|
||||
errorShotSeq = 0;
|
||||
}
|
||||
|
||||
function nextErrorShotPath(label) {
|
||||
if (!errorShotDir) return DEFAULT_ERROR_SHOT_PATH;
|
||||
return resolve(errorShotDir, `onec-error-${++errorShotSeq}-${slugify(label || 'shot')}.png`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a per-context wrapper: same shape as buildContext output, but every call
|
||||
@@ -69,7 +85,7 @@ export function buildContext({ noRecord = false } = {}) {
|
||||
let errorShot;
|
||||
try {
|
||||
const png = await ctx.screenshot();
|
||||
errorShot = ERROR_SHOT_PATH;
|
||||
errorShot = nextErrorShotPath(name);
|
||||
writeFileSync(errorShot, png);
|
||||
} catch {}
|
||||
// Try to fetch call stack for modal errors before throwing
|
||||
@@ -127,7 +143,7 @@ export async function executeScript(code, { noRecord } = {}) {
|
||||
if (!shotFile) {
|
||||
try {
|
||||
const png = await browser.screenshot();
|
||||
shotFile = ERROR_SHOT_PATH;
|
||||
shotFile = nextErrorShotPath('script');
|
||||
writeFileSync(shotFile, png);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// web-test cli/util v1.2 — generic helpers for CLI commands
|
||||
// web-test cli/util v1.3 — generic helpers for CLI commands
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
export function out(obj) {
|
||||
@@ -35,12 +35,29 @@ export function elapsed2(start, stop) {
|
||||
return Math.round(((stop || Date.now()) - start) / 100) / 10;
|
||||
}
|
||||
|
||||
const TRANSLIT = {
|
||||
а: 'a', б: 'b', в: 'v', г: 'g', д: 'd', е: 'e', ё: 'e', ж: 'zh', з: 'z', и: 'i',
|
||||
й: 'y', к: 'k', л: 'l', м: 'm', н: 'n', о: 'o', п: 'p', р: 'r', с: 's', т: 't',
|
||||
у: 'u', ф: 'f', х: 'h', ц: 'ts', ч: 'ch', ш: 'sh', щ: 'sch', ъ: '', ы: 'y', ь: '',
|
||||
э: 'e', ю: 'yu', я: 'ya',
|
||||
};
|
||||
|
||||
/**
|
||||
* ASCII-only slug for artifact file names (screenshots, videos).
|
||||
* Non-ASCII names are unusable as Allure attachments: the Allure CLI silently
|
||||
* fails to resolve them and emits `"size": 0` with no link to the file
|
||||
* (JAVA_OPTS encoding flags do not help). Cyrillic is transliterated so the
|
||||
* name stays readable; anything else non-ASCII collapses to `-`.
|
||||
*/
|
||||
export function slugify(s) {
|
||||
return String(s).trim()
|
||||
.replace(/[\s/\\:*?"<>|]+/g, '-')
|
||||
const ascii = String(s).trim().toLowerCase()
|
||||
.replace(/[а-яё]/g, ch => TRANSLIT[ch] ?? '-');
|
||||
return ascii
|
||||
.replace(/[^a-z0-9._-]+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-|-$/g, '')
|
||||
.slice(0, 60) || 'step';
|
||||
.slice(0, 60)
|
||||
.replace(/^-|-$/g, '') || 'step';
|
||||
}
|
||||
|
||||
export function formatDuration(seconds) {
|
||||
|
||||
Reference in New Issue
Block a user