@remix-run/test 0.2.0 → 0.3.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 +39 -33
- package/dist/app/client/entry.js +4 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +68 -23
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/config.d.ts +35 -21
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +73 -33
- package/dist/lib/fake-timers.d.ts +7 -0
- package/dist/lib/fake-timers.d.ts.map +1 -1
- package/dist/lib/fake-timers.js +27 -8
- package/dist/lib/import-module.d.ts.map +1 -1
- package/dist/lib/import-module.js +11 -2
- 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/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 +6 -0
- 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 +113 -0
- package/dist/lib/worker.js +6 -55
- package/package.json +4 -4
- package/src/app/client/entry.ts +4 -0
- package/src/cli.ts +91 -28
- package/src/index.ts +1 -1
- package/src/lib/config.ts +124 -58
- package/src/lib/fake-timers.ts +33 -8
- package/src/lib/import-module.ts +12 -2
- package/src/lib/reporters/dot.ts +9 -0
- package/src/lib/reporters/files.ts +9 -0
- package/src/lib/reporters/spec.ts +9 -0
- package/src/lib/reporters/tap.ts +9 -0
- package/src/lib/runner-browser.ts +6 -0
- package/src/lib/runner.ts +253 -50
- 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/README.md
CHANGED
|
@@ -41,13 +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
|
+
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.
|
|
51
52
|
|
|
52
53
|
If you install `@remix-run/test` directly instead of the umbrella `remix` package, the same runner is available as `remix-test`:
|
|
53
54
|
|
|
@@ -76,15 +77,18 @@ export default {
|
|
|
76
77
|
// Max number of concurrent test workers (default `os.availableParallelism()`)
|
|
77
78
|
concurrency: 2,
|
|
78
79
|
|
|
80
|
+
// Pool for server and E2E test files ("forks", "threads")
|
|
81
|
+
pool: 'forks',
|
|
82
|
+
|
|
79
83
|
// Code coverage options
|
|
80
84
|
coverage: {
|
|
81
85
|
// Enable coverage reporting
|
|
82
86
|
enabled: true,
|
|
83
87
|
// Output directory (default: ".coverage")
|
|
84
88
|
dir: '.coverage',
|
|
85
|
-
// Glob
|
|
86
|
-
include:
|
|
87
|
-
exclude:
|
|
89
|
+
// Glob pattern(s) to include/exclude
|
|
90
|
+
include: 'src/**',
|
|
91
|
+
exclude: 'src/**/*.test.ts',
|
|
88
92
|
// Minimum thresholds (%)
|
|
89
93
|
statements: 80,
|
|
90
94
|
lines: 80,
|
|
@@ -92,12 +96,13 @@ export default {
|
|
|
92
96
|
functions: 80,
|
|
93
97
|
},
|
|
94
98
|
|
|
99
|
+
// Glob pattern(s) identifying test files
|
|
95
100
|
glob: {
|
|
96
|
-
//
|
|
101
|
+
// All test files (default: "**/*.test{,.browser,.e2e}.{ts,tsx}").
|
|
97
102
|
test: '**/*.test{,.browser,.e2e}.ts',
|
|
98
|
-
//
|
|
103
|
+
// Browser test files (default: "**/*.test.browser.{ts,tsx}")
|
|
99
104
|
browser: '**/*.test.browser.ts',
|
|
100
|
-
//
|
|
105
|
+
// E2E test files (default: "**/*.test.e2e.{ts,tsx}")
|
|
101
106
|
e2e: '**/*.test.e2e.ts',
|
|
102
107
|
},
|
|
103
108
|
|
|
@@ -114,7 +119,7 @@ export default {
|
|
|
114
119
|
},
|
|
115
120
|
},
|
|
116
121
|
|
|
117
|
-
//
|
|
122
|
+
// Playwright project(s) to run E2E tests for
|
|
118
123
|
project: 'chromium',
|
|
119
124
|
|
|
120
125
|
// Test reporter ("spec", "files", "tap", "dot")
|
|
@@ -123,8 +128,8 @@ export default {
|
|
|
123
128
|
// Path to a setup module (see Setup section below)
|
|
124
129
|
setup: './test/setup.ts',
|
|
125
130
|
|
|
126
|
-
//
|
|
127
|
-
type: 'server,browser,e2e',
|
|
131
|
+
// Test type(s) to run ("server", "browser", "e2e")
|
|
132
|
+
type: ['server', 'browser', 'e2e'],
|
|
128
133
|
|
|
129
134
|
// Watch for file changes and re-run
|
|
130
135
|
watch: false,
|
|
@@ -141,28 +146,29 @@ remix test --config ./tests/config.ts
|
|
|
141
146
|
|
|
142
147
|
You may also specify any config field as a CLI flag which will take precedence over config file values:
|
|
143
148
|
|
|
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
|
-
| `--
|
|
149
|
+
| Flag | Short |
|
|
150
|
+
| --------------------------- | --------- | --- |
|
|
151
|
+
| `--browser.echo` | |
|
|
152
|
+
| `--browser.open` | |
|
|
153
|
+
| `--concurrency <n>` | `-c` |
|
|
154
|
+
| `--coverage` | |
|
|
155
|
+
| `--coverage.dir <path>` | |
|
|
156
|
+
| `--coverage.include` | |
|
|
157
|
+
| `--coverage.exclude` | |
|
|
158
|
+
| `--coverage.statements` | |
|
|
159
|
+
| `--coverage.lines` | |
|
|
160
|
+
| `--coverage.branches` | |
|
|
161
|
+
| `--coverage.functions` | |
|
|
162
|
+
| `--glob.test` | |
|
|
163
|
+
| `--glob.browser` | |
|
|
164
|
+
| `--glob.e2e` | |
|
|
165
|
+
| `--playwrightConfig <path>` | |
|
|
166
|
+
| `--pool <forks | threads>` | |
|
|
167
|
+
| `--project <name>` | `-p` |
|
|
168
|
+
| `--reporter <name>` | `-r` |
|
|
169
|
+
| `--setup <path>` | `-s` |
|
|
170
|
+
| `--type <name>` | `-t` |
|
|
171
|
+
| `--watch` | `-w` |
|
|
166
172
|
|
|
167
173
|
### Setup
|
|
168
174
|
|
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/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,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"}
|
package/dist/cli.js
CHANGED
|
@@ -2,15 +2,14 @@ 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`.';
|
|
14
13
|
export async function runRemixTest(options = {}) {
|
|
15
14
|
let argv = options.argv ?? process.argv.slice(2);
|
|
16
15
|
let cwd = await resolveCwd(options.cwd ?? process.cwd());
|
|
@@ -119,9 +118,6 @@ async function runRemixTestInCwd(argv, cwd) {
|
|
|
119
118
|
browserServerFilesKey = browserFilesKey;
|
|
120
119
|
browserPort = result.port;
|
|
121
120
|
}
|
|
122
|
-
let playwrightConfig = config.playwrightConfig == null || typeof config.playwrightConfig === 'string'
|
|
123
|
-
? await loadPlaywrightConfig(config.playwrightConfig, cwd)
|
|
124
|
-
: config.playwrightConfig;
|
|
125
121
|
let reporter = createReporter(config.reporter);
|
|
126
122
|
let startTime = performance.now();
|
|
127
123
|
let counts = {
|
|
@@ -136,6 +132,7 @@ async function runRemixTestInCwd(argv, cwd) {
|
|
|
136
132
|
let serverResult = await runServerTests(serverFiles, reporter, config.concurrency, 'server', {
|
|
137
133
|
coverage: config.coverage,
|
|
138
134
|
cwd,
|
|
135
|
+
pool: config.pool,
|
|
139
136
|
});
|
|
140
137
|
counts.failed += serverResult.failed;
|
|
141
138
|
counts.passed += serverResult.passed;
|
|
@@ -145,12 +142,17 @@ async function runRemixTestInCwd(argv, cwd) {
|
|
|
145
142
|
}
|
|
146
143
|
// Run browser/e2e tests for all browsers configured by the user
|
|
147
144
|
if (browserFiles.length > 0 || e2eFiles.length > 0) {
|
|
145
|
+
let { loadPlaywrightConfig, resolveProjects } = await importPlaywrightSupport();
|
|
146
|
+
let runBrowserTests = browserFiles.length > 0 ? (await importBrowserTestRunner()).runBrowserTests : undefined;
|
|
147
|
+
let playwrightConfig = config.playwrightConfig == null || typeof config.playwrightConfig === 'string'
|
|
148
|
+
? await loadPlaywrightConfig(config.playwrightConfig, cwd)
|
|
149
|
+
: config.playwrightConfig;
|
|
148
150
|
let projects = resolveProjects(playwrightConfig);
|
|
149
151
|
if (config.project) {
|
|
150
|
-
let projectNames = config.project
|
|
151
|
-
projects = projects.filter((project) => project.name && projectNames.
|
|
152
|
+
let projectNames = new Set(config.project);
|
|
153
|
+
projects = projects.filter((project) => project.name && projectNames.has(project.name));
|
|
152
154
|
if (projects.length === 0) {
|
|
153
|
-
throw new Error(`No playwright projects found with name(s) "${config.project}"`);
|
|
155
|
+
throw new Error(`No playwright projects found with name(s) "${config.project.join(', ')}"`);
|
|
154
156
|
}
|
|
155
157
|
}
|
|
156
158
|
let lastBrowserResult = null;
|
|
@@ -166,7 +168,7 @@ async function runRemixTestInCwd(argv, cwd) {
|
|
|
166
168
|
}
|
|
167
169
|
}
|
|
168
170
|
let [browserResult, e2eResult] = await Promise.all([
|
|
169
|
-
|
|
171
|
+
runBrowserTests != null
|
|
170
172
|
? runBrowserTests({
|
|
171
173
|
baseUrl: `http://localhost:${browserPort}`,
|
|
172
174
|
console: config.browser?.echo,
|
|
@@ -185,6 +187,7 @@ async function runRemixTestInCwd(argv, cwd) {
|
|
|
185
187
|
projectName: project.name,
|
|
186
188
|
coverage: config.coverage,
|
|
187
189
|
cwd,
|
|
190
|
+
pool: config.pool,
|
|
188
191
|
})
|
|
189
192
|
: null,
|
|
190
193
|
]);
|
|
@@ -246,6 +249,36 @@ async function runRemixTestInCwd(argv, cwd) {
|
|
|
246
249
|
}
|
|
247
250
|
return await runPromise;
|
|
248
251
|
}
|
|
252
|
+
async function importPlaywrightSupport() {
|
|
253
|
+
try {
|
|
254
|
+
return await import("./lib/playwright.js");
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
throw toPlaywrightImportError(error);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async function importBrowserTestRunner() {
|
|
261
|
+
try {
|
|
262
|
+
return await import("./lib/runner-browser.js");
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
throw toPlaywrightImportError(error);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function toPlaywrightImportError(error) {
|
|
269
|
+
return isMissingPlaywrightImport(error) ? new Error(MISSING_PLAYWRIGHT_MESSAGE) : error;
|
|
270
|
+
}
|
|
271
|
+
function isMissingPlaywrightImport(error) {
|
|
272
|
+
if (!isRecord(error) || typeof error.message !== 'string') {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
return ((error.code === 'ERR_MODULE_NOT_FOUND' || error.code === 'MODULE_NOT_FOUND') &&
|
|
276
|
+
(error.message.includes("Cannot find package 'playwright'") ||
|
|
277
|
+
error.message.includes("Cannot find module 'playwright'")));
|
|
278
|
+
}
|
|
279
|
+
function isRecord(value) {
|
|
280
|
+
return typeof value === 'object' && value !== null;
|
|
281
|
+
}
|
|
249
282
|
async function resolveCwd(cwd) {
|
|
250
283
|
try {
|
|
251
284
|
return await fsp.realpath(cwd);
|
|
@@ -257,12 +290,12 @@ async function resolveCwd(cwd) {
|
|
|
257
290
|
async function discoverTests(config, cwd) {
|
|
258
291
|
let files = await findFiles(config.glob.test, config.glob.exclude, cwd);
|
|
259
292
|
if (files.length === 0) {
|
|
260
|
-
console.log(`No test files found matching pattern: ${config.glob.test}`);
|
|
293
|
+
console.log(`No test files found matching pattern: ${config.glob.test.join(', ')}`);
|
|
261
294
|
return null;
|
|
262
295
|
}
|
|
263
296
|
let browserSet = new Set(await findFiles(config.glob.browser, config.glob.exclude, cwd));
|
|
264
297
|
let e2eSet = new Set(await findFiles(config.glob.e2e, config.glob.exclude, cwd));
|
|
265
|
-
let types = new Set(config.type
|
|
298
|
+
let types = new Set(config.type);
|
|
266
299
|
let browserFiles = types.has('browser') ? files.filter((f) => browserSet.has(f)) : [];
|
|
267
300
|
let e2eFiles = types.has('e2e') ? files.filter((file) => e2eSet.has(file)) : [];
|
|
268
301
|
let serverFiles = types.has('server')
|
|
@@ -270,7 +303,7 @@ async function discoverTests(config, cwd) {
|
|
|
270
303
|
: [];
|
|
271
304
|
let totalFiles = browserFiles.length + serverFiles.length + e2eFiles.length;
|
|
272
305
|
if (totalFiles === 0) {
|
|
273
|
-
console.log(`No test files remain after filtering for type ${config.type}`);
|
|
306
|
+
console.log(`No test files remain after filtering for type ${config.type.join(', ')}`);
|
|
274
307
|
return null;
|
|
275
308
|
}
|
|
276
309
|
console.log(`Found ${totalFiles} test file(s) (${serverFiles.length} server, ${browserFiles.length} browser, ${e2eFiles.length} e2e)`);
|
|
@@ -281,25 +314,37 @@ async function discoverTests(config, cwd) {
|
|
|
281
314
|
e2eFiles,
|
|
282
315
|
};
|
|
283
316
|
}
|
|
284
|
-
async function findFiles(
|
|
285
|
-
let files =
|
|
317
|
+
async function findFiles(patterns, excludePatterns, cwd) {
|
|
318
|
+
let files = new Set();
|
|
286
319
|
if (IS_BUN) {
|
|
287
320
|
// Bun's `fs.promises.glob` follows symlinks and doesn't prune traversal
|
|
288
321
|
// via `exclude`, so it enters pnpm symlink cycles in `node_modules`.
|
|
289
322
|
// Use Bun's native Glob, which defaults to `followSymlinks: false`.
|
|
290
323
|
// @ts-expect-error — bun module is only resolvable under the Bun runtime
|
|
291
324
|
let { Glob } = await import('bun');
|
|
292
|
-
let
|
|
293
|
-
let
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
325
|
+
let excludeGlobs = excludePatterns.map((p) => new Glob(p));
|
|
326
|
+
for (let pattern of patterns) {
|
|
327
|
+
let glob = new Glob(pattern);
|
|
328
|
+
for await (let file of glob.scan({ cwd, absolute: true })) {
|
|
329
|
+
let rel = toPosix(path.relative(cwd, file));
|
|
330
|
+
if (!excludeGlobs.some((eg) => eg.match(rel))) {
|
|
331
|
+
files.add(toPosix(file));
|
|
332
|
+
}
|
|
297
333
|
}
|
|
298
334
|
}
|
|
299
|
-
return files;
|
|
335
|
+
return [...files];
|
|
300
336
|
}
|
|
301
|
-
for
|
|
302
|
-
|
|
337
|
+
for (let pattern of patterns) {
|
|
338
|
+
for await (let file of fsp.glob(pattern, { cwd, exclude: excludePatterns })) {
|
|
339
|
+
files.add(toPosix(path.resolve(cwd, file)));
|
|
340
|
+
}
|
|
303
341
|
}
|
|
304
|
-
return files;
|
|
342
|
+
return [...files];
|
|
343
|
+
}
|
|
344
|
+
// Normalize discovered paths so set membership across the test/browser/e2e
|
|
345
|
+
// `findFiles` calls is byte-stable on every platform. Node accepts forward
|
|
346
|
+
// slashes for filesystem operations on Windows, so downstream `fs.readFile`
|
|
347
|
+
// etc. work without further conversion.
|
|
348
|
+
function toPosix(p) {
|
|
349
|
+
return path.sep === '/' ? p : p.replace(/\\/g, '/');
|
|
305
350
|
}
|
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,7 @@
|
|
|
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
|
+
export type RemixTestPool = 'forks' | 'threads';
|
|
4
5
|
export interface RemixTestConfig {
|
|
5
6
|
/**
|
|
6
7
|
* Options for controlling the playwright browser
|
|
@@ -12,17 +13,18 @@ export interface RemixTestConfig {
|
|
|
12
13
|
open?: boolean;
|
|
13
14
|
};
|
|
14
15
|
/**
|
|
15
|
-
* Glob patterns to identify test files
|
|
16
|
-
*
|
|
17
|
-
* - `glob.
|
|
18
|
-
* - `glob.
|
|
19
|
-
* - `glob.
|
|
16
|
+
* Glob patterns to identify test files. Each field accepts a single pattern
|
|
17
|
+
* or an array of patterns; arrays are unioned during discovery.
|
|
18
|
+
* - `glob.test`: Glob pattern(s) for all test files (--glob.test)
|
|
19
|
+
* - `glob.browser`: Glob pattern(s) for the subset of browser test files (--glob.browser)
|
|
20
|
+
* - `glob.e2e`: Glob pattern(s) for the subset of e2e test files (--glob.e2e)
|
|
21
|
+
* - `glob.exclude`: Glob pattern(s) for paths to exclude from discovery (--glob.exclude)
|
|
20
22
|
*/
|
|
21
23
|
glob?: {
|
|
22
|
-
test?: string;
|
|
23
|
-
browser?: string;
|
|
24
|
-
e2e?: string;
|
|
25
|
-
exclude?: string;
|
|
24
|
+
test?: string | string[];
|
|
25
|
+
browser?: string | string[];
|
|
26
|
+
e2e?: string | string[];
|
|
27
|
+
exclude?: string | string[];
|
|
26
28
|
};
|
|
27
29
|
/** Max number of concurrent test workers (--concurrency) */
|
|
28
30
|
concurrency?: number | string;
|
|
@@ -32,8 +34,8 @@ export interface RemixTestConfig {
|
|
|
32
34
|
*/
|
|
33
35
|
coverage?: boolean | {
|
|
34
36
|
dir?: string;
|
|
35
|
-
include?: string[];
|
|
36
|
-
exclude?: string[];
|
|
37
|
+
include?: string | string[];
|
|
38
|
+
exclude?: string | string[];
|
|
37
39
|
statements?: number | string;
|
|
38
40
|
lines?: number | string;
|
|
39
41
|
branches?: number | string;
|
|
@@ -49,12 +51,23 @@ export interface RemixTestConfig {
|
|
|
49
51
|
* PlaywrightTestConfig object. CLI `--playwrightConfig` only accepts a file path.
|
|
50
52
|
*/
|
|
51
53
|
playwrightConfig?: string | PlaywrightTestConfig;
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Pool used to run server and E2E test files. Forked child processes are the default,
|
|
56
|
+
* but worker threads are available for projects that prefer the previous behavior.
|
|
57
|
+
*/
|
|
58
|
+
pool?: RemixTestPool;
|
|
59
|
+
/**
|
|
60
|
+
* Filter tests to specific playwright project(s) (--project). Accepts a single
|
|
61
|
+
* project name or an array of names; `--project` may be repeated on the CLI.
|
|
62
|
+
*/
|
|
63
|
+
project?: string | string[];
|
|
54
64
|
/** Test reporter (--reporter) */
|
|
55
65
|
reporter?: string;
|
|
56
|
-
/**
|
|
57
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Test type(s) to run (--type). Accepts a single type or an array of types;
|
|
68
|
+
* `--type` may be repeated on the CLI. Valid values: "server", "browser", "e2e".
|
|
69
|
+
*/
|
|
70
|
+
type?: string | string[];
|
|
58
71
|
/** Watch mode — re-run tests on file changes (--watch) */
|
|
59
72
|
watch?: boolean;
|
|
60
73
|
}
|
|
@@ -74,16 +87,17 @@ export interface ResolvedRemixTestConfig {
|
|
|
74
87
|
functions?: number;
|
|
75
88
|
} | undefined;
|
|
76
89
|
glob: {
|
|
77
|
-
test: string;
|
|
78
|
-
browser: string;
|
|
79
|
-
e2e: string;
|
|
80
|
-
exclude: string;
|
|
90
|
+
test: string[];
|
|
91
|
+
browser: string[];
|
|
92
|
+
e2e: string[];
|
|
93
|
+
exclude: string[];
|
|
81
94
|
};
|
|
82
95
|
playwrightConfig: string | PlaywrightTestConfig | undefined;
|
|
83
|
-
project: string | undefined;
|
|
96
|
+
project: string[] | undefined;
|
|
84
97
|
reporter: string;
|
|
98
|
+
pool: RemixTestPool;
|
|
85
99
|
setup: string | undefined;
|
|
86
|
-
type: string;
|
|
100
|
+
type: string[];
|
|
87
101
|
watch: boolean;
|
|
88
102
|
}
|
|
89
103
|
export declare function loadConfig(args?: string[], cwd?: string): Promise<ResolvedRemixTestConfig>;
|
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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"}
|
package/dist/lib/config.js
CHANGED
|
@@ -40,19 +40,23 @@ const cliOptions = {
|
|
|
40
40
|
},
|
|
41
41
|
'glob.browser': {
|
|
42
42
|
type: 'string',
|
|
43
|
-
|
|
43
|
+
multiple: true,
|
|
44
|
+
description: 'Glob pattern(s) for browser test files',
|
|
44
45
|
},
|
|
45
46
|
'glob.e2e': {
|
|
46
47
|
type: 'string',
|
|
47
|
-
|
|
48
|
+
multiple: true,
|
|
49
|
+
description: 'Glob pattern(s) for E2E test files',
|
|
48
50
|
},
|
|
49
51
|
'glob.exclude': {
|
|
50
52
|
type: 'string',
|
|
51
|
-
|
|
53
|
+
multiple: true,
|
|
54
|
+
description: 'Glob pattern(s) for paths to exclude from discovery',
|
|
52
55
|
},
|
|
53
56
|
'glob.test': {
|
|
54
57
|
type: 'string',
|
|
55
|
-
|
|
58
|
+
multiple: true,
|
|
59
|
+
description: 'Glob pattern(s) for all test files',
|
|
56
60
|
},
|
|
57
61
|
concurrency: {
|
|
58
62
|
type: 'string',
|
|
@@ -109,7 +113,12 @@ const cliOptions = {
|
|
|
109
113
|
project: {
|
|
110
114
|
type: 'string',
|
|
111
115
|
short: 'p',
|
|
112
|
-
|
|
116
|
+
multiple: true,
|
|
117
|
+
description: 'Filter to specific Playwright project(s)',
|
|
118
|
+
},
|
|
119
|
+
pool: {
|
|
120
|
+
type: 'string',
|
|
121
|
+
description: 'Pool used to run server and E2E test files: forks, threads (default: forks)',
|
|
113
122
|
},
|
|
114
123
|
reporter: {
|
|
115
124
|
type: 'string',
|
|
@@ -119,7 +128,8 @@ const cliOptions = {
|
|
|
119
128
|
type: {
|
|
120
129
|
type: 'string',
|
|
121
130
|
short: 't',
|
|
122
|
-
|
|
131
|
+
multiple: true,
|
|
132
|
+
description: 'Test types to run (default: server, browser, e2e)',
|
|
123
133
|
},
|
|
124
134
|
watch: {
|
|
125
135
|
type: 'boolean',
|
|
@@ -143,16 +153,17 @@ const defaultValues = {
|
|
|
143
153
|
functions: undefined,
|
|
144
154
|
},
|
|
145
155
|
glob: {
|
|
146
|
-
test: '**/*.test{,.e2e,.browser}.{ts,tsx}',
|
|
147
|
-
browser: '**/*.test.browser.{ts,tsx}',
|
|
148
|
-
e2e: '**/*.test.e2e.{ts,tsx}',
|
|
149
|
-
exclude: 'node_modules/**',
|
|
156
|
+
test: ['**/*.test{,.e2e,.browser}.{ts,tsx}'],
|
|
157
|
+
browser: ['**/*.test.browser.{ts,tsx}'],
|
|
158
|
+
e2e: ['**/*.test.e2e.{ts,tsx}'],
|
|
159
|
+
exclude: ['node_modules/**'],
|
|
150
160
|
},
|
|
151
|
-
|
|
152
|
-
type: 'server,browser,e2e',
|
|
153
|
-
setup: undefined,
|
|
161
|
+
pool: 'forks',
|
|
154
162
|
playwrightConfig: undefined,
|
|
155
163
|
project: undefined,
|
|
164
|
+
reporter: process.env.CI === 'true' ? 'files' : 'spec',
|
|
165
|
+
setup: undefined,
|
|
166
|
+
type: ['server', 'browser', 'e2e'],
|
|
156
167
|
watch: false,
|
|
157
168
|
};
|
|
158
169
|
export async function loadConfig(args = process.argv.slice(2), cwd = process.cwd()) {
|
|
@@ -163,10 +174,10 @@ export async function loadConfig(args = process.argv.slice(2), cwd = process.cwd
|
|
|
163
174
|
}
|
|
164
175
|
export function getRemixTestHelpText(_target = process.stdout) {
|
|
165
176
|
let lines = [
|
|
166
|
-
'Usage: remix-test [glob] [options]',
|
|
177
|
+
'Usage: remix-test [glob...] [options]',
|
|
167
178
|
'',
|
|
168
179
|
'Arguments:',
|
|
169
|
-
` glob Glob pattern for test files (default: "${defaultValues.glob.test}")`,
|
|
180
|
+
` glob Glob pattern(s) for test files (default: "${defaultValues.glob.test.join(', ')}")`,
|
|
170
181
|
'',
|
|
171
182
|
'Options:',
|
|
172
183
|
];
|
|
@@ -181,17 +192,25 @@ export function getRemixTestHelpText(_target = process.stdout) {
|
|
|
181
192
|
function parseCliArgs(args) {
|
|
182
193
|
return util.parseArgs({ args, options: cliOptions, allowPositionals: true });
|
|
183
194
|
}
|
|
195
|
+
function toArray(value) {
|
|
196
|
+
return Array.isArray(value) ? [...value] : [value];
|
|
197
|
+
}
|
|
198
|
+
function toCommaSeparatedArray(value) {
|
|
199
|
+
return toArray(value).flatMap((item) => item
|
|
200
|
+
.split(',')
|
|
201
|
+
.map((part) => part.trim())
|
|
202
|
+
.filter(Boolean));
|
|
203
|
+
}
|
|
184
204
|
function resolveConfig(fileConfig, { values: cliValues, positionals }) {
|
|
185
205
|
let fileCoverage = typeof fileConfig.coverage === 'boolean' ? {} : fileConfig.coverage || {};
|
|
186
206
|
return {
|
|
187
207
|
glob: {
|
|
188
|
-
test: positionals
|
|
189
|
-
|
|
190
|
-
fileConfig.glob?.test ??
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
exclude: cliValues['glob.exclude'] ?? fileConfig.glob?.exclude ?? defaultValues.glob.exclude,
|
|
208
|
+
test: toArray(positionals.length > 0
|
|
209
|
+
? positionals
|
|
210
|
+
: (cliValues['glob.test'] ?? fileConfig.glob?.test ?? defaultValues.glob.test)),
|
|
211
|
+
browser: toArray(cliValues['glob.browser'] ?? fileConfig.glob?.browser ?? defaultValues.glob.browser),
|
|
212
|
+
e2e: toArray(cliValues['glob.e2e'] ?? fileConfig.glob?.e2e ?? defaultValues.glob.e2e),
|
|
213
|
+
exclude: toArray(cliValues['glob.exclude'] ?? fileConfig.glob?.exclude ?? defaultValues.glob.exclude),
|
|
195
214
|
},
|
|
196
215
|
browser: {
|
|
197
216
|
echo: cliValues['browser.echo'] ?? fileConfig.browser?.echo ?? defaultValues.browser.echo,
|
|
@@ -201,12 +220,18 @@ function resolveConfig(fileConfig, { values: cliValues, positionals }) {
|
|
|
201
220
|
coverage: cliValues.coverage === true || !!fileConfig.coverage
|
|
202
221
|
? {
|
|
203
222
|
dir: cliValues['coverage.dir'] ?? fileCoverage.dir ?? defaultValues.coverage.dir,
|
|
204
|
-
include:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
223
|
+
include: (() => {
|
|
224
|
+
let raw = cliValues['coverage.include'] ??
|
|
225
|
+
fileCoverage.include ??
|
|
226
|
+
defaultValues.coverage.include;
|
|
227
|
+
return raw === undefined ? undefined : toArray(raw);
|
|
228
|
+
})(),
|
|
229
|
+
exclude: (() => {
|
|
230
|
+
let raw = cliValues['coverage.exclude'] ??
|
|
231
|
+
fileCoverage.exclude ??
|
|
232
|
+
defaultValues.coverage.exclude;
|
|
233
|
+
return raw === undefined ? undefined : toArray(raw);
|
|
234
|
+
})(),
|
|
210
235
|
statements: cliValues['coverage.statements'] !== undefined
|
|
211
236
|
? Number(cliValues['coverage.statements'])
|
|
212
237
|
: fileCoverage.statements !== undefined
|
|
@@ -231,12 +256,22 @@ function resolveConfig(fileConfig, { values: cliValues, positionals }) {
|
|
|
231
256
|
: undefined,
|
|
232
257
|
setup: cliValues.setup ?? fileConfig.setup ?? defaultValues.setup,
|
|
233
258
|
playwrightConfig: cliValues.playwrightConfig ?? fileConfig.playwrightConfig ?? defaultValues.playwrightConfig,
|
|
234
|
-
|
|
259
|
+
pool: resolvePool(cliValues.pool ?? fileConfig.pool ?? defaultValues.pool),
|
|
260
|
+
project: (() => {
|
|
261
|
+
let raw = cliValues.project ?? fileConfig.project ?? defaultValues.project;
|
|
262
|
+
return raw === undefined ? undefined : toCommaSeparatedArray(raw);
|
|
263
|
+
})(),
|
|
235
264
|
reporter: cliValues.reporter ?? fileConfig.reporter ?? defaultValues.reporter,
|
|
236
|
-
type: cliValues.type ?? fileConfig.type ?? defaultValues.type,
|
|
265
|
+
type: toCommaSeparatedArray(cliValues.type ?? fileConfig.type ?? defaultValues.type),
|
|
237
266
|
watch: cliValues.watch ?? fileConfig.watch ?? defaultValues.watch,
|
|
238
267
|
};
|
|
239
268
|
}
|
|
269
|
+
function resolvePool(value) {
|
|
270
|
+
if (value === 'forks' || value === 'threads') {
|
|
271
|
+
return value;
|
|
272
|
+
}
|
|
273
|
+
throw new Error(`Unsupported test pool "${value}". Supported pools are: forks, threads`);
|
|
274
|
+
}
|
|
240
275
|
async function loadConfigFile(configPath, cwd) {
|
|
241
276
|
let candidates = configPath
|
|
242
277
|
? [path.resolve(cwd, configPath)]
|
|
@@ -244,12 +279,17 @@ async function loadConfigFile(configPath, cwd) {
|
|
|
244
279
|
for (let candidate of candidates) {
|
|
245
280
|
try {
|
|
246
281
|
await fsp.access(candidate);
|
|
247
|
-
let mod = await importModule(candidate, import.meta);
|
|
248
|
-
return mod.default ?? mod;
|
|
249
282
|
}
|
|
250
283
|
catch {
|
|
251
|
-
// not found
|
|
284
|
+
// not found — try the next candidate
|
|
285
|
+
continue;
|
|
252
286
|
}
|
|
287
|
+
// The file exists; let import errors propagate rather than silently
|
|
288
|
+
// falling through to defaults — that masking is what hid "Windows
|
|
289
|
+
// absolute paths aren't valid ESM specifiers" by classifying every
|
|
290
|
+
// browser test as a server test.
|
|
291
|
+
let mod = await importModule(candidate, import.meta);
|
|
292
|
+
return mod.default ?? mod;
|
|
253
293
|
}
|
|
254
294
|
return {};
|
|
255
295
|
}
|