@remix-run/test 0.1.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 +161 -50
- package/dist/app/client/entry.d.ts +2 -0
- package/dist/app/client/entry.d.ts.map +1 -0
- package/dist/app/client/entry.js +328 -0
- package/dist/app/client/iframe.d.ts +2 -0
- package/dist/app/client/iframe.d.ts.map +1 -0
- package/dist/app/client/iframe.js +22 -0
- package/dist/app/server.d.ts +6 -0
- package/dist/app/server.d.ts.map +1 -0
- package/dist/app/server.js +303 -0
- package/dist/cli-entry.d.ts +3 -0
- package/dist/cli-entry.d.ts.map +1 -0
- package/dist/cli-entry.js +14 -0
- package/dist/cli.d.ts +7 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +319 -140
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/colors.d.ts +2 -0
- package/dist/lib/colors.d.ts.map +1 -0
- package/dist/lib/colors.js +2 -0
- package/dist/lib/config.d.ts +59 -14
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +181 -38
- package/dist/lib/context.d.ts +37 -13
- package/dist/lib/context.d.ts.map +1 -1
- package/dist/lib/context.js +19 -3
- package/dist/lib/coverage-loader.d.ts +16 -0
- package/dist/lib/coverage-loader.d.ts.map +1 -0
- package/dist/lib/coverage-loader.js +20 -0
- package/dist/lib/coverage.d.ts +28 -0
- package/dist/lib/coverage.d.ts.map +1 -0
- package/dist/lib/coverage.js +212 -0
- package/dist/lib/executor.d.ts +3 -26
- package/dist/lib/executor.d.ts.map +1 -1
- package/dist/lib/executor.js +11 -6
- package/dist/lib/fake-timers.d.ts +13 -0
- package/dist/lib/fake-timers.d.ts.map +1 -0
- package/dist/lib/fake-timers.js +64 -0
- package/dist/lib/import-module.d.ts +2 -0
- package/dist/lib/import-module.d.ts.map +1 -0
- package/dist/lib/import-module.js +38 -0
- package/dist/lib/normalize.d.ts +2 -0
- package/dist/lib/normalize.d.ts.map +1 -0
- package/dist/lib/{utils.js → normalize.js} +0 -9
- package/dist/lib/playwright.d.ts +1 -1
- package/dist/lib/playwright.d.ts.map +1 -1
- package/dist/lib/playwright.js +5 -8
- package/dist/lib/reporters/dot.d.ts +1 -2
- package/dist/lib/reporters/dot.d.ts.map +1 -1
- package/dist/lib/reporters/dot.js +12 -1
- package/dist/lib/reporters/files.d.ts +1 -2
- package/dist/lib/reporters/files.d.ts.map +1 -1
- package/dist/lib/reporters/files.js +12 -1
- package/dist/lib/reporters/index.d.ts +4 -5
- package/dist/lib/reporters/index.d.ts.map +1 -1
- package/dist/lib/reporters/index.js +3 -3
- package/dist/lib/reporters/results.d.ts +30 -0
- package/dist/lib/reporters/results.d.ts.map +1 -0
- package/dist/lib/reporters/results.js +1 -0
- package/dist/lib/reporters/spec.d.ts +1 -2
- package/dist/lib/reporters/spec.d.ts.map +1 -1
- package/dist/lib/reporters/spec.js +12 -1
- package/dist/lib/reporters/tap.d.ts +1 -2
- package/dist/lib/reporters/tap.d.ts.map +1 -1
- package/dist/lib/reporters/tap.js +11 -1
- package/dist/lib/runner-browser.d.ts +21 -0
- package/dist/lib/runner-browser.d.ts.map +1 -0
- package/dist/lib/runner-browser.js +123 -0
- package/dist/lib/runner.d.ts +24 -2
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/runner.js +216 -38
- package/dist/lib/runtime.d.ts +2 -0
- package/dist/lib/runtime.d.ts.map +1 -0
- package/dist/lib/runtime.js +2 -0
- package/dist/lib/ts-transform.d.ts +4 -0
- package/dist/lib/ts-transform.d.ts.map +1 -0
- package/dist/lib/ts-transform.js +29 -0
- 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 -46
- 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 +7 -28
- package/dist/test/coverage/fixture.d.ts +5 -0
- package/dist/test/coverage/fixture.d.ts.map +1 -0
- package/dist/test/coverage/fixture.js +32 -0
- package/dist/test/coverage/test-browser.d.ts +2 -0
- package/dist/test/coverage/test-browser.d.ts.map +1 -0
- package/dist/test/coverage/test-browser.js +24 -0
- package/dist/test/coverage/test-e2e.d.ts +2 -0
- package/dist/test/coverage/test-e2e.d.ts.map +1 -0
- package/dist/test/coverage/test-e2e.js +60 -0
- package/dist/test/coverage/test-unit.d.ts +2 -0
- package/dist/test/coverage/test-unit.d.ts.map +1 -0
- package/dist/test/coverage/test-unit.js +27 -0
- package/dist/test/framework.test.browser.d.ts +2 -0
- package/dist/test/framework.test.browser.d.ts.map +1 -0
- package/dist/test/framework.test.browser.js +107 -0
- package/dist/test/framework.test.e2e.d.ts.map +1 -0
- package/dist/test/framework.test.e2e.js +34 -0
- package/package.json +30 -9
- package/src/app/client/entry.ts +357 -0
- package/src/app/client/iframe.ts +18 -0
- package/src/app/server.ts +336 -0
- package/src/cli-entry.ts +15 -0
- package/src/cli.ts +382 -145
- package/src/index.ts +2 -1
- package/src/lib/colors.ts +3 -0
- package/src/lib/config.ts +266 -54
- package/src/lib/context.ts +59 -17
- package/src/lib/coverage-loader.ts +31 -0
- package/src/lib/coverage.ts +320 -0
- package/src/lib/executor.ts +18 -35
- package/src/lib/fake-timers.ts +89 -0
- package/src/lib/import-module.ts +39 -0
- package/src/lib/{utils.ts → normalize.ts} +0 -18
- package/src/lib/playwright.ts +5 -7
- package/src/lib/reporters/dot.ts +12 -2
- package/src/lib/reporters/files.ts +12 -2
- package/src/lib/reporters/index.ts +4 -5
- package/src/lib/reporters/results.ts +29 -0
- package/src/lib/reporters/spec.ts +12 -2
- package/src/lib/reporters/tap.ts +11 -2
- package/src/lib/runner-browser.ts +171 -0
- package/src/lib/runner.ts +308 -53
- package/src/lib/runtime.ts +2 -0
- package/src/lib/ts-transform.ts +36 -0
- package/src/lib/worker-e2e-file.ts +98 -0
- package/src/lib/worker-e2e.ts +14 -49
- 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 +8 -28
- package/src/test/coverage/fixture.ts +34 -0
- package/src/test/coverage/test-browser.ts +29 -0
- package/src/test/coverage/test-e2e.ts +70 -0
- package/src/test/coverage/test-unit.ts +32 -0
- package/tsconfig.json +3 -1
- package/dist/lib/e2e-server.d.ts +0 -11
- package/dist/lib/e2e-server.d.ts.map +0 -1
- package/dist/lib/e2e-server.js +0 -15
- package/dist/lib/framework.test.d.ts +0 -2
- package/dist/lib/framework.test.d.ts.map +0 -1
- package/dist/lib/framework.test.e2e.d.ts.map +0 -1
- package/dist/lib/framework.test.e2e.js +0 -29
- package/dist/lib/framework.test.js +0 -283
- package/dist/lib/utils.d.ts +0 -16
- package/dist/lib/utils.d.ts.map +0 -1
- package/src/lib/e2e-server.ts +0 -28
- /package/dist/{lib → test}/framework.test.e2e.d.ts +0 -0
package/src/cli.ts
CHANGED
|
@@ -1,210 +1,447 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import * as fsp from 'node:fs/promises'
|
|
2
|
+
import type * as http from 'node:http'
|
|
3
3
|
import * as path from 'node:path'
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
4
|
+
import type * as browserTestRunner from './lib/runner-browser.ts'
|
|
5
|
+
import {
|
|
6
|
+
getRemixTestHelpText,
|
|
7
|
+
IS_RUNNING_FROM_SRC,
|
|
8
|
+
loadConfig,
|
|
9
|
+
type ResolvedRemixTestConfig,
|
|
10
|
+
} from './lib/config.ts'
|
|
11
|
+
import type * as playwrightSupport from './lib/playwright.ts'
|
|
12
|
+
import { generateCombinedCoverageReport } from './lib/coverage.ts'
|
|
6
13
|
import { createReporter } from './lib/reporters/index.ts'
|
|
14
|
+
import { runServerTests } from './lib/runner.ts'
|
|
7
15
|
import { createWatcher } from './lib/watcher.ts'
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
16
|
+
import { importModule } from './lib/import-module.ts'
|
|
17
|
+
import type { Counts } from './lib/reporters/results.ts'
|
|
18
|
+
import { IS_BUN } from './lib/runtime.ts'
|
|
19
|
+
import { isMainThread } from 'node:worker_threads'
|
|
20
|
+
|
|
21
|
+
export { getRemixTestHelpText }
|
|
22
|
+
|
|
23
|
+
const MISSING_PLAYWRIGHT_MESSAGE =
|
|
24
|
+
'Playwright is required to run browser and E2E tests. Install it with `npm i -D playwright`.'
|
|
25
|
+
|
|
26
|
+
export interface RunRemixTestOptions {
|
|
27
|
+
argv?: string[]
|
|
28
|
+
cwd?: string
|
|
29
|
+
}
|
|
11
30
|
|
|
12
|
-
|
|
31
|
+
type RunBrowserTests = typeof browserTestRunner.runBrowserTests
|
|
13
32
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
33
|
+
interface DiscoveredTests {
|
|
34
|
+
files: string[]
|
|
35
|
+
serverFiles: string[]
|
|
36
|
+
browserFiles: string[]
|
|
37
|
+
e2eFiles: string[]
|
|
38
|
+
}
|
|
20
39
|
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
export async function runRemixTest(options: RunRemixTestOptions = {}): Promise<number> {
|
|
41
|
+
let argv = options.argv ?? process.argv.slice(2)
|
|
42
|
+
let cwd = await resolveCwd(options.cwd ?? process.cwd())
|
|
43
|
+
let previousCwd = process.cwd()
|
|
23
44
|
|
|
24
|
-
|
|
25
|
-
|
|
45
|
+
if (!isMainThread) {
|
|
46
|
+
return await runRemixTestInCwd(argv, cwd)
|
|
47
|
+
}
|
|
26
48
|
|
|
27
|
-
|
|
28
|
-
|
|
49
|
+
try {
|
|
50
|
+
process.chdir(cwd)
|
|
51
|
+
return await runRemixTestInCwd(argv, cwd)
|
|
52
|
+
} finally {
|
|
53
|
+
process.chdir(previousCwd)
|
|
29
54
|
}
|
|
30
|
-
} catch {
|
|
31
|
-
cleanupAndExit(1)
|
|
32
55
|
}
|
|
33
56
|
|
|
34
|
-
async function
|
|
35
|
-
if (
|
|
57
|
+
async function runRemixTestInCwd(argv: string[], cwd: string): Promise<number> {
|
|
58
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
59
|
+
console.log(getRemixTestHelpText())
|
|
60
|
+
return 0
|
|
61
|
+
}
|
|
36
62
|
|
|
37
|
-
|
|
63
|
+
let config = await loadConfig(argv, cwd)
|
|
64
|
+
let hasExited = false
|
|
65
|
+
let latestExitCode = 0
|
|
66
|
+
let watcher: ReturnType<typeof createWatcher> | undefined
|
|
67
|
+
let running = false
|
|
68
|
+
let queued = false
|
|
69
|
+
let rerunTimer: NodeJS.Timeout | undefined
|
|
70
|
+
let browserServer: http.Server | undefined
|
|
71
|
+
let browserServerFilesKey: string | undefined
|
|
72
|
+
let browserPort: number | undefined
|
|
73
|
+
let resolveRun: ((exitCode: number) => void) | undefined
|
|
74
|
+
|
|
75
|
+
let runPromise = new Promise<number>((resolve) => {
|
|
76
|
+
resolveRun = resolve
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
let cleanupAndExit = (code: number) => {
|
|
80
|
+
if (hasExited) return
|
|
81
|
+
hasExited = true
|
|
82
|
+
watcher?.close()
|
|
83
|
+
browserServer?.close()
|
|
84
|
+
clearTimeout(rerunTimer)
|
|
85
|
+
process.off('SIGINT', handleInterrupt)
|
|
86
|
+
process.off('SIGTERM', handleInterrupt)
|
|
87
|
+
resolveRun?.(code)
|
|
88
|
+
}
|
|
38
89
|
|
|
39
|
-
let
|
|
90
|
+
let handleInterrupt = () => cleanupAndExit(latestExitCode)
|
|
91
|
+
|
|
92
|
+
let closeBrowserServer = async () => {
|
|
93
|
+
if (!browserServer) return
|
|
94
|
+
let server = browserServer
|
|
95
|
+
await new Promise<void>((resolve, reject) =>
|
|
96
|
+
server.close((error) => (error ? reject(error) : resolve())),
|
|
97
|
+
)
|
|
98
|
+
browserServer = undefined
|
|
99
|
+
browserServerFilesKey = undefined
|
|
100
|
+
browserPort = undefined
|
|
101
|
+
}
|
|
40
102
|
|
|
41
|
-
|
|
42
|
-
if (config.
|
|
43
|
-
let mod = await tsImport(path.resolve(process.cwd(), config.setup), {
|
|
44
|
-
parentURL: import.meta.url,
|
|
45
|
-
})
|
|
46
|
-
let globalSetup: (() => Promise<void> | void) | undefined = mod.globalSetup
|
|
47
|
-
globalTeardown = mod.globalTeardown
|
|
48
|
-
await globalSetup?.()
|
|
49
|
-
}
|
|
103
|
+
let queueRerun = (reason: string) => {
|
|
104
|
+
if (!config.watch || hasExited) return
|
|
50
105
|
|
|
51
|
-
|
|
106
|
+
clearTimeout(rerunTimer)
|
|
52
107
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
108
|
+
rerunTimer = setTimeout(() => {
|
|
109
|
+
rerunTimer = undefined
|
|
110
|
+
if (running) {
|
|
111
|
+
queued = true
|
|
112
|
+
} else {
|
|
113
|
+
console.log(`\n↻ Change detected (${reason}), re-running tests...\n`)
|
|
114
|
+
void executeRun()
|
|
115
|
+
}
|
|
116
|
+
}, 100)
|
|
117
|
+
}
|
|
57
118
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
? await loadPlaywrightConfig(config.playwrightConfig)
|
|
61
|
-
: config.playwrightConfig
|
|
119
|
+
let executeRun = async () => {
|
|
120
|
+
if (hasExited) return
|
|
62
121
|
|
|
63
|
-
|
|
64
|
-
let startTime = performance.now()
|
|
122
|
+
running = true
|
|
65
123
|
|
|
66
|
-
let
|
|
67
|
-
passed: 0,
|
|
68
|
-
failed: 0,
|
|
69
|
-
skipped: 0,
|
|
70
|
-
todo: 0,
|
|
71
|
-
}
|
|
124
|
+
let globalTeardown: (() => Promise<void> | void) | undefined
|
|
72
125
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
counts.todo += serverResult.todo
|
|
81
|
-
}
|
|
126
|
+
try {
|
|
127
|
+
if (config.setup) {
|
|
128
|
+
let mod = await importModule(path.resolve(cwd, config.setup), import.meta)
|
|
129
|
+
let globalSetup: (() => Promise<void> | void) | undefined = mod.globalSetup
|
|
130
|
+
globalTeardown = mod.globalTeardown
|
|
131
|
+
await globalSetup?.()
|
|
132
|
+
}
|
|
82
133
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
134
|
+
let discoveredTests = await discoverTests(config, cwd)
|
|
135
|
+
if (discoveredTests == null) {
|
|
136
|
+
latestExitCode = 1
|
|
137
|
+
cleanupAndExit(latestExitCode)
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let { files, serverFiles, browserFiles, e2eFiles } = discoveredTests
|
|
142
|
+
|
|
143
|
+
if (config.watch) {
|
|
144
|
+
watcher ??= createWatcher((file) => queueRerun(file))
|
|
145
|
+
watcher.update(files)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let browserFilesKey = browserFiles.join('\0')
|
|
149
|
+
if (browserServer && browserFiles.length === 0) {
|
|
150
|
+
await closeBrowserServer()
|
|
151
|
+
} else if (
|
|
152
|
+
browserFiles.length > 0 &&
|
|
153
|
+
(!browserServer || browserServerFilesKey !== browserFilesKey)
|
|
154
|
+
) {
|
|
155
|
+
await closeBrowserServer()
|
|
156
|
+
let { startServer } = IS_RUNNING_FROM_SRC
|
|
157
|
+
? await importModule('./app/server.ts', import.meta)
|
|
158
|
+
: await import(`./app/server.js`)
|
|
159
|
+
let result = await startServer(browserFiles)
|
|
160
|
+
browserServer = result.server
|
|
161
|
+
browserServerFilesKey = browserFilesKey
|
|
162
|
+
browserPort = result.port
|
|
92
163
|
}
|
|
93
164
|
|
|
94
|
-
|
|
95
|
-
|
|
165
|
+
let reporter = createReporter(config.reporter)
|
|
166
|
+
let startTime = performance.now()
|
|
96
167
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
168
|
+
let counts: Counts = {
|
|
169
|
+
passed: 0,
|
|
170
|
+
failed: 0,
|
|
171
|
+
skipped: 0,
|
|
172
|
+
todo: 0,
|
|
173
|
+
}
|
|
174
|
+
let allCoverageMaps: Array<ReturnType<typeof Object.values>[number] | null | undefined> = []
|
|
175
|
+
|
|
176
|
+
if (serverFiles.length > 0) {
|
|
177
|
+
reporter.onSectionStart('\nRunning server tests:')
|
|
178
|
+
let serverResult = await runServerTests(
|
|
179
|
+
serverFiles,
|
|
180
|
+
reporter,
|
|
181
|
+
config.concurrency,
|
|
182
|
+
'server',
|
|
183
|
+
{
|
|
184
|
+
coverage: config.coverage,
|
|
185
|
+
cwd,
|
|
186
|
+
pool: config.pool,
|
|
187
|
+
},
|
|
188
|
+
)
|
|
189
|
+
counts.failed += serverResult.failed
|
|
190
|
+
counts.passed += serverResult.passed
|
|
191
|
+
counts.skipped += serverResult.skipped
|
|
192
|
+
counts.todo += serverResult.todo
|
|
193
|
+
allCoverageMaps.push(serverResult.coverageMap)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Run browser/e2e tests for all browsers configured by the user
|
|
197
|
+
if (browserFiles.length > 0 || e2eFiles.length > 0) {
|
|
198
|
+
let { loadPlaywrightConfig, resolveProjects } = await importPlaywrightSupport()
|
|
199
|
+
let runBrowserTests =
|
|
200
|
+
browserFiles.length > 0 ? (await importBrowserTestRunner()).runBrowserTests : undefined
|
|
201
|
+
let playwrightConfig =
|
|
202
|
+
config.playwrightConfig == null || typeof config.playwrightConfig === 'string'
|
|
203
|
+
? await loadPlaywrightConfig(config.playwrightConfig, cwd)
|
|
204
|
+
: config.playwrightConfig
|
|
205
|
+
let projects = resolveProjects(playwrightConfig)
|
|
206
|
+
|
|
207
|
+
if (config.project) {
|
|
208
|
+
let projectNames = new Set(config.project)
|
|
209
|
+
projects = projects.filter((project) => project.name && projectNames.has(project.name))
|
|
210
|
+
if (projects.length === 0) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`No playwright projects found with name(s) "${config.project.join(', ')}"`,
|
|
102
213
|
)
|
|
103
|
-
} else {
|
|
104
|
-
project.playwrightUseOpts = { ...project.playwrightUseOpts, headless: false }
|
|
105
214
|
}
|
|
106
215
|
}
|
|
107
216
|
|
|
108
|
-
let
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
217
|
+
let lastBrowserResult: Awaited<ReturnType<RunBrowserTests>> | null = null
|
|
218
|
+
|
|
219
|
+
for (let project of projects) {
|
|
220
|
+
reporter.onSectionStart(`\nRunning tests for project \`${project.name}\`:`)
|
|
221
|
+
|
|
222
|
+
if (config.browser?.open) {
|
|
223
|
+
if (project.playwrightUseOpts?.headless === true) {
|
|
224
|
+
let label = project.name ? ` (project "${project.name}")` : ''
|
|
225
|
+
console.warn(
|
|
226
|
+
`Warning: browser.open is set but playwright headless is explicitly true${label} — ignoring browser.open`,
|
|
227
|
+
)
|
|
228
|
+
} else {
|
|
229
|
+
project.playwrightUseOpts = { ...project.playwrightUseOpts, headless: false }
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
let [browserResult, e2eResult] = await Promise.all([
|
|
234
|
+
runBrowserTests != null
|
|
235
|
+
? runBrowserTests({
|
|
236
|
+
baseUrl: `http://localhost:${browserPort}`,
|
|
237
|
+
console: config.browser?.echo,
|
|
238
|
+
coverage: !!config.coverage,
|
|
239
|
+
open: config.browser?.open,
|
|
240
|
+
playwrightUseOpts: project.playwrightUseOpts,
|
|
241
|
+
projectName: project.name,
|
|
242
|
+
reporter,
|
|
243
|
+
testFiles: browserFiles,
|
|
244
|
+
})
|
|
245
|
+
: null,
|
|
246
|
+
e2eFiles.length > 0
|
|
247
|
+
? runServerTests(e2eFiles, reporter, config.concurrency, 'e2e', {
|
|
248
|
+
open: config.browser?.open,
|
|
249
|
+
playwrightUseOpts: project.playwrightUseOpts,
|
|
250
|
+
projectName: project.name,
|
|
251
|
+
coverage: config.coverage,
|
|
252
|
+
cwd,
|
|
253
|
+
pool: config.pool,
|
|
254
|
+
})
|
|
255
|
+
: null,
|
|
256
|
+
])
|
|
257
|
+
|
|
258
|
+
counts.passed += (browserResult?.results.passed ?? 0) + (e2eResult?.passed ?? 0)
|
|
259
|
+
counts.failed += (browserResult?.results.failed ?? 0) + (e2eResult?.failed ?? 0)
|
|
260
|
+
counts.skipped += (browserResult?.results.skipped ?? 0) + (e2eResult?.skipped ?? 0)
|
|
261
|
+
counts.todo += (browserResult?.results.todo ?? 0) + (e2eResult?.todo ?? 0)
|
|
262
|
+
allCoverageMaps.push(browserResult?.coverageMap)
|
|
263
|
+
allCoverageMaps.push(e2eResult?.coverageMap)
|
|
264
|
+
|
|
265
|
+
if (browserResult) {
|
|
266
|
+
lastBrowserResult = browserResult
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (config.browser?.open && lastBrowserResult) {
|
|
271
|
+
console.log('\nBrowser is open. Press Ctrl+C to close.')
|
|
272
|
+
await Promise.race([
|
|
273
|
+
lastBrowserResult.disconnected,
|
|
274
|
+
new Promise<void>((resolve) => {
|
|
275
|
+
process.once('SIGINT', resolve)
|
|
276
|
+
process.once('SIGTERM', resolve)
|
|
277
|
+
}),
|
|
278
|
+
])
|
|
279
|
+
await lastBrowserResult.close()
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
reporter.onSummary(counts, performance.now() - startTime)
|
|
284
|
+
|
|
285
|
+
let thresholdsPassed = true
|
|
286
|
+
if (config.coverage) {
|
|
287
|
+
thresholdsPassed = await generateCombinedCoverageReport(
|
|
288
|
+
allCoverageMaps,
|
|
289
|
+
cwd,
|
|
290
|
+
config.coverage,
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
latestExitCode = counts.failed > 0 || !thresholdsPassed ? 1 : 0
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.error('Error running tests:', error)
|
|
296
|
+
latestExitCode = 1
|
|
297
|
+
} finally {
|
|
298
|
+
await globalTeardown?.()
|
|
299
|
+
running = false
|
|
300
|
+
if (queued) {
|
|
301
|
+
queued = false
|
|
302
|
+
queueRerun('queued change')
|
|
303
|
+
} else if (!config.watch) {
|
|
304
|
+
cleanupAndExit(latestExitCode)
|
|
121
305
|
}
|
|
122
306
|
}
|
|
307
|
+
}
|
|
123
308
|
|
|
124
|
-
|
|
309
|
+
process.on('SIGINT', handleInterrupt)
|
|
310
|
+
process.on('SIGTERM', handleInterrupt)
|
|
125
311
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
await globalTeardown?.()
|
|
132
|
-
running = false
|
|
133
|
-
if (queued) {
|
|
134
|
-
queued = false
|
|
135
|
-
queueRerun('queued change')
|
|
136
|
-
} else if (!config.watch) {
|
|
137
|
-
cleanupAndExit(latestExitCode)
|
|
312
|
+
try {
|
|
313
|
+
await executeRun()
|
|
314
|
+
|
|
315
|
+
if (config.watch && !hasExited) {
|
|
316
|
+
console.log('Watching for changes. Press Ctrl+C to stop.')
|
|
138
317
|
}
|
|
318
|
+
} catch {
|
|
319
|
+
cleanupAndExit(1)
|
|
139
320
|
}
|
|
321
|
+
|
|
322
|
+
return await runPromise
|
|
140
323
|
}
|
|
141
324
|
|
|
142
|
-
async function
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
let exclude = ['node_modules/**', '.git/**']
|
|
325
|
+
async function importPlaywrightSupport(): Promise<typeof playwrightSupport> {
|
|
326
|
+
try {
|
|
327
|
+
return await import('./lib/playwright.ts')
|
|
328
|
+
} catch (error) {
|
|
329
|
+
throw toPlaywrightImportError(error)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
150
332
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
333
|
+
async function importBrowserTestRunner(): Promise<typeof browserTestRunner> {
|
|
334
|
+
try {
|
|
335
|
+
return await import('./lib/runner-browser.ts')
|
|
336
|
+
} catch (error) {
|
|
337
|
+
throw toPlaywrightImportError(error)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function toPlaywrightImportError(error: unknown): unknown {
|
|
342
|
+
return isMissingPlaywrightImport(error) ? new Error(MISSING_PLAYWRIGHT_MESSAGE) : error
|
|
343
|
+
}
|
|
154
344
|
|
|
155
|
-
|
|
345
|
+
function isMissingPlaywrightImport(error: unknown): boolean {
|
|
346
|
+
if (!isRecord(error) || typeof error.message !== 'string') {
|
|
347
|
+
return false
|
|
156
348
|
}
|
|
157
349
|
|
|
158
|
-
|
|
350
|
+
return (
|
|
351
|
+
(error.code === 'ERR_MODULE_NOT_FOUND' || error.code === 'MODULE_NOT_FOUND') &&
|
|
352
|
+
(error.message.includes("Cannot find package 'playwright'") ||
|
|
353
|
+
error.message.includes("Cannot find module 'playwright'"))
|
|
354
|
+
)
|
|
355
|
+
}
|
|
159
356
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
357
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
358
|
+
return typeof value === 'object' && value !== null
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function resolveCwd(cwd: string): Promise<string> {
|
|
362
|
+
try {
|
|
363
|
+
return await fsp.realpath(cwd)
|
|
364
|
+
} catch {
|
|
365
|
+
return path.resolve(cwd)
|
|
163
366
|
}
|
|
367
|
+
}
|
|
164
368
|
|
|
165
|
-
|
|
369
|
+
async function discoverTests(
|
|
370
|
+
config: ResolvedRemixTestConfig,
|
|
371
|
+
cwd: string,
|
|
372
|
+
): Promise<DiscoveredTests | null> {
|
|
373
|
+
let files = await findFiles(config.glob.test, config.glob.exclude, cwd)
|
|
166
374
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
375
|
+
if (files.length === 0) {
|
|
376
|
+
console.log(`No test files found matching pattern: ${config.glob.test.join(', ')}`)
|
|
377
|
+
return null
|
|
378
|
+
}
|
|
170
379
|
|
|
171
|
-
let
|
|
380
|
+
let browserSet = new Set(await findFiles(config.glob.browser, config.glob.exclude, cwd))
|
|
381
|
+
let e2eSet = new Set(await findFiles(config.glob.e2e, config.glob.exclude, cwd))
|
|
382
|
+
let types = new Set(config.type)
|
|
383
|
+
let browserFiles = types.has('browser') ? files.filter((f) => browserSet.has(f)) : []
|
|
384
|
+
let e2eFiles = types.has('e2e') ? files.filter((file) => e2eSet.has(file)) : []
|
|
385
|
+
let serverFiles = types.has('server')
|
|
386
|
+
? files.filter((file) => !browserSet.has(file) && !e2eSet.has(file))
|
|
387
|
+
: []
|
|
388
|
+
let totalFiles = browserFiles.length + serverFiles.length + e2eFiles.length
|
|
172
389
|
|
|
173
390
|
if (totalFiles === 0) {
|
|
174
|
-
console.log(`No test files remain after filtering for type ${config.type}`)
|
|
175
|
-
|
|
391
|
+
console.log(`No test files remain after filtering for type ${config.type.join(', ')}`)
|
|
392
|
+
return null
|
|
176
393
|
}
|
|
177
394
|
|
|
178
395
|
console.log(
|
|
179
|
-
`Found ${totalFiles} test file(s) (${serverFiles.length} server, ${e2eFiles.length} e2e)`,
|
|
396
|
+
`Found ${totalFiles} test file(s) (${serverFiles.length} server, ${browserFiles.length} browser, ${e2eFiles.length} e2e)`,
|
|
180
397
|
)
|
|
181
398
|
|
|
182
399
|
return {
|
|
183
400
|
files,
|
|
184
401
|
serverFiles,
|
|
402
|
+
browserFiles,
|
|
185
403
|
e2eFiles,
|
|
186
404
|
}
|
|
187
405
|
}
|
|
188
406
|
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
407
|
+
async function findFiles(
|
|
408
|
+
patterns: string[],
|
|
409
|
+
excludePatterns: string[],
|
|
410
|
+
cwd: string,
|
|
411
|
+
): Promise<string[]> {
|
|
412
|
+
let files = new Set<string>()
|
|
413
|
+
|
|
414
|
+
if (IS_BUN) {
|
|
415
|
+
// Bun's `fs.promises.glob` follows symlinks and doesn't prune traversal
|
|
416
|
+
// via `exclude`, so it enters pnpm symlink cycles in `node_modules`.
|
|
417
|
+
// Use Bun's native Glob, which defaults to `followSymlinks: false`.
|
|
418
|
+
// @ts-expect-error — bun module is only resolvable under the Bun runtime
|
|
419
|
+
let { Glob } = await import('bun')
|
|
420
|
+
let excludeGlobs = excludePatterns.map((p) => new Glob(p))
|
|
421
|
+
for (let pattern of patterns) {
|
|
422
|
+
let glob = new Glob(pattern)
|
|
423
|
+
for await (let file of glob.scan({ cwd, absolute: true })) {
|
|
424
|
+
let rel = toPosix(path.relative(cwd, file))
|
|
425
|
+
if (!excludeGlobs.some((eg: { match: (s: string) => boolean }) => eg.match(rel))) {
|
|
426
|
+
files.add(toPosix(file))
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return [...files]
|
|
431
|
+
}
|
|
193
432
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
queued = true
|
|
198
|
-
} else {
|
|
199
|
-
console.log(`\n↻ Change detected (${reason}), re-running tests...\n`)
|
|
200
|
-
void executeRun()
|
|
433
|
+
for (let pattern of patterns) {
|
|
434
|
+
for await (let file of fsp.glob(pattern, { cwd, exclude: excludePatterns })) {
|
|
435
|
+
files.add(toPosix(path.resolve(cwd, file)))
|
|
201
436
|
}
|
|
202
|
-
}
|
|
437
|
+
}
|
|
438
|
+
return [...files]
|
|
203
439
|
}
|
|
204
440
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
441
|
+
// Normalize discovered paths so set membership across the test/browser/e2e
|
|
442
|
+
// `findFiles` calls is byte-stable on every platform. Node accepts forward
|
|
443
|
+
// slashes for filesystem operations on Windows, so downstream `fs.readFile`
|
|
444
|
+
// etc. work without further conversion.
|
|
445
|
+
function toPosix(p: string): string {
|
|
446
|
+
return path.sep === '/' ? p : p.replace(/\\/g, '/')
|
|
210
447
|
}
|
package/src/index.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 {
|
|
3
3
|
describe,
|
|
4
4
|
it,
|
|
@@ -13,3 +13,4 @@ export {
|
|
|
13
13
|
} from './lib/framework.ts'
|
|
14
14
|
export { mock } from './lib/mock.ts'
|
|
15
15
|
export type { TestContext } from './lib/context.ts'
|
|
16
|
+
export type { FakeTimers } from './lib/fake-timers.ts'
|