e2e 0.16.0 → 0.18.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/README.md +10 -0
- package/dist/.build.json +1 -1
- package/dist/agent/act.js +7 -4
- package/dist/agent/action-dispatcher.js +4 -5
- package/dist/agent/actions.d.ts +32 -23
- package/dist/agent/actions.js +38 -24
- package/dist/agent/ai-sdk.js +5 -4
- package/dist/agent/error.js +3 -3
- package/dist/agent/invocation.js +3 -4
- package/dist/agent/model/sdk.js +2 -0
- package/dist/agent/model/tool-choice.d.ts +9 -0
- package/dist/agent/model/tool-choice.js +14 -0
- package/dist/agent/observation-feed.d.ts +5 -6
- package/dist/agent/observation-feed.js +8 -11
- package/dist/agent/observation.d.ts +40 -11
- package/dist/agent/observation.js +109 -59
- package/dist/agent/phases.d.ts +7 -1
- package/dist/agent/phases.js +12 -3
- package/dist/agent/point-tap.d.ts +8 -8
- package/dist/agent/point-tap.js +1 -1
- package/dist/agent/prompts.js +3 -0
- package/dist/agent/replay.d.ts +19 -8
- package/dist/agent/replay.js +29 -29
- package/dist/agent/scroll-target.d.ts +2 -3
- package/dist/agent/step-cache.d.ts +43 -15
- package/dist/agent/step-cache.js +156 -67
- package/dist/agent/tool-loop.js +94 -5
- package/dist/cache/anchors.d.ts +72 -23
- package/dist/cache/anchors.js +233 -60
- package/dist/cache/context.d.ts +25 -14
- package/dist/cache/context.js +50 -8
- package/dist/cache/decide.d.ts +2 -2
- package/dist/cache/decide.js +4 -6
- package/dist/cache/identity.d.ts +18 -4
- package/dist/cache/identity.js +9 -4
- package/dist/cache/recorder.d.ts +15 -1
- package/dist/cache/recorder.js +36 -23
- package/dist/cache/rekeyed.d.ts +46 -0
- package/dist/cache/rekeyed.js +89 -0
- package/dist/cache/relocate.d.ts +14 -14
- package/dist/cache/relocate.js +33 -25
- package/dist/cache/route.d.ts +32 -33
- package/dist/cache/route.js +94 -50
- package/dist/cache/store.d.ts +6 -0
- package/dist/cache/store.js +21 -1
- package/dist/cache/trace.d.ts +53 -14
- package/dist/cache/trace.js +76 -30
- package/dist/cli/bin.js +2 -2
- package/dist/cli/index.js +43 -32
- package/dist/cli/init/engines.d.ts +1 -3
- package/dist/cli/init/engines.js +2 -2
- package/dist/cli/init/gateways.d.ts +1 -1
- package/dist/cli/init/gateways.js +11 -1
- package/dist/cli/init/mcp-config.d.ts +2 -1
- package/dist/cli/init/mcp-config.js +8 -3
- package/dist/cli/init/sibling-versions.json +1 -1
- package/dist/cli/init/versions.d.ts +0 -8
- package/dist/cli/init/versions.js +1 -10
- package/dist/cli/mcp.d.ts +3 -2
- package/dist/cli/mcp.js +52 -16
- package/dist/cli/run-output.d.ts +17 -0
- package/dist/cli/run-output.js +45 -0
- package/dist/collect/collect.js +8 -4
- package/dist/collect/registry.js +27 -16
- package/dist/config/compiled-files.d.ts +36 -0
- package/dist/config/compiled-files.js +50 -0
- package/dist/config/diagnose.js +21 -19
- package/dist/config/esm-hooks.d.ts +39 -44
- package/dist/config/esm-hooks.js +238 -63
- package/dist/config/load.d.ts +1 -7
- package/dist/config/load.js +23 -66
- package/dist/config/register.d.ts +8 -0
- package/dist/config/register.js +9 -0
- package/dist/config/resolve.d.ts +1 -0
- package/dist/config/resolve.js +33 -1
- package/dist/config/specifiers.d.ts +19 -0
- package/dist/config/specifiers.js +33 -0
- package/dist/config/tsconfig.d.ts +56 -0
- package/dist/config/tsconfig.js +115 -0
- package/dist/config/typescript.d.ts +19 -0
- package/dist/config/typescript.js +253 -0
- package/dist/engine/contract.d.ts +1 -0
- package/dist/engine/index.d.ts +4 -2
- package/dist/engine/session.js +2 -2
- package/dist/expect/async.js +4 -2
- package/dist/expect/poll-scope.d.ts +52 -0
- package/dist/expect/poll-scope.js +113 -0
- package/dist/expect/poll.js +33 -6
- package/dist/expect/values.js +11 -4
- package/dist/internal/abandoned.d.ts +11 -0
- package/dist/internal/abandoned.js +23 -0
- package/dist/internal/brands.d.ts +2 -0
- package/dist/internal/brands.js +2 -0
- package/dist/internal/client-identity.d.ts +9 -0
- package/dist/internal/client-identity.js +14 -0
- package/dist/internal/errors.d.ts +10 -3
- package/dist/internal/errors.js +23 -8
- package/dist/internal/node-version.d.ts +16 -7
- package/dist/internal/node-version.js +50 -15
- package/dist/internal/package-manager.js +1 -1
- package/dist/internal/realm-slot.d.ts +5 -4
- package/dist/internal/redact.d.ts +9 -4
- package/dist/internal/redact.js +120 -23
- package/dist/internal/time.d.ts +26 -4
- package/dist/internal/time.js +58 -14
- package/dist/internal/urls.d.ts +9 -5
- package/dist/internal/urls.js +19 -10
- package/dist/locator/engine.d.ts +12 -2
- package/dist/locator/engine.js +55 -14
- package/dist/locator/screen.js +59 -9
- package/dist/mcp/catalog.d.ts +3 -2
- package/dist/mcp/catalog.js +6 -6
- package/dist/mcp/output.d.ts +59 -0
- package/dist/mcp/output.js +129 -0
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.js +9 -12
- package/dist/mcp/session.d.ts +8 -1
- package/dist/mcp/session.js +56 -15
- package/dist/mcp/tools.js +7 -5
- package/dist/oauth/chatgpt.js +3 -23
- package/dist/oauth/cli.js +1 -0
- package/dist/oauth/copilot.d.ts +5 -4
- package/dist/oauth/copilot.js +80 -7
- package/dist/oauth/grok.js +2 -1
- package/dist/oauth/models.js +2 -1
- package/dist/oauth/opencode-console.d.ts +10 -0
- package/dist/oauth/opencode-console.js +133 -0
- package/dist/oauth/providers/github-copilot.d.ts +12 -0
- package/dist/oauth/providers/github-copilot.js +77 -23
- package/dist/oauth/providers/opencode-console.d.ts +31 -0
- package/dist/oauth/providers/opencode-console.js +148 -0
- package/dist/oauth/providers.d.ts +2 -6
- package/dist/oauth/providers.js +3 -7
- package/dist/oauth/responses.d.ts +9 -0
- package/dist/oauth/responses.js +23 -0
- package/dist/report/build.d.ts +26 -0
- package/dist/report/build.js +16 -8
- package/dist/report/failure-text.d.ts +6 -1
- package/dist/report/failure-text.js +14 -4
- package/dist/report/format.d.ts +10 -5
- package/dist/report/format.js +12 -5
- package/dist/report/junit.js +11 -12
- package/dist/report/list.d.ts +12 -0
- package/dist/report/list.js +61 -24
- package/dist/report/markdown.d.ts +1 -1
- package/dist/report/markdown.js +37 -14
- package/dist/report/repeats.d.ts +6 -1
- package/dist/report/repeats.js +12 -3
- package/dist/run/artifacts.d.ts +22 -4
- package/dist/run/artifacts.js +71 -3
- package/dist/run/execute.d.ts +34 -5
- package/dist/run/execute.js +93 -44
- package/dist/run/extended-fixtures.js +6 -2
- package/dist/run/failure-evidence.js +10 -4
- package/dist/run/fixtures.js +6 -8
- package/dist/run/in-process.d.ts +2 -0
- package/dist/run/in-process.js +52 -10
- package/dist/run/last-run.d.ts +42 -8
- package/dist/run/last-run.js +121 -14
- package/dist/run/process-output.d.ts +73 -0
- package/dist/run/process-output.js +130 -0
- package/dist/run/provision.d.ts +2 -0
- package/dist/run/provision.js +1 -0
- package/dist/run/realm.d.ts +4 -1
- package/dist/run/realm.js +20 -6
- package/dist/run/records.d.ts +20 -4
- package/dist/run/records.js +26 -0
- package/dist/run/retry.d.ts +10 -1
- package/dist/run/retry.js +30 -12
- package/dist/run/runner.d.ts +9 -1
- package/dist/run/runner.js +101 -39
- package/dist/run/scheduler.js +255 -19
- package/dist/run/secrecy.d.ts +22 -19
- package/dist/run/secrecy.js +22 -18
- package/dist/run/serial.d.ts +75 -2
- package/dist/run/serial.js +243 -59
- package/dist/run/standalone.js +1 -1
- package/dist/run/steps.d.ts +0 -6
- package/dist/run/steps.js +3 -20
- package/dist/run/trace-redaction.d.ts +5 -6
- package/dist/run/trace-redaction.js +5 -6
- package/dist/run/worker/entry.js +40 -18
- package/dist/run/worker/handle.js +10 -5
- package/dist/run/worker/protocol.d.ts +85 -3
- package/dist/run/worker/session.d.ts +2 -0
- package/dist/run/worker/session.js +29 -0
- package/dist/telemetry/environment.d.ts +2 -1
- package/dist/telemetry/environment.js +12 -2
- package/dist/telemetry/events.js +1 -0
- package/dist/telemetry/project.d.ts +5 -6
- package/dist/telemetry/project.js +6 -11
- package/dist/telemetry/store.d.ts +5 -5
- package/dist/telemetry/store.js +5 -7
- package/dist/telemetry/telemetry.d.ts +2 -3
- package/dist/telemetry/telemetry.js +2 -4
- package/dist/types.d.ts +15 -6
- package/docs/agent-steps.mdx +1 -0
- package/docs/agents.mdx +7 -3
- package/docs/api-testing.mdx +2 -1
- package/docs/authentication.mdx +6 -3
- package/docs/browser.mdx +49 -12
- package/docs/bug-bash.mdx +2 -1
- package/docs/cache.mdx +66 -19
- package/docs/ci.mdx +14 -9
- package/docs/coding-agents.mdx +2 -1
- package/docs/core-concepts.mdx +208 -0
- package/docs/debugging.mdx +25 -12
- package/docs/decision-models.mdx +142 -0
- package/docs/example-projects.mdx +32 -0
- package/docs/executors.mdx +7 -2
- package/docs/explore.mdx +1 -0
- package/docs/github.mdx +14 -5
- package/docs/index.mdx +9 -5
- package/docs/integrations/eas.mdx +1 -0
- package/docs/integrations/index.mdx +1 -0
- package/docs/integrations/kernel.mdx +3 -2
- package/docs/migrate/cypress.mdx +26 -3
- package/docs/migrate/detox.mdx +24 -1
- package/docs/migrate/maestro.mdx +28 -4
- package/docs/migrate/playwright.mdx +44 -18
- package/docs/migrate/selenium.mdx +27 -3
- package/docs/mobile-ci.mdx +6 -5
- package/docs/mobile.mdx +96 -8
- package/docs/models.mdx +60 -9
- package/docs/quickstart.mdx +28 -16
- package/docs/reference/agent.mdx +13 -3
- package/docs/reference/app.mdx +2 -1
- package/docs/reference/cli.mdx +41 -18
- package/docs/reference/config.mdx +67 -13
- package/docs/reference/engine.mdx +25 -13
- package/docs/reference/environment.mdx +10 -2
- package/docs/reference/errors.mdx +13 -10
- package/docs/reference/expect.mdx +36 -13
- package/docs/reference/mcp.mdx +21 -8
- package/docs/reference/mobile.mdx +24 -13
- package/docs/reference/reporters.mdx +9 -2
- package/docs/reference/screen.mdx +49 -16
- package/docs/reference/test.mdx +32 -5
- package/docs/reference/web.mdx +133 -23
- package/docs/security.mdx +58 -42
- package/docs/subscriptions.mdx +92 -12
- package/docs/telemetry.mdx +9 -6
- package/docs/tools.mdx +1 -0
- package/docs/{starting-your-app.mdx → web.mdx} +157 -11
- package/docs/writing-an-engine.mdx +2 -1
- package/package.json +25 -8
- package/schema/report-v1.schema.json +14 -1
- package/skills/e2e/SKILL.md +5 -3
- package/skills/e2e/references/agent.md +33 -11
- package/skills/e2e/references/debugging.md +4 -4
- package/skills/e2e/references/mcp.md +8 -3
- package/skills/e2e/references/running.md +15 -9
- package/skills/e2e/references/setup.md +45 -16
- package/skills/e2e/references/writing-tests.md +35 -18
- package/docs/viewports.mdx +0 -146
- package/docs/writing-tests.mdx +0 -256
package/dist/run/last-run.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** What `--last-failed` reads back from the previous run's report. */
|
|
2
2
|
import type { Report1Document } from '../report/build.ts';
|
|
3
|
+
type ReportCarried = NonNullable<Report1Document['run']['carried']>;
|
|
3
4
|
/**
|
|
4
5
|
* The previous run's report, read from where this run will write its own. It
|
|
5
6
|
* is what `--last-failed` selects from, and reporters receive it as
|
|
@@ -9,13 +10,46 @@ import type { Report1Document } from '../report/build.ts';
|
|
|
9
10
|
export declare function readLastRun(reportPath: string): Promise<Report1Document>;
|
|
10
11
|
/**
|
|
11
12
|
* The result ids of a report's tests that did not pass: failed, timed out,
|
|
12
|
-
* interrupted, or skipped because a setup, a serial predecessor, a hook,
|
|
13
|
-
* the
|
|
14
|
-
* `afterAll` that failed, until a run where that hook
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
13
|
+
* interrupted, or skipped because a setup, a serial predecessor, a hook, the
|
|
14
|
+
* worker, or the failure limit stopped it, every selected test in the scope
|
|
15
|
+
* of a `beforeAll` or `afterAll` that failed, until a run where that hook
|
|
16
|
+
* passes, and every test the report carries from an earlier run that it did
|
|
17
|
+
* not run again. A report older than `selected` counts every result as
|
|
18
|
+
* selected. An explicit skip and a filtered test passed in the sense that
|
|
19
|
+
* matters here: nothing to run again. The ids are
|
|
20
|
+
* `resultId(testId, target, agent)`, so a test is named per target and agent
|
|
21
|
+
* whichever of its `--repeat-each` runs did not pass.
|
|
20
22
|
*/
|
|
21
23
|
export declare function lastFailedIds(document: Report1Document): ReadonlySet<string>;
|
|
24
|
+
/** What a rerun's collection found: the tests it collected by id, and the files it discovered but could not collect. */
|
|
25
|
+
export interface RerunCollection {
|
|
26
|
+
readonly testIds: ReadonlySet<string>;
|
|
27
|
+
readonly uncollectedFiles: ReadonlySet<string>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* What a `--last-failed` rerun carries forward from the report it selected
|
|
31
|
+
* from: every result that report owed a rerun and this run did not run, as
|
|
32
|
+
* the run that last ran it reported it; the serial groups those results'
|
|
33
|
+
* attempts live on; and the suite hook failures whose scope still holds one
|
|
34
|
+
* of them. A result is owed when it did not pass, or when it is in the scope
|
|
35
|
+
* of a failed `beforeAll` or `afterAll` this run did not see pass: a hook
|
|
36
|
+
* whose scope this run ran again without it failing is resolved, and one
|
|
37
|
+
* that failed again is this run's own error, which still makes its left-out
|
|
38
|
+
* tests owed. A test this run did not run is one it collected (`collected`,
|
|
39
|
+
* by test id), on a target the config still has, that it did not select:
|
|
40
|
+
* left out by a filter, on a target or agent the run did not ask for, or a
|
|
41
|
+
* setup no selected test needed, which the report lists no row for. A test in
|
|
42
|
+
* a file the run found but could not collect (`uncollectedFiles`, a narrowed
|
|
43
|
+
* run past a file that fails to import) is still there and stays carried. A
|
|
44
|
+
* test the run no longer collects cannot run again and is dropped. A test
|
|
45
|
+
* this run selected is no longer carried, whatever its outcome: its own
|
|
46
|
+
* result says it now. Undefined when nothing is carried.
|
|
47
|
+
*/
|
|
48
|
+
export declare function carryForward(lastRun: Report1Document, current: Report1Document, collected: RerunCollection): ReportCarried | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* The artifact paths a report names, relative to the artifacts root: every
|
|
51
|
+
* attempt's of its results and serial groups, and of what it carries. What a
|
|
52
|
+
* `--last-failed` rerun keeps of the tree when it starts.
|
|
53
|
+
*/
|
|
54
|
+
export declare function reportArtifactPaths(document: Report1Document): ReadonlySet<string>;
|
|
55
|
+
export {};
|
package/dist/run/last-run.js
CHANGED
|
@@ -8,7 +8,16 @@ const UNDELIVERED_SKIPS = new Set([
|
|
|
8
8
|
'serial-predecessor-failed',
|
|
9
9
|
'hook-failed',
|
|
10
10
|
'infrastructure-unavailable',
|
|
11
|
+
'failure-limit',
|
|
11
12
|
]);
|
|
13
|
+
/** The id `--last-failed` names a result by: its test, target, and agent, whichever its repeat. */
|
|
14
|
+
function rerunId(result) {
|
|
15
|
+
return resultId(result.testId, result.targetId, result.agent);
|
|
16
|
+
}
|
|
17
|
+
/** Whether a run error is a failed `beforeAll` or `afterAll`, the failures `--last-failed` runs the scope of again. */
|
|
18
|
+
function isSuiteHookFailure(error) {
|
|
19
|
+
return error.phase === 'beforeAll' || error.phase === 'afterAll';
|
|
20
|
+
}
|
|
12
21
|
/** Whether a result is one `--last-failed` runs again: it did not pass, or the run never got to it. */
|
|
13
22
|
function didNotPass(result) {
|
|
14
23
|
switch (result.status) {
|
|
@@ -72,19 +81,88 @@ export async function readLastRun(reportPath) {
|
|
|
72
81
|
}
|
|
73
82
|
/**
|
|
74
83
|
* The result ids of a report's tests that did not pass: failed, timed out,
|
|
75
|
-
* interrupted, or skipped because a setup, a serial predecessor, a hook,
|
|
76
|
-
* the
|
|
77
|
-
* `afterAll` that failed, until a run where that hook
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
84
|
+
* interrupted, or skipped because a setup, a serial predecessor, a hook, the
|
|
85
|
+
* worker, or the failure limit stopped it, every selected test in the scope
|
|
86
|
+
* of a `beforeAll` or `afterAll` that failed, until a run where that hook
|
|
87
|
+
* passes, and every test the report carries from an earlier run that it did
|
|
88
|
+
* not run again. A report older than `selected` counts every result as
|
|
89
|
+
* selected. An explicit skip and a filtered test passed in the sense that
|
|
90
|
+
* matters here: nothing to run again. The ids are
|
|
91
|
+
* `resultId(testId, target, agent)`, so a test is named per target and agent
|
|
92
|
+
* whichever of its `--repeat-each` runs did not pass.
|
|
83
93
|
*/
|
|
84
94
|
export function lastFailedIds(document) {
|
|
85
|
-
const hookFailures = document.run.errors.filter(
|
|
95
|
+
const hookFailures = document.run.errors.filter(isSuiteHookFailure);
|
|
86
96
|
const rerun = (result) => didNotPass(result) || (result.selected !== false && hookFailures.some((error) => inHookScope(result, error)));
|
|
87
|
-
|
|
97
|
+
const ids = new Set(document.run.results.filter(rerun).map(rerunId));
|
|
98
|
+
for (const result of document.run.carried?.results ?? [])
|
|
99
|
+
ids.add(rerunId(result));
|
|
100
|
+
return ids;
|
|
101
|
+
}
|
|
102
|
+
/** Whether two suite hook failures are the same hook's: one phase, one scope. */
|
|
103
|
+
function sameHook(a, b) {
|
|
104
|
+
if (a.phase !== b.phase)
|
|
105
|
+
return false;
|
|
106
|
+
if (a.scope === undefined || b.scope === undefined)
|
|
107
|
+
return a.scope === b.scope;
|
|
108
|
+
return (a.scope.file === b.scope.file &&
|
|
109
|
+
a.scope.targetId === b.scope.targetId &&
|
|
110
|
+
a.scope.titlePath.length === b.scope.titlePath.length &&
|
|
111
|
+
a.scope.titlePath.every((title, index) => b.scope.titlePath[index] === title));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* What a `--last-failed` rerun carries forward from the report it selected
|
|
115
|
+
* from: every result that report owed a rerun and this run did not run, as
|
|
116
|
+
* the run that last ran it reported it; the serial groups those results'
|
|
117
|
+
* attempts live on; and the suite hook failures whose scope still holds one
|
|
118
|
+
* of them. A result is owed when it did not pass, or when it is in the scope
|
|
119
|
+
* of a failed `beforeAll` or `afterAll` this run did not see pass: a hook
|
|
120
|
+
* whose scope this run ran again without it failing is resolved, and one
|
|
121
|
+
* that failed again is this run's own error, which still makes its left-out
|
|
122
|
+
* tests owed. A test this run did not run is one it collected (`collected`,
|
|
123
|
+
* by test id), on a target the config still has, that it did not select:
|
|
124
|
+
* left out by a filter, on a target or agent the run did not ask for, or a
|
|
125
|
+
* setup no selected test needed, which the report lists no row for. A test in
|
|
126
|
+
* a file the run found but could not collect (`uncollectedFiles`, a narrowed
|
|
127
|
+
* run past a file that fails to import) is still there and stays carried. A
|
|
128
|
+
* test the run no longer collects cannot run again and is dropped. A test
|
|
129
|
+
* this run selected is no longer carried, whatever its outcome: its own
|
|
130
|
+
* result says it now. Undefined when nothing is carried.
|
|
131
|
+
*/
|
|
132
|
+
export function carryForward(lastRun, current, collected) {
|
|
133
|
+
const selected = new Set(current.run.results.filter((result) => result.selected !== false).map(rerunId));
|
|
134
|
+
const targets = new Set(current.run.targets.map((target) => target.id));
|
|
135
|
+
const stillThere = (result) => collected.testIds.has(result.testId) || collected.uncollectedFiles.has(result.file);
|
|
136
|
+
const notRun = (result) => !selected.has(rerunId(result)) && stillThere(result) && targets.has(result.targetId);
|
|
137
|
+
const failedAgain = current.run.errors.filter(isSuiteHookFailure);
|
|
138
|
+
const hooks = [...lastRun.run.errors.filter(isSuiteHookFailure), ...(lastRun.run.carried?.errors ?? [])];
|
|
139
|
+
const reportedAgain = (error) => failedAgain.some((again) => sameHook(again, error));
|
|
140
|
+
const passedAgain = (error) => !reportedAgain(error) &&
|
|
141
|
+
current.run.results.some((result) => result.selected !== false && result.status !== 'skipped' && inHookScope(result, error));
|
|
142
|
+
const unresolved = hooks.filter((error) => !passedAgain(error));
|
|
143
|
+
// A test the run before left out is known by the row it carried, not by its filtered one.
|
|
144
|
+
const history = [...lastRun.run.results.filter((result) => result.selected !== false), ...(lastRun.run.carried?.results ?? [])];
|
|
145
|
+
const results = history.filter((result) => notRun(result) && (didNotPass(result) || unresolved.some((error) => inHookScope(result, error))));
|
|
146
|
+
if (results.length === 0)
|
|
147
|
+
return undefined;
|
|
148
|
+
const groupIds = new Set(results.flatMap((result) => (result.serialGroupId === undefined ? [] : [result.serialGroupId])));
|
|
149
|
+
const currentGroupIds = new Set(current.run.serialGroups.map((group) => group.id));
|
|
150
|
+
const serialGroups = [...lastRun.run.serialGroups, ...(lastRun.run.carried?.serialGroups ?? [])].filter((group) => groupIds.has(group.id) && !currentGroupIds.has(group.id));
|
|
151
|
+
const errors = unresolved.filter((error) => !reportedAgain(error) && results.some((result) => inHookScope(result, error)));
|
|
152
|
+
return { results, serialGroups, errors };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The artifact paths a report names, relative to the artifacts root: every
|
|
156
|
+
* attempt's of its results and serial groups, and of what it carries. What a
|
|
157
|
+
* `--last-failed` rerun keeps of the tree when it starts.
|
|
158
|
+
*/
|
|
159
|
+
export function reportArtifactPaths(document) {
|
|
160
|
+
const parts = [document.run, ...(document.run.carried === undefined ? [] : [document.run.carried])];
|
|
161
|
+
const attempts = parts.flatMap((part) => [
|
|
162
|
+
...part.results.flatMap((result) => result.attempts),
|
|
163
|
+
...part.serialGroups.flatMap((group) => group.attempts),
|
|
164
|
+
]);
|
|
165
|
+
return new Set(attempts.flatMap((attempt) => attempt.artifacts.flatMap((artifact) => (artifact.path === undefined ? [] : [artifact.path]))));
|
|
88
166
|
}
|
|
89
167
|
/**
|
|
90
168
|
* The shape the read relies on: a report-1 envelope whose every result has
|
|
@@ -96,10 +174,38 @@ function isReport(value) {
|
|
|
96
174
|
return false;
|
|
97
175
|
const document = value;
|
|
98
176
|
return (document.schemaVersion === 'report-1' &&
|
|
99
|
-
|
|
100
|
-
document.run.
|
|
101
|
-
|
|
102
|
-
|
|
177
|
+
isRunPart(document.run) &&
|
|
178
|
+
(document.run.carried === undefined || isRunPart(document.run.carried)));
|
|
179
|
+
}
|
|
180
|
+
/** A run, or what one carries: results and errors of the shape read here, and its serial groups. */
|
|
181
|
+
function isRunPart(value) {
|
|
182
|
+
if (typeof value !== 'object' || value === null)
|
|
183
|
+
return false;
|
|
184
|
+
const part = value;
|
|
185
|
+
return (Array.isArray(part.results) &&
|
|
186
|
+
part.results.every(isResult) &&
|
|
187
|
+
Array.isArray(part.errors) &&
|
|
188
|
+
part.errors.every(isError) &&
|
|
189
|
+
Array.isArray(part.serialGroups) &&
|
|
190
|
+
part.serialGroups.every((group) => typeof group === 'object' && group !== null && hasAttempts(group)));
|
|
191
|
+
}
|
|
192
|
+
/** Whether a result or serial group lists its attempts, each with its artifacts, the way a rerun reads them. */
|
|
193
|
+
function hasAttempts(value) {
|
|
194
|
+
const { attempts } = value;
|
|
195
|
+
return Array.isArray(attempts) && attempts.every(hasArtifacts);
|
|
196
|
+
}
|
|
197
|
+
/** Whether an attempt lists its artifacts as records whose path, when there is one, is a string. */
|
|
198
|
+
function hasArtifacts(attempt) {
|
|
199
|
+
if (typeof attempt !== 'object' || attempt === null)
|
|
200
|
+
return false;
|
|
201
|
+
const { artifacts } = attempt;
|
|
202
|
+
return (Array.isArray(artifacts) &&
|
|
203
|
+
artifacts.every((artifact) => {
|
|
204
|
+
if (typeof artifact !== 'object' || artifact === null)
|
|
205
|
+
return false;
|
|
206
|
+
const { path } = artifact;
|
|
207
|
+
return path === undefined || typeof path === 'string';
|
|
208
|
+
}));
|
|
103
209
|
}
|
|
104
210
|
function isResult(value) {
|
|
105
211
|
if (typeof value !== 'object' || value === null)
|
|
@@ -110,6 +216,7 @@ function isResult(value) {
|
|
|
110
216
|
typeof result.status === 'string' &&
|
|
111
217
|
typeof result.file === 'string' &&
|
|
112
218
|
Array.isArray(result.titlePath) &&
|
|
219
|
+
hasAttempts(result) &&
|
|
113
220
|
(skip === undefined || (typeof skip === 'object' && skip !== null && typeof skip.cause === 'string')));
|
|
114
221
|
}
|
|
115
222
|
function isError(value) {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What user code in an `e2e run` process prints outside a test body: the
|
|
3
|
+
* config's top-level code, a test file's top level while it is collected,
|
|
4
|
+
* and reporters in the runner; the config's top-level code again in each
|
|
5
|
+
* worker. It is redacted with every secret value the process knows, as a
|
|
6
|
+
* test's output is. Until a config has loaded its secrets are unknown, so
|
|
7
|
+
* what is printed meanwhile is held, and withheld if the load fails.
|
|
8
|
+
*/
|
|
9
|
+
import { type SecretLedger } from '../internal/redact.ts';
|
|
10
|
+
import type { ListReporterOutput } from '../report/list.ts';
|
|
11
|
+
export type OutputStream = 'stdout' | 'stderr';
|
|
12
|
+
/** Writes text to one stream of the terminal; the callback fires once the stream took it. Answers false while the stream is backed up, like `Writable.write`. */
|
|
13
|
+
export type TerminalWrite = (text: string, callback?: () => void) => boolean;
|
|
14
|
+
/** Where runner output goes instead of the terminal while the list reporter shows the run. */
|
|
15
|
+
export interface OutputView {
|
|
16
|
+
/** Prints redacted text from `stream`. */
|
|
17
|
+
show(stream: OutputStream, text: string): void;
|
|
18
|
+
/** Prints what `show` left unfinished; nothing more follows. */
|
|
19
|
+
end(): void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Holds what is written while a config loads. Released once the load
|
|
23
|
+
* registered the config's secrets; dropped for a warning when it failed.
|
|
24
|
+
*/
|
|
25
|
+
export declare class ConfigLoadHold {
|
|
26
|
+
private held;
|
|
27
|
+
/** Holds `chunk` if a load is in flight; answers whether it did. */
|
|
28
|
+
hold(stream: OutputStream, chunk: string | Uint8Array): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Runs `load`, which loads a config and registers its secrets, holding
|
|
31
|
+
* what is written meanwhile. Then hands each held write to `release` in
|
|
32
|
+
* order, or, if `load` threw, `warn`s with how much was withheld.
|
|
33
|
+
*/
|
|
34
|
+
during<T>(load: () => Promise<T>, release: (stream: OutputStream, chunk: string | Uint8Array) => void, warn: (line: string) => void): Promise<T>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The runner process's user output, on its way to the terminal or, while
|
|
38
|
+
* the list reporter shows it, through the reporter, which prints it above
|
|
39
|
+
* its live window instead of letting it land inside.
|
|
40
|
+
*/
|
|
41
|
+
export declare class RunnerOutput {
|
|
42
|
+
private readonly terminal;
|
|
43
|
+
/** Each stream redacted on its own, across writes: a value split over two writes is still caught. */
|
|
44
|
+
private readonly redactors;
|
|
45
|
+
private readonly loadHold;
|
|
46
|
+
private view;
|
|
47
|
+
/** The terminal's stdout for the list reporter: its own lines and live window go straight there. */
|
|
48
|
+
readonly listOutput: ListReporterOutput;
|
|
49
|
+
constructor(terminal: Readonly<Record<OutputStream, TerminalWrite>>, ledger: SecretLedger);
|
|
50
|
+
/**
|
|
51
|
+
* One write of user code to `stream`, redacted: the tail a later write
|
|
52
|
+
* could complete into a value waits for it, or for `end`. Held while a
|
|
53
|
+
* config loads. Answers what the terminal did.
|
|
54
|
+
*/
|
|
55
|
+
write(stream: OutputStream, chunk: string | Uint8Array, callback?: () => void): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Runs `load`, a config load that registers the config's secrets, holding
|
|
58
|
+
* what is printed until it ends: the config's top-level code may print a
|
|
59
|
+
* value before the process knows it is one. Released redacted once it
|
|
60
|
+
* loaded; withheld with a warning when it failed.
|
|
61
|
+
*/
|
|
62
|
+
withholdDuring<T>(load: () => Promise<T>): Promise<T>;
|
|
63
|
+
/**
|
|
64
|
+
* Passes what is printed to `view` from now on, or to the terminal again
|
|
65
|
+
* when undefined. What the previous view was given is released to it
|
|
66
|
+
* first, unfinished lines included, and that view ended.
|
|
67
|
+
*/
|
|
68
|
+
showThrough(view: OutputView | undefined): void;
|
|
69
|
+
/** Releases what is still held, redacted, to the terminal. Later writes stay redacted; only a tail a later write could complete into a value is held. */
|
|
70
|
+
end(): void;
|
|
71
|
+
private release;
|
|
72
|
+
private emit;
|
|
73
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What user code in an `e2e run` process prints outside a test body: the
|
|
3
|
+
* config's top-level code, a test file's top level while it is collected,
|
|
4
|
+
* and reporters in the runner; the config's top-level code again in each
|
|
5
|
+
* worker. It is redacted with every secret value the process knows, as a
|
|
6
|
+
* test's output is. Until a config has loaded its secrets are unknown, so
|
|
7
|
+
* what is printed meanwhile is held, and withheld if the load fails.
|
|
8
|
+
*/
|
|
9
|
+
import { StreamRedactor } from '../internal/redact.js';
|
|
10
|
+
/** The warning that stands in for what a config printed before it failed to load. */
|
|
11
|
+
function withheldWarning(bytes) {
|
|
12
|
+
return `withheld ${bytes} bytes of output printed while the config failed to load: its secrets are unknown, so the output cannot be redacted`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Holds what is written while a config loads. Released once the load
|
|
16
|
+
* registered the config's secrets; dropped for a warning when it failed.
|
|
17
|
+
*/
|
|
18
|
+
export class ConfigLoadHold {
|
|
19
|
+
held;
|
|
20
|
+
/** Holds `chunk` if a load is in flight; answers whether it did. */
|
|
21
|
+
hold(stream, chunk) {
|
|
22
|
+
if (this.held === undefined)
|
|
23
|
+
return false;
|
|
24
|
+
// A copy: the writer may reuse its buffer once the write returns.
|
|
25
|
+
this.held.push({ stream, chunk: typeof chunk === 'string' ? chunk : Buffer.from(chunk) });
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Runs `load`, which loads a config and registers its secrets, holding
|
|
30
|
+
* what is written meanwhile. Then hands each held write to `release` in
|
|
31
|
+
* order, or, if `load` threw, `warn`s with how much was withheld.
|
|
32
|
+
*/
|
|
33
|
+
async during(load, release, warn) {
|
|
34
|
+
this.held = [];
|
|
35
|
+
let loaded = false;
|
|
36
|
+
try {
|
|
37
|
+
const value = await load();
|
|
38
|
+
loaded = true;
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
const held = this.held;
|
|
43
|
+
this.held = undefined;
|
|
44
|
+
if (loaded) {
|
|
45
|
+
for (const { stream, chunk } of held)
|
|
46
|
+
release(stream, chunk);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
const bytes = held.reduce((total, { chunk }) => total + Buffer.byteLength(chunk), 0);
|
|
50
|
+
if (bytes > 0)
|
|
51
|
+
warn(withheldWarning(bytes));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The runner process's user output, on its way to the terminal or, while
|
|
58
|
+
* the list reporter shows it, through the reporter, which prints it above
|
|
59
|
+
* its live window instead of letting it land inside.
|
|
60
|
+
*/
|
|
61
|
+
export class RunnerOutput {
|
|
62
|
+
terminal;
|
|
63
|
+
/** Each stream redacted on its own, across writes: a value split over two writes is still caught. */
|
|
64
|
+
redactors;
|
|
65
|
+
loadHold = new ConfigLoadHold();
|
|
66
|
+
view;
|
|
67
|
+
/** The terminal's stdout for the list reporter: its own lines and live window go straight there. */
|
|
68
|
+
listOutput;
|
|
69
|
+
constructor(terminal, ledger) {
|
|
70
|
+
this.terminal = terminal;
|
|
71
|
+
this.redactors = { stdout: new StreamRedactor(ledger), stderr: new StreamRedactor(ledger) };
|
|
72
|
+
this.listOutput = {
|
|
73
|
+
write: (line) => void terminal.stdout(`${line}\n`),
|
|
74
|
+
raw: (text) => void terminal.stdout(text),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* One write of user code to `stream`, redacted: the tail a later write
|
|
79
|
+
* could complete into a value waits for it, or for `end`. Held while a
|
|
80
|
+
* config loads. Answers what the terminal did.
|
|
81
|
+
*/
|
|
82
|
+
write(stream, chunk, callback) {
|
|
83
|
+
if (this.loadHold.hold(stream, chunk)) {
|
|
84
|
+
if (callback !== undefined)
|
|
85
|
+
process.nextTick(callback);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return this.emit(stream, this.redactors[stream].push(chunk), callback);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Runs `load`, a config load that registers the config's secrets, holding
|
|
92
|
+
* what is printed until it ends: the config's top-level code may print a
|
|
93
|
+
* value before the process knows it is one. Released redacted once it
|
|
94
|
+
* loaded; withheld with a warning when it failed.
|
|
95
|
+
*/
|
|
96
|
+
withholdDuring(load) {
|
|
97
|
+
return this.loadHold.during(load, (stream, chunk) => this.write(stream, chunk), (line) => this.emit('stderr', `e2e: [warning] ${line}\n`));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Passes what is printed to `view` from now on, or to the terminal again
|
|
101
|
+
* when undefined. What the previous view was given is released to it
|
|
102
|
+
* first, unfinished lines included, and that view ended.
|
|
103
|
+
*/
|
|
104
|
+
showThrough(view) {
|
|
105
|
+
if (this.view !== undefined) {
|
|
106
|
+
this.release();
|
|
107
|
+
this.view.end();
|
|
108
|
+
}
|
|
109
|
+
this.view = view;
|
|
110
|
+
}
|
|
111
|
+
/** Releases what is still held, redacted, to the terminal. Later writes stay redacted; only a tail a later write could complete into a value is held. */
|
|
112
|
+
end() {
|
|
113
|
+
this.showThrough(undefined);
|
|
114
|
+
this.release();
|
|
115
|
+
}
|
|
116
|
+
release() {
|
|
117
|
+
this.emit('stdout', this.redactors.stdout.flush());
|
|
118
|
+
this.emit('stderr', this.redactors.stderr.flush());
|
|
119
|
+
}
|
|
120
|
+
emit(stream, text, callback) {
|
|
121
|
+
if (text === '' || this.view !== undefined) {
|
|
122
|
+
if (text !== '')
|
|
123
|
+
this.view?.show(stream, text);
|
|
124
|
+
if (callback !== undefined)
|
|
125
|
+
process.nextTick(callback);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
return this.terminal[stream](text, callback);
|
|
129
|
+
}
|
|
130
|
+
}
|
package/dist/run/provision.d.ts
CHANGED
|
@@ -55,6 +55,8 @@ export interface PrepareScope {
|
|
|
55
55
|
/** The same `env` the workers are started with: what prepare provisions must be where a launch will look for it. */
|
|
56
56
|
readonly env: NodeJS.ProcessEnv;
|
|
57
57
|
readonly signal: AbortSignal;
|
|
58
|
+
/** Whether the run asked for a visible surface, so a hook provisions for the browser it will launch. */
|
|
59
|
+
readonly headed: boolean;
|
|
58
60
|
/** Where a hook's progress lines go, under the target they concern. */
|
|
59
61
|
readonly notice: (targetName: string, line: string) => void;
|
|
60
62
|
}
|
package/dist/run/provision.js
CHANGED
package/dist/run/realm.d.ts
CHANGED
|
@@ -33,7 +33,10 @@ export declare function findRegistered(realm: Realm, test: CollectedTest): Regis
|
|
|
33
33
|
/**
|
|
34
34
|
* Runs one hook, or a fixture teardown, within its budget. One that overruns
|
|
35
35
|
* fails with the runner's timeout error naming `label`; `onTimeout` lets the
|
|
36
|
-
* caller cancel what it started.
|
|
36
|
+
* caller cancel what it started. The hook owns the `expect.poll` calls it
|
|
37
|
+
* starts: one still running when it returns fails it with
|
|
38
|
+
* `STEP_NOT_AWAITED` and is cancelled, and one left by a hook that failed is
|
|
39
|
+
* cancelled beside that failure.
|
|
37
40
|
*/
|
|
38
41
|
export declare function runHook(label: string, run: () => void | Promise<void>, timeoutMs: number, onTimeout?: () => void): Promise<void>;
|
|
39
42
|
export interface RealmManagerOptions {
|
package/dist/run/realm.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Suite realm lifecycle: module re-import, beforeAll/afterAll scope tracking. */
|
|
2
2
|
import { importModule } from '../config/load.js';
|
|
3
|
+
import { PollScope, runInPollScope } from '../expect/poll-scope.js';
|
|
3
4
|
import { classifyError, E2EError, serializeError, TestTimeoutError, } from '../internal/errors.js';
|
|
4
5
|
import { DebugTrace } from '../internal/debug.js';
|
|
5
6
|
import { titlePathKey } from '../internal/ids.js';
|
|
@@ -12,13 +13,26 @@ export function findRegistered(realm, test) {
|
|
|
12
13
|
/**
|
|
13
14
|
* Runs one hook, or a fixture teardown, within its budget. One that overruns
|
|
14
15
|
* fails with the runner's timeout error naming `label`; `onTimeout` lets the
|
|
15
|
-
* caller cancel what it started.
|
|
16
|
+
* caller cancel what it started. The hook owns the `expect.poll` calls it
|
|
17
|
+
* starts: one still running when it returns fails it with
|
|
18
|
+
* `STEP_NOT_AWAITED` and is cancelled, and one left by a hook that failed is
|
|
19
|
+
* cancelled beside that failure.
|
|
16
20
|
*/
|
|
17
|
-
export function runHook(label, run, timeoutMs, onTimeout) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
export async function runHook(label, run, timeoutMs, onTimeout) {
|
|
22
|
+
const polls = new PollScope(`the ${label}`);
|
|
23
|
+
try {
|
|
24
|
+
await withTimeout(runInPollScope(polls, () => Promise.resolve().then(run)), timeoutMs, () => {
|
|
25
|
+
onTimeout?.();
|
|
26
|
+
return new TestTimeoutError(`${label} timed out`);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (cause) {
|
|
30
|
+
polls.close();
|
|
31
|
+
throw cause;
|
|
32
|
+
}
|
|
33
|
+
const notAwaited = polls.close();
|
|
34
|
+
if (notAwaited !== undefined)
|
|
35
|
+
throw notAwaited;
|
|
22
36
|
}
|
|
23
37
|
/** Creates realms and runs suite-scope hooks with per-realm entry tracking. */
|
|
24
38
|
export class RealmManager {
|
package/dist/run/records.d.ts
CHANGED
|
@@ -30,13 +30,13 @@ export interface ArtifactRecord {
|
|
|
30
30
|
/**
|
|
31
31
|
* Mirrors report-1: how much of the file the runner masked. A screenshot is
|
|
32
32
|
* `complete`; a trace is `complete` once every registered secret value was
|
|
33
|
-
* rewritten out of its text, and `not-required` when
|
|
34
|
-
*
|
|
33
|
+
* rewritten out of its text, and `not-required` when the run has no
|
|
34
|
+
* secret value to rewrite; a video is `incomplete`, since a recording masks nothing
|
|
35
35
|
* (a secure field renders its own dots, but anything else the screen
|
|
36
36
|
* showed is in the frames), and is kept as it is; a download is
|
|
37
37
|
* `incomplete` too, bytes the app served and the runner did not rewrite,
|
|
38
|
-
* unless
|
|
39
|
-
*
|
|
38
|
+
* unless the file is text the session's ledger was run over, which makes
|
|
39
|
+
* it `complete`. report-1 also admits an
|
|
40
40
|
* `incomplete` artifact without a `path`: a video a hosted service keeps,
|
|
41
41
|
* recorded by `url`, or one its producer withheld, which this runner never
|
|
42
42
|
* writes.
|
|
@@ -148,6 +148,22 @@ export interface ResultRecord {
|
|
|
148
148
|
* every other field is plain data.
|
|
149
149
|
*/
|
|
150
150
|
export type WireResultRecord = Omit<ResultRecord, 'target'>;
|
|
151
|
+
/** The failure a runtime skip followed, with the evidence its attempt captured. */
|
|
152
|
+
export interface FailureBeforeSkip {
|
|
153
|
+
readonly error: SerializedError;
|
|
154
|
+
readonly failure: FailureEvidence | undefined;
|
|
155
|
+
/** The failing attempt's artifacts; for a serial member, the group attempt's. */
|
|
156
|
+
readonly artifacts: readonly ArtifactRecord[];
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The failure a test's `test.skip(...)` followed: the last attempt that
|
|
160
|
+
* failed before a skipped retry, or a soft failure the skipping attempt kept.
|
|
161
|
+
* Engine cleanup diagnostics after the skip are not one. A serial member's
|
|
162
|
+
* attempts live on its group, which `groupOf` looks up only for such a member.
|
|
163
|
+
*/
|
|
164
|
+
export declare function failureBeforeSkip(result: Pick<ResultRecord, 'status' | 'skip' | 'attempts' | 'serialGroupId' | 'test'>, groupOf: (id: string) => SerialGroupRecord | undefined): FailureBeforeSkip | undefined;
|
|
165
|
+
/** Whether any skipped result followed a failure: what `failOnSkippedFailure` fails a run for. */
|
|
166
|
+
export declare function someSkippedAfterFailure(results: readonly ResultRecord[], serialGroups: readonly SerialGroupRecord[]): boolean;
|
|
151
167
|
/** Strips the live target from a result for transport. */
|
|
152
168
|
export declare function encodeResult(record: ResultRecord): WireResultRecord;
|
|
153
169
|
export interface RunError {
|
package/dist/run/records.js
CHANGED
|
@@ -7,6 +7,32 @@
|
|
|
7
7
|
export function isFailedStatus(status) {
|
|
8
8
|
return status !== 'passed' && status !== 'skipped' && status !== 'flaky';
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* The failure a test's `test.skip(...)` followed: the last attempt that
|
|
12
|
+
* failed before a skipped retry, or a soft failure the skipping attempt kept.
|
|
13
|
+
* Engine cleanup diagnostics after the skip are not one. A serial member's
|
|
14
|
+
* attempts live on its group, which `groupOf` looks up only for such a member.
|
|
15
|
+
*/
|
|
16
|
+
export function failureBeforeSkip(result, groupOf) {
|
|
17
|
+
if (result.status !== 'skipped' || result.skip?.cause !== 'explicit')
|
|
18
|
+
return undefined;
|
|
19
|
+
const runs = result.serialGroupId === undefined
|
|
20
|
+
? result.attempts
|
|
21
|
+
: (groupOf(result.serialGroupId)?.attempts ?? []).flatMap((attempt) => attempt.members
|
|
22
|
+
.filter((member) => member.testId === result.test.id)
|
|
23
|
+
.map((member) => ({ ...member, artifacts: attempt.artifacts })));
|
|
24
|
+
for (const run of runs.toReversed()) {
|
|
25
|
+
const error = run.error ?? run.secondaryErrors.find((secondary) => secondary.phase !== 'cleanup');
|
|
26
|
+
if (error !== undefined)
|
|
27
|
+
return { error, failure: run.failure, artifacts: run.artifacts };
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
/** Whether any skipped result followed a failure: what `failOnSkippedFailure` fails a run for. */
|
|
32
|
+
export function someSkippedAfterFailure(results, serialGroups) {
|
|
33
|
+
const groups = new Map(serialGroups.map((group) => [group.id, group]));
|
|
34
|
+
return results.some((result) => failureBeforeSkip(result, (id) => groups.get(id)) !== undefined);
|
|
35
|
+
}
|
|
10
36
|
/** Strips the live target from a result for transport. */
|
|
11
37
|
export function encodeResult(record) {
|
|
12
38
|
const { target: _target, ...rest } = record;
|
package/dist/run/retry.d.ts
CHANGED
|
@@ -12,6 +12,15 @@ export declare function isRetryEligible(attempt: RetryAttempt): boolean;
|
|
|
12
12
|
* Runs up to `maxAttempts` attempts, stopping on pass, interrupt, or a
|
|
13
13
|
* failure that does not consume retry budget. `runOnce` returns the
|
|
14
14
|
* finished attempt, or undefined when the attempt could not start (the
|
|
15
|
-
* loop stops and the last observed status stands).
|
|
15
|
+
* loop stops and the last observed status stands). An interrupt during a
|
|
16
|
+
* retry keeps the verdict the failed attempt before it reached: the test
|
|
17
|
+
* did fail, and only its second chance was cut short.
|
|
16
18
|
*/
|
|
17
19
|
export declare function runWithRetries(maxAttempts: number, interruptSignal: AbortSignal, runOnce: (attemptIndex: number) => Promise<RetryAttempt | undefined>): Promise<ResultStatus>;
|
|
20
|
+
/**
|
|
21
|
+
* The verdict a sequence of finished attempts reaches: a pass is `flaky`
|
|
22
|
+
* after a failure, an interrupt during a retry keeps the verdict before it,
|
|
23
|
+
* and a self-skip decides. Also the verdict of attempts a worker finished
|
|
24
|
+
* before the run's interrupt stopped it, so both read alike.
|
|
25
|
+
*/
|
|
26
|
+
export declare function retryVerdict(attempts: readonly RetryAttempt[]): ResultStatus;
|