donobu 5.26.0 → 5.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/donobu-cli.js +203 -251
- package/dist/codegen/CodeGenerator.js +12 -16
- package/dist/esm/cli/donobu-cli.js +203 -251
- package/dist/esm/codegen/CodeGenerator.js +12 -16
- package/dist/esm/managers/DonobuFlowsManager.js +2 -1
- package/dist/esm/managers/TestsManager.js +2 -2
- package/dist/esm/models/CreateTest.d.ts +1 -1
- package/dist/esm/models/CreateTest.js +6 -0
- package/dist/esm/persistence/DonobuSqliteDb.js +102 -0
- package/dist/esm/persistence/TestConfigHash.d.ts +11 -0
- package/dist/esm/persistence/TestConfigHash.js +31 -0
- package/dist/esm/persistence/flows/FlowsPersistenceSqlite.d.ts +0 -9
- package/dist/esm/persistence/flows/FlowsPersistenceSqlite.js +4 -33
- package/dist/esm/persistence/normalizeFlowMetadata.d.ts +16 -0
- package/dist/esm/persistence/normalizeFlowMetadata.js +34 -0
- package/dist/esm/reporter/buildReport.d.ts +22 -0
- package/dist/esm/reporter/buildReport.js +106 -0
- package/dist/esm/reporter/html.d.ts +5 -9
- package/dist/esm/reporter/html.js +25 -101
- package/dist/esm/reporter/markdown.d.ts +33 -0
- package/dist/esm/reporter/markdown.js +62 -0
- package/dist/esm/reporter/merge.d.ts +33 -0
- package/dist/esm/reporter/merge.js +229 -0
- package/dist/esm/reporter/model.d.ts +101 -0
- package/dist/esm/reporter/model.js +27 -0
- package/dist/{cli/playwright-json-to-html.d.ts → esm/reporter/render.d.ts} +9 -14
- package/dist/esm/{cli/playwright-json-to-html.js → reporter/render.js} +52 -152
- package/dist/esm/reporter/renderMarkdown.d.ts +11 -0
- package/dist/esm/{cli/playwright-json-to-markdown.js → reporter/renderMarkdown.js} +31 -110
- package/dist/esm/reporter/renderSlack.d.ts +17 -0
- package/dist/esm/reporter/renderSlack.js +100 -0
- package/dist/esm/reporter/reportWalk.d.ts +28 -0
- package/dist/esm/reporter/reportWalk.js +61 -0
- package/dist/esm/reporter/slack.d.ts +93 -0
- package/dist/esm/reporter/slack.js +150 -0
- package/dist/esm/reporter/stateFile.d.ts +31 -0
- package/dist/esm/reporter/stateFile.js +70 -0
- package/dist/esm/tools/AssertPageTool.d.ts +2 -2
- package/dist/esm/utils/MiscUtils.d.ts +0 -13
- package/dist/esm/utils/MiscUtils.js +0 -21
- package/dist/esm/utils/displayName.d.ts +16 -0
- package/dist/esm/utils/displayName.js +28 -0
- package/dist/managers/DonobuFlowsManager.js +2 -1
- package/dist/managers/TestsManager.js +2 -2
- package/dist/models/CreateTest.d.ts +1 -1
- package/dist/models/CreateTest.js +6 -0
- package/dist/persistence/DonobuSqliteDb.js +102 -0
- package/dist/persistence/TestConfigHash.d.ts +11 -0
- package/dist/persistence/TestConfigHash.js +31 -0
- package/dist/persistence/flows/FlowsPersistenceSqlite.d.ts +0 -9
- package/dist/persistence/flows/FlowsPersistenceSqlite.js +4 -33
- package/dist/persistence/normalizeFlowMetadata.d.ts +16 -0
- package/dist/persistence/normalizeFlowMetadata.js +34 -0
- package/dist/reporter/buildReport.d.ts +22 -0
- package/dist/reporter/buildReport.js +106 -0
- package/dist/reporter/html.d.ts +5 -9
- package/dist/reporter/html.js +25 -101
- package/dist/reporter/markdown.d.ts +33 -0
- package/dist/reporter/markdown.js +62 -0
- package/dist/reporter/merge.d.ts +33 -0
- package/dist/reporter/merge.js +229 -0
- package/dist/reporter/model.d.ts +101 -0
- package/dist/reporter/model.js +27 -0
- package/dist/{esm/cli/playwright-json-to-html.d.ts → reporter/render.d.ts} +9 -14
- package/dist/{cli/playwright-json-to-html.js → reporter/render.js} +52 -152
- package/dist/reporter/renderMarkdown.d.ts +11 -0
- package/dist/{cli/playwright-json-to-markdown.js → reporter/renderMarkdown.js} +31 -110
- package/dist/reporter/renderSlack.d.ts +17 -0
- package/dist/reporter/renderSlack.js +100 -0
- package/dist/reporter/reportWalk.d.ts +28 -0
- package/dist/reporter/reportWalk.js +61 -0
- package/dist/reporter/slack.d.ts +93 -0
- package/dist/reporter/slack.js +150 -0
- package/dist/reporter/stateFile.d.ts +31 -0
- package/dist/reporter/stateFile.js +70 -0
- package/dist/tools/AssertPageTool.d.ts +2 -2
- package/dist/utils/MiscUtils.d.ts +0 -13
- package/dist/utils/MiscUtils.js +0 -21
- package/dist/utils/displayName.d.ts +16 -0
- package/dist/utils/displayName.js +28 -0
- package/package.json +11 -5
- package/dist/cli/playwright-json-to-markdown.d.ts +0 -43
- package/dist/cli/playwright-json-to-slack-json.d.ts +0 -3
- package/dist/cli/playwright-json-to-slack-json.js +0 -214
- package/dist/esm/cli/playwright-json-to-markdown.d.ts +0 -43
- package/dist/esm/cli/playwright-json-to-slack-json.d.ts +0 -3
- package/dist/esm/cli/playwright-json-to-slack-json.js +0 -214
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Donobu Markdown Reporter for Playwright.
|
|
3
|
+
*
|
|
4
|
+
* Writes a Markdown summary of the run to disk. Pairs with the
|
|
5
|
+
* GitHub Actions step-summary pattern: the CI job just cats the file into
|
|
6
|
+
* `$GITHUB_STEP_SUMMARY`.
|
|
7
|
+
*
|
|
8
|
+
* @usage
|
|
9
|
+
* ```ts
|
|
10
|
+
* // playwright.config.ts
|
|
11
|
+
* reporter: [
|
|
12
|
+
* ['donobu/reporter/markdown', { outputFile: 'test-results/report.md' }],
|
|
13
|
+
* ],
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* During an auto-heal rerun (`DONOBU_AUTO_HEAL_ACTIVE=1`) the reporter skips
|
|
17
|
+
* file writes. It always merges its output entry into the shared state file
|
|
18
|
+
* so the orchestrator knows to regenerate the markdown from the merged report.
|
|
19
|
+
*/
|
|
20
|
+
import type { FullResult, Reporter, TestCase, TestResult } from '@playwright/test/reporter';
|
|
21
|
+
export interface DonobuMarkdownReporterOptions {
|
|
22
|
+
/** Path to write the Markdown report. Defaults to `test-results/report.md`. */
|
|
23
|
+
outputFile?: string;
|
|
24
|
+
}
|
|
25
|
+
export default class DonobuMarkdownReporter implements Reporter {
|
|
26
|
+
private readonly options;
|
|
27
|
+
private readonly resultsByTest;
|
|
28
|
+
constructor(options?: DonobuMarkdownReporterOptions);
|
|
29
|
+
onTestEnd(test: TestCase, result: TestResult): void;
|
|
30
|
+
onEnd(_result: FullResult): Promise<void>;
|
|
31
|
+
printsToStdio(): boolean;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Donobu Markdown Reporter for Playwright.
|
|
4
|
+
*
|
|
5
|
+
* Writes a Markdown summary of the run to disk. Pairs with the
|
|
6
|
+
* GitHub Actions step-summary pattern: the CI job just cats the file into
|
|
7
|
+
* `$GITHUB_STEP_SUMMARY`.
|
|
8
|
+
*
|
|
9
|
+
* @usage
|
|
10
|
+
* ```ts
|
|
11
|
+
* // playwright.config.ts
|
|
12
|
+
* reporter: [
|
|
13
|
+
* ['donobu/reporter/markdown', { outputFile: 'test-results/report.md' }],
|
|
14
|
+
* ],
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* During an auto-heal rerun (`DONOBU_AUTO_HEAL_ACTIVE=1`) the reporter skips
|
|
18
|
+
* file writes. It always merges its output entry into the shared state file
|
|
19
|
+
* so the orchestrator knows to regenerate the markdown from the merged report.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
const fs_1 = require("fs");
|
|
23
|
+
const path_1 = require("path");
|
|
24
|
+
const buildReport_1 = require("./buildReport");
|
|
25
|
+
const renderMarkdown_1 = require("./renderMarkdown");
|
|
26
|
+
const stateFile_1 = require("./stateFile");
|
|
27
|
+
class DonobuMarkdownReporter {
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
this.resultsByTest = new Map();
|
|
30
|
+
this.options = options;
|
|
31
|
+
}
|
|
32
|
+
onTestEnd(test, result) {
|
|
33
|
+
const existing = this.resultsByTest.get(test);
|
|
34
|
+
if (existing) {
|
|
35
|
+
existing.push(result);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
this.resultsByTest.set(test, [result]);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async onEnd(_result) {
|
|
42
|
+
const outputFile = (0, path_1.resolve)(this.options.outputFile ?? 'test-results/report.md');
|
|
43
|
+
const outputDir = (0, path_1.dirname)(outputFile);
|
|
44
|
+
const autoHealActive = process.env.DONOBU_AUTO_HEAL_ACTIVE === '1';
|
|
45
|
+
const report = (0, buildReport_1.buildDonobuReport)(this.resultsByTest);
|
|
46
|
+
(0, stateFile_1.mergeStateFileEntry)(process.env.PLAYWRIGHT_JSON_OUTPUT_DIR, report, {
|
|
47
|
+
markdown: { outputFile },
|
|
48
|
+
});
|
|
49
|
+
if (autoHealActive) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const markdown = (0, renderMarkdown_1.renderMarkdown)(report);
|
|
53
|
+
(0, fs_1.mkdirSync)(outputDir, { recursive: true });
|
|
54
|
+
(0, fs_1.writeFileSync)(outputFile, markdown, 'utf8');
|
|
55
|
+
console.error(`Donobu Markdown report written to ${outputFile}`);
|
|
56
|
+
}
|
|
57
|
+
printsToStdio() {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.default = DonobuMarkdownReporter;
|
|
62
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pure in-memory merge of two `DonobuReport`s.
|
|
3
|
+
*
|
|
4
|
+
* Combines the initial Playwright run and an auto-heal rerun into a single
|
|
5
|
+
* report that carries both attempts and annotates any test whose outcome
|
|
6
|
+
* flipped from failing → passing as `self-healed`. The result is destined for
|
|
7
|
+
* the HTML renderer (and, for back-compat, the on-disk Playwright JSON that
|
|
8
|
+
* downstream dashboards consume).
|
|
9
|
+
*
|
|
10
|
+
* This module intentionally owns zero I/O. Callers load the reports off disk,
|
|
11
|
+
* call `mergeReports`, then persist / relocate attachments / re-render as
|
|
12
|
+
* needed. Keeping the merge pure makes it trivial to test and reason about.
|
|
13
|
+
*/
|
|
14
|
+
import type { DonobuReport, HealedTestDescriptor } from './model';
|
|
15
|
+
export interface MergeReportsParams {
|
|
16
|
+
/** Pre-loaded initial report, or null when the initial run produced none. */
|
|
17
|
+
initialReport: DonobuReport | null;
|
|
18
|
+
/** Pre-loaded heal-run report, or null when the heal run produced none. */
|
|
19
|
+
healReport: DonobuReport | null;
|
|
20
|
+
/** Tests the orchestrator declared healed — used when the heal report
|
|
21
|
+
* doesn't expose them under a matching key (e.g. filter rewrites). */
|
|
22
|
+
healedTests: HealedTestDescriptor[];
|
|
23
|
+
/** Whether the heal rerun exited cleanly. */
|
|
24
|
+
healSucceeded: boolean;
|
|
25
|
+
/** Recorded in the merged report metadata for triage auto-discovery. */
|
|
26
|
+
triageRunDir?: string;
|
|
27
|
+
/** Recorded in the merged report metadata purely for provenance. */
|
|
28
|
+
initialReportSourcePath?: string;
|
|
29
|
+
healReportSourcePath?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function mergeReports(params: MergeReportsParams): DonobuReport | null;
|
|
32
|
+
export declare function buildTestKey(file?: string, projectName?: string, title?: string): string;
|
|
33
|
+
//# sourceMappingURL=merge.d.ts.map
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Pure in-memory merge of two `DonobuReport`s.
|
|
4
|
+
*
|
|
5
|
+
* Combines the initial Playwright run and an auto-heal rerun into a single
|
|
6
|
+
* report that carries both attempts and annotates any test whose outcome
|
|
7
|
+
* flipped from failing → passing as `self-healed`. The result is destined for
|
|
8
|
+
* the HTML renderer (and, for back-compat, the on-disk Playwright JSON that
|
|
9
|
+
* downstream dashboards consume).
|
|
10
|
+
*
|
|
11
|
+
* This module intentionally owns zero I/O. Callers load the reports off disk,
|
|
12
|
+
* call `mergeReports`, then persist / relocate attachments / re-render as
|
|
13
|
+
* needed. Keeping the merge pure makes it trivial to test and reason about.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.mergeReports = mergeReports;
|
|
17
|
+
exports.buildTestKey = buildTestKey;
|
|
18
|
+
function mergeReports(params) {
|
|
19
|
+
const { initialReport, healReport } = params;
|
|
20
|
+
if (!initialReport && !healReport) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
// Clone so we never mutate either input.
|
|
24
|
+
const combined = JSON.parse(JSON.stringify(initialReport ?? healReport));
|
|
25
|
+
const initialIndex = indexReport(initialReport);
|
|
26
|
+
const combinedIndex = indexReport(combined);
|
|
27
|
+
const healIndex = indexReport(healReport);
|
|
28
|
+
const healedKeys = new Set();
|
|
29
|
+
if (healReport) {
|
|
30
|
+
const processedHealEntries = new Set();
|
|
31
|
+
const processHealEntry = (healEntry) => {
|
|
32
|
+
const key = buildTestKey(healEntry.suite.file, healEntry.test.projectName, healEntry.test.title);
|
|
33
|
+
let combinedEntry = (healEntry.test.testId
|
|
34
|
+
? combinedIndex.byId.get(healEntry.test.testId)
|
|
35
|
+
: undefined) ??
|
|
36
|
+
combinedIndex.byKey.get(key) ??
|
|
37
|
+
null;
|
|
38
|
+
if (!combinedEntry) {
|
|
39
|
+
combinedEntry = insertTestIntoReport(combined, healEntry);
|
|
40
|
+
if (healEntry.test.testId) {
|
|
41
|
+
combinedIndex.byId.set(healEntry.test.testId, combinedEntry);
|
|
42
|
+
}
|
|
43
|
+
combinedIndex.byKey.set(key, combinedEntry);
|
|
44
|
+
}
|
|
45
|
+
const originalEntry = (healEntry.test.testId
|
|
46
|
+
? initialIndex.byId.get(healEntry.test.testId)
|
|
47
|
+
: undefined) ??
|
|
48
|
+
initialIndex.byKey.get(key) ??
|
|
49
|
+
null;
|
|
50
|
+
const combinedTest = combinedEntry.test;
|
|
51
|
+
if (healEntry.test.results?.length) {
|
|
52
|
+
combinedTest.results = [
|
|
53
|
+
...(combinedTest.results ?? []),
|
|
54
|
+
...healEntry.test.results,
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
if (healEntry.test.status !== undefined) {
|
|
58
|
+
combinedTest.status = healEntry.test.status;
|
|
59
|
+
}
|
|
60
|
+
if (healEntry.test.outcome !== undefined) {
|
|
61
|
+
combinedTest.outcome = healEntry.test.outcome;
|
|
62
|
+
}
|
|
63
|
+
const originalStatus = originalEntry
|
|
64
|
+
? getFinalResultStatus(originalEntry.test)
|
|
65
|
+
: undefined;
|
|
66
|
+
const healStatus = getFinalResultStatus(healEntry.test);
|
|
67
|
+
if (healStatus === 'passed' &&
|
|
68
|
+
originalStatus &&
|
|
69
|
+
originalStatus !== 'passed') {
|
|
70
|
+
combinedTest.annotations = combinedTest.annotations ?? [];
|
|
71
|
+
if (!combinedTest.annotations.some((annotation) => annotation.type === 'self-healed')) {
|
|
72
|
+
combinedTest.annotations.push({
|
|
73
|
+
type: 'self-healed',
|
|
74
|
+
description: 'Automatically healed by Donobu auto-heal rerun after applying treatment plan.',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
combinedTest.donobuStatus = 'healed';
|
|
78
|
+
healedKeys.add(key);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const iterateEntries = (entries) => {
|
|
82
|
+
for (const [, healEntry] of entries) {
|
|
83
|
+
if (processedHealEntries.has(healEntry)) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
processedHealEntries.add(healEntry);
|
|
87
|
+
processHealEntry(healEntry);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
iterateEntries(healIndex.byId);
|
|
91
|
+
iterateEntries(healIndex.byKey);
|
|
92
|
+
}
|
|
93
|
+
if (params.healSucceeded && healedKeys.size === 0) {
|
|
94
|
+
params.healedTests.forEach((descriptor) => {
|
|
95
|
+
// Descriptors must arrive with `testCase.file` already normalized to the
|
|
96
|
+
// same form that suite.file has in the reports — the caller owns path
|
|
97
|
+
// normalization because it has access to the CWD the run was launched in.
|
|
98
|
+
const key = buildTestKey(descriptor.testCase.file, descriptor.testCase.projectName, descriptor.testCase.title);
|
|
99
|
+
const entry = combinedIndex.byKey.get(key);
|
|
100
|
+
if (entry) {
|
|
101
|
+
entry.test.annotations = entry.test.annotations ?? [];
|
|
102
|
+
if (!entry.test.annotations.some((annotation) => annotation.type === 'self-healed')) {
|
|
103
|
+
entry.test.annotations.push({
|
|
104
|
+
type: 'self-healed',
|
|
105
|
+
description: 'Automatically healed by Donobu auto-heal rerun after applying treatment plan.',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
entry.test.donobuStatus = 'healed';
|
|
109
|
+
healedKeys.add(key);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
combined.stats = computeReportStats(combined);
|
|
114
|
+
const mergedMetadata = {
|
|
115
|
+
...(combined.metadata ?? {}),
|
|
116
|
+
donobuMergedReport: true,
|
|
117
|
+
mergedAtIso: new Date().toISOString(),
|
|
118
|
+
sources: {
|
|
119
|
+
initial: params.initialReportSourcePath ?? null,
|
|
120
|
+
autoHeal: params.healReportSourcePath ?? null,
|
|
121
|
+
},
|
|
122
|
+
...(params.triageRunDir ? { triageRunDir: params.triageRunDir } : {}),
|
|
123
|
+
donobuHealedTests: Array.from(healedKeys.values()),
|
|
124
|
+
};
|
|
125
|
+
combined.metadata = mergedMetadata;
|
|
126
|
+
return combined;
|
|
127
|
+
}
|
|
128
|
+
// Playwright does not reliably expose stable IDs across reports; fall back to
|
|
129
|
+
// a composite key. Exported so the orchestrator can build matching keys for
|
|
130
|
+
// heal descriptors when needed.
|
|
131
|
+
function buildTestKey(file, projectName, title) {
|
|
132
|
+
return [file ?? 'unknown-file', projectName ?? 'default', title ?? '']
|
|
133
|
+
.map((segment) => segment.toString())
|
|
134
|
+
.join('::');
|
|
135
|
+
}
|
|
136
|
+
function getFinalResultStatus(test) {
|
|
137
|
+
if (!test) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
return test.results?.at?.(-1)?.status ?? test.status;
|
|
141
|
+
}
|
|
142
|
+
function indexReport(report) {
|
|
143
|
+
const byId = new Map();
|
|
144
|
+
const byKey = new Map();
|
|
145
|
+
if (!report?.suites) {
|
|
146
|
+
return { byId, byKey };
|
|
147
|
+
}
|
|
148
|
+
report.suites.forEach((suite) => {
|
|
149
|
+
suite.specs?.forEach((spec) => {
|
|
150
|
+
spec.tests?.forEach((test) => {
|
|
151
|
+
const entry = { suite, spec, test };
|
|
152
|
+
if (test.testId) {
|
|
153
|
+
byId.set(test.testId, entry);
|
|
154
|
+
}
|
|
155
|
+
const key = buildTestKey(suite.file, test.projectName, test.title);
|
|
156
|
+
byKey.set(key, entry);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
return { byId, byKey };
|
|
161
|
+
}
|
|
162
|
+
function insertTestIntoReport(report, entry) {
|
|
163
|
+
const suites = report.suites ?? [];
|
|
164
|
+
let suite = suites.find((candidate) => candidate.file === entry.suite.file);
|
|
165
|
+
if (!suite) {
|
|
166
|
+
suite = JSON.parse(JSON.stringify(entry.suite));
|
|
167
|
+
suite.specs = [];
|
|
168
|
+
report.suites = [...suites, suite];
|
|
169
|
+
}
|
|
170
|
+
let spec = suite.specs.find((candidate) => candidate.title === entry.spec.title);
|
|
171
|
+
if (!spec) {
|
|
172
|
+
spec = JSON.parse(JSON.stringify(entry.spec));
|
|
173
|
+
spec.tests = [];
|
|
174
|
+
suite.specs.push(spec);
|
|
175
|
+
}
|
|
176
|
+
const testClone = JSON.parse(JSON.stringify(entry.test));
|
|
177
|
+
spec.tests.push(testClone);
|
|
178
|
+
return { suite, spec, test: testClone };
|
|
179
|
+
}
|
|
180
|
+
function computeReportStats(report) {
|
|
181
|
+
let expected = 0;
|
|
182
|
+
let unexpected = 0;
|
|
183
|
+
let skipped = 0;
|
|
184
|
+
let flaky = 0;
|
|
185
|
+
let total = 0;
|
|
186
|
+
let duration = 0;
|
|
187
|
+
if (!report?.suites) {
|
|
188
|
+
return report?.stats ?? {};
|
|
189
|
+
}
|
|
190
|
+
report.suites.forEach((suite) => {
|
|
191
|
+
suite.specs?.forEach((spec) => {
|
|
192
|
+
spec.tests?.forEach((test) => {
|
|
193
|
+
total += 1;
|
|
194
|
+
const finalResult = test.results?.at(-1);
|
|
195
|
+
if (finalResult?.duration) {
|
|
196
|
+
duration += finalResult.duration;
|
|
197
|
+
}
|
|
198
|
+
const status = finalResult?.status ?? test.status;
|
|
199
|
+
switch (status) {
|
|
200
|
+
case 'passed':
|
|
201
|
+
expected += 1;
|
|
202
|
+
break;
|
|
203
|
+
case 'skipped':
|
|
204
|
+
skipped += 1;
|
|
205
|
+
break;
|
|
206
|
+
case 'flaky':
|
|
207
|
+
flaky += 1;
|
|
208
|
+
break;
|
|
209
|
+
case 'failed':
|
|
210
|
+
case 'timedOut':
|
|
211
|
+
case 'interrupted':
|
|
212
|
+
unexpected += 1;
|
|
213
|
+
break;
|
|
214
|
+
default:
|
|
215
|
+
unexpected += 1;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
expected,
|
|
222
|
+
unexpected,
|
|
223
|
+
flaky,
|
|
224
|
+
skipped,
|
|
225
|
+
duration,
|
|
226
|
+
total,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=merge.js.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Canonical Donobu test-report model.
|
|
3
|
+
*
|
|
4
|
+
* This is the single in-memory / on-disk shape shared between the reporter,
|
|
5
|
+
* the auto-heal orchestrator, the merge step, and the HTML renderer. It is a
|
|
6
|
+
* Playwright-JSON superset with explicit Donobu fields: downstream tools that
|
|
7
|
+
* only understand Playwright JSON continue to work because unknown fields are
|
|
8
|
+
* ignored.
|
|
9
|
+
*
|
|
10
|
+
* The file is intentionally loose about the inner Playwright shape (`unknown`
|
|
11
|
+
* at the boundary, `any` inside the renderer) — the goal here is to give every
|
|
12
|
+
* consumer one type to import and one filename to look for on disk, not to
|
|
13
|
+
* retype the whole Playwright reporter surface.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Filename the reporter writes into each run's Playwright output directory.
|
|
17
|
+
* The auto-heal orchestrator looks for this file (in both the initial run's
|
|
18
|
+
* output dir and the heal-run's staging dir) to drive the merge + re-render.
|
|
19
|
+
*
|
|
20
|
+
* Internal contract only — not a public artifact and not guaranteed to stay
|
|
21
|
+
* stable across Donobu versions.
|
|
22
|
+
*/
|
|
23
|
+
export declare const DONOBU_REPORT_STATE_FILENAME = ".donobu-report-state.json";
|
|
24
|
+
/**
|
|
25
|
+
* Per-format output record written by each Donobu reporter into the shared
|
|
26
|
+
* state file. The auto-heal orchestrator reads this map after merging two
|
|
27
|
+
* runs and re-renders whichever formats the user has configured, landing each
|
|
28
|
+
* result at the same path the reporter originally chose.
|
|
29
|
+
*/
|
|
30
|
+
export interface DonobuReportOutputs {
|
|
31
|
+
html?: {
|
|
32
|
+
outputFile: string;
|
|
33
|
+
};
|
|
34
|
+
markdown?: {
|
|
35
|
+
outputFile: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Slack-specific configuration is read from environment variables
|
|
39
|
+
* (`DONOBU_SLACK_WEBHOOK_URL`, `DONOBU_REPORT_URL`) — see the `slack.ts`
|
|
40
|
+
* reporter docs — so the only thing tracked per-output is where the
|
|
41
|
+
* payload was written.
|
|
42
|
+
*/
|
|
43
|
+
slack?: {
|
|
44
|
+
outputFile: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export interface DonobuReportMetadata {
|
|
48
|
+
/** True on reports that are the result of merging an initial + heal run. */
|
|
49
|
+
donobuMergedReport?: boolean;
|
|
50
|
+
mergedAtIso?: string;
|
|
51
|
+
sources?: {
|
|
52
|
+
initial?: string | null;
|
|
53
|
+
autoHeal?: string | null;
|
|
54
|
+
};
|
|
55
|
+
/** Absolute path to the triage run directory that produced the plans/evidence. */
|
|
56
|
+
triageRunDir?: string;
|
|
57
|
+
/** Composite keys (`file::projectName::title`) of tests that auto-heal repaired. */
|
|
58
|
+
donobuHealedTests?: string[];
|
|
59
|
+
/**
|
|
60
|
+
* Per-format output paths recorded by each configured Donobu reporter. The
|
|
61
|
+
* orchestrator iterates this to regenerate every output after the auto-heal
|
|
62
|
+
* merge.
|
|
63
|
+
*/
|
|
64
|
+
donobuOutputs?: DonobuReportOutputs;
|
|
65
|
+
[key: string]: unknown;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Donobu's canonical test-report shape. Produced by the reporter, merged by
|
|
69
|
+
* `mergeReports`, rendered by `renderHtml`. Suites / specs / tests / results
|
|
70
|
+
* are left as `unknown[]` here and walked with `any` inside the renderer —
|
|
71
|
+
* the renderer already tolerates the full breadth of Playwright's JSON.
|
|
72
|
+
*
|
|
73
|
+
* Triage data (treatment plans, failure evidence) is loaded separately and
|
|
74
|
+
* passed alongside a `DonobuReport` to the renderer — it is not part of the
|
|
75
|
+
* serialized model.
|
|
76
|
+
*/
|
|
77
|
+
export interface DonobuReport {
|
|
78
|
+
suites?: unknown[];
|
|
79
|
+
stats?: unknown;
|
|
80
|
+
config?: unknown;
|
|
81
|
+
errors?: unknown[];
|
|
82
|
+
metadata?: DonobuReportMetadata;
|
|
83
|
+
[key: string]: unknown;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A test the orchestrator has designated as healed (used to annotate the
|
|
87
|
+
* merged report when the heal-run didn't expose the test under a matching key).
|
|
88
|
+
*
|
|
89
|
+
* `TPlan` is generic so callers that have a stronger type for the treatment
|
|
90
|
+
* plan (e.g. a Zod-inferred type) can preserve it; the merge step itself only
|
|
91
|
+
* reads `testCase`, so `unknown` is a safe default for the model's boundary.
|
|
92
|
+
*/
|
|
93
|
+
export interface HealedTestDescriptor<TPlan = unknown> {
|
|
94
|
+
plan: TPlan;
|
|
95
|
+
testCase: {
|
|
96
|
+
title?: string;
|
|
97
|
+
file?: string;
|
|
98
|
+
projectName?: string;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Canonical Donobu test-report model.
|
|
4
|
+
*
|
|
5
|
+
* This is the single in-memory / on-disk shape shared between the reporter,
|
|
6
|
+
* the auto-heal orchestrator, the merge step, and the HTML renderer. It is a
|
|
7
|
+
* Playwright-JSON superset with explicit Donobu fields: downstream tools that
|
|
8
|
+
* only understand Playwright JSON continue to work because unknown fields are
|
|
9
|
+
* ignored.
|
|
10
|
+
*
|
|
11
|
+
* The file is intentionally loose about the inner Playwright shape (`unknown`
|
|
12
|
+
* at the boundary, `any` inside the renderer) — the goal here is to give every
|
|
13
|
+
* consumer one type to import and one filename to look for on disk, not to
|
|
14
|
+
* retype the whole Playwright reporter surface.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.DONOBU_REPORT_STATE_FILENAME = void 0;
|
|
18
|
+
/**
|
|
19
|
+
* Filename the reporter writes into each run's Playwright output directory.
|
|
20
|
+
* The auto-heal orchestrator looks for this file (in both the initial run's
|
|
21
|
+
* output dir and the heal-run's staging dir) to drive the merge + re-render.
|
|
22
|
+
*
|
|
23
|
+
* Internal contract only — not a public artifact and not guaranteed to stay
|
|
24
|
+
* stable across Donobu versions.
|
|
25
|
+
*/
|
|
26
|
+
exports.DONOBU_REPORT_STATE_FILENAME = '.donobu-report-state.json';
|
|
27
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
/**
|
|
3
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Donobu HTML report renderer.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
* data) into a
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* @usage
|
|
10
|
-
* ```bash
|
|
11
|
-
* npm exec playwright-json-to-html report.json -o report.html
|
|
12
|
-
* npm exec playwright-json-to-html report.json --triage-dir ./donobu-triage/run-id/ -o report.html
|
|
13
|
-
* cat merged-report.json | npx playwright-json-to-html --triage-dir ./triage/ -o report.html
|
|
14
|
-
* ```
|
|
4
|
+
* Pure library that turns a `DonobuReport` (Playwright-JSON superset with
|
|
5
|
+
* optional triage data) into a self-contained HTML document. No filesystem
|
|
6
|
+
* writes, no CLI arg parsing, no environment variable reads — callers (the
|
|
7
|
+
* reporter and the auto-heal orchestrator) own I/O.
|
|
15
8
|
*/
|
|
9
|
+
import type { DonobuReport } from './model';
|
|
10
|
+
export declare function stripAnsi(str: string): string;
|
|
16
11
|
interface TreatmentPlanRecord {
|
|
17
12
|
generatedAtIso?: string;
|
|
18
13
|
plan: {
|
|
@@ -145,6 +140,6 @@ export interface TriageData {
|
|
|
145
140
|
evidence: FailureEvidenceRecord[];
|
|
146
141
|
}
|
|
147
142
|
export declare function loadTriageData(triageDir: string): TriageData;
|
|
148
|
-
export declare function
|
|
143
|
+
export declare function renderHtml(report: DonobuReport, triage: TriageData, outputDir: string | null): string;
|
|
149
144
|
export {};
|
|
150
|
-
//# sourceMappingURL=
|
|
145
|
+
//# sourceMappingURL=render.d.ts.map
|