Electron+Vite+React项目使用Playwright进行端到端测试实战指南
1. 项目概述如果你正在用 Electron Vite React 这套技术栈开发桌面应用那么恭喜你你选了一条现代且高效的开发路径。但随之而来的一个现实问题是当应用功能越来越复杂每次手动点点点来测试所有功能不仅耗时耗力还容易遗漏。单元测试能保证函数逻辑正确但用户看到的、交互的是一个完整的窗口点击按钮、填写表单、触发菜单这些“端到端”的流程谁来保证这就是我们今天要聊的核心为你的 electron-vite-react 项目引入 Playwright 进行端到端测试。简单来说端到端测试就是模拟真实用户的操作从启动应用开始到完成一系列交互最后验证结果是否符合预期。它站在用户视角是保障应用质量最重要的一道防线。而 Playwright作为微软出品的现代自动化测试框架凭借其对 Electron 的原生支持、强大的 API 和出色的稳定性已经成为这个领域的新宠。它不像传统的 WebDriver 那样需要额外的驱动而是直接通过 Chrome DevTools Protocol 与你的 Electron 应用“对话”这意味着更快的执行速度和更可靠的测试。这篇文章我会带你从零开始在一个典型的 electron-vite-react 项目中搭建一套完整的 Playwright 端到端测试环境。无论你是测试新手还是想从其他框架如 Cypress、WebdriverIO迁移过来都能找到清晰的路径。我们将不仅覆盖“怎么做”更会深入“为什么这么做”并分享我在实际项目中踩过的坑和总结的经验让你少走弯路。2. 为什么选择 Playwright 作为 Electron 的测试方案在开始动手之前我们先花点时间理清思路为什么在众多测试工具中我强烈推荐 Playwright 来测试 Electron 应用这背后是技术选型的综合考量。2.1 技术栈的天然契合度我们的技术栈是 Electron Vite React。Electron 应用本质上是 Chromium 浏览器 Node.js 运行时。Playwright 的核心优势之一就是它直接通过 Chrome DevTools Protocol 与 Chromium 内核通信。这意味着当 Playwright 启动你的 Electron 应用时它不是在模拟一个外部浏览器而是在与你的应用主进程建立直接、底层的连接。这种连接方式带来了几个决定性优势执行速度极快省去了 WebDriver 协议转换和网络通信的开销操作指令几乎可以瞬间得到响应。可靠性高避免了因 WebDriver 版本与 Chromium 版本不匹配导致的诡异问题。Playwright 与 Chromium 版本的绑定关系更紧密、更稳定。能力强大可以访问到 Electron 特有的 API。比如你可以在测试脚本中直接调用electron.app.isPackaged来判断当前是开发模式还是生产包或者模拟系统级别的对话框、菜单操作这是传统 Web 端到端测试工具难以做到的。2.2 与 Vite 开发流的无缝集成Vite 以其闪电般的启动速度和热更新著称。Playwright 能很好地融入这个高效的开发流。你可以在开发时让 Playwright 连接到 Vite 启动的开发服务器通常是http://localhost:5173进行快速测试。更重要的是Playwright 支持在无头模式下运行测试这非常适合集成到 CI/CD 流水线中。想象一下每次提交代码后自动构建、启动应用、运行全套端到端测试整个过程在服务器上静默完成只有失败时才会通知你这极大地提升了开发效率和代码质量。2.3 对比其他主流方案你可能也听说过 Selenium 或 Cypress。这里简单对比一下Selenium WebDriver这是老牌方案生态庞大。但为 Electron 配置 ChromeDriver 的过程相对繁琐且执行速度较慢。在 Electron 的官方文档中它也被列为一种可选方案但维护和调试成本更高。CypressCypress 在 Web 端测试中非常流行但它对 Electron 的支持一直处于实验性或社区维护状态其架构设计更偏向于纯 Web 应用访问 Electron 主进程能力有限稳定性不如 Playwright 官方支持来得可靠。WebdriverIOWebdriverIO 是一个优秀的封装层它底层可以使用 WebDriver 或 DevTools 协议。当配置为使用 Electron 服务时它本质上也是利用了类似 Playwright 的底层能力。但 Playwright 的 API 设计更现代、更统一一套 API 测所有浏览器且其 Test Runner 内置了并行、重试、报告等强大功能开箱即用。注意选择工具时一个重要的原则是“官方支持优先”。Playwright 官方文档有专门的 Electron 测试章节这意味着你遇到问题时更有可能找到官方解决方案社区支持也更好。2.4 Playwright 的核心能力预览在 Electron 测试中Playwright 赋予我们两大核心对象ElectronApplication代表整个 Electron 应用实例。你可以通过它启动/关闭应用访问 Electron 的主进程模块如app,BrowserWindow,dialog执行主进程环境下的脚本。Page代表一个渲染器进程中的页面即一个 BrowserWindow 的内容。你可以像在浏览器中一样进行点击、输入、断言等所有常见的 DOM 操作。这种清晰的模型让测试代码的逻辑非常直观启动应用 - 获取窗口 - 操作页面 - 断言结果。3. 环境搭建与项目初始化理论说清楚了我们开始动手。假设你已经有一个基于electron-vite模板创建的 React 项目。如果没有可以快速创建一个# 使用 electron-vite 官方模板创建项目 npm create electron-vitelatest my-electron-app -- --template react cd my-electron-app接下来我们为这个项目注入测试能力。3.1 安装 Playwright 核心依赖首先安装 Playwright Test 运行器及其对 Electron 的支持。这里注意我们安装的是playwright/test包它包含了运行器和核心库。npm install --save-dev playwright/test安装完成后Playwright 推荐初始化一个配置文件并安装它自带的浏览器对于 Electron 测试我们主要用 Chromium但安装全套也无妨。执行以下命令npx playwright install --with-deps chromium # 或者安装所有浏览器Chromium, Firefox, WebKit # npx playwright install--with-deps参数会同时安装一些必要的系统依赖库这在 Linux CI 环境中尤其重要。3.2 配置 Playwright 以识别 Electron 应用Playwright 默认是为测试网页设计的。我们需要告诉它如何启动我们的 Electron 应用。在项目根目录创建或修改playwright.config.ts或.js文件。// playwright.config.ts import { defineConfig, devices } from playwright/test; export default defineConfig({ testDir: ./e2e, // 测试文件存放的目录 fullyParallel: true, // 是否完全并行运行测试 forbidOnly: !!process.env.CI, // 在CI环境中禁止使用 test.only retries: process.env.CI ? 2 : 0, // CI环境下失败重试2次 workers: process.env.CI ? 1 : undefined, // CI环境下使用1个worker本地可更多 reporter: html, // 生成漂亮的HTML报告 use: { trace: on-first-retry, // 失败时记录追踪信息 }, // 重点定义项目。这里我们定义一个专门给Electron用的项目。 projects: [ { name: electron, // 这里不使用 devices因为我们不是测浏览器 use: { // 你可以在这里定义一些全局的配置比如视口大小但Electron中通常由应用窗口决定 }, // 告诉Playwright这个项目是测试Electron的 metadata: { mode: electron } }, ], });这个配置的关键在于projects里定义了一个名为electron的项目并通过metadata标记了其模式。但更重要的是我们如何在测试文件中启动应用这通常不在全局配置中完成而是在每个测试文件或一个全局的 setup 钩子中完成因为启动参数可能因测试而异。3.3 调整 Electron 主进程以支持测试默认的 Electron 主进程通常是src/main/index.js或.ts是为正常启动设计的。为了在测试环境中更稳定我们可能需要做一些小调整。一个常见的需求是在测试模式下我们可能希望应用启动后不自动打开开发者工具或者允许来自 Playwright 的连接。通常Playwright 的electron.launch()API 会处理好这些。但你需要确保你的主进程能够处理来自命令行参数的可能变化。例如你的主进程创建窗口的代码可能是这样的// src/main/index.js (片段) function createWindow() { const mainWindow new BrowserWindow({ width: 1200, height: 800, webPreferences: { preload: path.join(__dirname, ../preload/index.js), contextIsolation: true, nodeIntegration: false, }, }); // 启动时加载页面 if (process.env.VITE_DEV_SERVER_URL) { mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL); // 开发模式下默认打开DevTools但在无头测试中可能需要关闭 // mainWindow.webContents.openDevTools(); } else { mainWindow.loadFile(path.join(__dirname, ../renderer/index.html)); } }在测试时Playwright 会以无头或非无头模式启动这个主进程。你不需要特意修改代码来“适配”Playwright这是它的优点之一。但是如果你有只在开发时执行的代码比如打开 DevTools可能需要根据环境变量如process.env.NODE_ENV test来条件执行。4. 编写你的第一个 Electron 端到端测试环境就绪我们来写一个实实在在的测试。假设我们有一个简单的计数器应用点击按钮数字会增加。4.1 测试文件结构与命名约定在项目根目录下创建e2e文件夹与配置中的testDir对应。在里面创建你的第一个测试文件通常以.spec.js或.spec.ts结尾。Playwright 默认会识别这种模式。my-electron-app/ ├── src/ ├── e2e/ │ └── counter.spec.ts ├── playwright.config.ts └── package.json4.2 编写基础测试启动应用与页面截图我们从最简单的测试开始启动应用获取第一个窗口并截一张图。这能验证 Playwright 是否能成功拉起你的应用。// e2e/counter.spec.ts import { test, expect, _electron as electron } from playwright/test; test(应用应能成功启动并打开主窗口, async () { // 1. 启动 Electron 应用 // args: [.] 中的 . 代表应用根目录electron.launch 会寻找 main.js/index.js // 如果你的主文件路径不同需要指定例如 args: [./src/main/index.js] const electronApp await electron.launch({ args: [.] }); // 2. 等待并获取第一个窗口BrowserWindow的 Page 对象 // firstWindow() 会等待窗口加载完毕 const window await electronApp.firstWindow(); // 3. 进行一些基础断言 // 断言窗口的标题如果设置了的话 await expect(window).toHaveTitle(/Your App Name|Electron/); // 根据实际情况调整正则 // 4. 可选截屏保存用于可视化检查或报告 await window.screenshot({ path: test-results/app-startup.png }); // 5. 关闭应用 await electronApp.close(); });运行这个测试npx playwright test --projectelectron如果一切顺利你会看到测试通过并且在项目根目录下生成一个test-results/app-startup.png截图。实操心得electron.launch({ args: [.] })中的args参数非常关键。它被传递给 Electron 可执行文件。这里的.意味着在当前目录寻找主入口文件通常是package.json中main字段指定的文件。如果你的项目结构特殊比如主文件在src/electron/main.js你可能需要写成args: [./src/electron/main.js]。启动失败时首先检查这个路径。4.3 模拟用户交互点击与断言现在我们来测试具体的功能。假设我们的 React 组件有一个按钮和一个显示计数的元素。// 假设的 React 组件 (src/renderer/App.tsx) function App() { const [count, setCount] useState(0); return ( div h1Electron Counter/h1 div>// e2e/counter.spec.ts (追加新的测试用例) test(计数器功能应正常工作, async () { const electronApp await electron.launch({ args: [.] }); const window await electronApp.firstWindow(); // 1. 获取计数器显示元素和按钮元素 // 使用 getByTestId 是最稳定、最推荐的方式 const counterDisplay window.getByTestId(counter-display); const incrementButton window.getByRole(button, { name: /increment/i }); // 2. 断言初始状态 await expect(counterDisplay).toHaveText(Count: 0); // 3. 模拟用户点击 await incrementButton.click(); // 4. 断言点击后的状态 await expect(counterDisplay).toHaveText(Count: 1); // 5. 可以连续操作 await incrementButton.click(); await incrementButton.click(); await expect(counterDisplay).toHaveText(Count: 3); await electronApp.close(); });这段测试代码清晰地模拟了用户操作找到按钮点击然后验证显示结果是否更新。getByTestId和getByRole是 Playwright 推荐的定位器它们比直接使用 CSS 选择器如window.locator(.counter)更具可读性和稳定性。4.4 访问 Electron 主进程上下文Playwright 的强大之处在于它能“穿透”渲染进程直接在你的主进程上下文中执行代码。这可以用来验证或触发一些只有主进程才能做的事情。// e2e/counter.spec.ts (追加测试用例) test(应能访问主进程属性, async () { const electronApp await electron.launch({ args: [.] }); // 在 Electron 主进程中执行代码 const isPackaged await electronApp.evaluate(async ({ app }) { // 这里的 app 参数就是你在主进程中 require(electron).app // 这个函数在主进程的上下文中运行可以访问所有 Node.js 和 Electron API return app.isPackaged; }); // 因为我们是在开发模式下运行测试所以 isPackaged 应该是 false expect(isPackaged).toBe(false); console.log(应用是否已打包: ${isPackaged}); // 另一个例子获取应用名称 const appName await electronApp.evaluate(async ({ app }) { return app.getName(); }); console.log(应用名称: ${appName}); expect(appName).toBeTruthy(); // 简单断言名称存在 await electronApp.close(); });这个evaluate方法极其有用。你可以用它来模拟主进程菜单操作。触发系统对话框并模拟用户选择。读写主进程才能访问的文件或配置。验证主进程状态机的正确性。注意事项在evaluate中执行的代码是字符串化的并在主进程环境中执行。因此你不能直接使用外部变量闭包。所有需要的数据必须通过参数传递。同时这里的代码是运行在测试 Node 环境之外的调试起来可能不那么直观建议将复杂的逻辑封装成主进程中的函数然后通过evaluate调用。5. 高级测试策略与实战技巧掌握了基础之后我们来看看如何构建健壮、可维护的测试套件。5.1 使用 Fixtures 优化测试结构如果每个测试都写一遍launch和close代码会非常冗余。Playwright Test 提供了fixtures机制来设置和清理测试环境。我们可以为 Electron 应用创建一个自定义 fixture。// e2e/fixtures/electron.fixture.ts import { test as base, _electron as electron, ElectronApplication, Page } from playwright/test; // 定义扩展的 fixtures 类型 export type ElectronTestOptions { electronApp: ElectronApplication; mainWindow: Page; }; // 扩展基础的 test注入我们的 fixtures export const test base.extendElectronTestOptions({ // electronApp fixture 会自动启动和关闭应用 electronApp: async ({}, use) { const app await electron.launch({ args: [.] }); await use(app); await app.close(); }, // mainWindow fixture 依赖于 electronApp mainWindow: async ({ electronApp }, use) { const window await electronApp.firstWindow(); await use(window); // 窗口会随着应用关闭而自动关闭通常不需要额外清理 }, }); // 现在可以导出这个增强版的 test 供其他测试文件使用 export { expect } from playwright/test;然后在你的测试文件中使用这个自定义的test// e2e/counter-advanced.spec.ts import { test, expect } from ./fixtures/electron.fixture; // 导入自定义 fixture // 现在测试函数的参数中会自动注入 electronApp 和 mainWindow test(使用 fixture 简化测试, async ({ mainWindow }) { // 直接使用 mainWindow无需手动启动和获取 const counterDisplay mainWindow.getByTestId(counter-display); await expect(counterDisplay).toBeVisible(); }); test(另一个测试也能共享 fixture, async ({ electronApp, mainWindow }) { // 仍然可以访问 electronApp 来执行主进程操作 const version await electronApp.evaluate(async ({ app }) app.getVersion()); console.log(version); // ... 页面操作 });使用 Fixtures 的好处是巨大的代码更简洁启动/关闭逻辑集中管理并且 Playwright 可以智能地复用上下文在某些情况下提升测试速度。5.2 处理异步加载与网络请求现代前端应用大量使用异步数据。你的 Electron 应用窗口在加载后可能还需要等待 API 数据返回、图片加载完成等。Playwright 提供了多种等待策略。test(应等待动态内容加载完成, async ({ mainWindow }) { // 1. 等待某个特定元素出现最常用 const dynamicItem mainWindow.getByText(Loading...); await expect(dynamicItem).toBeHidden(); // 等待“加载中”提示消失 await expect(mainWindow.getByText(Data loaded successfully)).toBeVisible(); // 2. 等待网络请求完成如果你的应用有 API 调用 // 首先监听一个特定的请求 const [response] await Promise.all([ mainWindow.waitForResponse(response response.url().includes(/api/data) response.status() 200), // 同时触发这个请求的动作比如点击刷新按钮 mainWindow.getByRole(button, { name: Refresh }).click(), ]); // 请求完成后再断言页面内容 const dataElement mainWindow.getByTestId(data-list); await expect(dataElement).toHaveCount(10); // 假设加载了10条数据 // 3. 使用通用的 waitForTimeout 作为最后手段不推荐 // await mainWindow.waitForTimeout(2000); // 等待2秒 });重要技巧尽量避免使用waitForTimeout。它让测试变得脆弱网络或机器慢可能导致超时且缓慢。优先使用waitForSelector,waitForResponse, 或 Playwright 的自动等待机制expect(locator).toBeVisible()本身就内置了等待。Playwright 的自动等待会检查元素是否满足条件如可见、启用、存在直到超时。5.3 模拟主进程事件与对话框测试桌面应用时经常需要模拟系统级别的交互比如文件选择对话框、消息提示框等。我们可以利用evaluate来模拟这些。test(应能处理文件打开对话框, async ({ electronApp, mainWindow }) { // 在触发对话框之前先在主进程中“劫持” dialog.showOpenDialog 方法 await electronApp.evaluate(async ({ dialog }) { const originalShowOpenDialog dialog.showOpenDialog; dialog.showOpenDialog async (options) { // 模拟用户选择了某个文件 console.log(模拟文件选择对话框被调用); return { canceled: false, filePaths: [/Users/test/mock-file.txt] }; }; // 将这个覆盖方法挂载到全局以便在测试后恢复需要更复杂的上下文管理 // 更简单的做法是在测试中直接调用被覆盖的函数或者通过 IPC 触发 }); // 然后在渲染进程触发一个会调用 dialog.showOpenDialog 的动作 // 例如点击一个“打开文件”按钮这个按钮会通过 preload 脚本调用主进程的 API await mainWindow.getByRole(button, { name: Open File }).click(); // 断言应用是否正确处理了模拟的返回值 await expect(mainWindow.getByText(mock-file.txt)).toBeVisible(); });这种方法需要你对应用的主进程-渲染进程通信通常通过 preload 脚本暴露 API有清晰的了解。更常见的模式是不直接模拟 Electron API而是通过测试专用的 IPC 通道让测试脚本向应用发送“模拟事件”。5.4 测试多窗口场景复杂的 Electron 应用可能有多个 BrowserWindow。Playwright 可以很好地处理它们。test(应能创建并操作新窗口, async ({ electronApp }) { const mainWindow await electronApp.firstWindow(); // 1. 监听新窗口的创建 const [newWindow] await Promise.all([ electronApp.waitForEvent(window), // 等待新窗口事件 // 触发创建新窗口的动作比如点击一个按钮 mainWindow.getByRole(button, { name: New Window }).click(), ]); // 2. 现在 newWindow 是一个新的 Page 对象 await expect(newWindow).toHaveTitle(Settings); // 假设新窗口是设置窗口 // 3. 在新窗口中操作 await newWindow.getByLabel(Username).fill(testuser); await newWindow.getByRole(button, { name: Save }).click(); // 4. 可以切换回主窗口验证状态是否更新 await mainWindow.bringToFront(); await expect(mainWindow.getByText(Settings updated)).toBeVisible(); // 5. 关闭新窗口如果需要 await newWindow.close(); // 注意关闭窗口后对应的 Page 对象可能失效后续不能再操作 });electronApp.waitForEvent(window)是处理多窗口的关键。它允许你在窗口创建时立即捕获到它。6. 集成到开发工作流与 CI/CD测试写好了如何让它真正发挥作用而不是躺在文件夹里吃灰6.1 在 package.json 中配置脚本在package.json的scripts部分添加测试命令。{ scripts: { dev: electron-vite dev, build: electron-vite build, preview: electron-vite preview, test:e2e: playwright test --projectelectron, test:e2e:ui: playwright test --projectelectron --ui, // 使用 Playwright 的 GUI 模式 test:e2e:debug: playwright test --projectelectron --debug, // 调试模式 test:e2e:headed: playwright test --projectelectron --headed // 有头模式方便观察 } }现在运行npm run test:e2e就可以执行所有端到端测试了。--ui模式会打开一个图形界面方便你查看、运行和调试单个测试非常适合开发阶段。6.2 在 CI/CD 流水线中运行以 GitHub Actions 为例自动化测试的价值在持续集成中最大化。以下是一个基本的 GitHub Actions 工作流配置示例它会在每次推送到主分支或发起拉取请求时运行 Electron 端到端测试。# .github/workflows/playwright.yml name: Playwright Tests on: push: branches: [ main, master ] pull_request: branches: [ main, master ] jobs: test: timeout-minutes: 10 # 设置超时 runs-on: ubuntu-latest # 使用 Linux 环境 steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 18 # 使用与项目匹配的 Node 版本 - name: Install dependencies run: npm ci # 使用 ci 命令确保依赖锁定 - name: Build Electron App (if needed) run: npm run build # 先构建应用。Playwright 可以直接测源码但构建后更接近生产环境。 env: NODE_ENV: production - name: Install Playwright Browsers run: npx playwright install --with-deps chromium - name: Run Playwright tests run: npm run test:e2e env: # 可能需要设置一些环境变量比如禁用 GPU 加速 ELECTRON_ENABLE_LOGGING: 1 # 启用 Electron 日志便于调试 - uses: actions/upload-artifactv4 if: always() # 即使测试失败也上传 with: name: playwright-report path: playwright-report/ # 上传 HTML 测试报告 retention-days: 7 - uses: actions/upload-artifactv4 if: always() with: name: test-results path: test-results/ # 上传截图、追踪文件等 retention-days: 7这个配置做了几件关键事情安装系统依赖--with-deps参数确保 Chromium 所需的库如 libatk被安装。构建应用在无头环境中直接运行开发服务器可能有问题。构建出可执行文件或打包后的代码再进行测试更可靠。运行测试执行我们定义的test:e2e命令。保存报告和结果将 Playwright 生成的 HTML 报告和截图等文件保存为制品方便测试失败后下载查看。6.3 处理 CI 环境中的常见问题在 CI 服务器上运行 Electron 测试可能会遇到一些特有的问题无头模式与显示问题Linux CI 环境通常没有图形界面。确保你的 Playwright 配置或启动参数中启用了无头模式electron.launch({ headless: true })并且应用代码不能依赖 GUI 特性如某些需要屏幕尺寸的库。资源路径问题在 CI 中当前工作目录和文件路径可能与本地不同。使用path.join(__dirname, ...)等绝对路径来引用资源避免使用相对路径。权限与沙盒某些 CI 环境可能有严格的权限限制。如果遇到启动失败可以尝试在electron.launch的args中添加 Electron 的启动标志例如--no-sandbox注意安全风险或--disable-gpu。const electronApp await electron.launch({ args: [., --no-sandbox, --disable-gpu], headless: true // CI 环境通常设为 true });测试稳定性网络、性能波动可能导致测试偶发失败。利用 Playwright 的retries配置在playwright.config.ts中进行自动重试。同时确保你的测试有足够的等待和健壮的定位器。7. 常见问题排查与调试技巧实录即使按照最佳实践测试过程中也难免会遇到问题。这里记录了我踩过的一些坑和解决方法。7.1 应用启动失败问题运行测试时electron.launch()超时或直接报错。检查点1主进程入口文件。确认args参数指向了正确的入口文件。如果入口文件是 TypeScript 且需要编译确保在测试前已经执行了构建npm run build或者配置 Playwright 使用构建后的输出目录。检查点2控制台日志。在electron.launch中添加{ timeout: 30000 }并查看详细的错误输出。有时主进程中有未捕获的异常导致崩溃。const electronApp await electron.launch({ args: [.], timeout: 30000, // 延长超时时间 // 可以捕获标准输出和错误 // stdio: inherit // 这会将 Electron 的日志打印到测试控制台有助于调试 });检查点3环境变量。你的应用是否依赖某些环境变量在测试脚本或 CI 配置中设置它们。7.2 元素定位不到或操作超时问题测试卡在await page.click(...)或await expect(...).toBeVisible()最终超时。检查点1页面是否真的加载完成在操作前先加一个基础断言比如await expect(window).toHaveTitle(...)确保窗口已经就绪。检查点2使用更稳健的定位器。放弃page.locator(.btn-primary)这种脆弱的 CSS 选择器。优先使用getByRole,getByTestId,getByText。为你的关键交互元素添加>// 处理 iframe const frame page.frameLocator(iframe[namecontent]); await frame.getByRole(button).click(); // 处理 Shadow DOM (需要 语法或 elementHandle) const shadowHost page.locator(my-custom-element); const shadowButton shadowHost.locator(, button); await shadowButton.click();7.3 主进程通信测试失败问题在electronApp.evaluate()中执行的代码报错或者无法达到预期效果。检查点1参数序列化。传递给evaluate的参数必须是可 JSON 序列化的。函数、DOM 元素、复杂的类实例无法传递。检查点2执行上下文。evaluate中的代码运行在主进程但它无法直接访问你测试文件中的变量。所有逻辑必须内联在函数字符串中或通过参数传入。检查点3异步代码。evaluate的回调函数可以是async的确保正确处理 Promise。// 正确 const result await electronApp.evaluate(async ({ app }) { return await someAsyncFunction(app); }); // 错误试图返回一个无法序列化的对象如 BrowserWindow const badResult await electronApp.evaluate(({ BrowserWindow }) { return BrowserWindow.getFocusedWindow(); // 返回的是一个 BrowserWindow 实例 });7.4 测试在 CI 上通过本地却失败或反之问题环境不一致导致。检查点1依赖版本。确保 CI 和本地使用相同版本的 Node.js, npm/yarn, 以及 Playwright。使用package-lock.json或yarn.lock锁定版本。检查点2系统依赖。Playwright 的 Chromium 需要一些系统库。本地环境通常齐全但 CI 环境可能缺失。npx playwright install --with-deps chromium命令就是为了解决这个问题。检查点3资源文件。测试是否依赖本地特定的文件路径使用绝对路径或通过环境变量配置。检查点4并发与状态隔离。确保测试之间是隔离的。一个测试修改了全局状态如写入某个文件可能会影响下一个测试。使用test.describe.serial()来串行运行有依赖关系的测试或者更好的做法是每个测试都自己准备和清理数据。7.5 调试测试当测试失败时高效的调试至关重要。使用--debug标志运行npm run test:e2e:debug。这会打开 Playwright Inspector允许你一步步执行测试查看每个步骤的页面快照、控制台日志和网络请求。使用--ui模式npm run test:e2e:ui启动 Playwright UI这是一个图形化的测试运行器可以方便地运行、查看和调试单个测试。添加page.pause()在你的测试代码中插入await page.pause();。当执行到这一行时测试会暂停并打开一个浏览器窗口你可以在其中手动操作并检查页面状态。查看追踪文件在playwright.config.ts中配置use: { trace: on-first-retry }。测试失败时会在test-results目录下生成一个追踪文件.zip。使用npx playwright show-trace trace.zip命令打开它可以重现测试的每一步操作是排查复杂问题的利器。打印日志在测试中适当使用console.log。Electron 主进程中的console.log会在启动 Electron 的终端输出。渲染进程中的日志可以通过page.on(console, msg console.log(msg.text()))来捕获。为电子应用构建端到端测试一开始可能会觉得增加了不少工作量但一旦测试套件稳定下来它将成为你重构代码、添加新功能时最坚实的后盾。Playwright 以其强大的能力和对 Electron 的良好支持让这个过程变得比以往任何时候都更顺畅。从今天开始为你下一个 electron-vite-react 项目加上 Playwright 测试吧你会发现代码的自信度会直线上升。