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.
Files changed (256) hide show
  1. package/README.md +10 -0
  2. package/dist/.build.json +1 -1
  3. package/dist/agent/act.js +7 -4
  4. package/dist/agent/action-dispatcher.js +4 -5
  5. package/dist/agent/actions.d.ts +32 -23
  6. package/dist/agent/actions.js +38 -24
  7. package/dist/agent/ai-sdk.js +5 -4
  8. package/dist/agent/error.js +3 -3
  9. package/dist/agent/invocation.js +3 -4
  10. package/dist/agent/model/sdk.js +2 -0
  11. package/dist/agent/model/tool-choice.d.ts +9 -0
  12. package/dist/agent/model/tool-choice.js +14 -0
  13. package/dist/agent/observation-feed.d.ts +5 -6
  14. package/dist/agent/observation-feed.js +8 -11
  15. package/dist/agent/observation.d.ts +40 -11
  16. package/dist/agent/observation.js +109 -59
  17. package/dist/agent/phases.d.ts +7 -1
  18. package/dist/agent/phases.js +12 -3
  19. package/dist/agent/point-tap.d.ts +8 -8
  20. package/dist/agent/point-tap.js +1 -1
  21. package/dist/agent/prompts.js +3 -0
  22. package/dist/agent/replay.d.ts +19 -8
  23. package/dist/agent/replay.js +29 -29
  24. package/dist/agent/scroll-target.d.ts +2 -3
  25. package/dist/agent/step-cache.d.ts +43 -15
  26. package/dist/agent/step-cache.js +156 -67
  27. package/dist/agent/tool-loop.js +94 -5
  28. package/dist/cache/anchors.d.ts +72 -23
  29. package/dist/cache/anchors.js +233 -60
  30. package/dist/cache/context.d.ts +25 -14
  31. package/dist/cache/context.js +50 -8
  32. package/dist/cache/decide.d.ts +2 -2
  33. package/dist/cache/decide.js +4 -6
  34. package/dist/cache/identity.d.ts +18 -4
  35. package/dist/cache/identity.js +9 -4
  36. package/dist/cache/recorder.d.ts +15 -1
  37. package/dist/cache/recorder.js +36 -23
  38. package/dist/cache/rekeyed.d.ts +46 -0
  39. package/dist/cache/rekeyed.js +89 -0
  40. package/dist/cache/relocate.d.ts +14 -14
  41. package/dist/cache/relocate.js +33 -25
  42. package/dist/cache/route.d.ts +32 -33
  43. package/dist/cache/route.js +94 -50
  44. package/dist/cache/store.d.ts +6 -0
  45. package/dist/cache/store.js +21 -1
  46. package/dist/cache/trace.d.ts +53 -14
  47. package/dist/cache/trace.js +76 -30
  48. package/dist/cli/bin.js +2 -2
  49. package/dist/cli/index.js +43 -32
  50. package/dist/cli/init/engines.d.ts +1 -3
  51. package/dist/cli/init/engines.js +2 -2
  52. package/dist/cli/init/gateways.d.ts +1 -1
  53. package/dist/cli/init/gateways.js +11 -1
  54. package/dist/cli/init/mcp-config.d.ts +2 -1
  55. package/dist/cli/init/mcp-config.js +8 -3
  56. package/dist/cli/init/sibling-versions.json +1 -1
  57. package/dist/cli/init/versions.d.ts +0 -8
  58. package/dist/cli/init/versions.js +1 -10
  59. package/dist/cli/mcp.d.ts +3 -2
  60. package/dist/cli/mcp.js +52 -16
  61. package/dist/cli/run-output.d.ts +17 -0
  62. package/dist/cli/run-output.js +45 -0
  63. package/dist/collect/collect.js +8 -4
  64. package/dist/collect/registry.js +27 -16
  65. package/dist/config/compiled-files.d.ts +36 -0
  66. package/dist/config/compiled-files.js +50 -0
  67. package/dist/config/diagnose.js +21 -19
  68. package/dist/config/esm-hooks.d.ts +39 -44
  69. package/dist/config/esm-hooks.js +238 -63
  70. package/dist/config/load.d.ts +1 -7
  71. package/dist/config/load.js +23 -66
  72. package/dist/config/register.d.ts +8 -0
  73. package/dist/config/register.js +9 -0
  74. package/dist/config/resolve.d.ts +1 -0
  75. package/dist/config/resolve.js +33 -1
  76. package/dist/config/specifiers.d.ts +19 -0
  77. package/dist/config/specifiers.js +33 -0
  78. package/dist/config/tsconfig.d.ts +56 -0
  79. package/dist/config/tsconfig.js +115 -0
  80. package/dist/config/typescript.d.ts +19 -0
  81. package/dist/config/typescript.js +253 -0
  82. package/dist/engine/contract.d.ts +1 -0
  83. package/dist/engine/index.d.ts +4 -2
  84. package/dist/engine/session.js +2 -2
  85. package/dist/expect/async.js +4 -2
  86. package/dist/expect/poll-scope.d.ts +52 -0
  87. package/dist/expect/poll-scope.js +113 -0
  88. package/dist/expect/poll.js +33 -6
  89. package/dist/expect/values.js +11 -4
  90. package/dist/internal/abandoned.d.ts +11 -0
  91. package/dist/internal/abandoned.js +23 -0
  92. package/dist/internal/brands.d.ts +2 -0
  93. package/dist/internal/brands.js +2 -0
  94. package/dist/internal/client-identity.d.ts +9 -0
  95. package/dist/internal/client-identity.js +14 -0
  96. package/dist/internal/errors.d.ts +10 -3
  97. package/dist/internal/errors.js +23 -8
  98. package/dist/internal/node-version.d.ts +16 -7
  99. package/dist/internal/node-version.js +50 -15
  100. package/dist/internal/package-manager.js +1 -1
  101. package/dist/internal/realm-slot.d.ts +5 -4
  102. package/dist/internal/redact.d.ts +9 -4
  103. package/dist/internal/redact.js +120 -23
  104. package/dist/internal/time.d.ts +26 -4
  105. package/dist/internal/time.js +58 -14
  106. package/dist/internal/urls.d.ts +9 -5
  107. package/dist/internal/urls.js +19 -10
  108. package/dist/locator/engine.d.ts +12 -2
  109. package/dist/locator/engine.js +55 -14
  110. package/dist/locator/screen.js +59 -9
  111. package/dist/mcp/catalog.d.ts +3 -2
  112. package/dist/mcp/catalog.js +6 -6
  113. package/dist/mcp/output.d.ts +59 -0
  114. package/dist/mcp/output.js +129 -0
  115. package/dist/mcp/server.d.ts +4 -0
  116. package/dist/mcp/server.js +9 -12
  117. package/dist/mcp/session.d.ts +8 -1
  118. package/dist/mcp/session.js +56 -15
  119. package/dist/mcp/tools.js +7 -5
  120. package/dist/oauth/chatgpt.js +3 -23
  121. package/dist/oauth/cli.js +1 -0
  122. package/dist/oauth/copilot.d.ts +5 -4
  123. package/dist/oauth/copilot.js +80 -7
  124. package/dist/oauth/grok.js +2 -1
  125. package/dist/oauth/models.js +2 -1
  126. package/dist/oauth/opencode-console.d.ts +10 -0
  127. package/dist/oauth/opencode-console.js +133 -0
  128. package/dist/oauth/providers/github-copilot.d.ts +12 -0
  129. package/dist/oauth/providers/github-copilot.js +77 -23
  130. package/dist/oauth/providers/opencode-console.d.ts +31 -0
  131. package/dist/oauth/providers/opencode-console.js +148 -0
  132. package/dist/oauth/providers.d.ts +2 -6
  133. package/dist/oauth/providers.js +3 -7
  134. package/dist/oauth/responses.d.ts +9 -0
  135. package/dist/oauth/responses.js +23 -0
  136. package/dist/report/build.d.ts +26 -0
  137. package/dist/report/build.js +16 -8
  138. package/dist/report/failure-text.d.ts +6 -1
  139. package/dist/report/failure-text.js +14 -4
  140. package/dist/report/format.d.ts +10 -5
  141. package/dist/report/format.js +12 -5
  142. package/dist/report/junit.js +11 -12
  143. package/dist/report/list.d.ts +12 -0
  144. package/dist/report/list.js +61 -24
  145. package/dist/report/markdown.d.ts +1 -1
  146. package/dist/report/markdown.js +37 -14
  147. package/dist/report/repeats.d.ts +6 -1
  148. package/dist/report/repeats.js +12 -3
  149. package/dist/run/artifacts.d.ts +22 -4
  150. package/dist/run/artifacts.js +71 -3
  151. package/dist/run/execute.d.ts +34 -5
  152. package/dist/run/execute.js +93 -44
  153. package/dist/run/extended-fixtures.js +6 -2
  154. package/dist/run/failure-evidence.js +10 -4
  155. package/dist/run/fixtures.js +6 -8
  156. package/dist/run/in-process.d.ts +2 -0
  157. package/dist/run/in-process.js +52 -10
  158. package/dist/run/last-run.d.ts +42 -8
  159. package/dist/run/last-run.js +121 -14
  160. package/dist/run/process-output.d.ts +73 -0
  161. package/dist/run/process-output.js +130 -0
  162. package/dist/run/provision.d.ts +2 -0
  163. package/dist/run/provision.js +1 -0
  164. package/dist/run/realm.d.ts +4 -1
  165. package/dist/run/realm.js +20 -6
  166. package/dist/run/records.d.ts +20 -4
  167. package/dist/run/records.js +26 -0
  168. package/dist/run/retry.d.ts +10 -1
  169. package/dist/run/retry.js +30 -12
  170. package/dist/run/runner.d.ts +9 -1
  171. package/dist/run/runner.js +101 -39
  172. package/dist/run/scheduler.js +255 -19
  173. package/dist/run/secrecy.d.ts +22 -19
  174. package/dist/run/secrecy.js +22 -18
  175. package/dist/run/serial.d.ts +75 -2
  176. package/dist/run/serial.js +243 -59
  177. package/dist/run/standalone.js +1 -1
  178. package/dist/run/steps.d.ts +0 -6
  179. package/dist/run/steps.js +3 -20
  180. package/dist/run/trace-redaction.d.ts +5 -6
  181. package/dist/run/trace-redaction.js +5 -6
  182. package/dist/run/worker/entry.js +40 -18
  183. package/dist/run/worker/handle.js +10 -5
  184. package/dist/run/worker/protocol.d.ts +85 -3
  185. package/dist/run/worker/session.d.ts +2 -0
  186. package/dist/run/worker/session.js +29 -0
  187. package/dist/telemetry/environment.d.ts +2 -1
  188. package/dist/telemetry/environment.js +12 -2
  189. package/dist/telemetry/events.js +1 -0
  190. package/dist/telemetry/project.d.ts +5 -6
  191. package/dist/telemetry/project.js +6 -11
  192. package/dist/telemetry/store.d.ts +5 -5
  193. package/dist/telemetry/store.js +5 -7
  194. package/dist/telemetry/telemetry.d.ts +2 -3
  195. package/dist/telemetry/telemetry.js +2 -4
  196. package/dist/types.d.ts +15 -6
  197. package/docs/agent-steps.mdx +1 -0
  198. package/docs/agents.mdx +7 -3
  199. package/docs/api-testing.mdx +2 -1
  200. package/docs/authentication.mdx +6 -3
  201. package/docs/browser.mdx +49 -12
  202. package/docs/bug-bash.mdx +2 -1
  203. package/docs/cache.mdx +66 -19
  204. package/docs/ci.mdx +14 -9
  205. package/docs/coding-agents.mdx +2 -1
  206. package/docs/core-concepts.mdx +208 -0
  207. package/docs/debugging.mdx +25 -12
  208. package/docs/decision-models.mdx +142 -0
  209. package/docs/example-projects.mdx +32 -0
  210. package/docs/executors.mdx +7 -2
  211. package/docs/explore.mdx +1 -0
  212. package/docs/github.mdx +14 -5
  213. package/docs/index.mdx +9 -5
  214. package/docs/integrations/eas.mdx +1 -0
  215. package/docs/integrations/index.mdx +1 -0
  216. package/docs/integrations/kernel.mdx +3 -2
  217. package/docs/migrate/cypress.mdx +26 -3
  218. package/docs/migrate/detox.mdx +24 -1
  219. package/docs/migrate/maestro.mdx +28 -4
  220. package/docs/migrate/playwright.mdx +44 -18
  221. package/docs/migrate/selenium.mdx +27 -3
  222. package/docs/mobile-ci.mdx +6 -5
  223. package/docs/mobile.mdx +96 -8
  224. package/docs/models.mdx +60 -9
  225. package/docs/quickstart.mdx +28 -16
  226. package/docs/reference/agent.mdx +13 -3
  227. package/docs/reference/app.mdx +2 -1
  228. package/docs/reference/cli.mdx +41 -18
  229. package/docs/reference/config.mdx +67 -13
  230. package/docs/reference/engine.mdx +25 -13
  231. package/docs/reference/environment.mdx +10 -2
  232. package/docs/reference/errors.mdx +13 -10
  233. package/docs/reference/expect.mdx +36 -13
  234. package/docs/reference/mcp.mdx +21 -8
  235. package/docs/reference/mobile.mdx +24 -13
  236. package/docs/reference/reporters.mdx +9 -2
  237. package/docs/reference/screen.mdx +49 -16
  238. package/docs/reference/test.mdx +32 -5
  239. package/docs/reference/web.mdx +133 -23
  240. package/docs/security.mdx +58 -42
  241. package/docs/subscriptions.mdx +92 -12
  242. package/docs/telemetry.mdx +9 -6
  243. package/docs/tools.mdx +1 -0
  244. package/docs/{starting-your-app.mdx → web.mdx} +157 -11
  245. package/docs/writing-an-engine.mdx +2 -1
  246. package/package.json +25 -8
  247. package/schema/report-v1.schema.json +14 -1
  248. package/skills/e2e/SKILL.md +5 -3
  249. package/skills/e2e/references/agent.md +33 -11
  250. package/skills/e2e/references/debugging.md +4 -4
  251. package/skills/e2e/references/mcp.md +8 -3
  252. package/skills/e2e/references/running.md +15 -9
  253. package/skills/e2e/references/setup.md +45 -16
  254. package/skills/e2e/references/writing-tests.md +35 -18
  255. package/docs/viewports.mdx +0 -146
  256. package/docs/writing-tests.mdx +0 -256
