@noego/wood 0.4.0 → 0.4.2

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 (85) 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/main/index.cjs +16 -2
  15. package/dist/main/index.cjs.map +1 -1
  16. package/dist/main/index.js +16 -2
  17. package/dist/main/index.js.map +1 -1
  18. package/dist/runtime/runtime.cjs +4 -4
  19. package/dist/runtime/runtime.cjs.map +1 -1
  20. package/dist/runtime/runtime.d.cts +2 -1
  21. package/dist/runtime/runtime.d.ts +2 -1
  22. package/dist/runtime/runtime.js +8 -5
  23. package/dist/runtime/runtime.js.map +1 -1
  24. package/dist/testing/browser.cjs +224 -32
  25. package/dist/testing/browser.cjs.map +1 -1
  26. package/dist/testing/browser.d.cts +18 -1
  27. package/dist/testing/browser.d.ts +18 -1
  28. package/dist/testing/browser.js +226 -32
  29. package/dist/testing/browser.js.map +1 -1
  30. package/dist/testing/create_controller_harness.cjs +3 -1
  31. package/dist/testing/create_controller_harness.cjs.map +1 -1
  32. package/dist/testing/create_controller_harness.d.cts +4 -2
  33. package/dist/testing/create_controller_harness.d.ts +4 -2
  34. package/dist/testing/create_controller_harness.js +3 -1
  35. package/dist/testing/create_controller_harness.js.map +1 -1
  36. package/dist/testing/index.cjs +3 -3
  37. package/dist/testing/index.cjs.map +1 -1
  38. package/dist/testing/index.d.cts +6 -4
  39. package/dist/testing/index.d.ts +6 -4
  40. package/dist/testing/index.js +2 -2
  41. package/dist/testing/index.js.map +1 -1
  42. package/dist/trace-testing/index.cjs +39 -0
  43. package/dist/trace-testing/index.cjs.map +1 -0
  44. package/dist/trace-testing/index.d.cts +6 -0
  45. package/dist/trace-testing/index.d.ts +6 -0
  46. package/dist/trace-testing/index.js +19 -0
  47. package/dist/trace-testing/index.js.map +1 -0
  48. package/dist/trace-testing/trace_assertions.cjs +29 -0
  49. package/dist/trace-testing/trace_assertions.cjs.map +1 -0
  50. package/dist/trace-testing/trace_assertions.d.cts +1 -0
  51. package/dist/trace-testing/trace_assertions.d.ts +1 -0
  52. package/dist/trace-testing/trace_assertions.js +7 -0
  53. package/dist/trace-testing/trace_assertions.js.map +1 -0
  54. package/dist/trace-testing/trace_query.cjs +33 -0
  55. package/dist/trace-testing/trace_query.cjs.map +1 -0
  56. package/dist/trace-testing/trace_query.d.cts +1 -0
  57. package/dist/trace-testing/trace_query.d.ts +1 -0
  58. package/dist/trace-testing/trace_query.js +11 -0
  59. package/dist/trace-testing/trace_query.js.map +1 -0
  60. package/dist/trace-testing/trace_recorder.cjs +50 -0
  61. package/dist/trace-testing/trace_recorder.cjs.map +1 -0
  62. package/dist/trace-testing/trace_recorder.d.cts +12 -0
  63. package/dist/trace-testing/trace_recorder.d.ts +12 -0
  64. package/dist/trace-testing/trace_recorder.js +32 -0
  65. package/dist/trace-testing/trace_recorder.js.map +1 -0
  66. package/dist/tracing/index.cjs +0 -4
  67. package/dist/tracing/index.cjs.map +1 -1
  68. package/dist/tracing/index.d.cts +1 -1
  69. package/dist/tracing/index.d.ts +1 -1
  70. package/dist/tracing/index.js +1 -5
  71. package/dist/tracing/index.js.map +1 -1
  72. package/dist/tracing/trace_hub.cjs +19 -86
  73. package/dist/tracing/trace_hub.cjs.map +1 -1
  74. package/dist/tracing/trace_hub.d.cts +11 -16
  75. package/dist/tracing/trace_hub.d.ts +11 -16
  76. package/dist/tracing/trace_hub.js +19 -74
  77. package/dist/tracing/trace_hub.js.map +1 -1
  78. package/dist/tracing/trace_provider.cjs +0 -6
  79. package/dist/tracing/trace_provider.cjs.map +1 -1
  80. package/dist/tracing/trace_provider.d.cts +1 -3
  81. package/dist/tracing/trace_provider.d.ts +1 -3
  82. package/dist/tracing/trace_provider.js +0 -7
  83. package/dist/tracing/trace_provider.js.map +1 -1
  84. package/docs/browser-testing.md +84 -0
  85. package/package.json +8 -1
