
Playwright ElectronApplication API 深度解析自动化 Electron 主进程与窗口管理【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwrightElectronApplication是 Playwright 实验性 Electron 支持中的核心对象它代表一个正在运行的 Electron 应用实例允许你直接对 Electron 主进程执行表达式evaluate、获取原生BrowserWindow句柄并把应用打开的每个窗口当作标准的 PlaywrightPage来操作。本文以 API 参考文档 class-electronapplication.md 为主体结合服务端实现 electron.ts 与测试用例 electron-app.spec.ts完整讲解ElectronApplication的全部事件与方法、底层双通道连接原理以及原生对话框 Mock 等实战技巧。如何获得 ElectronApplication 实例ElectronApplication只能通过Electron.launch()获得。Electron 命名空间通过playwright包的_electron导出访问自 v1.9 起提供const { _electron: electron } require(playwright); (async () { // Launch Electron app. const electronApp await electron.launch({ args: [main.js] }); // Evaluation expression in the Electron context. const appPath await electronApp.evaluate(async ({ app }) { // This runs in the main Electron process, parameter here is always // the result of the require(electron) in the main app script. return app.getAppPath(); }); console.log(appPath); // Get the first window that the app opens, wait if necessary. const window await electronApp.firstWindow(); // Print the title. console.log(await window.title()); // Capture a screenshot. await window.screenshot({ path: intro.png }); // Direct Electron console to Node terminal. window.on(console, console.log); // Click button. await window.click(textClick me); // Exit app. await electronApp.close(); })();这个例子覆盖了ElectronApplication最常见的四种能力evaluate()在 Electron 主进程中执行表达式回调的第一个参数固定是主进程require(electron)的结果因此可以直接访问app、BrowserWindow、clipboard、dialog等全部主进程 APIfirstWindow()等待应用打开的第一个窗口返回Page对象即可使用title()、screenshot()、click()等全部页面自动化能力window.on(console, ...)桥接窗口内页面的 console 输出close()关闭整个应用。从源码结构看_electron导出在 index.mjs 中直接透传自内部 Playwright 实例客户端侧在 client/playwright.ts 中初始化为Electron对象launch()的详细参数executablePath、args、cwd、env、timeout以及一批 context 级选项请参阅姊妹文档 class-electron.md。需要说明的是Playwright 对 Electron 的支持是实验性experimental的官方文档中列出的受支持版本为 Electron v12.2.0、v13.4.0、v14。若应用在 launch 阶段超时可检查nodeCliInspectFuseV1Options.EnableNodeCliInspectArgumentsfuse 是否被设置成了false。主进程控制evaluate、evaluateHandle 与 processevaluate(expression, arg)evaluate()v1.9返回Serializable在主进程执行你传入的表达式或函数并返回其结果如果传入的函数返回Promiseevaluate()会等待其 resolve 后返回结果如果返回值不可序列化则返回undefinedPlaywright 额外支持传输几个 JSON 无法表达的值-0、NaN、Infinity、-Infinity第二个参数arg是可选的会作为回调的第二个参数传入。从实现上看Playwright 在 Node 侧 DevTools 会话收到Runtime.executionContextCreated事件后会主动求值require(electron)对 Electron 28 还必须携带includeCommandLineAPI: true才能拿到require并把结果包装为一个ElectronModule类型的JSHandle见 electron.ts 第 74-86 行。这也解释了为什么evaluate()回调的第一个参数“总是”require(electron)的结果——它是框架预先准备好的模块句柄而非普通注入变量。evaluate的典型用法来自 electron-app.spec.ts// 读取应用路径 const appPath await app.evaluate(async ({ app }) app.getAppPath()); // 操作剪贴板传参 arg 作为第二个参数 await app.evaluate(async ({ clipboard }, text) clipboard.writeText(text), Hello from Playwright); const text await app.evaluate(async ({ clipboard }) clipboard.readText()); // 通过 evaluate 优雅退出应用 await electronApp.evaluate(({ app }) app.quit()); // 在测试中动态创建窗口 await electronApp.evaluate(({ BrowserWindow }) { const window new BrowserWindow({ width: 800, height: 600 }); void window.loadURL(data:text/html,titleHello World!/title); });TypeScript 下回调参数的类型会被自动推断evaluate无参/带参两种重载均有独立类型见文档中%%-evaluate-expression-%%与%%-js-electron-evaluate-workerfunction-%%两个参数变体测试should infer evaluate types验证了无参、带参、异步、以及把JSHandle作为arg传入会在 Electron 侧自动解包等场景。evaluateHandle(expression, arg)evaluateHandle()v1.9返回JSHandle与evaluate()的唯一区别是返回值以JSHandle形式交给调用者返回Promise时同样会等待其 resolve拿到的JSHandle可以继续参与后续evaluate或调用jsonValue()、evaluate()等方法。const handle await app.evaluateHandle(({ app }) app); // 在主进程内比较同一个 app 实例 expect(await app.evaluate(({ app }, appHandle) app appHandle, appHandle)).toBeTruthy(); const jsonValue await (await app.evaluateHandle(({ app }) ({ path: app.getAppPath() }))).jsonValue();process()process()v1.21返回该 Electron 应用主进程对应的 NodeChildProcess。这让你可以监听进程级事件exit、close、读取标准输出等const closePromise new Promise(f electronApp.process().on(close, f)); await electronApp.evaluate(({ app }) app.quit()); await closePromise;窗口管理windows、firstWindow、browserWindow 与 window 事件ElectronApplication下的每个窗口都被建模为一个 PlaywrightPage文档中提供了三个便捷方法加一个事件event: window每当 Electron 中有一个窗口被创建并且加载完成时触发参数为Page。源码中该事件由 Chromium 侧的BrowserContext.Events.Page转接而来见 electron.ts 第 88 行_browserContext.on(BrowserContext.Events.Page, ...)。注意一个细节window事件对应的Page的close()并不走默认的Target.closeTarget该调用在窗口关闭与导航提交竞态时可能挂起而是被替换为“从主进程关闭”的自定义逻辑——通过webContents.fromDevToolsTargetId(targetId)找到对应的webContents再调用wc.close({ waitForBeforeUnload })见 electron.ts 第 141-161 行_onPage方法。method: firstWindow({ timeout })等待应用打开的第一个窗口返回Page。v1.33 起支持timeout选项最大等待毫秒数默认3000030 秒传0禁用超时默认值可通过BrowserContext.setDefaultTimeout修改。const electronApp await electron.launch({ args: [main.js] }); const window await electronApp.firstWindow();测试should wait for first window展示了配合evaluate动态建窗的典型流程先用evaluate在主进程new BrowserWindow(...)再firstWindow()拿到刚加载完成的Page并断言title()。method: windows()返回当前所有已打开窗口的Page数组const [mainPage, secondPage] electronApp.windows();对BrowserView这类附加到同一BrowserWindow的视图windows()同样会为其单独建一个Page而browserWindow()会返回它们共同的原生窗口测试should create page for browser view与should return same browser window for browser view pages验证了这一点。method: browserWindow(page)v1.11传入一个 PlaywrightPage返回与之对应的原生BrowserWindow对象以JSHandle形式可用于访问bw.id、bw.title、bw.setMenuBarVisibility(...)等 Playwright 页面 API 覆盖不到的 Electron 能力const page await electronApp.firstWindow(); const bwHandle await electronApp.browserWindow(page); const title await bwHandle.evaluate((bw: BrowserWindow) bw.title);实现上它先取Page底层 Chromium 目标的targetId再通过webContents.fromDevToolsTargetId(targetId)找到webContents最后用BrowserWindow.fromWebContents(wc)还原原生窗口见 electron.ts 第 163-171 行browserWindow方法。从源码结构看由于 Electron 一定基于 Chromium这里直接假设page.delegate是CRPage类型。事件订阅close 与 consoleevent: close应用进程终止时发出。触发途径有三种测试should fire close event ...系列用例确认三者都会按序产生application(close)、context(close)、process(exit)三个事件调用electronApp.close()调用electronApp.context().close()应用自己app.quit()退出。close()方法v1.9关闭 Electron 应用。从 electron.ts 的构造逻辑看Playwright 给底层BrowserContext设置了自定义关闭处理器先通过主进程句柄执行app.quit()而非直接 kill 进程再关闭 Node 侧 DevTools 连接并等待进程退出重复调用close()是幂等的 no-op。event: consolev1.42当 Electron主进程内的 JavaScript 调用consoleAPIconsole.log、console.dir等时发出参数为ConsoleMessage。主进程中console.log传入的参数都可通过事件处理函数里的ConsoleMessage.args()获取electronApp.on(console, async msg { const values []; for (const arg of msg.args()) values.push(await arg.jsonValue()); console.log(...values); }); await electronApp.evaluate(() console.log(hello, 5, { foo: bar }));服务端由Runtime.consoleAPICalled协议事件转接见 electron.ts 第 98-120 行_onConsoleAPI其中有一个值得了解的过滤规则executionContextId 0的报文会被直接丢弃。这些报文是 DevTools 协议缓存的“最近 1000 条”历史 console 消息即使执行上下文已被移除也会被上报此时既无法解析参数也会先于测试代码订阅事件而被投递因此忽略它们。测试should fire console events验证了log/debug/info/error/warn五种类型都能被捕获且console.warn在 Playwright 侧被规范化为warning类型should fire console events with handles and complex objects进一步验证复杂对象参数可以通过args()[i].jsonValue()还原、甚至用evaluate比对是否为同一个对象引用。method: waitForEvent(event, optionsOrPredicate)v1.9等待某个事件触发并把事件数据传入谓词函数谓词返回真值时 resolve 并返回事件数据如果在事件触发前应用被关闭会抛错。event事件名如window、close、consoleoptionsOrPredicate可选接收事件数据的谓词函数或选项对象其中predicate指定谓词、timeout指定最大等待毫秒数默认300000表示不超时默认值可用BrowserContext.setDefaultTimeout修改。典型用法是在触发条件的动作发出前就挂好 Promiseconst windowPromise electronApp.waitForEvent(window); await mainWindow.click(button); const window await windowPromise;在测试基建 electronTest.ts 的newWindowfixture 中waitForEvent(window)与evaluate建窗动作被Promise.all并发执行这正是该方法的推荐使用模式——避免事件早于监听器注册而丢失。context()以 BrowserContext 身份操作整个应用context()v1.9返回该 Electron 应用对应的BrowserContext可以设置应用级的路由、初始化脚本、全局函数等。测试用例直接印证了三大能力应用级路由should route networkawait app.context().route(**/empty.html, async (route) { await route.fulfill({ status: 200, contentType: text/html, body: titleHello World/title }); }); const page await newWindow(app); await page.goto(https://localhost:1000/empty.html); expect(await page.title()).toBe(Hello World);初始化脚本should support init scriptawait app.context().addInitScript(window.magic 42;);exposeFunctionshould expose functionawait app.context().exposeFunction(add, (a, b) a b); // 页面内 window[result] add(20, 22) 得到 42从 electron.ts 看ElectronApplication的构造函数直接持有browser._defaultContextcontext()只是把它转手给调用者——也就是说 Electron 应用运行在 Chromium 浏览器的默认 context 上所有 context 级 APIroute、addInitScript、exposeFunction、tracing.startHar、clearCookies等都天然可用。底层原理launch 时的双通道连接与 ready 延迟理解ElectronApplication的方法语义离不开Electron.launch()的启动流程electron.ts 第 180-320 行追加两个关键参数--inspect0让 Electron 内嵌的 Node 进程在随机端口开放 V8 Inspector--remote-debugging-port0让 Chromium 渲染侧在随机端口开放 DevTools。--remote-debugging-port0必须位于 Playwright 追加参数的末尾因为 loader 依赖它来裁剪process.argv注入 loader对非打包应用Playwright 会在参数最前注入-r lib/server/electron/loader.js见 electron.ts 第 220 行注释打包后的应用可能有自己的命令行处理因此只对默认 Electron 可执行文件注入。loader.ts 做了两件事把process.argv裁剪回用户视角Electron 主脚本看到的仍是args传入的原始参数测试should preserve args验证了含 ^|\\等特殊字符的 argv 也能原样保留延迟ready事件钩住app.emit/app.whenReady/app.isReady把ready挂起直到 Playwright 连接就绪后通过求值__playwright_run()即ElectronApplication.initialize()electron.ts 第 122-126 行才放行从而避免浏览器初始化与渲染器 attach 的竞态。测试should dispatch ready event精确验证了事件顺序isReady false→will-finish-launching fired→ready fired→whenReady resolved→isReady true等待两路输出建立连接Playwright 逐行扫描进程 stderr分别匹配Debugger listening on ws://...Node 通道与DevTools listening on ws://...Chromium 通道为每条通道建立WebSocketTransportCRConnection。这正是ElectronApplication内部持有_nodeConnection驱动主进程与_browserContext驱动渲染窗口两条连接的原因平台细节Linux 下默认追加--no-sandbox除非chromiumSandbox开启或用户已显式传参Windows 下因需要shell: true来运行.cmd会把可执行路径与参数拼成带引号的单个命令字符串另外 launch 前会delete env.NODE_OPTIONS防止调试 Playwright 测试时断点挂到 Electron 的 Node 上导致自动化无法接入。实战技巧用 evaluate 拦截原生 dialogElectron 的dialogAPIdialog.showOpenDialog、dialog.showSaveDialog、dialog.showMessageBox等运行在主进程并直连操作系统Playwright 不会拦截它们。官方文档class-electron.md给出的做法是用ElectronApplication.evaluate直接在主进程替换对应方法使测试无需任何 OS 级 UI 即可确定性运行// Stub the open dialog to always return a fixed path. await electronApp.evaluate(({ dialog }, filePaths) { dialog.showOpenDialog () Promise.resolve({ canceled: false, filePaths }); }, [/path/to/file.txt]); // Stub the save dialog. await electronApp.evaluate(({ dialog }, filePath) { dialog.showSaveDialog () Promise.resolve({ canceled: false, filePath }); }, /path/to/saved.txt); // Stub showMessageBox to click the first button. await electronApp.evaluate(({ dialog }) { dialog.showMessageBox () Promise.resolve({ response: 0, checkboxChecked: false }); });替换会一直生效直到应用关闭同步版本showOpenDialogSync、showSaveDialogSync、showMessageBoxSync可以照同样的方式 stub区别只是直接返回值而不返回Promise。这是evaluate“第一参数就是require(electron)模块”这一设计最直接的应用场景。小结ElectronApplication API 速查成员类型起始版本说明evaluate(expression, arg)异步方法v1.9主进程求值回调首参为require(electron)结果支持 Promise 与-0/NaN/Infinity/-InfinityevaluateHandle(expression, arg)异步方法v1.9同evaluate返回JSHandleprocess()方法v1.21返回主进程ChildProcessfirstWindow({ timeout })异步方法v1.9timeout v1.33等待首个窗口默认超时 30000mswindows()方法v1.9所有已打开窗口的Page数组browserWindow(page)异步方法v1.11由Page取原生BrowserWindow句柄context()方法v1.9应用级BrowserContext支持 route/initScript/exposeFunction 等close()异步方法v1.9通过app.quit()优雅退出应用幂等waitForEvent(event, optionsOrPredicate)异步方法v1.9带谓词/超时的事件等待默认 30000msclose事件v1.9进程终止时触发window事件v1.9每个创建且加载完成的窗口触发参数为Pageconsole事件v1.42主进程 console API 调用参数为ConsoleMessage完整 API 参考见 class-electronapplication.md 与 class-electron.md行为回归测试集中在 tests/electron/electron-app.spec.ts可用的最小被测应用参考 tests/electron/electron-app.js 与 tests/electron/electron-window-app.js。【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwright创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考