@@ -0,0 +1,56 @@
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 { type PathsMatcher, type TsConfigJson } from 'get-tsconfig';
17
+ export type CompilerOptions = TsConfigJson.CompilerOptions;
18
+ export interface ProjectTsconfig {
19
+ readonly compilerOptions: CompilerOptions;
20
+ /** The candidate files a bare specifier maps to through `paths` or `baseUrl`; null when it sets neither. */
21
+ readonly paths: PathsMatcher | null;
22
+ }
23
+ /** Carries the tsconfig.json files the runner warned about to its workers, so each file is reported once per run. */
24
+ export declare const WARNED_TSCONFIGS_ENV = "E2E_WARNED_TSCONFIGS";
25
+ /** The tsconfig.json files warned about so far, as the value of `WARNED_TSCONFIGS_ENV` for a worker. */
26
+ export declare function warnedTsconfigs(): string;
27
+ /** The project as one module graph sees it. */
28
+ export declare class ProjectView {
29
+ /** The nearest tsconfig.json of each directory looked up so far; undefined where none is above it. */
30
+ private readonly nearest;
31
+ /** Each tsconfig.json read so far; undefined for one that could not be read. */
32
+ private readonly read;
33
+ /** get-tsconfig's own cache of the files it read. */
34
+ private readonly reads;
35
+ /** File lookups answered so far, for a graph's view; undefined where the disk is asked each time. */
36
+ private readonly files;
37
+ constructor(memoizeFiles: boolean);
38
+ /**
39
+ * The tsconfig that governs `file`, or undefined when no readable
40
+ * tsconfig.json is above it. A solution-style tsconfig.json (`files: []`
41
+ * and `references`, as Vite writes) hands a file to the referenced
42
+ * tsconfig that includes it, the way TypeScript's editor support does.
43
+ */
44
+ tsconfigFor(file: string): ProjectTsconfig | undefined;
45
+ /**
46
+ * `tsconfigPath` read with its `extends` chain, or undefined when it
47
+ * cannot be, with a warning: an `extends` naming a package that is not
48
+ * installed (`@tsconfig/node22`) should not stop a run, so TypeScript
49
+ * compiles with default settings and no `paths` instead.
50
+ */
51
+ private readTsconfig;
52
+ /** Whether `file` is a regular file. */
53
+ isFile(file: string): boolean;
54
+ }
55
+ /** The view of the module graph `graph` names (the `e2e-graph` value of a URL), or the shared one for null. */
56
+ export declare function projectView(graph: string | null): ProjectView;
@@ -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'];
@@ -147,8 +147,8 @@ export interface EngineFixtureContext {
147
147
  /**
148
148
  * Resolves a navigation target against the base URL. Throws
149
149
  * `APP_URL_REQUIRED` when the target declares no URL and `POLICY_DENIED`
150
- * for a `file:`, `data:`, or `javascript:` scheme, so a fixture never
151
- * re-implements the rule the harness owns.
150
+ * for any scheme but `http:` and `https:` (`about:blank` aside), so a
151
+ * fixture never re-implements the rule the harness owns.
152
152
  */
153
153
  resolveUrl(url: string): string;
154
154
  };
