@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,573 @@
1
+ import { writeFileSync } from "node:fs"
2
+ import { createDetailedMessage } from "@jsenv/log"
3
+ import {
4
+ Abort,
5
+ createCallbackListNotifiedOnce,
6
+ raceProcessTeardownEvents,
7
+ raceCallbacks,
8
+ } from "@jsenv/abort"
9
+ import { urlIsInsideOf } from "@jsenv/urls"
10
+ import { memoize } from "@jsenv/utils/src/memoize/memoize.js"
11
+ import { WEB_URL_CONVERTER } from "@jsenv/core/src/helpers/web_url_converter.js"
12
+
13
+ import { filterV8Coverage } from "../coverage/v8_coverage.js"
14
+ import { composeTwoFileByFileIstanbulCoverages } from "../coverage/istanbul_coverage_composition.js"
15
+ import { initJsSupervisorMiddleware } from "./middleware_js_supervisor.js"
16
+ import { initIstanbulMiddleware } from "./middleware_istanbul.js"
17
+
18
+ const browserPromiseCache = new Map()
19
+
20
+ export const createRuntimeUsingPlaywright = ({
21
+ browserName,
22
+ browserVersion,
23
+ coveragePlaywrightAPIAvailable = false,
24
+ shouldIgnoreError = () => false,
25
+ transformErrorHook = (error) => error,
26
+ isolatedTab = false,
27
+ headful,
28
+ playwrightLaunchOptions = {},
29
+ ignoreHTTPSErrors = true,
30
+ }) => {
31
+ const label = `${browserName}${browserVersion}`
32
+ const runtime = {
33
+ type: "browser",
34
+ name: browserName,
35
+ version: browserVersion,
36
+ capabilities: {
37
+ coverageV8: coveragePlaywrightAPIAvailable,
38
+ },
39
+ }
40
+
41
+ runtime.run = async ({
42
+ signal = new AbortController().signal,
43
+ logger,
44
+ rootDirectoryUrl,
45
+ webServer,
46
+ fileRelativeUrl,
47
+
48
+ // measurePerformance,
49
+ collectPerformance,
50
+ coverageEnabled = false,
51
+ coverageConfig,
52
+ coverageMethodForBrowsers,
53
+ coverageFileUrl,
54
+
55
+ stopAfterAllSignal,
56
+ stopSignal,
57
+ keepRunning,
58
+ onConsole,
59
+ }) => {
60
+ const fileUrl = new URL(fileRelativeUrl, rootDirectoryUrl).href
61
+ if (!urlIsInsideOf(fileUrl, webServer.rootDirectoryUrl)) {
62
+ throw new Error(`Cannot execute file that is outside web server root directory
63
+ --- file ---
64
+ ${fileUrl}
65
+ --- web server root directory url ---
66
+ ${webServer.rootDirectoryUrl}`)
67
+ }
68
+ const fileServerUrl = WEB_URL_CONVERTER.asWebUrl(fileUrl, webServer)
69
+ const cleanupCallbackList = createCallbackListNotifiedOnce()
70
+ const cleanup = memoize(async (reason) => {
71
+ await cleanupCallbackList.notify({ reason })
72
+ })
73
+
74
+ const isBrowserDedicatedToExecution = isolatedTab || !stopAfterAllSignal
75
+ let browserAndContextPromise = isBrowserDedicatedToExecution
76
+ ? null
77
+ : browserPromiseCache.get(label)
78
+ if (!browserAndContextPromise) {
79
+ browserAndContextPromise = (async () => {
80
+ const browser = await launchBrowserUsingPlaywright({
81
+ signal,
82
+ browserName,
83
+ stopOnExit: true,
84
+ playwrightLaunchOptions: {
85
+ ...playwrightLaunchOptions,
86
+ headless: !headful,
87
+ },
88
+ })
89
+ if (browser._initializer.version) {
90
+ runtime.version = browser._initializer.version
91
+ }
92
+ const browserContext = await browser.newContext({ ignoreHTTPSErrors })
93
+ return { browser, browserContext }
94
+ })()
95
+ if (!isBrowserDedicatedToExecution) {
96
+ browserPromiseCache.set(label, browserAndContextPromise)
97
+ cleanupCallbackList.add(() => {
98
+ browserPromiseCache.delete(label)
99
+ })
100
+ }
101
+ }
102
+ const { browser, browserContext } = await browserAndContextPromise
103
+ const closeBrowser = async () => {
104
+ const disconnected = browser.isConnected()
105
+ ? new Promise((resolve) => {
106
+ const disconnectedCallback = () => {
107
+ browser.removeListener("disconnected", disconnectedCallback)
108
+ resolve()
109
+ }
110
+ browser.on("disconnected", disconnectedCallback)
111
+ })
112
+ : Promise.resolve()
113
+ // for some reason without this 150ms timeout
114
+ // browser.close() never resolves (playwright does not like something)
115
+ await new Promise((resolve) => setTimeout(resolve, 150))
116
+ try {
117
+ await browser.close()
118
+ } catch (e) {
119
+ if (isTargetClosedError(e)) {
120
+ return
121
+ }
122
+ throw e
123
+ }
124
+ await disconnected
125
+ }
126
+
127
+ const page = await browserContext.newPage()
128
+
129
+ const istanbulInstrumentationEnabled =
130
+ coverageEnabled &&
131
+ (!runtime.capabilities.coverageV8 ||
132
+ coverageMethodForBrowsers === "istanbul")
133
+ if (istanbulInstrumentationEnabled) {
134
+ await initIstanbulMiddleware(page, {
135
+ webServer,
136
+ rootDirectoryUrl,
137
+ coverageConfig,
138
+ })
139
+ }
140
+ if (!webServer.isJsenvDevServer) {
141
+ await initJsSupervisorMiddleware(page, {
142
+ webServer,
143
+ fileUrl,
144
+ fileServerUrl,
145
+ })
146
+ }
147
+ const closePage = async () => {
148
+ try {
149
+ await page.close()
150
+ } catch (e) {
151
+ if (isTargetClosedError(e)) {
152
+ return
153
+ }
154
+ throw e
155
+ }
156
+ }
157
+
158
+ const result = {
159
+ status: "pending",
160
+ namespace: null,
161
+ errors: [],
162
+ }
163
+ const callbacks = []
164
+ if (coverageEnabled) {
165
+ if (
166
+ runtime.capabilities.coverageV8 &&
167
+ coverageMethodForBrowsers === "playwright"
168
+ ) {
169
+ await page.coverage.startJSCoverage({
170
+ // reportAnonymousScripts: true,
171
+ })
172
+ callbacks.push(async () => {
173
+ const v8CoveragesWithWebUrls = await page.coverage.stopJSCoverage()
174
+ // we convert urls starting with http:// to file:// because we later
175
+ // convert the url to filesystem path in istanbulCoverageFromV8Coverage function
176
+ const v8CoveragesWithFsUrls = v8CoveragesWithWebUrls.map(
177
+ (v8CoveragesWithWebUrl) => {
178
+ const fsUrl = WEB_URL_CONVERTER.asFileUrl(
179
+ v8CoveragesWithWebUrl.url,
180
+ webServer,
181
+ )
182
+ return {
183
+ ...v8CoveragesWithWebUrl,
184
+ url: fsUrl,
185
+ }
186
+ },
187
+ )
188
+ const coverage = await filterV8Coverage(
189
+ { result: v8CoveragesWithFsUrls },
190
+ {
191
+ rootDirectoryUrl,
192
+ coverageConfig,
193
+ },
194
+ )
195
+ writeFileSync(
196
+ new URL(coverageFileUrl),
197
+ JSON.stringify(coverage, null, " "),
198
+ )
199
+ })
200
+ } else {
201
+ callbacks.push(() => {
202
+ const scriptExecutionResults = result.namespace
203
+ if (scriptExecutionResults) {
204
+ const coverage =
205
+ generateCoverageForPage(scriptExecutionResults) || {}
206
+ writeFileSync(
207
+ new URL(coverageFileUrl),
208
+ JSON.stringify(coverage, null, " "),
209
+ )
210
+ }
211
+ })
212
+ }
213
+ } else {
214
+ callbacks.push(() => {
215
+ const scriptExecutionResults = result.namespace
216
+ if (scriptExecutionResults) {
217
+ Object.keys(scriptExecutionResults).forEach((fileRelativeUrl) => {
218
+ delete scriptExecutionResults[fileRelativeUrl].coverage
219
+ })
220
+ }
221
+ })
222
+ }
223
+
224
+ if (collectPerformance) {
225
+ callbacks.push(async () => {
226
+ const performance = await page.evaluate(
227
+ /* eslint-disable no-undef */
228
+ /* istanbul ignore next */
229
+ () => {
230
+ const { performance } = window
231
+ if (!performance) {
232
+ return null
233
+ }
234
+ const measures = {}
235
+ const measurePerfEntries = performance.getEntriesByType("measure")
236
+ measurePerfEntries.forEach((measurePerfEntry) => {
237
+ measures[measurePerfEntry.name] = measurePerfEntry.duration
238
+ })
239
+ return {
240
+ timeOrigin: performance.timeOrigin,
241
+ timing: performance.timing.toJSON(),
242
+ navigation: performance.navigation.toJSON(),
243
+ measures,
244
+ }
245
+ },
246
+ /* eslint-enable no-undef */
247
+ )
248
+ result.performance = performance
249
+ })
250
+ }
251
+
252
+ // https://github.com/GoogleChrome/puppeteer/blob/v1.4.0/docs/api.md#event-console
253
+ const removeConsoleListener = registerEvent({
254
+ object: page,
255
+ eventType: "console",
256
+ // https://github.com/microsoft/playwright/blob/master/docs/api.md#event-console
257
+ callback: async (consoleMessage) => {
258
+ onConsole({
259
+ type: consoleMessage.type(),
260
+ text: `${extractTextFromConsoleMessage(consoleMessage)}\n`,
261
+ })
262
+ },
263
+ })
264
+ cleanupCallbackList.add(removeConsoleListener)
265
+ const actionOperation = Abort.startOperation()
266
+ actionOperation.addAbortSignal(signal)
267
+
268
+ const winnerPromise = new Promise((resolve, reject) => {
269
+ raceCallbacks(
270
+ {
271
+ aborted: (cb) => {
272
+ return actionOperation.addAbortCallback(cb)
273
+ },
274
+ // https://github.com/GoogleChrome/puppeteer/blob/v1.4.0/docs/api.md#event-error
275
+ error: (cb) => {
276
+ return registerEvent({
277
+ object: page,
278
+ eventType: "error",
279
+ callback: (error) => {
280
+ if (shouldIgnoreError(error, "error")) {
281
+ return
282
+ }
283
+ cb(transformErrorHook(error))
284
+ },
285
+ })
286
+ },
287
+ // https://github.com/GoogleChrome/puppeteer/blob/v1.4.0/docs/api.md#event-pageerror
288
+ // pageerror: () => {
289
+ // return registerEvent({
290
+ // object: page,
291
+ // eventType: "pageerror",
292
+ // callback: (error) => {
293
+ // if (
294
+ // webServer.isJsenvDevServer ||
295
+ // shouldIgnoreError(error, "pageerror")
296
+ // ) {
297
+ // return
298
+ // }
299
+ // result.errors.push(transformErrorHook(error))
300
+ // },
301
+ // })
302
+ // },
303
+ closed: (cb) => {
304
+ // https://github.com/GoogleChrome/puppeteer/blob/v1.4.0/docs/api.md#event-disconnected
305
+ if (isBrowserDedicatedToExecution) {
306
+ browser.on("disconnected", async () => {
307
+ cb({ reason: "browser disconnected" })
308
+ })
309
+ cleanupCallbackList.add(closePage)
310
+ cleanupCallbackList.add(closeBrowser)
311
+ } else {
312
+ const disconnectedCallback = async () => {
313
+ throw new Error("browser disconnected during execution")
314
+ }
315
+ browser.on("disconnected", disconnectedCallback)
316
+ page.on("close", () => {
317
+ cb({ reason: "page closed" })
318
+ })
319
+ cleanupCallbackList.add(closePage)
320
+ cleanupCallbackList.add(() => {
321
+ browser.removeListener("disconnected", disconnectedCallback)
322
+ })
323
+ const notifyPrevious = stopAfterAllSignal.notify
324
+ stopAfterAllSignal.notify = async () => {
325
+ await notifyPrevious()
326
+ browser.removeListener("disconnected", disconnectedCallback)
327
+ logger.debug(
328
+ `stopAfterAllSignal notified -> closing ${browserName}`,
329
+ )
330
+ await closeBrowser()
331
+ }
332
+ }
333
+ },
334
+ response: async (cb) => {
335
+ try {
336
+ await page.goto(fileServerUrl, { timeout: 0 })
337
+ const returnValue = await page.evaluate(
338
+ /* eslint-disable no-undef */
339
+ /* istanbul ignore next */
340
+ async () => {
341
+ let startTime
342
+ try {
343
+ startTime = window.performance.timing.navigationStart
344
+ } catch (e) {
345
+ startTime = Date.now()
346
+ }
347
+ if (!window.__supervisor__) {
348
+ throw new Error("window.__supervisor__ is undefined")
349
+ }
350
+ const executionResultFromJsenvSupervisor =
351
+ await window.__supervisor__.getDocumentExecutionResult()
352
+ return {
353
+ type: "window_supervisor",
354
+ startTime,
355
+ endTime: Date.now(),
356
+ executionResults:
357
+ executionResultFromJsenvSupervisor.executionResults,
358
+ }
359
+ },
360
+ /* eslint-enable no-undef */
361
+ )
362
+ cb(returnValue)
363
+ } catch (e) {
364
+ reject(e)
365
+ }
366
+ },
367
+ },
368
+ resolve,
369
+ )
370
+ })
371
+
372
+ const writeResult = async () => {
373
+ const winner = await winnerPromise
374
+ if (winner.name === "aborted") {
375
+ result.status = "aborted"
376
+ return
377
+ }
378
+ if (winner.name === "error") {
379
+ let error = winner.data
380
+ result.status = "failed"
381
+ result.errors.push(error)
382
+ return
383
+ }
384
+ if (winner.name === "pageerror") {
385
+ let error = winner.data
386
+ result.status = "failed"
387
+ result.errors.push(error)
388
+ return
389
+ }
390
+ if (winner.name === "closed") {
391
+ result.status = "failed"
392
+ result.errors.push(
393
+ isBrowserDedicatedToExecution
394
+ ? new Error(`browser disconnected during execution`)
395
+ : new Error(`page closed during execution`),
396
+ )
397
+ return
398
+ }
399
+ // winner.name === "response"
400
+ const { executionResults } = winner.data
401
+ result.status = "completed"
402
+ result.namespace = executionResults
403
+ Object.keys(executionResults).forEach((key) => {
404
+ const executionResult = executionResults[key]
405
+ if (executionResult.status === "failed") {
406
+ result.status = "failed"
407
+ if (executionResult.exception) {
408
+ result.errors.push({
409
+ ...executionResult.exception,
410
+ stack: executionResult.exception.text,
411
+ })
412
+ } else {
413
+ result.errors.push({
414
+ ...executionResult.error,
415
+ stack: executionResult.error.stack,
416
+ })
417
+ }
418
+ }
419
+ })
420
+ }
421
+
422
+ try {
423
+ await writeResult()
424
+ if (collectPerformance) {
425
+ result.performance = performance
426
+ }
427
+ await callbacks.reduce(async (previous, callback) => {
428
+ await previous
429
+ await callback()
430
+ }, Promise.resolve())
431
+ } catch (e) {
432
+ result.status = "failed"
433
+ result.errors = [e]
434
+ }
435
+ if (keepRunning) {
436
+ stopSignal.notify = cleanup
437
+ } else {
438
+ await cleanup("execution done")
439
+ }
440
+ return result
441
+ }
442
+ return runtime
443
+ }
444
+
445
+ const generateCoverageForPage = (scriptExecutionResults) => {
446
+ let istanbulCoverageComposed = null
447
+ Object.keys(scriptExecutionResults).forEach((fileRelativeUrl) => {
448
+ const istanbulCoverage = scriptExecutionResults[fileRelativeUrl].coverage
449
+ istanbulCoverageComposed = istanbulCoverageComposed
450
+ ? composeTwoFileByFileIstanbulCoverages(
451
+ istanbulCoverageComposed,
452
+ istanbulCoverage,
453
+ )
454
+ : istanbulCoverage
455
+ })
456
+ return istanbulCoverageComposed
457
+ }
458
+
459
+ const launchBrowserUsingPlaywright = async ({
460
+ signal,
461
+ browserName,
462
+ stopOnExit,
463
+ playwrightLaunchOptions,
464
+ }) => {
465
+ const launchBrowserOperation = Abort.startOperation()
466
+ launchBrowserOperation.addAbortSignal(signal)
467
+ const playwright = await importPlaywright({ browserName })
468
+ if (stopOnExit) {
469
+ launchBrowserOperation.addAbortSource((abort) => {
470
+ return raceProcessTeardownEvents(
471
+ {
472
+ SIGHUP: true,
473
+ SIGTERM: true,
474
+ SIGINT: true,
475
+ beforeExit: true,
476
+ exit: true,
477
+ },
478
+ abort,
479
+ )
480
+ })
481
+ }
482
+ const browserClass = playwright[browserName]
483
+ try {
484
+ const browser = await browserClass.launch({
485
+ ...playwrightLaunchOptions,
486
+ // let's handle them to close properly browser + remove listener
487
+ // instead of relying on playwright to do so
488
+ handleSIGINT: false,
489
+ handleSIGTERM: false,
490
+ handleSIGHUP: false,
491
+ })
492
+ launchBrowserOperation.throwIfAborted()
493
+ return browser
494
+ } catch (e) {
495
+ if (launchBrowserOperation.signal.aborted && isTargetClosedError(e)) {
496
+ // rethrow the abort error
497
+ launchBrowserOperation.throwIfAborted()
498
+ }
499
+ throw e
500
+ } finally {
501
+ await launchBrowserOperation.end()
502
+ }
503
+ }
504
+
505
+ const importPlaywright = async ({ browserName }) => {
506
+ try {
507
+ const namespace = await import("playwright")
508
+ return namespace
509
+ } catch (e) {
510
+ if (e.code === "ERR_MODULE_NOT_FOUND") {
511
+ throw new Error(
512
+ createDetailedMessage(
513
+ `"playwright" not found. You need playwright in your dependencies to use "${browserName}"`,
514
+ {
515
+ suggestion: `npm install --save-dev playwright`,
516
+ },
517
+ ),
518
+ { cause: e },
519
+ )
520
+ }
521
+ throw e
522
+ }
523
+ }
524
+
525
+ const isTargetClosedError = (error) => {
526
+ if (error.message.match(/Protocol error \(.*?\): Target closed/)) {
527
+ return true
528
+ }
529
+ if (error.message.match(/Protocol error \(.*?\): Browser.*?closed/)) {
530
+ return true
531
+ }
532
+ return error.message.includes("browserContext.close: Browser closed")
533
+ }
534
+
535
+ const extractTextFromConsoleMessage = (consoleMessage) => {
536
+ return consoleMessage.text()
537
+ // ensure we use a string so that istanbul won't try
538
+ // to put any coverage statement inside it
539
+ // ideally we should use uneval no ?
540
+ // eslint-disable-next-line no-new-func
541
+ // const functionEvaluatedBrowserSide = new Function(
542
+ // "value",
543
+ // `if (value instanceof Error) {
544
+ // return value.stack
545
+ // }
546
+ // return value`,
547
+ // )
548
+ // const argValues = await Promise.all(
549
+ // message.args().map(async (arg) => {
550
+ // const jsHandle = arg
551
+ // try {
552
+ // return await jsHandle.executionContext().evaluate(functionEvaluatedBrowserSide, jsHandle)
553
+ // } catch (e) {
554
+ // return String(jsHandle)
555
+ // }
556
+ // }),
557
+ // )
558
+ // const text = argValues.reduce((previous, value, index) => {
559
+ // let string
560
+ // if (typeof value === "object") string = JSON.stringify(value, null, " ")
561
+ // else string = String(value)
562
+ // if (index === 0) return `${previous}${string}`
563
+ // return `${previous} ${string}`
564
+ // }, "")
565
+ // return text
566
+ }
567
+
568
+ const registerEvent = ({ object, eventType, callback }) => {
569
+ object.on(eventType, callback)
570
+ return () => {
571
+ object.removeListener(eventType, callback)
572
+ }
573
+ }
@@ -0,0 +1,40 @@
1
+ import { createRuntimeUsingPlaywright } from "./using_playwright.js"
2
+
3
+ const webkitParams = {
4
+ browserName: "webkit",
5
+ // browserVersion will be set by "browser._initializer.version"
6
+ // see also https://github.com/microsoft/playwright/releases
7
+ browserVersion: "unset",
8
+ shouldIgnoreError: (error) => {
9
+ // we catch error during execution but safari throw unhandled rejection
10
+ // in a non-deterministic way.
11
+ // I suppose it's due to some race condition to decide if the promise is catched or not
12
+ // for now we'll ignore unhandled rejection on wekbkit
13
+ if (error.name === "Unhandled Promise Rejection") {
14
+ return true
15
+ }
16
+ return false
17
+ },
18
+ transformErrorHook: (error) => {
19
+ // Force error stack to contain the error message
20
+ // because it's not the case on webkit
21
+ error.stack = `${error.message}
22
+ at ${error.stack}`
23
+ return error
24
+ },
25
+ }
26
+
27
+ export const webkit = (params) => {
28
+ return createRuntimeUsingPlaywright({
29
+ ...webkitParams,
30
+ ...params,
31
+ })
32
+ }
33
+
34
+ export const webkitIsolatedTab = (params) => {
35
+ return createRuntimeUsingPlaywright({
36
+ ...webkitParams,
37
+ isolatedTab: true,
38
+ ...params,
39
+ })
40
+ }