diff --git a/.claude/skills/web-test/regress.md b/.claude/skills/web-test/regress.md index 92cd9832..5f8e058e 100644 --- a/.claude/skills/web-test/regress.md +++ b/.claude/skills/web-test/regress.md @@ -366,10 +366,11 @@ node $RUN test tests// --tags=smoke # by tag node $RUN test tests// --grep='накладн' # by name regex node $RUN test tests// --bail --retry=1 # stop on first fail, allow 1 retry node $RUN test tests// --report=allure-results --format=allure --report-dir=allure-results +node $RUN test tests// --report=- # machine JSON to stdout, progress to stderr node $RUN test tests// -- --rebuild-stand # after `--` → hookArgs ``` -Default report is JSON when `--report=…` is given. Allure needs `--format=allure` + a directory. JUnit similarly with `--format=junit`. +**Output contract.** `test` behaves like a test runner: by default the human report (with the summary as the last line) goes to **stdout** — read the tail of stdout + exit code. The machine report is opt-in via `--report`: `--report=path` writes it to a file (default JSON; XML for `--format=junit`), `--report=-` writes it to stdout while progress moves to stderr. Allure needs `--format=allure` + a directory (`-` is invalid for allure). For detailed triage use `--report=path` or `--report=-`. **In `--report=-` mode never use `2>&1`** — it merges stderr progress into the stdout JSON. (In the default mode there is no JSON in stdout, so `… | tail` is safe.) ### Allure static config — `_allure/` diff --git a/.claude/skills/web-test/scripts/cli/commands/test.mjs b/.claude/skills/web-test/scripts/cli/commands/test.mjs index 3eed600f..e44dc089 100644 --- a/.claude/skills/web-test/scripts/cli/commands/test.mjs +++ b/.claude/skills/web-test/scripts/cli/commands/test.mjs @@ -1,4 +1,4 @@ -// web-test cli/commands/test v1.0 — regression test runner +// web-test cli/commands/test v1.1 — 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'; @@ -96,9 +96,15 @@ export async function cmdTest(rawArgs) { if (opts.format === 'junit' && !opts.report) { die('--format=junit requires --report=path.xml'); } + // `--report=-` means "machine report to stdout" (Unix `-` convention). + // Only meaningful for streamable formats (json/junit); allure is a directory. + const reportToStdout = opts.report === '-'; + if (reportToStdout && opts.format === 'allure') { + die('--report=- (stdout) is not valid with --format=allure: allure emits a directory of files, not a single stream. Use --report-dir= instead.'); + } const reportDir = opts.reportDir ? resolve(opts.reportDir) - : (opts.report ? dirname(resolve(opts.report)) : testDir); + : (opts.report && !reportToStdout ? dirname(resolve(opts.report)) : testDir); if (opts.screenshot !== 'off') { try { mkdirSync(reportDir, { recursive: true }); } catch {} } @@ -154,8 +160,9 @@ export async function cmdTest(rawArgs) { hooks = await import('file:///' + hooksPath.replace(/\\/g, '/')); } - // Console header - const W = process.stderr; + // Human-readable report goes to stdout (test-runner convention: jest/pytest/playwright). + // In `--report -` mode the machine JSON/XML takes over stdout, so progress moves to stderr. + const W = reportToStdout ? process.stderr : process.stdout; W.write(`\nweb-test -- ${url}\n`); W.write(`Running ${filtered.length} tests from ${relative(process.cwd(), testDir).replace(/\\/g, '/') || '.'}/\n\n`); @@ -418,13 +425,14 @@ export async function cmdTest(rawArgs) { summary: { total: results.length, passed: passCount, failed: failCount, skipped: skipCount }, tests: results, }; - out(report); - if (opts.format === 'allure') { writeAllure(results, reportDir, severityIndex); syncAllureExtras(testDir, reportDir); } else if (opts.format === 'junit') { - writeFileSync(resolve(opts.report), buildJUnit(report, testDir)); + if (reportToStdout) process.stdout.write(buildJUnit(report, testDir) + '\n'); + else writeFileSync(resolve(opts.report), buildJUnit(report, testDir)); + } else if (reportToStdout) { + out(report); } else if (opts.report) { writeFileSync(resolve(opts.report), JSON.stringify(report, null, 2)); } diff --git a/.claude/skills/web-test/scripts/cli/util.mjs b/.claude/skills/web-test/scripts/cli/util.mjs index c24a52e6..0bc1c913 100644 --- a/.claude/skills/web-test/scripts/cli/util.mjs +++ b/.claude/skills/web-test/scripts/cli/util.mjs @@ -1,4 +1,4 @@ -// web-test cli/util v1.0 — generic helpers for CLI commands +// web-test cli/util v1.1 — generic helpers for CLI commands // Source: https://github.com/Nikolay-Shirokov/cc-1c-skills export function out(obj) { @@ -100,7 +100,8 @@ Options for test: --bail Stop on first failure --retry=N Retry failed tests N times --timeout=ms Per-test timeout (default: 30000) - --report=path Write JSON report to file + --report=path Write machine report (JSON/JUnit) to file + --report=- Write machine report to stdout (progress moves to stderr) --report-dir=path Directory for screenshots and other artifacts --screenshot=mode on-failure (default) | every-step | off --format=fmt json (default) | allure | junit diff --git a/docs/web-test-regression-guide.md b/docs/web-test-regression-guide.md index 046d7edf..772b5470 100644 --- a/docs/web-test-regression-guide.md +++ b/docs/web-test-regression-guide.md @@ -241,9 +241,9 @@ export default async function({ ✗ Проведение приходной накладной (12.7s) └ Заполнить табличную часть (5.2s) Не найден столбец "Цена" в табличной части "Товары" - скриншот: tests/учёт-поступлений/error-shot.png + screenshot: tests/учёт-поступлений/error-shot.png -23 пройдено, 1 упал, 0 пропущено (3 мин 42 с) +23 passed, 1 failed, 0 skipped (3m 42s) ``` ### Подробный отчёт diff --git a/docs/web-test-regression-spec.md b/docs/web-test-regression-spec.md index 806732c9..35a745a6 100644 --- a/docs/web-test-regression-spec.md +++ b/docs/web-test-regression-spec.md @@ -22,7 +22,8 @@ node run.mjs test [url] [флаги] | `--bail` | false | Остановиться при первом падении | | `--retry=N` | 0 | Повторить упавшие тесты N раз | | `--timeout=ms` | 30000 | Таймаут на тест (мс) | -| `--report=path` | (нет) | Записать отчёт в файл (JSON или XML для `--format=junit`) | +| `--report=path` | (нет) | Записать машинный отчёт в файл (JSON или XML для `--format=junit`) | +| `--report=-` | (нет) | Машинный отчёт в stdout (`-` = stdout); человеческий прогресс уходит в stderr | | `--format=fmt` | json | Формат отчёта: `json` / `allure` / `junit` | | `--report-dir=path` | dirname(report) / testDir | Каталог для скриншотов, видео, Allure-результатов | | `--screenshot=strategy` | on-failure | `on-failure` / `every-step` / `off` | @@ -35,7 +36,20 @@ URL необязателен, если в каталоге тестов есть - `--screenshot=` принимается только `on-failure | every-step | off`; при невалидном значении движок выводит ошибку и завершается с ненулевым кодом до старта прогона. - `--format=` принимается только `json | allure | junit`; иначе — завершение с ошибкой. -- `--format=junit` требует `--report=` (иначе некуда писать XML); иначе — завершение с ошибкой. +- `--format=junit` требует `--report=` (иначе некуда писать XML); иначе — завершение с ошибкой. Значение `-` (stdout) для junit допустимо. +- `--report=-` (stdout) несовместимо с `--format=allure`: allure пишет каталог, а не поток; иначе — завершение с ошибкой. + +### Потоки вывода (stdout / stderr) + +`test` ведёт себя как тест-раннер (jest/pytest/playwright): человеческий отчёт со сводкой в конце идёт в **stdout**. Машинный отчёт (JSON/JUnit) включается отдельно флагом `--report`. + +| Запуск | stdout | stderr | Файл | +|--------|--------|--------|------| +| `test …` (дефолт) | человеческий отчёт, **сводка последней строкой** | — | — | +| `test … --report=file` | человеческий отчёт (виден прогресс + сводка) | — | JSON/JUnit в файл | +| `test … --report=-` | **чистый машинный отчёт** (JSON или JUnit-XML) | человеческий прогресс | — | +| `--format=allure …` | человеческий отчёт | — | артефакты allure в каталоге | +| любой | — | — | exit code **0/1** всегда | ### Режим выполнения @@ -753,9 +767,11 @@ await step('Кладовщик проверяет статус', async () => { ## 10. Консольный вывод +Вывод — на английском (статусы `passed/failed/skipped` зеркалят ключи JSON-отчёта и Allure/JUnit): + ``` -web-test — http://localhost/app/ru_RU -Запуск 25 тестов из tests/myapp/ +web-test -- http://localhost/app/ru_RU +Running 25 tests from tests/myapp/ ✓ Навигация по разделам (2.1s) ✓ CRUD справочника Контрагенты (12.3s) @@ -766,13 +782,15 @@ web-test — http://localhost/app/ru_RU ├ Открыть форму (2.0s) └ ✗ Сохранить пустую форму (6.1s) Ожидалось модальное окно ошибки, но форма сохранилась - скриншот: error-shot-10.png + screenshot: error-shot-10.png ○ Составной тип (skip: не реализовано) 23 passed, 1 failed, 1 skipped (2m 0.5s) ``` -Для passed-тестов выводится одна строка `✓ name (duration)`. Шаги печатаются только для упавших — после строки `✗`, с отступом, плюс сообщение ошибки и путь к скриншоту. Полная картина по шагам — в JSON-отчёте (`--report=…`). +Для passed-тестов выводится одна строка `✓ name (duration)`. Шаги печатаются только для упавших — после строки `✗`, с отступом, плюс сообщение ошибки и путь к скриншоту (`screenshot:`). Полная картина по шагам — в машинном отчёте (`--report=…` или `--report=-`). + +По умолчанию этот отчёт идёт в **stdout** и заканчивается строкой сводки (`N passed, M failed, K skipped (Xs)`) — модель читает хвост stdout + exit code. В режиме `--report=-` (Unix-конвенция `-` = stdout) stdout занимает чистый машинный отчёт (JSON/JUnit), а человеческий прогресс уходит в stderr. --- diff --git a/tests/web-test/README.md b/tests/web-test/README.md index 9a494e59..3fc0226b 100644 --- a/tests/web-test/README.md +++ b/tests/web-test/README.md @@ -31,7 +31,8 @@ Exit code: 0 = все прошли, 1 = есть падения. | `--bail` | Остановиться на первой ошибке | | `--retry=N` | Перепрогон упавших тестов N раз | | `--timeout=ms` | Таймаут одного теста (default 30000) | -| `--report=path` | Сохранить отчёт в файл | +| `--report=path` | Сохранить машинный отчёт в файл | +| `--report=-` | Машинный отчёт в stdout (прогресс → stderr) | | `--format=json\|allure\|junit` | Формат отчёта | | `--report-dir=path` | Корень для Allure/JUnit артефактов | | `--screenshot=on-failure\|every-step\|off` | Когда снимать скриншоты |