@@ -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
@@ -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, E2EError, errorMessage, isForeignE2EError, TestError, } from '../internal/errors.js';
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 (cause instanceof E2EError || isForeignE2EError(cause) || asEngineError(cause) !== undefined) {
48
+ if (isE2EError(cause) || asEngineError(cause) !== undefined) {
49
49
  throw cause;
50
50
  }
51
51
  throw new EngineError('ENGINE_FAILURE', `${label} failed: ${errorMessage(cause)}`, {
@@ -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
  });
@@ -144,7 +145,8 @@ class AsyncExpectationImpl {
144
145
  const refs = await engine.resolveAll(this.internals.expression, deadline);
145
146
  return { count: refs.length, node: null, nodes: [] };
146
147
  }
147
- const { node, count } = await engine.tryRead(this.internals.expression, deadline);
148
+ const missingFrame = spec.evaluableWithoutNode === true ? 'empty' : 'wait';
149
+ const { node, count } = await engine.tryRead(this.internals.expression, deadline, missingFrame);
148
150
  return { count, node, nodes: [] };
149
151
  }
150
152
  /** A state matcher; its Playwright flag set to `false` expects the opposite state. */
@@ -0,0 +1,52 @@
1
+ /** Which phase of a test owns an `expect.poll`, so one left running fails that phase and nothing later. */
2
+ import { TestError } from '../internal/errors.ts';
3
+ /**
4
+ * The polls one phase started: a test body with its fixtures and
5
+ * `beforeEach` hooks, one teardown, or one suite hook. A poll is not a step
6
+ * and records nothing; what the scope adds is that a poll the phase did not
7
+ * await ends with the phase instead of failing whatever runs when it times out.
8
+ */
9
+ export declare class PollScope {
10
+ private readonly owner;
11
+ private readonly running;
12
+ private closed;
13
+ /** `owner` completes "... returned before", as in `the test body`. */
14
+ constructor(owner: string);
15
+ /**
16
+ * Runs one poll, owned by this scope until it settles. `work` receives the
17
+ * signal that cancels it when the scope closes first. A poll started after
18
+ * the scope closed, by work its phase left running (a body past its
19
+ * timeout), is cancelled before its first read: it belongs to no phase
20
+ * that could still fail, and must not fail the one running now.
21
+ */
22
+ track<T>(label: string, stack: string | undefined, work: (signal: AbortSignal) => Promise<T>): Promise<T>;
23
+ /**
24
+ * Ends the scope when its phase has returned: every poll still running is
25
+ * cancelled and its later rejection observed. Returns the error naming
26
+ * them, at the line of the first, or nothing when every poll had settled.
27
+ * A caller whose phase was cut short (a timeout, a throw) may drop the
28
+ * error: the phase may have been awaiting the poll. Closing twice returns
29
+ * nothing the second time.
30
+ */
31
+ close(): TestError | undefined;
32
+ private cancel;
33
+ }
34
+ /**
35
+ * Runs `run` with `scope` owning every poll it starts, however deep in its
36
+ * async work and however long that work outlives the phase: a poll belongs
37
+ * to the phase that started it, never to the one running when it is called.
38
+ */
39
+ export declare function runInPollScope<T>(scope: PollScope, run: () => T): T;
40
+ /** One lineage of async work whose polls go to the scope current where it started, until `handOver`. */
41
+ export interface PollLineage {
42
+ run<T>(run: () => T): T;
43
+ /** Sends the lineage's polls from now on to the scope current at this call. */
44
+ handOver(): void;
45
+ }
46
+ /**
47
+ * A lineage for work that spans two phases, a fixture: its setup runs in the
48
+ * body's phase, and the rest of its function after `use` is its teardown's.
49
+ */
50
+ export declare function pollLineage(): PollLineage;
51
+ /** The scope a poll started here belongs to, or undefined outside any phase (a standalone script). */
52
+ export declare function currentPollScope(): PollScope | undefined;