import type { Ivya } from 'ivya' import type { SerializedConfig, VitestTestRunner, WorkerGlobalState } from 'vitest' import type { OTELCarrier, Traces } from 'vitest/internal/traces' import type { IframeOrchestrator } from './orchestrator' import type { CommandsManager } from './tester/tester-utils' import type { BrowserTraceAttempt } from './tester/trace' async function importId(id: string): Promise { const name = `/@id/${id}`.replace(/\\/g, '/') return getBrowserState().wrapModule(() => import(/* @vite-ignore */ name)) } async function importFs(id: string): Promise { const name = `/@fs/${id}`.replace(/\\/g, '/') return getBrowserState().wrapModule(() => import(/* @vite-ignore */ name)) } export const moduleRunner = { isBrowser: true, import: (id: string): Promise => { if (id[0] === '/' || id[1] === ':') { return importFs(id) } return importId(id) }, } export const now: () => number = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now export function getConfig(): SerializedConfig { return getBrowserState().config } export function ensureAwaited(promise: (error?: Error) => Promise): Promise { const test = getWorkerState().current if (!test || test.type !== 'test') { return promise() } let awaited = false const sourceError = new Error('STACK_TRACE_ERROR') test.onFinished ??= [] test.onFinished.push(() => { if (!awaited) { const error = new Error( `The call was not awaited. This method is asynchronous and must be awaited; otherwise, the call will not start to avoid unhandled rejections.`, ) error.stack = sourceError.stack?.replace(sourceError.message, error.message) throw error } }) // don't even start the promise if it's not awaited to not cause any unhanded promise rejections let promiseResult: Promise | undefined return { then(onFulfilled, onRejected) { awaited = true return (promiseResult ||= promise(sourceError)).then(onFulfilled, onRejected) }, catch(onRejected) { awaited = true return (promiseResult ||= promise(sourceError)).catch(onRejected) }, finally(onFinally) { awaited = true return (promiseResult ||= promise(sourceError)).finally(onFinally) }, [Symbol.toStringTag]: 'Promise', } satisfies Promise } // This object is the shared runtime bus for browser artifacts loaded into the // same page. Some browser code is built and served as independent bundles, so // getBrowserState() is the API they use to share runtime state instead of // relying on imports resolving to the same module instance. export interface BrowserRunnerState { files: string[] runningFiles: string[] config: SerializedConfig provider: string runner: VitestTestRunner viteConfig: { root: string } providedContext: string type: 'tester' | 'orchestrator' disposeExceptionTracker: () => void wrapModule: (module: () => T) => T iframeId?: string sessionId: string testerId: string otelCarrier?: OTELCarrier method: 'run' | 'collect' orchestrator?: IframeOrchestrator commands: CommandsManager activeTraceTaskIds: Set browserTraceAttempts: Map // lazily loaded only when traceView is enabled browserTraceDomSnapshot?: typeof import('rrweb-snapshot') // import started by the orchestrator so every tester reuses one module instance browserTraceDomSnapshotPromise?: Promise selectorEngine: Ivya traces: Traces cleanups: Array<() => unknown> cdp?: { on: (event: string, listener: (payload: any) => void) => void once: (event: string, listener: (payload: any) => void) => void off: (event: string, listener: (payload: any) => void) => void send: (method: string, params?: Record) => Promise emit: (event: string, payload: unknown) => void } aria: typeof import('ivya/aria') } /* @__NO_SIDE_EFFECTS__ */ export function getBrowserState(): BrowserRunnerState { // @ts-expect-error not typed global return window.__vitest_browser_runner__ } /* @__NO_SIDE_EFFECTS__ */ export function getOrchestratorState(): BrowserRunnerState { // @ts-expect-error not typed global return window.parent.__vitest_browser_runner__ } /* @__NO_SIDE_EFFECTS__ */ export function getWorkerState(): WorkerGlobalState { // @ts-expect-error not typed global const state = window.__vitest_worker__ if (!state) { throw new Error( 'Worker state is not found. This is an issue with Vitest. Please, open an issue.', ) } return state }