mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-28 07:31:00 +03:00
feat(web-test): --screenshot=on-failure|every-step|off + --report-dir
Раннер v1.5. Парсит --screenshot и --report-dir, мерж с config.screenshot.
- every-step: после успешного step() пишет {reportDir}/{testIdx}-{stepIdx}-{slug}.png,
путь в step.screenshot.
- off: ни пошаговых, ни error-shot.
- on-failure (default): error-shot уехал из .claude/skills/web-test/
в {reportDir}/error-{testIdx}-{slug}.png.
reportDir фоллбэчит: --report-dir → dirname(--report) → testDir.
Известная нестыковка: error-shot из buildContext/executeScript остаётся в
.claude/skills/web-test/error-shot.png — затронем при T2 (Allure).
Live-проверка: 01-navigation с every-step (5 PNG), off (пусто),
default on-failure на стуб-failing тесте (error-shot в reportDir).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
3ac1d425cd
commit
56cd18a6b4
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
// web-test run v1.4 — CLI runner for 1C web client automation
|
// web-test run v1.5 — CLI runner for 1C web client automation
|
||||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
/**
|
/**
|
||||||
* CLI runner for 1C web client automation.
|
* CLI runner for 1C web client automation.
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import * as browser from './browser.mjs';
|
import * as browser from './browser.mjs';
|
||||||
import { readFileSync, writeFileSync, unlinkSync, existsSync, readdirSync } from 'fs';
|
import { readFileSync, writeFileSync, unlinkSync, existsSync, readdirSync, mkdirSync } from 'fs';
|
||||||
import { resolve, dirname, basename, relative } from 'path';
|
import { resolve, dirname, basename, relative } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
@@ -335,7 +335,7 @@ function cmdStatus() {
|
|||||||
|
|
||||||
async function cmdTest(rawArgs) {
|
async function cmdTest(rawArgs) {
|
||||||
// Parse flags
|
// Parse flags
|
||||||
const opts = { bail: false, retry: 0, timeout: 30000, report: null, format: 'json' };
|
const opts = { bail: false, retry: 0, timeout: 30000, report: null, format: 'json', screenshot: null, reportDir: null };
|
||||||
let tags = null, grep = null;
|
let tags = null, grep = null;
|
||||||
const positional = [];
|
const positional = [];
|
||||||
for (const a of rawArgs) {
|
for (const a of rawArgs) {
|
||||||
@@ -346,6 +346,8 @@ async function cmdTest(rawArgs) {
|
|||||||
else if (a.startsWith('--timeout=')) opts.timeout = parseInt(a.slice(10)) || 30000;
|
else if (a.startsWith('--timeout=')) opts.timeout = parseInt(a.slice(10)) || 30000;
|
||||||
else if (a.startsWith('--report=')) opts.report = a.slice(9);
|
else if (a.startsWith('--report=')) opts.report = a.slice(9);
|
||||||
else if (a.startsWith('--format=')) opts.format = a.slice(9);
|
else if (a.startsWith('--format=')) opts.format = a.slice(9);
|
||||||
|
else if (a.startsWith('--screenshot=')) opts.screenshot = a.slice(13);
|
||||||
|
else if (a.startsWith('--report-dir=')) opts.reportDir = a.slice(13);
|
||||||
else if (!a.startsWith('--')) positional.push(a);
|
else if (!a.startsWith('--')) positional.push(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,6 +380,17 @@ async function cmdTest(rawArgs) {
|
|||||||
if (!tags && config.tags) tags = config.tags;
|
if (!tags && config.tags) tags = config.tags;
|
||||||
opts.timeout = rawArgs.some(a => a.startsWith('--timeout=')) ? opts.timeout : (config.timeout || opts.timeout);
|
opts.timeout = rawArgs.some(a => a.startsWith('--timeout=')) ? opts.timeout : (config.timeout || opts.timeout);
|
||||||
opts.retry = rawArgs.some(a => a.startsWith('--retry=')) ? opts.retry : (config.retries || opts.retry);
|
opts.retry = rawArgs.some(a => a.startsWith('--retry=')) ? opts.retry : (config.retries || opts.retry);
|
||||||
|
opts.screenshot = opts.screenshot || config.screenshot || 'on-failure';
|
||||||
|
if (!['on-failure', 'every-step', 'off'].includes(opts.screenshot)) {
|
||||||
|
die(`Invalid --screenshot=${opts.screenshot} (expected on-failure|every-step|off)`);
|
||||||
|
}
|
||||||
|
// Resolve report directory: --report-dir, else dirname(--report), else testDir
|
||||||
|
const reportDir = opts.reportDir
|
||||||
|
? resolve(opts.reportDir)
|
||||||
|
: (opts.report ? dirname(resolve(opts.report)) : testDir);
|
||||||
|
if (opts.screenshot !== 'off') {
|
||||||
|
try { mkdirSync(reportDir, { recursive: true }); } catch {}
|
||||||
|
}
|
||||||
|
|
||||||
// Discover test files
|
// Discover test files
|
||||||
const testFiles = discoverTests(testPath);
|
const testFiles = discoverTests(testPath);
|
||||||
@@ -443,7 +456,9 @@ async function cmdTest(rawArgs) {
|
|||||||
if (hooks.beforeAll) await hooks.beforeAll(ctx);
|
if (hooks.beforeAll) await hooks.beforeAll(ctx);
|
||||||
|
|
||||||
// Execute tests
|
// Execute tests
|
||||||
|
let testIdx = 0;
|
||||||
for (const t of filtered) {
|
for (const t of filtered) {
|
||||||
|
testIdx++;
|
||||||
if (t.skip) {
|
if (t.skip) {
|
||||||
const reason = typeof t.skip === 'string' ? t.skip : '';
|
const reason = typeof t.skip === 'string' ? t.skip : '';
|
||||||
W.write(` \u25CB ${t.name}${reason ? ` (skip: ${reason})` : ' (skip)'}\n`);
|
W.write(` \u25CB ${t.name}${reason ? ` (skip: ${reason})` : ' (skip)'}\n`);
|
||||||
@@ -460,6 +475,7 @@ async function cmdTest(rawArgs) {
|
|||||||
const output = [];
|
const output = [];
|
||||||
let steps = [];
|
let steps = [];
|
||||||
let currentSteps = steps;
|
let currentSteps = steps;
|
||||||
|
let stepIdx = 0;
|
||||||
const t0 = Date.now();
|
const t0 = Date.now();
|
||||||
|
|
||||||
// Wire up per-test log and step
|
// Wire up per-test log and step
|
||||||
@@ -469,6 +485,8 @@ async function cmdTest(rawArgs) {
|
|||||||
currentSteps.push(s);
|
currentSteps.push(s);
|
||||||
const prev = currentSteps;
|
const prev = currentSteps;
|
||||||
currentSteps = s.steps;
|
currentSteps = s.steps;
|
||||||
|
stepIdx++;
|
||||||
|
const myIdx = stepIdx;
|
||||||
try {
|
try {
|
||||||
await fn();
|
await fn();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -478,6 +496,15 @@ async function cmdTest(rawArgs) {
|
|||||||
} finally {
|
} finally {
|
||||||
s.stop = Date.now();
|
s.stop = Date.now();
|
||||||
currentSteps = prev;
|
currentSteps = prev;
|
||||||
|
if (opts.screenshot === 'every-step' && s.status === 'passed') {
|
||||||
|
try {
|
||||||
|
const slug = slugify(name);
|
||||||
|
const file = resolve(reportDir, `${testIdx}-${myIdx}-${slug}.png`);
|
||||||
|
const png = await browser.screenshot();
|
||||||
|
writeFileSync(file, png);
|
||||||
|
s.screenshot = file;
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -513,12 +540,12 @@ async function cmdTest(rawArgs) {
|
|||||||
// Built-in state reset
|
// Built-in state reset
|
||||||
await resetState(ctx);
|
await resetState(ctx);
|
||||||
|
|
||||||
// Screenshot on failure
|
// Screenshot on failure (skip if strategy is 'off')
|
||||||
let shotFile = e.onecError?.screenshot;
|
let shotFile = e.onecError?.screenshot;
|
||||||
if (!shotFile) {
|
if (!shotFile && opts.screenshot !== 'off') {
|
||||||
try {
|
try {
|
||||||
const png = await browser.screenshot();
|
const png = await browser.screenshot();
|
||||||
shotFile = resolve(__dirname, '..', `error-shot-${t.file.replace(/[/\\]/g, '-')}.png`);
|
shotFile = resolve(reportDir, `error-${testIdx}-${slugify(t.file.replace(/\.test\.mjs$/, ''))}.png`);
|
||||||
writeFileSync(shotFile, png);
|
writeFileSync(shotFile, png);
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
@@ -623,6 +650,14 @@ function elapsed2(start, stop) {
|
|||||||
return Math.round(((stop || Date.now()) - start) / 100) / 10;
|
return Math.round(((stop || Date.now()) - start) / 100) / 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function slugify(s) {
|
||||||
|
return String(s).trim()
|
||||||
|
.replace(/[\s/\\:*?"<>|]+/g, '-')
|
||||||
|
.replace(/-+/g, '-')
|
||||||
|
.replace(/^-|-$/g, '')
|
||||||
|
.slice(0, 60) || 'step';
|
||||||
|
}
|
||||||
|
|
||||||
function formatDuration(seconds) {
|
function formatDuration(seconds) {
|
||||||
if (seconds < 60) return `${Math.round(seconds * 10) / 10}s`;
|
if (seconds < 60) return `${Math.round(seconds * 10) / 10}s`;
|
||||||
const m = Math.floor(seconds / 60);
|
const m = Math.floor(seconds / 60);
|
||||||
@@ -764,5 +799,8 @@ Options for test:
|
|||||||
--bail Stop on first failure
|
--bail Stop on first failure
|
||||||
--retry=N Retry failed tests N times
|
--retry=N Retry failed tests N times
|
||||||
--timeout=ms Per-test timeout (default: 30000)
|
--timeout=ms Per-test timeout (default: 30000)
|
||||||
--report=path Write JSON report to file`);
|
--report=path Write JSON report to file
|
||||||
|
--report-dir=path Directory for screenshots and other artifacts
|
||||||
|
--screenshot=mode on-failure (default) | every-step | off
|
||||||
|
--format=fmt json (default) | allure | junit`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user