import type { BrowserContext, Page } from "playwright"; import { ExecutionLayer } from "@/workers/rpa/kernel/execution-layer"; import { SelectorGraph } from "@/workers/rpa/kernel/selector-graph"; import { StateEngine } from "@/workers/rpa/kernel/state-engine"; import { assertPlaywrightPage, assertRPAContext, isRPAContext, type IRPAContext, } from "@/workers/rpa/kernel/context-validator"; export type { IRPAContext } from "@/workers/rpa/kernel/context-validator"; /** RPA 运行期唯一状态容器(无模块级全局变量) */ export class RPAContext implements IRPAContext { readonly selectors: SelectorGraph; readonly exec: ExecutionLayer; readonly runtime: ExecutionLayer; readonly state: StateEngine; private widgetScrollAtMs = 0; constructor( readonly page: Page, readonly browserContext?: BrowserContext, ) { assertPlaywrightPage(page, "RPAContext.constructor(page)"); this.selectors = new SelectorGraph(); this.exec = ExecutionLayer.forPage(page); this.runtime = this.exec; this.state = new StateEngine(this); assertRPAContext(this, "RPAContext.constructor"); } static fromSession( pageOrCtx: Page | RPAContext, browserContext?: BrowserContext, ): RPAContext { return resolveRPAContext(pageOrCtx, browserContext); } getWidgetScrollAt(): number { return this.widgetScrollAtMs; } setWidgetScrollAt(ms: number): void { this.widgetScrollAtMs = ms; } } /** Page | RPAContext 统一解析(禁止把 Context 再包一层导致 page.locator 断裂) */ export function resolveRPAContext( pageOrCtx: Page | RPAContext, browserContext?: BrowserContext, ): RPAContext { if (isRPAContext(pageOrCtx)) { assertRPAContext(pageOrCtx); return pageOrCtx as RPAContext; } assertPlaywrightPage(pageOrCtx, "resolveRPAContext(page)"); const created = new RPAContext(pageOrCtx, browserContext); assertRPAContext(created, "RPAContext.fromSession"); return created; }