e2e 0.17.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/ai-sdk.js +5 -4
- package/dist/agent/error.js +3 -3
- package/dist/agent/invocation.js +3 -4
- package/dist/agent/model/tool-choice.d.ts +9 -0
- package/dist/agent/model/tool-choice.js +14 -0
- package/dist/agent/observation-feed.js +3 -4
- package/dist/agent/phases.d.ts +7 -1
- package/dist/agent/phases.js +12 -3
- package/dist/agent/prompts.js +3 -0
- package/dist/agent/replay.js +18 -15
- package/dist/agent/step-cache.d.ts +2 -1
- package/dist/agent/step-cache.js +9 -5
- package/dist/agent/tool-loop.js +92 -5
- package/dist/cache/trace.js +6 -2
- package/dist/cli/bin.js +2 -2
- package/dist/cli/index.js +35 -24
- 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/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/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 +2 -0
- package/dist/engine/session.js +2 -2
- package/dist/expect/async.js +2 -1
- package/dist/expect/poll.js +2 -2
- 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 +2 -0
- package/dist/internal/redact.js +4 -0
- package/dist/internal/time.d.ts +21 -5
- package/dist/internal/time.js +35 -3
- package/dist/locator/engine.js +47 -11
- package/dist/locator/screen.js +31 -5
- 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 +55 -14
- package/dist/mcp/tools.js +7 -5
- package/dist/report/list.d.ts +11 -0
- package/dist/report/list.js +23 -6
- package/dist/run/artifacts.d.ts +4 -4
- package/dist/run/artifacts.js +3 -2
- package/dist/run/execute.d.ts +25 -5
- package/dist/run/execute.js +44 -20
- package/dist/run/failure-evidence.js +6 -2
- package/dist/run/in-process.js +51 -10
- 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/records.d.ts +4 -4
- package/dist/run/retry.d.ts +7 -0
- package/dist/run/retry.js +27 -11
- package/dist/run/runner.d.ts +8 -0
- package/dist/run/runner.js +32 -9
- 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 +73 -2
- package/dist/run/serial.js +239 -58
- package/dist/run/standalone.js +1 -1
- package/dist/run/trace-redaction.d.ts +5 -6
- package/dist/run/trace-redaction.js +5 -6
- package/dist/run/worker/entry.js +35 -14
- package/dist/run/worker/handle.js +10 -5
- package/dist/run/worker/protocol.d.ts +83 -3
- package/dist/run/worker/session.js +28 -0
- package/dist/telemetry/environment.d.ts +2 -1
- package/dist/telemetry/environment.js +12 -2
- 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 +6 -3
- package/docs/agent-steps.mdx +1 -0
- package/docs/agents.mdx +1 -0
- package/docs/api-testing.mdx +2 -1
- package/docs/authentication.mdx +3 -2
- package/docs/browser.mdx +7 -6
- package/docs/bug-bash.mdx +2 -1
- package/docs/cache.mdx +2 -1
- package/docs/ci.mdx +3 -2
- package/docs/coding-agents.mdx +2 -1
- package/docs/core-concepts.mdx +208 -0
- package/docs/debugging.mdx +19 -9
- package/docs/decision-models.mdx +142 -0
- package/docs/example-projects.mdx +32 -0
- package/docs/executors.mdx +3 -0
- package/docs/explore.mdx +1 -0
- package/docs/github.mdx +2 -1
- 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 +2 -1
- 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 +27 -4
- package/docs/migrate/selenium.mdx +27 -3
- package/docs/mobile-ci.mdx +6 -5
- package/docs/mobile.mdx +79 -8
- package/docs/models.mdx +7 -2
- package/docs/quickstart.mdx +26 -15
- package/docs/reference/agent.mdx +13 -3
- package/docs/reference/app.mdx +1 -0
- package/docs/reference/cli.mdx +11 -5
- package/docs/reference/config.mdx +42 -6
- package/docs/reference/engine.mdx +22 -10
- package/docs/reference/environment.mdx +2 -1
- package/docs/reference/errors.mdx +11 -8
- package/docs/reference/expect.mdx +2 -1
- package/docs/reference/mcp.mdx +21 -8
- package/docs/reference/mobile.mdx +1 -0
- package/docs/reference/reporters.mdx +1 -0
- package/docs/reference/screen.mdx +19 -9
- package/docs/reference/test.mdx +21 -1
- package/docs/reference/web.mdx +8 -2
- package/docs/security.mdx +33 -27
- package/docs/subscriptions.mdx +1 -0
- package/docs/telemetry.mdx +8 -5
- 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 +11 -7
- package/skills/e2e/SKILL.md +3 -1
- package/skills/e2e/references/agent.md +11 -0
- package/skills/e2e/references/debugging.md +1 -1
- package/skills/e2e/references/mcp.md +8 -3
- package/skills/e2e/references/running.md +2 -1
- package/skills/e2e/references/setup.md +9 -1
- package/skills/e2e/references/writing-tests.md +1 -1
- package/docs/assertions.mdx +0 -94
- package/docs/goals.mdx +0 -176
- package/docs/locators.mdx +0 -64
- package/docs/physical-devices.mdx +0 -76
- package/docs/viewports.mdx +0 -146
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the loader reads about the project besides the modules themselves:
|
|
3
|
+
* the tsconfig.json that governs a file (the nearest one above it, the way
|
|
4
|
+
* `tsc` finds it, with `extends` applied, or the project it references that
|
|
5
|
+
* includes the file) and which candidate files exist.
|
|
6
|
+
*
|
|
7
|
+
* Both are kept with the module graph that read them, the way the graph's
|
|
8
|
+
* modules are: a fresh graph (a config loaded with `graph: true`, which
|
|
9
|
+
* carries `e2e-graph` on every project file) reads its tsconfig.json and
|
|
10
|
+
* file lookups once, as they are when it loads. Every other load in the
|
|
11
|
+
* process shares one view, whose tsconfig.json is read once (collecting
|
|
12
|
+
* fifty test files reads the `extends` chain once) and whose file lookups
|
|
13
|
+
* ask the disk each time, as Node.js's own resolution does, so a test file
|
|
14
|
+
* imported again sees files added or removed since.
|
|
15
|
+
*/
|
|
16
|
+
import { statSync } from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { createFilesMatcher, createPathsMatcher, findTsconfig, parseTsconfig } from 'get-tsconfig';
|
|
19
|
+
/** Carries the tsconfig.json files the runner warned about to its workers, so each file is reported once per run. */
|
|
20
|
+
export const WARNED_TSCONFIGS_ENV = 'E2E_WARNED_TSCONFIGS';
|
|
21
|
+
/** The tsconfig.json files a warning was printed for: in this process, or in the runner that spawned it. */
|
|
22
|
+
const warned = new Set((process.env[WARNED_TSCONFIGS_ENV] ?? '').split(path.delimiter).filter((file) => file !== ''));
|
|
23
|
+
/** The tsconfig.json files warned about so far, as the value of `WARNED_TSCONFIGS_ENV` for a worker. */
|
|
24
|
+
export function warnedTsconfigs() {
|
|
25
|
+
return [...warned].join(path.delimiter);
|
|
26
|
+
}
|
|
27
|
+
/** The tsconfig.json a project reference names: the file, or `tsconfig.json` in the directory. */
|
|
28
|
+
function referencedTsconfig(owner, reference) {
|
|
29
|
+
const target = path.resolve(path.dirname(owner), reference);
|
|
30
|
+
return target.endsWith('.json') ? target : path.join(target, 'tsconfig.json');
|
|
31
|
+
}
|
|
32
|
+
/** The project as one module graph sees it. */
|
|
33
|
+
export class ProjectView {
|
|
34
|
+
/** The nearest tsconfig.json of each directory looked up so far; undefined where none is above it. */
|
|
35
|
+
nearest = new Map();
|
|
36
|
+
/** Each tsconfig.json read so far; undefined for one that could not be read. */
|
|
37
|
+
read = new Map();
|
|
38
|
+
/** get-tsconfig's own cache of the files it read. */
|
|
39
|
+
reads = new Map();
|
|
40
|
+
/** File lookups answered so far, for a graph's view; undefined where the disk is asked each time. */
|
|
41
|
+
files;
|
|
42
|
+
constructor(memoizeFiles) {
|
|
43
|
+
this.files = memoizeFiles ? new Map() : undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The tsconfig that governs `file`, or undefined when no readable
|
|
47
|
+
* tsconfig.json is above it. A solution-style tsconfig.json (`files: []`
|
|
48
|
+
* and `references`, as Vite writes) hands a file to the referenced
|
|
49
|
+
* tsconfig that includes it, the way TypeScript's editor support does.
|
|
50
|
+
*/
|
|
51
|
+
tsconfigFor(file) {
|
|
52
|
+
const directory = path.dirname(file);
|
|
53
|
+
if (!this.nearest.has(directory))
|
|
54
|
+
this.nearest.set(directory, findTsconfig(directory, 'tsconfig.json', this.reads));
|
|
55
|
+
const nearest = this.nearest.get(directory);
|
|
56
|
+
const root = nearest === undefined ? undefined : this.readTsconfig(nearest);
|
|
57
|
+
if (root === undefined || root.references.length === 0 || root.includes(file) !== undefined)
|
|
58
|
+
return root?.tsconfig;
|
|
59
|
+
for (const reference of root.references) {
|
|
60
|
+
const referenced = this.readTsconfig(reference);
|
|
61
|
+
if (referenced !== undefined && referenced.includes(file) !== undefined)
|
|
62
|
+
return referenced.tsconfig;
|
|
63
|
+
}
|
|
64
|
+
return root.tsconfig;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* `tsconfigPath` read with its `extends` chain, or undefined when it
|
|
68
|
+
* cannot be, with a warning: an `extends` naming a package that is not
|
|
69
|
+
* installed (`@tsconfig/node22`) should not stop a run, so TypeScript
|
|
70
|
+
* compiles with default settings and no `paths` instead.
|
|
71
|
+
*/
|
|
72
|
+
readTsconfig(tsconfigPath) {
|
|
73
|
+
if (this.read.has(tsconfigPath))
|
|
74
|
+
return this.read.get(tsconfigPath);
|
|
75
|
+
let read;
|
|
76
|
+
try {
|
|
77
|
+
const result = { path: tsconfigPath, config: parseTsconfig(tsconfigPath, this.reads) };
|
|
78
|
+
read = {
|
|
79
|
+
tsconfig: { compilerOptions: result.config.compilerOptions ?? {}, paths: createPathsMatcher(result) },
|
|
80
|
+
includes: createFilesMatcher(result),
|
|
81
|
+
references: (result.config.references ?? []).map((reference) => referencedTsconfig(tsconfigPath, reference.path)),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (cause) {
|
|
85
|
+
if (!warned.has(tsconfigPath)) {
|
|
86
|
+
warned.add(tsconfigPath);
|
|
87
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
88
|
+
process.emitWarning(`e2e ignores ${tsconfigPath}, which it cannot read (${reason}): TypeScript compiles with default settings and no paths`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
this.read.set(tsconfigPath, read);
|
|
92
|
+
return read;
|
|
93
|
+
}
|
|
94
|
+
/** Whether `file` is a regular file. */
|
|
95
|
+
isFile(file) {
|
|
96
|
+
let known = this.files?.get(file);
|
|
97
|
+
if (known === undefined) {
|
|
98
|
+
known = statSync(file, { throwIfNoEntry: false })?.isFile() === true;
|
|
99
|
+
this.files?.set(file, known);
|
|
100
|
+
}
|
|
101
|
+
return known;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** One view per graph key; the empty key is every load outside a fresh graph. */
|
|
105
|
+
const views = new Map();
|
|
106
|
+
/** The view of the module graph `graph` names (the `e2e-graph` value of a URL), or the shared one for null. */
|
|
107
|
+
export function projectView(graph) {
|
|
108
|
+
const key = graph ?? '';
|
|
109
|
+
let view = views.get(key);
|
|
110
|
+
if (view === undefined) {
|
|
111
|
+
view = new ProjectView(graph !== null);
|
|
112
|
+
views.set(key, view);
|
|
113
|
+
}
|
|
114
|
+
return view;
|
|
115
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiles a TypeScript or JSX file to JavaScript this Node.js runs, with
|
|
3
|
+
* oxc. The output carries an inline source map, so stack traces, a test's
|
|
4
|
+
* location, and failure code frames point at the source. Syntax the running
|
|
5
|
+
* Node.js lacks (`using`, for one) is lowered for it, with helpers from e2e's
|
|
6
|
+
* own copy of `@oxc-project/runtime`, which the project need not install.
|
|
7
|
+
* The caller passes the compiler options that change what runs: JSX, legacy
|
|
8
|
+
* decorators, class field semantics, and import elision.
|
|
9
|
+
*/
|
|
10
|
+
import { type CompiledExtension } from './compiled-files.ts';
|
|
11
|
+
import type { CompilerOptions } from './tsconfig.ts';
|
|
12
|
+
/**
|
|
13
|
+
* `source`, the content of `file` as the next loader handed it over,
|
|
14
|
+
* compiled to JavaScript for its kind with `compilerOptions`, with an
|
|
15
|
+
* inline source map. When an earlier loader already compiled the file and
|
|
16
|
+
* left its own inline map, the two are composed, so positions still lead to
|
|
17
|
+
* the file on disk.
|
|
18
|
+
*/
|
|
19
|
+
export declare function compileTypeScript(file: string, source: string, kind: CompiledExtension, compilerOptions: CompilerOptions): string;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiles a TypeScript or JSX file to JavaScript this Node.js runs, with
|
|
3
|
+
* oxc. The output carries an inline source map, so stack traces, a test's
|
|
4
|
+
* location, and failure code frames point at the source. Syntax the running
|
|
5
|
+
* Node.js lacks (`using`, for one) is lowered for it, with helpers from e2e's
|
|
6
|
+
* own copy of `@oxc-project/runtime`, which the project need not install.
|
|
7
|
+
* The caller passes the compiler options that change what runs: JSX, legacy
|
|
8
|
+
* decorators, class field semantics, and import elision.
|
|
9
|
+
*/
|
|
10
|
+
import remapping, {} from '@jridgewell/remapping';
|
|
11
|
+
import { createRequire } from 'node:module';
|
|
12
|
+
import { pathToFileURL } from 'node:url';
|
|
13
|
+
import { InfrastructureError } from '../internal/errors.js';
|
|
14
|
+
import { HOOKED_REQUIRE_KEY } from './compiled-files.js';
|
|
15
|
+
const ownRequire = createRequire(import.meta.url);
|
|
16
|
+
/** oxc's transformer and parser, loaded on first use. */
|
|
17
|
+
let compiler;
|
|
18
|
+
/**
|
|
19
|
+
* oxc, loaded when the first file compiles rather than when e2e loads, so
|
|
20
|
+
* a command that compiles nothing (`--version`, `--help`) still runs
|
|
21
|
+
* without oxc's native binding. npm leaves that binding out with
|
|
22
|
+
* `--omit=optional`, and from a lockfile written on another platform
|
|
23
|
+
* (npm/cli#4828); the error names it and the way back.
|
|
24
|
+
*/
|
|
25
|
+
function oxc() {
|
|
26
|
+
if (compiler !== undefined)
|
|
27
|
+
return compiler;
|
|
28
|
+
try {
|
|
29
|
+
compiler = { transform: ownRequire('oxc-transform'), parser: ownRequire('oxc-parser') };
|
|
30
|
+
}
|
|
31
|
+
catch (cause) {
|
|
32
|
+
const reason = (cause instanceof Error ? cause.message : String(cause)).split('\n')[0];
|
|
33
|
+
// napi-rs's loader says so when no platform package is installed.
|
|
34
|
+
const missingBinding = /Cannot find native binding/.test(String(cause?.message));
|
|
35
|
+
const platform = `${process.platform}-${process.arch}`;
|
|
36
|
+
const fix = missingBinding
|
|
37
|
+
? `its native bindings for ${platform} (@oxc-transform/binding-${platform}* and @oxc-parser/binding-${platform}*) are not installed. npm skips it with --omit=optional, or when package-lock.json was written on another platform (npm/cli#4828): install again without --omit=optional, or delete package-lock.json and node_modules and run npm install`
|
|
38
|
+
: `it could not be loaded (${reason})`;
|
|
39
|
+
throw new InfrastructureError('TYPESCRIPT_COMPILER_UNAVAILABLE', `e2e compiles TypeScript with oxc, and ${fix}`, { cause });
|
|
40
|
+
}
|
|
41
|
+
return compiler;
|
|
42
|
+
}
|
|
43
|
+
/** The first ECMAScript edition that defines class fields rather than assigning them. */
|
|
44
|
+
const DEFINED_FIELDS_SINCE = 2022;
|
|
45
|
+
/** JSX as the tsconfig asks for it; classic `React.createElement` when it names no automatic runtime. */
|
|
46
|
+
function jsxOptions(options) {
|
|
47
|
+
if (options.jsx === 'react-jsx' || options.jsx === 'react-jsxdev') {
|
|
48
|
+
return { runtime: 'automatic', importSource: options.jsxImportSource ?? 'react', development: options.jsx === 'react-jsxdev' };
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
runtime: 'classic',
|
|
52
|
+
pragma: options.jsxFactory ?? 'React.createElement',
|
|
53
|
+
pragmaFrag: options.jsxFragmentFactory ?? 'React.Fragment',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The ECMAScript year a tsconfig `target` names, or undefined for `ESNext`
|
|
58
|
+
* and an unset target. `ES6` is 2015; `ES3` and `ES5` count as 2009, the
|
|
59
|
+
* year of ES5.
|
|
60
|
+
*/
|
|
61
|
+
function targetYear(target) {
|
|
62
|
+
const edition = /^es(\d+)$/i.exec(target ?? '')?.[1];
|
|
63
|
+
if (edition === undefined)
|
|
64
|
+
return undefined;
|
|
65
|
+
const number = Number(edition);
|
|
66
|
+
if (number === 6)
|
|
67
|
+
return 2015;
|
|
68
|
+
return number < 2015 ? 2009 : number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* `useDefineForClassFields` as TypeScript reads it: explicit, or false for a
|
|
72
|
+
* target below ES2022. An unset target keeps define semantics, as esbuild did.
|
|
73
|
+
*/
|
|
74
|
+
function definesClassFields(options) {
|
|
75
|
+
if (options.useDefineForClassFields !== undefined)
|
|
76
|
+
return options.useDefineForClassFields;
|
|
77
|
+
const year = targetYear(options.target);
|
|
78
|
+
return year === undefined || year >= DEFINED_FIELDS_SINCE;
|
|
79
|
+
}
|
|
80
|
+
/** The oxc options a tsconfig's compiler options call for. */
|
|
81
|
+
function transformOptions(options) {
|
|
82
|
+
const assignsFields = !definesClassFields(options);
|
|
83
|
+
return {
|
|
84
|
+
jsx: jsxOptions(options),
|
|
85
|
+
typescript: {
|
|
86
|
+
onlyRemoveTypeImports: options.verbatimModuleSyntax === true,
|
|
87
|
+
removeClassFieldsWithoutInitializer: assignsFields,
|
|
88
|
+
},
|
|
89
|
+
assumptions: { setPublicClassFields: assignsFields },
|
|
90
|
+
...(options.experimentalDecorators === true
|
|
91
|
+
? { decorator: { legacy: true, emitDecoratorMetadata: options.emitDecoratorMetadata === true } }
|
|
92
|
+
: {}),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** `file:line:column: message` for one oxc-transform error; it counts UTF-8 bytes, an editor counts characters. */
|
|
96
|
+
function describeError(file, source, error) {
|
|
97
|
+
const start = error.labels[0]?.start;
|
|
98
|
+
if (start === undefined)
|
|
99
|
+
return `${file}: ${error.message}`;
|
|
100
|
+
return `${file}:${position(source.subarray(0, start).toString('utf8'))}: ${error.message}`;
|
|
101
|
+
}
|
|
102
|
+
/** `line:column` just past `text`, both counted from 1. */
|
|
103
|
+
function position(text) {
|
|
104
|
+
const lines = text.split('\n');
|
|
105
|
+
return `${lines.length}:${lines.at(-1).length + 1}`;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Syntax oxc 0.152 passes through unchanged that Node.js cannot run, found
|
|
109
|
+
* before compiling so the error names the line and the fix instead of a
|
|
110
|
+
* bare `SyntaxError` from Node.js with no location. Each check only parses
|
|
111
|
+
* a file whose text could hold the construct.
|
|
112
|
+
*/
|
|
113
|
+
function rejectUnsupportedSyntax(file, source, kind, options) {
|
|
114
|
+
const legacyDecorators = options.experimentalDecorators === true;
|
|
115
|
+
const commonJs = kind.format === 'commonjs';
|
|
116
|
+
// A decorator's `@` starts a line or follows whitespace or punctuation, which rules out a package import ('@scope/pkg').
|
|
117
|
+
const mayHold = (commonJs && /\b(?:import|export)\b/.test(source)) || /\baccessor\b/.test(source) || (!legacyDecorators && /(?:^|[\s(,;{}])@[A-Za-z_$]/m.test(source));
|
|
118
|
+
if (!mayHold)
|
|
119
|
+
return;
|
|
120
|
+
const parsed = oxc().parser.parseSync(file, source, { lang: kind.lang, sourceType: kind.format });
|
|
121
|
+
const problems = [];
|
|
122
|
+
if (commonJs) {
|
|
123
|
+
for (const declaration of parsed.module.staticImports) {
|
|
124
|
+
if (declaration.entries.length === 0 || declaration.entries.some((entry) => !entry.isType)) {
|
|
125
|
+
problems.push({ start: declaration.start, message: 'a .cts file is CommonJS, and e2e does not turn an import declaration into require(): write `import name = require(...)`, or `import type` for types' });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
for (const declaration of parsed.module.staticExports) {
|
|
129
|
+
// `export interface`, `export type`, and `export declare` emit nothing, as tsc allows in .cts.
|
|
130
|
+
if (declaration.entries.every((entry) => entry.isType))
|
|
131
|
+
continue;
|
|
132
|
+
problems.push({ start: declaration.start, message: 'a .cts file is CommonJS, and e2e does not turn an export declaration into module.exports: write `export = ...`' });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const { Visitor } = oxc().parser;
|
|
136
|
+
new Visitor({
|
|
137
|
+
AccessorProperty(node) {
|
|
138
|
+
problems.push({ start: node.start, message: '`accessor` class fields (auto-accessors) are not supported: oxc, which compiles e2e\'s TypeScript, does not lower them yet; write a getter and setter over a private field' });
|
|
139
|
+
},
|
|
140
|
+
Decorator(node) {
|
|
141
|
+
if (!legacyDecorators) {
|
|
142
|
+
problems.push({ start: node.start, message: 'decorators need `"experimentalDecorators": true` in tsconfig.json: e2e compiles TypeScript\'s legacy decorators, and Node.js does not run standard decorators yet' });
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
}).visit(parsed.program);
|
|
146
|
+
if (problems.length > 0) {
|
|
147
|
+
throw new SyntaxError(problems.map(({ start, message }) => `${file}:${position(source.slice(0, start))}: ${message}`).join('\n'));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* A `require` that runs resolve hooks, for compiled CommonJS. The `require`
|
|
152
|
+
* Node.js hands a CommonJS module an ES module imported skips them: before
|
|
153
|
+
* nodejs/node#62920 (24.18.0, 26.2.0; not in Node.js 22), and on every
|
|
154
|
+
* version once a `module.register` loader is in the chain too (Yarn PnP,
|
|
155
|
+
* tsx through NODE_OPTIONS). Without it, `require('./helper')` from such a
|
|
156
|
+
* module misses `helper.ts`, a tsconfig alias, and the helpers compiled code
|
|
157
|
+
* requires from e2e's install. The loader puts the function on `globalThis`
|
|
158
|
+
* when it registers (`esm-hooks.ts`): the `require` such a module starts
|
|
159
|
+
* with cannot load another ES module, e2e's included.
|
|
160
|
+
*/
|
|
161
|
+
const HOOKED_REQUIRE = `require = globalThis[Symbol.for(${JSON.stringify(HOOKED_REQUIRE_KEY)})](__filename);`;
|
|
162
|
+
/**
|
|
163
|
+
* Compiled CommonJS as Node.js can run it, read from oxc's own parse of
|
|
164
|
+
* the output:
|
|
165
|
+
* - Oxc keeps a file whose only imports were type-only a module by printing
|
|
166
|
+
* `export {};`, even for CommonJS, where that statement is a syntax error
|
|
167
|
+
* and TypeScript emits nothing. It is an oxc bug, not yet reported
|
|
168
|
+
* upstream (oxc 0.152.0, `crates/oxc_transformer/src/typescript/
|
|
169
|
+
* annotations.rs`, the `no_modules_remaining && some_modules_deleted`
|
|
170
|
+
* branch). The statement is blanked out, so nothing after it moves.
|
|
171
|
+
* - `HOOKED_REQUIRE` follows the last directive on
|
|
172
|
+
* its line, so the prologue stays first and no mapped position moves; with
|
|
173
|
+
* no directive it takes a line of its own at the top (after a hashbang),
|
|
174
|
+
* and the source map gains an unmapped line there.
|
|
175
|
+
*/
|
|
176
|
+
function runnableCommonJs(file, code, mappings) {
|
|
177
|
+
const { program } = oxc().parser.parseSync(file, code, { lang: 'js', sourceType: 'commonjs' });
|
|
178
|
+
let runnable = code;
|
|
179
|
+
for (const statement of program.body) {
|
|
180
|
+
if (statement.type === 'ExportNamedDeclaration' && statement.declaration === null && statement.source === null && statement.specifiers.length === 0) {
|
|
181
|
+
runnable = `${runnable.slice(0, statement.start)}${' '.repeat(statement.end - statement.start)}${runnable.slice(statement.end)}`;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const directives = program.body.filter((statement) => statement.type === 'ExpressionStatement' && typeof statement.directive === 'string');
|
|
185
|
+
const last = directives.at(-1);
|
|
186
|
+
if (last !== undefined)
|
|
187
|
+
return { code: `${runnable.slice(0, last.end)} ${HOOKED_REQUIRE}${runnable.slice(last.end)}`, mappings };
|
|
188
|
+
const line = program.hashbang === null ? 0 : 1;
|
|
189
|
+
const lines = runnable.split('\n');
|
|
190
|
+
lines.splice(line, 0, HOOKED_REQUIRE);
|
|
191
|
+
const mappedLines = mappings.split(';');
|
|
192
|
+
mappedLines.splice(line, 0, '');
|
|
193
|
+
return { code: lines.join('\n'), mappings: mappedLines.join(';') };
|
|
194
|
+
}
|
|
195
|
+
/** Each helper's file in e2e's copy of `@oxc-project/runtime`, resolved once. */
|
|
196
|
+
const helperFiles = new Map();
|
|
197
|
+
/**
|
|
198
|
+
* The compiled code with each runtime helper it imports or requires taken
|
|
199
|
+
* from e2e's own copy: by URL for `import`, by path for `require`. Oxc 0.152
|
|
200
|
+
* has no option to name the helper source, so the import and require
|
|
201
|
+
* specifiers it printed (`helpersUsed`) are rewritten; a redirect of the
|
|
202
|
+
* package name in the resolver would also catch a project's own
|
|
203
|
+
* `@oxc-project/runtime` imports.
|
|
204
|
+
*/
|
|
205
|
+
function withOwnHelpers(code, helpers, format) {
|
|
206
|
+
let rewritten = code;
|
|
207
|
+
for (const specifier of new Set(Object.values(helpers))) {
|
|
208
|
+
let file = helperFiles.get(specifier);
|
|
209
|
+
if (file === undefined) {
|
|
210
|
+
file = ownRequire.resolve(specifier);
|
|
211
|
+
helperFiles.set(specifier, file);
|
|
212
|
+
}
|
|
213
|
+
const written = JSON.stringify(specifier);
|
|
214
|
+
rewritten =
|
|
215
|
+
format === 'module'
|
|
216
|
+
? rewritten.replaceAll(` from ${written};`, ` from ${JSON.stringify(pathToFileURL(file).href)};`)
|
|
217
|
+
: rewritten.replaceAll(`require(${written})`, `require(${JSON.stringify(file)})`);
|
|
218
|
+
}
|
|
219
|
+
return rewritten;
|
|
220
|
+
}
|
|
221
|
+
/** An inline source map at the end of `source`, as another loader earlier in the chain leaves one. */
|
|
222
|
+
const INLINE_MAP = /\n\/\/# sourceMappingURL=data:application\/json(?:;charset=utf-8)?;base64,([A-Za-z0-9+/=]+)\s*$/;
|
|
223
|
+
/**
|
|
224
|
+
* `source`, the content of `file` as the next loader handed it over,
|
|
225
|
+
* compiled to JavaScript for its kind with `compilerOptions`, with an
|
|
226
|
+
* inline source map. When an earlier loader already compiled the file and
|
|
227
|
+
* left its own inline map, the two are composed, so positions still lead to
|
|
228
|
+
* the file on disk.
|
|
229
|
+
*/
|
|
230
|
+
export function compileTypeScript(file, source, kind, compilerOptions) {
|
|
231
|
+
rejectUnsupportedSyntax(file, source, kind, compilerOptions);
|
|
232
|
+
const result = oxc().transform.transformSync(file, source, {
|
|
233
|
+
...transformOptions(compilerOptions),
|
|
234
|
+
lang: kind.lang,
|
|
235
|
+
sourceType: kind.format,
|
|
236
|
+
target: `node${process.versions.node}`,
|
|
237
|
+
sourcemap: true,
|
|
238
|
+
});
|
|
239
|
+
const errors = result.errors.filter((error) => error.severity === 'Error');
|
|
240
|
+
if (errors.length > 0) {
|
|
241
|
+
const bytes = Buffer.from(source, 'utf8');
|
|
242
|
+
throw new SyntaxError(errors.map((error) => describeError(file, bytes, error)).join('\n'));
|
|
243
|
+
}
|
|
244
|
+
let code = withOwnHelpers(result.code, result.helpersUsed, kind.format);
|
|
245
|
+
let mappings = result.map?.mappings ?? '';
|
|
246
|
+
if (kind.format === 'commonjs')
|
|
247
|
+
({ code, mappings } = runnableCommonJs(file, code, mappings));
|
|
248
|
+
// Absolute, so a frame names the file without the loader's query, whatever characters its name holds.
|
|
249
|
+
const own = { version: 3, mappings, names: result.map?.names ?? [], sources: [pathToFileURL(file).href] };
|
|
250
|
+
const incoming = INLINE_MAP.exec(source)?.[1];
|
|
251
|
+
const map = incoming === undefined ? own : remapping([own, JSON.parse(Buffer.from(incoming, 'base64').toString('utf8'))], () => null);
|
|
252
|
+
return `${code}\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(JSON.stringify(map)).toString('base64')}\n`;
|
|
253
|
+
}
|
|
@@ -297,6 +297,7 @@ export type PointerAction = {
|
|
|
297
297
|
} | {
|
|
298
298
|
readonly kind: 'swipeTo';
|
|
299
299
|
readonly target: ViewportPoint;
|
|
300
|
+
readonly durationMs?: number;
|
|
300
301
|
};
|
|
301
302
|
/** Every pointer action kind; what an engine declares in `pointerActions`. */
|
|
302
303
|
export type PointerActionKind = PointerAction['kind'];
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -362,6 +362,8 @@ export interface EnginePrepareInfo {
|
|
|
362
362
|
* fail every test behind it.
|
|
363
363
|
*/
|
|
364
364
|
readonly signal: AbortSignal;
|
|
365
|
+
/** Whether the run asked for a visible surface (`--headed`), as `EngineInitInfo` declares it. */
|
|
366
|
+
readonly headed: boolean;
|
|
365
367
|
/**
|
|
366
368
|
* Reports one line of progress. The runner streams it as a `notice` run
|
|
367
369
|
* event, so it reaches the reporter and every host sink instead of being
|
package/dist/engine/session.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* a non-retryable `ENGINE_FAILURE`, so a crashed engine is an infrastructure
|
|
12
12
|
* failure everywhere and never a retry-eligible test failure.
|
|
13
13
|
*/
|
|
14
|
-
import { asEngineError, ConfigurationError,
|
|
14
|
+
import { asEngineError, ConfigurationError, errorMessage, isE2EError, TestError, } from '../internal/errors.js';
|
|
15
15
|
import { requireKey } from '../internal/keys.js';
|
|
16
16
|
import { EngineError, } from './surface.js';
|
|
17
17
|
/** Located refs are pruned oldest-first past this bound so the map cannot grow unboundedly. */
|
|
@@ -45,7 +45,7 @@ function countSecure(node) {
|
|
|
45
45
|
* engine failing outside its contract, reported as infrastructure.
|
|
46
46
|
*/
|
|
47
47
|
function normalize(cause, label) {
|
|
48
|
-
if (
|
|
48
|
+
if (isE2EError(cause) || asEngineError(cause) !== undefined) {
|
|
49
49
|
throw cause;
|
|
50
50
|
}
|
|
51
51
|
throw new EngineError('ENGINE_FAILURE', `${label} failed: ${errorMessage(cause)}`, {
|
package/dist/expect/async.js
CHANGED
|
@@ -106,7 +106,7 @@ class AsyncExpectationImpl {
|
|
|
106
106
|
return undefined;
|
|
107
107
|
return spec.predicate(sample);
|
|
108
108
|
},
|
|
109
|
-
onTimeout: () => {
|
|
109
|
+
onTimeout: (cause) => {
|
|
110
110
|
const expected = `${this.negated ? 'not ' : ''}${spec.describeExpected}`;
|
|
111
111
|
const observed = spec.observed(lastSample);
|
|
112
112
|
return new TestError('ASSERTION_FAILED', [
|
|
@@ -117,6 +117,7 @@ class AsyncExpectationImpl {
|
|
|
117
117
|
].join('\n'), {
|
|
118
118
|
// The same facts, one per field, for a reporter that lays them out.
|
|
119
119
|
details: { locator: this.label, expected, observed, matches: lastSample.count },
|
|
120
|
+
...(cause === undefined ? {} : { cause }),
|
|
120
121
|
});
|
|
121
122
|
},
|
|
122
123
|
});
|
package/dist/expect/poll.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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
5
|
import { currentPollScope } from './poll-scope.js';
|
|
@@ -39,7 +39,7 @@ function describeError(error) {
|
|
|
39
39
|
return error instanceof Error ? error.message : String(error);
|
|
40
40
|
}
|
|
41
41
|
function isNotYet(error) {
|
|
42
|
-
return error
|
|
42
|
+
return isE2EError(error) && error.code === 'ASSERTION_FAILED';
|
|
43
43
|
}
|
|
44
44
|
function cancelled() {
|
|
45
45
|
return new E2EError('infrastructure', 'CANCELLED', 'operation cancelled');
|
|
@@ -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;
|