@@ -36,10 +36,16 @@ var import_node_fs = __toESM(require("node:fs"), 1);
36
36
  var import_node_module = require("node:module");
37
37
  var import_node_path = __toESM(require("node:path"), 1);
38
38
  var import_node_url = require("node:url");
39
+ var import_tracing = require("../tracing/index.js");
40
+ var import_trace_testing = require("../trace-testing/index.js");
39
41
  const DEFAULT_VIEWPORT = {
40
42
  width: 1280,
41
43
  height: 800
42
44
  };
45
+ const DEFAULT_EXPECT_TIMEOUT_MS = 1e4;
46
+ const DEFAULT_RESOURCE_CLOSE_TIMEOUT_MS = 5e3;
47
+ const WORKER_RUNTIME_IDLE_CLOSE_MS = 5e3;
48
+ const browserWorkerRuntimes = /* @__PURE__ */ new Map();
43
49
  function normalizeRoot(rootDir) {
44
50
  return import_node_path.default.resolve(rootDir ?? process.cwd());
45
51
  }
@@ -62,6 +68,18 @@ function mergeBridge(base, override) {
62
68
  record: override?.record ?? base?.record
63
69
  };
64
70
  }
71
+ async function waitForBoundedClose(promise, timeoutMs = DEFAULT_RESOURCE_CLOSE_TIMEOUT_MS) {
72
+ let timeout;
73
+ await Promise.race([
74
+ promise.catch(() => void 0),
75
+ new Promise((resolve) => {
76
+ timeout = setTimeout(resolve, timeoutMs);
77
+ })
78
+ ]);
79
+ if (timeout) {
80
+ clearTimeout(timeout);
81
+ }
82
+ }
65
83
  function channelToManifestEntry(channel, handler) {
66
84
  const pathSegments = channel.split(".").filter(Boolean);
67
85
  const action = pathSegments.length > 0 ? pathSegments[pathSegments.length - 1] : channel;
@@ -92,24 +110,32 @@ function createBridgeState(input) {
92
110
  };
93
111
  }
94
112
  function createBrowserBridgeFixture(input) {
113
+ return createBrowserBridgeFixtureWithTrace(input, void 0);
114
+ }
115
+ function createBrowserBridgeFixtureWithTrace(input, traceConfig) {
95
116
  const state = createBridgeState(input);
96
117
  const manifest = Array.from(state.operations.entries()).map(([channel, handler]) => channelToManifestEntry(channel, handler));
97
118
  return {
98
119
  state,
99
120
  manifest,
100
- bridgeInit: buildBridgeInitScript(manifest)
121
+ bridgeInit: buildBridgeInitScript(manifest, traceConfig)
101
122
  };
102
123
  }
