@jsenv/test 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/controllable_child_process.mjs +129 -0
  2. package/dist/controllable_worker_thread.mjs +91 -0
  3. package/dist/importmap_node_loader.mjs +551 -0
  4. package/dist/js/execute_using_dynamic_import.js +850 -0
  5. package/dist/js/v8_coverage.js +508 -0
  6. package/dist/jsenv_test.js +5993 -0
  7. package/dist/no_experimental_warnings.cjs +10 -0
  8. package/package.json +56 -0
  9. package/src/coverage/babel_plugin_instrument.js +48 -0
  10. package/src/coverage/coverage_reporter_html_directory.js +32 -0
  11. package/src/coverage/coverage_reporter_json_file.js +17 -0
  12. package/src/coverage/coverage_reporter_text_log.js +19 -0
  13. package/src/coverage/empty_coverage_factory.js +52 -0
  14. package/src/coverage/file_by_file_coverage.js +25 -0
  15. package/src/coverage/istanbul_coverage_composition.js +28 -0
  16. package/src/coverage/istanbul_coverage_map_from_coverage.js +16 -0
  17. package/src/coverage/list_files_not_covered.js +15 -0
  18. package/src/coverage/missing_coverage.js +41 -0
  19. package/src/coverage/report_to_coverage.js +198 -0
  20. package/src/coverage/v8_and_istanbul.js +37 -0
  21. package/src/coverage/v8_coverage.js +26 -0
  22. package/src/coverage/v8_coverage_composition.js +24 -0
  23. package/src/coverage/v8_coverage_node_directory.js +85 -0
  24. package/src/coverage/v8_coverage_to_istanbul.js +99 -0
  25. package/src/execution/execute.js +111 -0
  26. package/src/execution/execute_steps.js +423 -0
  27. package/src/execution/execute_test_plan.js +372 -0
  28. package/src/execution/execution_colors.js +10 -0
  29. package/src/execution/execution_steps.js +65 -0
  30. package/src/execution/gc.js +9 -0
  31. package/src/execution/logs_file_execution.js +427 -0
  32. package/src/execution/logs_file_execution.test.mjs +41 -0
  33. package/src/execution/run.js +160 -0
  34. package/src/execution/web_server_param.js +73 -0
  35. package/src/main.js +10 -0
  36. package/src/runtime_browsers/chromium.js +24 -0
  37. package/src/runtime_browsers/firefox.js +23 -0
  38. package/src/runtime_browsers/middleware_istanbul.js +65 -0
  39. package/src/runtime_browsers/middleware_js_supervisor.js +98 -0
  40. package/src/runtime_browsers/using_playwright.js +573 -0
  41. package/src/runtime_browsers/webkit.js +40 -0
  42. package/src/runtime_node/child_exec_options.js +166 -0
  43. package/src/runtime_node/controllable_child_process.mjs +135 -0
  44. package/src/runtime_node/controllable_worker_thread.mjs +103 -0
  45. package/src/runtime_node/exec_options.js +57 -0
  46. package/src/runtime_node/execute_using_dynamic_import.js +53 -0
  47. package/src/runtime_node/exit_codes.js +9 -0
  48. package/src/runtime_node/importmap_node_loader.mjs +51 -0
  49. package/src/runtime_node/importmap_node_loader_file_url.js +4 -0
  50. package/src/runtime_node/kill_process_tree.js +76 -0
  51. package/src/runtime_node/no_experimental_warnings.cjs +13 -0
  52. package/src/runtime_node/no_experimental_warnings_file_url.js +4 -0
  53. package/src/runtime_node/node_child_process.js +374 -0
  54. package/src/runtime_node/node_execution_performance.js +67 -0
  55. package/src/runtime_node/node_worker_thread.js +301 -0
  56. package/src/runtime_node/profiler_v8_coverage.js +56 -0
