@noego/wood 0.4.1 → 0.4.3

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 (81) hide show
  1. package/README.md +100 -2
  2. package/dist/client/global_polyfill.d.cts +1 -2
  3. package/dist/client/global_polyfill.d.ts +1 -2
  4. package/dist/client/index.cjs.map +1 -1
  5. package/dist/client/index.d.cts +1 -1
  6. package/dist/client/index.d.ts +1 -1
  7. package/dist/client/index.js.map +1 -1
  8. package/dist/index.cjs +0 -4
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.js +0 -4
  13. package/dist/index.js.map +1 -1
  14. package/dist/testing/browser.cjs +253 -36
  15. package/dist/testing/browser.cjs.map +1 -1
  16. package/dist/testing/browser.d.cts +6 -2
  17. package/dist/testing/browser.d.ts +6 -2
  18. package/dist/testing/browser.js +253 -37
  19. package/dist/testing/browser.js.map +1 -1
  20. package/dist/testing/create_controller_harness.cjs +3 -1
  21. package/dist/testing/create_controller_harness.cjs.map +1 -1
  22. package/dist/testing/create_controller_harness.d.cts +4 -2
  23. package/dist/testing/create_controller_harness.d.ts +4 -2
  24. package/dist/testing/create_controller_harness.js +3 -1
  25. package/dist/testing/create_controller_harness.js.map +1 -1
  26. package/dist/testing/index.cjs +3 -3
  27. package/dist/testing/index.cjs.map +1 -1
  28. package/dist/testing/index.d.cts +5 -3
  29. package/dist/testing/index.d.ts +5 -3
  30. package/dist/testing/index.js +2 -2
  31. package/dist/testing/index.js.map +1 -1
  32. package/dist/trace-testing/index.cjs +3 -3
  33. package/dist/trace-testing/index.cjs.map +1 -1
  34. package/dist/trace-testing/index.d.cts +2 -3
  35. package/dist/trace-testing/index.d.ts +2 -3
  36. package/dist/trace-testing/index.js +3 -3
  37. package/dist/trace-testing/index.js.map +1 -1
  38. package/dist/trace-testing/trace_assertions.cjs +2 -46
  39. package/dist/trace-testing/trace_assertions.cjs.map +1 -1
  40. package/dist/trace-testing/trace_assertions.d.cts +1 -15
  41. package/dist/trace-testing/trace_assertions.d.ts +1 -15
  42. package/dist/trace-testing/trace_assertions.js +3 -45
  43. package/dist/trace-testing/trace_assertions.js.map +1 -1
  44. package/dist/trace-testing/trace_query.cjs +4 -115
  45. package/dist/trace-testing/trace_query.cjs.map +1 -1
  46. package/dist/trace-testing/trace_query.d.cts +1 -24
  47. package/dist/trace-testing/trace_query.d.ts +1 -24
  48. package/dist/trace-testing/trace_query.js +5 -112
  49. package/dist/trace-testing/trace_query.js.map +1 -1
  50. package/dist/trace-testing/trace_recorder.cjs +50 -0
  51. package/dist/trace-testing/trace_recorder.cjs.map +1 -0
  52. package/dist/trace-testing/trace_recorder.d.cts +12 -0
  53. package/dist/trace-testing/trace_recorder.d.ts +12 -0
  54. package/dist/trace-testing/trace_recorder.js +32 -0
  55. package/dist/trace-testing/trace_recorder.js.map +1 -0
  56. package/dist/tracing/index.cjs +0 -4
  57. package/dist/tracing/index.cjs.map +1 -1
  58. package/dist/tracing/index.d.cts +1 -1
  59. package/dist/tracing/index.d.ts +1 -1
  60. package/dist/tracing/index.js +1 -5
  61. package/dist/tracing/index.js.map +1 -1
  62. package/dist/tracing/trace_hub.cjs +0 -84
  63. package/dist/tracing/trace_hub.cjs.map +1 -1
  64. package/dist/tracing/trace_hub.d.cts +1 -16
  65. package/dist/tracing/trace_hub.d.ts +1 -16
  66. package/dist/tracing/trace_hub.js +0 -72
  67. package/dist/tracing/trace_hub.js.map +1 -1
  68. package/dist/tracing/trace_provider.cjs +0 -6
  69. package/dist/tracing/trace_provider.cjs.map +1 -1
  70. package/dist/tracing/trace_provider.d.cts +1 -3
  71. package/dist/tracing/trace_provider.d.ts +1 -3
  72. package/dist/tracing/trace_provider.js +0 -7
  73. package/dist/tracing/trace_provider.js.map +1 -1
  74. package/docs/browser-testing.md +84 -0
  75. package/package.json +2 -1
  76. package/dist/trace-testing/trace_scenario.cjs +0 -61
  77. package/dist/trace-testing/trace_scenario.cjs.map +0 -1
  78. package/dist/trace-testing/trace_scenario.d.cts +0 -27
  79. package/dist/trace-testing/trace_scenario.d.ts +0 -27
  80. package/dist/trace-testing/trace_scenario.js +0 -37
  81. package/dist/trace-testing/trace_scenario.js.map +0 -1
