feat(web-test): test CLI принимает несколько путей + флаг --url, fail-fast валидация

Привод контракта `run.mjs test` к общей практике (pytest/jest/playwright):
- позиционные аргументы = пути к тестам, можно несколько
  (`test a.test.mjs b.test.mjs dir/`); discoverTests объединяет, дедуплицирует,
  сортирует (порядок по числовым префиксам сохраняется);
- URL переехал из первого позиционного во флаг --url= (по умолчанию из
  webtest.config.mjs) — раньше url-первым путал: второй путь уходил в page.goto()
  и падал с криптовым 'Cannot navigate to invalid URL';
- fail-fast: несуществующий путь → понятная ошибка вместо падения goto;
  аргумент, похожий на URL, подсказывает про --url=.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-06-02 20:07:03 +03:00
parent 31fa66d8fe
commit 7f2bf9d2d3
4 changed files with 46 additions and 25 deletions
@@ -1,4 +1,4 @@
// web-test cli/commands/test v1.2 — regression test runner
// web-test cli/commands/test v1.3 — regression test runner
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import { existsSync, writeFileSync, mkdirSync } from 'fs';
import { resolve, dirname, basename, relative } from 'path';
@@ -20,11 +20,12 @@ export async function cmdTest(rawArgs) {
// Parse flags
const opts = { bail: false, retry: 0, timeout: 30000, report: null, format: 'json', screenshot: null, reportDir: null, record: false };
let tags = null, grep = null;
let tags = null, grep = null, urlFlag = null;
const positional = [];
for (const a of ownArgs) {
if (a.startsWith('--tags=')) tags = a.slice(7).split(',');
else if (a.startsWith('--grep=')) grep = new RegExp(a.slice(7), 'i');
else if (a.startsWith('--url=')) urlFlag = a.slice(6);
else if (a === '--bail') opts.bail = true;
else if (a.startsWith('--retry=')) opts.retry = parseInt(a.slice(8)) || 0;
else if (a.startsWith('--timeout=')) opts.timeout = parseInt(a.slice(10)) || 30000;
@@ -36,20 +37,28 @@ export async function cmdTest(rawArgs) {
else if (!a.startsWith('--')) positional.push(a);
}
// Determine URL and test path
let url, testPath;
if (positional.length === 2) {
url = positional[0];
testPath = resolve(positional[1]);
} else if (positional.length === 1) {
testPath = resolve(positional[0]);
} else {
die('Usage: node run.mjs test [url] <dir|file> [--tags=...] [--bail] [--retry=N] [--timeout=ms] [--report=path]');
// Positional args are ALWAYS test paths (one or many). URL comes from --url= or config
// (see webtest.config.mjs). This matches pytest/jest/playwright; a positional that looks
// like a URL is a mistake → fail fast with a hint instead of feeding it to page.goto().
const isUrl = (s) => /^https?:\/\//i.test(s);
let url = urlFlag || null;
const testPaths = [...positional];
if (testPaths.length === 0) {
die('Usage: node run.mjs test <dir|file>... [--url=URL] [--tags=...] [--grep=...] [--bail] [--retry=N] [--timeout=ms] [--report=path]');
}
for (const p of testPaths) {
if (existsSync(resolve(p))) continue;
if (isUrl(p)) {
die(`"${p}" looks like a URL — use --url=<url>; positional args are test paths.`);
}
die(`Test path not found: "${p}". To run a subset use --grep= / --tags=, or pass an existing dir/file.`);
}
// Load config if exists
const isFile = testPath.endsWith('.test.mjs');
const testDir = isFile ? dirname(testPath) : testPath;
// Load config if exists. config (webtest.config.mjs) and hooks (_hooks.mjs) resolve from
// the FIRST path's directory — list paths from the same suite folder.
const firstPath = resolve(testPaths[0]);
const isFile = firstPath.endsWith('.test.mjs');
const testDir = isFile ? dirname(firstPath) : firstPath;
const configPath = resolve(testDir, 'webtest.config.mjs');
let config = {};
if (existsSync(configPath)) {
@@ -110,8 +119,8 @@ export async function cmdTest(rawArgs) {
}
// Discover test files
const testFiles = discoverTests(testPath);
if (!testFiles.length) die(`No *.test.mjs files found in ${testPath}`);
const testFiles = discoverTests(testPaths);
if (!testFiles.length) die(`No *.test.mjs files found in ${testPaths.join(', ')}`);
// Import and filter tests
const tests = [];
@@ -1,10 +1,14 @@
// web-test cli/test-runner/discover v1.0 — test file discovery + state reset between tests
// web-test cli/test-runner/discover v1.1 — test file discovery + state reset between tests
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import { existsSync, readdirSync } from 'fs';
import { resolve } from 'path';
export function discoverTests(testPath) {
if (testPath.endsWith('.test.mjs')) return existsSync(testPath) ? [testPath] : [];
// Accepts a single path or an array of paths (files and/or dirs). Each .test.mjs file is
// taken directly; each directory is walked recursively (skipping _ / . prefixes). Results
// are deduped and sorted — sorting preserves the numeric-prefix order the suite relies on
// (00-, 01-, …) even when paths are listed out of order.
export function discoverTests(testPaths) {
const paths = Array.isArray(testPaths) ? testPaths : [testPaths];
const files = [];
function walk(dir) {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
@@ -14,8 +18,15 @@ export function discoverTests(testPath) {
else if (entry.name.endsWith('.test.mjs')) files.push(full);
}
}
walk(testPath);
return files.sort();
for (const p of paths) {
const full = resolve(p);
if (full.endsWith('.test.mjs')) {
if (existsSync(full)) files.push(full);
} else if (existsSync(full)) {
walk(full);
}
}
return [...new Set(files)].sort();
}
export async function resetState(ctx) {
+3 -2
View File
@@ -1,4 +1,4 @@
// web-test cli/util v1.1 — generic helpers for CLI commands
// web-test cli/util v1.2 — generic helpers for CLI commands
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
export function out(obj) {
@@ -85,7 +85,7 @@ Commands:
shot [file] Take screenshot (default: shot.png)
stop Logout and close browser
status Check session status
test [url] <dir|file> Run regression tests (*.test.mjs)
test <dir|file>... Run regression tests (*.test.mjs); accepts multiple paths
Options for exec:
--no-record Skip video recording (record() becomes no-op)
@@ -95,6 +95,7 @@ Global options (any command):
Default: on (env: WEB_TEST_PRESERVE_CLIPBOARD=0 to disable globally).
Options for test:
--url=URL Override the base URL (default: from webtest.config.mjs)
--tags=smoke,crud Filter tests by tags
--grep=pattern Filter tests by name (regex)
--bail Stop on first failure
+2 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node
// web-test run v1.17 — CLI entry-point (распилено по cli/)
// web-test run v1.18 — CLI entry-point (распилено по cli/)
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
/**
* CLI runner for 1C web client automation.
@@ -14,7 +14,7 @@
* node src/run.mjs shot [file] — take screenshot
* node src/run.mjs stop — logout + close browser
* node src/run.mjs status — check session
* node src/run.mjs test [url] <dir|file> — run regression tests
* node src/run.mjs test <dir|file>... [--url] — run regression tests
*
* Внутренности живут в cli/: util, session, exec-context, server,
* commands/{start,run,exec,shot,stop,status,test}, test-runner/*.