@@ -0,0 +1,427 @@
1
+ import wrapAnsi from "wrap-ansi"
2
+ import {
3
+ ANSI,
4
+ UNICODE,
5
+ msAsEllapsedTime,
6
+ msAsDuration,
7
+ byteAsMemoryUsage,
8
+ } from "@jsenv/log"
9
+
10
+ import { EXECUTION_COLORS } from "./execution_colors.js"
11
+
12
+ export const createExecutionLog = (
13
+ {
14
+ executionIndex,
15
+ fileRelativeUrl,
16
+ runtimeName,
17
+ runtimeVersion,
18
+ executionParams,
19
+ executionResult,
20
+ startMs,
21
+ endMs,
22
+ },
23
+ {
24
+ completedExecutionLogAbbreviation,
25
+ counters,
26
+ logRuntime,
27
+ logEachDuration,
28
+ timeEllapsed,
29
+ memoryHeap,
30
+ },
31
+ ) => {
32
+ const { status } = executionResult
33
+ const descriptionFormatter = descriptionFormatters[status]
34
+ const description = descriptionFormatter({
35
+ index: executionIndex,
36
+ total: counters.total,
37
+ executionParams,
38
+ })
39
+ const summary = createIntermediateSummary({
40
+ executionIndex,
41
+ counters,
42
+ timeEllapsed,
43
+ memoryHeap,
44
+ })
45
+ let log
46
+ if (completedExecutionLogAbbreviation && status === "completed") {
47
+ log = `${description}${summary}`
48
+ } else {
49
+ const { consoleCalls = [], errors = [] } = executionResult
50
+ const consoleOutput = formatConsoleCalls(consoleCalls)
51
+ const errorsOutput = formatErrors(errors)
52
+ log = formatExecution({
53
+ label: `${description}${summary}`,
54
+ details: {
55
+ file: fileRelativeUrl,
56
+ ...(logRuntime ? { runtime: `${runtimeName}/${runtimeVersion}` } : {}),
57
+ ...(logEachDuration
58
+ ? {
59
+ duration:
60
+ status === "executing"
61
+ ? msAsEllapsedTime(Date.now() - startMs)
62
+ : msAsDuration(endMs - startMs),
63
+ }
64
+ : {}),
65
+ },
66
+ consoleOutput,
67
+ errorsOutput,
68
+ })
69
+ }
70
+
71
+ const { columns = 80 } = process.stdout
72
+ log = wrapAnsi(log, columns, {
73
+ trim: false,
74
+ hard: true,
75
+ wordWrap: false,
76
+ })
77
+ if (endMs) {
78
+ if (completedExecutionLogAbbreviation) {
79
+ return `${log}\n`
80
+ }
81
+ if (executionIndex === counters.total - 1) {
82
+ return `${log}\n`
83
+ }
84
+ return `${log}\n\n`
85
+ }
86
+ return log
87
+ }
88
+
89
+ const formatErrors = (errors) => {
90
+ if (errors.length === 0) {
91
+ return ""
92
+ }
93
+ const formatError = (error) => error.stack || error.message || error
94
+
95
+ if (errors.length === 1) {
96
+ return `${ANSI.color(`-------- error --------`, ANSI.RED)}
97
+ ${formatError(errors[0])}
98
+ ${ANSI.color(`-------------------------`, ANSI.RED)}`
99
+ }
100
+
101
+ let output = []
102
+ errors.forEach((error) => {
103
+ output.push(
104
+ prefixFirstAndIndentRemainingLines({
105
+ prefix: `${UNICODE.CIRCLE_CROSS} `,
106
+ indentation: " ",
107
+ text: formatError(error),
108
+ }),
109
+ )
110
+ })
111
+ return `${ANSI.color(`-------- errors (${errors.length}) --------`, ANSI.RED)}
112
+ ${output.join(`\n`)}
113
+ ${ANSI.color(`-------------------------`, ANSI.RED)}`
114
+ }
115
+
116
+ export const createSummaryLog = (
117
+ summary,
118
+ ) => `-------------- summary -----------------
119
+ ${createAllExecutionsSummary(summary)}
120
+ total duration: ${msAsDuration(summary.duration)}
121
+ ----------------------------------------`
122
+
123
+ const createAllExecutionsSummary = ({ counters }) => {
124
+ if (counters.total === 0) {
125
+ return `no execution`
126
+ }
127
+ const executionLabel =
128
+ counters.total === 1 ? `1 execution` : `${counters.total} executions`
129
+ return `${executionLabel}: ${createStatusSummary({
130
+ counters,
131
+ })}`
132
+ }
133
+
134
+ const createIntermediateSummary = ({
135
+ executionIndex,
136
+ counters,
137
+ memoryHeap,
138
+ timeEllapsed,
139
+ }) => {
140
+ const parts = []
141
+ if (executionIndex > 0 || counters.done > 0) {
142
+ parts.push(
143
+ createStatusSummary({
144
+ counters: {
145
+ ...counters,
146
+ total: executionIndex + 1,
147
+ },
148
+ }),
149
+ )
150
+ }
151
+ if (timeEllapsed) {
152
+ parts.push(`duration: ${msAsEllapsedTime(timeEllapsed)}`)
153
+ }
154
+ if (memoryHeap) {
155
+ parts.push(`memory heap: ${byteAsMemoryUsage(memoryHeap)}`)
156
+ }
157
+ if (parts.length === 0) {
158
+ return ""
159
+ }
160
+ return ` (${parts.join(` / `)})`
161
+ }
162
+
163
+ const createStatusSummary = ({ counters }) => {
164
+ if (counters.aborted === counters.total) {
165
+ return `all ${ANSI.color(`aborted`, EXECUTION_COLORS.aborted)}`
166
+ }
167
+ if (counters.timedout === counters.total) {
168
+ return `all ${ANSI.color(`timed out`, EXECUTION_COLORS.timedout)}`
169
+ }
170
+ if (counters.failed === counters.total) {
171
+ return `all ${ANSI.color(`failed`, EXECUTION_COLORS.failed)}`
172
+ }
173
+ if (counters.completed === counters.total) {
174
+ return `all ${ANSI.color(`completed`, EXECUTION_COLORS.completed)}`
175
+ }
176
+ if (counters.cancelled === counters.total) {
177
+ return `all ${ANSI.color(`cancelled`, EXECUTION_COLORS.cancelled)}`
178
+ }
179
+ return createMixedDetails({
180
+ counters,
181
+ })
182
+ }
183
+
184
+ const createMixedDetails = ({ counters }) => {
185
+ const parts = []
186
+ if (counters.timedout) {
187
+ parts.push(
188
+ `${counters.timedout} ${ANSI.color(
189
+ `timed out`,
190
+ EXECUTION_COLORS.timedout,
191
+ )}`,
192
+ )
193
+ }
194
+ if (counters.failed) {
195
+ parts.push(
196
+ `${counters.failed} ${ANSI.color(`failed`, EXECUTION_COLORS.failed)}`,
197
+ )
198
+ }
199
+ if (counters.completed) {
200
+ parts.push(
201
+ `${counters.completed} ${ANSI.color(
202
+ `completed`,
203
+ EXECUTION_COLORS.completed,
204
+ )}`,
205
+ )
206
+ }
207
+ if (counters.aborted) {
208
+ parts.push(
209
+ `${counters.aborted} ${ANSI.color(`aborted`, EXECUTION_COLORS.aborted)}`,
210
+ )
211
+ }
212
+ if (counters.cancelled) {
213
+ parts.push(
214
+ `${counters.cancelled} ${ANSI.color(
215
+ `cancelled`,
216
+ EXECUTION_COLORS.cancelled,
217
+ )}`,
218
+ )
219
+ }
220
+ return `${parts.join(", ")}`
221
+ }
222
+
223
+ const descriptionFormatters = {
224
+ executing: ({ index, total }) => {
225
+ return ANSI.color(
226
+ `executing ${padNumber(index, total)} of ${total}`,
227
+ EXECUTION_COLORS.executing,
228
+ )
229
+ },
230
+ aborted: ({ index, total }) => {
231
+ return ANSI.color(
232
+ `${UNICODE.FAILURE_RAW} execution ${padNumber(
233
+ index,
234
+ total,
235
+ )} of ${total} aborted`,
236
+ EXECUTION_COLORS.aborted,
237
+ )
238
+ },
239
+ timedout: ({ index, total, executionParams }) => {
240
+ return ANSI.color(
241
+ `${UNICODE.FAILURE_RAW} execution ${padNumber(
242
+ index,
243
+ total,
244
+ )} of ${total} timeout after ${executionParams.allocatedMs}ms`,
245
+ EXECUTION_COLORS.timedout,
246
+ )
247
+ },
248
+ failed: ({ index, total }) => {
249
+ return ANSI.color(
250
+ `${UNICODE.FAILURE_RAW} execution ${padNumber(
251
+ index,
252
+ total,
253
+ )} of ${total} failed`,
254
+ EXECUTION_COLORS.failed,
255
+ )
256
+ },
257
+ completed: ({ index, total }) => {
258
+ return ANSI.color(
259
+ `${UNICODE.OK_RAW} execution ${padNumber(
260
+ index,
261
+ total,
262
+ )} of ${total} completed`,
263
+ EXECUTION_COLORS.completed,
264
+ )
265
+ },
266
+ cancelled: ({ index, total }) => {
267
+ return ANSI.color(
268
+ `${UNICODE.FAILURE_RAW} execution ${padNumber(
269
+ index,
270
+ total,
271
+ )} of ${total} cancelled`,
272
+ EXECUTION_COLORS.cancelled,
273
+ )
274
+ },
275
+ }
276
+
277
+ const padNumber = (index, total) => {
278
+ const number = index + 1
279
+ const numberWidth = String(number).length
280
+ const totalWith = String(total).length
281
+ let missingWidth = totalWith - numberWidth
282
+ let padded = ""
283
+ while (missingWidth--) {
284
+ padded += "0"
285
+ }
286
+ padded += number
287
+ return padded
288
+ }
289
+
290
+ const formatConsoleCalls = (consoleCalls) => {
291
+ if (consoleCalls.length === 0) {
292
+ return ""
293
+ }
294
+ const repartition = {
295
+ debug: 0,
296
+ info: 0,
297
+ warning: 0,
298
+ error: 0,
299
+ log: 0,
300
+ }
301
+ consoleCalls.forEach((consoleCall) => {
302
+ repartition[consoleCall.type]++
303
+ })
304
+ const consoleOutput = formatConsoleOutput(consoleCalls)
305
+
306
+ return `${ANSI.color(
307
+ `-------- ${formatConsoleSummary(repartition)} --------`,
308
+ ANSI.GREY,
309
+ )}
310
+ ${consoleOutput}
311
+ ${ANSI.color(`-------------------------`, ANSI.GREY)}`
312
+ }
313
+
314
+ export const formatConsoleOutput = (consoleCalls) => {
315
+ // inside Node.js you can do process.stdout.write()
316
+ // and in that case the consoleCall is not suffixed with "\n"
317
+ // we want to keep these calls together in the output
318
+ const regroupedCalls = []
319
+ consoleCalls.forEach((consoleCall, index) => {
320
+ if (index === 0) {
321
+ regroupedCalls.push(consoleCall)
322
+ return
323
+ }
324
+ const previousCall = consoleCalls[index - 1]
325
+ if (previousCall.type !== consoleCall.type) {
326
+ regroupedCalls.push(consoleCall)
327
+ return
328
+ }
329
+ if (previousCall.text.endsWith("\n")) {
330
+ regroupedCalls.push(consoleCall)
331
+ return
332
+ }
333
+ if (previousCall.text.endsWith("\r")) {
334
+ regroupedCalls.push(consoleCall)
335
+ return
336
+ }
337
+ const previousRegroupedCallIndex = regroupedCalls.length - 1
338
+ const previousRegroupedCall = regroupedCalls[previousRegroupedCallIndex]
339
+ previousRegroupedCall.text = `${previousRegroupedCall.text}${consoleCall.text}`
340
+ })
341
+
342
+ let consoleOutput = ``
343
+ regroupedCalls.forEach((regroupedCall, index) => {
344
+ const text = regroupedCall.text
345
+ const textFormatted = prefixFirstAndIndentRemainingLines({
346
+ prefix: CONSOLE_ICONS[regroupedCall.type],
347
+ text,
348
+ trimLines: true,
349
+ trimLastLine: index === regroupedCalls.length - 1,
350
+ })
351
+ consoleOutput += textFormatted
352
+ })
353
+ return consoleOutput
354
+ }
355
+
356
+ const prefixFirstAndIndentRemainingLines = ({
357
+ prefix,
358
+ indentation = " ",
359
+ text,
360
+ trimLines,
361
+ trimLastLine,
362
+ }) => {
363
+ const lines = text.split(/\r?\n/)
364
+ const firstLine = lines.shift()
365
+ let result = `${prefix} ${firstLine}`
366
+ let i = 0
367
+ while (i < lines.length) {
368
+ const line = trimLines ? lines[i].trim() : lines[i]
369
+ i++
370
+ result += line.length
371
+ ? `\n${indentation}${line}`
372
+ : trimLastLine && i === lines.length
373
+ ? ""
374
+ : `\n`
375
+ }
376
+ return result
377
+ }
378
+
379
+ const CONSOLE_ICONS = {
380
+ debug: UNICODE.DEBUG,
381
+ info: UNICODE.INFO,
382
+ warning: UNICODE.WARNING,
383
+ error: UNICODE.FAILURE,
384
+ log: " ",
385
+ }
386
+
387
+ const formatConsoleSummary = (repartition) => {
388
+ const { debug, info, warning, error } = repartition
389
+ const parts = []
390
+ if (error) {
391
+ parts.push(`${CONSOLE_ICONS.error} ${error}`)
392
+ }
393
+ if (warning) {
394
+ parts.push(`${CONSOLE_ICONS.warning} ${warning}`)
395
+ }
396
+ if (info) {
397
+ parts.push(`${CONSOLE_ICONS.info} ${info}`)
398
+ }
399
+ if (debug) {
400
+ parts.push(`${CONSOLE_ICONS.debug} ${debug}`)
401
+ }
402
+ if (parts.length === 0) {
403
+ return `console`
404
+ }
405
+ return `console (${parts.join(" ")})`
406
+ }
407
+
408
+ const formatExecution = ({
409
+ label,
410
+ details = {},
411
+ consoleOutput,
412
+ errorsOutput,
413
+ }) => {
414
+ let message = ``
415
+ message += label
416
+ Object.keys(details).forEach((key) => {
417
+ message += `
418
+ ${key}: ${details[key]}`
419
+ })
420
+ if (consoleOutput) {
421
+ message += `\n${consoleOutput}`
422
+ }
423
+ if (errorsOutput) {
424
+ message += `\n${errorsOutput}`
425
+ }
426
+ return message
427
+ }
@@ -0,0 +1,41 @@
1
+ import { assert } from "@jsenv/assert"
2
+
3
+ import { formatConsoleOutput } from "./logs_file_execution.js"
4
+
5
+ {
6
+ const actual = formatConsoleOutput([
7
+ { type: "log", text: "a\n" },
8
+ { type: "log", text: "b\n" },
9
+ ])
10
+ const expected = ` a
11
+ b`
12
+ assert({ actual, expected })
13
+ }
14
+
15
+ {
16
+ const actual = formatConsoleOutput([
17
+ { type: "log", text: "a" },
18
+ { type: "log", text: "b" },
19
+ ])
20
+ const expected = ` ab`
21
+ assert({ actual, expected })
22
+ }
23
+
24
+ {
25
+ const actual = formatConsoleOutput([
26
+ {
27
+ type: "log",
28
+ text: `1
29
+ 2`,
30
+ },
31
+ {
32
+ type: "log",
33
+ text: `alpha
34
+ beta`,
35
+ },
36
+ ])
37
+ const expected = ` 1
38
+ 2alpha
39
+ beta`
40
+ assert({ actual, expected })
41
+ }
@@ -0,0 +1,160 @@
1
+ /*
2
+ * Export a function capable to run a file on a runtime.
3
+ *
4
+ * - Used internally by "executeTestPlan" part of the documented API
5
+ * - Used internally by "execute" an advanced API not documented
6
+ * - logs generated during file execution can be collected
7
+ * - logs generated during file execution can be mirrored (re-logged to the console)
8
+ * - File is given allocatedMs to complete
9
+ * - Errors are collected
10
+ * - File execution result is returned, it contains status/errors/namespace/consoleCalls
11
+ */
12
+
13
+ import { createId } from "@paralleldrive/cuid2"
14
+ import { Abort, raceCallbacks } from "@jsenv/abort"
15
+ import { ensureParentDirectories } from "@jsenv/filesystem"
16
+
17
+ export const run = async ({
18
+ signal = new AbortController().signal,
19
+ logger,
20
+ allocatedMs,
21
+ keepRunning = false,
22
+ mirrorConsole = false,
23
+ collectConsole = false,
24
+ coverageEnabled = false,
25
+ coverageTempDirectoryUrl,
26
+ collectPerformance = false,
27
+ runtime,
28
+ runtimeParams,
29
+ }) => {
30
+ const result = {
31
+ status: "pending",
32
+ errors: [],
33
+ namespace: null,
34
+ }
35
+ const callbacks = []
36
+
37
+ const onConsoleRef = { current: () => {} }
38
+ const stopSignal = { notify: () => {} }
39
+ const runtimeLabel = `${runtime.name}/${runtime.version}`
40
+
41
+ const runOperation = Abort.startOperation()
42
+ runOperation.addAbortSignal(signal)
43
+ let timeoutAbortSource
44
+ if (
45
+ // ideally we would rather log than the timeout is ignored
46
+ // when keepRunning is true
47
+ !keepRunning &&
48
+ typeof allocatedMs === "number" &&
49
+ allocatedMs !== Infinity
50
+ ) {
51
+ timeoutAbortSource = runOperation.timeout(allocatedMs)
52
+ }
53
+ const consoleCalls = []
54
+ onConsoleRef.current = ({ type, text }) => {
55
+ if (mirrorConsole) {
56
+ if (type === "error") {
57
+ process.stderr.write(text)
58
+ } else {
59
+ process.stdout.write(text)
60
+ }
61
+ }
62
+ if (collectConsole) {
63
+ consoleCalls.push({ type, text })
64
+ }
65
+ }
66
+ if (collectConsole) {
67
+ result.consoleCalls = consoleCalls
68
+ }
69
+
70
+ // we do not keep coverage in memory, it can grow very big
71
+ // instead we store it on the filesystem
72
+ // and they can be read later at "coverageFileUrl"
73
+ let coverageFileUrl
74
+ if (coverageEnabled) {
75
+ coverageFileUrl = new URL(
76
+ `./${runtime.name}/${createId()}.json`,
77
+ coverageTempDirectoryUrl,
78
+ ).href
79
+ await ensureParentDirectories(coverageFileUrl)
80
+ if (coverageEnabled) {
81
+ result.coverageFileUrl = coverageFileUrl
82
+ // written within the child_process/worker_thread or during runtime.run()
83
+ // for browsers
84
+ // (because it takes time to serialize and transfer the coverage object)
85
+ }
86
+ }
87
+
88
+ const startMs = Date.now()
89
+ callbacks.push(() => {
90
+ result.duration = Date.now() - startMs
91
+ })
92
+
93
+ try {
94
+ logger.debug(`run() ${runtimeLabel}`)
95
+ runOperation.throwIfAborted()
96
+ const winnerPromise = new Promise((resolve) => {
97
+ raceCallbacks(
98
+ {
99
+ aborted: (cb) => {
100
+ runOperation.signal.addEventListener("abort", cb)
101
+ return () => {
102
+ runOperation.signal.removeEventListener("abort", cb)
103
+ }
104
+ },
105
+ runned: async (cb) => {
106
+ try {
107
+ const runResult = await runtime.run({
108
+ signal: runOperation.signal,
109
+ logger,
110
+ ...runtimeParams,
111
+ collectConsole,
112
+ collectPerformance,
113
+ coverageFileUrl,
114
+ keepRunning,
115
+ stopSignal,
116
+ onConsole: (log) => onConsoleRef.current(log),
117
+ })
118
+ cb(runResult)
119
+ } catch (e) {
120
+ cb({
121
+ status: "failed",
122
+ errors: [e],
123
+ })
124
+ }
125
+ },
126
+ },
127
+ resolve,
128
+ )
129
+ })
130
+ const winner = await winnerPromise
131
+ if (winner.name === "aborted") {
132
+ runOperation.throwIfAborted()
133
+ }
134
+ const { status, namespace, errors, performance } = winner.data
135
+ result.status = status
136
+ result.errors.push(...errors)
137
+ result.namespace = namespace
138
+ if (collectPerformance) {
139
+ result.performance = performance
140
+ }
141
+ } catch (e) {
142
+ if (Abort.isAbortError(e)) {
143
+ if (timeoutAbortSource && timeoutAbortSource.signal.aborted) {
144
+ result.status = "timedout"
145
+ } else {
146
+ result.status = "aborted"
147
+ }
148
+ } else {
149
+ result.status = "failed"
150
+ result.errors.push(e)
151
+ }
152
+ } finally {
153
+ await runOperation.end()
154
+ }
155
+
156
+ callbacks.forEach((callback) => {
157
+ callback()
158
+ })
159
+ return result
160
+ }
@@ -0,0 +1,73 @@
1
+ import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
2
+ import { pingServer } from "@jsenv/core/src/helpers/ping_server.js"
3
+ import { basicFetch } from "@jsenv/core/src/helpers/basic_fetch.js"
4
+
5
+ export const assertAndNormalizeWebServer = async (webServer) => {
6
+ if (!webServer) {
7
+ throw new TypeError(
8
+ `webServer is required when running tests on browser(s)`,
9
+ )
10
+ }
11
+ const unexpectedParamNames = Object.keys(webServer).filter((key) => {
12
+ return !["origin", "moduleUrl", "rootDirectoryUrl"].includes(key)
13
+ })
14
+ if (unexpectedParamNames.length > 0) {
15
+ throw new TypeError(
16
+ `${unexpectedParamNames.join(",")}: there is no such param to webServer`,
17
+ )
18
+ }
19
+
20
+ let aServerIsListening = await pingServer(webServer.origin)
21
+ if (!aServerIsListening) {
22
+ if (!webServer.moduleUrl) {
23
+ throw new TypeError(
24
+ `webServer.moduleUrl is required as there is no server listening "${webServer.origin}"`,
25
+ )
26
+ }
27
+ try {
28
+ process.env.IMPORTED_BY_TEST_PLAN = "1"
29
+ await import(webServer.moduleUrl)
30
+ delete process.env.IMPORTED_BY_TEST_PLAN
31
+ } catch (e) {
32
+ if (e.code === "ERR_MODULE_NOT_FOUND") {
33
+ throw new Error(
34
+ `webServer.moduleUrl does not lead to a file at "${webServer.moduleUrl}"`,
35
+ )
36
+ }
37
+ throw e
38
+ }
39
+ aServerIsListening = await pingServer(webServer.origin)
40
+ if (!aServerIsListening) {
41
+ throw new Error(
42
+ `webServer.moduleUrl did not start a server listening at "${webServer.origin}", check file at "${webServer.moduleUrl}"`,
43
+ )
44
+ }
45
+ }
46
+ const { headers } = await basicFetch(webServer.origin, {
47
+ method: "GET",
48
+ rejectUnauthorized: false,
49
+ headers: {
50
+ "x-server-inspect": "1",
51
+ },
52
+ })
53
+ if (String(headers["server"]).includes("jsenv_dev_server")) {
54
+ webServer.isJsenvDevServer = true
55
+ const { json } = await basicFetch(`${webServer.origin}/__params__.json`, {
56
+ rejectUnauthorized: false,
57
+ })
58
+ if (webServer.rootDirectoryUrl === undefined) {
59
+ const jsenvDevServerParams = await json()
60
+ webServer.rootDirectoryUrl = jsenvDevServerParams.sourceDirectoryUrl
61
+ } else {
62
+ webServer.rootDirectoryUrl = assertAndNormalizeDirectoryUrl(
63
+ webServer.rootDirectoryUrl,
64
+ "webServer.rootDirectoryUrl",
65
+ )
66
+ }
67
+ } else {
68
+ webServer.rootDirectoryUrl = assertAndNormalizeDirectoryUrl(
69
+ webServer.rootDirectoryUrl,
70
+ "webServer.rootDirectoryUrl",
71
+ )
72
+ }
73
+ }
package/src/main.js ADDED
@@ -0,0 +1,10 @@
1
+ // test
2
+ export { executeTestPlan } from "./execution/execute_test_plan.js"
3
+ // runtimes (used to execute tests)
4
+ export { chromium, chromiumIsolatedTab } from "./runtime_browsers/chromium.js"
5
+ export { firefox, firefoxIsolatedTab } from "./runtime_browsers/firefox.js"
6
+ export { webkit, webkitIsolatedTab } from "./runtime_browsers/webkit.js"
7
+ export { nodeChildProcess } from "./runtime_node/node_child_process.js"
8
+ export { nodeWorkerThread } from "./runtime_node/node_worker_thread.js"
9
+ // advanced
10
+ export { execute } from "./execution/execute.js"