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/retry.js
CHANGED
|
@@ -8,28 +8,46 @@ export function isRetryEligible(attempt) {
|
|
|
8
8
|
* Runs up to `maxAttempts` attempts, stopping on pass, interrupt, or a
|
|
9
9
|
* failure that does not consume retry budget. `runOnce` returns the
|
|
10
10
|
* finished attempt, or undefined when the attempt could not start (the
|
|
11
|
-
* loop stops and the last observed status stands).
|
|
11
|
+
* loop stops and the last observed status stands). An interrupt during a
|
|
12
|
+
* retry keeps the verdict the failed attempt before it reached: the test
|
|
13
|
+
* did fail, and only its second chance was cut short.
|
|
12
14
|
*/
|
|
13
15
|
export async function runWithRetries(maxAttempts, interruptSignal, runOnce) {
|
|
14
|
-
|
|
15
|
-
let attemptCount = 0;
|
|
16
|
+
const attempts = [];
|
|
16
17
|
for (let attemptIndex = 0; attemptIndex < maxAttempts; attemptIndex += 1) {
|
|
17
18
|
if (interruptSignal.aborted)
|
|
18
19
|
break;
|
|
19
20
|
const attempt = await runOnce(attemptIndex);
|
|
20
21
|
if (attempt === undefined)
|
|
21
22
|
break;
|
|
22
|
-
|
|
23
|
-
if (attempt.status === 'passed')
|
|
24
|
-
return attemptCount > 1 ? 'flaky' : 'passed';
|
|
25
|
-
if (attempt.status === 'interrupted')
|
|
26
|
-
return 'interrupted';
|
|
23
|
+
attempts.push(attempt);
|
|
27
24
|
// A body that skipped itself has decided; a retry would only ask again.
|
|
28
|
-
if (attempt.status === 'skipped')
|
|
29
|
-
|
|
30
|
-
finalStatus = attempt.status;
|
|
25
|
+
if (attempt.status === 'passed' || attempt.status === 'interrupted' || attempt.status === 'skipped')
|
|
26
|
+
break;
|
|
31
27
|
if (!isRetryEligible(attempt))
|
|
32
28
|
break;
|
|
33
29
|
}
|
|
34
|
-
return
|
|
30
|
+
return retryVerdict(attempts);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The verdict a sequence of finished attempts reaches: a pass is `flaky`
|
|
34
|
+
* after a failure, an interrupt during a retry keeps the verdict before it,
|
|
35
|
+
* and a self-skip decides. Also the verdict of attempts a worker finished
|
|
36
|
+
* before the run's interrupt stopped it, so both read alike.
|
|
37
|
+
*/
|
|
38
|
+
export function retryVerdict(attempts) {
|
|
39
|
+
let verdict = 'failed';
|
|
40
|
+
for (const [index, attempt] of attempts.entries()) {
|
|
41
|
+
switch (attempt.status) {
|
|
42
|
+
case 'passed':
|
|
43
|
+
return index > 0 ? 'flaky' : 'passed';
|
|
44
|
+
case 'interrupted':
|
|
45
|
+
return index > 0 ? verdict : 'interrupted';
|
|
46
|
+
case 'skipped':
|
|
47
|
+
return 'skipped';
|
|
48
|
+
default:
|
|
49
|
+
verdict = attempt.status;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return verdict;
|
|
35
53
|
}
|
package/dist/run/runner.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ import { type Shard, type TagMode } from '../collect/select.ts';
|
|
|
5
5
|
import type { ExploreProgress } from '../explore/progress.ts';
|
|
6
6
|
import { type Report1Document, type ReportExplore } from '../report/build.ts';
|
|
7
7
|
import { type RunEventSink, type RunExitCode, type RunStatus } from './events.ts';
|
|
8
|
-
import type
|
|
8
|
+
import { type ResultRecord } from './records.ts';
|
|
9
|
+
import type { RunnerOutput } from './process-output.ts';
|
|
9
10
|
import type { BuiltinReporter, E2EConfig, RecordingMode } from '../types.ts';
|
|
10
11
|
export interface RunOptions {
|
|
11
12
|
cwd?: string | undefined;
|
|
@@ -109,6 +110,13 @@ export interface RunOptions {
|
|
|
109
110
|
onEvent?: RunEventSink | undefined;
|
|
110
111
|
/** Budget for each reporter's `onRunFinished`, in ms. Only the test harness sets it; there is no flag. */
|
|
111
112
|
reporterTimeout?: number | undefined;
|
|
113
|
+
/**
|
|
114
|
+
* The process's stdout and stderr, when the caller claimed them (the CLI):
|
|
115
|
+
* what user code in this process prints is held while the config loads
|
|
116
|
+
* and shown through the list reporter while the run reports, and the list
|
|
117
|
+
* reporter writes to the terminal past it.
|
|
118
|
+
*/
|
|
119
|
+
processOutput?: RunnerOutput | undefined;
|
|
112
120
|
}
|
|
113
121
|
/**
|
|
114
122
|
* A registration supplied in memory, under the virtual file name the report
|
package/dist/run/runner.js
CHANGED
|
@@ -18,11 +18,14 @@ import { writeJsonReport } from '../report/write.js';
|
|
|
18
18
|
import { createRunEventEmitter, toEventResult } from './events.js';
|
|
19
19
|
import { allocateAppPorts } from './app-ports.js';
|
|
20
20
|
import { inProcessSpawner } from './in-process.js';
|
|
21
|
+
import { someSkippedAfterFailure } from './records.js';
|
|
21
22
|
import { runUnits } from './scheduler.js';
|
|
22
23
|
import { buildWorkPlans, plannedSlots } from './units.js';
|
|
23
24
|
import { SessionStore } from './sessions.js';
|
|
24
25
|
import { outputLayout } from './output.js';
|
|
25
|
-
import {
|
|
26
|
+
import { registerStaticSecrets } from './secrecy.js';
|
|
27
|
+
import { claimRerunDir, pruneArtifacts } from './artifacts.js';
|
|
28
|
+
import { carryForward, lastFailedIds, readLastRun, reportArtifactPaths } from './last-run.js';
|
|
26
29
|
import { childProcessSpawner } from './worker/handle.js';
|
|
27
30
|
import { setSecretRegistry } from '../secrets.js';
|
|
28
31
|
import { withAbort } from '../internal/time.js';
|
|
@@ -129,14 +132,27 @@ export async function run(options = {}) {
|
|
|
129
132
|
//
|
|
130
133
|
// Free ports for URLs declared with port 0 are chosen here, once: workers
|
|
131
134
|
// re-resolve the config and get the assignments in their bootstrap.
|
|
135
|
+
//
|
|
136
|
+
// The config's secrets are known to the process before what its top-level
|
|
137
|
+
// code printed is released, so that output is redacted like any other.
|
|
138
|
+
const loadConfig = async () => {
|
|
139
|
+
const config = await loadRunConfig(options, cwd, env, cli);
|
|
140
|
+
registerStaticSecrets(config.allSecrets);
|
|
141
|
+
return allocateAppPorts(config);
|
|
142
|
+
};
|
|
143
|
+
const processOutput = options.processOutput;
|
|
132
144
|
const loaded = await debug
|
|
133
|
-
.time('config.load', () =>
|
|
145
|
+
.time('config.load', () => (processOutput === undefined ? loadConfig() : processOutput.withholdDuring(loadConfig)))
|
|
134
146
|
.then((config) => ({ config, error: undefined }), (cause) => ({ config: undefined, error: classifyError(cause) }));
|
|
135
147
|
// Every reporter, built-in or configured, is one `Reporter` on one spine:
|
|
136
148
|
// the ids name the shipped ones, and a config failure still leaves the ids
|
|
137
149
|
// the CLI asked for, so the failure renders through them.
|
|
138
150
|
const reporterIds = loaded.config?.reporters ?? options.reporters ?? ['list'];
|
|
139
|
-
const listReporter = options.quiet === true || !reporterIds.includes('list') ? undefined : new ListReporter();
|
|
151
|
+
const listReporter = options.quiet === true || !reporterIds.includes('list') ? undefined : new ListReporter(processOutput?.listOutput);
|
|
152
|
+
// Until the run is over, what user code prints lands above the live window.
|
|
153
|
+
processOutput?.showThrough(listReporter === undefined
|
|
154
|
+
? undefined
|
|
155
|
+
: { show: (stream, text) => listReporter.processOutput(stream, text), end: () => listReporter.endProcessOutput() });
|
|
140
156
|
const activeReporters = [
|
|
141
157
|
...(listReporter === undefined ? [] : [listReporter]),
|
|
142
158
|
...reporterIds.filter((id) => id !== 'list').map((id) => STATELESS_REPORTERS[id]),
|
|
@@ -180,32 +196,45 @@ export async function run(options = {}) {
|
|
|
180
196
|
emit({ type: 'run-stopped', failures, limit });
|
|
181
197
|
};
|
|
182
198
|
/**
|
|
183
|
-
* The exit code is a fold over run state — every result, every run error,
|
|
199
|
+
* The exit code is a fold over run state — every result and serial group, every run error,
|
|
184
200
|
* the interrupt — never threaded through by hand. A run error recorded
|
|
185
201
|
* anywhere, including during teardown or the report write, reaches the exit
|
|
186
202
|
* code the same way.
|
|
187
203
|
*/
|
|
188
204
|
const currentExitCode = () => combineExitCodes([
|
|
189
|
-
...
|
|
205
|
+
...verdictExitCodes(results, serialGroups),
|
|
206
|
+
...(loaded.config?.failOnSkippedFailure === true && someSkippedAfterFailure(results, serialGroups) ? [1] : []),
|
|
190
207
|
...runErrors.map((runError) => exitCodeForCategory(runError.error.category)),
|
|
191
208
|
...(interruptController.signal.aborted ? [130] : []),
|
|
192
209
|
].filter((code) => code !== 130 || !(runAborted || stoppedEarly)));
|
|
193
210
|
// Detected once the config names the project root; a report written before
|
|
194
211
|
// that (a config failure) has no checkout to describe.
|
|
195
212
|
let vcs;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
213
|
+
// The report `--last-failed` selected from, once collection has read it;
|
|
214
|
+
// reporters get it beside this run's report to fold the rerun into it, and
|
|
215
|
+
// this run's report carries what it owed that this run left out.
|
|
216
|
+
let lastRun;
|
|
217
|
+
// The ids of every test collection found, a setup no selected test needed
|
|
218
|
+
// among them, and the files it could not collect: what a rerun can still
|
|
219
|
+
// carry when the report lists no row.
|
|
220
|
+
let rerunCollection = { testIds: new Set(), uncollectedFiles: new Set() };
|
|
221
|
+
const buildRunReport = (exitCode) => {
|
|
222
|
+
const document = buildReport({
|
|
223
|
+
runId,
|
|
224
|
+
config: loaded.config,
|
|
225
|
+
vcs,
|
|
226
|
+
startedAt,
|
|
227
|
+
status: statusOf(exitCode),
|
|
228
|
+
exitCode,
|
|
229
|
+
results,
|
|
230
|
+
serialGroups,
|
|
231
|
+
runErrors,
|
|
232
|
+
targetProvenance,
|
|
233
|
+
explore: options.tests?.explore?.snapshot(),
|
|
234
|
+
});
|
|
235
|
+
const carried = lastRun === undefined ? undefined : carryForward(lastRun, document, rerunCollection);
|
|
236
|
+
return carried === undefined ? document : { ...document, run: { ...document.run, carried } };
|
|
237
|
+
};
|
|
209
238
|
/**
|
|
210
239
|
* Writes the canonical report and returns its path only once the file
|
|
211
240
|
* exists: a host must never be handed a path to a report that was not
|
|
@@ -249,9 +278,6 @@ export async function run(options = {}) {
|
|
|
249
278
|
return undefined;
|
|
250
279
|
}
|
|
251
280
|
};
|
|
252
|
-
// The report `--last-failed` selected from, once collection has read it;
|
|
253
|
-
// reporters get it beside this run's report to fold the rerun into it.
|
|
254
|
-
let lastRun;
|
|
255
281
|
/**
|
|
256
282
|
* Whether the run got as far as its tests. Only such a run writes into the
|
|
257
283
|
* output directory: one that stopped before leaves the previous run's
|
|
@@ -281,6 +307,9 @@ export async function run(options = {}) {
|
|
|
281
307
|
...(reportPath === undefined ? {} : { reportPath }),
|
|
282
308
|
...(aiTracePath === undefined ? {} : { aiTracePath }),
|
|
283
309
|
});
|
|
310
|
+
// The list reporter has printed its summary and stopped its window; a
|
|
311
|
+
// line another reporter left unfinished on `run-finished` prints too.
|
|
312
|
+
processOutput?.showThrough(undefined);
|
|
284
313
|
if (debug.enabled) {
|
|
285
314
|
process.stderr.write(debug.summary());
|
|
286
315
|
process.stderr.write(agentStepTable(results, serialGroups));
|
|
@@ -364,6 +393,10 @@ export async function run(options = {}) {
|
|
|
364
393
|
: collectInMemory(config.projectRoot, options.tests.file, options.tests.registration, consumesSession(options.tests.registration) ? await collectSetups(config) : undefined);
|
|
365
394
|
const inputs = await selectionInputs(options, config);
|
|
366
395
|
lastRun = inputs.lastRun;
|
|
396
|
+
rerunCollection = {
|
|
397
|
+
testIds: new Set(collection.tests.map((test) => test.id)),
|
|
398
|
+
uncollectedFiles: new Set(collection.uncollected.map((skipped) => skipped.file)),
|
|
399
|
+
};
|
|
367
400
|
const selection = repeatEach(select(collection, config, inputs.filters, options.passWithNoTests !== undefined ? { passWithNoTests: options.passWithNoTests } : {}), options.repeatEach ?? 1);
|
|
368
401
|
return { collection, selection };
|
|
369
402
|
}));
|
|
@@ -409,7 +442,7 @@ export async function run(options = {}) {
|
|
|
409
442
|
// stream as `notice` events under it, so the reporter prints them instead
|
|
410
443
|
// of a worker's stderr fighting the live status block.
|
|
411
444
|
try {
|
|
412
|
-
plans = await debug.time('engine.prepare', () => prepareEngines(plans, runWorkers, engines, { runId, projectRoot: config.projectRoot, env, signal: interrupted, notice }, emit));
|
|
445
|
+
plans = await debug.time('engine.prepare', () => prepareEngines(plans, runWorkers, engines, { runId, projectRoot: config.projectRoot, env, signal: interrupted, headed: options.headed ?? false, notice }, emit));
|
|
413
446
|
}
|
|
414
447
|
catch (cause) {
|
|
415
448
|
if (!interrupted.aborted)
|
|
@@ -450,15 +483,26 @@ export async function run(options = {}) {
|
|
|
450
483
|
// Tests are about to start, and only now is the previous run's evidence
|
|
451
484
|
// given up: the artifact tree is emptied, so what is there once this run
|
|
452
485
|
// ends is its own and nothing a report no longer names, and this run's
|
|
453
|
-
// report replaces the last. A
|
|
454
|
-
//
|
|
455
|
-
//
|
|
486
|
+
// report replaces the last. A `--last-failed` rerun keeps the evidence
|
|
487
|
+
// the report it reruns names instead, since that report's results fold
|
|
488
|
+
// into the rerun's and its carried tests do not run again, and files its
|
|
489
|
+
// own attempts in a fresh `rerun-<n>` directory beside it, so no attempt
|
|
490
|
+
// overwrites an earlier one's files. A run that stopped before here (no
|
|
491
|
+
// test selected, a collection error, a target that cannot record what it
|
|
492
|
+
// asks, an app that failed to start, an interrupt) leaves both, and
|
|
456
493
|
// `--last-failed` still reads the run that executed. A wipe that fails
|
|
457
494
|
// part way has already given up the old evidence, so this run's report
|
|
458
495
|
// records the failure.
|
|
459
496
|
testsStarted = true;
|
|
497
|
+
let rerunDir;
|
|
460
498
|
try {
|
|
461
|
-
|
|
499
|
+
if (lastRun === undefined) {
|
|
500
|
+
await rm(layout.artifacts, { recursive: true, force: true });
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
await pruneArtifacts(layout.artifacts, reportArtifactPaths(lastRun));
|
|
504
|
+
rerunDir = await claimRerunDir(layout.artifacts);
|
|
505
|
+
}
|
|
462
506
|
}
|
|
463
507
|
catch (cause) {
|
|
464
508
|
recordFailure(cause, 'launch');
|
|
@@ -482,6 +526,7 @@ export async function run(options = {}) {
|
|
|
482
526
|
inMemory: options.tests,
|
|
483
527
|
runId,
|
|
484
528
|
artifactsRoot,
|
|
529
|
+
rerunDir,
|
|
485
530
|
sessionStore: store,
|
|
486
531
|
headed: options.headed ?? false,
|
|
487
532
|
debug,
|
|
@@ -495,6 +540,7 @@ export async function run(options = {}) {
|
|
|
495
540
|
ports: config.ports,
|
|
496
541
|
runId,
|
|
497
542
|
artifactsRoot,
|
|
543
|
+
rerunDir,
|
|
498
544
|
headed: options.headed ?? false,
|
|
499
545
|
sessionsRoot: layout.sessions,
|
|
500
546
|
sessionKeyBase64: store.exportKeyForWorker(),
|
|
@@ -723,14 +769,20 @@ function plannedFiles(selection) {
|
|
|
723
769
|
function statusOf(exitCode) {
|
|
724
770
|
return exitCode === 0 ? 'passed' : exitCode === 1 ? 'failed' : exitCode === 130 ? 'interrupted' : 'error';
|
|
725
771
|
}
|
|
726
|
-
|
|
772
|
+
/**
|
|
773
|
+
* The exit codes the verdicts imply: a failed test or serial group exits 1,
|
|
774
|
+
* or its attempts' error category when that is worse. A serial group answers
|
|
775
|
+
* for its members, whose results carry no attempts, and for a group attempt
|
|
776
|
+
* that failed before any member ran (a launch), whose members are skipped.
|
|
777
|
+
*/
|
|
778
|
+
function verdictExitCodes(results, serialGroups) {
|
|
727
779
|
const codes = [0];
|
|
728
|
-
for (const
|
|
729
|
-
switch (
|
|
780
|
+
for (const verdict of [...results, ...serialGroups]) {
|
|
781
|
+
switch (verdict.status) {
|
|
730
782
|
case 'failed':
|
|
731
783
|
case 'timed-out':
|
|
732
784
|
codes.push(1);
|
|
733
|
-
for (const attempt of
|
|
785
|
+
for (const attempt of verdict.attempts) {
|
|
734
786
|
if (attempt.error !== undefined)
|
|
735
787
|
codes.push(exitCodeForCategory(attempt.error.category));
|
|
736
788
|
}
|
|
@@ -747,13 +799,21 @@ function resultExitCodes(results) {
|
|
|
747
799
|
/** Why a reporter's `onRunFinished` was given up on: the message is the stderr line. */
|
|
748
800
|
class ReporterAbandoned extends Error {
|
|
749
801
|
}
|
|
802
|
+
/**
|
|
803
|
+
* The built-in reporters that write files beside `report.json`. A forced
|
|
804
|
+
* interrupt never abandons them: they are local writes, and giving one up
|
|
805
|
+
* would leave the previous run's `summary.md` or `junit.xml` beside this
|
|
806
|
+
* run's report, where CI reads it as this run's.
|
|
807
|
+
*/
|
|
808
|
+
const FILE_REPORTERS = new Set([STATELESS_REPORTERS.junit, STATELESS_REPORTERS.markdown]);
|
|
750
809
|
/**
|
|
751
810
|
* Awaits every reporter's `onRunFinished` at once, each within `timeoutMs`
|
|
752
|
-
* and until a forced interrupt, and
|
|
753
|
-
* with. Each reporter gets a signal
|
|
754
|
-
*
|
|
755
|
-
* reporter that throws, runs out of
|
|
756
|
-
* rows is one line on stderr; it can
|
|
811
|
+
* and, except the built-in file reporters, until a forced interrupt, and
|
|
812
|
+
* collects the summary rows they resolve with. Each reporter gets a signal
|
|
813
|
+
* that aborts on either, so a well-behaved one cancels its own work and
|
|
814
|
+
* leaves no handle holding the process. A reporter that throws, runs out of
|
|
815
|
+
* time, or returns something other than rows is one line on stderr; it can
|
|
816
|
+
* never change the run's outcome.
|
|
757
817
|
*/
|
|
758
818
|
async function runReporters(reporters, finished, timeoutMs, force) {
|
|
759
819
|
const warn = (reporter, detail) => {
|
|
@@ -767,10 +827,12 @@ async function runReporters(reporters, finished, timeoutMs, force) {
|
|
|
767
827
|
const abandon = new AbortController();
|
|
768
828
|
const timer = setTimeout(() => abandon.abort(new ReporterAbandoned(`did not finish within ${budget}`)), timeoutMs);
|
|
769
829
|
const onForce = () => abandon.abort(new ReporterAbandoned('abandoned: the run was forced to stop'));
|
|
770
|
-
if (
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
830
|
+
if (!FILE_REPORTERS.has(reporter)) {
|
|
831
|
+
if (force.aborted)
|
|
832
|
+
onForce();
|
|
833
|
+
else
|
|
834
|
+
force.addEventListener('abort', onForce, { once: true });
|
|
835
|
+
}
|
|
774
836
|
try {
|
|
775
837
|
const result = await withAbort(() => Promise.resolve(onRunFinished.call(reporter, finished, abandon.signal)), abandon.signal, () => abandon.signal.reason);
|
|
776
838
|
if (result === undefined)
|