@noego/wood 0.4.1 → 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 (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 +188 -34
  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 +188 -35
  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
@@ -37,10 +37,15 @@ 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
39
  var import_tracing = require("../tracing/index.js");
40
+ var import_trace_testing = require("../trace-testing/index.js");
40
41
  const DEFAULT_VIEWPORT = {
41
42
  width: 1280,
42
43
  height: 800
43
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();
44
49
  function normalizeRoot(rootDir) {
45
50
  return import_node_path.default.resolve(rootDir ?? process.cwd());
46
51
  }
@@ -63,6 +68,18 @@ function mergeBridge(base, override) {
63
68
  record: override?.record ?? base?.record
64
69
  };
65
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
+ }
66
83
  function channelToManifestEntry(channel, handler) {
67
84
  const pathSegments = channel.split(".").filter(Boolean);
68
85
  const action = pathSegments.length > 0 ? pathSegments[pathSegments.length - 1] : channel;
@@ -194,12 +211,13 @@ async function loadViews(viewsPath) {
194
211
  const parser = await import("../parser/views_parser.cjs");
195
212
  return parser.parseViewsFile(viewsPath);
196
213
  }
197
- async function createViteServer(rootDir) {
214
+ async function createViteServer(rootDir, cacheDir) {
198
215
  const vite = await loadVite();
199
216
  const svelte = await loadSveltePlugin(rootDir);
200
217
  const plugins = svelte ? [svelte()] : [];
201
218
  const server = await vite.createServer({
202
219
  root: rootDir,
220
+ cacheDir,
203
221
  logLevel: process.env.WOOD_BROWSER_TEST_DEBUG ? "info" : "silent",
204
222
  plugins,
205
223
  resolve: {
@@ -210,6 +228,92 @@ async function createViteServer(rootDir) {
210
228
  await server.listen();
211
229
  return server;
212
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
+ }
213
317
  function serverBaseUrl(server) {
214
318
  return server.resolvedUrls?.local?.[0] ?? "http://127.0.0.1:5173";
215
319
  }
@@ -416,8 +520,8 @@ async function installBridge(page, bridgeState, manifest, bridgeInit) {
416
520
  window.__WOOD_BROWSER_BRIDGE_MANIFEST__ = entries;
417
521
  }, manifest);
418
522
  }
419
- async function installTraceBinding(page, trace, context) {
420
- if (!trace) {
523
+ async function installTraceBinding(page, traceRecorder, context) {
524
+ if (!traceRecorder) {
421
525
  return;
422
526
  }
423
527
  await page.exposeBinding("__woodBrowserTrace", async (_source, payload) => {
@@ -500,20 +604,35 @@ async function elementRect(page, selector) {
500
604
  }).catch(() => null);
501
605
  }
502
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
+ }
503
620
  return {
504
621
  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.`);
622
+ await waitForVisible(selector);
509
623
  },
510
624
  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.`);
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
+ }
515
633
  },
516
634
  async insideViewport(selector, options = {}) {
635
+ await waitForVisible(selector);
517
636
  const margin = options.margin ?? 0;
518
637
  const rect = await elementRect(page, selector);
519
638
  if (!rect) throw new Error(`Expected "${selector}" to exist.`);
@@ -524,6 +643,7 @@ function createExpect(page, outputDir) {
524
643
  }
525
644
  },
526
645
  async notClipped(selector) {
646
+ await waitForVisible(selector);
527
647
  const result = await page.locator(selector).first().evaluate((element) => {
528
648
  const rect = element.getBoundingClientRect();
529
649
  let parent = element.parentElement;
@@ -545,6 +665,8 @@ function createExpect(page, outputDir) {
545
665
  }
546
666
  },
547
667
  async notOverlapping(firstSelector, secondSelector) {
668
+ await waitForVisible(firstSelector);
669
+ await waitForVisible(secondSelector);
548
670
  const first = await elementRect(page, firstSelector);
549
671
  const second = await elementRect(page, secondSelector);
550
672
  if (!first || !second) throw new Error(`Expected both "${firstSelector}" and "${secondSelector}" to exist.`);
@@ -554,6 +676,8 @@ function createExpect(page, outputDir) {
554
676
  }
555
677
  },
556
678
  async zIndexAbove(topSelector, lowerSelector) {
679
+ await waitForVisible(topSelector);
680
+ await waitForVisible(lowerSelector);
557
681
  const values = await page.evaluate((payload) => {
558
682
  const { topSelector: top, lowerSelector: lower } = payload;
559
683
  const topElement = document.querySelector(top);
@@ -627,7 +751,7 @@ async function openMountPage(input) {
627
751
  await page.setViewportSize({ width: viewport.width, height: viewport.height });
628
752
  const entryPath = writeEntry(input.runtime.tempDir, input.name, input.entrySource);
629
753
  const htmlPath = writeHtml(input.runtime.tempDir, input.name, entryPath, input.css);
630
- await installTraceBinding(input.runtime.page, input.trace, input.traceContext ?? {});
754
+ await installTraceBinding(input.runtime.page, input.traceRecorder, input.traceContext ?? {});
631
755
  await installBridge(page, input.bridge.state, input.bridge.manifest, input.bridge.bridgeInit);
632
756
  const mountedBridge = createMountedBridge(page, input.bridge.state);
633
757
  const context = {
@@ -660,23 +784,47 @@ async function openMountPage(input) {
660
784
  async function createHarness(config = {}) {
661
785
  const rootDir = normalizeRoot(config.rootDir);
662
786
  const browserName = config.browser?.browserName ?? "chromium";
663
- const playwrightBrowser = await loadPlaywright(browserName);
664
- const launchedBrowser = await playwrightBrowser.launch({
665
- headless: config.browser?.headless ?? true
787
+ const headless = config.browser?.headless ?? true;
788
+ const workerRuntime = await acquireBrowserWorkerRuntime({
789
+ rootDir,
790
+ browserName,
791
+ headless
666
792
  });
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
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;
673
802
  }
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;
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
+ }
677
825
  const runtime = {
678
- browser: launchedBrowser,
679
- server,
826
+ context,
827
+ server: workerRuntime.server,
680
828
  page,
681
829
  tempDir,
682
830
  rootDir
@@ -688,7 +836,7 @@ async function createHarness(config = {}) {
688
836
  });
689
837
  return {
690
838
  page,
691
- ...trace ? { trace } : {},
839
+ ...traceRecorder ? { traceRecorder } : {},
692
840
  async mountRoute(routeKey, options = {}) {
693
841
  const bridge = createBrowserBridgeFixtureWithTrace(mergeBridge(config.bridge, options.bridge), config.trace);
694
842
  const route = {
@@ -721,7 +869,7 @@ async function createHarness(config = {}) {
721
869
  css,
722
870
  viewport: options.viewport ?? config.viewport,
723
871
  bridge,
724
- trace,
872
+ traceRecorder,
725
873
  traceContext: {
726
874
  windowId: config.trace?.windowId ?? routeKey.split(".")[0],
727
875
  routeKey,
@@ -758,7 +906,7 @@ async function createHarness(config = {}) {
758
906
  css,
759
907
  viewport: options.viewport ?? config.viewport,
760
908
  bridge,
761
- trace,
909
+ traceRecorder,
762
910
  traceContext: {
763
911
  windowId: config.trace?.windowId ?? "wood-browser-harness",
764
912
  harnessId: config.trace?.harnessId
@@ -791,7 +939,7 @@ async function createHarness(config = {}) {
791
939
  css,
792
940
  viewport: tree.viewport ?? config.viewport,
793
941
  bridge,
794
- trace,
942
+ traceRecorder,
795
943
  traceContext: {
796
944
  windowId: config.trace?.windowId ?? tree.page?.key?.split(".")[0] ?? "wood-browser-harness",
797
945
  routeKey: tree.page?.key,
@@ -808,11 +956,17 @@ async function createHarness(config = {}) {
808
956
  });
809
957
  },
810
958
  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();
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();
815
968
  import_node_fs.default.rmSync(tempDir, { recursive: true, force: true });
969
+ releaseRuntimeOnce();
816
970
  }
817
971
  };
818
972
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/testing/browser.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport {\n createTraceCapture,\n dispatchRendererTracePayload,\n type TraceCapture,\n type TraceLevel,\n type TraceRuntimeContext,\n} from '../tracing/index.js';\nimport type { ViewDef } from '../types/views.cjs';\n\ntype MaybePromise<T> = T | Promise<T>;\ntype BrowserName = 'chromium' | 'firefox' | 'webkit';\ntype OperationHandler = unknown | ((\n input: unknown,\n context: WoodBridgeFixtureContext,\n) => MaybePromise<unknown>);\n\nexport interface WoodViewport {\n width: number;\n height: number;\n deviceScaleFactor?: number;\n}\n\nexport interface WoodBridgeRecordedCall {\n channel: string;\n input: unknown;\n timestamp: string;\n}\n\nexport interface WoodBridgeRecordedEvent {\n channel: string;\n payload: unknown;\n timestamp: string;\n}\n\nexport interface WoodBridgeFixtureContext {\n channel: string;\n calls: WoodBridgeRecordedCall[];\n}\n\nexport interface WoodBridgeFixtureInput {\n operations?: Record<string, OperationHandler>;\n events?: Record<string, unknown | unknown[]>;\n record?: boolean;\n}\n\nexport interface WoodBrowserPageContext {\n page: any;\n bridge: MountedWoodBridge;\n route?: {\n key: string;\n params: Record<string, string>;\n query: Record<string, string>;\n };\n}\n\nexport interface WoodBrowserHarnessConfig {\n rootDir?: string;\n generatedApp?: string;\n componentDir?: string;\n viewsConfig?: string;\n css?: string[];\n viewport?: WoodViewport;\n bridge?: WoodBridgeFixtureInput;\n page?: {\n beforeMount?: (context: WoodBrowserPageContext) => MaybePromise<void>;\n afterMount?: (context: WoodBrowserPageContext) => MaybePromise<void>;\n };\n browser?: {\n browserName?: BrowserName;\n headless?: boolean;\n };\n trace?: WoodBrowserTraceConfig;\n}\n\nexport interface WoodBrowserTraceConfig {\n enabled?: boolean;\n maxEvents?: number;\n minLevel?: TraceLevel;\n windowId?: string;\n harnessId?: string;\n}\n\nexport interface MountRouteOptions {\n route?: {\n params?: Record<string, string>;\n query?: Record<string, string>;\n };\n bridge?: WoodBridgeFixtureInput;\n viewport?: WoodViewport;\n page?: {\n beforeMount?: (context: WoodBrowserPageContext) => MaybePromise<void>;\n afterMount?: (context: WoodBrowserPageContext) => MaybePromise<void>;\n };\n}\n\nexport interface MountComponentOptions {\n props?: Record<string, unknown>;\n bridge?: WoodBridgeFixtureInput;\n viewport?: WoodViewport;\n page?: {\n beforeMount?: (context: WoodBrowserPageContext) => MaybePromise<void>;\n afterMount?: (context: WoodBrowserPageContext) => MaybePromise<void>;\n };\n}\n\nexport interface MountTreeOptions {\n layouts?: Array<{\n component: string;\n props?: Record<string, unknown>;\n }>;\n view: {\n component: string;\n props?: Record<string, unknown>;\n };\n page?: {\n key?: string;\n params?: Record<string, string>;\n query?: Record<string, string>;\n };\n bridge?: WoodBridgeFixtureInput;\n viewport?: WoodViewport;\n}\n\nexport interface MountedWoodBridge {\n emit(channel: string, payload: unknown): Promise<void>;\n calls(channel?: string): WoodBridgeRecordedCall[];\n events(channel?: string): WoodBridgeRecordedEvent[];\n setOperation(channel: string, handler: OperationHandler): void;\n clearCalls(): void;\n}\n\nexport interface MountedWoodNavigation {\n current(): Promise<{\n page: string;\n params: Record<string, string>;\n query: Record<string, string>;\n } | null>;\n go(\n page: string,\n params?: Record<string, string>,\n query?: Record<string, string>,\n ): Promise<void>;\n replace(\n page: string,\n params?: Record<string, string>,\n query?: Record<string, string>,\n ): Promise<void>;\n}\n\nexport interface WoodBrowserExpect {\n visible(selector: string): Promise<void>;\n hidden(selector: string): Promise<void>;\n insideViewport(selector: string, options?: { margin?: number }): Promise<void>;\n notClipped(selector: string): Promise<void>;\n notOverlapping(firstSelector: string, secondSelector: string): Promise<void>;\n zIndexAbove(topSelector: string, lowerSelector: string): Promise<void>;\n stableAfterAnimation(selector?: string, options?: {\n timeoutMs?: number;\n frameCount?: number;\n }): Promise<void>;\n screenshot(name: string, options?: {\n fullPage?: boolean;\n mask?: string[];\n threshold?: number;\n }): Promise<void>;\n}\n\nexport interface MountedWoodSurface {\n page: any;\n bridge: MountedWoodBridge;\n navigation: MountedWoodNavigation;\n expect: WoodBrowserExpect;\n locator(selector: string): any;\n click(selector: string): Promise<void>;\n fill(selector: string, value: string): Promise<void>;\n evaluate<T>(fn: () => T | Promise<T>): Promise<T>;\n destroy(): Promise<void>;\n}\n\nexport interface MountedWoodRoute extends MountedWoodSurface {\n routeKey: string;\n}\n\nexport type MountedWoodComponent = MountedWoodSurface;\nexport type MountedWoodTree = MountedWoodSurface;\n\nexport interface WoodBrowserHarness {\n page: any;\n trace?: TraceCapture;\n mountRoute(routeKey: string, options?: MountRouteOptions): Promise<MountedWoodRoute>;\n mountComponent(component: string, options?: MountComponentOptions): Promise<MountedWoodComponent>;\n mountTree(tree: MountTreeOptions): Promise<MountedWoodTree>;\n destroy(): Promise<void>;\n}\n\ntype BridgeState = {\n operations: Map<string, OperationHandler>;\n initialEvents: WoodBridgeRecordedEvent[];\n calls: WoodBridgeRecordedCall[];\n emittedEvents: WoodBridgeRecordedEvent[];\n record: boolean;\n};\n\ntype RuntimeBridgeManifestEntry = {\n channel: string;\n controller: string;\n action: string;\n path: string[];\n hasInput: boolean;\n};\n\ntype HarnessRuntime = {\n browser: any;\n server: any;\n page: any;\n tempDir: string;\n rootDir: string;\n};\n\nconst DEFAULT_VIEWPORT: WoodViewport = {\n width: 1280,\n height: 800,\n};\n\nfunction normalizeRoot(rootDir: string | undefined): string {\n return path.resolve(rootDir ?? process.cwd());\n}\n\nfunction resolveFromRoot(rootDir: string, filePath: string): string {\n return path.isAbsolute(filePath) ? filePath : path.resolve(rootDir, filePath);\n}\n\nfunction viteImport(filePath: string): string {\n return `/@fs/${filePath.replace(/\\\\/g, '/').replace(/^\\/+/, '')}`;\n}\n\nfunction mergeBridge(\n base: WoodBridgeFixtureInput | undefined,\n override: WoodBridgeFixtureInput | undefined,\n): WoodBridgeFixtureInput {\n return {\n operations: {\n ...(base?.operations ?? {}),\n ...(override?.operations ?? {}),\n },\n events: {\n ...(base?.events ?? {}),\n ...(override?.events ?? {}),\n },\n record: override?.record ?? base?.record,\n };\n}\n\nfunction channelToManifestEntry(channel: string, handler: OperationHandler): RuntimeBridgeManifestEntry {\n const pathSegments = channel.split('.').filter(Boolean);\n const action = pathSegments.length > 0 ? pathSegments[pathSegments.length - 1] : channel;\n const controller = pathSegments.length > 1 ? pathSegments[0] : channel;\n\n return {\n channel,\n controller,\n action,\n path: pathSegments,\n hasInput: typeof handler === 'function',\n };\n}\n\nfunction createBridgeState(input: WoodBridgeFixtureInput | undefined): BridgeState {\n const initialEvents = Object.entries(input?.events ?? {}).flatMap(([channel, payloadOrList]) => {\n const payloads = Array.isArray(payloadOrList) ? payloadOrList : [payloadOrList];\n return payloads.map((payload) => ({\n channel,\n payload,\n timestamp: new Date().toISOString(),\n }));\n });\n\n return {\n operations: new Map(Object.entries(input?.operations ?? {})),\n initialEvents,\n calls: [],\n emittedEvents: [],\n record: input?.record ?? true,\n };\n}\n\nexport function createBrowserBridgeFixture(input: WoodBridgeFixtureInput | undefined): {\n state: BridgeState;\n manifest: RuntimeBridgeManifestEntry[];\n bridgeInit: string;\n} {\n return createBrowserBridgeFixtureWithTrace(input, undefined);\n}\n\nfunction createBrowserBridgeFixtureWithTrace(\n input: WoodBridgeFixtureInput | undefined,\n traceConfig: WoodBrowserTraceConfig | undefined,\n): {\n state: BridgeState;\n manifest: RuntimeBridgeManifestEntry[];\n bridgeInit: string;\n} {\n const state = createBridgeState(input);\n const manifest = Array.from(state.operations.entries())\n .map(([channel, handler]) => channelToManifestEntry(channel, handler));\n\n return {\n state,\n manifest,\n bridgeInit: buildBridgeInitScript(manifest, traceConfig),\n };\n}\n\nfunction buildBridgeInitScript(\n manifest: RuntimeBridgeManifestEntry[],\n traceConfig: WoodBrowserTraceConfig | undefined,\n): string {\n const traceEnabled = traceConfig?.enabled === true;\n const traceMinLevel = traceConfig?.minLevel ?? 'debug';\n return `\n(() => {\n const manifest = ${JSON.stringify(manifest)};\n const listeners = new Map();\n const bridge = {\n __rpcManifest: manifest,\n __woodConfig: async () => ({\n rendererTraceEnabled: ${JSON.stringify(traceEnabled)},\n rendererTraceMinLevel: ${JSON.stringify(traceMinLevel)}\n }),\n __log: () => {},\n __trace: (payload) => window.__woodBrowserTrace?.(payload),\n __window: {\n current: async () => ({ ok: true, windowId: 'wood-browser-harness', defaultRoute: window.__WOOD_BROWSER_DEFAULT_ROUTE__ }),\n open: async () => ({}),\n close: async () => ({}),\n focus: async () => ({}),\n minimize: async () => ({}),\n maximize: async () => ({}),\n },\n on(channel, callback) {\n if (!listeners.has(channel)) listeners.set(channel, new Set());\n listeners.get(channel).add(callback);\n return () => listeners.get(channel)?.delete(callback);\n },\n };\n\n for (const entry of manifest) {\n let current = bridge;\n for (let i = 0; i < entry.path.length - 1; i += 1) {\n const segment = entry.path[i];\n current[segment] ??= {};\n current = current[segment];\n }\n const leaf = entry.path[entry.path.length - 1];\n current[leaf] = async (data) => window.__woodBrowserInvoke(entry.channel, data);\n }\n\n window.__woodBrowserEmit = (channel, payload) => {\n const handlers = listeners.get(channel);\n if (!handlers) return;\n for (const handler of handlers) handler(payload);\n };\n window.__NOEGO_WOOD_BRIDGE__ = bridge;\n window.wood = bridge;\n globalThis.__NOEGO_WOOD_BRIDGE__ = bridge;\n globalThis.wood = bridge;\n})();\n`;\n}\n\nasync function loadPlaywright(browserName: BrowserName): Promise<any> {\n const playwright = await import('playwright');\n return playwright[browserName];\n}\n\nasync function loadVite(): Promise<any> {\n return await import('vite');\n}\n\nasync function loadSveltePlugin(rootDir: string): Promise<any | null> {\n try {\n const rootPluginPath = path.join(\n rootDir,\n 'node_modules',\n '@sveltejs',\n 'vite-plugin-svelte',\n 'src',\n 'index.js',\n );\n if (fs.existsSync(rootPluginPath)) {\n const mod = await import(pathToFileURL(rootPluginPath).href);\n return mod.svelte;\n }\n const rootRequire = createRequire(path.join(rootDir, 'package.json'));\n const mod = await import(pathToFileURL(rootRequire.resolve('@sveltejs/vite-plugin-svelte')).href);\n return mod.svelte;\n } catch {\n try {\n const mod = await import('@sveltejs/vite-plugin-svelte');\n return mod.svelte;\n } catch {\n return null;\n }\n }\n}\n\nasync function loadViews(viewsPath: string): Promise<ViewDef[]> {\n const parser = await import('../parser/views_parser.cjs');\n return parser.parseViewsFile(viewsPath);\n}\n\nasync function createViteServer(rootDir: string): Promise<any> {\n const vite = await loadVite();\n const svelte = await loadSveltePlugin(rootDir);\n const plugins = svelte ? [svelte()] : [];\n const server = await vite.createServer({\n root: rootDir,\n logLevel: process.env.WOOD_BROWSER_TEST_DEBUG ? 'info' : 'silent',\n plugins,\n resolve: {\n dedupe: ['svelte'],\n },\n server: { port: 0 },\n });\n await server.listen();\n return server;\n}\n\nfunction serverBaseUrl(server: any): string {\n return server.resolvedUrls?.local?.[0] ?? 'http://127.0.0.1:5173';\n}\n\nfunction assertFileExists(filePath: string, label: string): void {\n if (!fs.existsSync(filePath)) {\n throw new Error(`${label} does not exist: ${filePath}`);\n }\n}\n\nfunction writeEntry(tempDir: string, name: string, source: string): string {\n fs.mkdirSync(tempDir, { recursive: true });\n const entryPath = path.join(tempDir, `${name}.ts`);\n fs.writeFileSync(entryPath, source);\n return entryPath;\n}\n\nfunction writeHtml(tempDir: string, name: string, entryPath: string, cssFiles: string[]): string {\n const styles = cssFiles\n .map((cssPath) => `<link rel=\"stylesheet\" href=\"${viteImport(cssPath)}\" />`)\n .join('\\n');\n const htmlPath = path.join(tempDir, `${name}.html`);\n fs.writeFileSync(htmlPath, `<!doctype html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n ${styles}\n </head>\n <body>\n <div id=\"app\"></div>\n <script type=\"module\" src=\"${viteImport(entryPath)}\"></script>\n </body>\n</html>\n`);\n return htmlPath;\n}\n\nfunction viewKey(view: ViewDef): string {\n return `${view.windowName}.${view.pageName}`;\n}\n\nfunction findView(views: ViewDef[], routeKey: string): ViewDef {\n const found = views.find((view) => viewKey(view) === routeKey);\n if (!found) {\n throw new Error(\n `Unknown Wood route \"${routeKey}\". Available routes: ${views.map(viewKey).join(', ')}`,\n );\n }\n return found;\n}\n\nfunction resolveComponentPath(rootDir: string, componentDir: string | undefined, filePath: string): string {\n if (path.isAbsolute(filePath)) return filePath;\n const base = componentDir ? resolveFromRoot(rootDir, componentDir) : rootDir;\n const componentDirName = componentDir ? path.basename(componentDir) : '';\n if (componentDirName && filePath.replace(/\\\\/g, '/').startsWith(`${componentDirName}/`)) {\n return path.resolve(base, filePath.slice(componentDirName.length + 1));\n }\n return path.resolve(base, filePath);\n}\n\nfunction buildRouteEntrySource(input: {\n routeKey: string;\n view: ViewDef;\n rootDir: string;\n componentDir?: string;\n route: {\n params: Record<string, string>;\n query: Record<string, string>;\n };\n}): string {\n const { view, rootDir, componentDir, routeKey, route } = input;\n const viewPath = resolveComponentPath(rootDir, componentDir, view.viewPath);\n const layoutPaths = view.layouts.map((layout) => resolveComponentPath(rootDir, componentDir, layout));\n const controllerPath = view.controller\n ? resolveComponentPath(rootDir, componentDir, view.controller)\n : null;\n const layoutControllerPaths = view.layoutControllers?.map((controller) =>\n controller ? resolveComponentPath(rootDir, componentDir, controller) : null,\n ) ?? [];\n\n const imports = [\n `import { mount } from 'svelte';`,\n `import NavigationShell from '@noego/wood/navigation-shell';`,\n `import { getContainer } from '@noego/wood/client';`,\n `import { getNavigation } from '@noego/wood/navigation';`,\n `import ViewComponent from '${viteImport(viewPath)}';`,\n ...layoutPaths.map((layoutPath, index) => `import Layout${index} from '${viteImport(layoutPath)}';`),\n ...(controllerPath ? [`import PageController from '${viteImport(controllerPath)}';`] : []),\n ...layoutControllerPaths.map((controllerPath, index) =>\n controllerPath ? `import LayoutController${index} from '${viteImport(controllerPath)}';` : '',\n ).filter(Boolean),\n ];\n\n const layoutControllerClasses = layoutControllerPaths.map((controllerPath, index) =>\n controllerPath ? `LayoutController${index}` : 'undefined',\n );\n\n return `${imports.join('\\n')}\n\nconst entry = {\n key: ${JSON.stringify(routeKey)},\n windowName: ${JSON.stringify(view.windowName)},\n pageName: ${JSON.stringify(view.pageName)},\n viewPath: ${JSON.stringify(view.viewPath)},\n layoutPaths: ${JSON.stringify(view.layouts)},\n layoutControllers: ${JSON.stringify(view.layoutControllers ?? [])},\n controller: ${JSON.stringify(view.controller)},\n layoutControllerClasses: [${layoutControllerClasses.join(', ')}],\n controllerClass: ${controllerPath ? 'PageController' : 'undefined'},\n view: ViewComponent,\n layouts: [${layoutPaths.map((_, index) => `Layout${index}`).join(', ')}],\n};\n\nfunction createScopedContainer() {\n const root = getContainer();\n const scope = root.extend();\n return scope;\n}\n\nasync function resolveController(ControllerClass) {\n if (!ControllerClass) return undefined;\n const scope = createScopedContainer();\n scope.registerClass?.(ControllerClass);\n return await scope.get(ControllerClass);\n}\n\nasync function createViewController(nextEntry) {\n return await resolveController(nextEntry.controllerClass);\n}\n\nasync function createLayoutControllers(nextEntry) {\n const controllers = [];\n for (const ControllerClass of nextEntry.layoutControllerClasses) {\n controllers.push(await resolveController(ControllerClass));\n }\n return controllers;\n}\n\nfunction getViewManifestEntry(nextKey) {\n return nextKey === entry.key ? entry : null;\n}\n\nconst navigation = getNavigation();\nnavigation.replace(entry.key, ${JSON.stringify(route.params)}, ${JSON.stringify(route.query)});\n\nconst controller = await createViewController(entry);\nconst layoutControllers = await createLayoutControllers(entry);\n\nwindow.__WOOD_BROWSER_APP__ = mount(NavigationShell, {\n target: document.getElementById('app'),\n props: {\n manifest: { [entry.key]: entry },\n initialEntry: entry,\n initialController: controller,\n initialLayoutControllers: layoutControllers,\n createViewControllerFn: createViewController,\n createLayoutControllersFn: createLayoutControllers,\n getViewManifestEntryFn: getViewManifestEntry,\n },\n});\n`;\n}\n\nfunction buildGeneratedAppEntrySource(input: {\n generatedApp: string;\n routeKey: string;\n}): string {\n return `import { app } from '${viteImport(input.generatedApp)}';\nwindow.__WOOD_BROWSER_DEFAULT_ROUTE__ = ${JSON.stringify(input.routeKey)};\nwindow.__WOOD_BROWSER_APP__ = await app(document.getElementById('app'));\n`;\n}\n\nfunction buildComponentEntrySource(input: {\n componentPath: string;\n props: Record<string, unknown>;\n}): string {\n return `import { mount } from 'svelte';\nimport Component from '${viteImport(input.componentPath)}';\nwindow.__WOOD_BROWSER_APP__ = mount(Component, {\n target: document.getElementById('app'),\n props: ${JSON.stringify(input.props)},\n});\n`;\n}\n\nfunction buildTreeEntrySource(input: {\n layouts: Array<{ component: string; props?: Record<string, unknown> }>;\n view: { component: string; props?: Record<string, unknown> };\n}): string {\n const imports = [\n `import { mount } from 'svelte';`,\n `import WoodRecursiveRender from '@noego/wood/recursive-render';`,\n `import ViewComponent from '${viteImport(input.view.component)}';`,\n ...input.layouts.map((layout, index) => `import Layout${index} from '${viteImport(layout.component)}';`),\n ];\n const layoutControllers = input.layouts.map((layout) => ({\n data: layout.props?.data,\n input: layout.props?.input,\n events: layout.props?.events,\n }));\n const viewController = {\n data: input.view.props?.data,\n input: input.view.props?.input,\n events: input.view.props?.events,\n };\n\n return `${imports.join('\\n')}\nwindow.__WOOD_BROWSER_APP__ = mount(WoodRecursiveRender, {\n target: document.getElementById('app'),\n props: {\n layouts: [${input.layouts.map((_, index) => `Layout${index}`).join(', ')}],\n layoutControllers: ${JSON.stringify(layoutControllers)},\n view: ViewComponent,\n controller: ${JSON.stringify(viewController)},\n },\n});\n`;\n}\n\nasync function installBridge(page: any, bridgeState: BridgeState, manifest: RuntimeBridgeManifestEntry[], bridgeInit: string): Promise<void> {\n await page.exposeBinding('__woodBrowserInvoke', async (_source: unknown, channel: string, input: unknown) => {\n const call = {\n channel,\n input,\n timestamp: new Date().toISOString(),\n };\n if (bridgeState.record) {\n bridgeState.calls.push(call);\n }\n const handler = bridgeState.operations.get(channel);\n if (typeof handler === 'function') {\n return await handler(input, { channel, calls: bridgeState.calls });\n }\n return handler;\n });\n await page.addInitScript(bridgeInit);\n await page.addInitScript((entries: RuntimeBridgeManifestEntry[]) => {\n window.__WOOD_BROWSER_BRIDGE_MANIFEST__ = entries;\n }, manifest);\n}\n\nasync function installTraceBinding(\n page: any,\n trace: TraceCapture | undefined,\n context: TraceRuntimeContext,\n): Promise<void> {\n if (!trace) {\n return;\n }\n await page.exposeBinding('__woodBrowserTrace', async (_source: unknown, payload: unknown) => {\n dispatchRendererTracePayload(payload, context);\n });\n}\n\nfunction createMountedBridge(page: any, state: BridgeState): MountedWoodBridge {\n return {\n async emit(channel: string, payload: unknown): Promise<void> {\n state.emittedEvents.push({\n channel,\n payload,\n timestamp: new Date().toISOString(),\n });\n await page.evaluate(({ eventChannel, eventPayload }: { eventChannel: string; eventPayload: unknown }) => {\n window.__woodBrowserEmit?.(eventChannel, eventPayload);\n }, { eventChannel: channel, eventPayload: payload });\n },\n calls(channel?: string): WoodBridgeRecordedCall[] {\n return channel ? state.calls.filter((call) => call.channel === channel) : [...state.calls];\n },\n events(channel?: string): WoodBridgeRecordedEvent[] {\n return channel\n ? state.emittedEvents.filter((event) => event.channel === channel)\n : [...state.emittedEvents];\n },\n setOperation(channel: string, handler: OperationHandler): void {\n state.operations.set(channel, handler);\n },\n clearCalls(): void {\n state.calls.length = 0;\n },\n };\n}\n\nfunction createMountedNavigation(page: any): MountedWoodNavigation {\n return {\n async current() {\n return await page.evaluate(async () => {\n const specifier = '@noego/wood/navigation';\n const mod = await import(/* @vite-ignore */ specifier);\n return mod.getNavigation().getCurrent();\n });\n },\n async go(routePage, params = {}, query = {}) {\n await page.evaluate(async (payload: {\n routePage: string;\n params: Record<string, string>;\n query: Record<string, string>;\n }) => {\n const { routePage: nextPage, params: nextParams, query: nextQuery } = payload;\n const specifier = '@noego/wood/navigation';\n const mod = await import(/* @vite-ignore */ specifier);\n mod.getNavigation().go(nextPage, nextParams, nextQuery);\n }, { routePage, params, query });\n },\n async replace(routePage, params = {}, query = {}) {\n await page.evaluate(async (payload: {\n routePage: string;\n params: Record<string, string>;\n query: Record<string, string>;\n }) => {\n const { routePage: nextPage, params: nextParams, query: nextQuery } = payload;\n const specifier = '@noego/wood/navigation';\n const mod = await import(/* @vite-ignore */ specifier);\n mod.getNavigation().replace(nextPage, nextParams, nextQuery);\n }, { routePage, params, query });\n },\n };\n}\n\nasync function elementRect(page: any, selector: string): Promise<{\n top: number;\n left: number;\n right: number;\n bottom: number;\n width: number;\n height: number;\n} | null> {\n return await page.locator(selector).evaluate((element: Element) => {\n const rect = element.getBoundingClientRect();\n return {\n top: rect.top,\n left: rect.left,\n right: rect.right,\n bottom: rect.bottom,\n width: rect.width,\n height: rect.height,\n };\n }).catch(() => null);\n}\n\nfunction createExpect(page: any, outputDir: string): WoodBrowserExpect {\n return {\n async visible(selector: string): Promise<void> {\n const count = await page.locator(selector).count();\n if (count < 1) throw new Error(`Expected \"${selector}\" to exist.`);\n const visible = await page.locator(selector).first().isVisible();\n if (!visible) throw new Error(`Expected \"${selector}\" to be visible.`);\n },\n async hidden(selector: string): Promise<void> {\n const count = await page.locator(selector).count();\n if (count < 1) return;\n const visible = await page.locator(selector).first().isVisible();\n if (visible) throw new Error(`Expected \"${selector}\" to be hidden.`);\n },\n async insideViewport(selector: string, options: { margin?: number } = {}): Promise<void> {\n const margin = options.margin ?? 0;\n const rect = await elementRect(page, selector);\n if (!rect) throw new Error(`Expected \"${selector}\" to exist.`);\n const viewport = page.viewportSize();\n if (!viewport) throw new Error('Cannot assert viewport bounds without a viewport.');\n if (\n rect.left < margin ||\n rect.top < margin ||\n rect.right > viewport.width - margin ||\n rect.bottom > viewport.height - margin\n ) {\n throw new Error(`Expected \"${selector}\" to be inside viewport. Rect: ${JSON.stringify(rect)}, viewport: ${JSON.stringify(viewport)}`);\n }\n },\n async notClipped(selector: string): Promise<void> {\n const result = await page.locator(selector).first().evaluate((element: Element) => {\n const rect = element.getBoundingClientRect();\n let parent = element.parentElement;\n while (parent) {\n const style = window.getComputedStyle(parent);\n const clips = /(auto|hidden|scroll|clip)/.test(`${style.overflow}${style.overflowX}${style.overflowY}`);\n if (clips) {\n const parentRect = parent.getBoundingClientRect();\n if (\n rect.left < parentRect.left ||\n rect.top < parentRect.top ||\n rect.right > parentRect.right ||\n rect.bottom > parentRect.bottom\n ) {\n return { ok: false, parent: parent.tagName, rect, parentRect };\n }\n }\n parent = parent.parentElement;\n }\n return { ok: true };\n });\n if (!result.ok) {\n throw new Error(`Expected \"${selector}\" not to be clipped. ${JSON.stringify(result)}`);\n }\n },\n async notOverlapping(firstSelector: string, secondSelector: string): Promise<void> {\n const first = await elementRect(page, firstSelector);\n const second = await elementRect(page, secondSelector);\n if (!first || !second) throw new Error(`Expected both \"${firstSelector}\" and \"${secondSelector}\" to exist.`);\n const overlaps = first.left < second.right\n && first.right > second.left\n && first.top < second.bottom\n && first.bottom > second.top;\n if (overlaps) {\n throw new Error(`Expected \"${firstSelector}\" not to overlap \"${secondSelector}\".`);\n }\n },\n async zIndexAbove(topSelector: string, lowerSelector: string): Promise<void> {\n const values = await page.evaluate((payload: { topSelector: string; lowerSelector: string }) => {\n const { topSelector: top, lowerSelector: lower } = payload;\n const topElement = document.querySelector(top);\n const lowerElement = document.querySelector(lower);\n if (!topElement || !lowerElement) return null;\n return {\n top: Number(window.getComputedStyle(topElement).zIndex) || 0,\n lower: Number(window.getComputedStyle(lowerElement).zIndex) || 0,\n };\n }, { topSelector, lowerSelector });\n if (!values) throw new Error(`Expected both \"${topSelector}\" and \"${lowerSelector}\" to exist.`);\n if (values.top <= values.lower) {\n throw new Error(`Expected \"${topSelector}\" z-index (${values.top}) to be above \"${lowerSelector}\" (${values.lower}).`);\n }\n },\n async stableAfterAnimation(selector = 'body', options = {}): Promise<void> {\n const frameCount = options.frameCount ?? 3;\n const timeoutMs = options.timeoutMs ?? 2000;\n const startedAt = Date.now();\n let previous = '';\n let stableFrames = 0;\n while (Date.now() - startedAt < timeoutMs) {\n const current = JSON.stringify(await elementRect(page, selector));\n if (current === previous) {\n stableFrames += 1;\n if (stableFrames >= frameCount) return;\n } else {\n stableFrames = 0;\n previous = current;\n }\n await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => resolve(undefined))));\n }\n throw new Error(`Expected \"${selector}\" to become stable within ${timeoutMs}ms.`);\n },\n async screenshot(name: string, options = {}): Promise<void> {\n const outputPath = path.join(outputDir, `${name}.png`);\n await fs.promises.mkdir(outputDir, { recursive: true });\n const masks = options.mask?.map((selector) => page.locator(selector)) ?? [];\n await page.screenshot({\n path: outputPath,\n fullPage: options.fullPage ?? true,\n mask: masks,\n });\n },\n };\n}\n\nfunction createSurface(input: {\n page: any;\n bridge: MountedWoodBridge;\n outputDir: string;\n destroy: () => Promise<void>;\n}): MountedWoodSurface {\n return {\n page: input.page,\n bridge: input.bridge,\n navigation: createMountedNavigation(input.page),\n expect: createExpect(input.page, input.outputDir),\n locator(selector: string): any {\n return input.page.locator(selector);\n },\n async click(selector: string): Promise<void> {\n await input.page.locator(selector).click();\n },\n async fill(selector: string, value: string): Promise<void> {\n await input.page.locator(selector).fill(value);\n },\n async evaluate<T>(fn: () => T | Promise<T>): Promise<T> {\n return await input.page.evaluate(fn);\n },\n destroy: input.destroy,\n };\n}\n\nasync function openMountPage(input: {\n runtime: HarnessRuntime;\n entrySource: string;\n name: string;\n css: string[];\n viewport?: WoodViewport;\n bridge: {\n state: BridgeState;\n manifest: RuntimeBridgeManifestEntry[];\n bridgeInit: string;\n };\n trace?: TraceCapture;\n traceContext?: TraceRuntimeContext;\n context: Omit<WoodBrowserPageContext, 'page' | 'bridge'>;\n hooks?: WoodBrowserHarnessConfig['page'];\n}): Promise<MountedWoodSurface> {\n const page = input.runtime.page;\n const viewport = input.viewport ?? DEFAULT_VIEWPORT;\n await page.setViewportSize({ width: viewport.width, height: viewport.height });\n const entryPath = writeEntry(input.runtime.tempDir, input.name, input.entrySource);\n const htmlPath = writeHtml(input.runtime.tempDir, input.name, entryPath, input.css);\n await installTraceBinding(input.runtime.page, input.trace, input.traceContext ?? {});\n await installBridge(page, input.bridge.state, input.bridge.manifest, input.bridge.bridgeInit);\n const mountedBridge = createMountedBridge(page, input.bridge.state);\n const context: WoodBrowserPageContext = {\n page,\n bridge: mountedBridge,\n ...input.context,\n };\n await input.hooks?.beforeMount?.(context);\n await page.goto(`${serverBaseUrl(input.runtime.server)}${viteImport(htmlPath)}`, {\n waitUntil: 'domcontentloaded',\n });\n for (const event of input.bridge.state.initialEvents) {\n await mountedBridge.emit(event.channel, event.payload);\n }\n await input.hooks?.afterMount?.(context);\n\n return createSurface({\n page,\n bridge: mountedBridge,\n outputDir: path.join(input.runtime.rootDir, 'test-results', 'wood-browser'),\n destroy: async () => {\n await page.evaluate(() => {\n const app = window.__WOOD_BROWSER_APP__;\n if (app && typeof app === 'object' && typeof (app as { $destroy?: () => void }).$destroy === 'function') {\n (app as { $destroy: () => void }).$destroy();\n }\n }).catch(() => undefined);\n },\n });\n}\n\nasync function createHarness(config: WoodBrowserHarnessConfig = {}): Promise<WoodBrowserHarness> {\n const rootDir = normalizeRoot(config.rootDir);\n const browserName = config.browser?.browserName ?? 'chromium';\n const playwrightBrowser = await loadPlaywright(browserName);\n const launchedBrowser = await playwrightBrowser.launch({\n headless: config.browser?.headless ?? true,\n });\n const server = await createViteServer(rootDir);\n const page = await launchedBrowser.newPage({\n viewport: {\n width: config.viewport?.width ?? DEFAULT_VIEWPORT.width,\n height: config.viewport?.height ?? DEFAULT_VIEWPORT.height,\n deviceScaleFactor: config.viewport?.deviceScaleFactor,\n },\n });\n const tempDir = fs.mkdtempSync(path.join(rootDir, '.wood-browser-harness-'));\n const trace = config.trace?.enabled === true\n ? createTraceCapture({ maxEvents: config.trace.maxEvents })\n : undefined;\n const runtime: HarnessRuntime = {\n browser: launchedBrowser,\n server,\n page,\n tempDir,\n rootDir,\n };\n const css = (config.css ?? []).map((cssPath) => {\n const resolved = resolveFromRoot(rootDir, cssPath);\n assertFileExists(resolved, 'CSS file');\n return resolved;\n });\n\n return {\n page,\n ...(trace ? { trace } : {}),\n async mountRoute(routeKey: string, options: MountRouteOptions = {}): Promise<MountedWoodRoute> {\n const bridge = createBrowserBridgeFixtureWithTrace(mergeBridge(config.bridge, options.bridge), config.trace);\n const route = {\n params: options.route?.params ?? {},\n query: options.route?.query ?? {},\n };\n let entrySource: string;\n if (config.viewsConfig) {\n const viewsPath = resolveFromRoot(rootDir, config.viewsConfig);\n const views = await loadViews(viewsPath);\n const view = findView(views, routeKey);\n entrySource = buildRouteEntrySource({\n routeKey,\n view,\n rootDir,\n componentDir: config.componentDir,\n route,\n });\n } else if (config.generatedApp) {\n const generatedApp = resolveFromRoot(rootDir, config.generatedApp);\n assertFileExists(generatedApp, 'Generated app');\n entrySource = buildGeneratedAppEntrySource({ generatedApp, routeKey });\n } else {\n throw new Error('mountRoute requires either viewsConfig or generatedApp in browser.createHarness().');\n }\n\n const mounted = await openMountPage({\n runtime,\n entrySource,\n name: `route-${routeKey.replace(/[^a-zA-Z0-9_-]/g, '-')}`,\n css,\n viewport: options.viewport ?? config.viewport,\n bridge,\n trace,\n traceContext: {\n windowId: config.trace?.windowId ?? routeKey.split('.')[0],\n routeKey,\n harnessId: config.trace?.harnessId,\n },\n context: {\n route: {\n key: routeKey,\n params: route.params,\n query: route.query,\n },\n },\n hooks: {\n beforeMount: options.page?.beforeMount ?? config.page?.beforeMount,\n afterMount: options.page?.afterMount ?? config.page?.afterMount,\n },\n });\n return {\n ...mounted,\n routeKey,\n };\n },\n async mountComponent(component: string, options: MountComponentOptions = {}): Promise<MountedWoodComponent> {\n const componentPath = resolveFromRoot(rootDir, component);\n assertFileExists(componentPath, 'Component');\n const bridge = createBrowserBridgeFixtureWithTrace(mergeBridge(config.bridge, options.bridge), config.trace);\n return await openMountPage({\n runtime,\n entrySource: buildComponentEntrySource({\n componentPath,\n props: options.props ?? {},\n }),\n name: `component-${path.basename(componentPath).replace(/[^a-zA-Z0-9_-]/g, '-')}`,\n css,\n viewport: options.viewport ?? config.viewport,\n bridge,\n trace,\n traceContext: {\n windowId: config.trace?.windowId ?? 'wood-browser-harness',\n harnessId: config.trace?.harnessId,\n },\n context: {},\n hooks: {\n beforeMount: options.page?.beforeMount ?? config.page?.beforeMount,\n afterMount: options.page?.afterMount ?? config.page?.afterMount,\n },\n });\n },\n async mountTree(tree: MountTreeOptions): Promise<MountedWoodTree> {\n const bridge = createBrowserBridgeFixtureWithTrace(mergeBridge(config.bridge, tree.bridge), config.trace);\n const layouts = (tree.layouts ?? []).map((layout) => ({\n component: resolveFromRoot(rootDir, layout.component),\n props: layout.props,\n }));\n for (const layout of layouts) {\n assertFileExists(layout.component, 'Layout component');\n }\n const view = {\n component: resolveFromRoot(rootDir, tree.view.component),\n props: tree.view.props,\n };\n assertFileExists(view.component, 'View component');\n return await openMountPage({\n runtime,\n entrySource: buildTreeEntrySource({ layouts, view }),\n name: `tree-${path.basename(view.component).replace(/[^a-zA-Z0-9_-]/g, '-')}`,\n css,\n viewport: tree.viewport ?? config.viewport,\n bridge,\n trace,\n traceContext: {\n windowId: config.trace?.windowId ?? tree.page?.key?.split('.')[0] ?? 'wood-browser-harness',\n routeKey: tree.page?.key,\n harnessId: config.trace?.harnessId,\n },\n context: tree.page?.key\n ? {\n route: {\n key: tree.page.key,\n params: tree.page.params ?? {},\n query: tree.page.query ?? {},\n },\n }\n : {},\n hooks: config.page,\n });\n },\n async destroy(): Promise<void> {\n await page.close().catch(() => undefined);\n await server.close().catch(() => undefined);\n await launchedBrowser.close().catch(() => undefined);\n trace?.stop();\n fs.rmSync(tempDir, { recursive: true, force: true });\n },\n };\n}\n\nexport const browser = {\n createHarness,\n};\n\ndeclare global {\n interface Window {\n __WOOD_BROWSER_APP__?: unknown;\n __WOOD_BROWSER_DEFAULT_ROUTE__?: string;\n __WOOD_BROWSER_BRIDGE_MANIFEST__?: RuntimeBridgeManifestEntry[];\n __woodBrowserInvoke?: (channel: string, input: unknown) => Promise<unknown>;\n __woodBrowserEmit?: (channel: string, payload: unknown) => void;\n __woodBrowserTrace?: (payload: unknown) => void;\n }\n}\n"],"mappings":"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","names":["path","fs","mod","controllerPath"]}
1
+ {"version":3,"sources":["../../src/testing/browser.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport {\n dispatchRendererTracePayload,\n type TraceLevel,\n type TraceRuntimeContext,\n} from '../tracing/index.js';\nimport { TraceRecorder } from '../trace-testing/index.js';\nimport type { ViewDef } from '../types/views.cjs';\n\ntype MaybePromise<T> = T | Promise<T>;\ntype BrowserName = 'chromium' | 'firefox' | 'webkit';\ntype OperationHandler = unknown | ((\n input: unknown,\n context: WoodBridgeFixtureContext,\n) => MaybePromise<unknown>);\n\nexport interface WoodViewport {\n width: number;\n height: number;\n deviceScaleFactor?: number;\n}\n\nexport interface WoodBridgeRecordedCall {\n channel: string;\n input: unknown;\n timestamp: string;\n}\n\nexport interface WoodBridgeRecordedEvent {\n channel: string;\n payload: unknown;\n timestamp: string;\n}\n\nexport interface WoodBridgeFixtureContext {\n channel: string;\n calls: WoodBridgeRecordedCall[];\n}\n\nexport interface WoodBridgeFixtureInput {\n operations?: Record<string, OperationHandler>;\n events?: Record<string, unknown | unknown[]>;\n record?: boolean;\n}\n\nexport interface WoodBrowserPageContext {\n page: any;\n bridge: MountedWoodBridge;\n route?: {\n key: string;\n params: Record<string, string>;\n query: Record<string, string>;\n };\n}\n\nexport interface WoodBrowserHarnessConfig {\n rootDir?: string;\n generatedApp?: string;\n componentDir?: string;\n viewsConfig?: string;\n css?: string[];\n viewport?: WoodViewport;\n bridge?: WoodBridgeFixtureInput;\n page?: {\n beforeMount?: (context: WoodBrowserPageContext) => MaybePromise<void>;\n afterMount?: (context: WoodBrowserPageContext) => MaybePromise<void>;\n };\n browser?: {\n browserName?: BrowserName;\n headless?: boolean;\n };\n trace?: WoodBrowserTraceConfig;\n}\n\nexport interface WoodBrowserTraceConfig {\n enabled?: boolean;\n maxEvents?: number;\n minLevel?: TraceLevel;\n windowId?: string;\n harnessId?: string;\n}\n\nexport interface MountRouteOptions {\n route?: {\n params?: Record<string, string>;\n query?: Record<string, string>;\n };\n bridge?: WoodBridgeFixtureInput;\n viewport?: WoodViewport;\n page?: {\n beforeMount?: (context: WoodBrowserPageContext) => MaybePromise<void>;\n afterMount?: (context: WoodBrowserPageContext) => MaybePromise<void>;\n };\n}\n\nexport interface MountComponentOptions {\n props?: Record<string, unknown>;\n bridge?: WoodBridgeFixtureInput;\n viewport?: WoodViewport;\n page?: {\n beforeMount?: (context: WoodBrowserPageContext) => MaybePromise<void>;\n afterMount?: (context: WoodBrowserPageContext) => MaybePromise<void>;\n };\n}\n\nexport interface MountTreeOptions {\n layouts?: Array<{\n component: string;\n props?: Record<string, unknown>;\n }>;\n view: {\n component: string;\n props?: Record<string, unknown>;\n };\n page?: {\n key?: string;\n params?: Record<string, string>;\n query?: Record<string, string>;\n };\n bridge?: WoodBridgeFixtureInput;\n viewport?: WoodViewport;\n}\n\nexport interface MountedWoodBridge {\n emit(channel: string, payload: unknown): Promise<void>;\n calls(channel?: string): WoodBridgeRecordedCall[];\n events(channel?: string): WoodBridgeRecordedEvent[];\n setOperation(channel: string, handler: OperationHandler): void;\n clearCalls(): void;\n}\n\nexport interface MountedWoodNavigation {\n current(): Promise<{\n page: string;\n params: Record<string, string>;\n query: Record<string, string>;\n } | null>;\n go(\n page: string,\n params?: Record<string, string>,\n query?: Record<string, string>,\n ): Promise<void>;\n replace(\n page: string,\n params?: Record<string, string>,\n query?: Record<string, string>,\n ): Promise<void>;\n}\n\nexport interface WoodBrowserExpect {\n visible(selector: string): Promise<void>;\n hidden(selector: string): Promise<void>;\n insideViewport(selector: string, options?: { margin?: number }): Promise<void>;\n notClipped(selector: string): Promise<void>;\n notOverlapping(firstSelector: string, secondSelector: string): Promise<void>;\n zIndexAbove(topSelector: string, lowerSelector: string): Promise<void>;\n stableAfterAnimation(selector?: string, options?: {\n timeoutMs?: number;\n frameCount?: number;\n }): Promise<void>;\n screenshot(name: string, options?: {\n fullPage?: boolean;\n mask?: string[];\n threshold?: number;\n }): Promise<void>;\n}\n\nexport interface MountedWoodSurface {\n page: any;\n bridge: MountedWoodBridge;\n navigation: MountedWoodNavigation;\n expect: WoodBrowserExpect;\n locator(selector: string): any;\n click(selector: string): Promise<void>;\n fill(selector: string, value: string): Promise<void>;\n evaluate<T>(fn: () => T | Promise<T>): Promise<T>;\n destroy(): Promise<void>;\n}\n\nexport interface MountedWoodRoute extends MountedWoodSurface {\n routeKey: string;\n}\n\nexport type MountedWoodComponent = MountedWoodSurface;\nexport type MountedWoodTree = MountedWoodSurface;\n\nexport interface WoodBrowserHarness {\n page: any;\n traceRecorder?: TraceRecorder;\n mountRoute(routeKey: string, options?: MountRouteOptions): Promise<MountedWoodRoute>;\n mountComponent(component: string, options?: MountComponentOptions): Promise<MountedWoodComponent>;\n mountTree(tree: MountTreeOptions): Promise<MountedWoodTree>;\n destroy(): Promise<void>;\n}\n\ntype BridgeState = {\n operations: Map<string, OperationHandler>;\n initialEvents: WoodBridgeRecordedEvent[];\n calls: WoodBridgeRecordedCall[];\n emittedEvents: WoodBridgeRecordedEvent[];\n record: boolean;\n};\n\ntype RuntimeBridgeManifestEntry = {\n channel: string;\n controller: string;\n action: string;\n path: string[];\n hasInput: boolean;\n};\n\ntype HarnessRuntime = {\n context: any;\n server: any;\n page: any;\n tempDir: string;\n rootDir: string;\n};\n\ntype BrowserWorkerRuntime = {\n browser: any;\n server: any;\n cacheDir: string;\n rootDir: string;\n key: string;\n activeHarnesses: number;\n closing: boolean;\n idleCloseTimer: NodeJS.Timeout | null;\n};\n\ntype BrowserWorkerRuntimeEntry = {\n promise: Promise<BrowserWorkerRuntime>;\n};\n\nconst DEFAULT_VIEWPORT: WoodViewport = {\n width: 1280,\n height: 800,\n};\nconst DEFAULT_EXPECT_TIMEOUT_MS = 10_000;\nconst DEFAULT_RESOURCE_CLOSE_TIMEOUT_MS = 5_000;\nconst WORKER_RUNTIME_IDLE_CLOSE_MS = 5_000;\nconst browserWorkerRuntimes = new Map<string, BrowserWorkerRuntimeEntry>();\n\nfunction normalizeRoot(rootDir: string | undefined): string {\n return path.resolve(rootDir ?? process.cwd());\n}\n\nfunction resolveFromRoot(rootDir: string, filePath: string): string {\n return path.isAbsolute(filePath) ? filePath : path.resolve(rootDir, filePath);\n}\n\nfunction viteImport(filePath: string): string {\n return `/@fs/${filePath.replace(/\\\\/g, '/').replace(/^\\/+/, '')}`;\n}\n\nfunction mergeBridge(\n base: WoodBridgeFixtureInput | undefined,\n override: WoodBridgeFixtureInput | undefined,\n): WoodBridgeFixtureInput {\n return {\n operations: {\n ...(base?.operations ?? {}),\n ...(override?.operations ?? {}),\n },\n events: {\n ...(base?.events ?? {}),\n ...(override?.events ?? {}),\n },\n record: override?.record ?? base?.record,\n };\n}\n\nasync function waitForBoundedClose(promise: Promise<unknown>, timeoutMs = DEFAULT_RESOURCE_CLOSE_TIMEOUT_MS): Promise<void> {\n let timeout: NodeJS.Timeout | undefined;\n await Promise.race([\n promise.catch(() => undefined),\n new Promise<void>((resolve) => {\n timeout = setTimeout(resolve, timeoutMs);\n }),\n ]);\n if (timeout) {\n clearTimeout(timeout);\n }\n}\n\nfunction channelToManifestEntry(channel: string, handler: OperationHandler): RuntimeBridgeManifestEntry {\n const pathSegments = channel.split('.').filter(Boolean);\n const action = pathSegments.length > 0 ? pathSegments[pathSegments.length - 1] : channel;\n const controller = pathSegments.length > 1 ? pathSegments[0] : channel;\n\n return {\n channel,\n controller,\n action,\n path: pathSegments,\n hasInput: typeof handler === 'function',\n };\n}\n\nfunction createBridgeState(input: WoodBridgeFixtureInput | undefined): BridgeState {\n const initialEvents = Object.entries(input?.events ?? {}).flatMap(([channel, payloadOrList]) => {\n const payloads = Array.isArray(payloadOrList) ? payloadOrList : [payloadOrList];\n return payloads.map((payload) => ({\n channel,\n payload,\n timestamp: new Date().toISOString(),\n }));\n });\n\n return {\n operations: new Map(Object.entries(input?.operations ?? {})),\n initialEvents,\n calls: [],\n emittedEvents: [],\n record: input?.record ?? true,\n };\n}\n\nexport function createBrowserBridgeFixture(input: WoodBridgeFixtureInput | undefined): {\n state: BridgeState;\n manifest: RuntimeBridgeManifestEntry[];\n bridgeInit: string;\n} {\n return createBrowserBridgeFixtureWithTrace(input, undefined);\n}\n\nfunction createBrowserBridgeFixtureWithTrace(\n input: WoodBridgeFixtureInput | undefined,\n traceConfig: WoodBrowserTraceConfig | undefined,\n): {\n state: BridgeState;\n manifest: RuntimeBridgeManifestEntry[];\n bridgeInit: string;\n} {\n const state = createBridgeState(input);\n const manifest = Array.from(state.operations.entries())\n .map(([channel, handler]) => channelToManifestEntry(channel, handler));\n\n return {\n state,\n manifest,\n bridgeInit: buildBridgeInitScript(manifest, traceConfig),\n };\n}\n\nfunction buildBridgeInitScript(\n manifest: RuntimeBridgeManifestEntry[],\n traceConfig: WoodBrowserTraceConfig | undefined,\n): string {\n const traceEnabled = traceConfig?.enabled === true;\n const traceMinLevel = traceConfig?.minLevel ?? 'debug';\n return `\n(() => {\n const manifest = ${JSON.stringify(manifest)};\n const listeners = new Map();\n const bridge = {\n __rpcManifest: manifest,\n __woodConfig: async () => ({\n rendererTraceEnabled: ${JSON.stringify(traceEnabled)},\n rendererTraceMinLevel: ${JSON.stringify(traceMinLevel)}\n }),\n __log: () => {},\n __trace: (payload) => window.__woodBrowserTrace?.(payload),\n __window: {\n current: async () => ({ ok: true, windowId: 'wood-browser-harness', defaultRoute: window.__WOOD_BROWSER_DEFAULT_ROUTE__ }),\n open: async () => ({}),\n close: async () => ({}),\n focus: async () => ({}),\n minimize: async () => ({}),\n maximize: async () => ({}),\n },\n on(channel, callback) {\n if (!listeners.has(channel)) listeners.set(channel, new Set());\n listeners.get(channel).add(callback);\n return () => listeners.get(channel)?.delete(callback);\n },\n };\n\n for (const entry of manifest) {\n let current = bridge;\n for (let i = 0; i < entry.path.length - 1; i += 1) {\n const segment = entry.path[i];\n current[segment] ??= {};\n current = current[segment];\n }\n const leaf = entry.path[entry.path.length - 1];\n current[leaf] = async (data) => window.__woodBrowserInvoke(entry.channel, data);\n }\n\n window.__woodBrowserEmit = (channel, payload) => {\n const handlers = listeners.get(channel);\n if (!handlers) return;\n for (const handler of handlers) handler(payload);\n };\n window.__NOEGO_WOOD_BRIDGE__ = bridge;\n window.wood = bridge;\n globalThis.__NOEGO_WOOD_BRIDGE__ = bridge;\n globalThis.wood = bridge;\n})();\n`;\n}\n\nasync function loadPlaywright(browserName: BrowserName): Promise<any> {\n const playwright = await import('playwright');\n return playwright[browserName];\n}\n\nasync function loadVite(): Promise<any> {\n return await import('vite');\n}\n\nasync function loadSveltePlugin(rootDir: string): Promise<any | null> {\n try {\n const rootPluginPath = path.join(\n rootDir,\n 'node_modules',\n '@sveltejs',\n 'vite-plugin-svelte',\n 'src',\n 'index.js',\n );\n if (fs.existsSync(rootPluginPath)) {\n const mod = await import(pathToFileURL(rootPluginPath).href);\n return mod.svelte;\n }\n const rootRequire = createRequire(path.join(rootDir, 'package.json'));\n const mod = await import(pathToFileURL(rootRequire.resolve('@sveltejs/vite-plugin-svelte')).href);\n return mod.svelte;\n } catch {\n try {\n const mod = await import('@sveltejs/vite-plugin-svelte');\n return mod.svelte;\n } catch {\n return null;\n }\n }\n}\n\nasync function loadViews(viewsPath: string): Promise<ViewDef[]> {\n const parser = await import('../parser/views_parser.cjs');\n return parser.parseViewsFile(viewsPath);\n}\n\nasync function createViteServer(rootDir: string, cacheDir: string): Promise<any> {\n const vite = await loadVite();\n const svelte = await loadSveltePlugin(rootDir);\n const plugins = svelte ? [svelte()] : [];\n const server = await vite.createServer({\n root: rootDir,\n cacheDir,\n logLevel: process.env.WOOD_BROWSER_TEST_DEBUG ? 'info' : 'silent',\n plugins,\n resolve: {\n dedupe: ['svelte'],\n },\n server: { port: 0 },\n });\n await server.listen();\n return server;\n}\n\nfunction workerRuntimeKey(input: {\n rootDir: string;\n browserName: BrowserName;\n headless: boolean;\n}): string {\n return [\n input.rootDir,\n input.browserName,\n input.headless ? 'headless' : 'headed',\n ].join('::');\n}\n\nasync function createBrowserWorkerRuntime(input: {\n rootDir: string;\n browserName: BrowserName;\n headless: boolean;\n key: string;\n}): Promise<BrowserWorkerRuntime> {\n const cacheRoot = fs.mkdtempSync(path.join(input.rootDir, '.wood-browser-vite-'));\n let browser: any | null = null;\n try {\n const playwrightBrowser = await loadPlaywright(input.browserName);\n browser = await playwrightBrowser.launch({ headless: input.headless });\n const server = await createViteServer(input.rootDir, path.join(cacheRoot, '.vite'));\n return {\n browser,\n server,\n cacheDir: cacheRoot,\n rootDir: input.rootDir,\n key: input.key,\n activeHarnesses: 0,\n closing: false,\n idleCloseTimer: null,\n };\n } catch (error) {\n await browser?.close().catch(() => undefined);\n fs.rmSync(cacheRoot, { recursive: true, force: true });\n throw error;\n }\n}\n\nasync function acquireBrowserWorkerRuntime(input: {\n rootDir: string;\n browserName: BrowserName;\n headless: boolean;\n}): Promise<BrowserWorkerRuntime> {\n const key = workerRuntimeKey(input);\n let entry = browserWorkerRuntimes.get(key);\n if (!entry) {\n entry = {\n promise: createBrowserWorkerRuntime({ ...input, key }),\n };\n browserWorkerRuntimes.set(key, entry);\n }\n\n const runtime = await entry.promise;\n if (runtime.closing) {\n browserWorkerRuntimes.delete(key);\n return await acquireBrowserWorkerRuntime(input);\n }\n if (runtime.idleCloseTimer) {\n clearTimeout(runtime.idleCloseTimer);\n runtime.idleCloseTimer = null;\n }\n runtime.activeHarnesses += 1;\n return runtime;\n}\n\nasync function closeBrowserWorkerRuntime(runtime: BrowserWorkerRuntime): Promise<void> {\n if (runtime.closing) {\n return;\n }\n runtime.closing = true;\n try {\n await Promise.all([\n waitForBoundedClose(runtime.server.close()),\n waitForBoundedClose(runtime.browser.close()),\n ]);\n } finally {\n fs.rmSync(runtime.cacheDir, { recursive: true, force: true });\n const entry = browserWorkerRuntimes.get(runtime.key);\n const current = entry ? await entry.promise.catch(() => null) : null;\n if (current === runtime) {\n browserWorkerRuntimes.delete(runtime.key);\n }\n }\n}\n\nfunction scheduleBrowserWorkerRuntimeClose(runtime: BrowserWorkerRuntime): void {\n if (runtime.closing || runtime.activeHarnesses > 0 || runtime.idleCloseTimer) {\n return;\n }\n runtime.idleCloseTimer = setTimeout(() => {\n runtime.idleCloseTimer = null;\n if (runtime.activeHarnesses === 0) {\n void closeBrowserWorkerRuntime(runtime);\n }\n }, WORKER_RUNTIME_IDLE_CLOSE_MS);\n runtime.idleCloseTimer.unref?.();\n}\n\nfunction releaseBrowserWorkerRuntime(runtime: BrowserWorkerRuntime): void {\n runtime.activeHarnesses = Math.max(0, runtime.activeHarnesses - 1);\n scheduleBrowserWorkerRuntimeClose(runtime);\n}\n\nfunction serverBaseUrl(server: any): string {\n return server.resolvedUrls?.local?.[0] ?? 'http://127.0.0.1:5173';\n}\n\nfunction assertFileExists(filePath: string, label: string): void {\n if (!fs.existsSync(filePath)) {\n throw new Error(`${label} does not exist: ${filePath}`);\n }\n}\n\nfunction writeEntry(tempDir: string, name: string, source: string): string {\n fs.mkdirSync(tempDir, { recursive: true });\n const entryPath = path.join(tempDir, `${name}.ts`);\n fs.writeFileSync(entryPath, source);\n return entryPath;\n}\n\nfunction writeHtml(tempDir: string, name: string, entryPath: string, cssFiles: string[]): string {\n const styles = cssFiles\n .map((cssPath) => `<link rel=\"stylesheet\" href=\"${viteImport(cssPath)}\" />`)\n .join('\\n');\n const htmlPath = path.join(tempDir, `${name}.html`);\n fs.writeFileSync(htmlPath, `<!doctype html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n ${styles}\n </head>\n <body>\n <div id=\"app\"></div>\n <script type=\"module\" src=\"${viteImport(entryPath)}\"></script>\n </body>\n</html>\n`);\n return htmlPath;\n}\n\nfunction viewKey(view: ViewDef): string {\n return `${view.windowName}.${view.pageName}`;\n}\n\nfunction findView(views: ViewDef[], routeKey: string): ViewDef {\n const found = views.find((view) => viewKey(view) === routeKey);\n if (!found) {\n throw new Error(\n `Unknown Wood route \"${routeKey}\". Available routes: ${views.map(viewKey).join(', ')}`,\n );\n }\n return found;\n}\n\nfunction resolveComponentPath(rootDir: string, componentDir: string | undefined, filePath: string): string {\n if (path.isAbsolute(filePath)) return filePath;\n const base = componentDir ? resolveFromRoot(rootDir, componentDir) : rootDir;\n const componentDirName = componentDir ? path.basename(componentDir) : '';\n if (componentDirName && filePath.replace(/\\\\/g, '/').startsWith(`${componentDirName}/`)) {\n return path.resolve(base, filePath.slice(componentDirName.length + 1));\n }\n return path.resolve(base, filePath);\n}\n\nfunction buildRouteEntrySource(input: {\n routeKey: string;\n view: ViewDef;\n rootDir: string;\n componentDir?: string;\n route: {\n params: Record<string, string>;\n query: Record<string, string>;\n };\n}): string {\n const { view, rootDir, componentDir, routeKey, route } = input;\n const viewPath = resolveComponentPath(rootDir, componentDir, view.viewPath);\n const layoutPaths = view.layouts.map((layout) => resolveComponentPath(rootDir, componentDir, layout));\n const controllerPath = view.controller\n ? resolveComponentPath(rootDir, componentDir, view.controller)\n : null;\n const layoutControllerPaths = view.layoutControllers?.map((controller) =>\n controller ? resolveComponentPath(rootDir, componentDir, controller) : null,\n ) ?? [];\n\n const imports = [\n `import { mount } from 'svelte';`,\n `import NavigationShell from '@noego/wood/navigation-shell';`,\n `import { getContainer } from '@noego/wood/client';`,\n `import { getNavigation } from '@noego/wood/navigation';`,\n `import ViewComponent from '${viteImport(viewPath)}';`,\n ...layoutPaths.map((layoutPath, index) => `import Layout${index} from '${viteImport(layoutPath)}';`),\n ...(controllerPath ? [`import PageController from '${viteImport(controllerPath)}';`] : []),\n ...layoutControllerPaths.map((controllerPath, index) =>\n controllerPath ? `import LayoutController${index} from '${viteImport(controllerPath)}';` : '',\n ).filter(Boolean),\n ];\n\n const layoutControllerClasses = layoutControllerPaths.map((controllerPath, index) =>\n controllerPath ? `LayoutController${index}` : 'undefined',\n );\n\n return `${imports.join('\\n')}\n\nconst entry = {\n key: ${JSON.stringify(routeKey)},\n windowName: ${JSON.stringify(view.windowName)},\n pageName: ${JSON.stringify(view.pageName)},\n viewPath: ${JSON.stringify(view.viewPath)},\n layoutPaths: ${JSON.stringify(view.layouts)},\n layoutControllers: ${JSON.stringify(view.layoutControllers ?? [])},\n controller: ${JSON.stringify(view.controller)},\n layoutControllerClasses: [${layoutControllerClasses.join(', ')}],\n controllerClass: ${controllerPath ? 'PageController' : 'undefined'},\n view: ViewComponent,\n layouts: [${layoutPaths.map((_, index) => `Layout${index}`).join(', ')}],\n};\n\nfunction createScopedContainer() {\n const root = getContainer();\n const scope = root.extend();\n return scope;\n}\n\nasync function resolveController(ControllerClass) {\n if (!ControllerClass) return undefined;\n const scope = createScopedContainer();\n scope.registerClass?.(ControllerClass);\n return await scope.get(ControllerClass);\n}\n\nasync function createViewController(nextEntry) {\n return await resolveController(nextEntry.controllerClass);\n}\n\nasync function createLayoutControllers(nextEntry) {\n const controllers = [];\n for (const ControllerClass of nextEntry.layoutControllerClasses) {\n controllers.push(await resolveController(ControllerClass));\n }\n return controllers;\n}\n\nfunction getViewManifestEntry(nextKey) {\n return nextKey === entry.key ? entry : null;\n}\n\nconst navigation = getNavigation();\nnavigation.replace(entry.key, ${JSON.stringify(route.params)}, ${JSON.stringify(route.query)});\n\nconst controller = await createViewController(entry);\nconst layoutControllers = await createLayoutControllers(entry);\n\nwindow.__WOOD_BROWSER_APP__ = mount(NavigationShell, {\n target: document.getElementById('app'),\n props: {\n manifest: { [entry.key]: entry },\n initialEntry: entry,\n initialController: controller,\n initialLayoutControllers: layoutControllers,\n createViewControllerFn: createViewController,\n createLayoutControllersFn: createLayoutControllers,\n getViewManifestEntryFn: getViewManifestEntry,\n },\n});\n`;\n}\n\nfunction buildGeneratedAppEntrySource(input: {\n generatedApp: string;\n routeKey: string;\n}): string {\n return `import { app } from '${viteImport(input.generatedApp)}';\nwindow.__WOOD_BROWSER_DEFAULT_ROUTE__ = ${JSON.stringify(input.routeKey)};\nwindow.__WOOD_BROWSER_APP__ = await app(document.getElementById('app'));\n`;\n}\n\nfunction buildComponentEntrySource(input: {\n componentPath: string;\n props: Record<string, unknown>;\n}): string {\n return `import { mount } from 'svelte';\nimport Component from '${viteImport(input.componentPath)}';\nwindow.__WOOD_BROWSER_APP__ = mount(Component, {\n target: document.getElementById('app'),\n props: ${JSON.stringify(input.props)},\n});\n`;\n}\n\nfunction buildTreeEntrySource(input: {\n layouts: Array<{ component: string; props?: Record<string, unknown> }>;\n view: { component: string; props?: Record<string, unknown> };\n}): string {\n const imports = [\n `import { mount } from 'svelte';`,\n `import WoodRecursiveRender from '@noego/wood/recursive-render';`,\n `import ViewComponent from '${viteImport(input.view.component)}';`,\n ...input.layouts.map((layout, index) => `import Layout${index} from '${viteImport(layout.component)}';`),\n ];\n const layoutControllers = input.layouts.map((layout) => ({\n data: layout.props?.data,\n input: layout.props?.input,\n events: layout.props?.events,\n }));\n const viewController = {\n data: input.view.props?.data,\n input: input.view.props?.input,\n events: input.view.props?.events,\n };\n\n return `${imports.join('\\n')}\nwindow.__WOOD_BROWSER_APP__ = mount(WoodRecursiveRender, {\n target: document.getElementById('app'),\n props: {\n layouts: [${input.layouts.map((_, index) => `Layout${index}`).join(', ')}],\n layoutControllers: ${JSON.stringify(layoutControllers)},\n view: ViewComponent,\n controller: ${JSON.stringify(viewController)},\n },\n});\n`;\n}\n\nasync function installBridge(page: any, bridgeState: BridgeState, manifest: RuntimeBridgeManifestEntry[], bridgeInit: string): Promise<void> {\n await page.exposeBinding('__woodBrowserInvoke', async (_source: unknown, channel: string, input: unknown) => {\n const call = {\n channel,\n input,\n timestamp: new Date().toISOString(),\n };\n if (bridgeState.record) {\n bridgeState.calls.push(call);\n }\n const handler = bridgeState.operations.get(channel);\n if (typeof handler === 'function') {\n return await handler(input, { channel, calls: bridgeState.calls });\n }\n return handler;\n });\n await page.addInitScript(bridgeInit);\n await page.addInitScript((entries: RuntimeBridgeManifestEntry[]) => {\n window.__WOOD_BROWSER_BRIDGE_MANIFEST__ = entries;\n }, manifest);\n}\n\nasync function installTraceBinding(\n page: any,\n traceRecorder: TraceRecorder | undefined,\n context: TraceRuntimeContext,\n): Promise<void> {\n if (!traceRecorder) {\n return;\n }\n await page.exposeBinding('__woodBrowserTrace', async (_source: unknown, payload: unknown) => {\n dispatchRendererTracePayload(payload, context);\n });\n}\n\nfunction createMountedBridge(page: any, state: BridgeState): MountedWoodBridge {\n return {\n async emit(channel: string, payload: unknown): Promise<void> {\n state.emittedEvents.push({\n channel,\n payload,\n timestamp: new Date().toISOString(),\n });\n await page.evaluate(({ eventChannel, eventPayload }: { eventChannel: string; eventPayload: unknown }) => {\n window.__woodBrowserEmit?.(eventChannel, eventPayload);\n }, { eventChannel: channel, eventPayload: payload });\n },\n calls(channel?: string): WoodBridgeRecordedCall[] {\n return channel ? state.calls.filter((call) => call.channel === channel) : [...state.calls];\n },\n events(channel?: string): WoodBridgeRecordedEvent[] {\n return channel\n ? state.emittedEvents.filter((event) => event.channel === channel)\n : [...state.emittedEvents];\n },\n setOperation(channel: string, handler: OperationHandler): void {\n state.operations.set(channel, handler);\n },\n clearCalls(): void {\n state.calls.length = 0;\n },\n };\n}\n\nfunction createMountedNavigation(page: any): MountedWoodNavigation {\n return {\n async current() {\n return await page.evaluate(async () => {\n const specifier = '@noego/wood/navigation';\n const mod = await import(/* @vite-ignore */ specifier);\n return mod.getNavigation().getCurrent();\n });\n },\n async go(routePage, params = {}, query = {}) {\n await page.evaluate(async (payload: {\n routePage: string;\n params: Record<string, string>;\n query: Record<string, string>;\n }) => {\n const { routePage: nextPage, params: nextParams, query: nextQuery } = payload;\n const specifier = '@noego/wood/navigation';\n const mod = await import(/* @vite-ignore */ specifier);\n mod.getNavigation().go(nextPage, nextParams, nextQuery);\n }, { routePage, params, query });\n },\n async replace(routePage, params = {}, query = {}) {\n await page.evaluate(async (payload: {\n routePage: string;\n params: Record<string, string>;\n query: Record<string, string>;\n }) => {\n const { routePage: nextPage, params: nextParams, query: nextQuery } = payload;\n const specifier = '@noego/wood/navigation';\n const mod = await import(/* @vite-ignore */ specifier);\n mod.getNavigation().replace(nextPage, nextParams, nextQuery);\n }, { routePage, params, query });\n },\n };\n}\n\nasync function elementRect(page: any, selector: string): Promise<{\n top: number;\n left: number;\n right: number;\n bottom: number;\n width: number;\n height: number;\n} | null> {\n return await page.locator(selector).evaluate((element: Element) => {\n const rect = element.getBoundingClientRect();\n return {\n top: rect.top,\n left: rect.left,\n right: rect.right,\n bottom: rect.bottom,\n width: rect.width,\n height: rect.height,\n };\n }).catch(() => null);\n}\n\nfunction createExpect(page: any, outputDir: string): WoodBrowserExpect {\n async function waitForVisible(selector: string): Promise<void> {\n const locator = page.locator(selector).first();\n try {\n await locator.waitFor({ state: 'attached', timeout: DEFAULT_EXPECT_TIMEOUT_MS });\n } catch {\n throw new Error(`Expected \"${selector}\" to exist.`);\n }\n try {\n await locator.waitFor({ state: 'visible', timeout: DEFAULT_EXPECT_TIMEOUT_MS });\n } catch {\n throw new Error(`Expected \"${selector}\" to be visible.`);\n }\n }\n\n return {\n async visible(selector: string): Promise<void> {\n await waitForVisible(selector);\n },\n async hidden(selector: string): Promise<void> {\n try {\n await page.locator(selector).first().waitFor({\n state: 'hidden',\n timeout: DEFAULT_EXPECT_TIMEOUT_MS,\n });\n } catch {\n throw new Error(`Expected \"${selector}\" to be hidden.`);\n }\n },\n async insideViewport(selector: string, options: { margin?: number } = {}): Promise<void> {\n await waitForVisible(selector);\n const margin = options.margin ?? 0;\n const rect = await elementRect(page, selector);\n if (!rect) throw new Error(`Expected \"${selector}\" to exist.`);\n const viewport = page.viewportSize();\n if (!viewport) throw new Error('Cannot assert viewport bounds without a viewport.');\n if (\n rect.left < margin ||\n rect.top < margin ||\n rect.right > viewport.width - margin ||\n rect.bottom > viewport.height - margin\n ) {\n throw new Error(`Expected \"${selector}\" to be inside viewport. Rect: ${JSON.stringify(rect)}, viewport: ${JSON.stringify(viewport)}`);\n }\n },\n async notClipped(selector: string): Promise<void> {\n await waitForVisible(selector);\n const result = await page.locator(selector).first().evaluate((element: Element) => {\n const rect = element.getBoundingClientRect();\n let parent = element.parentElement;\n while (parent) {\n const style = window.getComputedStyle(parent);\n const clips = /(auto|hidden|scroll|clip)/.test(`${style.overflow}${style.overflowX}${style.overflowY}`);\n if (clips) {\n const parentRect = parent.getBoundingClientRect();\n if (\n rect.left < parentRect.left ||\n rect.top < parentRect.top ||\n rect.right > parentRect.right ||\n rect.bottom > parentRect.bottom\n ) {\n return { ok: false, parent: parent.tagName, rect, parentRect };\n }\n }\n parent = parent.parentElement;\n }\n return { ok: true };\n });\n if (!result.ok) {\n throw new Error(`Expected \"${selector}\" not to be clipped. ${JSON.stringify(result)}`);\n }\n },\n async notOverlapping(firstSelector: string, secondSelector: string): Promise<void> {\n await waitForVisible(firstSelector);\n await waitForVisible(secondSelector);\n const first = await elementRect(page, firstSelector);\n const second = await elementRect(page, secondSelector);\n if (!first || !second) throw new Error(`Expected both \"${firstSelector}\" and \"${secondSelector}\" to exist.`);\n const overlaps = first.left < second.right\n && first.right > second.left\n && first.top < second.bottom\n && first.bottom > second.top;\n if (overlaps) {\n throw new Error(`Expected \"${firstSelector}\" not to overlap \"${secondSelector}\".`);\n }\n },\n async zIndexAbove(topSelector: string, lowerSelector: string): Promise<void> {\n await waitForVisible(topSelector);\n await waitForVisible(lowerSelector);\n const values = await page.evaluate((payload: { topSelector: string; lowerSelector: string }) => {\n const { topSelector: top, lowerSelector: lower } = payload;\n const topElement = document.querySelector(top);\n const lowerElement = document.querySelector(lower);\n if (!topElement || !lowerElement) return null;\n return {\n top: Number(window.getComputedStyle(topElement).zIndex) || 0,\n lower: Number(window.getComputedStyle(lowerElement).zIndex) || 0,\n };\n }, { topSelector, lowerSelector });\n if (!values) throw new Error(`Expected both \"${topSelector}\" and \"${lowerSelector}\" to exist.`);\n if (values.top <= values.lower) {\n throw new Error(`Expected \"${topSelector}\" z-index (${values.top}) to be above \"${lowerSelector}\" (${values.lower}).`);\n }\n },\n async stableAfterAnimation(selector = 'body', options = {}): Promise<void> {\n const frameCount = options.frameCount ?? 3;\n const timeoutMs = options.timeoutMs ?? 2000;\n const startedAt = Date.now();\n let previous = '';\n let stableFrames = 0;\n while (Date.now() - startedAt < timeoutMs) {\n const current = JSON.stringify(await elementRect(page, selector));\n if (current === previous) {\n stableFrames += 1;\n if (stableFrames >= frameCount) return;\n } else {\n stableFrames = 0;\n previous = current;\n }\n await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => resolve(undefined))));\n }\n throw new Error(`Expected \"${selector}\" to become stable within ${timeoutMs}ms.`);\n },\n async screenshot(name: string, options = {}): Promise<void> {\n const outputPath = path.join(outputDir, `${name}.png`);\n await fs.promises.mkdir(outputDir, { recursive: true });\n const masks = options.mask?.map((selector) => page.locator(selector)) ?? [];\n await page.screenshot({\n path: outputPath,\n fullPage: options.fullPage ?? true,\n mask: masks,\n });\n },\n };\n}\n\nfunction createSurface(input: {\n page: any;\n bridge: MountedWoodBridge;\n outputDir: string;\n destroy: () => Promise<void>;\n}): MountedWoodSurface {\n return {\n page: input.page,\n bridge: input.bridge,\n navigation: createMountedNavigation(input.page),\n expect: createExpect(input.page, input.outputDir),\n locator(selector: string): any {\n return input.page.locator(selector);\n },\n async click(selector: string): Promise<void> {\n await input.page.locator(selector).click();\n },\n async fill(selector: string, value: string): Promise<void> {\n await input.page.locator(selector).fill(value);\n },\n async evaluate<T>(fn: () => T | Promise<T>): Promise<T> {\n return await input.page.evaluate(fn);\n },\n destroy: input.destroy,\n };\n}\n\nasync function openMountPage(input: {\n runtime: HarnessRuntime;\n entrySource: string;\n name: string;\n css: string[];\n viewport?: WoodViewport;\n bridge: {\n state: BridgeState;\n manifest: RuntimeBridgeManifestEntry[];\n bridgeInit: string;\n };\n traceRecorder?: TraceRecorder;\n traceContext?: TraceRuntimeContext;\n context: Omit<WoodBrowserPageContext, 'page' | 'bridge'>;\n hooks?: WoodBrowserHarnessConfig['page'];\n}): Promise<MountedWoodSurface> {\n const page = input.runtime.page;\n const viewport = input.viewport ?? DEFAULT_VIEWPORT;\n await page.setViewportSize({ width: viewport.width, height: viewport.height });\n const entryPath = writeEntry(input.runtime.tempDir, input.name, input.entrySource);\n const htmlPath = writeHtml(input.runtime.tempDir, input.name, entryPath, input.css);\n await installTraceBinding(input.runtime.page, input.traceRecorder, input.traceContext ?? {});\n await installBridge(page, input.bridge.state, input.bridge.manifest, input.bridge.bridgeInit);\n const mountedBridge = createMountedBridge(page, input.bridge.state);\n const context: WoodBrowserPageContext = {\n page,\n bridge: mountedBridge,\n ...input.context,\n };\n await input.hooks?.beforeMount?.(context);\n await page.goto(`${serverBaseUrl(input.runtime.server)}${viteImport(htmlPath)}`, {\n waitUntil: 'domcontentloaded',\n });\n for (const event of input.bridge.state.initialEvents) {\n await mountedBridge.emit(event.channel, event.payload);\n }\n await input.hooks?.afterMount?.(context);\n\n return createSurface({\n page,\n bridge: mountedBridge,\n outputDir: path.join(input.runtime.rootDir, 'test-results', 'wood-browser'),\n destroy: async () => {\n await page.evaluate(() => {\n const app = window.__WOOD_BROWSER_APP__;\n if (app && typeof app === 'object' && typeof (app as { $destroy?: () => void }).$destroy === 'function') {\n (app as { $destroy: () => void }).$destroy();\n }\n }).catch(() => undefined);\n },\n });\n}\n\nasync function createHarness(config: WoodBrowserHarnessConfig = {}): Promise<WoodBrowserHarness> {\n const rootDir = normalizeRoot(config.rootDir);\n const browserName = config.browser?.browserName ?? 'chromium';\n const headless = config.browser?.headless ?? true;\n const workerRuntime = await acquireBrowserWorkerRuntime({\n rootDir,\n browserName,\n headless,\n });\n let released = false;\n let destroyed = false;\n let tempDir: string | null = null;\n let context: any | null = null;\n let page: any | null = null;\n let traceRecorder: TraceRecorder | undefined;\n\n function releaseRuntimeOnce(): void {\n if (released) {\n return;\n }\n released = true;\n releaseBrowserWorkerRuntime(workerRuntime);\n }\n\n try {\n tempDir = fs.mkdtempSync(path.join(rootDir, '.wood-browser-harness-'));\n context = await workerRuntime.browser.newContext({\n viewport: {\n width: config.viewport?.width ?? DEFAULT_VIEWPORT.width,\n height: config.viewport?.height ?? DEFAULT_VIEWPORT.height,\n deviceScaleFactor: config.viewport?.deviceScaleFactor,\n },\n });\n page = await context.newPage();\n traceRecorder = config.trace?.enabled === true\n ? new TraceRecorder()\n : undefined;\n traceRecorder?.enable({ maxEvents: config.trace?.maxEvents });\n } catch (error) {\n if (page) await waitForBoundedClose(page.close());\n if (context) await waitForBoundedClose(context.close());\n if (tempDir) fs.rmSync(tempDir, { recursive: true, force: true });\n releaseRuntimeOnce();\n throw error;\n }\n\n const runtime: HarnessRuntime = {\n context,\n server: workerRuntime.server,\n page,\n tempDir,\n rootDir,\n };\n const css = (config.css ?? []).map((cssPath) => {\n const resolved = resolveFromRoot(rootDir, cssPath);\n assertFileExists(resolved, 'CSS file');\n return resolved;\n });\n\n return {\n page,\n ...(traceRecorder ? { traceRecorder } : {}),\n async mountRoute(routeKey: string, options: MountRouteOptions = {}): Promise<MountedWoodRoute> {\n const bridge = createBrowserBridgeFixtureWithTrace(mergeBridge(config.bridge, options.bridge), config.trace);\n const route = {\n params: options.route?.params ?? {},\n query: options.route?.query ?? {},\n };\n let entrySource: string;\n if (config.viewsConfig) {\n const viewsPath = resolveFromRoot(rootDir, config.viewsConfig);\n const views = await loadViews(viewsPath);\n const view = findView(views, routeKey);\n entrySource = buildRouteEntrySource({\n routeKey,\n view,\n rootDir,\n componentDir: config.componentDir,\n route,\n });\n } else if (config.generatedApp) {\n const generatedApp = resolveFromRoot(rootDir, config.generatedApp);\n assertFileExists(generatedApp, 'Generated app');\n entrySource = buildGeneratedAppEntrySource({ generatedApp, routeKey });\n } else {\n throw new Error('mountRoute requires either viewsConfig or generatedApp in browser.createHarness().');\n }\n\n const mounted = await openMountPage({\n runtime,\n entrySource,\n name: `route-${routeKey.replace(/[^a-zA-Z0-9_-]/g, '-')}`,\n css,\n viewport: options.viewport ?? config.viewport,\n bridge,\n traceRecorder,\n traceContext: {\n windowId: config.trace?.windowId ?? routeKey.split('.')[0],\n routeKey,\n harnessId: config.trace?.harnessId,\n },\n context: {\n route: {\n key: routeKey,\n params: route.params,\n query: route.query,\n },\n },\n hooks: {\n beforeMount: options.page?.beforeMount ?? config.page?.beforeMount,\n afterMount: options.page?.afterMount ?? config.page?.afterMount,\n },\n });\n return {\n ...mounted,\n routeKey,\n };\n },\n async mountComponent(component: string, options: MountComponentOptions = {}): Promise<MountedWoodComponent> {\n const componentPath = resolveFromRoot(rootDir, component);\n assertFileExists(componentPath, 'Component');\n const bridge = createBrowserBridgeFixtureWithTrace(mergeBridge(config.bridge, options.bridge), config.trace);\n return await openMountPage({\n runtime,\n entrySource: buildComponentEntrySource({\n componentPath,\n props: options.props ?? {},\n }),\n name: `component-${path.basename(componentPath).replace(/[^a-zA-Z0-9_-]/g, '-')}`,\n css,\n viewport: options.viewport ?? config.viewport,\n bridge,\n traceRecorder,\n traceContext: {\n windowId: config.trace?.windowId ?? 'wood-browser-harness',\n harnessId: config.trace?.harnessId,\n },\n context: {},\n hooks: {\n beforeMount: options.page?.beforeMount ?? config.page?.beforeMount,\n afterMount: options.page?.afterMount ?? config.page?.afterMount,\n },\n });\n },\n async mountTree(tree: MountTreeOptions): Promise<MountedWoodTree> {\n const bridge = createBrowserBridgeFixtureWithTrace(mergeBridge(config.bridge, tree.bridge), config.trace);\n const layouts = (tree.layouts ?? []).map((layout) => ({\n component: resolveFromRoot(rootDir, layout.component),\n props: layout.props,\n }));\n for (const layout of layouts) {\n assertFileExists(layout.component, 'Layout component');\n }\n const view = {\n component: resolveFromRoot(rootDir, tree.view.component),\n props: tree.view.props,\n };\n assertFileExists(view.component, 'View component');\n return await openMountPage({\n runtime,\n entrySource: buildTreeEntrySource({ layouts, view }),\n name: `tree-${path.basename(view.component).replace(/[^a-zA-Z0-9_-]/g, '-')}`,\n css,\n viewport: tree.viewport ?? config.viewport,\n bridge,\n traceRecorder,\n traceContext: {\n windowId: config.trace?.windowId ?? tree.page?.key?.split('.')[0] ?? 'wood-browser-harness',\n routeKey: tree.page?.key,\n harnessId: config.trace?.harnessId,\n },\n context: tree.page?.key\n ? {\n route: {\n key: tree.page.key,\n params: tree.page.params ?? {},\n query: tree.page.query ?? {},\n },\n }\n : {},\n hooks: config.page,\n });\n },\n async destroy(): Promise<void> {\n if (destroyed) {\n return;\n }\n destroyed = true;\n await Promise.all([\n waitForBoundedClose(page.close()),\n waitForBoundedClose(context.close()),\n ]);\n traceRecorder?.disable();\n fs.rmSync(tempDir, { recursive: true, force: true });\n releaseRuntimeOnce();\n },\n };\n}\n\nexport const browser = {\n createHarness,\n};\n\ndeclare global {\n interface Window {\n __WOOD_BROWSER_APP__?: unknown;\n __WOOD_BROWSER_DEFAULT_ROUTE__?: string;\n __WOOD_BROWSER_BRIDGE_MANIFEST__?: RuntimeBridgeManifestEntry[];\n __woodBrowserInvoke?: (channel: string, input: unknown) => Promise<unknown>;\n __woodBrowserEmit?: (channel: string, payload: unknown) => void;\n __woodBrowserTrace?: (payload: unknown) => void;\n }\n}\n"],"mappings":"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","names":["path","fs","mod","browser","controllerPath"]}