103
- function buildBridgeInitScript(manifest) {
124
+ function buildBridgeInitScript(manifest, traceConfig) {
125
+ const traceEnabled = traceConfig?.enabled === true;
126
+ const traceMinLevel = traceConfig?.minLevel ?? "debug";
104
127
  return `
105
128
  (() => {
106
129
  const manifest = ${JSON.stringify(manifest)};
107
130
  const listeners = new Map();
108
131
  const bridge = {
109
132
  __rpcManifest: manifest,
110
- __woodConfig: async () => ({ rendererTraceEnabled: false }),
133
+ __woodConfig: async () => ({
134
+ rendererTraceEnabled: ${JSON.stringify(traceEnabled)},
135
+ rendererTraceMinLevel: ${JSON.stringify(traceMinLevel)}
136
+ }),
111
137
  __log: () => {},
112
- __trace: () => {},
138
+ __trace: (payload) => window.__woodBrowserTrace?.(payload),
113
139
  __window: {
114
140
  current: async () => ({ ok: true, windowId: 'wood-browser-harness', defaultRoute: window.__WOOD_BROWSER_DEFAULT_ROUTE__ }),
115
141
  open: async () => ({}),
@@ -185,12 +211,13 @@ async function loadViews(viewsPath) {
185
211
  const parser = await import("../parser/views_parser.cjs");
186
212
  return parser.parseViewsFile(viewsPath);
187
213
  }
188
- async function createViteServer(rootDir) {
214
+ async function createViteServer(rootDir, cacheDir) {
189
215
  const vite = await loadVite();
190
216
  const svelte = await loadSveltePlugin(rootDir);
191
217
  const plugins = svelte ? [svelte()] : [];
192
218
  const server = await vite.createServer({
193
219
  root: rootDir,
220
+ cacheDir,
194
221
  logLevel: process.env.WOOD_BROWSER_TEST_DEBUG ? "info" : "silent",
195
222
  plugins,
196
223
  resolve: {
@@ -201,6 +228,92 @@ async function createViteServer(rootDir) {
201
228
  await server.listen();
202
229
  return server;
203
230
  }
231
+ function workerRuntimeKey(input) {
232
+ return [
233
+ input.rootDir,
234
+ input.browserName,
235
+ input.headless ? "headless" : "headed"
236
+ ].join("::");
237
+ }
238
+ async function createBrowserWorkerRuntime(input) {
239
+ const cacheRoot = import_node_fs.default.mkdtempSync(import_node_path.default.join(input.rootDir, ".wood-browser-vite-"));
240
+ let browser2 = null;
241
+ try {
242
+ const playwrightBrowser = await loadPlaywright(input.browserName);
243
+ browser2 = await playwrightBrowser.launch({ headless: input.headless });
244
+ const server = await createViteServer(input.rootDir, import_node_path.default.join(cacheRoot, ".vite"));
245
+ return {
246
+ browser: browser2,
247
+ server,
248
+ cacheDir: cacheRoot,
249
+ rootDir: input.rootDir,
250
+ key: input.key,
251
+ activeHarnesses: 0,
252
+ closing: false,
253
+ idleCloseTimer: null
254
+ };
255
+ } catch (error) {
256
+ await browser2?.close().catch(() => void 0);
257
+ import_node_fs.default.rmSync(cacheRoot, { recursive: true, force: true });
258
+ throw error;
259
+ }
260
+ }
261
+ async function acquireBrowserWorkerRuntime(input) {
262
+ const key = workerRuntimeKey(input);
263
+ let entry = browserWorkerRuntimes.get(key);
264
+ if (!entry) {
265
+ entry = {
266
+ promise: createBrowserWorkerRuntime({ ...input, key })
267
+ };
268
+ browserWorkerRuntimes.set(key, entry);
269
+ }
270
+ const runtime = await entry.promise;
271
+ if (runtime.closing) {
272
+ browserWorkerRuntimes.delete(key);
273
+ return await acquireBrowserWorkerRuntime(input);
274
+ }
275
+ if (runtime.idleCloseTimer) {
276
+ clearTimeout(runtime.idleCloseTimer);
277
+ runtime.idleCloseTimer = null;
278
+ }
279
+ runtime.activeHarnesses += 1;
280
+ return runtime;
281
+ }
282
+ async function closeBrowserWorkerRuntime(runtime) {
283
+ if (runtime.closing) {
284
+ return;
285
+ }
286
+ runtime.closing = true;
287
+ try {
288
+ await Promise.all([
289
+ waitForBoundedClose(runtime.server.close()),
290
+ waitForBoundedClose(runtime.browser.close())
291
+ ]);
292
+ } finally {
293
+ import_node_fs.default.rmSync(runtime.cacheDir, { recursive: true, force: true });
294
+ const entry = browserWorkerRuntimes.get(runtime.key);
295
+ const current = entry ? await entry.promise.catch(() => null) : null;
296
+ if (current === runtime) {
297
+ browserWorkerRuntimes.delete(runtime.key);
298
+ }
299
+ }
300
+ }
301
+ function scheduleBrowserWorkerRuntimeClose(runtime) {
302
+ if (runtime.closing || runtime.activeHarnesses > 0 || runtime.idleCloseTimer) {
303
+ return;
304
+ }
305
+ runtime.idleCloseTimer = setTimeout(() => {
306
+ runtime.idleCloseTimer = null;
307
+ if (runtime.activeHarnesses === 0) {
308
+ void closeBrowserWorkerRuntime(runtime);
309
+ }
310
+ }, WORKER_RUNTIME_IDLE_CLOSE_MS);
311
+ runtime.idleCloseTimer.unref?.();
312
+ }
313
+ function releaseBrowserWorkerRuntime(runtime) {
314
+ runtime.activeHarnesses = Math.max(0, runtime.activeHarnesses - 1);
315
+ scheduleBrowserWorkerRuntimeClose(runtime);
316
+ }
204
317
  function serverBaseUrl(server) {
205
318
  return server.resolvedUrls?.local?.[0] ?? "http://127.0.0.1:5173";
206
319
  }
@@ -407,6 +520,14 @@ async function installBridge(page, bridgeState, manifest, bridgeInit) {
407
520
  window.__WOOD_BROWSER_BRIDGE_MANIFEST__ = entries;
408
521
  }, manifest);
409
522
  }
523
+ async function installTraceBinding(page, traceRecorder, context) {
524
+ if (!traceRecorder) {
525
+ return;
526
+ }
527
+ await page.exposeBinding("__woodBrowserTrace", async (_source, payload) => {
528
+ (0, import_tracing.dispatchRendererTracePayload)(payload, context);
529
+ });
530
+ }
410
531
  function createMountedBridge(page, state) {
411
532
  return {
412
533
  async emit(channel, payload) {
@@ -483,20 +604,35 @@ async function elementRect(page, selector) {
483
604
  }).catch(() => null);
484
605
  }
485
606
  function createExpect(page, outputDir) {
607
+ async function waitForVisible(selector) {
608
+ const locator = page.locator(selector).first();
609
+ try {
610
+ await locator.waitFor({ state: "attached", timeout: DEFAULT_EXPECT_TIMEOUT_MS });
611
+ } catch {
612
+ throw new Error(`Expected "${selector}" to exist.`);
613
+ }
614
+ try {
615
+ await locator.waitFor({ state: "visible", timeout: DEFAULT_EXPECT_TIMEOUT_MS });
616
+ } catch {
617
+ throw new Error(`Expected "${selector}" to be visible.`);
618
+ }
619
+ }
486
620
  return {
487
621
  async visible(selector) {
488
- const count = await page.locator(selector).count();
489
- if (count < 1) throw new Error(`Expected "${selector}" to exist.`);
490
- const visible = await page.locator(selector).first().isVisible();
491
- if (!visible) throw new Error(`Expected "${selector}" to be visible.`);
622
+ await waitForVisible(selector);
492
623
  },
493
624
  async hidden(selector) {
494
- const count = await page.locator(selector).count();
495
- if (count < 1) return;
496
- const visible = await page.locator(selector).first().isVisible();
497
- if (visible) throw new Error(`Expected "${selector}" to be hidden.`);
625
+ try {
626
+ await page.locator(selector).first().waitFor({
627
+ state: "hidden",
628
+ timeout: DEFAULT_EXPECT_TIMEOUT_MS
629
+ });
630
+ } catch {
631
+ throw new Error(`Expected "${selector}" to be hidden.`);
632
+ }
498
633
  },
499
634
  async insideViewport(selector, options = {}) {
635
+ await waitForVisible(selector);
500
636
  const margin = options.margin ?? 0;
501
637
  const rect = await elementRect(page, selector);
502
638
  if (!rect) throw new Error(`Expected "${selector}" to exist.`);
@@ -507,6 +643,7 @@ function createExpect(page, outputDir) {
507
643
  }
508
644
  },
509
645
  async notClipped(selector) {
646
+ await waitForVisible(selector);
510
647
  const result = await page.locator(selector).first().evaluate((element) => {
511
648
  const rect = element.getBoundingClientRect();
512
649
  let parent = element.parentElement;
@@ -528,6 +665,8 @@ function createExpect(page, outputDir) {
528
665
  }
529
666
  },
530
667
  async notOverlapping(firstSelector, secondSelector) {
668
+ await waitForVisible(firstSelector);
669
+ await waitForVisible(secondSelector);
531
670
  const first = await elementRect(page, firstSelector);
532
671
  const second = await elementRect(page, secondSelector);
533
672
  if (!first || !second) throw new Error(`Expected both "${firstSelector}" and "${secondSelector}" to exist.`);
@@ -537,6 +676,8 @@ function createExpect(page, outputDir) {
537
676
  }
538
677
  },
539
678
  async zIndexAbove(topSelector, lowerSelector) {
679
+ await waitForVisible(topSelector);
680
+ await waitForVisible(lowerSelector);
540
681
  const values = await page.evaluate((payload) => {
541
682
  const { topSelector: top, lowerSelector: lower } = payload;
542
683
  const topElement = document.querySelector(top);
@@ -610,6 +751,7 @@ async function openMountPage(input) {
610
751
  await page.setViewportSize({ width: viewport.width, height: viewport.height });
611
752
  const entryPath = writeEntry(input.runtime.tempDir, input.name, input.entrySource);
612
753
  const htmlPath = writeHtml(input.runtime.tempDir, input.name, entryPath, input.css);
754
+ await installTraceBinding(input.runtime.page, input.traceRecorder, input.traceContext ?? {});
613
755
  await installBridge(page, input.bridge.state, input.bridge.manifest, input.bridge.bridgeInit);
614
756
  const mountedBridge = createMountedBridge(page, input.bridge.state);
615
757
  const context = {
@@ -642,22 +784,47 @@ async function openMountPage(input) {
642
784
  async function createHarness(config = {}) {
643
785
  const rootDir = normalizeRoot(config.rootDir);
644
786
  const browserName = config.browser?.browserName ?? "chromium";
645
- const playwrightBrowser = await loadPlaywright(browserName);
646
- const launchedBrowser = await playwrightBrowser.launch({
647
- headless: config.browser?.headless ?? true
787
+ const headless = config.browser?.headless ?? true;
788
+ const workerRuntime = await acquireBrowserWorkerRuntime({
789
+ rootDir,
790
+ browserName,
791
+ headless
648
792
  });
649
- const server = await createViteServer(rootDir);
650
- const page = await launchedBrowser.newPage({
651
- viewport: {
652
- width: config.viewport?.width ?? DEFAULT_VIEWPORT.width,
653
- height: config.viewport?.height ?? DEFAULT_VIEWPORT.height,
654
- deviceScaleFactor: config.viewport?.deviceScaleFactor
793
+ let released = false;
794
+ let destroyed = false;
795
+ let tempDir = null;
796
+ let context = null;
797
+ let page = null;
798
+ let traceRecorder;
799
+ function releaseRuntimeOnce() {
800
+ if (released) {
801
+ return;
655
802
  }
656
- });
657
- const tempDir = import_node_fs.default.mkdtempSync(import_node_path.default.join(rootDir, ".wood-browser-harness-"));
803
+ released = true;
804
+ releaseBrowserWorkerRuntime(workerRuntime);
805
+ }
806
+ try {
807
+ tempDir = import_node_fs.default.mkdtempSync(import_node_path.default.join(rootDir, ".wood-browser-harness-"));
808
+ context = await workerRuntime.browser.newContext({
809
+ viewport: {
810
+ width: config.viewport?.width ?? DEFAULT_VIEWPORT.width,
811
+ height: config.viewport?.height ?? DEFAULT_VIEWPORT.height,
812
+ deviceScaleFactor: config.viewport?.deviceScaleFactor
813
+ }
814
+ });
815
+ page = await context.newPage();
816
+ traceRecorder = config.trace?.enabled === true ? new import_trace_testing.TraceRecorder() : void 0;
817
+ traceRecorder?.enable({ maxEvents: config.trace?.maxEvents });
818
+ } catch (error) {
819
+ if (page) await waitForBoundedClose(page.close());
820
+ if (context) await waitForBoundedClose(context.close());
821
+ if (tempDir) import_node_fs.default.rmSync(tempDir, { recursive: true, force: true });
822
+ releaseRuntimeOnce();
823
+ throw error;
824
+ }
658
825
  const runtime = {
659
- browser: launchedBrowser,
660
- server,
826
+ context,
827
+ server: workerRuntime.server,
661
828
  page,
662
829
  tempDir,
663
830
  rootDir
@@ -669,8 +836,9 @@ async function createHarness(config = {}) {
669
836
  });
670
837
  return {
671
838
  page,
839
+ ...traceRecorder ? { traceRecorder } : {},
672
840
  async mountRoute(routeKey, options = {}) {
673
- const bridge = createBrowserBridgeFixture(mergeBridge(config.bridge, options.bridge));
841
+ const bridge = createBrowserBridgeFixtureWithTrace(mergeBridge(config.bridge, options.bridge), config.trace);
674
842
  const route = {
675
843
  params: options.route?.params ?? {},
676
844
  query: options.route?.query ?? {}
@@ -701,6 +869,12 @@ async function createHarness(config = {}) {
701
869
  css,
702
870
  viewport: options.viewport ?? config.viewport,
703
871
  bridge,
872
+ traceRecorder,
873
+ traceContext: {
874
+ windowId: config.trace?.windowId ?? routeKey.split(".")[0],
875
+ routeKey,
876
+ harnessId: config.trace?.harnessId
877
+ },
704
878
  context: {
705
879
  route: {
706
880
  key: routeKey,
@@ -721,7 +895,7 @@ async function createHarness(config = {}) {
721
895
  async mountComponent(component, options = {}) {
722
896
  const componentPath = resolveFromRoot(rootDir, component);
723
897
  assertFileExists(componentPath, "Component");
724
- const bridge = createBrowserBridgeFixture(mergeBridge(config.bridge, options.bridge));
898
+ const bridge = createBrowserBridgeFixtureWithTrace(mergeBridge(config.bridge, options.bridge), config.trace);
725
899
  return await openMountPage({
726
900
  runtime,
727
901
  entrySource: buildComponentEntrySource({
@@ -732,6 +906,11 @@ async function createHarness(config = {}) {
732
906
  css,
733
907
  viewport: options.viewport ?? config.viewport,
734
908
  bridge,
909
+ traceRecorder,
910
+ traceContext: {
911
+ windowId: config.trace?.windowId ?? "wood-browser-harness",
912
+ harnessId: config.trace?.harnessId
913
+ },
735
914
  context: {},
736
915
  hooks: {
737
916
  beforeMount: options.page?.beforeMount ?? config.page?.beforeMount,
@@ -740,7 +919,7 @@ async function createHarness(config = {}) {
740
919
  });
741
920
  },
742
921
  async mountTree(tree) {
743
- const bridge = createBrowserBridgeFixture(mergeBridge(config.bridge, tree.bridge));
922
+ const bridge = createBrowserBridgeFixtureWithTrace(mergeBridge(config.bridge, tree.bridge), config.trace);
744
923
  const layouts = (tree.layouts ?? []).map((layout) => ({
745
924
  component: resolveFromRoot(rootDir, layout.component),
746
925
  props: layout.props
@@ -760,6 +939,12 @@ async function createHarness(config = {}) {
760
939
  css,
761
940
  viewport: tree.viewport ?? config.viewport,
762
941
  bridge,
942
+ traceRecorder,
943
+ traceContext: {
944
+ windowId: config.trace?.windowId ?? tree.page?.key?.split(".")[0] ?? "wood-browser-harness",
945
+ routeKey: tree.page?.key,
946
+ harnessId: config.trace?.harnessId
947
+ },
763
948
  context: tree.page?.key ? {
764
949
  route: {
765
950
  key: tree.page.key,
@@ -771,10 +956,17 @@ async function createHarness(config = {}) {
771
956
  });
772
957
  },
773
958
  async destroy() {
774
- await page.close().catch(() => void 0);
775
- await server.close().catch(() => void 0);
776
- await launchedBrowser.close().catch(() => void 0);
959
+ if (destroyed) {
960
+ return;
961
+ }
962
+ destroyed = true;
963
+ await Promise.all([
964
+ waitForBoundedClose(page.close()),
965
+ waitForBoundedClose(context.close())
966
+ ]);
967
+ traceRecorder?.disable();
777
968
  import_node_fs.default.rmSync(tempDir, { recursive: true, force: true });
969
+ releaseRuntimeOnce();
778
970
  }
779
971
  };
780
972
  }