@@ -33,14 +33,31 @@ __export(browser_exports, {
33
33
  });
34
34
  module.exports = __toCommonJS(browser_exports);
35
35
  var import_node_fs = __toESM(require("node:fs"), 1);
36
+ var import_node_os = __toESM(require("node:os"), 1);
36
37
  var import_node_module = require("node:module");
37
38
  var import_node_path = __toESM(require("node:path"), 1);
38
39
  var import_node_url = require("node:url");
39
40
  var import_tracing = require("../tracing/index.js");
41
+ var import_trace_testing = require("../trace-testing/index.js");
40
42
  const DEFAULT_VIEWPORT = {
41
43
  width: 1280,
42
44
  height: 800
43
45
  };
46
+ const DEFAULT_EXPECT_TIMEOUT_MS = 1e4;
47
+ const DEFAULT_RESOURCE_CLOSE_TIMEOUT_MS = 5e3;
48
+ const WORKER_RUNTIME_IDLE_CLOSE_MS = 5e3;
49
+ const browserWorkerRuntimes = /* @__PURE__ */ new Map();
50
+ const browserWorkerCacheDirs = /* @__PURE__ */ new Set();
51
+ process.on("exit", () => {
52
+ for (const cacheDir of browserWorkerCacheDirs) {
53
+ try {
54
+ import_node_fs.default.rmSync(cacheDir, { recursive: true, force: true });
55
+ } catch {
56
+ }
57
+ }
58
+ browserWorkerCacheDirs.clear();
59
+ browserWorkerRuntimes.clear();
60
+ });
44
61
  function normalizeRoot(rootDir) {
45
62
  return import_node_path.default.resolve(rootDir ?? process.cwd());
46
63
  }
@@ -63,6 +80,18 @@ function mergeBridge(base, override) {
63
80
  record: override?.record ?? base?.record
64
81
  };
65
82
  }
