/** * Vue renderer adapter for Imhotep Playwright. * * Mounts a Vue component into a container div on the page. * Uses Vue's createApp API (Vue 3) or constructor (Vue 2) depending * on what is available in the injected Vue runtime. */ import { Page } from 'playwright' import type { SceneTarget } from 'imhotep-core/scene-target' import { RendererAdapter } from './renderers.js' export interface VueAdapterOptions { /** Id for this renderer instance (defaults to 'vue'). */ id?: string /** * Vue createApp entry point; used for capability detection at adapter * construction time (e.g. determining Vue 3 vs Vue 2). The actual runtime * must be exposed on the page as `window.__imhotepVueCreateApp`. */ createApp: (component: unknown, props?: Record) => { mount(selector: string): void unmount(): void } /** Container selector or id to mount into. */ containerSelector?: string } export function createVueAdapter(options: VueAdapterOptions): RendererAdapter { const { createApp, containerSelector = '#__imhotep-mount' } = options return { id: options.id ?? 'vue', async mount(page: Page, target: SceneTarget, input: unknown): Promise { if (target.kind !== 'vue-component') { throw new Error(`Vue adapter received non-Vue target: ${target.kind}`) } const component = (input as Record)?.component ?? target.componentId const props = (input as Record)?.props ?? input await page.evaluate( (args: { componentRef: string propsJson: string containerSelector: string }) => { const container = document.querySelector(args.containerSelector) if (!container) { throw new Error(`Mount container not found: ${args.containerSelector}`) } // Clear previous content. container.innerHTML = '' // Resolve Vue createApp BEFORE looking for the component. // This gives a clearer error when Vue is missing vs component missing. const createApp = (window as unknown as Record).__imhotepVueCreateApp as | ((component: unknown, props?: Record) => { mount(selector: string): void unmount(): void }) | undefined if (!createApp) { throw new Error( 'Vue createApp not found on window.__imhotepVueCreateApp. ' + 'Expose Vue.createApp as window.__imhotepVueCreateApp before mounting. ' + 'Example: window.__imhotepVueCreateApp = Vue.createApp' ) } // Resolve the component from the global scope. const componentMap = (window as unknown as Record).__imhotepComponents as | Record | undefined const Component = componentMap?.[args.componentRef] ?? (window as unknown as Record)[args.componentRef] if (!Component) { throw new Error( `Component "${args.componentRef}" not found on window. ` + `Expose it as window.__imhotepComponents = { "${args.componentRef}": MyComponent } ` + `or window["${args.componentRef}"] = MyComponent` ) } const parsedProps = JSON.parse(args.propsJson) const app = createApp(Component, parsedProps) app.mount(args.containerSelector) }, { componentRef: component as string, propsJson: JSON.stringify(props), containerSelector, } ) }, async unmount(page: Page): Promise { await page.evaluate((selector: string) => { // Attempt Vue 3 unmount if the element has a __vue_app__ property. const container = document.querySelector(selector) if (container) { const app = (container as unknown as Record).__vue_app__ as | { unmount(): void } | undefined if (app?.unmount) { app.unmount() } else { container.innerHTML = '' } } }, containerSelector) }, } }