donobu 5.41.1 → 5.41.3
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/esm/managers/InteractionVisualizer.d.ts +2 -0
- package/dist/esm/managers/InteractionVisualizer.js +17 -6
- package/dist/esm/reporter/buildReport.d.ts +1 -1
- package/dist/esm/reporter/buildReport.js +8 -2
- package/dist/esm/reporter/html.d.ts +3 -1
- package/dist/esm/reporter/html.js +4 -1
- package/dist/esm/reporter/markdown.d.ts +3 -1
- package/dist/esm/reporter/markdown.js +4 -1
- package/dist/esm/reporter/slack.d.ts +3 -1
- package/dist/esm/reporter/slack.js +4 -1
- package/dist/managers/InteractionVisualizer.d.ts +2 -0
- package/dist/managers/InteractionVisualizer.js +17 -6
- package/dist/reporter/buildReport.d.ts +1 -1
- package/dist/reporter/buildReport.js +8 -2
- package/dist/reporter/html.d.ts +3 -1
- package/dist/reporter/html.js +4 -1
- package/dist/reporter/markdown.d.ts +3 -1
- package/dist/reporter/markdown.js +4 -1
- package/dist/reporter/slack.d.ts +3 -1
- package/dist/reporter/slack.js +4 -1
- package/package.json +1 -1
|
@@ -2,6 +2,8 @@ import type { Locator, Page } from 'playwright';
|
|
|
2
2
|
export declare class InteractionVisualizer {
|
|
3
3
|
readonly defaultMessageDurationMillis: number;
|
|
4
4
|
private static readonly RAW_MOUSE_D;
|
|
5
|
+
private static readonly TIP_X;
|
|
6
|
+
private static readonly TIP_Y;
|
|
5
7
|
private static readonly SVG_MOUSE;
|
|
6
8
|
private static readonly SVG_MOUSE_JSON;
|
|
7
9
|
private static readonly CSS;
|
|
@@ -145,7 +145,7 @@ class InteractionVisualizer {
|
|
|
145
145
|
}
|
|
146
146
|
async ensureCursor(page) {
|
|
147
147
|
const id = InteractionVisualizer.CONTAINER_ID;
|
|
148
|
-
await page.evaluate(([containerId, position, svg, svgAttrs]) => {
|
|
148
|
+
await page.evaluate(([containerId, position, svg, svgAttrs, tipX, tipY]) => {
|
|
149
149
|
const root = document.getElementById(containerId).shadowRoot;
|
|
150
150
|
let cursor = root.querySelector('.donobu-virtual-mouse');
|
|
151
151
|
if (cursor) {
|
|
@@ -175,24 +175,26 @@ class InteractionVisualizer {
|
|
|
175
175
|
}
|
|
176
176
|
const pos = position;
|
|
177
177
|
cursor.style.transitionDuration = '0s';
|
|
178
|
-
cursor.style.transform = `translate(${pos.x -
|
|
178
|
+
cursor.style.transform = `translate(${pos.x - tipX}px, ${pos.y - tipY}px)`;
|
|
179
179
|
root.appendChild(cursor);
|
|
180
180
|
}, [
|
|
181
181
|
id,
|
|
182
182
|
this.cursorPos,
|
|
183
183
|
InteractionVisualizer.SVG_MOUSE,
|
|
184
184
|
JSON.stringify(InteractionVisualizer.SVG_MOUSE_JSON),
|
|
185
|
+
InteractionVisualizer.TIP_X,
|
|
186
|
+
InteractionVisualizer.TIP_Y,
|
|
185
187
|
]);
|
|
186
188
|
}
|
|
187
189
|
async moveCursor(page, target, duration) {
|
|
188
190
|
await this.ensureCursor(page);
|
|
189
191
|
const id = InteractionVisualizer.CONTAINER_ID;
|
|
190
|
-
await page.evaluate(([containerId, end, duration]) => {
|
|
192
|
+
await page.evaluate(([containerId, end, duration, tipX, tipY]) => {
|
|
191
193
|
const root = document.getElementById(containerId).shadowRoot;
|
|
192
194
|
const cursor = root.querySelector('.donobu-virtual-mouse');
|
|
193
195
|
cursor.style.transitionDuration = `${duration}ms`;
|
|
194
196
|
const endPos = end;
|
|
195
|
-
cursor.style.transform = `translate(${endPos.x -
|
|
197
|
+
cursor.style.transform = `translate(${endPos.x - tipX}px, ${endPos.y - tipY}px)`;
|
|
196
198
|
cursor.classList.add('rippling');
|
|
197
199
|
const done = new Promise((resolve) => {
|
|
198
200
|
const finish = () => {
|
|
@@ -204,7 +206,13 @@ class InteractionVisualizer {
|
|
|
204
206
|
setTimeout(finish, duration + 50);
|
|
205
207
|
});
|
|
206
208
|
return done;
|
|
207
|
-
}, [
|
|
209
|
+
}, [
|
|
210
|
+
id,
|
|
211
|
+
target,
|
|
212
|
+
duration,
|
|
213
|
+
InteractionVisualizer.TIP_X,
|
|
214
|
+
InteractionVisualizer.TIP_Y,
|
|
215
|
+
]);
|
|
208
216
|
this.cursorPos = target;
|
|
209
217
|
}
|
|
210
218
|
async showMessage(page, target, text, duration) {
|
|
@@ -246,6 +254,9 @@ exports.InteractionVisualizer = InteractionVisualizer;
|
|
|
246
254
|
/* Constants */
|
|
247
255
|
/* ------------------------------------------------------------------ */
|
|
248
256
|
InteractionVisualizer.RAW_MOUSE_D = 'M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z';
|
|
257
|
+
// Arrow tip within the 24×24 viewBox — matches the M command at the start of RAW_MOUSE_D.
|
|
258
|
+
InteractionVisualizer.TIP_X = 4.037;
|
|
259
|
+
InteractionVisualizer.TIP_Y = 4.688;
|
|
249
260
|
InteractionVisualizer.SVG_MOUSE = `
|
|
250
261
|
<svg xmlns="http://www.w3.org/2000/svg"
|
|
251
262
|
width="32"
|
|
@@ -279,7 +290,7 @@ InteractionVisualizer.CSS = `
|
|
|
279
290
|
.donobu-virtual-mouse{width:24px;height:24px;position:absolute;z-index:2147483646;filter:drop-shadow(1px 1px 1px rgba(0,0,0,.5));transition:transform .15s ease-in-out;pointer-events:none;opacity:1;visibility:visible}
|
|
280
291
|
.donobu-virtual-mouse.hidden{opacity:0;visibility:hidden}
|
|
281
292
|
.donobu-virtual-mouse svg{width:100%;height:100%;display:block}
|
|
282
|
-
.donobu-virtual-mouse.rippling::after{content:"";position:absolute;left
|
|
293
|
+
.donobu-virtual-mouse.rippling::after{content:"";position:absolute;left:${InteractionVisualizer.TIP_X}px;top:${InteractionVisualizer.TIP_Y}px;width:24px;height:24px;border-radius:50%;background:#FF781B;transform:translate(-50%,-50%) scale(.5);opacity:.4;animation:ripple-pop .6s ease-out forwards}
|
|
283
294
|
@keyframes ripple-pop{40%{transform:translate(-50%,-50%) scale(1);opacity:.5}100%{transform:translate(-50%,-50%) scale(2);opacity:0}}
|
|
284
295
|
`;
|
|
285
296
|
InteractionVisualizer.CONTAINER_ID = 'donobu-iv-overlay';
|
|
@@ -18,5 +18,5 @@ import type { DonobuReport } from './model';
|
|
|
18
18
|
* entry for their format) before persisting the result — this helper owns the
|
|
19
19
|
* structure walk, not the output-path accounting.
|
|
20
20
|
*/
|
|
21
|
-
export declare function buildDonobuReport(resultsByTest: Map<TestCase, TestResult[]
|
|
21
|
+
export declare function buildDonobuReport(resultsByTest: Map<TestCase, TestResult[]>, rootDir?: string): DonobuReport;
|
|
22
22
|
//# sourceMappingURL=buildReport.d.ts.map
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.buildDonobuReport = buildDonobuReport;
|
|
14
|
+
const path_1 = require("path");
|
|
14
15
|
/**
|
|
15
16
|
* Build a canonical `DonobuReport` from the per-test result accumulators that
|
|
16
17
|
* each Donobu reporter maintains during a run.
|
|
@@ -19,13 +20,18 @@ exports.buildDonobuReport = buildDonobuReport;
|
|
|
19
20
|
* entry for their format) before persisting the result — this helper owns the
|
|
20
21
|
* structure walk, not the output-path accounting.
|
|
21
22
|
*/
|
|
22
|
-
function buildDonobuReport(resultsByTest) {
|
|
23
|
+
function buildDonobuReport(resultsByTest, rootDir) {
|
|
23
24
|
// Group tests by file path, then by test title.
|
|
24
25
|
// Multiple TestCase objects with the same (file, title) represent the same
|
|
25
26
|
// spec running under different Playwright projects.
|
|
27
|
+
// Paths are normalized to be relative to the Playwright rootDir (mirroring
|
|
28
|
+
// the native JSON reporter) so GitHub Actions summaries and other consumers
|
|
29
|
+
// don't show absolute CI runner paths.
|
|
26
30
|
const byFile = new Map();
|
|
27
31
|
for (const test of resultsByTest.keys()) {
|
|
28
|
-
const file =
|
|
32
|
+
const file = rootDir
|
|
33
|
+
? (0, path_1.relative)(rootDir, test.location.file)
|
|
34
|
+
: test.location.file;
|
|
29
35
|
if (!byFile.has(file)) {
|
|
30
36
|
byFile.set(file, new Map());
|
|
31
37
|
}
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* the Playwright JSON output directory so the orchestrator can pick it up,
|
|
31
31
|
* merge it with the initial run's state, and re-render the HTML once.
|
|
32
32
|
*/
|
|
33
|
-
import type { FullResult, Reporter, TestCase, TestResult } from '@playwright/test/reporter';
|
|
33
|
+
import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestResult } from '@playwright/test/reporter';
|
|
34
34
|
export interface DonobuHtmlReporterOptions {
|
|
35
35
|
/** Path to write the HTML report. Defaults to `test-results/index.html`. */
|
|
36
36
|
outputFile?: string;
|
|
@@ -45,7 +45,9 @@ export default class DonobuHtmlReporter implements Reporter {
|
|
|
45
45
|
private readonly options;
|
|
46
46
|
/** Accumulates all TestResult objects per TestCase (one per retry attempt). */
|
|
47
47
|
private readonly resultsByTest;
|
|
48
|
+
private rootDir;
|
|
48
49
|
constructor(options?: DonobuHtmlReporterOptions);
|
|
50
|
+
onBegin(config: FullConfig, _suite: Suite): void;
|
|
49
51
|
onTestEnd(test: TestCase, result: TestResult): void;
|
|
50
52
|
onEnd(_result: FullResult): Promise<void>;
|
|
51
53
|
printsToStdio(): boolean;
|
|
@@ -45,6 +45,9 @@ class DonobuHtmlReporter {
|
|
|
45
45
|
this.resultsByTest = new Map();
|
|
46
46
|
this.options = options;
|
|
47
47
|
}
|
|
48
|
+
onBegin(config, _suite) {
|
|
49
|
+
this.rootDir = config.rootDir;
|
|
50
|
+
}
|
|
48
51
|
onTestEnd(test, result) {
|
|
49
52
|
const existing = this.resultsByTest.get(test);
|
|
50
53
|
if (existing) {
|
|
@@ -58,7 +61,7 @@ class DonobuHtmlReporter {
|
|
|
58
61
|
const outputFile = (0, path_1.resolve)(this.options.outputFile ?? 'test-results/index.html');
|
|
59
62
|
const outputDir = (0, path_1.dirname)(outputFile);
|
|
60
63
|
const autoHealActive = envVars_1.env.data.DONOBU_AUTO_HEAL_ACTIVE === '1';
|
|
61
|
-
const report = (0, buildReport_1.buildDonobuReport)(this.resultsByTest);
|
|
64
|
+
const report = (0, buildReport_1.buildDonobuReport)(this.resultsByTest, this.rootDir);
|
|
62
65
|
// Persist the full report + this reporter's output entry to the shared
|
|
63
66
|
// state file so other reporters and the auto-heal orchestrator can find
|
|
64
67
|
// it. When multiple reporters run in the same process this merges each
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* file writes. It always merges its output entry into the shared state file
|
|
21
21
|
* so the orchestrator knows to regenerate the markdown from the merged report.
|
|
22
22
|
*/
|
|
23
|
-
import type { FullResult, Reporter, TestCase, TestResult } from '@playwright/test/reporter';
|
|
23
|
+
import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestResult } from '@playwright/test/reporter';
|
|
24
24
|
export interface DonobuMarkdownReporterOptions {
|
|
25
25
|
/** Path to write the Markdown report. Defaults to `test-results/report.md`. */
|
|
26
26
|
outputFile?: string;
|
|
@@ -28,7 +28,9 @@ export interface DonobuMarkdownReporterOptions {
|
|
|
28
28
|
export default class DonobuMarkdownReporter implements Reporter {
|
|
29
29
|
private readonly options;
|
|
30
30
|
private readonly resultsByTest;
|
|
31
|
+
private rootDir;
|
|
31
32
|
constructor(options?: DonobuMarkdownReporterOptions);
|
|
33
|
+
onBegin(config: FullConfig, _suite: Suite): void;
|
|
32
34
|
onTestEnd(test: TestCase, result: TestResult): void;
|
|
33
35
|
onEnd(_result: FullResult): Promise<void>;
|
|
34
36
|
printsToStdio(): boolean;
|
|
@@ -34,6 +34,9 @@ class DonobuMarkdownReporter {
|
|
|
34
34
|
this.resultsByTest = new Map();
|
|
35
35
|
this.options = options;
|
|
36
36
|
}
|
|
37
|
+
onBegin(config, _suite) {
|
|
38
|
+
this.rootDir = config.rootDir;
|
|
39
|
+
}
|
|
37
40
|
onTestEnd(test, result) {
|
|
38
41
|
const existing = this.resultsByTest.get(test);
|
|
39
42
|
if (existing) {
|
|
@@ -47,7 +50,7 @@ class DonobuMarkdownReporter {
|
|
|
47
50
|
const outputFile = (0, path_1.resolve)(this.options.outputFile ?? 'test-results/report.md');
|
|
48
51
|
const outputDir = (0, path_1.dirname)(outputFile);
|
|
49
52
|
const autoHealActive = envVars_1.env.data.DONOBU_AUTO_HEAL_ACTIVE === '1';
|
|
50
|
-
const report = (0, buildReport_1.buildDonobuReport)(this.resultsByTest);
|
|
53
|
+
const report = (0, buildReport_1.buildDonobuReport)(this.resultsByTest, this.rootDir);
|
|
51
54
|
(0, stateFile_1.mergeStateFileEntry)(envVars_1.env.data.PLAYWRIGHT_JSON_OUTPUT_DIR, report, {
|
|
52
55
|
markdown: { outputFile },
|
|
53
56
|
});
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
* both the initial run and the heal rerun; the orchestrator posts the
|
|
72
72
|
* merged payload after re-rendering.
|
|
73
73
|
*/
|
|
74
|
-
import type { FullResult, Reporter, TestCase, TestResult } from '@playwright/test/reporter';
|
|
74
|
+
import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestResult } from '@playwright/test/reporter';
|
|
75
75
|
import { type SlackBlockPayload } from './renderSlack';
|
|
76
76
|
export interface DonobuSlackReporterOptions {
|
|
77
77
|
/** Path to write the Slack payload. Defaults to `test-results/slack-payload.json`. */
|
|
@@ -85,7 +85,9 @@ export declare function postSlackPayload(webhookUrl: string, payload: SlackBlock
|
|
|
85
85
|
export default class DonobuSlackReporter implements Reporter {
|
|
86
86
|
private readonly options;
|
|
87
87
|
private readonly resultsByTest;
|
|
88
|
+
private rootDir;
|
|
88
89
|
constructor(options?: DonobuSlackReporterOptions);
|
|
90
|
+
onBegin(config: FullConfig, _suite: Suite): void;
|
|
89
91
|
onTestEnd(test: TestCase, result: TestResult): void;
|
|
90
92
|
onEnd(_result: FullResult): Promise<void>;
|
|
91
93
|
printsToStdio(): boolean;
|
|
@@ -106,6 +106,9 @@ class DonobuSlackReporter {
|
|
|
106
106
|
this.resultsByTest = new Map();
|
|
107
107
|
this.options = options;
|
|
108
108
|
}
|
|
109
|
+
onBegin(config, _suite) {
|
|
110
|
+
this.rootDir = config.rootDir;
|
|
111
|
+
}
|
|
109
112
|
onTestEnd(test, result) {
|
|
110
113
|
const existing = this.resultsByTest.get(test);
|
|
111
114
|
if (existing) {
|
|
@@ -121,7 +124,7 @@ class DonobuSlackReporter {
|
|
|
121
124
|
const autoHealActive = envVars_1.env.data.DONOBU_AUTO_HEAL_ACTIVE === '1';
|
|
122
125
|
const autoHealOrchestrated = envVars_1.env.data.DONOBU_AUTO_HEAL_ORCHESTRATED === '1';
|
|
123
126
|
const reportUrl = envVars_1.env.data.DONOBU_REPORT_URL;
|
|
124
|
-
const report = (0, buildReport_1.buildDonobuReport)(this.resultsByTest);
|
|
127
|
+
const report = (0, buildReport_1.buildDonobuReport)(this.resultsByTest, this.rootDir);
|
|
125
128
|
(0, stateFile_1.mergeStateFileEntry)(envVars_1.env.data.PLAYWRIGHT_JSON_OUTPUT_DIR, report, {
|
|
126
129
|
slack: { outputFile },
|
|
127
130
|
});
|
|
@@ -2,6 +2,8 @@ import type { Locator, Page } from 'playwright';
|
|
|
2
2
|
export declare class InteractionVisualizer {
|
|
3
3
|
readonly defaultMessageDurationMillis: number;
|
|
4
4
|
private static readonly RAW_MOUSE_D;
|
|
5
|
+
private static readonly TIP_X;
|
|
6
|
+
private static readonly TIP_Y;
|
|
5
7
|
private static readonly SVG_MOUSE;
|
|
6
8
|
private static readonly SVG_MOUSE_JSON;
|
|
7
9
|
private static readonly CSS;
|
|
@@ -145,7 +145,7 @@ class InteractionVisualizer {
|
|
|
145
145
|
}
|
|
146
146
|
async ensureCursor(page) {
|
|
147
147
|
const id = InteractionVisualizer.CONTAINER_ID;
|
|
148
|
-
await page.evaluate(([containerId, position, svg, svgAttrs]) => {
|
|
148
|
+
await page.evaluate(([containerId, position, svg, svgAttrs, tipX, tipY]) => {
|
|
149
149
|
const root = document.getElementById(containerId).shadowRoot;
|
|
150
150
|
let cursor = root.querySelector('.donobu-virtual-mouse');
|
|
151
151
|
if (cursor) {
|
|
@@ -175,24 +175,26 @@ class InteractionVisualizer {
|
|
|
175
175
|
}
|
|
176
176
|
const pos = position;
|
|
177
177
|
cursor.style.transitionDuration = '0s';
|
|
178
|
-
cursor.style.transform = `translate(${pos.x -
|
|
178
|
+
cursor.style.transform = `translate(${pos.x - tipX}px, ${pos.y - tipY}px)`;
|
|
179
179
|
root.appendChild(cursor);
|
|
180
180
|
}, [
|
|
181
181
|
id,
|
|
182
182
|
this.cursorPos,
|
|
183
183
|
InteractionVisualizer.SVG_MOUSE,
|
|
184
184
|
JSON.stringify(InteractionVisualizer.SVG_MOUSE_JSON),
|
|
185
|
+
InteractionVisualizer.TIP_X,
|
|
186
|
+
InteractionVisualizer.TIP_Y,
|
|
185
187
|
]);
|
|
186
188
|
}
|
|
187
189
|
async moveCursor(page, target, duration) {
|
|
188
190
|
await this.ensureCursor(page);
|
|
189
191
|
const id = InteractionVisualizer.CONTAINER_ID;
|
|
190
|
-
await page.evaluate(([containerId, end, duration]) => {
|
|
192
|
+
await page.evaluate(([containerId, end, duration, tipX, tipY]) => {
|
|
191
193
|
const root = document.getElementById(containerId).shadowRoot;
|
|
192
194
|
const cursor = root.querySelector('.donobu-virtual-mouse');
|
|
193
195
|
cursor.style.transitionDuration = `${duration}ms`;
|
|
194
196
|
const endPos = end;
|
|
195
|
-
cursor.style.transform = `translate(${endPos.x -
|
|
197
|
+
cursor.style.transform = `translate(${endPos.x - tipX}px, ${endPos.y - tipY}px)`;
|
|
196
198
|
cursor.classList.add('rippling');
|
|
197
199
|
const done = new Promise((resolve) => {
|
|
198
200
|
const finish = () => {
|
|
@@ -204,7 +206,13 @@ class InteractionVisualizer {
|
|
|
204
206
|
setTimeout(finish, duration + 50);
|
|
205
207
|
});
|
|
206
208
|
return done;
|
|
207
|
-
}, [
|
|
209
|
+
}, [
|
|
210
|
+
id,
|
|
211
|
+
target,
|
|
212
|
+
duration,
|
|
213
|
+
InteractionVisualizer.TIP_X,
|
|
214
|
+
InteractionVisualizer.TIP_Y,
|
|
215
|
+
]);
|
|
208
216
|
this.cursorPos = target;
|
|
209
217
|
}
|
|
210
218
|
async showMessage(page, target, text, duration) {
|
|
@@ -246,6 +254,9 @@ exports.InteractionVisualizer = InteractionVisualizer;
|
|
|
246
254
|
/* Constants */
|
|
247
255
|
/* ------------------------------------------------------------------ */
|
|
248
256
|
InteractionVisualizer.RAW_MOUSE_D = 'M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z';
|
|
257
|
+
// Arrow tip within the 24×24 viewBox — matches the M command at the start of RAW_MOUSE_D.
|
|
258
|
+
InteractionVisualizer.TIP_X = 4.037;
|
|
259
|
+
InteractionVisualizer.TIP_Y = 4.688;
|
|
249
260
|
InteractionVisualizer.SVG_MOUSE = `
|
|
250
261
|
<svg xmlns="http://www.w3.org/2000/svg"
|
|
251
262
|
width="32"
|
|
@@ -279,7 +290,7 @@ InteractionVisualizer.CSS = `
|
|
|
279
290
|
.donobu-virtual-mouse{width:24px;height:24px;position:absolute;z-index:2147483646;filter:drop-shadow(1px 1px 1px rgba(0,0,0,.5));transition:transform .15s ease-in-out;pointer-events:none;opacity:1;visibility:visible}
|
|
280
291
|
.donobu-virtual-mouse.hidden{opacity:0;visibility:hidden}
|
|
281
292
|
.donobu-virtual-mouse svg{width:100%;height:100%;display:block}
|
|
282
|
-
.donobu-virtual-mouse.rippling::after{content:"";position:absolute;left
|
|
293
|
+
.donobu-virtual-mouse.rippling::after{content:"";position:absolute;left:${InteractionVisualizer.TIP_X}px;top:${InteractionVisualizer.TIP_Y}px;width:24px;height:24px;border-radius:50%;background:#FF781B;transform:translate(-50%,-50%) scale(.5);opacity:.4;animation:ripple-pop .6s ease-out forwards}
|
|
283
294
|
@keyframes ripple-pop{40%{transform:translate(-50%,-50%) scale(1);opacity:.5}100%{transform:translate(-50%,-50%) scale(2);opacity:0}}
|
|
284
295
|
`;
|
|
285
296
|
InteractionVisualizer.CONTAINER_ID = 'donobu-iv-overlay';
|
|
@@ -18,5 +18,5 @@ import type { DonobuReport } from './model';
|
|
|
18
18
|
* entry for their format) before persisting the result — this helper owns the
|
|
19
19
|
* structure walk, not the output-path accounting.
|
|
20
20
|
*/
|
|
21
|
-
export declare function buildDonobuReport(resultsByTest: Map<TestCase, TestResult[]
|
|
21
|
+
export declare function buildDonobuReport(resultsByTest: Map<TestCase, TestResult[]>, rootDir?: string): DonobuReport;
|
|
22
22
|
//# sourceMappingURL=buildReport.d.ts.map
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.buildDonobuReport = buildDonobuReport;
|
|
14
|
+
const path_1 = require("path");
|
|
14
15
|
/**
|
|
15
16
|
* Build a canonical `DonobuReport` from the per-test result accumulators that
|
|
16
17
|
* each Donobu reporter maintains during a run.
|
|
@@ -19,13 +20,18 @@ exports.buildDonobuReport = buildDonobuReport;
|
|
|
19
20
|
* entry for their format) before persisting the result — this helper owns the
|
|
20
21
|
* structure walk, not the output-path accounting.
|
|
21
22
|
*/
|
|
22
|
-
function buildDonobuReport(resultsByTest) {
|
|
23
|
+
function buildDonobuReport(resultsByTest, rootDir) {
|
|
23
24
|
// Group tests by file path, then by test title.
|
|
24
25
|
// Multiple TestCase objects with the same (file, title) represent the same
|
|
25
26
|
// spec running under different Playwright projects.
|
|
27
|
+
// Paths are normalized to be relative to the Playwright rootDir (mirroring
|
|
28
|
+
// the native JSON reporter) so GitHub Actions summaries and other consumers
|
|
29
|
+
// don't show absolute CI runner paths.
|
|
26
30
|
const byFile = new Map();
|
|
27
31
|
for (const test of resultsByTest.keys()) {
|
|
28
|
-
const file =
|
|
32
|
+
const file = rootDir
|
|
33
|
+
? (0, path_1.relative)(rootDir, test.location.file)
|
|
34
|
+
: test.location.file;
|
|
29
35
|
if (!byFile.has(file)) {
|
|
30
36
|
byFile.set(file, new Map());
|
|
31
37
|
}
|
package/dist/reporter/html.d.ts
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* the Playwright JSON output directory so the orchestrator can pick it up,
|
|
31
31
|
* merge it with the initial run's state, and re-render the HTML once.
|
|
32
32
|
*/
|
|
33
|
-
import type { FullResult, Reporter, TestCase, TestResult } from '@playwright/test/reporter';
|
|
33
|
+
import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestResult } from '@playwright/test/reporter';
|
|
34
34
|
export interface DonobuHtmlReporterOptions {
|
|
35
35
|
/** Path to write the HTML report. Defaults to `test-results/index.html`. */
|
|
36
36
|
outputFile?: string;
|
|
@@ -45,7 +45,9 @@ export default class DonobuHtmlReporter implements Reporter {
|
|
|
45
45
|
private readonly options;
|
|
46
46
|
/** Accumulates all TestResult objects per TestCase (one per retry attempt). */
|
|
47
47
|
private readonly resultsByTest;
|
|
48
|
+
private rootDir;
|
|
48
49
|
constructor(options?: DonobuHtmlReporterOptions);
|
|
50
|
+
onBegin(config: FullConfig, _suite: Suite): void;
|
|
49
51
|
onTestEnd(test: TestCase, result: TestResult): void;
|
|
50
52
|
onEnd(_result: FullResult): Promise<void>;
|
|
51
53
|
printsToStdio(): boolean;
|
package/dist/reporter/html.js
CHANGED
|
@@ -45,6 +45,9 @@ class DonobuHtmlReporter {
|
|
|
45
45
|
this.resultsByTest = new Map();
|
|
46
46
|
this.options = options;
|
|
47
47
|
}
|
|
48
|
+
onBegin(config, _suite) {
|
|
49
|
+
this.rootDir = config.rootDir;
|
|
50
|
+
}
|
|
48
51
|
onTestEnd(test, result) {
|
|
49
52
|
const existing = this.resultsByTest.get(test);
|
|
50
53
|
if (existing) {
|
|
@@ -58,7 +61,7 @@ class DonobuHtmlReporter {
|
|
|
58
61
|
const outputFile = (0, path_1.resolve)(this.options.outputFile ?? 'test-results/index.html');
|
|
59
62
|
const outputDir = (0, path_1.dirname)(outputFile);
|
|
60
63
|
const autoHealActive = envVars_1.env.data.DONOBU_AUTO_HEAL_ACTIVE === '1';
|
|
61
|
-
const report = (0, buildReport_1.buildDonobuReport)(this.resultsByTest);
|
|
64
|
+
const report = (0, buildReport_1.buildDonobuReport)(this.resultsByTest, this.rootDir);
|
|
62
65
|
// Persist the full report + this reporter's output entry to the shared
|
|
63
66
|
// state file so other reporters and the auto-heal orchestrator can find
|
|
64
67
|
// it. When multiple reporters run in the same process this merges each
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* file writes. It always merges its output entry into the shared state file
|
|
21
21
|
* so the orchestrator knows to regenerate the markdown from the merged report.
|
|
22
22
|
*/
|
|
23
|
-
import type { FullResult, Reporter, TestCase, TestResult } from '@playwright/test/reporter';
|
|
23
|
+
import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestResult } from '@playwright/test/reporter';
|
|
24
24
|
export interface DonobuMarkdownReporterOptions {
|
|
25
25
|
/** Path to write the Markdown report. Defaults to `test-results/report.md`. */
|
|
26
26
|
outputFile?: string;
|
|
@@ -28,7 +28,9 @@ export interface DonobuMarkdownReporterOptions {
|
|
|
28
28
|
export default class DonobuMarkdownReporter implements Reporter {
|
|
29
29
|
private readonly options;
|
|
30
30
|
private readonly resultsByTest;
|
|
31
|
+
private rootDir;
|
|
31
32
|
constructor(options?: DonobuMarkdownReporterOptions);
|
|
33
|
+
onBegin(config: FullConfig, _suite: Suite): void;
|
|
32
34
|
onTestEnd(test: TestCase, result: TestResult): void;
|
|
33
35
|
onEnd(_result: FullResult): Promise<void>;
|
|
34
36
|
printsToStdio(): boolean;
|
|
@@ -34,6 +34,9 @@ class DonobuMarkdownReporter {
|
|
|
34
34
|
this.resultsByTest = new Map();
|
|
35
35
|
this.options = options;
|
|
36
36
|
}
|
|
37
|
+
onBegin(config, _suite) {
|
|
38
|
+
this.rootDir = config.rootDir;
|
|
39
|
+
}
|
|
37
40
|
onTestEnd(test, result) {
|
|
38
41
|
const existing = this.resultsByTest.get(test);
|
|
39
42
|
if (existing) {
|
|
@@ -47,7 +50,7 @@ class DonobuMarkdownReporter {
|
|
|
47
50
|
const outputFile = (0, path_1.resolve)(this.options.outputFile ?? 'test-results/report.md');
|
|
48
51
|
const outputDir = (0, path_1.dirname)(outputFile);
|
|
49
52
|
const autoHealActive = envVars_1.env.data.DONOBU_AUTO_HEAL_ACTIVE === '1';
|
|
50
|
-
const report = (0, buildReport_1.buildDonobuReport)(this.resultsByTest);
|
|
53
|
+
const report = (0, buildReport_1.buildDonobuReport)(this.resultsByTest, this.rootDir);
|
|
51
54
|
(0, stateFile_1.mergeStateFileEntry)(envVars_1.env.data.PLAYWRIGHT_JSON_OUTPUT_DIR, report, {
|
|
52
55
|
markdown: { outputFile },
|
|
53
56
|
});
|
package/dist/reporter/slack.d.ts
CHANGED
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
* both the initial run and the heal rerun; the orchestrator posts the
|
|
72
72
|
* merged payload after re-rendering.
|
|
73
73
|
*/
|
|
74
|
-
import type { FullResult, Reporter, TestCase, TestResult } from '@playwright/test/reporter';
|
|
74
|
+
import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestResult } from '@playwright/test/reporter';
|
|
75
75
|
import { type SlackBlockPayload } from './renderSlack';
|
|
76
76
|
export interface DonobuSlackReporterOptions {
|
|
77
77
|
/** Path to write the Slack payload. Defaults to `test-results/slack-payload.json`. */
|
|
@@ -85,7 +85,9 @@ export declare function postSlackPayload(webhookUrl: string, payload: SlackBlock
|
|
|
85
85
|
export default class DonobuSlackReporter implements Reporter {
|
|
86
86
|
private readonly options;
|
|
87
87
|
private readonly resultsByTest;
|
|
88
|
+
private rootDir;
|
|
88
89
|
constructor(options?: DonobuSlackReporterOptions);
|
|
90
|
+
onBegin(config: FullConfig, _suite: Suite): void;
|
|
89
91
|
onTestEnd(test: TestCase, result: TestResult): void;
|
|
90
92
|
onEnd(_result: FullResult): Promise<void>;
|
|
91
93
|
printsToStdio(): boolean;
|
package/dist/reporter/slack.js
CHANGED
|
@@ -106,6 +106,9 @@ class DonobuSlackReporter {
|
|
|
106
106
|
this.resultsByTest = new Map();
|
|
107
107
|
this.options = options;
|
|
108
108
|
}
|
|
109
|
+
onBegin(config, _suite) {
|
|
110
|
+
this.rootDir = config.rootDir;
|
|
111
|
+
}
|
|
109
112
|
onTestEnd(test, result) {
|
|
110
113
|
const existing = this.resultsByTest.get(test);
|
|
111
114
|
if (existing) {
|
|
@@ -121,7 +124,7 @@ class DonobuSlackReporter {
|
|
|
121
124
|
const autoHealActive = envVars_1.env.data.DONOBU_AUTO_HEAL_ACTIVE === '1';
|
|
122
125
|
const autoHealOrchestrated = envVars_1.env.data.DONOBU_AUTO_HEAL_ORCHESTRATED === '1';
|
|
123
126
|
const reportUrl = envVars_1.env.data.DONOBU_REPORT_URL;
|
|
124
|
-
const report = (0, buildReport_1.buildDonobuReport)(this.resultsByTest);
|
|
127
|
+
const report = (0, buildReport_1.buildDonobuReport)(this.resultsByTest, this.rootDir);
|
|
125
128
|
(0, stateFile_1.mergeStateFileEntry)(envVars_1.env.data.PLAYWRIGHT_JSON_OUTPUT_DIR, report, {
|
|
126
129
|
slack: { outputFile },
|
|
127
130
|
});
|