@remix-run/test 0.3.0 → 0.4.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 +4 -11
- package/dist/app/server.d.ts.map +1 -1
- package/dist/app/server.js +10 -10
- package/dist/cli.d.ts +30 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +19 -0
- package/dist/lib/config.d.ts +20 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +9 -0
- package/dist/lib/context.d.ts +5 -5
- package/dist/lib/coverage-loader.js +2 -2
- package/dist/lib/coverage.js +1 -1
- package/dist/lib/fake-timers.d.ts +32 -0
- package/dist/lib/fake-timers.d.ts.map +1 -1
- package/dist/lib/framework.d.ts +12 -6
- package/dist/lib/framework.d.ts.map +1 -1
- package/dist/lib/framework.js +24 -12
- package/dist/lib/import-module.d.ts.map +1 -1
- package/dist/lib/import-module.js +5 -4
- package/dist/lib/reporters/results.d.ts +1 -1
- package/dist/lib/reporters/results.d.ts.map +1 -1
- package/dist/lib/runner-browser.d.ts.map +1 -1
- package/dist/lib/runner-browser.js +40 -8
- package/dist/lib/worker-server.js +7 -8
- package/package.json +4 -4
- package/src/app/server.ts +11 -10
- package/src/cli.ts +30 -0
- package/src/lib/config.ts +20 -0
- package/src/lib/context.ts +5 -5
- package/src/lib/coverage-loader.ts +2 -2
- package/src/lib/coverage.ts +1 -1
- package/src/lib/fake-timers.ts +32 -0
- package/src/lib/framework.ts +53 -36
- package/src/lib/import-module.ts +5 -4
- package/src/lib/reporters/results.ts +1 -1
- package/src/lib/runner-browser.ts +46 -8
- package/src/lib/ts-transform.ts +1 -1
- package/src/lib/worker-server.ts +8 -8
- package/tsconfig.json +6 -3
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# test
|
|
2
2
|
|
|
3
3
|
A test framework for JavaScript and TypeScript projects.
|
|
4
4
|
|
|
@@ -50,13 +50,6 @@ remix test "src/**/*.test.ts" "tests/**/*.test.tsx"
|
|
|
50
50
|
|
|
51
51
|
Or, you may control via the `glob.test` config field/CLI arg. Each `glob.*` field accepts a single string or an array of patterns, and `--glob.*` flags can be repeated on the CLI.
|
|
52
52
|
|
|
53
|
-
If you install `@remix-run/test` directly instead of the umbrella `remix` package, the same runner is available as `remix-test`:
|
|
54
|
-
|
|
55
|
-
```sh
|
|
56
|
-
npm i @remix-run/test
|
|
57
|
-
remix-test
|
|
58
|
-
```
|
|
59
|
-
|
|
60
53
|
### Config File
|
|
61
54
|
|
|
62
55
|
Create a `remix-test.config.ts` (or `.js`) file at the root of your project (shown with default values):
|
|
@@ -216,11 +209,11 @@ suite('My Test Suite', () => {
|
|
|
216
209
|
|
|
217
210
|
### Programmatic runner
|
|
218
211
|
|
|
219
|
-
|
|
212
|
+
`remix/test/cli` exports `runRemixTest()` for tools that want to run the test runner without
|
|
220
213
|
exiting the current process:
|
|
221
214
|
|
|
222
215
|
```ts
|
|
223
|
-
import { runRemixTest } from '
|
|
216
|
+
import { runRemixTest } from 'remix/test/cli'
|
|
224
217
|
|
|
225
218
|
let exitCode = await runRemixTest({
|
|
226
219
|
argv: ['--type', 'server'],
|
|
@@ -228,7 +221,7 @@ let exitCode = await runRemixTest({
|
|
|
228
221
|
})
|
|
229
222
|
```
|
|
230
223
|
|
|
231
|
-
`runRemixTest()` returns the runner exit code. The `remix test`
|
|
224
|
+
`runRemixTest()` returns the runner exit code. The `remix test` bin wrapper calls
|
|
232
225
|
`process.exit()` with that code when the run finishes so open workers, browsers, or project handles
|
|
233
226
|
cannot keep the CLI alive.
|
|
234
227
|
|
package/dist/app/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/app/server.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/app/server.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAWjC,wBAAsB,WAAW,CAC/B,YAAY,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC;IAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAkChD"}
|
package/dist/app/server.js
CHANGED
|
@@ -8,6 +8,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
8
8
|
import { SourceMapConsumer, SourceMapGenerator } from 'source-map-js';
|
|
9
9
|
import { getBrowserTestRootDir, IS_RUNNING_FROM_SRC } from "../lib/config.js";
|
|
10
10
|
import { transformTypeScript } from "../lib/ts-transform.js";
|
|
11
|
+
const log = (str) => console.log(`[remix:test] ${str}`);
|
|
12
|
+
const logError = (str, e) => console.error(`[remix:test] Error: ${str}\n`, e);
|
|
11
13
|
export async function startServer(browserFiles) {
|
|
12
14
|
let handle = createRequestHandler(browserFiles);
|
|
13
15
|
let port = 44101;
|
|
@@ -16,7 +18,7 @@ export async function startServer(browserFiles) {
|
|
|
16
18
|
try {
|
|
17
19
|
let server = http.createServer((req, res) => {
|
|
18
20
|
handle(req, res).catch((error) => {
|
|
19
|
-
|
|
21
|
+
logError(`Unhandled error for ${req.url}`, error);
|
|
20
22
|
if (!res.headersSent) {
|
|
21
23
|
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
22
24
|
}
|
|
@@ -28,7 +30,7 @@ export async function startServer(browserFiles) {
|
|
|
28
30
|
server.once('error', reject);
|
|
29
31
|
server.listen(port, () => {
|
|
30
32
|
server.removeListener('error', reject);
|
|
31
|
-
|
|
33
|
+
log(`Test server running on http://localhost:${port}`);
|
|
32
34
|
resolve();
|
|
33
35
|
});
|
|
34
36
|
});
|
|
@@ -38,7 +40,7 @@ export async function startServer(browserFiles) {
|
|
|
38
40
|
if (error.code !== 'EADDRINUSE')
|
|
39
41
|
throw error;
|
|
40
42
|
lastError = error;
|
|
41
|
-
|
|
43
|
+
log(`Port ${port} is in use, trying another port...`);
|
|
42
44
|
port += 1;
|
|
43
45
|
}
|
|
44
46
|
}
|
|
@@ -88,7 +90,7 @@ function createRequestHandler(browserFiles) {
|
|
|
88
90
|
return;
|
|
89
91
|
}
|
|
90
92
|
catch (error) {
|
|
91
|
-
|
|
93
|
+
logError(`Error serving ${url.pathname}`, error);
|
|
92
94
|
sendText(res, 500, String(error));
|
|
93
95
|
return;
|
|
94
96
|
}
|
|
@@ -134,9 +136,8 @@ async function serveScript(res, filePath, urlPath, rootDir) {
|
|
|
134
136
|
code = result.code;
|
|
135
137
|
}
|
|
136
138
|
catch (error) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
sendText(res, 500, msg);
|
|
139
|
+
logError(`Failed to transform ${urlPath}`, error);
|
|
140
|
+
sendText(res, 500, `Failed to transform ${urlPath}`);
|
|
140
141
|
return;
|
|
141
142
|
}
|
|
142
143
|
}
|
|
@@ -147,9 +148,8 @@ async function serveScript(res, filePath, urlPath, rootDir) {
|
|
|
147
148
|
code = await rewriteImports(code, filePath, rootDir);
|
|
148
149
|
}
|
|
149
150
|
catch (error) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
sendText(res, 500, msg);
|
|
151
|
+
logError(`Failed to rewrite imports for ${urlPath}`, error);
|
|
152
|
+
sendText(res, 500, `Failed to rewrite imports for ${urlPath}`);
|
|
153
153
|
return;
|
|
154
154
|
}
|
|
155
155
|
res.writeHead(200, { 'Content-Type': 'application/javascript' });
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,8 +1,38 @@
|
|
|
1
1
|
import { getRemixTestHelpText } from './lib/config.ts';
|
|
2
2
|
export { getRemixTestHelpText };
|
|
3
|
+
/**
|
|
4
|
+
* Options accepted by {@link runRemixTest}.
|
|
5
|
+
*/
|
|
3
6
|
export interface RunRemixTestOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Argument vector to parse. When omitted, `process.argv.slice(2)` is used
|
|
9
|
+
* so the regular CLI flags work transparently.
|
|
10
|
+
*/
|
|
4
11
|
argv?: string[];
|
|
12
|
+
/**
|
|
13
|
+
* Working directory the runner resolves config and test files against
|
|
14
|
+
* (default `process.cwd()`).
|
|
15
|
+
*/
|
|
5
16
|
cwd?: string;
|
|
6
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Programmatic entry point for the `remix-test` CLI. Loads the user's
|
|
20
|
+
* {@link RemixTestConfig}, discovers test files, and runs them through the
|
|
21
|
+
* server/browser/E2E pipelines configured by the project. In watch mode the
|
|
22
|
+
* promise resolves when the user terminates the runner; otherwise it resolves
|
|
23
|
+
* once the run finishes.
|
|
24
|
+
*
|
|
25
|
+
* @param options Optional overrides for the parsed argv and working directory.
|
|
26
|
+
* @returns The exit code the host process should use (`0` on success, `1` on
|
|
27
|
+
* test failure or unrecoverable error).
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* import { runRemixTest } from '@remix-run/test/cli'
|
|
32
|
+
*
|
|
33
|
+
* let exitCode = await runRemixTest()
|
|
34
|
+
* process.exit(exitCode)
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
7
37
|
export declare function runRemixTest(options?: RunRemixTestOptions): Promise<number>;
|
|
8
38
|
//# sourceMappingURL=cli.d.ts.map
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,oBAAoB,EAIrB,MAAM,iBAAiB,CAAA;AAWxB,OAAO,EAAE,oBAAoB,EAAE,CAAA;AAK/B,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAWD,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAerF"}
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,oBAAoB,EAIrB,MAAM,iBAAiB,CAAA;AAWxB,OAAO,EAAE,oBAAoB,EAAE,CAAA;AAK/B;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAWD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAerF"}
|
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,25 @@ import { IS_BUN } from "./lib/runtime.js";
|
|
|
10
10
|
import { isMainThread } from 'node:worker_threads';
|
|
11
11
|
export { getRemixTestHelpText };
|
|
12
12
|
const MISSING_PLAYWRIGHT_MESSAGE = 'Playwright is required to run browser and E2E tests. Install it with `npm i -D playwright`.';
|
|
13
|
+
/**
|
|
14
|
+
* Programmatic entry point for the `remix-test` CLI. Loads the user's
|
|
15
|
+
* {@link RemixTestConfig}, discovers test files, and runs them through the
|
|
16
|
+
* server/browser/E2E pipelines configured by the project. In watch mode the
|
|
17
|
+
* promise resolves when the user terminates the runner; otherwise it resolves
|
|
18
|
+
* once the run finishes.
|
|
19
|
+
*
|
|
20
|
+
* @param options Optional overrides for the parsed argv and working directory.
|
|
21
|
+
* @returns The exit code the host process should use (`0` on success, `1` on
|
|
22
|
+
* test failure or unrecoverable error).
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { runRemixTest } from '@remix-run/test/cli'
|
|
27
|
+
*
|
|
28
|
+
* let exitCode = await runRemixTest()
|
|
29
|
+
* process.exit(exitCode)
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
13
32
|
export async function runRemixTest(options = {}) {
|
|
14
33
|
let argv = options.argv ?? process.argv.slice(2);
|
|
15
34
|
let cwd = await resolveCwd(options.cwd ?? process.cwd());
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import type { PlaywrightTestConfig } from 'playwright/test';
|
|
2
2
|
export declare const IS_RUNNING_FROM_SRC: boolean;
|
|
3
3
|
export declare function getBrowserTestRootDir(): string;
|
|
4
|
+
/**
|
|
5
|
+
* Worker pool used by `remix-test` to run server and E2E test files.
|
|
6
|
+
* `'forks'` (default) uses child processes for stronger isolation; `'threads'`
|
|
7
|
+
* uses worker threads for projects that prefer lower-overhead startup.
|
|
8
|
+
*/
|
|
4
9
|
export type RemixTestPool = 'forks' | 'threads';
|
|
10
|
+
/**
|
|
11
|
+
* User-facing configuration for the `remix-test` CLI. Every field is
|
|
12
|
+
* optional — unset fields fall back to runner defaults. The same shape can
|
|
13
|
+
* be exported from a config file (see `--config`) or passed inline to
|
|
14
|
+
* {@link runRemixTest} via the corresponding flags.
|
|
15
|
+
*/
|
|
5
16
|
export interface RemixTestConfig {
|
|
6
17
|
/**
|
|
7
18
|
* Options for controlling the playwright browser
|
|
@@ -101,5 +112,14 @@ export interface ResolvedRemixTestConfig {
|
|
|
101
112
|
watch: boolean;
|
|
102
113
|
}
|
|
103
114
|
export declare function loadConfig(args?: string[], cwd?: string): Promise<ResolvedRemixTestConfig>;
|
|
115
|
+
/**
|
|
116
|
+
* Returns the formatted `remix-test --help` text. Useful for embedding the
|
|
117
|
+
* runner's CLI options in higher-level tooling.
|
|
118
|
+
*
|
|
119
|
+
* @param _target Output stream the help text will be written to. Reserved
|
|
120
|
+
* for future use (e.g. width-aware formatting); currently
|
|
121
|
+
* unused.
|
|
122
|
+
* @returns The help text as a single string ready to write to a stream.
|
|
123
|
+
*/
|
|
104
124
|
export declare function getRemixTestHelpText(_target?: NodeJS.WriteStream): string;
|
|
105
125
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/config.js
CHANGED
|
@@ -172,6 +172,15 @@ export async function loadConfig(args = process.argv.slice(2), cwd = process.cwd
|
|
|
172
172
|
let config = resolveConfig(fileConfig, parsed);
|
|
173
173
|
return config;
|
|
174
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Returns the formatted `remix-test --help` text. Useful for embedding the
|
|
177
|
+
* runner's CLI options in higher-level tooling.
|
|
178
|
+
*
|
|
179
|
+
* @param _target Output stream the help text will be written to. Reserved
|
|
180
|
+
* for future use (e.g. width-aware formatting); currently
|
|
181
|
+
* unused.
|
|
182
|
+
* @returns The help text as a single string ready to write to a stream.
|
|
183
|
+
*/
|
|
175
184
|
export function getRemixTestHelpText(_target = process.stdout) {
|
|
176
185
|
let lines = [
|
|
177
186
|
'Usage: remix-test [glob...] [options]',
|
package/dist/lib/context.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface TestServer {
|
|
|
13
13
|
close(): Promise<void>;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Test Context providing utilities for testing via remix-test
|
|
16
|
+
* Test Context providing utilities for testing via `remix-test`. The context is
|
|
17
17
|
* passed as the first argument to the {@link test}/{@link it} functions.
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
@@ -33,8 +33,8 @@ export interface TestContext {
|
|
|
33
33
|
*/
|
|
34
34
|
after(fn: () => void): void;
|
|
35
35
|
/**
|
|
36
|
-
* Mock tracker for the current test. Mirrors the shape of Node's
|
|
37
|
-
* `t.mock`. Method mocks created
|
|
36
|
+
* Mock tracker for the current test using {@link mock}. Mirrors the shape of Node's
|
|
37
|
+
* `t.mock`. Method mocks created via `t.mock` are auto-restored on test completion.
|
|
38
38
|
*/
|
|
39
39
|
mock: {
|
|
40
40
|
/**
|
|
@@ -67,8 +67,8 @@ export interface TestContext {
|
|
|
67
67
|
/**
|
|
68
68
|
* Wires a running test server up to a Playwright page so the test can drive
|
|
69
69
|
* it. The server is closed automatically when the test ends. Pair with
|
|
70
|
-
*
|
|
71
|
-
*
|
|
70
|
+
* {@link createTestServer} from `@remix-run/node-fetch-server/test` to spin
|
|
71
|
+
* up the server.
|
|
72
72
|
*
|
|
73
73
|
* @param server - The running server the page should target
|
|
74
74
|
* @returns A `Page` whose `baseURL` is set to `server.baseUrl`.
|
|
@@ -3,8 +3,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
3
3
|
import { transformTypeScript } from "./ts-transform.js";
|
|
4
4
|
// Custom ESM loader hook for TypeScript files.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
6
|
+
// Applies an un-minified esbuild transform that preserves line structure.
|
|
7
|
+
// This ensures V8 coverage byte offsets map
|
|
8
8
|
// cleanly to TypeScript source lines via the inline source map, giving
|
|
9
9
|
// accurate per-line coverage rather than collapsing multiple statements onto
|
|
10
10
|
// a single minified line.
|
package/dist/lib/coverage.js
CHANGED
|
@@ -1,12 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle returned by `mock.timers.enable()` for driving fake timers during a
|
|
3
|
+
* test. While enabled, `setTimeout`, `setInterval`, `clearTimeout`,
|
|
4
|
+
* `clearInterval`, and `Date.now` use the fake clock instead of the real one;
|
|
5
|
+
* timers fire only when the test calls `advance` (or `advanceAsync`).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* it('debounces save calls', (t) => {
|
|
10
|
+
* let timers = t.mock.timers.enable()
|
|
11
|
+
* let save = t.mock.fn()
|
|
12
|
+
* let debounced = debounce(save, 100)
|
|
13
|
+
* debounced(); debounced(); debounced()
|
|
14
|
+
* timers.advance(100)
|
|
15
|
+
* assert.equal(save.mock.calls.length, 1)
|
|
16
|
+
* })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
1
19
|
export interface FakeTimers {
|
|
20
|
+
/**
|
|
21
|
+
* Advance the fake clock by `ms` milliseconds, synchronously firing every
|
|
22
|
+
* timer whose deadline is reached during the advance.
|
|
23
|
+
*
|
|
24
|
+
* @param ms Number of milliseconds to advance.
|
|
25
|
+
*/
|
|
2
26
|
advance(ms: number): void;
|
|
3
27
|
/**
|
|
4
28
|
* Like `advance`, but yields to microtasks between each timer firing so
|
|
5
29
|
* Promise continuations (and any timers they schedule) can settle before
|
|
6
30
|
* the next firing is processed. Use this when a callback awaits work that
|
|
7
31
|
* itself depends on the fake clock.
|
|
32
|
+
*
|
|
33
|
+
* @param ms Number of milliseconds to advance.
|
|
34
|
+
* @returns A promise that resolves once all reachable timers have fired.
|
|
8
35
|
*/
|
|
9
36
|
advanceAsync(ms: number): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Restore the original timer functions and the real clock. Called
|
|
39
|
+
* automatically after the test finishes; may also be called early to
|
|
40
|
+
* disable fake timers mid-test.
|
|
41
|
+
*/
|
|
10
42
|
restore(): void;
|
|
11
43
|
}
|
|
12
44
|
export declare function createFakeTimers(): FakeTimers;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake-timers.d.ts","sourceRoot":"","sources":["../../src/lib/fake-timers.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB
|
|
1
|
+
{"version":3,"file":"fake-timers.d.ts","sourceRoot":"","sources":["../../src/lib/fake-timers.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvC;;;;OAIG;IACH,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,wBAAgB,gBAAgB,IAAI,UAAU,CA0E7C"}
|
package/dist/lib/framework.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { TestContext } from './context.ts';
|
|
2
|
+
declare function describeImpl(name: string, fn: () => void): void;
|
|
3
|
+
declare function describeImpl(name: string, meta: SuiteMeta, fn: () => void): void;
|
|
2
4
|
/**
|
|
3
|
-
* Groups related tests into a named suite. Suites can be nested
|
|
4
|
-
* as such
|
|
5
|
+
* Groups related tests into a named suite. Suites can be nested and will be displayed
|
|
6
|
+
* as such in reporter output. Lifecycle hooks registered inside
|
|
5
7
|
* a `describe` block apply only to tests within that block.
|
|
6
8
|
*
|
|
7
9
|
* @example
|
|
@@ -15,9 +17,10 @@ import type { TestContext } from './context.ts';
|
|
|
15
17
|
* describe.todo('planned suite')
|
|
16
18
|
*
|
|
17
19
|
* @param name - The suite name shown in reporter output.
|
|
20
|
+
* @param meta - Suite metadata such as `skip` or `only`.
|
|
18
21
|
* @param fn - A function that registers the tests and lifecycle hooks in this suite.
|
|
19
22
|
*/
|
|
20
|
-
export declare const describe:
|
|
23
|
+
export declare const describe: typeof describeImpl & {
|
|
21
24
|
skip: (name: string, fn: () => void) => void;
|
|
22
25
|
only: (name: string, fn: () => void) => void;
|
|
23
26
|
todo: (name: string) => void;
|
|
@@ -31,6 +34,8 @@ type TestMeta = {
|
|
|
31
34
|
only?: boolean;
|
|
32
35
|
};
|
|
33
36
|
type TestFn = (t: TestContext) => void | Promise<void>;
|
|
37
|
+
declare function itImpl(name: string, fn: TestFn): void;
|
|
38
|
+
declare function itImpl(name: string, meta: TestMeta, fn: TestFn): void;
|
|
34
39
|
/**
|
|
35
40
|
* Defines a single test case. The optional `TestContext` argument `t` provides
|
|
36
41
|
* mock helpers and per-test cleanup registration.
|
|
@@ -47,21 +52,22 @@ type TestFn = (t: TestContext) => void | Promise<void>;
|
|
|
47
52
|
* it.todo('coming soon')
|
|
48
53
|
*
|
|
49
54
|
* @param name - The test name shown in reporter output.
|
|
55
|
+
* @param meta - Test metadata such as `skip` or `only`.
|
|
50
56
|
* @param fn - The test body, receiving a {@link TestContext} as its first argument.
|
|
51
57
|
*/
|
|
52
|
-
export declare const it:
|
|
58
|
+
export declare const it: typeof itImpl & {
|
|
53
59
|
skip: (name: string, fn?: TestFn | undefined) => void;
|
|
54
60
|
only: (name: string, fn: TestFn) => void;
|
|
55
61
|
todo: (name: string) => void;
|
|
56
62
|
};
|
|
57
63
|
/** Alias for {@link describe}. */
|
|
58
|
-
export declare const suite:
|
|
64
|
+
export declare const suite: typeof describeImpl & {
|
|
59
65
|
skip: (name: string, fn: () => void) => void;
|
|
60
66
|
only: (name: string, fn: () => void) => void;
|
|
61
67
|
todo: (name: string) => void;
|
|
62
68
|
};
|
|
63
69
|
/** Alias for {@link it}. */
|
|
64
|
-
export declare const test:
|
|
70
|
+
export declare const test: typeof itImpl & {
|
|
65
71
|
skip: (name: string, fn?: TestFn | undefined) => void;
|
|
66
72
|
only: (name: string, fn: TestFn) => void;
|
|
67
73
|
todo: (name: string) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"framework.d.ts","sourceRoot":"","sources":["../../src/lib/framework.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"framework.d.ts","sourceRoot":"","sources":["../../src/lib/framework.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AA4F/C,iBAAS,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;AACzD,iBAAS,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;AAO1E;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,QAAQ;;;;CAUnB,CAAA;AAEF,KAAK,SAAS,GAAG;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AACnD,KAAK,QAAQ,GAAG;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAClD,KAAK,MAAM,GAAG,CAAC,CAAC,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAatD,iBAAS,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;AAC/C,iBAAS,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;AAO/D;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,EAAE;;;;CAUb,CAAA;AAEF,kCAAkC;AAClC,eAAO,MAAM,KAAK;;;;CAAW,CAAA;AAC7B,4BAA4B;AAC5B,eAAO,MAAM,IAAI;;;;CAAK,CAAA;AA2BtB;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,QAGxD;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,QAGvD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,QAGvD;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,QAGtD;AAED,mEAAiE;AACjE,eAAO,MAAM,MAAM,kBAAY,CAAA;AAC/B,kEAAgE;AAChE,eAAO,MAAM,KAAK,iBAAW,CAAA"}
|
package/dist/lib/framework.js
CHANGED
|
@@ -26,6 +26,14 @@ function registerDescribe(name, fn, flags) {
|
|
|
26
26
|
throw new Error(`Duplicate suite name: "${fullName}"`);
|
|
27
27
|
}
|
|
28
28
|
let suite = { name: fullName, tests: [], ...flags };
|
|
29
|
+
// Children inherit `skip`/`only` from their parent so that
|
|
30
|
+
// `describe.skip('parent', () => describe('child', () => it(...)))` actually
|
|
31
|
+
// skips the child's tests. The executor walks `rootSuites` as a flat list and
|
|
32
|
+
// only inspects each suite's own flag, so the propagation has to happen here.
|
|
33
|
+
if (currentSuite?.skip)
|
|
34
|
+
suite.skip = true;
|
|
35
|
+
if (currentSuite?.only)
|
|
36
|
+
suite.only = true;
|
|
29
37
|
// Inherit lifecycle hooks from parent suite (or root hooks if at top level)
|
|
30
38
|
let parent = currentSuite ?? rootHooks;
|
|
31
39
|
if (parent.beforeEach)
|
|
@@ -53,9 +61,14 @@ function registerDescribe(name, fn, flags) {
|
|
|
53
61
|
currentSuite = prevSuite;
|
|
54
62
|
}
|
|
55
63
|
}
|
|
64
|
+
function describeImpl(name, metaOrFn, fn) {
|
|
65
|
+
let meta = typeof metaOrFn === 'function' ? {} : metaOrFn;
|
|
66
|
+
let suiteFn = typeof metaOrFn === 'function' ? metaOrFn : fn;
|
|
67
|
+
registerDescribe(name, suiteFn, meta);
|
|
68
|
+
}
|
|
56
69
|
/**
|
|
57
|
-
* Groups related tests into a named suite. Suites can be nested
|
|
58
|
-
* as such
|
|
70
|
+
* Groups related tests into a named suite. Suites can be nested and will be displayed
|
|
71
|
+
* as such in reporter output. Lifecycle hooks registered inside
|
|
59
72
|
* a `describe` block apply only to tests within that block.
|
|
60
73
|
*
|
|
61
74
|
* @example
|
|
@@ -69,13 +82,10 @@ function registerDescribe(name, fn, flags) {
|
|
|
69
82
|
* describe.todo('planned suite')
|
|
70
83
|
*
|
|
71
84
|
* @param name - The suite name shown in reporter output.
|
|
85
|
+
* @param meta - Suite metadata such as `skip` or `only`.
|
|
72
86
|
* @param fn - A function that registers the tests and lifecycle hooks in this suite.
|
|
73
87
|
*/
|
|
74
|
-
export const describe = Object.assign(
|
|
75
|
-
let meta = typeof metaOrFn === 'function' ? {} : metaOrFn;
|
|
76
|
-
let suiteFn = typeof metaOrFn === 'function' ? metaOrFn : fn;
|
|
77
|
-
registerDescribe(name, suiteFn, meta);
|
|
78
|
-
}, {
|
|
88
|
+
export const describe = Object.assign(describeImpl, {
|
|
79
89
|
skip: (name, fn) => registerDescribe(name, fn, { skip: true }),
|
|
80
90
|
only: (name, fn) => registerDescribe(name, fn, { only: true }),
|
|
81
91
|
todo: (name) => {
|
|
@@ -93,6 +103,11 @@ function registerIt(name, fn, flags) {
|
|
|
93
103
|
}
|
|
94
104
|
suite.tests.push({ name, fn, suite, ...flags });
|
|
95
105
|
}
|
|
106
|
+
function itImpl(name, metaOrFn, fn) {
|
|
107
|
+
let meta = typeof metaOrFn === 'function' ? {} : metaOrFn;
|
|
108
|
+
let testFn = typeof metaOrFn === 'function' ? metaOrFn : fn;
|
|
109
|
+
registerIt(name, testFn, meta);
|
|
110
|
+
}
|
|
96
111
|
/**
|
|
97
112
|
* Defines a single test case. The optional `TestContext` argument `t` provides
|
|
98
113
|
* mock helpers and per-test cleanup registration.
|
|
@@ -109,13 +124,10 @@ function registerIt(name, fn, flags) {
|
|
|
109
124
|
* it.todo('coming soon')
|
|
110
125
|
*
|
|
111
126
|
* @param name - The test name shown in reporter output.
|
|
127
|
+
* @param meta - Test metadata such as `skip` or `only`.
|
|
112
128
|
* @param fn - The test body, receiving a {@link TestContext} as its first argument.
|
|
113
129
|
*/
|
|
114
|
-
export const it = Object.assign(
|
|
115
|
-
let meta = typeof metaOrFn === 'function' ? {} : metaOrFn;
|
|
116
|
-
let testFn = typeof metaOrFn === 'function' ? metaOrFn : fn;
|
|
117
|
-
registerIt(name, testFn, meta);
|
|
118
|
-
}, {
|
|
130
|
+
export const it = Object.assign(itImpl, {
|
|
119
131
|
skip: (name, fn) => registerIt(name, fn ?? (() => { }), { skip: true }),
|
|
120
132
|
only: (name, fn) => registerIt(name, fn, { only: true }),
|
|
121
133
|
todo: (name) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-module.d.ts","sourceRoot":"","sources":["../../src/lib/import-module.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"import-module.d.ts","sourceRoot":"","sources":["../../src/lib/import-module.ts"],"names":[],"mappings":"AAmBA,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,CAoBpF"}
|
|
@@ -8,7 +8,6 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
8
8
|
};
|
|
9
9
|
import * as path from 'node:path';
|
|
10
10
|
import { pathToFileURL } from 'node:url';
|
|
11
|
-
import { tsImport } from 'tsx/esm/api';
|
|
12
11
|
import { IS_BUN } from "./runtime.js";
|
|
13
12
|
function hasImportMetaResolve(meta) {
|
|
14
13
|
return 'resolve' in meta && typeof meta.resolve === 'function';
|
|
@@ -23,8 +22,8 @@ function hasImportMetaResolve(meta) {
|
|
|
23
22
|
export async function importModule(specifier, meta) {
|
|
24
23
|
// Absolute Windows paths (`C:\foo\bar.ts`) aren't valid ESM specifiers — only
|
|
25
24
|
// `file:///C:/foo/bar.ts` URLs, relative specifiers, or POSIX absolute paths
|
|
26
|
-
// are. Convert any absolute filesystem path to its `file:` URL so
|
|
27
|
-
//
|
|
25
|
+
// are. Convert any absolute filesystem path to its `file:` URL so module
|
|
26
|
+
// loaders and `import()` accept it on every platform. POSIX absolute paths
|
|
28
27
|
// happen to work as specifiers without conversion, but going through
|
|
29
28
|
// `pathToFileURL` is safe and platform-agnostic.
|
|
30
29
|
let resolvedSpecifier = path.isAbsolute(specifier) ? pathToFileURL(specifier).href : specifier;
|
|
@@ -34,5 +33,7 @@ export async function importModule(specifier, meta) {
|
|
|
34
33
|
}
|
|
35
34
|
return import(__rewriteRelativeImportExtension(meta.resolve(resolvedSpecifier, meta.url)));
|
|
36
35
|
}
|
|
37
|
-
|
|
36
|
+
// node-tsx uses Node APIs that fail in Bun if statically imported
|
|
37
|
+
let { loadModule } = await import('@remix-run/node-tsx/load-module');
|
|
38
|
+
return loadModule(resolvedSpecifier, meta.url);
|
|
38
39
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"results.d.ts","sourceRoot":"","sources":["../../../src/lib/reporters/results.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"results.d.ts","sourceRoot":"","sources":["../../../src/lib/reporters/results.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAErD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;IAChD,KAAK,CAAC,EAAE;QACN,OAAO,EAAE,MAAM,CAAA;QACf,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;IACD,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,yBAAyB,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,eAAe,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACnF;AAED,MAAM,MAAM,MAAM,GAAG;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner-browser.d.ts","sourceRoot":"","sources":["../../src/lib/runner-browser.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,WAAW,EAEjB,MAAM,eAAe,CAAA;AACtB,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"runner-browser.d.ts","sourceRoot":"","sources":["../../src/lib/runner-browser.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,WAAW,EAEjB,MAAM,eAAe,CAAA;AACtB,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAazD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,QAAQ,CAAA;IAGlB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,wBAAsB,eAAe,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC;IACtE,OAAO,EAAE,WAAW,CAAA;IACpB,WAAW,EAAE,WAAW,GAAG,IAAI,CAAA;IAC/B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5B,CAAC,CAiKD"}
|
|
@@ -3,6 +3,7 @@ import { colors } from "./colors.js";
|
|
|
3
3
|
import { getBrowserTestRootDir } from "./config.js";
|
|
4
4
|
import { collectCoverageMapFromPlaywright, } from "./coverage.js";
|
|
5
5
|
import { getBrowserLauncher, getPlaywrightLaunchOptions, getPlaywrightPageOptions, } from "./playwright.js";
|
|
6
|
+
const BROWSER_TEST_FILE_TIMEOUT_MS = 90_000;
|
|
6
7
|
// The harness reports each test result with `filePath` set to the
|
|
7
8
|
// `/scripts/<rel>` URL the iframe loaded. Reporters expect a real filesystem
|
|
8
9
|
// path so they can compute `path.relative(cwd, ...)` cleanly; otherwise they
|
|
@@ -27,12 +28,10 @@ export async function runBrowserTests(options) {
|
|
|
27
28
|
try {
|
|
28
29
|
browser = await getBrowserLauncher(options.playwrightUseOpts).launch(getPlaywrightLaunchOptions(options.playwrightUseOpts));
|
|
29
30
|
page = await browser.newPage(getPlaywrightPageOptions(options.playwrightUseOpts));
|
|
30
|
-
// Cap
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
page.setDefaultTimeout(90_000);
|
|
35
|
-
page.setDefaultNavigationTimeout(90_000);
|
|
31
|
+
// Cap individual browser operations, then separately watch for per-file
|
|
32
|
+
// progress so large suites can run longer than this without hiding hangs.
|
|
33
|
+
page.setDefaultTimeout(BROWSER_TEST_FILE_TIMEOUT_MS);
|
|
34
|
+
page.setDefaultNavigationTimeout(BROWSER_TEST_FILE_TIMEOUT_MS);
|
|
36
35
|
if (options.console) {
|
|
37
36
|
page.on('console', (msg) => console.log(`${colors.dim('[browser console]')} ${msg.text()}`));
|
|
38
37
|
}
|
|
@@ -47,6 +46,26 @@ export async function runBrowserTests(options) {
|
|
|
47
46
|
let totalSkipped = 0;
|
|
48
47
|
let totalTodo = 0;
|
|
49
48
|
let rootDir = getBrowserTestRootDir();
|
|
49
|
+
let completedFiles = 0;
|
|
50
|
+
let totalFiles = options.testFiles?.length ?? 0;
|
|
51
|
+
let progressTimeoutId;
|
|
52
|
+
let rejectProgressTimeout = () => { };
|
|
53
|
+
let progressTimeoutPromise = new Promise((_, reject) => {
|
|
54
|
+
rejectProgressTimeout = reject;
|
|
55
|
+
});
|
|
56
|
+
function clearProgressTimeout() {
|
|
57
|
+
if (progressTimeoutId !== undefined) {
|
|
58
|
+
clearTimeout(progressTimeoutId);
|
|
59
|
+
progressTimeoutId = undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function resetProgressTimeout() {
|
|
63
|
+
clearProgressTimeout();
|
|
64
|
+
progressTimeoutId = setTimeout(() => {
|
|
65
|
+
let progress = totalFiles > 0 ? ` (${completedFiles}/${totalFiles} files completed)` : '';
|
|
66
|
+
rejectProgressTimeout(new Error(`Timed out waiting ${BROWSER_TEST_FILE_TIMEOUT_MS}ms for browser test progress${progress}`));
|
|
67
|
+
}, BROWSER_TEST_FILE_TIMEOUT_MS);
|
|
68
|
+
}
|
|
50
69
|
await page.route('**/file-results', async (route) => {
|
|
51
70
|
let results = route.request().postDataJSON();
|
|
52
71
|
for (let test of results.tests) {
|
|
@@ -58,6 +77,8 @@ export async function runBrowserTests(options) {
|
|
|
58
77
|
totalFailed += results.failed;
|
|
59
78
|
totalSkipped += results.skipped;
|
|
60
79
|
totalTodo += results.todo;
|
|
80
|
+
completedFiles++;
|
|
81
|
+
resetProgressTimeout();
|
|
61
82
|
await route.fulfill({ status: 200 });
|
|
62
83
|
});
|
|
63
84
|
// Fail the tests if any /scripts/ request fails (harness scripts, test
|
|
@@ -77,8 +98,19 @@ export async function runBrowserTests(options) {
|
|
|
77
98
|
});
|
|
78
99
|
// Prevent unhandled rejection if we fail before setting up the listener
|
|
79
100
|
errorPromise.catch(() => { });
|
|
80
|
-
|
|
81
|
-
|
|
101
|
+
progressTimeoutPromise.catch(() => { });
|
|
102
|
+
resetProgressTimeout();
|
|
103
|
+
try {
|
|
104
|
+
await page.goto(options.baseUrl);
|
|
105
|
+
await Promise.race([
|
|
106
|
+
page.waitForFunction('window.__testsDone', undefined, { timeout: 0 }),
|
|
107
|
+
errorPromise,
|
|
108
|
+
progressTimeoutPromise,
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
clearProgressTimeout();
|
|
113
|
+
}
|
|
82
114
|
if (coverageEnabled) {
|
|
83
115
|
let entries = (await page.coverage.stopJSCoverage());
|
|
84
116
|
if (entries.length > 0) {
|