@opice/harness 0.4.0 → 0.5.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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/reporter.d.ts +11 -0
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +2 -0
- package/dist/reporter.js.map +1 -1
- package/dist/scenario.d.ts +52 -8
- package/dist/scenario.d.ts.map +1 -1
- package/dist/scenario.js +152 -25
- package/dist/scenario.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/reporter.ts +13 -0
- package/src/scenario.ts +172 -26
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { el, tid, waitFor, wait, evalJs, screenshot } from './element.js';
|
|
|
2
2
|
export { byLabel, byRole, byText } from './accessible.js';
|
|
3
3
|
export { back, currentPath, currentUrl, forward, open, reload } from './navigation.js';
|
|
4
4
|
export { getPage, getContext } from './context.js';
|
|
5
|
-
export { browserTest, invariant, step } from './scenario.js';
|
|
5
|
+
export { browserTest, DEFAULT_WALKTHROUGH_TIMEOUT_MS, invariant, step } from './scenario.js';
|
|
6
6
|
export type { BrowserTestMeta, StepContract } from './scenario.js';
|
|
7
7
|
export { getReporter, setReporter, configureFromEnv } from './reporter.js';
|
|
8
8
|
export type { Reporter, ReporterConfig, StepEvent, ScenarioStart, ScenarioFinish } from './reporter.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAEzD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAEtF,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAElD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAEzD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAEtF,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAElD,OAAO,EAAE,WAAW,EAAE,8BAA8B,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AAC5F,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAElE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAC1E,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEvG,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAExC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAC5G,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEvD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC7D,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAI9C,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAGzC,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { el, tid, waitFor, wait, evalJs, screenshot } from './element.js';
|
|
|
2
2
|
export { byLabel, byRole, byText } from './accessible.js';
|
|
3
3
|
export { back, currentPath, currentUrl, forward, open, reload } from './navigation.js';
|
|
4
4
|
export { getPage, getContext } from './context.js';
|
|
5
|
-
export { browserTest, invariant, step } from './scenario.js';
|
|
5
|
+
export { browserTest, DEFAULT_WALKTHROUGH_TIMEOUT_MS, invariant, step } from './scenario.js';
|
|
6
6
|
export { getReporter, setReporter, configureFromEnv } from './reporter.js';
|
|
7
7
|
export { parseOpiceDsn } from './dsn.js';
|
|
8
8
|
export { command, call, runCommand, makeCtx, loadUserCommands, findUserCommandsFile, z } from './command.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAEzD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAEtF,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAElD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAEzD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAEtF,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAElD,OAAO,EAAE,WAAW,EAAE,8BAA8B,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AAG5F,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAG1E,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAGxC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAG5G,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAG7D,iFAAiF;AACjF,uEAAuE;AACvE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA"}
|
package/dist/reporter.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ export interface ReporterConfig {
|
|
|
24
24
|
}
|
|
25
25
|
export interface StepEvent {
|
|
26
26
|
scenarioId: string;
|
|
27
|
+
/**
|
|
28
|
+
* 0-based retry attempt that produced this step. The platform shows only the
|
|
29
|
+
* final attempt's steps; earlier attempts are kept for forensics. Defaults
|
|
30
|
+
* to 0 on the platform side when omitted (older clients).
|
|
31
|
+
*/
|
|
32
|
+
attempt?: number;
|
|
27
33
|
/** Authoring order within the scenario, assigned at step() call time. */
|
|
28
34
|
sequence: number;
|
|
29
35
|
/**
|
|
@@ -65,6 +71,11 @@ export interface ScenarioFinish {
|
|
|
65
71
|
scenarioId: string;
|
|
66
72
|
status: 'passed' | 'failed';
|
|
67
73
|
durationMs: number;
|
|
74
|
+
/**
|
|
75
|
+
* Total attempts the scenario took (>= 1). A passed scenario with
|
|
76
|
+
* `attempts > 1` is flaky. Omitted ⇒ the platform defaults it to 1.
|
|
77
|
+
*/
|
|
78
|
+
attempts?: number;
|
|
68
79
|
}
|
|
69
80
|
export interface Reporter {
|
|
70
81
|
startScenario(input: ScenarioStart): Promise<string>;
|
package/dist/reporter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAaH,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oEAAoE;IACpE,MAAM,CAAC,EAAE,IAAI,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,SAAS;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;OAKG;IACH,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,GAAG,SAAS,CAAA;IAC/D,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC3B,UAAU,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAaH,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oEAAoE;IACpE,MAAM,CAAC,EAAE,IAAI,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,SAAS;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;OAKG;IACH,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,GAAG,SAAS,CAAA;IAC/D,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC3B,UAAU,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,QAAQ;IACxB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACpD,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3C,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACtB;AAWD,eAAO,MAAM,WAAW,QAAwC,CAAA;AAMhE,MAAM,WAAW,UAAU;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;CACb;AAqKD,wBAAgB,WAAW,IAAI,QAAQ,CAEtC;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAEpD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,QAAQ,CA8B/E"}
|
package/dist/reporter.js
CHANGED
|
@@ -93,6 +93,7 @@ class HttpReporter {
|
|
|
93
93
|
? await this.encodeScreenshot(event.screenshotPath)
|
|
94
94
|
: undefined;
|
|
95
95
|
await this.fetch('POST', `/api/v1/runs/${runId}/scenarios/${event.scenarioId}/steps`, {
|
|
96
|
+
attempt: event.attempt,
|
|
96
97
|
sequence: event.sequence,
|
|
97
98
|
kind: event.kind,
|
|
98
99
|
name: event.name,
|
|
@@ -111,6 +112,7 @@ class HttpReporter {
|
|
|
111
112
|
await this.fetch('PATCH', `/api/v1/runs/${runId}/scenarios/${input.scenarioId}`, {
|
|
112
113
|
status: input.status,
|
|
113
114
|
durationMs: input.durationMs,
|
|
115
|
+
attempts: input.attempts,
|
|
114
116
|
});
|
|
115
117
|
}
|
|
116
118
|
async flush() {
|
package/dist/reporter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reporter.js","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,+EAA+E;AAC/E,MAAM,kBAAkB,GAAG,MAAM,CAAA;AACjC,kFAAkF;AAClF,MAAM,eAAe,GAAG,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"reporter.js","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"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"}
|
package/dist/scenario.d.ts
CHANGED
|
@@ -30,19 +30,63 @@ export interface BrowserTestMeta {
|
|
|
30
30
|
seeds?: string[];
|
|
31
31
|
/** Identities / roles the scenario acts as, e.g. `['crmOperator']`. */
|
|
32
32
|
roles?: string[];
|
|
33
|
+
/**
|
|
34
|
+
* One-time scenario setup, run once before the walkthrough (in `beforeAll`) —
|
|
35
|
+
* the place for "establish a precondition the steps assume", e.g. minting
|
|
36
|
+
* auth tokens. Replaces a hand-written `beforeAll` in the body form. Runs
|
|
37
|
+
* before any browser navigation, so it can register cookies/identity the
|
|
38
|
+
* first paint needs.
|
|
39
|
+
*/
|
|
40
|
+
setup?: () => void | Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Per-scenario retry budget (body form only). A flaky scenario that fails
|
|
43
|
+
* then passes within the budget is reported as **passed but flaky** (the
|
|
44
|
+
* dashboard badges it). Each attempt gets a fresh browser + a clean
|
|
45
|
+
* navigation, so a retry can't inherit the failed attempt's page state.
|
|
46
|
+
*
|
|
47
|
+
* Omit to inherit the global default (`opice test --retries=N` / `bun test
|
|
48
|
+
* --retry=N`). Ignored by the legacy registrar form (it can't be retried
|
|
49
|
+
* cleanly — it shares one browser across its `test()` blocks).
|
|
50
|
+
*/
|
|
51
|
+
retries?: number;
|
|
52
|
+
/**
|
|
53
|
+
* Per-scenario timeout (ms) for the walkthrough body. Defaults to
|
|
54
|
+
* {@link DEFAULT_WALKTHROUGH_TIMEOUT_MS}. Body form only.
|
|
55
|
+
*/
|
|
56
|
+
timeout?: number;
|
|
33
57
|
}
|
|
34
58
|
/**
|
|
35
|
-
*
|
|
59
|
+
* Default timeout for a walkthrough body. A real browser walk — first page
|
|
60
|
+
* load, async data, a dev server compiling a chunk on first hit — easily
|
|
61
|
+
* exceeds bun's 5s default; each retrying assertion still bounds itself.
|
|
62
|
+
*/
|
|
63
|
+
export declare const DEFAULT_WALKTHROUGH_TIMEOUT_MS = 60000;
|
|
64
|
+
/**
|
|
65
|
+
* Register a top-level browser test scenario. Two forms, picked automatically:
|
|
66
|
+
*
|
|
67
|
+
* **Body form (preferred)** — pass an **async** function; it IS the walkthrough:
|
|
68
|
+
*
|
|
69
|
+
* browserTest({ name: '…', retries: 2, setup: () => mintTokens() }, async () => {
|
|
70
|
+
* await step('…', async () => { … })
|
|
71
|
+
* })
|
|
72
|
+
*
|
|
73
|
+
* `browserTest` owns the single `test('walkthrough', …)` call, so it honours
|
|
74
|
+
* `meta.retries` (bun `{ retry }`) and `meta.timeout`. Each attempt opens a
|
|
75
|
+
* **fresh** browser context + clean navigation, so a retry never inherits the
|
|
76
|
+
* failed attempt's page state. `meta.setup` runs once before the walkthrough.
|
|
77
|
+
*
|
|
78
|
+
* **Legacy registrar form** — pass a **sync** function that registers its own
|
|
79
|
+
* `beforeAll`/`test`/`describe` blocks (the old multi-test pattern). The browser
|
|
80
|
+
* is launched once in `beforeAll` and shared across those blocks. It can't be
|
|
81
|
+
* retried cleanly (shared state), so `meta.retries` is ignored.
|
|
36
82
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* typically contains nested `describe`/`test` blocks), and tears the browser
|
|
40
|
-
* down in `afterAll`.
|
|
83
|
+
* The two are told apart by whether `fn` is an `AsyncFunction`: a walkthrough
|
|
84
|
+
* body always awaits its steps; a registrar never needs to be async.
|
|
41
85
|
*
|
|
42
|
-
* Metadata is the **first** argument (`{ name, url, hash, feature, seeds,
|
|
43
|
-
*
|
|
86
|
+
* Metadata is the **first** argument (`{ name, url, hash, feature, seeds, roles,
|
|
87
|
+
* setup, retries, timeout }`); `name` is required.
|
|
44
88
|
*/
|
|
45
|
-
export declare function browserTest(meta: BrowserTestMeta, fn: () => void): void;
|
|
89
|
+
export declare function browserTest(meta: BrowserTestMeta, fn: () => void | Promise<void>): void;
|
|
46
90
|
/**
|
|
47
91
|
* The durable contract of a step or invariant, separate from its mechanics.
|
|
48
92
|
*
|
package/dist/scenario.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scenario.d.ts","sourceRoot":"","sources":["../src/scenario.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,eAAe;IAC/B,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAA;IACZ,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;
|
|
1
|
+
{"version":3,"file":"scenario.d.ts","sourceRoot":"","sources":["../src/scenario.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,eAAe;IAC/B,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAA;IACZ,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,QAAS,CAAA;AAwCpD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAwIvF;AAkDD;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC5B,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;CACb;AA+FD,KAAK,QAAQ,GAAG,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAE1C,UAAU,MAAM;IACf,uBAAuB;IACvB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3C,8EAA8E;IAC9E,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrD,qDAAqD;IACrD,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACnE;AAED,UAAU,UAAU;IACnB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpE;;;;;OAKG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACjF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,IAAI,EAAE,MAAM,GAAG,UAa3B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,SAAS,EAAE;IACvB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3C,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAWpE,CAAA"}
|
package/dist/scenario.js
CHANGED
|
@@ -16,6 +16,12 @@ function bunTest() {
|
|
|
16
16
|
return require('bun:test');
|
|
17
17
|
}
|
|
18
18
|
const PLAYGROUND_URL = process.env['PLAYGROUND_URL'] ?? 'http://localhost:15180';
|
|
19
|
+
/**
|
|
20
|
+
* Default timeout for a walkthrough body. A real browser walk — first page
|
|
21
|
+
* load, async data, a dev server compiling a chunk on first hit — easily
|
|
22
|
+
* exceeds bun's 5s default; each retrying assertion still bounds itself.
|
|
23
|
+
*/
|
|
24
|
+
export const DEFAULT_WALKTHROUGH_TIMEOUT_MS = 60_000;
|
|
19
25
|
/**
|
|
20
26
|
* Best-effort capture of the `*.test.ts` path that called `browserTest`, by
|
|
21
27
|
* walking the stack for the first `.test.` frame. Reported so a failed
|
|
@@ -49,16 +55,35 @@ let currentScenarioPending = 0;
|
|
|
49
55
|
// fire-and-forget and would otherwise be sequenced by arrival order at the
|
|
50
56
|
// worker, which screenshot-encoding latency can reshuffle.
|
|
51
57
|
let currentScenarioStepSeq = 0;
|
|
58
|
+
// 0-based index of the current attempt. In the body form the walkthrough wrapper
|
|
59
|
+
// bumps it on every (re-)invocation, so steps carry the attempt that produced
|
|
60
|
+
// them and the dashboard shows only the final one. The legacy form never
|
|
61
|
+
// retries, so it stays 0.
|
|
62
|
+
let currentAttempt = 0;
|
|
52
63
|
/**
|
|
53
|
-
* Register a top-level browser test scenario.
|
|
64
|
+
* Register a top-level browser test scenario. Two forms, picked automatically:
|
|
65
|
+
*
|
|
66
|
+
* **Body form (preferred)** — pass an **async** function; it IS the walkthrough:
|
|
67
|
+
*
|
|
68
|
+
* browserTest({ name: '…', retries: 2, setup: () => mintTokens() }, async () => {
|
|
69
|
+
* await step('…', async () => { … })
|
|
70
|
+
* })
|
|
54
71
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
72
|
+
* `browserTest` owns the single `test('walkthrough', …)` call, so it honours
|
|
73
|
+
* `meta.retries` (bun `{ retry }`) and `meta.timeout`. Each attempt opens a
|
|
74
|
+
* **fresh** browser context + clean navigation, so a retry never inherits the
|
|
75
|
+
* failed attempt's page state. `meta.setup` runs once before the walkthrough.
|
|
59
76
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
77
|
+
* **Legacy registrar form** — pass a **sync** function that registers its own
|
|
78
|
+
* `beforeAll`/`test`/`describe` blocks (the old multi-test pattern). The browser
|
|
79
|
+
* is launched once in `beforeAll` and shared across those blocks. It can't be
|
|
80
|
+
* retried cleanly (shared state), so `meta.retries` is ignored.
|
|
81
|
+
*
|
|
82
|
+
* The two are told apart by whether `fn` is an `AsyncFunction`: a walkthrough
|
|
83
|
+
* body always awaits its steps; a registrar never needs to be async.
|
|
84
|
+
*
|
|
85
|
+
* Metadata is the **first** argument (`{ name, url, hash, feature, seeds, roles,
|
|
86
|
+
* setup, retries, timeout }`); `name` is required.
|
|
62
87
|
*/
|
|
63
88
|
export function browserTest(meta, fn) {
|
|
64
89
|
if (typeof meta === 'string') {
|
|
@@ -71,13 +96,17 @@ export function browserTest(meta, fn) {
|
|
|
71
96
|
}
|
|
72
97
|
const reporter = getReporter();
|
|
73
98
|
const testFile = captureTestFile();
|
|
74
|
-
const { describe, beforeAll, afterAll } = bunTest();
|
|
99
|
+
const { describe, beforeAll, afterAll, test } = bunTest();
|
|
100
|
+
// An async fn is the walkthrough body (browserTest owns its test()); a sync
|
|
101
|
+
// fn is the legacy registrar (it registers its own test()/hooks).
|
|
102
|
+
const isBody = fn.constructor.name === 'AsyncFunction';
|
|
75
103
|
describe(meta.name, () => {
|
|
76
104
|
beforeAll(async () => {
|
|
77
105
|
currentScenarioStart = Date.now();
|
|
78
|
-
currentScenarioFailures = 0;
|
|
79
106
|
currentScenarioPending = 0;
|
|
107
|
+
currentScenarioFailures = 0;
|
|
80
108
|
currentScenarioStepSeq = 0;
|
|
109
|
+
currentAttempt = 0;
|
|
81
110
|
try {
|
|
82
111
|
currentScenarioId = await reporter.startScenario({
|
|
83
112
|
name: meta.name,
|
|
@@ -91,20 +120,37 @@ export function browserTest(meta, fn) {
|
|
|
91
120
|
catch {
|
|
92
121
|
currentScenarioId = null;
|
|
93
122
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
123
|
+
try {
|
|
124
|
+
// One-time precondition (mint tokens, …), before any navigation.
|
|
125
|
+
if (meta.setup)
|
|
126
|
+
await meta.setup();
|
|
127
|
+
// Body form opens the browser per attempt (in the test wrapper);
|
|
128
|
+
// the legacy registrar shares one browser, launched here once.
|
|
129
|
+
if (!isBody)
|
|
130
|
+
await openScenario(meta);
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
// Setup failed before any step ran. bun:test does NOT run afterAll
|
|
134
|
+
// when beforeAll throws, so the scenario started above would otherwise
|
|
135
|
+
// sit on the dashboard as 'running' forever — record a synthetic failed
|
|
136
|
+
// step, finish it as failed here, then re-throw so the run stays red.
|
|
137
|
+
await recordSetupFailure(reporter, e);
|
|
138
|
+
if (currentScenarioId) {
|
|
139
|
+
try {
|
|
140
|
+
await reporter.finishScenario({
|
|
141
|
+
scenarioId: currentScenarioId,
|
|
142
|
+
status: 'failed',
|
|
143
|
+
durationMs: Date.now() - currentScenarioStart,
|
|
144
|
+
attempts: 1,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// best-effort
|
|
149
|
+
}
|
|
150
|
+
currentScenarioId = null;
|
|
151
|
+
}
|
|
152
|
+
throw e;
|
|
153
|
+
}
|
|
108
154
|
}, 30_000);
|
|
109
155
|
afterAll(async () => {
|
|
110
156
|
try {
|
|
@@ -135,7 +181,9 @@ export function browserTest(meta, fn) {
|
|
|
135
181
|
const durationMs = Date.now() - currentScenarioStart;
|
|
136
182
|
const status = currentScenarioFailures > 0 ? 'failed' : 'passed';
|
|
137
183
|
try {
|
|
138
|
-
|
|
184
|
+
// attempts = final attempt index + 1. A passed scenario with
|
|
185
|
+
// attempts > 1 failed at least once first → flaky.
|
|
186
|
+
await reporter.finishScenario({ scenarioId: currentScenarioId, status, durationMs, attempts: currentAttempt + 1 });
|
|
139
187
|
}
|
|
140
188
|
catch {
|
|
141
189
|
// best-effort
|
|
@@ -143,9 +191,86 @@ export function browserTest(meta, fn) {
|
|
|
143
191
|
}
|
|
144
192
|
currentScenarioId = null;
|
|
145
193
|
}, 30_000);
|
|
146
|
-
|
|
194
|
+
if (isBody) {
|
|
195
|
+
const body = fn;
|
|
196
|
+
const timeout = meta.timeout ?? DEFAULT_WALKTHROUGH_TIMEOUT_MS;
|
|
197
|
+
// Only set `retry` when a budget is configured — leaving it unset lets
|
|
198
|
+
// bun's global `--retry` default apply; passing `retry: 0` overrides it.
|
|
199
|
+
const testOptions = meta.retries === undefined ? { timeout } : { timeout, retry: meta.retries };
|
|
200
|
+
// bun re-runs the test body for every retry attempt; `attempt` counts
|
|
201
|
+
// those invocations (0-based). Each opens a fresh browser + navigation.
|
|
202
|
+
let attempt = -1;
|
|
203
|
+
test('walkthrough', async () => {
|
|
204
|
+
attempt++;
|
|
205
|
+
currentAttempt = attempt;
|
|
206
|
+
currentScenarioFailures = 0;
|
|
207
|
+
currentScenarioStepSeq = 0;
|
|
208
|
+
currentScenarioPending = 0;
|
|
209
|
+
try {
|
|
210
|
+
await openScenario(meta);
|
|
211
|
+
}
|
|
212
|
+
catch (e) {
|
|
213
|
+
// Setup failed: record it (afterAll finishes the scenario) and fail
|
|
214
|
+
// the attempt so bun retries or, once spent, leaves the run red.
|
|
215
|
+
await recordSetupFailure(reporter, e);
|
|
216
|
+
throw e;
|
|
217
|
+
}
|
|
218
|
+
await body();
|
|
219
|
+
}, testOptions);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
// Legacy registrar: it registers its own test()/hooks; the shared
|
|
223
|
+
// browser was opened in beforeAll above.
|
|
224
|
+
fn();
|
|
225
|
+
}
|
|
147
226
|
});
|
|
148
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Open a fresh isolated browser context + page for `meta` and navigate to its
|
|
230
|
+
* scenario URL. `launchPage()` closes any previous context first, so calling
|
|
231
|
+
* this again (a retry attempt) tears down the failed attempt's page cleanly.
|
|
232
|
+
*/
|
|
233
|
+
async function openScenario(meta) {
|
|
234
|
+
const page = await launchPage();
|
|
235
|
+
// Repo-level context setup (browser-setup.ts) runs before the first
|
|
236
|
+
// navigation, so an addInitScript it registers fires before the app's own
|
|
237
|
+
// scripts on first paint.
|
|
238
|
+
const setup = await loadUserSetup();
|
|
239
|
+
if (setup)
|
|
240
|
+
await setup(getContext());
|
|
241
|
+
const base = meta.url ?? PLAYGROUND_URL;
|
|
242
|
+
const url = meta.hash ? `${base}#${meta.hash}` : base;
|
|
243
|
+
// `domcontentloaded`, not the default `load`: an SPA paints after its JS runs
|
|
244
|
+
// and may hold `load` on a slow chunk or long-lived connection, so waiting for
|
|
245
|
+
// `load` flakily times out under CI contention. Readiness is handled by the
|
|
246
|
+
// test's retrying assertions.
|
|
247
|
+
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Record a synthetic failed 'scenario setup' step for the current attempt and
|
|
251
|
+
* count it toward scenario failures. Does NOT finish the scenario (the caller
|
|
252
|
+
* decides whether afterAll will, or whether it must finish inline).
|
|
253
|
+
*/
|
|
254
|
+
async function recordSetupFailure(reporter, e) {
|
|
255
|
+
currentScenarioFailures++;
|
|
256
|
+
if (!currentScenarioId)
|
|
257
|
+
return;
|
|
258
|
+
try {
|
|
259
|
+
await reporter.recordStep({
|
|
260
|
+
scenarioId: currentScenarioId,
|
|
261
|
+
attempt: currentAttempt,
|
|
262
|
+
sequence: currentScenarioStepSeq++,
|
|
263
|
+
kind: 'step',
|
|
264
|
+
name: 'scenario setup',
|
|
265
|
+
status: 'failed',
|
|
266
|
+
durationMs: Date.now() - currentScenarioStart,
|
|
267
|
+
error: e instanceof Error ? e.message : String(e),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// best-effort: reporting the failure must never mask the original error.
|
|
272
|
+
}
|
|
273
|
+
}
|
|
149
274
|
async function runUnit(unit) {
|
|
150
275
|
const reporter = getReporter();
|
|
151
276
|
// Capture order at call time, before the fire-and-forget record below.
|
|
@@ -163,6 +288,7 @@ async function runUnit(unit) {
|
|
|
163
288
|
if (currentScenarioId) {
|
|
164
289
|
void reporter.recordStep({
|
|
165
290
|
scenarioId: currentScenarioId,
|
|
291
|
+
attempt: currentAttempt,
|
|
166
292
|
sequence,
|
|
167
293
|
kind: unit.kind,
|
|
168
294
|
name: unit.name,
|
|
@@ -212,6 +338,7 @@ async function runUnit(unit) {
|
|
|
212
338
|
if (currentScenarioId) {
|
|
213
339
|
void reporter.recordStep({
|
|
214
340
|
scenarioId: currentScenarioId,
|
|
341
|
+
attempt: currentAttempt,
|
|
215
342
|
sequence,
|
|
216
343
|
kind: unit.kind,
|
|
217
344
|
name: unit.name,
|
package/dist/scenario.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scenario.js","sourceRoot":"","sources":["../src/scenario.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"scenario.js","sourceRoot":"","sources":["../src/scenario.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { back, currentPath, currentUrl, forward, open, reload } from './navigati
|
|
|
6
6
|
|
|
7
7
|
export { getPage, getContext } from './context.js'
|
|
8
8
|
|
|
9
|
-
export { browserTest, invariant, step } from './scenario.js'
|
|
9
|
+
export { browserTest, DEFAULT_WALKTHROUGH_TIMEOUT_MS, invariant, step } from './scenario.js'
|
|
10
10
|
export type { BrowserTestMeta, StepContract } from './scenario.js'
|
|
11
11
|
|
|
12
12
|
export { getReporter, setReporter, configureFromEnv } from './reporter.js'
|
package/src/reporter.ts
CHANGED
|
@@ -37,6 +37,12 @@ export interface ReporterConfig {
|
|
|
37
37
|
|
|
38
38
|
export interface StepEvent {
|
|
39
39
|
scenarioId: string
|
|
40
|
+
/**
|
|
41
|
+
* 0-based retry attempt that produced this step. The platform shows only the
|
|
42
|
+
* final attempt's steps; earlier attempts are kept for forensics. Defaults
|
|
43
|
+
* to 0 on the platform side when omitted (older clients).
|
|
44
|
+
*/
|
|
45
|
+
attempt?: number
|
|
40
46
|
/** Authoring order within the scenario, assigned at step() call time. */
|
|
41
47
|
sequence: number
|
|
42
48
|
/**
|
|
@@ -80,6 +86,11 @@ export interface ScenarioFinish {
|
|
|
80
86
|
scenarioId: string
|
|
81
87
|
status: 'passed' | 'failed'
|
|
82
88
|
durationMs: number
|
|
89
|
+
/**
|
|
90
|
+
* Total attempts the scenario took (>= 1). A passed scenario with
|
|
91
|
+
* `attempts > 1` is flaky. Omitted ⇒ the platform defaults it to 1.
|
|
92
|
+
*/
|
|
93
|
+
attempts?: number
|
|
83
94
|
}
|
|
84
95
|
|
|
85
96
|
export interface Reporter {
|
|
@@ -171,6 +182,7 @@ class HttpReporter implements Reporter {
|
|
|
171
182
|
? await this.encodeScreenshot(event.screenshotPath)
|
|
172
183
|
: undefined
|
|
173
184
|
await this.fetch('POST', `/api/v1/runs/${runId}/scenarios/${event.scenarioId}/steps`, {
|
|
185
|
+
attempt: event.attempt,
|
|
174
186
|
sequence: event.sequence,
|
|
175
187
|
kind: event.kind,
|
|
176
188
|
name: event.name,
|
|
@@ -190,6 +202,7 @@ class HttpReporter implements Reporter {
|
|
|
190
202
|
await this.fetch('PATCH', `/api/v1/runs/${runId}/scenarios/${input.scenarioId}`, {
|
|
191
203
|
status: input.status,
|
|
192
204
|
durationMs: input.durationMs,
|
|
205
|
+
attempts: input.attempts,
|
|
193
206
|
})
|
|
194
207
|
}
|
|
195
208
|
|
package/src/scenario.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { createRequire } from 'node:module'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { closePage, getContext, launchPage } from './context.js'
|
|
4
4
|
import { screenshot } from './element.js'
|
|
5
|
-
import { getReporter } from './reporter.js'
|
|
5
|
+
import { getReporter, type Reporter } from './reporter.js'
|
|
6
6
|
import { loadUserSetup } from './setup.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -51,8 +51,39 @@ export interface BrowserTestMeta {
|
|
|
51
51
|
seeds?: string[]
|
|
52
52
|
/** Identities / roles the scenario acts as, e.g. `['crmOperator']`. */
|
|
53
53
|
roles?: string[]
|
|
54
|
+
/**
|
|
55
|
+
* One-time scenario setup, run once before the walkthrough (in `beforeAll`) —
|
|
56
|
+
* the place for "establish a precondition the steps assume", e.g. minting
|
|
57
|
+
* auth tokens. Replaces a hand-written `beforeAll` in the body form. Runs
|
|
58
|
+
* before any browser navigation, so it can register cookies/identity the
|
|
59
|
+
* first paint needs.
|
|
60
|
+
*/
|
|
61
|
+
setup?: () => void | Promise<void>
|
|
62
|
+
/**
|
|
63
|
+
* Per-scenario retry budget (body form only). A flaky scenario that fails
|
|
64
|
+
* then passes within the budget is reported as **passed but flaky** (the
|
|
65
|
+
* dashboard badges it). Each attempt gets a fresh browser + a clean
|
|
66
|
+
* navigation, so a retry can't inherit the failed attempt's page state.
|
|
67
|
+
*
|
|
68
|
+
* Omit to inherit the global default (`opice test --retries=N` / `bun test
|
|
69
|
+
* --retry=N`). Ignored by the legacy registrar form (it can't be retried
|
|
70
|
+
* cleanly — it shares one browser across its `test()` blocks).
|
|
71
|
+
*/
|
|
72
|
+
retries?: number
|
|
73
|
+
/**
|
|
74
|
+
* Per-scenario timeout (ms) for the walkthrough body. Defaults to
|
|
75
|
+
* {@link DEFAULT_WALKTHROUGH_TIMEOUT_MS}. Body form only.
|
|
76
|
+
*/
|
|
77
|
+
timeout?: number
|
|
54
78
|
}
|
|
55
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Default timeout for a walkthrough body. A real browser walk — first page
|
|
82
|
+
* load, async data, a dev server compiling a chunk on first hit — easily
|
|
83
|
+
* exceeds bun's 5s default; each retrying assertion still bounds itself.
|
|
84
|
+
*/
|
|
85
|
+
export const DEFAULT_WALKTHROUGH_TIMEOUT_MS = 60_000
|
|
86
|
+
|
|
56
87
|
/**
|
|
57
88
|
* Best-effort capture of the `*.test.ts` path that called `browserTest`, by
|
|
58
89
|
* walking the stack for the first `.test.` frame. Reported so a failed
|
|
@@ -85,19 +116,38 @@ let currentScenarioPending = 0
|
|
|
85
116
|
// fire-and-forget and would otherwise be sequenced by arrival order at the
|
|
86
117
|
// worker, which screenshot-encoding latency can reshuffle.
|
|
87
118
|
let currentScenarioStepSeq = 0
|
|
119
|
+
// 0-based index of the current attempt. In the body form the walkthrough wrapper
|
|
120
|
+
// bumps it on every (re-)invocation, so steps carry the attempt that produced
|
|
121
|
+
// them and the dashboard shows only the final one. The legacy form never
|
|
122
|
+
// retries, so it stays 0.
|
|
123
|
+
let currentAttempt = 0
|
|
88
124
|
|
|
89
125
|
/**
|
|
90
|
-
* Register a top-level browser test scenario.
|
|
126
|
+
* Register a top-level browser test scenario. Two forms, picked automatically:
|
|
127
|
+
*
|
|
128
|
+
* **Body form (preferred)** — pass an **async** function; it IS the walkthrough:
|
|
129
|
+
*
|
|
130
|
+
* browserTest({ name: '…', retries: 2, setup: () => mintTokens() }, async () => {
|
|
131
|
+
* await step('…', async () => { … })
|
|
132
|
+
* })
|
|
133
|
+
*
|
|
134
|
+
* `browserTest` owns the single `test('walkthrough', …)` call, so it honours
|
|
135
|
+
* `meta.retries` (bun `{ retry }`) and `meta.timeout`. Each attempt opens a
|
|
136
|
+
* **fresh** browser context + clean navigation, so a retry never inherits the
|
|
137
|
+
* failed attempt's page state. `meta.setup` runs once before the walkthrough.
|
|
91
138
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
139
|
+
* **Legacy registrar form** — pass a **sync** function that registers its own
|
|
140
|
+
* `beforeAll`/`test`/`describe` blocks (the old multi-test pattern). The browser
|
|
141
|
+
* is launched once in `beforeAll` and shared across those blocks. It can't be
|
|
142
|
+
* retried cleanly (shared state), so `meta.retries` is ignored.
|
|
96
143
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
144
|
+
* The two are told apart by whether `fn` is an `AsyncFunction`: a walkthrough
|
|
145
|
+
* body always awaits its steps; a registrar never needs to be async.
|
|
146
|
+
*
|
|
147
|
+
* Metadata is the **first** argument (`{ name, url, hash, feature, seeds, roles,
|
|
148
|
+
* setup, retries, timeout }`); `name` is required.
|
|
99
149
|
*/
|
|
100
|
-
export function browserTest(meta: BrowserTestMeta, fn: () => void): void {
|
|
150
|
+
export function browserTest(meta: BrowserTestMeta, fn: () => void | Promise<void>): void {
|
|
101
151
|
if (typeof meta === 'string') {
|
|
102
152
|
// Migration aid: the old signature was `browserTest(name, fn, options)`.
|
|
103
153
|
throw new Error(
|
|
@@ -110,14 +160,18 @@ export function browserTest(meta: BrowserTestMeta, fn: () => void): void {
|
|
|
110
160
|
}
|
|
111
161
|
const reporter = getReporter()
|
|
112
162
|
const testFile = captureTestFile()
|
|
113
|
-
const { describe, beforeAll, afterAll } = bunTest()
|
|
163
|
+
const { describe, beforeAll, afterAll, test } = bunTest()
|
|
164
|
+
// An async fn is the walkthrough body (browserTest owns its test()); a sync
|
|
165
|
+
// fn is the legacy registrar (it registers its own test()/hooks).
|
|
166
|
+
const isBody = fn.constructor.name === 'AsyncFunction'
|
|
114
167
|
|
|
115
168
|
describe(meta.name, () => {
|
|
116
169
|
beforeAll(async () => {
|
|
117
170
|
currentScenarioStart = Date.now()
|
|
118
|
-
currentScenarioFailures = 0
|
|
119
171
|
currentScenarioPending = 0
|
|
172
|
+
currentScenarioFailures = 0
|
|
120
173
|
currentScenarioStepSeq = 0
|
|
174
|
+
currentAttempt = 0
|
|
121
175
|
try {
|
|
122
176
|
currentScenarioId = await reporter.startScenario({
|
|
123
177
|
name: meta.name,
|
|
@@ -130,19 +184,33 @@ export function browserTest(meta: BrowserTestMeta, fn: () => void): void {
|
|
|
130
184
|
} catch {
|
|
131
185
|
currentScenarioId = null
|
|
132
186
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
187
|
+
try {
|
|
188
|
+
// One-time precondition (mint tokens, …), before any navigation.
|
|
189
|
+
if (meta.setup) await meta.setup()
|
|
190
|
+
// Body form opens the browser per attempt (in the test wrapper);
|
|
191
|
+
// the legacy registrar shares one browser, launched here once.
|
|
192
|
+
if (!isBody) await openScenario(meta)
|
|
193
|
+
} catch (e) {
|
|
194
|
+
// Setup failed before any step ran. bun:test does NOT run afterAll
|
|
195
|
+
// when beforeAll throws, so the scenario started above would otherwise
|
|
196
|
+
// sit on the dashboard as 'running' forever — record a synthetic failed
|
|
197
|
+
// step, finish it as failed here, then re-throw so the run stays red.
|
|
198
|
+
await recordSetupFailure(reporter, e)
|
|
199
|
+
if (currentScenarioId) {
|
|
200
|
+
try {
|
|
201
|
+
await reporter.finishScenario({
|
|
202
|
+
scenarioId: currentScenarioId,
|
|
203
|
+
status: 'failed',
|
|
204
|
+
durationMs: Date.now() - currentScenarioStart,
|
|
205
|
+
attempts: 1,
|
|
206
|
+
})
|
|
207
|
+
} catch {
|
|
208
|
+
// best-effort
|
|
209
|
+
}
|
|
210
|
+
currentScenarioId = null
|
|
211
|
+
}
|
|
212
|
+
throw e
|
|
213
|
+
}
|
|
146
214
|
}, 30_000)
|
|
147
215
|
|
|
148
216
|
afterAll(async () => {
|
|
@@ -174,7 +242,9 @@ export function browserTest(meta: BrowserTestMeta, fn: () => void): void {
|
|
|
174
242
|
const durationMs = Date.now() - currentScenarioStart
|
|
175
243
|
const status = currentScenarioFailures > 0 ? 'failed' : 'passed'
|
|
176
244
|
try {
|
|
177
|
-
|
|
245
|
+
// attempts = final attempt index + 1. A passed scenario with
|
|
246
|
+
// attempts > 1 failed at least once first → flaky.
|
|
247
|
+
await reporter.finishScenario({ scenarioId: currentScenarioId, status, durationMs, attempts: currentAttempt + 1 })
|
|
178
248
|
} catch {
|
|
179
249
|
// best-effort
|
|
180
250
|
}
|
|
@@ -182,10 +252,84 @@ export function browserTest(meta: BrowserTestMeta, fn: () => void): void {
|
|
|
182
252
|
currentScenarioId = null
|
|
183
253
|
}, 30_000)
|
|
184
254
|
|
|
185
|
-
|
|
255
|
+
if (isBody) {
|
|
256
|
+
const body = fn as () => Promise<void>
|
|
257
|
+
const timeout = meta.timeout ?? DEFAULT_WALKTHROUGH_TIMEOUT_MS
|
|
258
|
+
// Only set `retry` when a budget is configured — leaving it unset lets
|
|
259
|
+
// bun's global `--retry` default apply; passing `retry: 0` overrides it.
|
|
260
|
+
const testOptions = meta.retries === undefined ? { timeout } : { timeout, retry: meta.retries }
|
|
261
|
+
// bun re-runs the test body for every retry attempt; `attempt` counts
|
|
262
|
+
// those invocations (0-based). Each opens a fresh browser + navigation.
|
|
263
|
+
let attempt = -1
|
|
264
|
+
test('walkthrough', async () => {
|
|
265
|
+
attempt++
|
|
266
|
+
currentAttempt = attempt
|
|
267
|
+
currentScenarioFailures = 0
|
|
268
|
+
currentScenarioStepSeq = 0
|
|
269
|
+
currentScenarioPending = 0
|
|
270
|
+
try {
|
|
271
|
+
await openScenario(meta)
|
|
272
|
+
} catch (e) {
|
|
273
|
+
// Setup failed: record it (afterAll finishes the scenario) and fail
|
|
274
|
+
// the attempt so bun retries or, once spent, leaves the run red.
|
|
275
|
+
await recordSetupFailure(reporter, e)
|
|
276
|
+
throw e
|
|
277
|
+
}
|
|
278
|
+
await body()
|
|
279
|
+
}, testOptions)
|
|
280
|
+
} else {
|
|
281
|
+
// Legacy registrar: it registers its own test()/hooks; the shared
|
|
282
|
+
// browser was opened in beforeAll above.
|
|
283
|
+
fn()
|
|
284
|
+
}
|
|
186
285
|
})
|
|
187
286
|
}
|
|
188
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Open a fresh isolated browser context + page for `meta` and navigate to its
|
|
290
|
+
* scenario URL. `launchPage()` closes any previous context first, so calling
|
|
291
|
+
* this again (a retry attempt) tears down the failed attempt's page cleanly.
|
|
292
|
+
*/
|
|
293
|
+
async function openScenario(meta: BrowserTestMeta): Promise<void> {
|
|
294
|
+
const page = await launchPage()
|
|
295
|
+
// Repo-level context setup (browser-setup.ts) runs before the first
|
|
296
|
+
// navigation, so an addInitScript it registers fires before the app's own
|
|
297
|
+
// scripts on first paint.
|
|
298
|
+
const setup = await loadUserSetup()
|
|
299
|
+
if (setup) await setup(getContext())
|
|
300
|
+
const base = meta.url ?? PLAYGROUND_URL
|
|
301
|
+
const url = meta.hash ? `${base}#${meta.hash}` : base
|
|
302
|
+
// `domcontentloaded`, not the default `load`: an SPA paints after its JS runs
|
|
303
|
+
// and may hold `load` on a slow chunk or long-lived connection, so waiting for
|
|
304
|
+
// `load` flakily times out under CI contention. Readiness is handled by the
|
|
305
|
+
// test's retrying assertions.
|
|
306
|
+
await page.goto(url, { waitUntil: 'domcontentloaded' })
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Record a synthetic failed 'scenario setup' step for the current attempt and
|
|
311
|
+
* count it toward scenario failures. Does NOT finish the scenario (the caller
|
|
312
|
+
* decides whether afterAll will, or whether it must finish inline).
|
|
313
|
+
*/
|
|
314
|
+
async function recordSetupFailure(reporter: Reporter, e: unknown): Promise<void> {
|
|
315
|
+
currentScenarioFailures++
|
|
316
|
+
if (!currentScenarioId) return
|
|
317
|
+
try {
|
|
318
|
+
await reporter.recordStep({
|
|
319
|
+
scenarioId: currentScenarioId,
|
|
320
|
+
attempt: currentAttempt,
|
|
321
|
+
sequence: currentScenarioStepSeq++,
|
|
322
|
+
kind: 'step',
|
|
323
|
+
name: 'scenario setup',
|
|
324
|
+
status: 'failed',
|
|
325
|
+
durationMs: Date.now() - currentScenarioStart,
|
|
326
|
+
error: e instanceof Error ? e.message : String(e),
|
|
327
|
+
})
|
|
328
|
+
} catch {
|
|
329
|
+
// best-effort: reporting the failure must never mask the original error.
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
189
333
|
type StepStatus = 'passed' | 'failed' | 'fixme' | 'fixmepass' | 'pending'
|
|
190
334
|
type StepKind = 'step' | 'invariant'
|
|
191
335
|
|
|
@@ -240,6 +384,7 @@ async function runUnit(unit: RunUnit): Promise<void> {
|
|
|
240
384
|
if (currentScenarioId) {
|
|
241
385
|
void reporter.recordStep({
|
|
242
386
|
scenarioId: currentScenarioId,
|
|
387
|
+
attempt: currentAttempt,
|
|
243
388
|
sequence,
|
|
244
389
|
kind: unit.kind,
|
|
245
390
|
name: unit.name,
|
|
@@ -285,6 +430,7 @@ async function runUnit(unit: RunUnit): Promise<void> {
|
|
|
285
430
|
if (currentScenarioId) {
|
|
286
431
|
void reporter.recordStep({
|
|
287
432
|
scenarioId: currentScenarioId,
|
|
433
|
+
attempt: currentAttempt,
|
|
288
434
|
sequence,
|
|
289
435
|
kind: unit.kind,
|
|
290
436
|
name: unit.name,
|