@noego/wood 0.3.0 → 0.4.1
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.
- package/dist/index.cjs +10 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -4
- package/dist/index.d.ts +1 -4
- package/dist/index.js +1 -7
- package/dist/index.js.map +1 -1
- package/dist/main/index.cjs +16 -2
- package/dist/main/index.cjs.map +1 -1
- package/dist/main/index.js +16 -2
- package/dist/main/index.js.map +1 -1
- package/dist/runtime/runtime.cjs +4 -4
- package/dist/runtime/runtime.cjs.map +1 -1
- package/dist/runtime/runtime.d.cts +2 -1
- package/dist/runtime/runtime.d.ts +2 -1
- package/dist/runtime/runtime.js +8 -5
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/testing/browser.cjs +45 -7
- package/dist/testing/browser.cjs.map +1 -1
- package/dist/testing/browser.d.cts +14 -1
- package/dist/testing/browser.d.ts +14 -1
- package/dist/testing/browser.js +48 -7
- package/dist/testing/browser.js.map +1 -1
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/trace-testing/index.cjs +39 -0
- package/dist/trace-testing/index.cjs.map +1 -0
- package/dist/trace-testing/index.d.cts +7 -0
- package/dist/trace-testing/index.d.ts +7 -0
- package/dist/trace-testing/index.js +19 -0
- package/dist/trace-testing/index.js.map +1 -0
- package/dist/trace-testing/trace_assertions.cjs +73 -0
- package/dist/trace-testing/trace_assertions.cjs.map +1 -0
- package/dist/trace-testing/trace_assertions.d.cts +15 -0
- package/dist/trace-testing/trace_assertions.d.ts +15 -0
- package/dist/trace-testing/trace_assertions.js +49 -0
- package/dist/trace-testing/trace_assertions.js.map +1 -0
- package/dist/trace-testing/trace_query.cjs +144 -0
- package/dist/trace-testing/trace_query.cjs.map +1 -0
- package/dist/trace-testing/trace_query.d.cts +24 -0
- package/dist/trace-testing/trace_query.d.ts +24 -0
- package/dist/trace-testing/trace_query.js +118 -0
- package/dist/trace-testing/trace_query.js.map +1 -0
- package/dist/trace-testing/trace_scenario.cjs +61 -0
- package/dist/trace-testing/trace_scenario.cjs.map +1 -0
- package/dist/trace-testing/trace_scenario.d.cts +27 -0
- package/dist/trace-testing/trace_scenario.d.ts +27 -0
- package/dist/trace-testing/trace_scenario.js +37 -0
- package/dist/trace-testing/trace_scenario.js.map +1 -0
- package/dist/tracing/index.cjs.map +1 -1
- package/dist/tracing/index.d.cts +1 -1
- package/dist/tracing/index.d.ts +1 -1
- package/dist/tracing/index.js.map +1 -1
- package/dist/tracing/trace_hub.cjs +19 -2
- package/dist/tracing/trace_hub.cjs.map +1 -1
- package/dist/tracing/trace_hub.d.cts +12 -2
- package/dist/tracing/trace_hub.d.ts +12 -2
- package/dist/tracing/trace_hub.js +19 -2
- package/dist/tracing/trace_hub.js.map +1 -1
- package/package.json +13 -1
|
@@ -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 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}\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 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 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),\n };\n}\n\nfunction buildBridgeInitScript(manifest: RuntimeBridgeManifestEntry[]): string {\n return `\n(() => {\n const manifest = ${JSON.stringify(manifest)};\n const listeners = new Map();\n const bridge = {\n __rpcManifest: manifest,\n __woodConfig: async () => ({ rendererTraceEnabled: false }),\n __log: () => {},\n __trace: () => {},\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\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 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 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 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 async mountRoute(routeKey: string, options: MountRouteOptions = {}): Promise<MountedWoodRoute> {\n const bridge = createBrowserBridgeFixture(mergeBridge(config.bridge, options.bridge));\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 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 = createBrowserBridgeFixture(mergeBridge(config.bridge, options.bridge));\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 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 = createBrowserBridgeFixture(mergeBridge(config.bridge, tree.bridge));\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 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 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 }\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 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,3 +1,6 @@
|
|
|
1
|
+
import { TraceCapture, TraceLevel } from '../tracing/trace_hub.cjs';
|
|
2
|
+
import 'rxjs';
|
|
3
|
+
|
|
1
4
|
type MaybePromise<T> = T | Promise<T>;
|
|
2
5
|
type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
|
3
6
|
type OperationHandler = unknown | ((input: unknown, context: WoodBridgeFixtureContext) => MaybePromise<unknown>);
|
|
@@ -50,6 +53,14 @@ interface WoodBrowserHarnessConfig {
|
|
|
50
53
|
browserName?: BrowserName;
|
|
51
54
|
headless?: boolean;
|
|
52
55
|
};
|
|
56
|
+
trace?: WoodBrowserTraceConfig;
|
|
57
|
+
}
|
|
58
|
+
interface WoodBrowserTraceConfig {
|
|
59
|
+
enabled?: boolean;
|
|
60
|
+
maxEvents?: number;
|
|
61
|
+
minLevel?: TraceLevel;
|
|
62
|
+
windowId?: string;
|
|
63
|
+
harnessId?: string;
|
|
53
64
|
}
|
|
54
65
|
interface MountRouteOptions {
|
|
55
66
|
route?: {
|
|
@@ -142,6 +153,7 @@ type MountedWoodComponent = MountedWoodSurface;
|
|
|
142
153
|
type MountedWoodTree = MountedWoodSurface;
|
|
143
154
|
interface WoodBrowserHarness {
|
|
144
155
|
page: any;
|
|
156
|
+
trace?: TraceCapture;
|
|
145
157
|
mountRoute(routeKey: string, options?: MountRouteOptions): Promise<MountedWoodRoute>;
|
|
146
158
|
mountComponent(component: string, options?: MountComponentOptions): Promise<MountedWoodComponent>;
|
|
147
159
|
mountTree(tree: MountTreeOptions): Promise<MountedWoodTree>;
|
|
@@ -177,7 +189,8 @@ declare global {
|
|
|
177
189
|
__WOOD_BROWSER_BRIDGE_MANIFEST__?: RuntimeBridgeManifestEntry[];
|
|
178
190
|
__woodBrowserInvoke?: (channel: string, input: unknown) => Promise<unknown>;
|
|
179
191
|
__woodBrowserEmit?: (channel: string, payload: unknown) => void;
|
|
192
|
+
__woodBrowserTrace?: (payload: unknown) => void;
|
|
180
193
|
}
|
|
181
194
|
}
|
|
182
195
|
|
|
183
|
-
export { type MountComponentOptions, type MountRouteOptions, type MountTreeOptions, type MountedWoodBridge, type MountedWoodComponent, type MountedWoodNavigation, type MountedWoodRoute, type MountedWoodSurface, type MountedWoodTree, type WoodBridgeFixtureContext, type WoodBridgeFixtureInput, type WoodBridgeRecordedCall, type WoodBridgeRecordedEvent, type WoodBrowserExpect, type WoodBrowserHarness, type WoodBrowserHarnessConfig, type WoodBrowserPageContext, type WoodViewport, browser, createBrowserBridgeFixture };
|
|
196
|
+
export { type MountComponentOptions, type MountRouteOptions, type MountTreeOptions, type MountedWoodBridge, type MountedWoodComponent, type MountedWoodNavigation, type MountedWoodRoute, type MountedWoodSurface, type MountedWoodTree, type WoodBridgeFixtureContext, type WoodBridgeFixtureInput, type WoodBridgeRecordedCall, type WoodBridgeRecordedEvent, type WoodBrowserExpect, type WoodBrowserHarness, type WoodBrowserHarnessConfig, type WoodBrowserPageContext, type WoodBrowserTraceConfig, type WoodViewport, browser, createBrowserBridgeFixture };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { TraceCapture, TraceLevel } from '../tracing/trace_hub.js';
|
|
2
|
+
import 'rxjs';
|
|
3
|
+
|
|
1
4
|
type MaybePromise<T> = T | Promise<T>;
|
|
2
5
|
type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
|
3
6
|
type OperationHandler = unknown | ((input: unknown, context: WoodBridgeFixtureContext) => MaybePromise<unknown>);
|
|
@@ -50,6 +53,14 @@ interface WoodBrowserHarnessConfig {
|
|
|
50
53
|
browserName?: BrowserName;
|
|
51
54
|
headless?: boolean;
|
|
52
55
|
};
|
|
56
|
+
trace?: WoodBrowserTraceConfig;
|
|
57
|
+
}
|
|
58
|
+
interface WoodBrowserTraceConfig {
|
|
59
|
+
enabled?: boolean;
|
|
60
|
+
maxEvents?: number;
|
|
61
|
+
minLevel?: TraceLevel;
|
|
62
|
+
windowId?: string;
|
|
63
|
+
harnessId?: string;
|
|
53
64
|
}
|
|
54
65
|
interface MountRouteOptions {
|
|
55
66
|
route?: {
|
|
@@ -142,6 +153,7 @@ type MountedWoodComponent = MountedWoodSurface;
|
|
|
142
153
|
type MountedWoodTree = MountedWoodSurface;
|
|
143
154
|
interface WoodBrowserHarness {
|
|
144
155
|
page: any;
|
|
156
|
+
trace?: TraceCapture;
|
|
145
157
|
mountRoute(routeKey: string, options?: MountRouteOptions): Promise<MountedWoodRoute>;
|
|
146
158
|
mountComponent(component: string, options?: MountComponentOptions): Promise<MountedWoodComponent>;
|
|
147
159
|
mountTree(tree: MountTreeOptions): Promise<MountedWoodTree>;
|
|
@@ -177,7 +189,8 @@ declare global {
|
|
|
177
189
|
__WOOD_BROWSER_BRIDGE_MANIFEST__?: RuntimeBridgeManifestEntry[];
|
|
178
190
|
__woodBrowserInvoke?: (channel: string, input: unknown) => Promise<unknown>;
|
|
179
191
|
__woodBrowserEmit?: (channel: string, payload: unknown) => void;
|
|
192
|
+
__woodBrowserTrace?: (payload: unknown) => void;
|
|
180
193
|
}
|
|
181
194
|
}
|
|
182
195
|
|
|
183
|
-
export { type MountComponentOptions, type MountRouteOptions, type MountTreeOptions, type MountedWoodBridge, type MountedWoodComponent, type MountedWoodNavigation, type MountedWoodRoute, type MountedWoodSurface, type MountedWoodTree, type WoodBridgeFixtureContext, type WoodBridgeFixtureInput, type WoodBridgeRecordedCall, type WoodBridgeRecordedEvent, type WoodBrowserExpect, type WoodBrowserHarness, type WoodBrowserHarnessConfig, type WoodBrowserPageContext, type WoodViewport, browser, createBrowserBridgeFixture };
|
|
196
|
+
export { type MountComponentOptions, type MountRouteOptions, type MountTreeOptions, type MountedWoodBridge, type MountedWoodComponent, type MountedWoodNavigation, type MountedWoodRoute, type MountedWoodSurface, type MountedWoodTree, type WoodBridgeFixtureContext, type WoodBridgeFixtureInput, type WoodBridgeRecordedCall, type WoodBridgeRecordedEvent, type WoodBrowserExpect, type WoodBrowserHarness, type WoodBrowserHarnessConfig, type WoodBrowserPageContext, type WoodBrowserTraceConfig, type WoodViewport, browser, createBrowserBridgeFixture };
|
package/dist/testing/browser.js
CHANGED
|
@@ -2,6 +2,10 @@ import fs from "node:fs";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
|
+
import {
|
|
6
|
+
createTraceCapture,
|
|
7
|
+
dispatchRendererTracePayload
|
|
8
|
+
} from "../tracing/index.js";
|
|
5
9
|
const DEFAULT_VIEWPORT = {
|
|
6
10
|
width: 1280,
|
|
7
11
|
height: 800
|
|
@@ -58,24 +62,32 @@ function createBridgeState(input) {
|
|
|
58
62
|
};
|
|
59
63
|
}
|
|
60
64
|
function createBrowserBridgeFixture(input) {
|
|
65
|
+
return createBrowserBridgeFixtureWithTrace(input, void 0);
|
|
66
|
+
}
|
|
67
|
+
function createBrowserBridgeFixtureWithTrace(input, traceConfig) {
|
|
61
68
|
const state = createBridgeState(input);
|
|
62
69
|
const manifest = Array.from(state.operations.entries()).map(([channel, handler]) => channelToManifestEntry(channel, handler));
|
|
63
70
|
return {
|
|
64
71
|
state,
|
|
65
72
|
manifest,
|
|
66
|
-
bridgeInit: buildBridgeInitScript(manifest)
|
|
73
|
+
bridgeInit: buildBridgeInitScript(manifest, traceConfig)
|
|
67
74
|
};
|
|
68
75
|
}
|
|
69
|
-
function buildBridgeInitScript(manifest) {
|
|
76
|
+
function buildBridgeInitScript(manifest, traceConfig) {
|
|
77
|
+
const traceEnabled = traceConfig?.enabled === true;
|
|
78
|
+
const traceMinLevel = traceConfig?.minLevel ?? "debug";
|
|
70
79
|
return `
|
|
71
80
|
(() => {
|
|
72
81
|
const manifest = ${JSON.stringify(manifest)};
|
|
73
82
|
const listeners = new Map();
|
|
74
83
|
const bridge = {
|
|
75
84
|
__rpcManifest: manifest,
|
|
76
|
-
__woodConfig: async () => ({
|
|
85
|
+
__woodConfig: async () => ({
|
|
86
|
+
rendererTraceEnabled: ${JSON.stringify(traceEnabled)},
|
|
87
|
+
rendererTraceMinLevel: ${JSON.stringify(traceMinLevel)}
|
|
88
|
+
}),
|
|
77
89
|
__log: () => {},
|
|
78
|
-
__trace: () =>
|
|
90
|
+
__trace: (payload) => window.__woodBrowserTrace?.(payload),
|
|
79
91
|
__window: {
|
|
80
92
|
current: async () => ({ ok: true, windowId: 'wood-browser-harness', defaultRoute: window.__WOOD_BROWSER_DEFAULT_ROUTE__ }),
|
|
81
93
|
open: async () => ({}),
|
|
@@ -373,6 +385,14 @@ async function installBridge(page, bridgeState, manifest, bridgeInit) {
|
|
|
373
385
|
window.__WOOD_BROWSER_BRIDGE_MANIFEST__ = entries;
|
|
374
386
|
}, manifest);
|
|
375
387
|
}
|
|
388
|
+
async function installTraceBinding(page, trace, context) {
|
|
389
|
+
if (!trace) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
await page.exposeBinding("__woodBrowserTrace", async (_source, payload) => {
|
|
393
|
+
dispatchRendererTracePayload(payload, context);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
376
396
|
function createMountedBridge(page, state) {
|
|
377
397
|
return {
|
|
378
398
|
async emit(channel, payload) {
|
|
@@ -576,6 +596,7 @@ async function openMountPage(input) {
|
|
|
576
596
|
await page.setViewportSize({ width: viewport.width, height: viewport.height });
|
|
577
597
|
const entryPath = writeEntry(input.runtime.tempDir, input.name, input.entrySource);
|
|
578
598
|
const htmlPath = writeHtml(input.runtime.tempDir, input.name, entryPath, input.css);
|
|
599
|
+
await installTraceBinding(input.runtime.page, input.trace, input.traceContext ?? {});
|
|
579
600
|
await installBridge(page, input.bridge.state, input.bridge.manifest, input.bridge.bridgeInit);
|
|
580
601
|
const mountedBridge = createMountedBridge(page, input.bridge.state);
|
|
581
602
|
const context = {
|
|
@@ -621,6 +642,7 @@ async function createHarness(config = {}) {
|
|
|
621
642
|
}
|
|
622
643
|
});
|
|
623
644
|
const tempDir = fs.mkdtempSync(path.join(rootDir, ".wood-browser-harness-"));
|
|
645
|
+
const trace = config.trace?.enabled === true ? createTraceCapture({ maxEvents: config.trace.maxEvents }) : void 0;
|
|
624
646
|
const runtime = {
|
|
625
647
|
browser: launchedBrowser,
|
|
626
648
|
server,
|
|
@@ -635,8 +657,9 @@ async function createHarness(config = {}) {
|
|
|
635
657
|
});
|
|
636
658
|
return {
|
|
637
659
|
page,
|
|
660
|
+
...trace ? { trace } : {},
|
|
638
661
|
async mountRoute(routeKey, options = {}) {
|
|
639
|
-
const bridge =
|
|
662
|
+
const bridge = createBrowserBridgeFixtureWithTrace(mergeBridge(config.bridge, options.bridge), config.trace);
|
|
640
663
|
const route = {
|
|
641
664
|
params: options.route?.params ?? {},
|
|
642
665
|
query: options.route?.query ?? {}
|
|
@@ -667,6 +690,12 @@ async function createHarness(config = {}) {
|
|
|
667
690
|
css,
|
|
668
691
|
viewport: options.viewport ?? config.viewport,
|
|
669
692
|
bridge,
|
|
693
|
+
trace,
|
|
694
|
+
traceContext: {
|
|
695
|
+
windowId: config.trace?.windowId ?? routeKey.split(".")[0],
|
|
696
|
+
routeKey,
|
|
697
|
+
harnessId: config.trace?.harnessId
|
|
698
|
+
},
|
|
670
699
|
context: {
|
|
671
700
|
route: {
|
|
672
701
|
key: routeKey,
|
|
@@ -687,7 +716,7 @@ async function createHarness(config = {}) {
|
|
|
687
716
|
async mountComponent(component, options = {}) {
|
|
688
717
|
const componentPath = resolveFromRoot(rootDir, component);
|
|
689
718
|
assertFileExists(componentPath, "Component");
|
|
690
|
-
const bridge =
|
|
719
|
+
const bridge = createBrowserBridgeFixtureWithTrace(mergeBridge(config.bridge, options.bridge), config.trace);
|
|
691
720
|
return await openMountPage({
|
|
692
721
|
runtime,
|
|
693
722
|
entrySource: buildComponentEntrySource({
|
|
@@ -698,6 +727,11 @@ async function createHarness(config = {}) {
|
|
|
698
727
|
css,
|
|
699
728
|
viewport: options.viewport ?? config.viewport,
|
|
700
729
|
bridge,
|
|
730
|
+
trace,
|
|
731
|
+
traceContext: {
|
|
732
|
+
windowId: config.trace?.windowId ?? "wood-browser-harness",
|
|
733
|
+
harnessId: config.trace?.harnessId
|
|
734
|
+
},
|
|
701
735
|
context: {},
|
|
702
736
|
hooks: {
|
|
703
737
|
beforeMount: options.page?.beforeMount ?? config.page?.beforeMount,
|
|
@@ -706,7 +740,7 @@ async function createHarness(config = {}) {
|
|
|
706
740
|
});
|
|
707
741
|
},
|
|
708
742
|
async mountTree(tree) {
|
|
709
|
-
const bridge =
|
|
743
|
+
const bridge = createBrowserBridgeFixtureWithTrace(mergeBridge(config.bridge, tree.bridge), config.trace);
|
|
710
744
|
const layouts = (tree.layouts ?? []).map((layout) => ({
|
|
711
745
|
component: resolveFromRoot(rootDir, layout.component),
|
|
712
746
|
props: layout.props
|
|
@@ -726,6 +760,12 @@ async function createHarness(config = {}) {
|
|
|
726
760
|
css,
|
|
727
761
|
viewport: tree.viewport ?? config.viewport,
|
|
728
762
|
bridge,
|
|
763
|
+
trace,
|
|
764
|
+
traceContext: {
|
|
765
|
+
windowId: config.trace?.windowId ?? tree.page?.key?.split(".")[0] ?? "wood-browser-harness",
|
|
766
|
+
routeKey: tree.page?.key,
|
|
767
|
+
harnessId: config.trace?.harnessId
|
|
768
|
+
},
|
|
729
769
|
context: tree.page?.key ? {
|
|
730
770
|
route: {
|
|
731
771
|
key: tree.page.key,
|
|
@@ -740,6 +780,7 @@ async function createHarness(config = {}) {
|
|
|
740
780
|
await page.close().catch(() => void 0);
|
|
741
781
|
await server.close().catch(() => void 0);
|
|
742
782
|
await launchedBrowser.close().catch(() => void 0);
|
|
783
|
+
trace?.stop();
|
|
743
784
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
744
785
|
}
|
|
745
786
|
};
|