mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-15 02:14:57 +03:00
feat(web-test): _allure/ конвенция + categories.json для триажа падений
run.mjs: - syncAllureExtras(testDir, reportDir) копирует все файлы из <testDir>/_allure/ в reportDir перед генерацией отчёта. Underscore в имени параллелен _hooks.mjs (инфра, не тест) — discovery его пропускает. - Вызов после writeAllure при --format=allure. tests/web-test/_allure/categories.json — 7 правил классификации падений по нашему 1С-домену: 1. License pool exhausted (1C) — известный multi-context flake. 2. 1C application error (modal) — exception modal через fetchErrorStack. 3. Section panel icon-only — деградация состояния стенда. 4. Navigation lookup miss — navigateSection/openCommand/navigateLink/switchTab. 5. Element not found — clickElement/fillField/selectValue/closeForm/fillTableRow/deleteTableRow. 6. Test timeout — Timeout (Nms) от раннера. 7. Assertion failure — наши createAssertions + 1С-specific (formHasField/tableHasRow/noErrors). spec §9: раздел «Доп. файлы Allure через <testDir>/_allure/» с таблицей поддерживаемых типов (categories.json / environment.properties / executor.json) и минимальным примером. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
import http from 'http';
|
||||
import * as browser from './browser.mjs';
|
||||
import { readFileSync, writeFileSync, unlinkSync, existsSync, readdirSync, mkdirSync } from 'fs';
|
||||
import { readFileSync, writeFileSync, unlinkSync, existsSync, readdirSync, mkdirSync, copyFileSync, statSync } from 'fs';
|
||||
import { resolve, dirname, basename, relative } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { randomUUID } from 'crypto';
|
||||
@@ -821,6 +821,7 @@ async function cmdTest(rawArgs) {
|
||||
|
||||
if (opts.format === 'allure') {
|
||||
writeAllure(results, reportDir, severityIndex);
|
||||
syncAllureExtras(testDir, reportDir);
|
||||
} else if (opts.format === 'junit') {
|
||||
writeFileSync(resolve(opts.report), buildJUnit(report, testDir));
|
||||
} else if (opts.report) {
|
||||
@@ -830,6 +831,28 @@ async function cmdTest(rawArgs) {
|
||||
if (failCount > 0) process.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy any files from `<testDir>/_allure/` into `reportDir`. Convention for
|
||||
* Allure customization that doesn't fit inside per-test JSON:
|
||||
* - `categories.json` — failure classification (regex → bucket)
|
||||
* - `environment.properties` — values shown in the Environment widget
|
||||
* - `executor.json` — CI/CD metadata
|
||||
* Underscored folder mirrors `_hooks.mjs` convention (infra, not a test).
|
||||
* Silent if folder absent.
|
||||
*/
|
||||
function syncAllureExtras(testDir, reportDir) {
|
||||
const extrasDir = resolve(testDir, '_allure');
|
||||
if (!existsSync(extrasDir)) return;
|
||||
try {
|
||||
if (!statSync(extrasDir).isDirectory()) return;
|
||||
} catch { return; }
|
||||
for (const entry of readdirSync(extrasDir, { withFileTypes: true })) {
|
||||
if (!entry.isFile()) continue;
|
||||
try { copyFileSync(resolve(extrasDir, entry.name), resolve(reportDir, entry.name)); }
|
||||
catch { /* best-effort */ }
|
||||
}
|
||||
}
|
||||
|
||||
function writeAllure(results, reportDir, severityIndex) {
|
||||
for (const tr of results) {
|
||||
if (tr.status === 'skipped') continue; // Allure ignores skipped without start/stop
|
||||
|
||||
@@ -752,6 +752,32 @@ await step('Кладовщик проверяет статус', async () => {
|
||||
|
||||
Пример: `tags: ['smoke', 'recording']` + `severity: { critical: ['smoke'], minor: ['recording'] }` → severity = `critical` (5 > 2).
|
||||
|
||||
#### Доп. файлы Allure через `<testDir>/_allure/`
|
||||
|
||||
Раннер ищет каталог `_allure/` рядом с тестами и копирует все его файлы в
|
||||
`reportDir` перед генерацией отчёта. Конвенция для статичной настройки
|
||||
Allure, которой нет места внутри per-test JSON:
|
||||
|
||||
| Файл | Назначение |
|
||||
|------|-----------|
|
||||
| `categories.json` | Классификация падений по regex (группировка failed-тестов в виджете Categories — «timeout», «license-flake», «1C modal» etc.) |
|
||||
| `environment.properties` | `key=value` строки в виджет Environment (URL, версия 1С, ветка git, build-номер) |
|
||||
| `executor.json` | CI/CD-метаданные (Jenkins URL, GitHub run-id и т.п.) |
|
||||
|
||||
Underscore в имени — параллель `_hooks.mjs` (инфраструктура, не тест).
|
||||
Discovery каталог `_allure/` пропускает по общему правилу (`startsWith('_')`).
|
||||
Если каталога нет — no-op.
|
||||
|
||||
Пример `categories.json` (минимальный):
|
||||
```json
|
||||
[
|
||||
{ "name": "Timeout", "messageRegex": "Timeout \\(\\d+ms\\)" },
|
||||
{ "name": "Assertion", "messageRegex": "(Expected|AssertionError).*" }
|
||||
]
|
||||
```
|
||||
|
||||
Полный пример с 1С-специфичными паттернами — см. `tests/web-test/_allure/categories.json`.
|
||||
|
||||
### JUnit XML (`--format=junit`)
|
||||
|
||||
```xml
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
[
|
||||
{
|
||||
"name": "License pool exhausted (1C)",
|
||||
"matchedStatuses": ["failed", "broken"],
|
||||
"messageRegex": ".*Не обнаружено свободной лицензии.*"
|
||||
},
|
||||
{
|
||||
"name": "1C application error (modal)",
|
||||
"matchedStatuses": ["failed"],
|
||||
"messageRegex": ".*(ВызватьИсключение|В поле введены некорректные данные|Произошла ошибка|Ошибка при вызове).*"
|
||||
},
|
||||
{
|
||||
"name": "Section panel icon-only (stand state)",
|
||||
"matchedStatuses": ["failed"],
|
||||
"messageRegex": ".*icon-only mode.*"
|
||||
},
|
||||
{
|
||||
"name": "Navigation lookup miss",
|
||||
"matchedStatuses": ["failed"],
|
||||
"messageRegex": ".*(navigateSection|openCommand|navigateLink|switchTab).*not found.*"
|
||||
},
|
||||
{
|
||||
"name": "Element not found",
|
||||
"matchedStatuses": ["failed"],
|
||||
"messageRegex": ".*(clickElement|fillField|fillFields|selectValue|closeForm|fillTableRow|deleteTableRow).*not found.*"
|
||||
},
|
||||
{
|
||||
"name": "Test timeout",
|
||||
"matchedStatuses": ["failed", "broken"],
|
||||
"messageRegex": "Timeout \\(\\d+ms\\)"
|
||||
},
|
||||
{
|
||||
"name": "Assertion failure",
|
||||
"matchedStatuses": ["failed"],
|
||||
"messageRegex": "(Expected|AssertionError|Field \".*\" not found in form|Form title .*does not contain|No row matching predicate|Form has errors).*"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user