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.
Files changed (87) hide show
  1. package/dist/cli/donobu-cli.js +203 -251
  2. package/dist/codegen/CodeGenerator.js +12 -16
  3. package/dist/esm/cli/donobu-cli.js +203 -251
  4. package/dist/esm/codegen/CodeGenerator.js +12 -16
  5. package/dist/esm/managers/DonobuFlowsManager.js +2 -1
  6. package/dist/esm/managers/TestsManager.js +2 -2
  7. package/dist/esm/models/CreateTest.d.ts +1 -1
  8. package/dist/esm/models/CreateTest.js +6 -0
  9. package/dist/esm/persistence/DonobuSqliteDb.js +102 -0
  10. package/dist/esm/persistence/TestConfigHash.d.ts +11 -0
  11. package/dist/esm/persistence/TestConfigHash.js +31 -0
  12. package/dist/esm/persistence/flows/FlowsPersistenceSqlite.d.ts +0 -9
  13. package/dist/esm/persistence/flows/FlowsPersistenceSqlite.js +4 -33
  14. package/dist/esm/persistence/normalizeFlowMetadata.d.ts +16 -0
  15. package/dist/esm/persistence/normalizeFlowMetadata.js +34 -0
  16. package/dist/esm/reporter/buildReport.d.ts +22 -0
  17. package/dist/esm/reporter/buildReport.js +106 -0
  18. package/dist/esm/reporter/html.d.ts +5 -9
  19. package/dist/esm/reporter/html.js +25 -101
  20. package/dist/esm/reporter/markdown.d.ts +33 -0
  21. package/dist/esm/reporter/markdown.js +62 -0
  22. package/dist/esm/reporter/merge.d.ts +33 -0
  23. package/dist/esm/reporter/merge.js +229 -0
  24. package/dist/esm/reporter/model.d.ts +101 -0
  25. package/dist/esm/reporter/model.js +27 -0
  26. package/dist/{cli/playwright-json-to-html.d.ts → esm/reporter/render.d.ts} +9 -14
  27. package/dist/esm/{cli/playwright-json-to-html.js → reporter/render.js} +52 -152
  28. package/dist/esm/reporter/renderMarkdown.d.ts +11 -0
  29. package/dist/esm/{cli/playwright-json-to-markdown.js → reporter/renderMarkdown.js} +31 -110
  30. package/dist/esm/reporter/renderSlack.d.ts +17 -0
  31. package/dist/esm/reporter/renderSlack.js +100 -0
  32. package/dist/esm/reporter/reportWalk.d.ts +28 -0
  33. package/dist/esm/reporter/reportWalk.js +61 -0
  34. package/dist/esm/reporter/slack.d.ts +93 -0
  35. package/dist/esm/reporter/slack.js +150 -0
  36. package/dist/esm/reporter/stateFile.d.ts +31 -0
  37. package/dist/esm/reporter/stateFile.js +70 -0
  38. package/dist/esm/tools/AssertPageTool.d.ts +2 -2
  39. package/dist/esm/utils/MiscUtils.d.ts +0 -13
  40. package/dist/esm/utils/MiscUtils.js +0 -21
  41. package/dist/esm/utils/displayName.d.ts +16 -0
  42. package/dist/esm/utils/displayName.js +28 -0
  43. package/dist/managers/DonobuFlowsManager.js +2 -1
  44. package/dist/managers/TestsManager.js +2 -2
  45. package/dist/models/CreateTest.d.ts +1 -1
  46. package/dist/models/CreateTest.js +6 -0
  47. package/dist/persistence/DonobuSqliteDb.js +102 -0
  48. package/dist/persistence/TestConfigHash.d.ts +11 -0
  49. package/dist/persistence/TestConfigHash.js +31 -0
  50. package/dist/persistence/flows/FlowsPersistenceSqlite.d.ts +0 -9
  51. package/dist/persistence/flows/FlowsPersistenceSqlite.js +4 -33
  52. package/dist/persistence/normalizeFlowMetadata.d.ts +16 -0
  53. package/dist/persistence/normalizeFlowMetadata.js +34 -0
  54. package/dist/reporter/buildReport.d.ts +22 -0
  55. package/dist/reporter/buildReport.js +106 -0
  56. package/dist/reporter/html.d.ts +5 -9
  57. package/dist/reporter/html.js +25 -101
  58. package/dist/reporter/markdown.d.ts +33 -0
  59. package/dist/reporter/markdown.js +62 -0
  60. package/dist/reporter/merge.d.ts +33 -0
  61. package/dist/reporter/merge.js +229 -0
  62. package/dist/reporter/model.d.ts +101 -0
  63. package/dist/reporter/model.js +27 -0
  64. package/dist/{esm/cli/playwright-json-to-html.d.ts → reporter/render.d.ts} +9 -14
  65. package/dist/{cli/playwright-json-to-html.js → reporter/render.js} +52 -152
  66. package/dist/reporter/renderMarkdown.d.ts +11 -0
  67. package/dist/{cli/playwright-json-to-markdown.js → reporter/renderMarkdown.js} +31 -110
  68. package/dist/reporter/renderSlack.d.ts +17 -0
  69. package/dist/reporter/renderSlack.js +100 -0
  70. package/dist/reporter/reportWalk.d.ts +28 -0
  71. package/dist/reporter/reportWalk.js +61 -0
  72. package/dist/reporter/slack.d.ts +93 -0
  73. package/dist/reporter/slack.js +150 -0
  74. package/dist/reporter/stateFile.d.ts +31 -0
  75. package/dist/reporter/stateFile.js +70 -0
  76. package/dist/tools/AssertPageTool.d.ts +2 -2
  77. package/dist/utils/MiscUtils.d.ts +0 -13
  78. package/dist/utils/MiscUtils.js +0 -21
  79. package/dist/utils/displayName.d.ts +16 -0
  80. package/dist/utils/displayName.js +28 -0
  81. package/package.json +11 -5
  82. package/dist/cli/playwright-json-to-markdown.d.ts +0 -43
  83. package/dist/cli/playwright-json-to-slack-json.d.ts +0 -3
  84. package/dist/cli/playwright-json-to-slack-json.js +0 -214
  85. package/dist/esm/cli/playwright-json-to-markdown.d.ts +0 -43
  86. package/dist/esm/cli/playwright-json-to-slack-json.d.ts +0 -3
  87. 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(MiscUtils_1.MiscUtils.getDisplayName(priorFlowMetadata, 'Untitled Flow'), 'DETERMINISTIC', priorFlowMetadata, toolCallsOnStart);
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 MiscUtils_1 = require("../utils/MiscUtils");
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: MiscUtils_1.MiscUtils.getDisplayName({ name: params.name ?? null, web }),
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