83
+ async function waitForBoundedClose(promise, timeoutMs = DEFAULT_RESOURCE_CLOSE_TIMEOUT_MS) {
84
+ let timeout;
85
+ await Promise.race([
86
+ promise.catch(() => void 0),
87
+ new Promise((resolve) => {
88
+ timeout = setTimeout(resolve, timeoutMs);
89
+ })
90
+ ]);
91
+ if (timeout) {
92
+ clearTimeout(timeout);
93
+ }
94
+ }
66
95
  function channelToManifestEntry(channel, handler) {
67
96
  const pathSegments = channel.split(".").filter(Boolean);
68
97
  const action = pathSegments.length > 0 ? pathSegments[pathSegments.length - 1] : channel;
@@ -164,6 +193,51 @@ async function loadPlaywright(browserName) {
164
193
  async function loadVite() {
165
194
  return await import("vite");
166
195
  }
196
+ function createPackageRequire(rootDir) {
197
+ return (0, import_node_module.createRequire)(import_node_path.default.join(rootDir, "package.json"));
198
+ }
199
+ function resolvePackageModule(rootDir, source) {
200
+ const rootRequire = createPackageRequire(rootDir);
201
+ const processRequire = (0, import_node_module.createRequire)(import_node_path.default.join(process.cwd(), "package.json"));
202
+ try {
203
+ return rootRequire.resolve(source);
204
+ } catch {
205
+ try {
206
+ return processRequire.resolve(source);
207
+ } catch {
208
+ return null;
209
+ }
210
+ }
211
+ }
212
+ function resolvePackageDir(rootDir, packageName) {
213
+ const packageJson = resolvePackageModule(rootDir, `${packageName}/package.json`);
214
+ return packageJson ? import_node_path.default.dirname(packageJson) : null;
215
+ }
216
+ function createWoodBrowserResolver(rootDir) {
217
+ const sveltePackageDir = resolvePackageDir(rootDir, "svelte");
218
+ const svelteClientEntry = sveltePackageDir ? import_node_path.default.join(sveltePackageDir, "src", "index-client.js") : null;
219
+ const svelteLegacyClientEntry = sveltePackageDir ? import_node_path.default.join(sveltePackageDir, "src", "legacy", "legacy-client.js") : null;
220
+ const svelteReactivityClientEntry = sveltePackageDir ? import_node_path.default.join(sveltePackageDir, "src", "reactivity", "index-client.js") : null;
221
+ return {
222
+ name: "wood-browser-resolver",
223
+ enforce: "pre",
224
+ resolveId(source) {
225
+ if (source === "svelte") {
226
+ return svelteClientEntry;
227
+ }
228
+ if (source === "svelte/legacy") {
229
+ return svelteLegacyClientEntry;
230
+ }
231
+ if (source === "svelte/reactivity") {
232
+ return svelteReactivityClientEntry;
233
+ }
234
+ if (source.startsWith("svelte/")) {
235
+ return resolvePackageModule(rootDir, source);
236
+ }
237
+ return null;
238
+ }
239
+ };
240
+ }
167
241
  async function loadSveltePlugin(rootDir) {
168
242
  try {
169
243
  const rootPluginPath = import_node_path.default.join(
@@ -194,22 +268,115 @@ async function loadViews(viewsPath) {
194
268
  const parser = await import("../parser/views_parser.cjs");
195
269
  return parser.parseViewsFile(viewsPath);
196
270
  }
197
- async function createViteServer(rootDir) {
271
+ async function createViteServer(rootDir, cacheDir) {
198
272
  const vite = await loadVite();
199
273
  const svelte = await loadSveltePlugin(rootDir);
200
- const plugins = svelte ? [svelte()] : [];
274
+ const plugins = [
275
+ createWoodBrowserResolver(rootDir),
276
+ ...svelte ? [svelte()] : []
277
+ ];
201
278
  const server = await vite.createServer({
202
279
  root: rootDir,
280
+ cacheDir,
203
281
  logLevel: process.env.WOOD_BROWSER_TEST_DEBUG ? "info" : "silent",
204
282
  plugins,
205
283
  resolve: {
206
284
  dedupe: ["svelte"]
207
285
  },
208
- server: { port: 0 }
286
+ server: { port: 0, fs: { allow: [rootDir, import_node_os.default.tmpdir()] } }
209
287
  });
210
288
  await server.listen();
211
289
  return server;
212
290
  }
291
+ function workerRuntimeKey(input) {
292
+ return [
293
+ input.rootDir,
294
+ input.browserName,
295
+ input.headless ? "headless" : "headed"
296
+ ].join("::");
297
+ }
298
+ async function createBrowserWorkerRuntime(input) {
299
+ const cacheRoot = import_node_fs.default.mkdtempSync(import_node_path.default.join(import_node_os.default.tmpdir(), "wood-browser-vite-"));
300
+ browserWorkerCacheDirs.add(cacheRoot);
301
+ let browser2 = null;
302
+ try {
303
+ const playwrightBrowser = await loadPlaywright(input.browserName);
304
+ browser2 = await playwrightBrowser.launch({ headless: input.headless });
305
+ const server = await createViteServer(input.rootDir, import_node_path.default.join(cacheRoot, ".vite"));
306
+ return {
307
+ browser: browser2,
308
+ server,
309
+ cacheDir: cacheRoot,
310
+ rootDir: input.rootDir,
311
+ key: input.key,
312
+ activeHarnesses: 0,
313
+ closing: false,
314
+ idleCloseTimer: null
315
+ };
316
+ } catch (error) {
317
+ await browser2?.close().catch(() => void 0);
318
+ import_node_fs.default.rmSync(cacheRoot, { recursive: true, force: true });
319
+ browserWorkerCacheDirs.delete(cacheRoot);
320
+ throw error;
321
+ }
322
+ }
323
+ async function acquireBrowserWorkerRuntime(input) {
324
+ const key = workerRuntimeKey(input);
325
+ let entry = browserWorkerRuntimes.get(key);
326
+ if (!entry) {
327
+ entry = {
328
+ promise: createBrowserWorkerRuntime({ ...input, key })
329
+ };
330
+ browserWorkerRuntimes.set(key, entry);
331
+ }
332
+ const runtime = await entry.promise;
333
+ if (runtime.closing) {
334
+ browserWorkerRuntimes.delete(key);
335
+ return await acquireBrowserWorkerRuntime(input);
336
+ }
337
+ if (runtime.idleCloseTimer) {
338
+ clearTimeout(runtime.idleCloseTimer);
339
+ runtime.idleCloseTimer = null;
340
+ }
341
+ runtime.activeHarnesses += 1;
342
+ return runtime;
343
+ }
344
+ async function closeBrowserWorkerRuntime(runtime) {
345
+ if (runtime.closing) {
346
+ return;
347
+ }
348
+ runtime.closing = true;
349
+ try {
350
+ await Promise.all([
351
+ waitForBoundedClose(runtime.server.close()),
352
+ waitForBoundedClose(runtime.browser.close())
353
+ ]);
354
+ } finally {
355
+ import_node_fs.default.rmSync(runtime.cacheDir, { recursive: true, force: true });
356
+ browserWorkerCacheDirs.delete(runtime.cacheDir);
357
+ const entry = browserWorkerRuntimes.get(runtime.key);
358
+ const current = entry ? await entry.promise.catch(() => null) : null;
359
+ if (current === runtime) {
360
+ browserWorkerRuntimes.delete(runtime.key);
361
+ }
362
+ }
363
+ }
364
+ function scheduleBrowserWorkerRuntimeClose(runtime) {
365
+ if (runtime.closing || runtime.activeHarnesses > 0 || runtime.idleCloseTimer) {
366
+ return;
367
+ }
368
+ runtime.idleCloseTimer = setTimeout(() => {
369
+ runtime.idleCloseTimer = null;
370
+ if (runtime.activeHarnesses === 0) {
371
+ void closeBrowserWorkerRuntime(runtime);
372
+ }
373
+ }, WORKER_RUNTIME_IDLE_CLOSE_MS);
374
+ runtime.idleCloseTimer.unref?.();
375
+ }
376
+ function releaseBrowserWorkerRuntime(runtime) {
377
+ runtime.activeHarnesses = Math.max(0, runtime.activeHarnesses - 1);
378
+ scheduleBrowserWorkerRuntimeClose(runtime);
379
+ }
213
380
  function serverBaseUrl(server) {
214
381
  return server.resolvedUrls?.local?.[0] ?? "http://127.0.0.1:5173";
215
382
  }
@@ -416,8 +583,8 @@ async function installBridge(page, bridgeState, manifest, bridgeInit) {
416
583
  window.__WOOD_BROWSER_BRIDGE_MANIFEST__ = entries;
417
584
  }, manifest);
418
585
  }
419
- async function installTraceBinding(page, trace, context) {
420
- if (!trace) {
586
+ async function installTraceBinding(page, traceRecorder, context) {
587
+ if (!traceRecorder) {
421
588
  return;
422
589
  }
423
590
  await page.exposeBinding("__woodBrowserTrace", async (_source, payload) => {
@@ -500,20 +667,35 @@ async function elementRect(page, selector) {
500
667
  }).catch(() => null);
501
668
  }
502
669
  function createExpect(page, outputDir) {
670
+ async function waitForVisible(selector) {
671
+ const locator = page.locator(selector).first();
672
+ try {
673
+ await locator.waitFor({ state: "attached", timeout: DEFAULT_EXPECT_TIMEOUT_MS });
674
+ } catch {
675
+ throw new Error(`Expected "${selector}" to exist.`);
676
+ }
677
+ try {
678
+ await locator.waitFor({ state: "visible", timeout: DEFAULT_EXPECT_TIMEOUT_MS });
679
+ } catch {
680
+ throw new Error(`Expected "${selector}" to be visible.`);
681
+ }
682
+ }
503
683
  return {
504
684
  async visible(selector) {
505
- const count = await page.locator(selector).count();
506
- if (count < 1) throw new Error(`Expected "${selector}" to exist.`);
507
- const visible = await page.locator(selector).first().isVisible();
508
- if (!visible) throw new Error(`Expected "${selector}" to be visible.`);
685
+ await waitForVisible(selector);
509
686
  },
510
687
  async hidden(selector) {
511
- const count = await page.locator(selector).count();
512
- if (count < 1) return;
513
- const visible = await page.locator(selector).first().isVisible();
514
- if (visible) throw new Error(`Expected "${selector}" to be hidden.`);
688
+ try {
689
+ await page.locator(selector).first().waitFor({
690
+ state: "hidden",
691
+ timeout: DEFAULT_EXPECT_TIMEOUT_MS
692
+ });
693
+ } catch {
694
+ throw new Error(`Expected "${selector}" to be hidden.`);
695
+ }
515
696
  },
516
697
  async insideViewport(selector, options = {}) {
698
+ await waitForVisible(selector);
517
699
  const margin = options.margin ?? 0;
518
700
  const rect = await elementRect(page, selector);
519
701
  if (!rect) throw new Error(`Expected "${selector}" to exist.`);
@@ -524,6 +706,7 @@ function createExpect(page, outputDir) {
524
706
  }
525
707
  },
526
708
  async notClipped(selector) {
709
+ await waitForVisible(selector);
527
710
  const result = await page.locator(selector).first().evaluate((element) => {
528
711
  const rect = element.getBoundingClientRect();
529
712
  let parent = element.parentElement;
@@ -545,6 +728,8 @@ function createExpect(page, outputDir) {
545
728
  }
546
729
  },
547
730
  async notOverlapping(firstSelector, secondSelector) {
731
+ await waitForVisible(firstSelector);
732
+ await waitForVisible(secondSelector);
548
733
  const first = await elementRect(page, firstSelector);
549
734
  const second = await elementRect(page, secondSelector);
550
735
  if (!first || !second) throw new Error(`Expected both "${firstSelector}" and "${secondSelector}" to exist.`);
@@ -554,6 +739,8 @@ function createExpect(page, outputDir) {
554
739
  }
555
740
  },
556
741
  async zIndexAbove(topSelector, lowerSelector) {
742
+ await waitForVisible(topSelector);
743
+ await waitForVisible(lowerSelector);
557
744
  const values = await page.evaluate((payload) => {
558
745
  const { topSelector: top, lowerSelector: lower } = payload;
559
746
  const topElement = document.querySelector(top);
@@ -627,7 +814,7 @@ async function openMountPage(input) {
627
814
  await page.setViewportSize({ width: viewport.width, height: viewport.height });
628
815
  const entryPath = writeEntry(input.runtime.tempDir, input.name, input.entrySource);
629
816
  const htmlPath = writeHtml(input.runtime.tempDir, input.name, entryPath, input.css);
630
- await installTraceBinding(input.runtime.page, input.trace, input.traceContext ?? {});
817
+ await installTraceBinding(input.runtime.page, input.traceRecorder, input.traceContext ?? {});
631
818
  await installBridge(page, input.bridge.state, input.bridge.manifest, input.bridge.bridgeInit);
632
819
  const mountedBridge = createMountedBridge(page, input.bridge.state);
633
820
  const context = {
@@ -660,23 +847,47 @@ async function openMountPage(input) {
660
847
  async function createHarness(config = {}) {
661
848
  const rootDir = normalizeRoot(config.rootDir);
662
849
  const browserName = config.browser?.browserName ?? "chromium";
663
- const playwrightBrowser = await loadPlaywright(browserName);
664
- const launchedBrowser = await playwrightBrowser.launch({
665
- headless: config.browser?.headless ?? true
850
+ const headless = config.browser?.headless ?? true;
851
+ const workerRuntime = await acquireBrowserWorkerRuntime({
852
+ rootDir,
853
+ browserName,
854
+ headless
666
855
  });
667
- const server = await createViteServer(rootDir);
668
- const page = await launchedBrowser.newPage({
669
- viewport: {
670
- width: config.viewport?.width ?? DEFAULT_VIEWPORT.width,
671
- height: config.viewport?.height ?? DEFAULT_VIEWPORT.height,
672
- deviceScaleFactor: config.viewport?.deviceScaleFactor
856
+ let released = false;
857
+ let destroyed = false;
858
+ let tempDir = null;
859
+ let context = null;
860
+ let page = null;
861
+ let traceRecorder;
862
+ function releaseRuntimeOnce() {
863
+ if (released) {
864
+ return;
673
865
  }
674
- });
675
- const tempDir = import_node_fs.default.mkdtempSync(import_node_path.default.join(rootDir, ".wood-browser-harness-"));
676
- const trace = config.trace?.enabled === true ? (0, import_tracing.createTraceCapture)({ maxEvents: config.trace.maxEvents }) : void 0;
866
+ released = true;
867
+ releaseBrowserWorkerRuntime(workerRuntime);
868
+ }
869
+ try {
870
+ tempDir = import_node_fs.default.mkdtempSync(import_node_path.default.join(import_node_os.default.tmpdir(), "wood-browser-harness-"));
871
+ context = await workerRuntime.browser.newContext({
872
+ viewport: {
873
+ width: config.viewport?.width ?? DEFAULT_VIEWPORT.width,
874
+ height: config.viewport?.height ?? DEFAULT_VIEWPORT.height,
875
+ deviceScaleFactor: config.viewport?.deviceScaleFactor
876
+ }
877
+ });
878
+ page = await context.newPage();
879
+ traceRecorder = config.trace?.enabled === true ? new import_trace_testing.TraceRecorder() : void 0;
880
+ traceRecorder?.enable({ maxEvents: config.trace?.maxEvents });
881
+ } catch (error) {
882
+ if (page) await waitForBoundedClose(page.close());
883
+ if (context) await waitForBoundedClose(context.close());
884
+ if (tempDir) import_node_fs.default.rmSync(tempDir, { recursive: true, force: true });
885
+ releaseRuntimeOnce();
886
+ throw error;
887
+ }
677
888
  const runtime = {
678
- browser: launchedBrowser,
679
- server,
889
+ context,
890
+ server: workerRuntime.server,
680
891
  page,
681
892
  tempDir,
682
893
  rootDir
@@ -688,7 +899,7 @@ async function createHarness(config = {}) {
688
899
  });
689
900
  return {
690
901
  page,
691
- ...trace ? { trace } : {},
902
+ ...traceRecorder ? { traceRecorder } : {},
692
903
  async mountRoute(routeKey, options = {}) {
693
904
  const bridge = createBrowserBridgeFixtureWithTrace(mergeBridge(config.bridge, options.bridge), config.trace);
694
905
  const route = {
@@ -721,7 +932,7 @@ async function createHarness(config = {}) {
721
932
  css,
722
933
  viewport: options.viewport ?? config.viewport,
723
934
  bridge,
724
- trace,
935
+ traceRecorder,
725
936
  traceContext: {
726
937
  windowId: config.trace?.windowId ?? routeKey.split(".")[0],
727
938
  routeKey,
@@ -758,7 +969,7 @@ async function createHarness(config = {}) {
758
969
  css,
759
970
  viewport: options.viewport ?? config.viewport,
760
971
  bridge,
761
- trace,
972
+ traceRecorder,
762
973
  traceContext: {
763
974
  windowId: config.trace?.windowId ?? "wood-browser-harness",
764
975
  harnessId: config.trace?.harnessId
@@ -791,7 +1002,7 @@ async function createHarness(config = {}) {
791
1002
  css,
792
1003
  viewport: tree.viewport ?? config.viewport,
793
1004
  bridge,
794
- trace,
1005
+ traceRecorder,
795
1006
  traceContext: {
796
1007
  windowId: config.trace?.windowId ?? tree.page?.key?.split(".")[0] ?? "wood-browser-harness",
797
1008
  routeKey: tree.page?.key,
@@ -808,11 +1019,17 @@ async function createHarness(config = {}) {
808
1019
  });
809
1020
  },
810
1021
  async destroy() {
811
- await page.close().catch(() => void 0);
812
- await server.close().catch(() => void 0);
813
- await launchedBrowser.close().catch(() => void 0);
814
- trace?.stop();
1022
+ if (destroyed) {
1023
+ return;
1024
+ }
1025
+ destroyed = true;
1026
+ await Promise.all([
1027
+ waitForBoundedClose(page.close()),
1028
+ waitForBoundedClose(context.close())
1029
+ ]);
1030
+ traceRecorder?.disable();
815
1031
  import_node_fs.default.rmSync(tempDir, { recursive: true, force: true });
1032
+ releaseRuntimeOnce();
816
1033
  }
817
1034
  };
818
1035
  }