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
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/** Which phase of a test owns an `expect.poll`, so one left running fails that phase and nothing later. */
|
|
2
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
|
+
import { markAbandonedRejection, relocateStack } from '../internal/abandoned.js';
|
|
4
|
+
import { pollScopesBrand } from '../internal/brands.js';
|
|
5
|
+
import { TestError, withHint } from '../internal/errors.js';
|
|
6
|
+
import { realmSlot } from '../internal/realm-slot.js';
|
|
7
|
+
/**
|
|
8
|
+
* The polls one phase started: a test body with its fixtures and
|
|
9
|
+
* `beforeEach` hooks, one teardown, or one suite hook. A poll is not a step
|
|
10
|
+
* and records nothing; what the scope adds is that a poll the phase did not
|
|
11
|
+
* await ends with the phase instead of failing whatever runs when it times out.
|
|
12
|
+
*/
|
|
13
|
+
export class PollScope {
|
|
14
|
+
owner;
|
|
15
|
+
running = new Set();
|
|
16
|
+
closed = false;
|
|
17
|
+
/** `owner` completes "... returned before", as in `the test body`. */
|
|
18
|
+
constructor(owner) {
|
|
19
|
+
this.owner = owner;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Runs one poll, owned by this scope until it settles. `work` receives the
|
|
23
|
+
* signal that cancels it when the scope closes first. A poll started after
|
|
24
|
+
* the scope closed, by work its phase left running (a body past its
|
|
25
|
+
* timeout), is cancelled before its first read: it belongs to no phase
|
|
26
|
+
* that could still fail, and must not fail the one running now.
|
|
27
|
+
*/
|
|
28
|
+
track(label, stack, work) {
|
|
29
|
+
const poll = { label, stack, controller: new AbortController(), promise: Promise.resolve(), abandoned: false };
|
|
30
|
+
if (this.closed)
|
|
31
|
+
this.cancel(poll);
|
|
32
|
+
const promise = (async () => {
|
|
33
|
+
try {
|
|
34
|
+
return await work(poll.controller.signal);
|
|
35
|
+
}
|
|
36
|
+
catch (cause) {
|
|
37
|
+
if (poll.abandoned)
|
|
38
|
+
markAbandonedRejection(cause);
|
|
39
|
+
throw cause;
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
this.running.delete(poll);
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
poll.promise = promise;
|
|
46
|
+
if (poll.abandoned)
|
|
47
|
+
promise.catch(() => undefined);
|
|
48
|
+
else
|
|
49
|
+
this.running.add(poll);
|
|
50
|
+
return promise;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Ends the scope when its phase has returned: every poll still running is
|
|
54
|
+
* cancelled and its later rejection observed. Returns the error naming
|
|
55
|
+
* them, at the line of the first, or nothing when every poll had settled.
|
|
56
|
+
* A caller whose phase was cut short (a timeout, a throw) may drop the
|
|
57
|
+
* error: the phase may have been awaiting the poll. Closing twice returns
|
|
58
|
+
* nothing the second time.
|
|
59
|
+
*/
|
|
60
|
+
close() {
|
|
61
|
+
this.closed = true;
|
|
62
|
+
const polls = [...this.running];
|
|
63
|
+
const [first] = polls;
|
|
64
|
+
if (first === undefined)
|
|
65
|
+
return undefined;
|
|
66
|
+
for (const poll of polls)
|
|
67
|
+
this.cancel(poll);
|
|
68
|
+
const more = polls.length > 1 ? ` and ${polls.length - 1} more` : '';
|
|
69
|
+
const error = new TestError('STEP_NOT_AWAITED', withHint(`${this.owner} returned before ${first.label}${more} finished`, 'put `await` in front of every expect.poll call'));
|
|
70
|
+
relocateStack(error, first.stack);
|
|
71
|
+
return error;
|
|
72
|
+
}
|
|
73
|
+
cancel(poll) {
|
|
74
|
+
this.running.delete(poll);
|
|
75
|
+
poll.abandoned = true;
|
|
76
|
+
poll.promise.catch(() => undefined);
|
|
77
|
+
poll.controller.abort();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const storage = new AsyncLocalStorage();
|
|
81
|
+
const scopes = { current: () => storage.getStore()?.scope };
|
|
82
|
+
const slot = realmSlot(pollScopesBrand);
|
|
83
|
+
/** Runs `run` in the lineage `owner` names, publishing the runner's context for a test module's `expect`. */
|
|
84
|
+
function enter(owner, run) {
|
|
85
|
+
if (slot.get(globalThis) !== scopes)
|
|
86
|
+
slot.set(globalThis, scopes);
|
|
87
|
+
return storage.run(owner, run);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Runs `run` with `scope` owning every poll it starts, however deep in its
|
|
91
|
+
* async work and however long that work outlives the phase: a poll belongs
|
|
92
|
+
* to the phase that started it, never to the one running when it is called.
|
|
93
|
+
*/
|
|
94
|
+
export function runInPollScope(scope, run) {
|
|
95
|
+
return enter({ scope }, run);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* A lineage for work that spans two phases, a fixture: its setup runs in the
|
|
99
|
+
* body's phase, and the rest of its function after `use` is its teardown's.
|
|
100
|
+
*/
|
|
101
|
+
export function pollLineage() {
|
|
102
|
+
const owner = { scope: scopes.current() };
|
|
103
|
+
return {
|
|
104
|
+
run: (run) => enter(owner, run),
|
|
105
|
+
handOver: () => {
|
|
106
|
+
owner.scope = scopes.current();
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** The scope a poll started here belongs to, or undefined outside any phase (a standalone script). */
|
|
111
|
+
export function currentPollScope() {
|
|
112
|
+
return slot.get(globalThis)?.current();
|
|
113
|
+
}
|
package/dist/expect/poll.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/** expect.poll: re-reads a value until a synchronous matcher passes. */
|
|
2
|
-
import { ConfigurationError, E2EError, TestError } from '../internal/errors.js';
|
|
2
|
+
import { ConfigurationError, E2EError, isE2EError, TestError } from '../internal/errors.js';
|
|
3
3
|
import { Deadline, POLL_INTERVAL_MS, sleep, withAbort, withTimeout } from '../internal/time.js';
|
|
4
4
|
import { currentAttempt } from './attempt.js';
|
|
5
|
+
import { currentPollScope } from './poll-scope.js';
|
|
5
6
|
import { createValueExpectation } from './values.js';
|
|
6
7
|
/** Outside an attempt (a standalone script) there is no config to read `assertionTimeout` from. */
|
|
7
8
|
const DEFAULT_TIMEOUT_MS = 5000;
|
|
9
|
+
/** Frames kept for the line a poll was called on; the user's line is a few frames up. */
|
|
10
|
+
const CALL_STACK_FRAMES = 20;
|
|
8
11
|
/** `satisfies` keeps this list equal to the ValueExpectation matcher set. */
|
|
9
12
|
const MATCHERS = {
|
|
10
13
|
toBe: true,
|
|
@@ -36,7 +39,7 @@ function describeError(error) {
|
|
|
36
39
|
return error instanceof Error ? error.message : String(error);
|
|
37
40
|
}
|
|
38
41
|
function isNotYet(error) {
|
|
39
|
-
return error
|
|
42
|
+
return isE2EError(error) && error.code === 'ASSERTION_FAILED';
|
|
40
43
|
}
|
|
41
44
|
function cancelled() {
|
|
42
45
|
return new E2EError('infrastructure', 'CANCELLED', 'operation cancelled');
|
|
@@ -46,7 +49,21 @@ function sample(read, deadline, signal) {
|
|
|
46
49
|
const bounded = withTimeout((async () => read())(), deadline.remaining(), () => new ReadHung());
|
|
47
50
|
return signal === undefined ? bounded : withAbort(bounded, signal, () => new ReadAborted());
|
|
48
51
|
}
|
|
49
|
-
|
|
52
|
+
/** The stack at a poll's call, for the error that names it when its phase did not await it. */
|
|
53
|
+
function callStack() {
|
|
54
|
+
const limit = Error.stackTraceLimit;
|
|
55
|
+
Error.stackTraceLimit = CALL_STACK_FRAMES;
|
|
56
|
+
try {
|
|
57
|
+
return new Error().stack;
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
Error.stackTraceLimit = limit;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function matcherLabel(negated, name) {
|
|
64
|
+
return `expect.poll(...).${negated ? 'not.' : ''}${name}(...)`;
|
|
65
|
+
}
|
|
66
|
+
async function pollMatcher(read, options, negated, name, args, scopeSignal) {
|
|
50
67
|
const attempt = currentAttempt();
|
|
51
68
|
const timeout = options.timeout ?? attempt?.assertionTimeout ?? DEFAULT_TIMEOUT_MS;
|
|
52
69
|
const interval = options.interval ?? POLL_INTERVAL_MS;
|
|
@@ -57,9 +74,14 @@ async function pollMatcher(read, options, negated, name, args) {
|
|
|
57
74
|
// the moment the attempt is cancelled, so a timed-out test never keeps
|
|
58
75
|
// reading through teardown.
|
|
59
76
|
const deadline = attempt === undefined ? own : Deadline.min(own, attempt.budget.deadline);
|
|
60
|
-
|
|
77
|
+
// The phase that started the poll cancels it when it returns without
|
|
78
|
+
// awaiting it, so the poll can never fail whatever runs after.
|
|
79
|
+
const signals = [attempt?.budget.signal, scopeSignal].filter((signal) => signal !== undefined);
|
|
80
|
+
const signal = signals.length === 0 ? undefined : AbortSignal.any(signals);
|
|
61
81
|
let last;
|
|
62
82
|
while (!deadline.expired()) {
|
|
83
|
+
if (signal?.aborted === true)
|
|
84
|
+
throw cancelled();
|
|
63
85
|
let value;
|
|
64
86
|
try {
|
|
65
87
|
value = await sample(read, deadline, signal);
|
|
@@ -89,7 +111,7 @@ async function pollMatcher(read, options, negated, name, args) {
|
|
|
89
111
|
? `timed out after ${timeout} ms`
|
|
90
112
|
: `stopped at the attempt deadline after ${Date.now() - startedAt} ms`;
|
|
91
113
|
throw new TestError('ASSERTION_FAILED', [
|
|
92
|
-
|
|
114
|
+
`${matcherLabel(negated, name)} ${ending}`,
|
|
93
115
|
...(options.message === undefined ? [] : [options.message]),
|
|
94
116
|
`last: ${last ?? 'no read completed'}`,
|
|
95
117
|
].join('\n'));
|
|
@@ -107,7 +129,12 @@ function checkOptions(options) {
|
|
|
107
129
|
function build(read, options, negated) {
|
|
108
130
|
const matchers = Object.fromEntries(Object.keys(MATCHERS).map((name) => [
|
|
109
131
|
name,
|
|
110
|
-
(...args) =>
|
|
132
|
+
(...args) => {
|
|
133
|
+
const scope = currentPollScope();
|
|
134
|
+
if (scope === undefined)
|
|
135
|
+
return pollMatcher(read, options, negated, name, args, undefined);
|
|
136
|
+
return scope.track(matcherLabel(negated, name), callStack(), (signal) => pollMatcher(read, options, negated, name, args, signal));
|
|
137
|
+
},
|
|
111
138
|
]));
|
|
112
139
|
return {
|
|
113
140
|
...matchers,
|
package/dist/expect/values.js
CHANGED
|
@@ -207,14 +207,21 @@ class ValueExpectationImpl {
|
|
|
207
207
|
this.check(holds(actual), () => `expected ${actual} to be ${phrase} ${expected}`, () => `expected ${actual} not to be ${phrase} ${expected}`);
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
|
-
/**
|
|
210
|
+
/**
|
|
211
|
+
* Walks `keys` into `value`; `present` says whether the whole path resolved.
|
|
212
|
+
* A primitive on the way is read through its wrapper object, as Jest does,
|
|
213
|
+
* so a string has its `length` and indices; `null` and `undefined` have no
|
|
214
|
+
* properties.
|
|
215
|
+
*/
|
|
211
216
|
function lookup(value, keys) {
|
|
212
217
|
let current = value;
|
|
213
218
|
for (const key of keys) {
|
|
214
|
-
if (
|
|
219
|
+
if (current === null || current === undefined)
|
|
215
220
|
return { present: false, value: undefined };
|
|
216
|
-
|
|
217
|
-
|
|
221
|
+
const holder = Object(current);
|
|
222
|
+
if (!(key in holder))
|
|
223
|
+
return { present: false, value: undefined };
|
|
224
|
+
current = holder[key];
|
|
218
225
|
}
|
|
219
226
|
return { present: true, value: current };
|
|
220
227
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Work a test left running when its phase returned: steps and polls nobody awaited. */
|
|
2
|
+
/** Records `cause` as the rejection of work already failed as not awaited. */
|
|
3
|
+
export declare function markAbandonedRejection(cause: unknown): void;
|
|
4
|
+
/**
|
|
5
|
+
* Whether an unhandled rejection is abandoned work's: already recorded as
|
|
6
|
+
* `STEP_NOT_AWAITED`, reaching the process only because the promise the
|
|
7
|
+
* test did not await rejected with it. Such a rejection is not a fault.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isAbandonedRejection(cause: unknown): boolean;
|
|
10
|
+
/** Points `error` at the frames of `stack`, keeping its own name and message as the first line. */
|
|
11
|
+
export declare function relocateStack(error: Error, stack: string | undefined): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Work a test left running when its phase returned: steps and polls nobody awaited. */
|
|
2
|
+
/** What abandoned work rejected with. The promise the test holds rejects with the same value, and nobody awaits it. */
|
|
3
|
+
const ABANDONED_REJECTIONS = new WeakSet();
|
|
4
|
+
/** Records `cause` as the rejection of work already failed as not awaited. */
|
|
5
|
+
export function markAbandonedRejection(cause) {
|
|
6
|
+
if (typeof cause === 'object' && cause !== null)
|
|
7
|
+
ABANDONED_REJECTIONS.add(cause);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Whether an unhandled rejection is abandoned work's: already recorded as
|
|
11
|
+
* `STEP_NOT_AWAITED`, reaching the process only because the promise the
|
|
12
|
+
* test did not await rejected with it. Such a rejection is not a fault.
|
|
13
|
+
*/
|
|
14
|
+
export function isAbandonedRejection(cause) {
|
|
15
|
+
return typeof cause === 'object' && cause !== null && ABANDONED_REJECTIONS.has(cause);
|
|
16
|
+
}
|
|
17
|
+
/** Points `error` at the frames of `stack`, keeping its own name and message as the first line. */
|
|
18
|
+
export function relocateStack(error, stack) {
|
|
19
|
+
if (stack === undefined)
|
|
20
|
+
return;
|
|
21
|
+
const frames = stack.split('\n').slice(1).join('\n');
|
|
22
|
+
error.stack = `${error.name}: ${error.message}\n${frames}`;
|
|
23
|
+
}
|
|
@@ -9,3 +9,5 @@ export declare const locatorBrand: unique symbol;
|
|
|
9
9
|
export declare const expectationBrand: unique symbol;
|
|
10
10
|
/** Slot on `globalThis` naming the running attempt for `expect.poll`. */
|
|
11
11
|
export declare const attemptBrand: unique symbol;
|
|
12
|
+
/** Slot on `globalThis` holding the runner's view of which phase owns an `expect.poll`. */
|
|
13
|
+
export declare const pollScopesBrand: unique symbol;
|
package/dist/internal/brands.js
CHANGED
|
@@ -9,3 +9,5 @@ export const locatorBrand = Symbol.for('e2e.locator');
|
|
|
9
9
|
export const expectationBrand = Symbol.for('e2e.expectation.v1');
|
|
10
10
|
/** Slot on `globalThis` naming the running attempt for `expect.poll`. */
|
|
11
11
|
export const attemptBrand = Symbol.for('e2e.attempt.v1');
|
|
12
|
+
/** Slot on `globalThis` holding the runner's view of which phase owns an `expect.poll`. */
|
|
13
|
+
export const pollScopesBrand = Symbol.for('e2e.pollScopes.v1');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** How e2e names itself to the vendors it calls. */
|
|
2
|
+
/** `e2e/<version> (<platform>; <arch>)`: what every vendor request identifies as. Never another client's name. */
|
|
3
|
+
export declare const USER_AGENT: string;
|
|
4
|
+
/**
|
|
5
|
+
* Sent with every model call, whichever provider serves it. The AI SDK appends
|
|
6
|
+
* its own user agent after ours; `http-referer` and `x-title` are the app
|
|
7
|
+
* attribution the Vercel AI Gateway and OpenRouter read, and others ignore.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MODEL_REQUEST_HEADERS: Readonly<Record<string, string>>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** How e2e names itself to the vendors it calls. */
|
|
2
|
+
import { packageVersion } from './package-version.js';
|
|
3
|
+
/** `e2e/<version> (<platform>; <arch>)`: what every vendor request identifies as. Never another client's name. */
|
|
4
|
+
export const USER_AGENT = `e2e/${packageVersion(import.meta.url, '../../package.json', '0.0.0')} (${process.platform}; ${process.arch})`;
|
|
5
|
+
/**
|
|
6
|
+
* Sent with every model call, whichever provider serves it. The AI SDK appends
|
|
7
|
+
* its own user agent after ours; `http-referer` and `x-title` are the app
|
|
8
|
+
* attribution the Vercel AI Gateway and OpenRouter read, and others ignore.
|
|
9
|
+
*/
|
|
10
|
+
export const MODEL_REQUEST_HEADERS = {
|
|
11
|
+
'user-agent': USER_AGENT,
|
|
12
|
+
'http-referer': 'https://tester.army/e2e',
|
|
13
|
+
'x-title': 'e2e',
|
|
14
|
+
};
|
|
@@ -3,6 +3,8 @@ import { type SourceLocation } from './source.ts';
|
|
|
3
3
|
import { type EngineErrorCode } from '../engine/contract.ts';
|
|
4
4
|
/** Message of an arbitrary thrown value, for diagnostics that must not throw. */
|
|
5
5
|
export declare function errorMessage(cause: unknown): string;
|
|
6
|
+
/** Installs the process-wide redactor `serializeError` uses when none is given; `undefined` clears it. */
|
|
7
|
+
export declare function setErrorRedactor(redact: ((text: string) => string) | undefined): void;
|
|
6
8
|
/**
|
|
7
9
|
* The message of a thrown value led by its code when it is an e2e error, as
|
|
8
10
|
* the CLI prints one: `POLICY_DENIED: forbidden URL scheme: file:`. Any
|
|
@@ -107,8 +109,13 @@ export declare class E2EError extends Error {
|
|
|
107
109
|
readonly [E2E_ERROR_MARKER] = true;
|
|
108
110
|
constructor(category: ErrorCategory, code: string, message: string, options?: E2EErrorOptions);
|
|
109
111
|
}
|
|
110
|
-
/**
|
|
111
|
-
|
|
112
|
+
/**
|
|
113
|
+
* True for an E2EError from this module copy or another. An engine imported by
|
|
114
|
+
* a config file loads its own copy of the runner, so its errors fail
|
|
115
|
+
* `instanceof E2EError` and every subclass check; test for runner errors with
|
|
116
|
+
* this, and read the subclass off `category` and `code`.
|
|
117
|
+
*/
|
|
118
|
+
export declare function isE2EError(value: unknown): value is Error & {
|
|
112
119
|
category: ErrorCategory;
|
|
113
120
|
code: string;
|
|
114
121
|
retryable: boolean;
|
|
@@ -159,7 +166,7 @@ export declare function serializeError(error: E2EError, extras?: {
|
|
|
159
166
|
/**
|
|
160
167
|
* Replaces secret values in the message, the details, and the stack: an
|
|
161
168
|
* assertion that observed a secret on screen, or a message that quoted
|
|
162
|
-
* one, must not carry it into the report.
|
|
169
|
+
* one, must not carry it into the report. Defaults to `setErrorRedactor`'s.
|
|
163
170
|
*/
|
|
164
171
|
redact?: ((text: string) => string) | undefined;
|
|
165
172
|
}): SerializedError;
|
package/dist/internal/errors.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
/** Runner-owned error taxonomy and exit-code mapping. */
|
|
2
2
|
import { stripVTControlCharacters } from 'node:util';
|
|
3
|
+
import { realmSlot } from './realm-slot.js';
|
|
3
4
|
import { sourceLocation } from './source.js';
|
|
4
5
|
import { ENGINE_ERROR_CODES, EngineError, RETRYABLE_ENGINE_ERROR_CODES, } from '../engine/contract.js';
|
|
5
6
|
/** Message of an arbitrary thrown value, for diagnostics that must not throw. */
|
|
6
7
|
export function errorMessage(cause) {
|
|
7
8
|
return cause instanceof Error ? cause.message : String(cause);
|
|
8
9
|
}
|
|
10
|
+
/** The redactor `serializeError` falls back to, on `globalThis` so a project's copy of e2e shares it. */
|
|
11
|
+
const redactorSlot = realmSlot('e2e.error-redactor.v1');
|
|
12
|
+
/** Installs the process-wide redactor `serializeError` uses when none is given; `undefined` clears it. */
|
|
13
|
+
export function setErrorRedactor(redact) {
|
|
14
|
+
if (redact === undefined)
|
|
15
|
+
redactorSlot.delete(globalThis);
|
|
16
|
+
else
|
|
17
|
+
redactorSlot.set(globalThis, redact);
|
|
18
|
+
}
|
|
9
19
|
/**
|
|
10
20
|
* The message of a thrown value led by its code when it is an e2e error, as
|
|
11
21
|
* the CLI prints one: `POLICY_DENIED: forbidden URL scheme: file:`. Any
|
|
12
22
|
* other value is its message alone.
|
|
13
23
|
*/
|
|
14
24
|
export function codedMessage(cause) {
|
|
15
|
-
const code =
|
|
25
|
+
const code = isE2EError(cause) ? `${cause.code}: ` : '';
|
|
16
26
|
return `${code}${errorMessage(cause)}`;
|
|
17
27
|
}
|
|
18
28
|
/**
|
|
@@ -129,8 +139,13 @@ const CATEGORIES = new Set([
|
|
|
129
139
|
'internal',
|
|
130
140
|
'interrupted',
|
|
131
141
|
]);
|
|
132
|
-
/**
|
|
133
|
-
|
|
142
|
+
/**
|
|
143
|
+
* True for an E2EError from this module copy or another. An engine imported by
|
|
144
|
+
* a config file loads its own copy of the runner, so its errors fail
|
|
145
|
+
* `instanceof E2EError` and every subclass check; test for runner errors with
|
|
146
|
+
* this, and read the subclass off `category` and `code`.
|
|
147
|
+
*/
|
|
148
|
+
export function isE2EError(value) {
|
|
134
149
|
return (value instanceof Error &&
|
|
135
150
|
value[E2E_ERROR_MARKER] === true &&
|
|
136
151
|
CATEGORIES.has(value.category) &&
|
|
@@ -197,8 +212,8 @@ export function combineExitCodes(codes) {
|
|
|
197
212
|
* non-retryable infrastructure ENGINE_FAILURE.
|
|
198
213
|
*/
|
|
199
214
|
export function translateEngineError(cause, suffix = '') {
|
|
200
|
-
if (cause
|
|
201
|
-
return cause;
|
|
215
|
+
if (isE2EError(cause))
|
|
216
|
+
return classifyError(cause);
|
|
202
217
|
const engineError = asEngineError(cause);
|
|
203
218
|
if (engineError !== undefined) {
|
|
204
219
|
switch (engineError.code) {
|
|
@@ -235,7 +250,7 @@ export function translateEngineError(cause, suffix = '') {
|
|
|
235
250
|
* `Error` from an installer - is infrastructure.
|
|
236
251
|
*/
|
|
237
252
|
export function translateProvisioningError(cause, suffix = '') {
|
|
238
|
-
if (
|
|
253
|
+
if (isE2EError(cause))
|
|
239
254
|
return classifyError(cause);
|
|
240
255
|
if (asEngineError(cause)?.code === 'CANCELLED')
|
|
241
256
|
return translateEngineError(cause);
|
|
@@ -245,7 +260,7 @@ export function translateProvisioningError(cause, suffix = '') {
|
|
|
245
260
|
export function classifyError(value) {
|
|
246
261
|
if (value instanceof E2EError)
|
|
247
262
|
return value;
|
|
248
|
-
if (
|
|
263
|
+
if (isE2EError(value)) {
|
|
249
264
|
const details = value.details;
|
|
250
265
|
return new E2EError(value.category, value.code, value.message, {
|
|
251
266
|
retryable: value.retryable,
|
|
@@ -264,7 +279,7 @@ export function classifyError(value) {
|
|
|
264
279
|
const MAX_MESSAGE_BYTES = 8192;
|
|
265
280
|
/** Serializes an error into the bounded report-1 error shape. */
|
|
266
281
|
export function serializeError(error, extras = {}) {
|
|
267
|
-
const redact = extras.redact ?? ((text) => text);
|
|
282
|
+
const redact = extras.redact ?? redactorSlot.get(globalThis) ?? ((text) => text);
|
|
268
283
|
const serialized = {
|
|
269
284
|
category: error.category,
|
|
270
285
|
code: error.code,
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The Node.js
|
|
3
|
-
* managers only warn about `engines`, so an
|
|
4
|
-
* otherwise surface as an unrelated TypeError deep
|
|
2
|
+
* The Node.js releases e2e runs on, as a set of release ranges. The CLI checks the floor before it loads
|
|
3
|
+
* anything else: package managers only warn about `engines`, so an
|
|
4
|
+
* unsupported runtime would otherwise surface as an unrelated TypeError deep
|
|
5
|
+
* inside a run.
|
|
5
6
|
*/
|
|
6
|
-
/**
|
|
7
|
-
export declare const
|
|
8
|
-
/**
|
|
9
|
-
|
|
7
|
+
/** `engines.node` in package.json; a unit test keeps the two in step. */
|
|
8
|
+
export declare const SUPPORTED_NODE_RANGE: string;
|
|
9
|
+
/**
|
|
10
|
+
* Explains a runtime e2e cannot run on, or undefined for one it can: a
|
|
11
|
+
* Node.js outside `SUPPORTED_NODE_RANGE`, or a runtime that reports a
|
|
12
|
+
* supported Node.js version without Node.js's `module.registerHooks` (Bun),
|
|
13
|
+
* which e2e's TypeScript loader runs on, or Deno.
|
|
14
|
+
*/
|
|
15
|
+
export declare function unsupportedRuntimeMessage(versions?: Pick<NodeJS.ProcessVersions, 'node'> & {
|
|
16
|
+
readonly bun?: string;
|
|
17
|
+
readonly deno?: string;
|
|
18
|
+
}, hasRegisterHooks?: boolean): string | undefined;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The Node.js
|
|
3
|
-
* managers only warn about `engines`, so an
|
|
4
|
-
* otherwise surface as an unrelated TypeError deep
|
|
2
|
+
* The Node.js releases e2e runs on, as a set of release ranges. The CLI checks the floor before it loads
|
|
3
|
+
* anything else: package managers only warn about `engines`, so an
|
|
4
|
+
* unsupported runtime would otherwise surface as an unrelated TypeError deep
|
|
5
|
+
* inside a run.
|
|
5
6
|
*/
|
|
6
|
-
|
|
7
|
-
export const MINIMUM_NODE_VERSION = '22.12.0';
|
|
7
|
+
import nodeModule from 'node:module';
|
|
8
8
|
function parse(version) {
|
|
9
9
|
const [major = 0, minor = 0, patch = 0] = version
|
|
10
10
|
.replace(/^v/, '')
|
|
@@ -12,16 +12,51 @@ function parse(version) {
|
|
|
12
12
|
.map((part) => Number.parseInt(part, 10) || 0);
|
|
13
13
|
return [major, minor, patch];
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
export function unsupportedNodeMessage(current, minimum = MINIMUM_NODE_VERSION) {
|
|
17
|
-
const have = parse(current);
|
|
18
|
-
const need = parse(minimum);
|
|
15
|
+
function atLeast(have, need) {
|
|
19
16
|
for (let i = 0; i < 3; i += 1) {
|
|
20
|
-
if (have[i]
|
|
21
|
-
return
|
|
22
|
-
if (have[i] < need[i]) {
|
|
23
|
-
return `e2e requires Node.js ${minimum} or newer; this is Node.js ${current.replace(/^v/, '')}. Upgrade Node.js, or switch versions with your version manager (nvm use 22, fnm use 22, volta pin node@22).`;
|
|
24
|
-
}
|
|
17
|
+
if (have[i] !== need[i])
|
|
18
|
+
return have[i] > need[i];
|
|
25
19
|
}
|
|
26
|
-
return
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
function within(version, ranges) {
|
|
23
|
+
const have = parse(version);
|
|
24
|
+
return ranges.some((range) => ('line' in range ? have[0] === range.line : true) && atLeast(have, range.since));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Where e2e's TypeScript loader works. It runs on `module.registerHooks`, and
|
|
28
|
+
* before these releases a CommonJS file an ES module imported cannot
|
|
29
|
+
* `require()` an ES module the hooks load, such as a project's TypeScript:
|
|
30
|
+
* Node.js throws reading its own cache (nodejs/node#59679, in 22.22.3 and
|
|
31
|
+
* 24.8.0). Node.js 23 has no such release.
|
|
32
|
+
*/
|
|
33
|
+
const SUPPORTED = [
|
|
34
|
+
{ line: 22, since: [22, 22, 3] },
|
|
35
|
+
{ since: [24, 8, 0] },
|
|
36
|
+
];
|
|
37
|
+
/** `engines.node` in package.json; a unit test keeps the two in step. */
|
|
38
|
+
export const SUPPORTED_NODE_RANGE = SUPPORTED.map((range) => `${'line' in range ? '^' : '>='}${range.since.join('.')}`).join(' || ');
|
|
39
|
+
/** Explains a Node.js outside `SUPPORTED_NODE_RANGE`, or undefined for one inside it. */
|
|
40
|
+
function unsupportedNodeMessage(current) {
|
|
41
|
+
if (within(current, SUPPORTED))
|
|
42
|
+
return undefined;
|
|
43
|
+
const required = SUPPORTED.map((range) => ('line' in range ? `${range.since.join('.')} or newer on Node.js ${range.line}` : `${range.since.join('.')} or newer`)).join(', or ');
|
|
44
|
+
return `e2e requires Node.js ${required}; this is Node.js ${current.replace(/^v/, '')}. Upgrade Node.js, or switch versions with your version manager (nvm install 24, fnm install 24, volta install node@24).`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Explains a runtime e2e cannot run on, or undefined for one it can: a
|
|
48
|
+
* Node.js outside `SUPPORTED_NODE_RANGE`, or a runtime that reports a
|
|
49
|
+
* supported Node.js version without Node.js's `module.registerHooks` (Bun),
|
|
50
|
+
* which e2e's TypeScript loader runs on, or Deno.
|
|
51
|
+
*/
|
|
52
|
+
export function unsupportedRuntimeMessage(versions = process.versions, hasRegisterHooks = typeof nodeModule.registerHooks === 'function') {
|
|
53
|
+
// Deno implements module.registerHooks, but not the require() of a native addon from inside a hook that oxc needs.
|
|
54
|
+
if (versions.deno !== undefined)
|
|
55
|
+
return `e2e runs on Node.js, not Deno ${versions.deno}: run the CLI with Node.js (npx e2e).`;
|
|
56
|
+
// Checked before the Node.js version a runtime reports, which upgrading Node.js does not change.
|
|
57
|
+
if (!hasRegisterHooks) {
|
|
58
|
+
const runtime = versions.bun !== undefined ? `Bun ${versions.bun}` : 'This runtime';
|
|
59
|
+
return `e2e runs on Node.js: ${runtime} reports Node.js ${versions.node} but has no module.registerHooks, which e2e's TypeScript loader needs. Run the CLI with Node.js: npx e2e, or bunx e2e (without --bun).`;
|
|
60
|
+
}
|
|
61
|
+
return unsupportedNodeMessage(versions.node);
|
|
27
62
|
}
|
|
@@ -44,7 +44,7 @@ export function runScriptCommand(manager, name) {
|
|
|
44
44
|
export function execCommand(manager, command) {
|
|
45
45
|
switch (manager) {
|
|
46
46
|
case 'npm':
|
|
47
|
-
return `npm exec ${command}`;
|
|
47
|
+
return `npm exec -- ${command}`;
|
|
48
48
|
case 'pnpm':
|
|
49
49
|
return `pnpm exec ${command}`;
|
|
50
50
|
case 'yarn':
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Typed accessor for a `Symbol.for` slot on a host object.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Typed accessor for a `Symbol.for` slot on a host object. A project can load
|
|
3
|
+
* a copy of e2e other than the runner's (a second install, a linked checkout,
|
|
4
|
+
* a fresh module registry under a test runner), so runner-owned instances are
|
|
5
|
+
* shared through registry symbols instead of module state. All unsafe reads
|
|
6
|
+
* live here; call sites stay fully typed.
|
|
6
7
|
*/
|
|
7
8
|
export interface RealmSlot<T> {
|
|
8
9
|
set(host: object, value: T): void;
|
|
@@ -37,6 +37,8 @@ export declare class SecretLedger {
|
|
|
37
37
|
register(name: string, value: string): void;
|
|
38
38
|
/** Bound so it can be handed out as a plain function. */
|
|
39
39
|
readonly redact: (text: string) => string;
|
|
40
|
+
/** Whether no value is registered, so there is nothing to redact. */
|
|
41
|
+
get isEmpty(): boolean;
|
|
40
42
|
/** Every registered name and value, in registration order. */
|
|
41
43
|
entries(): readonly (readonly [string, string])[];
|
|
42
44
|
/**
|
|
@@ -47,9 +49,12 @@ export declare class SecretLedger {
|
|
|
47
49
|
* `FRAGMENT_LENGTH` characters, or half of a value shorter than twice that.
|
|
48
50
|
* A cut is one position, so a boundary this short seldom matches plain text
|
|
49
51
|
* by chance. Matched as the value is written, in any case (`caseKey`), as
|
|
50
|
-
* a CSS `text-transform` shows it
|
|
51
|
-
*
|
|
52
|
-
*
|
|
52
|
+
* a CSS `text-transform` shows it, and with every whitespace run on both
|
|
53
|
+
* sides read as one space (`readingKey`) and the value's leading whitespace
|
|
54
|
+
* dropped where whole-value matching trims it (`valuePattern`), as an
|
|
55
|
+
* engine that collapses text before cutting it shows it: the cut falls on
|
|
56
|
+
* text as the engine read it, before any serializer spells it. A case
|
|
57
|
+
* mapping that changes length (`ß` to `SS`) is not followed. The part is found before whole values are
|
|
53
58
|
* rewritten, so a value that starts with another registered value is not
|
|
54
59
|
* half rewritten as the shorter one, and an occurrence running into the
|
|
55
60
|
* part joins its marker. Bound like `redact`.
|
|
@@ -61,7 +66,7 @@ export declare class SecretLedger {
|
|
|
61
66
|
* becomes its marker. For a recording that keeps what an engine read raw
|
|
62
67
|
* (a Playwright trace holds the page's cut text and selections), where a
|
|
63
68
|
* value cut or selected partway through survives whole-value matching.
|
|
64
|
-
* Matched
|
|
69
|
+
* Matched in any case and with whitespace runs collapsed, like `redactCut`. A
|
|
65
70
|
* base64 or base64url run that decodes to text holding a value or a
|
|
66
71
|
* fragment is rewritten whole (a basic-auth header the engine sent). Bound
|
|
67
72
|
* like `redact`.
|