@remix-run/test 0.2.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 +43 -44
- package/dist/app/client/entry.js +4 -0
- 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 +87 -23
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/config.d.ts +55 -21
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +82 -33
- 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 +39 -0
- package/dist/lib/fake-timers.d.ts.map +1 -1
- package/dist/lib/fake-timers.js +27 -8
- 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 +13 -3
- package/dist/lib/reporters/dot.d.ts.map +1 -1
- package/dist/lib/reporters/dot.js +10 -0
- package/dist/lib/reporters/files.d.ts.map +1 -1
- package/dist/lib/reporters/files.js +10 -0
- package/dist/lib/reporters/results.d.ts +1 -1
- package/dist/lib/reporters/results.d.ts.map +1 -1
- package/dist/lib/reporters/spec.d.ts.map +1 -1
- package/dist/lib/reporters/spec.js +10 -0
- package/dist/lib/reporters/tap.d.ts.map +1 -1
- package/dist/lib/reporters/tap.js +10 -0
- package/dist/lib/runner-browser.d.ts.map +1 -1
- package/dist/lib/runner-browser.js +40 -2
- package/dist/lib/runner.d.ts +18 -1
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/runner.js +187 -38
- package/dist/lib/worker-e2e-file.d.ts +11 -0
- package/dist/lib/worker-e2e-file.d.ts.map +1 -0
- package/dist/lib/worker-e2e-file.js +69 -0
- package/dist/lib/worker-e2e.js +11 -47
- package/dist/lib/worker-process.d.ts +2 -0
- package/dist/lib/worker-process.d.ts.map +1 -0
- package/dist/lib/worker-process.js +55 -0
- package/dist/lib/worker-results.d.ts +3 -0
- package/dist/lib/worker-results.d.ts.map +1 -0
- package/dist/lib/worker-results.js +20 -0
- package/dist/lib/worker-server.d.ts +10 -0
- package/dist/lib/worker-server.d.ts.map +1 -0
- package/dist/lib/worker-server.js +112 -0
- package/dist/lib/worker.js +6 -55
- package/package.json +5 -5
- package/src/app/client/entry.ts +4 -0
- package/src/app/server.ts +11 -10
- package/src/cli.ts +121 -28
- package/src/index.ts +1 -1
- package/src/lib/config.ts +144 -58
- 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 +65 -8
- package/src/lib/framework.ts +53 -36
- package/src/lib/import-module.ts +14 -3
- package/src/lib/reporters/dot.ts +9 -0
- package/src/lib/reporters/files.ts +9 -0
- package/src/lib/reporters/results.ts +1 -1
- package/src/lib/reporters/spec.ts +9 -0
- package/src/lib/reporters/tap.ts +9 -0
- package/src/lib/runner-browser.ts +46 -2
- package/src/lib/runner.ts +253 -50
- package/src/lib/ts-transform.ts +1 -1
- package/src/lib/worker-e2e-file.ts +98 -0
- package/src/lib/worker-e2e.ts +14 -51
- package/src/lib/worker-process.ts +69 -0
- package/src/lib/worker-results.ts +22 -0
- package/src/lib/worker-server.ts +123 -0
- package/src/lib/worker.ts +7 -47
- 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
|
|
|
@@ -41,20 +41,14 @@ Run tests with the CLI:
|
|
|
41
41
|
remix test
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
By default, `remix test` discovers all files matching `**/*.test{,.e2e}.{ts,tsx}`. Pass
|
|
44
|
+
By default, `remix test` discovers all files matching `**/*.test{,.e2e}.{ts,tsx}`. Pass one or more globs as positional arguments to override:
|
|
45
45
|
|
|
46
46
|
```sh
|
|
47
47
|
remix test "src/**/*.test.ts"
|
|
48
|
+
remix test "src/**/*.test.ts" "tests/**/*.test.tsx"
|
|
48
49
|
```
|
|
49
50
|
|
|
50
|
-
Or, you may control via the `glob.test` config field/CLI arg.
|
|
51
|
-
|
|
52
|
-
If you install `@remix-run/test` directly instead of the umbrella `remix` package, the same runner is available as `remix-test`:
|
|
53
|
-
|
|
54
|
-
```sh
|
|
55
|
-
npm i @remix-run/test
|
|
56
|
-
remix-test
|
|
57
|
-
```
|
|
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.
|
|
58
52
|
|
|
59
53
|
### Config File
|
|
60
54
|
|
|
@@ -76,15 +70,18 @@ export default {
|
|
|
76
70
|
// Max number of concurrent test workers (default `os.availableParallelism()`)
|
|
77
71
|
concurrency: 2,
|
|
78
72
|
|
|
73
|
+
// Pool for server and E2E test files ("forks", "threads")
|
|
74
|
+
pool: 'forks',
|
|
75
|
+
|
|
79
76
|
// Code coverage options
|
|
80
77
|
coverage: {
|
|
81
78
|
// Enable coverage reporting
|
|
82
79
|
enabled: true,
|
|
83
80
|
// Output directory (default: ".coverage")
|
|
84
81
|
dir: '.coverage',
|
|
85
|
-
// Glob
|
|
86
|
-
include:
|
|
87
|
-
exclude:
|
|
82
|
+
// Glob pattern(s) to include/exclude
|
|
83
|
+
include: 'src/**',
|
|
84
|
+
exclude: 'src/**/*.test.ts',
|
|
88
85
|
// Minimum thresholds (%)
|
|
89
86
|
statements: 80,
|
|
90
87
|
lines: 80,
|
|
@@ -92,12 +89,13 @@ export default {
|
|
|
92
89
|
functions: 80,
|
|
93
90
|
},
|
|
94
91
|
|
|
92
|
+
// Glob pattern(s) identifying test files
|
|
95
93
|
glob: {
|
|
96
|
-
//
|
|
94
|
+
// All test files (default: "**/*.test{,.browser,.e2e}.{ts,tsx}").
|
|
97
95
|
test: '**/*.test{,.browser,.e2e}.ts',
|
|
98
|
-
//
|
|
96
|
+
// Browser test files (default: "**/*.test.browser.{ts,tsx}")
|
|
99
97
|
browser: '**/*.test.browser.ts',
|
|
100
|
-
//
|
|
98
|
+
// E2E test files (default: "**/*.test.e2e.{ts,tsx}")
|
|
101
99
|
e2e: '**/*.test.e2e.ts',
|
|
102
100
|
},
|
|
103
101
|
|
|
@@ -114,7 +112,7 @@ export default {
|
|
|
114
112
|
},
|
|
115
113
|
},
|
|
116
114
|
|
|
117
|
-
//
|
|
115
|
+
// Playwright project(s) to run E2E tests for
|
|
118
116
|
project: 'chromium',
|
|
119
117
|
|
|
120
118
|
// Test reporter ("spec", "files", "tap", "dot")
|
|
@@ -123,8 +121,8 @@ export default {
|
|
|
123
121
|
// Path to a setup module (see Setup section below)
|
|
124
122
|
setup: './test/setup.ts',
|
|
125
123
|
|
|
126
|
-
//
|
|
127
|
-
type: 'server,browser,e2e',
|
|
124
|
+
// Test type(s) to run ("server", "browser", "e2e")
|
|
125
|
+
type: ['server', 'browser', 'e2e'],
|
|
128
126
|
|
|
129
127
|
// Watch for file changes and re-run
|
|
130
128
|
watch: false,
|
|
@@ -141,28 +139,29 @@ remix test --config ./tests/config.ts
|
|
|
141
139
|
|
|
142
140
|
You may also specify any config field as a CLI flag which will take precedence over config file values:
|
|
143
141
|
|
|
144
|
-
| Flag | Short
|
|
145
|
-
| --------------------------- |
|
|
146
|
-
| `--browser.echo` |
|
|
147
|
-
| `--browser.open` |
|
|
148
|
-
| `--concurrency <n>` | `-c`
|
|
149
|
-
| `--coverage` |
|
|
150
|
-
| `--coverage.dir <path>` |
|
|
151
|
-
| `--coverage.include` |
|
|
152
|
-
| `--coverage.exclude` |
|
|
153
|
-
| `--coverage.statements` |
|
|
154
|
-
| `--coverage.lines` |
|
|
155
|
-
| `--coverage.branches` |
|
|
156
|
-
| `--coverage.functions` |
|
|
157
|
-
| `--glob.test` |
|
|
158
|
-
| `--glob.browser` |
|
|
159
|
-
| `--glob.e2e` |
|
|
160
|
-
| `--playwrightConfig <path>` |
|
|
161
|
-
| `--
|
|
162
|
-
| `--
|
|
163
|
-
| `--
|
|
164
|
-
| `--
|
|
165
|
-
| `--
|
|
142
|
+
| Flag | Short |
|
|
143
|
+
| --------------------------- | --------- | --- |
|
|
144
|
+
| `--browser.echo` | |
|
|
145
|
+
| `--browser.open` | |
|
|
146
|
+
| `--concurrency <n>` | `-c` |
|
|
147
|
+
| `--coverage` | |
|
|
148
|
+
| `--coverage.dir <path>` | |
|
|
149
|
+
| `--coverage.include` | |
|
|
150
|
+
| `--coverage.exclude` | |
|
|
151
|
+
| `--coverage.statements` | |
|
|
152
|
+
| `--coverage.lines` | |
|
|
153
|
+
| `--coverage.branches` | |
|
|
154
|
+
| `--coverage.functions` | |
|
|
155
|
+
| `--glob.test` | |
|
|
156
|
+
| `--glob.browser` | |
|
|
157
|
+
| `--glob.e2e` | |
|
|
158
|
+
| `--playwrightConfig <path>` | |
|
|
159
|
+
| `--pool <forks | threads>` | |
|
|
160
|
+
| `--project <name>` | `-p` |
|
|
161
|
+
| `--reporter <name>` | `-r` |
|
|
162
|
+
| `--setup <path>` | `-s` |
|
|
163
|
+
| `--type <name>` | `-t` |
|
|
164
|
+
| `--watch` | `-w` |
|
|
166
165
|
|
|
167
166
|
### Setup
|
|
168
167
|
|
|
@@ -210,11 +209,11 @@ suite('My Test Suite', () => {
|
|
|
210
209
|
|
|
211
210
|
### Programmatic runner
|
|
212
211
|
|
|
213
|
-
|
|
212
|
+
`remix/test/cli` exports `runRemixTest()` for tools that want to run the test runner without
|
|
214
213
|
exiting the current process:
|
|
215
214
|
|
|
216
215
|
```ts
|
|
217
|
-
import { runRemixTest } from '
|
|
216
|
+
import { runRemixTest } from 'remix/test/cli'
|
|
218
217
|
|
|
219
218
|
let exitCode = await runRemixTest({
|
|
220
219
|
argv: ['--type', 'server'],
|
|
@@ -222,7 +221,7 @@ let exitCode = await runRemixTest({
|
|
|
222
221
|
})
|
|
223
222
|
```
|
|
224
223
|
|
|
225
|
-
`runRemixTest()` returns the runner exit code. The `remix test`
|
|
224
|
+
`runRemixTest()` returns the runner exit code. The `remix test` bin wrapper calls
|
|
226
225
|
`process.exit()` with that code when the run finishes so open workers, browsers, or project handles
|
|
227
226
|
cannot keep the CLI alive.
|
|
228
227
|
|
package/dist/app/client/entry.js
CHANGED
|
@@ -167,6 +167,10 @@ function runInIframe(testFile) {
|
|
|
167
167
|
return new Promise((resolve) => {
|
|
168
168
|
let iframe = document.createElement('iframe');
|
|
169
169
|
iframe.src = `/iframe?file=${encodeURIComponent(testFile)}`;
|
|
170
|
+
// Make the iframe as big so we don't get unintentional scrolling in test UIs
|
|
171
|
+
let parentBody = iframe.contentWindow?.document.body;
|
|
172
|
+
iframe.width = Math.max(parentBody?.scrollWidth ?? 0, 800).toString();
|
|
173
|
+
iframe.height = Math.max(Math.round((parentBody?.scrollHeight ?? 0) / 2), 400).toString();
|
|
170
174
|
document.body.appendChild(iframe);
|
|
171
175
|
function onMessage(event) {
|
|
172
176
|
if (event.source !== iframe.contentWindow)
|
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":"
|
|
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
|
@@ -2,15 +2,33 @@ import * as fsp from 'node:fs/promises';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { getRemixTestHelpText, IS_RUNNING_FROM_SRC, loadConfig, } from "./lib/config.js";
|
|
4
4
|
import { generateCombinedCoverageReport } from "./lib/coverage.js";
|
|
5
|
-
import { loadPlaywrightConfig, resolveProjects } from "./lib/playwright.js";
|
|
6
5
|
import { createReporter } from "./lib/reporters/index.js";
|
|
7
|
-
import { runBrowserTests } from "./lib/runner-browser.js";
|
|
8
6
|
import { runServerTests } from "./lib/runner.js";
|
|
9
7
|
import { createWatcher } from "./lib/watcher.js";
|
|
10
8
|
import { importModule } from "./lib/import-module.js";
|
|
11
9
|
import { IS_BUN } from "./lib/runtime.js";
|
|
12
10
|
import { isMainThread } from 'node:worker_threads';
|
|
13
11
|
export { getRemixTestHelpText };
|
|
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
|
+
*/
|
|
14
32
|
export async function runRemixTest(options = {}) {
|
|
15
33
|
let argv = options.argv ?? process.argv.slice(2);
|
|
16
34
|
let cwd = await resolveCwd(options.cwd ?? process.cwd());
|
|
@@ -119,9 +137,6 @@ async function runRemixTestInCwd(argv, cwd) {
|
|
|
119
137
|
browserServerFilesKey = browserFilesKey;
|
|
120
138
|
browserPort = result.port;
|
|
121
139
|
}
|
|
122
|
-
let playwrightConfig = config.playwrightConfig == null || typeof config.playwrightConfig === 'string'
|
|
123
|
-
? await loadPlaywrightConfig(config.playwrightConfig, cwd)
|
|
124
|
-
: config.playwrightConfig;
|
|
125
140
|
let reporter = createReporter(config.reporter);
|
|
126
141
|
let startTime = performance.now();
|
|
127
142
|
let counts = {
|
|
@@ -136,6 +151,7 @@ async function runRemixTestInCwd(argv, cwd) {
|
|
|
136
151
|
let serverResult = await runServerTests(serverFiles, reporter, config.concurrency, 'server', {
|
|
137
152
|
coverage: config.coverage,
|
|
138
153
|
cwd,
|
|
154
|
+
pool: config.pool,
|
|
139
155
|
});
|
|
140
156
|
counts.failed += serverResult.failed;
|
|
141
157
|
counts.passed += serverResult.passed;
|
|
@@ -145,12 +161,17 @@ async function runRemixTestInCwd(argv, cwd) {
|
|
|
145
161
|
}
|
|
146
162
|
// Run browser/e2e tests for all browsers configured by the user
|
|
147
163
|
if (browserFiles.length > 0 || e2eFiles.length > 0) {
|
|
164
|
+
let { loadPlaywrightConfig, resolveProjects } = await importPlaywrightSupport();
|
|
165
|
+
let runBrowserTests = browserFiles.length > 0 ? (await importBrowserTestRunner()).runBrowserTests : undefined;
|
|
166
|
+
let playwrightConfig = config.playwrightConfig == null || typeof config.playwrightConfig === 'string'
|
|
167
|
+
? await loadPlaywrightConfig(config.playwrightConfig, cwd)
|
|
168
|
+
: config.playwrightConfig;
|
|
148
169
|
let projects = resolveProjects(playwrightConfig);
|
|
149
170
|
if (config.project) {
|
|
150
|
-
let projectNames = config.project
|
|
151
|
-
projects = projects.filter((project) => project.name && projectNames.
|
|
171
|
+
let projectNames = new Set(config.project);
|
|
172
|
+
projects = projects.filter((project) => project.name && projectNames.has(project.name));
|
|
152
173
|
if (projects.length === 0) {
|
|
153
|
-
throw new Error(`No playwright projects found with name(s) "${config.project}"`);
|
|
174
|
+
throw new Error(`No playwright projects found with name(s) "${config.project.join(', ')}"`);
|
|
154
175
|
}
|
|
155
176
|
}
|
|
156
177
|
let lastBrowserResult = null;
|
|
@@ -166,7 +187,7 @@ async function runRemixTestInCwd(argv, cwd) {
|
|
|
166
187
|
}
|
|
167
188
|
}
|
|
168
189
|
let [browserResult, e2eResult] = await Promise.all([
|
|
169
|
-
|
|
190
|
+
runBrowserTests != null
|
|
170
191
|
? runBrowserTests({
|
|
171
192
|
baseUrl: `http://localhost:${browserPort}`,
|
|
172
193
|
console: config.browser?.echo,
|
|
@@ -185,6 +206,7 @@ async function runRemixTestInCwd(argv, cwd) {
|
|
|
185
206
|
projectName: project.name,
|
|
186
207
|
coverage: config.coverage,
|
|
187
208
|
cwd,
|
|
209
|
+
pool: config.pool,
|
|
188
210
|
})
|
|
189
211
|
: null,
|
|
190
212
|
]);
|
|
@@ -246,6 +268,36 @@ async function runRemixTestInCwd(argv, cwd) {
|
|
|
246
268
|
}
|
|
247
269
|
return await runPromise;
|
|
248
270
|
}
|
|
271
|
+
async function importPlaywrightSupport() {
|
|
272
|
+
try {
|
|
273
|
+
return await import("./lib/playwright.js");
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
throw toPlaywrightImportError(error);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function importBrowserTestRunner() {
|
|
280
|
+
try {
|
|
281
|
+
return await import("./lib/runner-browser.js");
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
throw toPlaywrightImportError(error);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function toPlaywrightImportError(error) {
|
|
288
|
+
return isMissingPlaywrightImport(error) ? new Error(MISSING_PLAYWRIGHT_MESSAGE) : error;
|
|
289
|
+
}
|
|
290
|
+
function isMissingPlaywrightImport(error) {
|
|
291
|
+
if (!isRecord(error) || typeof error.message !== 'string') {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
return ((error.code === 'ERR_MODULE_NOT_FOUND' || error.code === 'MODULE_NOT_FOUND') &&
|
|
295
|
+
(error.message.includes("Cannot find package 'playwright'") ||
|
|
296
|
+
error.message.includes("Cannot find module 'playwright'")));
|
|
297
|
+
}
|
|
298
|
+
function isRecord(value) {
|
|
299
|
+
return typeof value === 'object' && value !== null;
|
|
300
|
+
}
|
|
249
301
|
async function resolveCwd(cwd) {
|
|
250
302
|
try {
|
|
251
303
|
return await fsp.realpath(cwd);
|
|
@@ -257,12 +309,12 @@ async function resolveCwd(cwd) {
|
|
|
257
309
|
async function discoverTests(config, cwd) {
|
|
258
310
|
let files = await findFiles(config.glob.test, config.glob.exclude, cwd);
|
|
259
311
|
if (files.length === 0) {
|
|
260
|
-
console.log(`No test files found matching pattern: ${config.glob.test}`);
|
|
312
|
+
console.log(`No test files found matching pattern: ${config.glob.test.join(', ')}`);
|
|
261
313
|
return null;
|
|
262
314
|
}
|
|
263
315
|
let browserSet = new Set(await findFiles(config.glob.browser, config.glob.exclude, cwd));
|
|
264
316
|
let e2eSet = new Set(await findFiles(config.glob.e2e, config.glob.exclude, cwd));
|
|
265
|
-
let types = new Set(config.type
|
|
317
|
+
let types = new Set(config.type);
|
|
266
318
|
let browserFiles = types.has('browser') ? files.filter((f) => browserSet.has(f)) : [];
|
|
267
319
|
let e2eFiles = types.has('e2e') ? files.filter((file) => e2eSet.has(file)) : [];
|
|
268
320
|
let serverFiles = types.has('server')
|
|
@@ -270,7 +322,7 @@ async function discoverTests(config, cwd) {
|
|
|
270
322
|
: [];
|
|
271
323
|
let totalFiles = browserFiles.length + serverFiles.length + e2eFiles.length;
|
|
272
324
|
if (totalFiles === 0) {
|
|
273
|
-
console.log(`No test files remain after filtering for type ${config.type}`);
|
|
325
|
+
console.log(`No test files remain after filtering for type ${config.type.join(', ')}`);
|
|
274
326
|
return null;
|
|
275
327
|
}
|
|
276
328
|
console.log(`Found ${totalFiles} test file(s) (${serverFiles.length} server, ${browserFiles.length} browser, ${e2eFiles.length} e2e)`);
|
|
@@ -281,25 +333,37 @@ async function discoverTests(config, cwd) {
|
|
|
281
333
|
e2eFiles,
|
|
282
334
|
};
|
|
283
335
|
}
|
|
284
|
-
async function findFiles(
|
|
285
|
-
let files =
|
|
336
|
+
async function findFiles(patterns, excludePatterns, cwd) {
|
|
337
|
+
let files = new Set();
|
|
286
338
|
if (IS_BUN) {
|
|
287
339
|
// Bun's `fs.promises.glob` follows symlinks and doesn't prune traversal
|
|
288
340
|
// via `exclude`, so it enters pnpm symlink cycles in `node_modules`.
|
|
289
341
|
// Use Bun's native Glob, which defaults to `followSymlinks: false`.
|
|
290
342
|
// @ts-expect-error — bun module is only resolvable under the Bun runtime
|
|
291
343
|
let { Glob } = await import('bun');
|
|
292
|
-
let
|
|
293
|
-
let
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
344
|
+
let excludeGlobs = excludePatterns.map((p) => new Glob(p));
|
|
345
|
+
for (let pattern of patterns) {
|
|
346
|
+
let glob = new Glob(pattern);
|
|
347
|
+
for await (let file of glob.scan({ cwd, absolute: true })) {
|
|
348
|
+
let rel = toPosix(path.relative(cwd, file));
|
|
349
|
+
if (!excludeGlobs.some((eg) => eg.match(rel))) {
|
|
350
|
+
files.add(toPosix(file));
|
|
351
|
+
}
|
|
297
352
|
}
|
|
298
353
|
}
|
|
299
|
-
return files;
|
|
354
|
+
return [...files];
|
|
300
355
|
}
|
|
301
|
-
for
|
|
302
|
-
|
|
356
|
+
for (let pattern of patterns) {
|
|
357
|
+
for await (let file of fsp.glob(pattern, { cwd, exclude: excludePatterns })) {
|
|
358
|
+
files.add(toPosix(path.resolve(cwd, file)));
|
|
359
|
+
}
|
|
303
360
|
}
|
|
304
|
-
return files;
|
|
361
|
+
return [...files];
|
|
362
|
+
}
|
|
363
|
+
// Normalize discovered paths so set membership across the test/browser/e2e
|
|
364
|
+
// `findFiles` calls is byte-stable on every platform. Node accepts forward
|
|
365
|
+
// slashes for filesystem operations on Windows, so downstream `fs.readFile`
|
|
366
|
+
// etc. work without further conversion.
|
|
367
|
+
function toPosix(p) {
|
|
368
|
+
return path.sep === '/' ? p : p.replace(/\\/g, '/');
|
|
305
369
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { RemixTestConfig } from './lib/config.ts';
|
|
1
|
+
export type { RemixTestConfig, RemixTestPool } from './lib/config.ts';
|
|
2
2
|
export { describe, it, suite, test, before, after, beforeEach, afterEach, beforeAll, afterAll, } from './lib/framework.ts';
|
|
3
3
|
export { mock } from './lib/mock.ts';
|
|
4
4
|
export type { TestContext } from './lib/context.ts';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACrE,OAAO,EACL,QAAQ,EACR,EAAE,EACF,KAAK,EACL,IAAI,EACJ,MAAM,EACN,KAAK,EACL,UAAU,EACV,SAAS,EACT,SAAS,EACT,QAAQ,GACT,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAA;AACpC,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA"}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -1,6 +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
|
+
*/
|
|
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
|
+
*/
|
|
4
16
|
export interface RemixTestConfig {
|
|
5
17
|
/**
|
|
6
18
|
* Options for controlling the playwright browser
|
|
@@ -12,17 +24,18 @@ export interface RemixTestConfig {
|
|
|
12
24
|
open?: boolean;
|
|
13
25
|
};
|
|
14
26
|
/**
|
|
15
|
-
* Glob patterns to identify test files
|
|
16
|
-
*
|
|
17
|
-
* - `glob.
|
|
18
|
-
* - `glob.
|
|
19
|
-
* - `glob.
|
|
27
|
+
* Glob patterns to identify test files. Each field accepts a single pattern
|
|
28
|
+
* or an array of patterns; arrays are unioned during discovery.
|
|
29
|
+
* - `glob.test`: Glob pattern(s) for all test files (--glob.test)
|
|
30
|
+
* - `glob.browser`: Glob pattern(s) for the subset of browser test files (--glob.browser)
|
|
31
|
+
* - `glob.e2e`: Glob pattern(s) for the subset of e2e test files (--glob.e2e)
|
|
32
|
+
* - `glob.exclude`: Glob pattern(s) for paths to exclude from discovery (--glob.exclude)
|
|
20
33
|
*/
|
|
21
34
|
glob?: {
|
|
22
|
-
test?: string;
|
|
23
|
-
browser?: string;
|
|
24
|
-
e2e?: string;
|
|
25
|
-
exclude?: string;
|
|
35
|
+
test?: string | string[];
|
|
36
|
+
browser?: string | string[];
|
|
37
|
+
e2e?: string | string[];
|
|
38
|
+
exclude?: string | string[];
|
|
26
39
|
};
|
|
27
40
|
/** Max number of concurrent test workers (--concurrency) */
|
|
28
41
|
concurrency?: number | string;
|
|
@@ -32,8 +45,8 @@ export interface RemixTestConfig {
|
|
|
32
45
|
*/
|
|
33
46
|
coverage?: boolean | {
|
|
34
47
|
dir?: string;
|
|
35
|
-
include?: string[];
|
|
36
|
-
exclude?: string[];
|
|
48
|
+
include?: string | string[];
|
|
49
|
+
exclude?: string | string[];
|
|
37
50
|
statements?: number | string;
|
|
38
51
|
lines?: number | string;
|
|
39
52
|
branches?: number | string;
|
|
@@ -49,12 +62,23 @@ export interface RemixTestConfig {
|
|
|
49
62
|
* PlaywrightTestConfig object. CLI `--playwrightConfig` only accepts a file path.
|
|
50
63
|
*/
|
|
51
64
|
playwrightConfig?: string | PlaywrightTestConfig;
|
|
52
|
-
/**
|
|
53
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Pool used to run server and E2E test files. Forked child processes are the default,
|
|
67
|
+
* but worker threads are available for projects that prefer the previous behavior.
|
|
68
|
+
*/
|
|
69
|
+
pool?: RemixTestPool;
|
|
70
|
+
/**
|
|
71
|
+
* Filter tests to specific playwright project(s) (--project). Accepts a single
|
|
72
|
+
* project name or an array of names; `--project` may be repeated on the CLI.
|
|
73
|
+
*/
|
|
74
|
+
project?: string | string[];
|
|
54
75
|
/** Test reporter (--reporter) */
|
|
55
76
|
reporter?: string;
|
|
56
|
-
/**
|
|
57
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Test type(s) to run (--type). Accepts a single type or an array of types;
|
|
79
|
+
* `--type` may be repeated on the CLI. Valid values: "server", "browser", "e2e".
|
|
80
|
+
*/
|
|
81
|
+
type?: string | string[];
|
|
58
82
|
/** Watch mode — re-run tests on file changes (--watch) */
|
|
59
83
|
watch?: boolean;
|
|
60
84
|
}
|
|
@@ -74,18 +98,28 @@ export interface ResolvedRemixTestConfig {
|
|
|
74
98
|
functions?: number;
|
|
75
99
|
} | undefined;
|
|
76
100
|
glob: {
|
|
77
|
-
test: string;
|
|
78
|
-
browser: string;
|
|
79
|
-
e2e: string;
|
|
80
|
-
exclude: string;
|
|
101
|
+
test: string[];
|
|
102
|
+
browser: string[];
|
|
103
|
+
e2e: string[];
|
|
104
|
+
exclude: string[];
|
|
81
105
|
};
|
|
82
106
|
playwrightConfig: string | PlaywrightTestConfig | undefined;
|
|
83
|
-
project: string | undefined;
|
|
107
|
+
project: string[] | undefined;
|
|
84
108
|
reporter: string;
|
|
109
|
+
pool: RemixTestPool;
|
|
85
110
|
setup: string | undefined;
|
|
86
|
-
type: string;
|
|
111
|
+
type: string[];
|
|
87
112
|
watch: boolean;
|
|
88
113
|
}
|
|
89
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
|
+
*/
|
|
90
124
|
export declare function getRemixTestHelpText(_target?: NodeJS.WriteStream): string;
|
|
91
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":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAG3D,eAAO,MAAM,mBAAmB,SAA4D,CAAA;AAa5F,wBAAgB,qBAAqB,IAAI,MAAM,CAS9C;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
|