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,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared helpers for walking a `DonobuReport` and classifying
|
|
3
|
+
* the tests inside it. Used by every renderer (HTML, Markdown, Slack) so they
|
|
4
|
+
* all agree on "what counts as self-healed" and how suites nest.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Recursively collect all specs from a suite and its nested sub-suites. The
|
|
8
|
+
* Playwright JSON report nests suites (e.g. describe blocks), so a flat view
|
|
9
|
+
* is what every renderer actually wants.
|
|
10
|
+
*/
|
|
11
|
+
export declare function collectSpecs(suite: any): any[];
|
|
12
|
+
/**
|
|
13
|
+
* A test is "self-healed" when either:
|
|
14
|
+
* - it carries a `self-healed` annotation (set by the merge step when a
|
|
15
|
+
* failing test flipped to passing on the heal rerun), or
|
|
16
|
+
* - its `donobuStatus` field has been set to `'healed'` (same signal, via
|
|
17
|
+
* the merged report's per-test metadata).
|
|
18
|
+
*/
|
|
19
|
+
export declare function isSelfHealed(test: any): boolean;
|
|
20
|
+
/** Normalized test-status the renderers use for counts and labels. */
|
|
21
|
+
export type NormalizedStatus = 'passed' | 'failed' | 'healed' | 'timedOut' | 'skipped' | 'interrupted' | 'unknown';
|
|
22
|
+
/**
|
|
23
|
+
* Derive the display status for a test. Prefers the final attempt's result
|
|
24
|
+
* status; bumps to `'healed'` when the self-heal signal is present; falls
|
|
25
|
+
* back to `'skipped'` when Playwright didn't emit a result.
|
|
26
|
+
*/
|
|
27
|
+
export declare function statusOf(test: any): NormalizedStatus;
|
|
28
|
+
//# sourceMappingURL=reportWalk.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Shared helpers for walking a `DonobuReport` and classifying
|
|
4
|
+
* the tests inside it. Used by every renderer (HTML, Markdown, Slack) so they
|
|
5
|
+
* all agree on "what counts as self-healed" and how suites nest.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.collectSpecs = collectSpecs;
|
|
9
|
+
exports.isSelfHealed = isSelfHealed;
|
|
10
|
+
exports.statusOf = statusOf;
|
|
11
|
+
/**
|
|
12
|
+
* Recursively collect all specs from a suite and its nested sub-suites. The
|
|
13
|
+
* Playwright JSON report nests suites (e.g. describe blocks), so a flat view
|
|
14
|
+
* is what every renderer actually wants.
|
|
15
|
+
*/
|
|
16
|
+
function collectSpecs(suite) {
|
|
17
|
+
const specs = [...(suite.specs ?? [])];
|
|
18
|
+
for (const child of suite.suites ?? []) {
|
|
19
|
+
specs.push(...collectSpecs(child));
|
|
20
|
+
}
|
|
21
|
+
return specs;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A test is "self-healed" when either:
|
|
25
|
+
* - it carries a `self-healed` annotation (set by the merge step when a
|
|
26
|
+
* failing test flipped to passing on the heal rerun), or
|
|
27
|
+
* - its `donobuStatus` field has been set to `'healed'` (same signal, via
|
|
28
|
+
* the merged report's per-test metadata).
|
|
29
|
+
*/
|
|
30
|
+
function isSelfHealed(test) {
|
|
31
|
+
const annotations = test?.annotations ?? [];
|
|
32
|
+
const hasAnnotation = annotations.some((a) => a?.type === 'self-healed');
|
|
33
|
+
return hasAnnotation || test?.donobuStatus === 'healed';
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Derive the display status for a test. Prefers the final attempt's result
|
|
37
|
+
* status; bumps to `'healed'` when the self-heal signal is present; falls
|
|
38
|
+
* back to `'skipped'` when Playwright didn't emit a result.
|
|
39
|
+
*/
|
|
40
|
+
function statusOf(test) {
|
|
41
|
+
const lastResult = test?.results?.at?.(-1);
|
|
42
|
+
if (test?.status === 'skipped' ||
|
|
43
|
+
(!lastResult && test?.status === undefined)) {
|
|
44
|
+
return 'skipped';
|
|
45
|
+
}
|
|
46
|
+
if (isSelfHealed(test)) {
|
|
47
|
+
return 'healed';
|
|
48
|
+
}
|
|
49
|
+
const status = lastResult?.status ?? 'unknown';
|
|
50
|
+
switch (status) {
|
|
51
|
+
case 'passed':
|
|
52
|
+
case 'failed':
|
|
53
|
+
case 'timedOut':
|
|
54
|
+
case 'skipped':
|
|
55
|
+
case 'interrupted':
|
|
56
|
+
return status;
|
|
57
|
+
default:
|
|
58
|
+
return 'unknown';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=reportWalk.js.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Donobu Slack Reporter for Playwright.
|
|
3
|
+
*
|
|
4
|
+
* Writes a Slack Block Kit payload to disk and, when configured, POSTs it
|
|
5
|
+
* directly to a Slack Incoming Webhook. During auto-heal the POST is deferred
|
|
6
|
+
* to the orchestrator so the user sees exactly one message per run reflecting
|
|
7
|
+
* the final (merged, possibly healed) outcome — not an initial failure
|
|
8
|
+
* message followed by a healed one.
|
|
9
|
+
*
|
|
10
|
+
* @usage
|
|
11
|
+
* The reporter takes no Slack-related options. All Slack configuration is
|
|
12
|
+
* driven by environment variables (see below) — typically populated from CI
|
|
13
|
+
* secrets and contextual run metadata. Wire it up in `playwright.config.ts`
|
|
14
|
+
* with no arguments:
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* reporter: [
|
|
18
|
+
* ['donobu/reporter/slack'],
|
|
19
|
+
* ],
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Optionally set `outputFile` to control the on-disk payload path:
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* reporter: [
|
|
26
|
+
* ['donobu/reporter/slack', { outputFile: 'test-results/slack-payload.json' }],
|
|
27
|
+
* ],
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @env Configuration
|
|
31
|
+
*
|
|
32
|
+
* **`DONOBU_SLACK_WEBHOOK_URL`** *(required for posting; secret)* — the Slack
|
|
33
|
+
* Incoming Webhook URL to POST the payload to. When unset, the reporter still
|
|
34
|
+
* writes the payload file but performs no network call (the file is then
|
|
35
|
+
* useful as a CI artifact or for piping to `curl` manually). This is treated
|
|
36
|
+
* as a secret and is read only from the environment — never as a reporter
|
|
37
|
+
* option — so it cannot accidentally end up checked into a config file.
|
|
38
|
+
*
|
|
39
|
+
* **`DONOBU_REPORT_URL`** *(optional; not secret)* — a URL to embed in the
|
|
40
|
+
* Slack message that links back to the full report (e.g. the GitHub Actions
|
|
41
|
+
* run URL, or a published HTML report). When unset, the message simply omits
|
|
42
|
+
* the link section. Read from the environment for symmetry with the webhook
|
|
43
|
+
* URL: both come from CI context, and a single configuration story keeps the
|
|
44
|
+
* mental model simple. To override per-suite, set the env var differently
|
|
45
|
+
* before invoking `donobu test`.
|
|
46
|
+
*
|
|
47
|
+
* **`DONOBU_AUTO_HEAL_ACTIVE`** / **`DONOBU_AUTO_HEAL_ORCHESTRATED`**
|
|
48
|
+
* *(set internally; not user-facing)* — coordination flags the donobu CLI
|
|
49
|
+
* sets so the reporter knows when to defer Slack posting to the orchestrator.
|
|
50
|
+
* Users should never set these themselves.
|
|
51
|
+
*
|
|
52
|
+
* @CI A typical GitHub Actions wiring:
|
|
53
|
+
*
|
|
54
|
+
* ```yaml
|
|
55
|
+
* env:
|
|
56
|
+
* DONOBU_SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
57
|
+
* DONOBU_REPORT_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
58
|
+
* run: npx donobu test --auto-heal
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* @auto-heal Per-run delivery semantics
|
|
62
|
+
*
|
|
63
|
+
* Every Donobu run produces exactly one Slack POST when a webhook is
|
|
64
|
+
* configured, regardless of whether auto-heal triggers:
|
|
65
|
+
* - **No auto-heal configured:** the reporter posts directly during `onEnd`.
|
|
66
|
+
* - **Auto-heal configured, tests pass:** the reporter defers; the
|
|
67
|
+
* orchestrator posts the initial payload after the early-return.
|
|
68
|
+
* - **Auto-heal configured, no eligible plans:** same — orchestrator posts
|
|
69
|
+
* the initial payload via the fallback path.
|
|
70
|
+
* - **Auto-heal configured, heal runs and merges:** the reporter defers in
|
|
71
|
+
* both the initial run and the heal rerun; the orchestrator posts the
|
|
72
|
+
* merged payload after re-rendering.
|
|
73
|
+
*/
|
|
74
|
+
import type { FullResult, Reporter, TestCase, TestResult } from '@playwright/test/reporter';
|
|
75
|
+
import { type SlackBlockPayload } from './renderSlack';
|
|
76
|
+
export interface DonobuSlackReporterOptions {
|
|
77
|
+
/** Path to write the Slack payload. Defaults to `test-results/slack-payload.json`. */
|
|
78
|
+
outputFile?: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* POST a Slack Block Kit payload to an Incoming Webhook URL. Logs failures but
|
|
82
|
+
* never throws — Slack delivery is a nice-to-have, not a test-run blocker.
|
|
83
|
+
*/
|
|
84
|
+
export declare function postSlackPayload(webhookUrl: string, payload: SlackBlockPayload): Promise<void>;
|
|
85
|
+
export default class DonobuSlackReporter implements Reporter {
|
|
86
|
+
private readonly options;
|
|
87
|
+
private readonly resultsByTest;
|
|
88
|
+
constructor(options?: DonobuSlackReporterOptions);
|
|
89
|
+
onTestEnd(test: TestCase, result: TestResult): void;
|
|
90
|
+
onEnd(_result: FullResult): Promise<void>;
|
|
91
|
+
printsToStdio(): boolean;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=slack.d.ts.map
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Donobu Slack Reporter for Playwright.
|
|
4
|
+
*
|
|
5
|
+
* Writes a Slack Block Kit payload to disk and, when configured, POSTs it
|
|
6
|
+
* directly to a Slack Incoming Webhook. During auto-heal the POST is deferred
|
|
7
|
+
* to the orchestrator so the user sees exactly one message per run reflecting
|
|
8
|
+
* the final (merged, possibly healed) outcome — not an initial failure
|
|
9
|
+
* message followed by a healed one.
|
|
10
|
+
*
|
|
11
|
+
* @usage
|
|
12
|
+
* The reporter takes no Slack-related options. All Slack configuration is
|
|
13
|
+
* driven by environment variables (see below) — typically populated from CI
|
|
14
|
+
* secrets and contextual run metadata. Wire it up in `playwright.config.ts`
|
|
15
|
+
* with no arguments:
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* reporter: [
|
|
19
|
+
* ['donobu/reporter/slack'],
|
|
20
|
+
* ],
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Optionally set `outputFile` to control the on-disk payload path:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* reporter: [
|
|
27
|
+
* ['donobu/reporter/slack', { outputFile: 'test-results/slack-payload.json' }],
|
|
28
|
+
* ],
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @env Configuration
|
|
32
|
+
*
|
|
33
|
+
* **`DONOBU_SLACK_WEBHOOK_URL`** *(required for posting; secret)* — the Slack
|
|
34
|
+
* Incoming Webhook URL to POST the payload to. When unset, the reporter still
|
|
35
|
+
* writes the payload file but performs no network call (the file is then
|
|
36
|
+
* useful as a CI artifact or for piping to `curl` manually). This is treated
|
|
37
|
+
* as a secret and is read only from the environment — never as a reporter
|
|
38
|
+
* option — so it cannot accidentally end up checked into a config file.
|
|
39
|
+
*
|
|
40
|
+
* **`DONOBU_REPORT_URL`** *(optional; not secret)* — a URL to embed in the
|
|
41
|
+
* Slack message that links back to the full report (e.g. the GitHub Actions
|
|
42
|
+
* run URL, or a published HTML report). When unset, the message simply omits
|
|
43
|
+
* the link section. Read from the environment for symmetry with the webhook
|
|
44
|
+
* URL: both come from CI context, and a single configuration story keeps the
|
|
45
|
+
* mental model simple. To override per-suite, set the env var differently
|
|
46
|
+
* before invoking `donobu test`.
|
|
47
|
+
*
|
|
48
|
+
* **`DONOBU_AUTO_HEAL_ACTIVE`** / **`DONOBU_AUTO_HEAL_ORCHESTRATED`**
|
|
49
|
+
* *(set internally; not user-facing)* — coordination flags the donobu CLI
|
|
50
|
+
* sets so the reporter knows when to defer Slack posting to the orchestrator.
|
|
51
|
+
* Users should never set these themselves.
|
|
52
|
+
*
|
|
53
|
+
* @CI A typical GitHub Actions wiring:
|
|
54
|
+
*
|
|
55
|
+
* ```yaml
|
|
56
|
+
* env:
|
|
57
|
+
* DONOBU_SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
58
|
+
* DONOBU_REPORT_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
59
|
+
* run: npx donobu test --auto-heal
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @auto-heal Per-run delivery semantics
|
|
63
|
+
*
|
|
64
|
+
* Every Donobu run produces exactly one Slack POST when a webhook is
|
|
65
|
+
* configured, regardless of whether auto-heal triggers:
|
|
66
|
+
* - **No auto-heal configured:** the reporter posts directly during `onEnd`.
|
|
67
|
+
* - **Auto-heal configured, tests pass:** the reporter defers; the
|
|
68
|
+
* orchestrator posts the initial payload after the early-return.
|
|
69
|
+
* - **Auto-heal configured, no eligible plans:** same — orchestrator posts
|
|
70
|
+
* the initial payload via the fallback path.
|
|
71
|
+
* - **Auto-heal configured, heal runs and merges:** the reporter defers in
|
|
72
|
+
* both the initial run and the heal rerun; the orchestrator posts the
|
|
73
|
+
* merged payload after re-rendering.
|
|
74
|
+
*/
|
|
75
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
76
|
+
exports.postSlackPayload = postSlackPayload;
|
|
77
|
+
const fs_1 = require("fs");
|
|
78
|
+
const path_1 = require("path");
|
|
79
|
+
const buildReport_1 = require("./buildReport");
|
|
80
|
+
const renderSlack_1 = require("./renderSlack");
|
|
81
|
+
const stateFile_1 = require("./stateFile");
|
|
82
|
+
/**
|
|
83
|
+
* POST a Slack Block Kit payload to an Incoming Webhook URL. Logs failures but
|
|
84
|
+
* never throws — Slack delivery is a nice-to-have, not a test-run blocker.
|
|
85
|
+
*/
|
|
86
|
+
async function postSlackPayload(webhookUrl, payload) {
|
|
87
|
+
try {
|
|
88
|
+
const response = await fetch(webhookUrl, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: { 'Content-Type': 'application/json' },
|
|
91
|
+
body: JSON.stringify(payload),
|
|
92
|
+
});
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
console.error(`Donobu Slack POST returned ${response.status}: ${await response.text().catch(() => '<no body>')}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.error(`Donobu Slack POST failed: ${error.message ?? error}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
class DonobuSlackReporter {
|
|
102
|
+
constructor(options = {}) {
|
|
103
|
+
this.resultsByTest = new Map();
|
|
104
|
+
this.options = options;
|
|
105
|
+
}
|
|
106
|
+
onTestEnd(test, result) {
|
|
107
|
+
const existing = this.resultsByTest.get(test);
|
|
108
|
+
if (existing) {
|
|
109
|
+
existing.push(result);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
this.resultsByTest.set(test, [result]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async onEnd(_result) {
|
|
116
|
+
const outputFile = (0, path_1.resolve)(this.options.outputFile ?? 'test-results/slack-payload.json');
|
|
117
|
+
const outputDir = (0, path_1.dirname)(outputFile);
|
|
118
|
+
const autoHealActive = process.env.DONOBU_AUTO_HEAL_ACTIVE === '1';
|
|
119
|
+
const autoHealOrchestrated = process.env.DONOBU_AUTO_HEAL_ORCHESTRATED === '1';
|
|
120
|
+
const reportUrl = process.env.DONOBU_REPORT_URL;
|
|
121
|
+
const report = (0, buildReport_1.buildDonobuReport)(this.resultsByTest);
|
|
122
|
+
(0, stateFile_1.mergeStateFileEntry)(process.env.PLAYWRIGHT_JSON_OUTPUT_DIR, report, {
|
|
123
|
+
slack: { outputFile },
|
|
124
|
+
});
|
|
125
|
+
// Payload file is always written — even during a heal rerun — so the
|
|
126
|
+
// auto-heal orchestrator and any CI artifact upload can see the final state.
|
|
127
|
+
const payload = (0, renderSlack_1.renderSlack)(report, { reportUrl });
|
|
128
|
+
(0, fs_1.mkdirSync)(outputDir, { recursive: true });
|
|
129
|
+
(0, fs_1.writeFileSync)(outputFile, JSON.stringify(payload, null, 2), 'utf8');
|
|
130
|
+
console.error(`Donobu Slack payload written to ${outputFile}`);
|
|
131
|
+
// Defer Slack POST to the orchestrator when either:
|
|
132
|
+
// - this is the auto-heal rerun (orchestrator will re-render and post the
|
|
133
|
+
// merged result itself), OR
|
|
134
|
+
// - the outer `donobu test` invocation enabled auto-heal (orchestrator will
|
|
135
|
+
// decide whether to post the pre-heal or merged payload once it knows
|
|
136
|
+
// whether auto-heal actually triggered).
|
|
137
|
+
if (autoHealActive || autoHealOrchestrated) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const webhookUrl = process.env.DONOBU_SLACK_WEBHOOK_URL;
|
|
141
|
+
if (webhookUrl) {
|
|
142
|
+
await postSlackPayload(webhookUrl, payload);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
printsToStdio() {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
exports.default = DonobuSlackReporter;
|
|
150
|
+
//# sourceMappingURL=slack.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared state-file helper for Donobu reporters.
|
|
3
|
+
*
|
|
4
|
+
* Each Donobu reporter (HTML, Markdown, Slack) records its intended output
|
|
5
|
+
* path into a single state file that the auto-heal orchestrator reads after
|
|
6
|
+
* merging two runs, so it can re-render every configured format at the same
|
|
7
|
+
* path the reporter originally chose.
|
|
8
|
+
*
|
|
9
|
+
* When multiple reporters run in the same Playwright process, each one calls
|
|
10
|
+
* {@link mergeStateFileEntry} in its `onEnd` hook. The helper is
|
|
11
|
+
* read-modify-write: it loads any previously-written state, merges this
|
|
12
|
+
* reporter's `donobuOutputs` entry in without clobbering sibling entries, and
|
|
13
|
+
* writes the result back. Synchronous fs I/O is sufficient — Playwright runs
|
|
14
|
+
* reporter hooks sequentially within a single JS event loop.
|
|
15
|
+
*/
|
|
16
|
+
import { type DonobuReport, type DonobuReportOutputs } from './model';
|
|
17
|
+
/**
|
|
18
|
+
* Merge a single reporter's `donobuOutputs` entry into the shared state file.
|
|
19
|
+
* Safe to call from multiple reporters in the same run — each reporter's
|
|
20
|
+
* entry is merged alongside any siblings already written.
|
|
21
|
+
*
|
|
22
|
+
* @param playwrightOutputDir - the Playwright JSON output dir (passed via
|
|
23
|
+
* `PLAYWRIGHT_JSON_OUTPUT_DIR`). When unset, the call is a no-op because
|
|
24
|
+
* we have nowhere canonical to land the state file.
|
|
25
|
+
* @param report - the fresh `DonobuReport` this reporter built from live events.
|
|
26
|
+
* Its `suites` overwrite any previous state (they're identical across
|
|
27
|
+
* reporters running the same test run, so either is fine).
|
|
28
|
+
* @param outputsEntry - the format-keyed entry this reporter is contributing.
|
|
29
|
+
*/
|
|
30
|
+
export declare function mergeStateFileEntry(playwrightOutputDir: string | undefined, report: DonobuReport, outputsEntry: DonobuReportOutputs): void;
|
|
31
|
+
//# sourceMappingURL=stateFile.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Shared state-file helper for Donobu reporters.
|
|
4
|
+
*
|
|
5
|
+
* Each Donobu reporter (HTML, Markdown, Slack) records its intended output
|
|
6
|
+
* path into a single state file that the auto-heal orchestrator reads after
|
|
7
|
+
* merging two runs, so it can re-render every configured format at the same
|
|
8
|
+
* path the reporter originally chose.
|
|
9
|
+
*
|
|
10
|
+
* When multiple reporters run in the same Playwright process, each one calls
|
|
11
|
+
* {@link mergeStateFileEntry} in its `onEnd` hook. The helper is
|
|
12
|
+
* read-modify-write: it loads any previously-written state, merges this
|
|
13
|
+
* reporter's `donobuOutputs` entry in without clobbering sibling entries, and
|
|
14
|
+
* writes the result back. Synchronous fs I/O is sufficient — Playwright runs
|
|
15
|
+
* reporter hooks sequentially within a single JS event loop.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.mergeStateFileEntry = mergeStateFileEntry;
|
|
19
|
+
const fs_1 = require("fs");
|
|
20
|
+
const path_1 = require("path");
|
|
21
|
+
const model_1 = require("./model");
|
|
22
|
+
/**
|
|
23
|
+
* Merge a single reporter's `donobuOutputs` entry into the shared state file.
|
|
24
|
+
* Safe to call from multiple reporters in the same run — each reporter's
|
|
25
|
+
* entry is merged alongside any siblings already written.
|
|
26
|
+
*
|
|
27
|
+
* @param playwrightOutputDir - the Playwright JSON output dir (passed via
|
|
28
|
+
* `PLAYWRIGHT_JSON_OUTPUT_DIR`). When unset, the call is a no-op because
|
|
29
|
+
* we have nowhere canonical to land the state file.
|
|
30
|
+
* @param report - the fresh `DonobuReport` this reporter built from live events.
|
|
31
|
+
* Its `suites` overwrite any previous state (they're identical across
|
|
32
|
+
* reporters running the same test run, so either is fine).
|
|
33
|
+
* @param outputsEntry - the format-keyed entry this reporter is contributing.
|
|
34
|
+
*/
|
|
35
|
+
function mergeStateFileEntry(playwrightOutputDir, report, outputsEntry) {
|
|
36
|
+
if (!playwrightOutputDir) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const statePath = (0, path_1.join)(playwrightOutputDir, model_1.DONOBU_REPORT_STATE_FILENAME);
|
|
40
|
+
let existing = null;
|
|
41
|
+
if ((0, fs_1.existsSync)(statePath)) {
|
|
42
|
+
try {
|
|
43
|
+
existing = JSON.parse((0, fs_1.readFileSync)(statePath, 'utf8'));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Corrupt state file — start fresh rather than failing the reporter.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const merged = {
|
|
50
|
+
...(existing ?? {}),
|
|
51
|
+
...report,
|
|
52
|
+
metadata: {
|
|
53
|
+
...(existing?.metadata ?? {}),
|
|
54
|
+
...(report.metadata ?? {}),
|
|
55
|
+
donobuOutputs: {
|
|
56
|
+
...(existing?.metadata?.donobuOutputs ?? {}),
|
|
57
|
+
...(report.metadata?.donobuOutputs ?? {}),
|
|
58
|
+
...outputsEntry,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
(0, fs_1.mkdirSync)(playwrightOutputDir, { recursive: true });
|
|
64
|
+
(0, fs_1.writeFileSync)(statePath, JSON.stringify(merged), 'utf8');
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Non-fatal — worst case the orchestrator falls back to no re-rendering.
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=stateFile.js.map
|
|
@@ -4,18 +4,18 @@ import { ToolCallResult } from '../models/ToolCallResult';
|
|
|
4
4
|
import { Tool } from './Tool';
|
|
5
5
|
export declare const AssertPageCoreSchema: z.ZodObject<{
|
|
6
6
|
type: z.ZodEnum<{
|
|
7
|
+
content: "content";
|
|
7
8
|
title: "title";
|
|
8
9
|
url: "url";
|
|
9
|
-
content: "content";
|
|
10
10
|
}>;
|
|
11
11
|
expected: z.ZodString;
|
|
12
12
|
isRegex: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
13
13
|
}, z.core.$strip>;
|
|
14
14
|
export declare const AssertPageToolGptSchema: z.ZodObject<{
|
|
15
15
|
type: z.ZodEnum<{
|
|
16
|
+
content: "content";
|
|
16
17
|
title: "title";
|
|
17
18
|
url: "url";
|
|
18
|
-
content: "content";
|
|
19
19
|
}>;
|
|
20
20
|
expected: z.ZodString;
|
|
21
21
|
isRegex: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import z from 'zod/v4';
|
|
2
2
|
import type { GptMessage } from '../models/GptMessage';
|
|
3
|
-
import type { WebTargetConfig } from '../models/RunConfig';
|
|
4
3
|
export declare class MiscUtils {
|
|
5
4
|
private constructor();
|
|
6
5
|
static readonly DONOBU_VERSION: string;
|
|
@@ -80,17 +79,5 @@ export declare class MiscUtils {
|
|
|
80
79
|
* Infers a MIME type from a file ID / filename extension.
|
|
81
80
|
*/
|
|
82
81
|
static inferMimeType(fileId: string): string;
|
|
83
|
-
/**
|
|
84
|
-
* Mirrors `getDisplayText` from frontend/src/lib/utils.ts.
|
|
85
|
-
*/
|
|
86
|
-
static getDisplayText(url: string): string;
|
|
87
|
-
/**
|
|
88
|
-
* Mirrors the fallback used in frontend FlowsTable for the display name of a
|
|
89
|
-
* flow, but can also apply to tests.
|
|
90
|
-
*/
|
|
91
|
-
static getDisplayName({ name, web }: {
|
|
92
|
-
name: string | null;
|
|
93
|
-
web?: WebTargetConfig;
|
|
94
|
-
}, fallbackName?: string): string;
|
|
95
82
|
}
|
|
96
83
|
//# sourceMappingURL=MiscUtils.d.ts.map
|
|
@@ -268,27 +268,6 @@ class MiscUtils {
|
|
|
268
268
|
return 'application/octet-stream';
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
|
-
/**
|
|
272
|
-
* Mirrors `getDisplayText` from frontend/src/lib/utils.ts.
|
|
273
|
-
*/
|
|
274
|
-
static getDisplayText(url) {
|
|
275
|
-
try {
|
|
276
|
-
const parsedUrl = new URL(url);
|
|
277
|
-
return parsedUrl.protocol === 'file:'
|
|
278
|
-
? parsedUrl.pathname.split('/').pop() || url
|
|
279
|
-
: parsedUrl.hostname;
|
|
280
|
-
}
|
|
281
|
-
catch {
|
|
282
|
-
return url;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* Mirrors the fallback used in frontend FlowsTable for the display name of a
|
|
287
|
-
* flow, but can also apply to tests.
|
|
288
|
-
*/
|
|
289
|
-
static getDisplayName({ name, web }, fallbackName = 'Untitled') {
|
|
290
|
-
return (name ?? MiscUtils.getDisplayText(web?.targetWebsite ?? '') ?? fallbackName);
|
|
291
|
-
}
|
|
292
271
|
}
|
|
293
272
|
exports.MiscUtils = MiscUtils;
|
|
294
273
|
MiscUtils.DONOBU_VERSION = MiscUtils.getPackageVersion();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { WebTargetConfig } from '../models/RunConfig';
|
|
2
|
+
/**
|
|
3
|
+
* Mirrors `getDisplayText` from frontend/src/lib/utils.ts. Returns the
|
|
4
|
+
* hostname for http(s) URLs, the final path segment for file:// URLs, or
|
|
5
|
+
* the input string unchanged if it can't be parsed as a URL.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getDisplayText(url: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Mirrors the fallback used in frontend FlowsTable for the display name of a
|
|
10
|
+
* flow, but can also apply to tests.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getDisplayName({ name, web }: {
|
|
13
|
+
name: string | null;
|
|
14
|
+
web?: WebTargetConfig;
|
|
15
|
+
}, fallbackName?: string): string;
|
|
16
|
+
//# sourceMappingURL=displayName.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDisplayText = getDisplayText;
|
|
4
|
+
exports.getDisplayName = getDisplayName;
|
|
5
|
+
/**
|
|
6
|
+
* Mirrors `getDisplayText` from frontend/src/lib/utils.ts. Returns the
|
|
7
|
+
* hostname for http(s) URLs, the final path segment for file:// URLs, or
|
|
8
|
+
* the input string unchanged if it can't be parsed as a URL.
|
|
9
|
+
*/
|
|
10
|
+
function getDisplayText(url) {
|
|
11
|
+
try {
|
|
12
|
+
const parsedUrl = new URL(url);
|
|
13
|
+
return parsedUrl.protocol === 'file:'
|
|
14
|
+
? parsedUrl.pathname.split('/').pop() || url
|
|
15
|
+
: parsedUrl.hostname;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return url;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Mirrors the fallback used in frontend FlowsTable for the display name of a
|
|
23
|
+
* flow, but can also apply to tests.
|
|
24
|
+
*/
|
|
25
|
+
function getDisplayName({ name, web }, fallbackName = 'Untitled') {
|
|
26
|
+
return name ?? getDisplayText(web?.targetWebsite ?? '') ?? fallbackName;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=displayName.js.map
|
|
@@ -52,6 +52,7 @@ const UnknownToolException_1 = require("../exceptions/UnknownToolException");
|
|
|
52
52
|
const GptConfig_1 = require("../models/GptConfig");
|
|
53
53
|
const resolveTargetRuntime_1 = require("../targets/resolveTargetRuntime");
|
|
54
54
|
const CustomToolRunnerTool_1 = require("../tools/CustomToolRunnerTool");
|
|
55
|
+
const displayName_1 = require("../utils/displayName");
|
|
55
56
|
const FlowLogBuffer_1 = require("../utils/FlowLogBuffer");
|
|
56
57
|
const Logger_1 = require("../utils/Logger");
|
|
57
58
|
const MiscUtils_1 = require("../utils/MiscUtils");
|
|
@@ -294,7 +295,7 @@ class DonobuFlowsManager {
|
|
|
294
295
|
async getFlowAsRerun(flowId, options) {
|
|
295
296
|
const priorFlowMetadata = await this.getFlowById(flowId);
|
|
296
297
|
const toolCallsOnStart = await this.getToolCallsForRerun(priorFlowMetadata, options);
|
|
297
|
-
return this.getFlowFromConfigAndToolCalls(
|
|
298
|
+
return this.getFlowFromConfigAndToolCalls((0, displayName_1.getDisplayName)(priorFlowMetadata, 'Untitled Flow'), 'DETERMINISTIC', priorFlowMetadata, toolCallsOnStart);
|
|
298
299
|
}
|
|
299
300
|
/**
|
|
300
301
|
* Takes a RunConfig object, or anything derived from it (FlowMetadata,
|
|
@@ -4,7 +4,7 @@ exports.TestsManager = void 0;
|
|
|
4
4
|
const crypto_1 = require("crypto");
|
|
5
5
|
const CannotDeleteRunningFlowException_1 = require("../exceptions/CannotDeleteRunningFlowException");
|
|
6
6
|
const TestNotFoundException_1 = require("../exceptions/TestNotFoundException");
|
|
7
|
-
const
|
|
7
|
+
const displayName_1 = require("../utils/displayName");
|
|
8
8
|
const FederatedPagination_1 = require("./FederatedPagination");
|
|
9
9
|
class TestsManager {
|
|
10
10
|
constructor(testsPersistenceRegistry, flowsManager) {
|
|
@@ -22,7 +22,7 @@ class TestsManager {
|
|
|
22
22
|
const testMetadata = {
|
|
23
23
|
id: testId,
|
|
24
24
|
metadataVersion: 1,
|
|
25
|
-
name:
|
|
25
|
+
name: (0, displayName_1.getDisplayName)({ name: params.name ?? null, web }),
|
|
26
26
|
suiteId: params.suiteId ?? null,
|
|
27
27
|
nextRunMode: params.nextRunMode ?? 'AUTONOMOUS',
|
|
28
28
|
target: params.target,
|
|
@@ -13,7 +13,6 @@ export declare const CreateTestSchema: z.ZodObject<{
|
|
|
13
13
|
javascript: z.ZodString;
|
|
14
14
|
}, z.core.$strip>>>>;
|
|
15
15
|
overallObjective: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
16
|
-
allowedTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
17
16
|
resultJsonSchema: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
18
17
|
callbackUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
19
18
|
maxToolCalls: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
@@ -140,6 +139,7 @@ export declare const CreateTestSchema: z.ZodObject<{
|
|
|
140
139
|
INSTRUCT: "INSTRUCT";
|
|
141
140
|
DETERMINISTIC: "DETERMINISTIC";
|
|
142
141
|
}>>>;
|
|
142
|
+
allowedTools: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
|
|
143
143
|
}, z.core.$loose>;
|
|
144
144
|
export type CreateTest = z.infer<typeof CreateTestSchema>;
|
|
145
145
|
//# sourceMappingURL=CreateTest.d.ts.map
|
|
@@ -36,6 +36,12 @@ exports.CreateTestSchema = RunConfig_1.RunConfigSchema.partial()
|
|
|
36
36
|
nextRunMode: RunMode_1.RunModeSchema.nullable()
|
|
37
37
|
.optional()
|
|
38
38
|
.describe('The run mode to use when creating flows from this test. Defaults to AUTONOMOUS.'),
|
|
39
|
+
// `allowedTools` is the only field in RunConfig that isn't already
|
|
40
|
+
// nullable at the base, so `.partial()` above only adds `.optional()`
|
|
41
|
+
// (accepts `undefined`, not `null`). Manual-test creation sends `null`
|
|
42
|
+
// here — mirror the nullable override that CreateDonobuFlowSchema uses
|
|
43
|
+
// so the two creation endpoints accept the same payload shape.
|
|
44
|
+
allowedTools: RunConfig_1.RunConfigSchema.shape.allowedTools.nullable().optional(),
|
|
39
45
|
})
|
|
40
46
|
.loose();
|
|
41
47
|
//# sourceMappingURL=CreateTest.js.map
|