Playwright自动化测试实战:从零搭建现代Web测试框架 1. 项目概述为什么是 Playwright如果你正在为现代 Web 应用的自动化测试头疼尤其是面对那些充斥着动态加载、复杂交互的单页应用SPA那么 Playwright 的出现很可能就是你的解药。我接触过 Selenium、Puppeteer 等一众工具最终在项目里全面转向 Playwright核心原因就一个它真正理解了测试工程师的痛点并提供了“Web 优先”的解决方案。简单说Playwright 是一个由微软开源的 Node.js 库它提供了一套统一的 API可以跨 Chromium、Firefox 和 WebKit 三大浏览器引擎驱动自动化。这听起来和 Selenium 有点像但它的设计哲学完全不同。Selenium 是基于 WebDriver 协议的“命令-响应”模式而 Playwright 更像是浏览器的“原生伙伴”它直接通过 DevTools Protocol 与浏览器内核对话这意味着更快的执行速度、更强大的控制能力以及最重要的——自动等待。你再也不用在代码里写满sleep(5)或者各种轮询等待元素出现了Playwright 内置的智能等待机制能极大地减少那些令人抓狂的“不稳定测试”。这个实战指南就是把我从零开始搭建 Playwright 测试框架到处理各种复杂场景文件下载、iframe、API 拦截、并行执行的经验系统地梳理出来。无论你是刚入门自动化测试的新手还是正在评估新工具的老手都能在这里找到可直接落地的配置、代码和避坑指南。2. 核心设计思路与框架选型2.1 Playwright vs. Selenium vs. Puppeteer我们为什么选它在做技术选型时我们对比了市面上主流的几个方案。Selenium 生态庞大但历史包袱重WebDriver 的通信开销和等待问题在复杂应用中尤为明显。Puppeteer 性能强劲但只绑定 Chromium且更偏向于爬虫和脚本场景其测试运行器功能相对较弱。Playwright 可以看作是 Puppeteer 的“全面升级版”它继承了高性能和深度控制能力并针对测试场景做了大量优化。它的核心优势在于跨浏览器一致性一套 API 覆盖三大浏览器引擎确保你的应用在 Chrome、Firefox 和 Safari 上表现一致。这对于需要做跨浏览器兼容性测试的团队是刚需。自动等待与 Web 优先断言这是革命性的。Playwright 在执行点击、输入等操作前会自动等待元素满足一系列可操作性条件如可见、启用、稳定等。它的断言如expect(locator).toBeVisible()也是可重试的会持续轮询直到条件满足或超时。这从根本上减少了因网络延迟或渲染速度导致的测试失败。强大的测试隔离每个测试用例都运行在一个全新的“浏览器上下文”中这相当于一个独立的用户会话拥有独立的 cookies、localStorage但创建开销极小。这避免了测试间的相互污染让测试可以安全地并行运行。丰富的工具链内置的测试生成器、追踪查看器、VS Code 扩展构成了一个完整的开发调试闭环。特别是追踪查看器当测试失败时它能提供一个包含所有步骤截图、网络请求、控制台日志的时间轴让你无需复现就能快速定位问题。基于以上几点对于需要构建稳定、快速、可维护的现代 Web 自动化测试体系的团队Playwright 是目前最值得投入的技术选择。2.2 项目结构与技术栈规划一个易于维护的测试项目结构清晰至关重要。我们的项目通常采用如下分层结构e2e-tests/ ├── package.json ├── playwright.config.ts # 主配置文件 ├── tests/ │ ├── fixtures/ # 测试夹具如登录状态复用 │ │ └── auth.setup.ts │ ├── pages/ # 页面对象模型Page Object │ │ ├── login.page.ts │ │ └── dashboard.page.ts │ ├── specs/ # 测试用例 │ │ ├── login.spec.ts │ │ └── user-flow.spec.ts │ └── utils/ # 工具函数 │ └── helper.ts ├── test-results/ # 测试报告和追踪文件.gitignore └── .github/workflows/ # CI/CD 流水线配置 └── playwright.yml技术栈说明语言优先推荐TypeScript。Playwright 对 TypeScript 的支持是顶级的完善的类型提示能极大提升开发效率和代码质量避免许多低级错误。测试运行器直接使用 Playwright Test。它是专门为 Playwright 设计的深度集成提供了并行、重试、报告等所有你需要的内置功能无需再集成 Jest 或 Mocha。断言库使用 Playwright Test 自带的expect它经过了扩展支持对 Locator 的异步断言。CI/CDGitHub Actions 是天然搭档官方提供了playwright/test的 GitHub Action开箱即用。3. 环境搭建与核心配置详解3.1 安装与初始化一步到位安装 Playwright 的最佳实践是使用官方的初始化命令它会帮你处理好一切。# 1. 初始化一个新的测试项目或进入现有项目目录 npm init playwrightlatest这个交互式命令会问你几个问题选择测试语言TypeScript 或 JavaScript。选 TypeScript测试目录位置默认tests或e2e。是否添加 GitHub Actions 工作流建议选“是”它会生成一个基础的 CI 配置。是否安装 Playwright 浏览器一定要选“是”。它会下载 Chromium、Firefox 和 WebKit 到本地node_modules目录确保环境一致。完成后你会得到playwright.config.ts配置文件、示例测试文件以及package.json中的相关脚本。注意如果遇到playwright install chromium很慢或失败通常是网络问题。可以尝试设置镜像源# 设置 npm 镜像如淘宝源 npm config set registry https://registry.npmmirror.com # 设置 Playwright 二进制下载镜像 PLAYWRIGHT_DOWNLOAD_HOSThttps://npmmirror.com/mirrors/playwright npx playwright install对于 CentOS 7 等老系统需确保 glibc 版本满足要求如 Playwright 1.54.0 需要 glibc 2.31。若系统版本过低可考虑在 Docker 容器中运行测试。3.2 配置文件playwright.config.ts深度解析这是 Playwright 测试的“大脑”理解每个配置项至关重要。import { defineConfig, devices } from playwright/test; export default defineConfig({ // 1. 测试目录和文件匹配模式 testDir: ./tests/specs, testMatch: **/*.spec.ts, // 只匹配 .spec.ts 文件 // 2. 全局超时设置 timeout: 30 * 1000, // 每个测试用例的超时时间毫秒 expect: { timeout: 10 * 1000, // 每个断言的最大等待时间 }, // 3. 是否并行运行以及如何并行 fullyParallel: true, // 尽可能并行运行所有测试文件 workers: process.env.CI ? 2 : undefined, // CI环境固定2个worker本地根据CPU核心数自动分配 retries: process.env.CI ? 2 : 0, // CI环境失败自动重试2次提高稳定性 // 4. 报告系统 reporter: [ [html, { outputFolder: playwright-report, open: never }], // 生成漂亮的HTML报告 [list], // 在控制台输出简洁结果 [junit, { outputFile: test-results/junit.xml }], // 用于CI集成如Jenkins ], // 5. 全局项目配置可以定义多套环境如桌面端、移动端、不同浏览器 projects: [ { name: chromium, use: { ...devices[Desktop Chrome] }, }, { name: firefox, use: { ...devices[Desktop Firefox] }, }, // 可以添加移动端模拟 // { // name: Mobile Safari, // use: { ...devices[iPhone 12] }, // }, ], // 6. 全局前置/后置钩子 globalSetup: ./tests/fixtures/global-setup.ts, // 所有worker启动前执行一次如启动服务 globalTeardown: ./tests/fixtures/global-teardown.ts, // 所有worker结束后执行一次 use: { // 所有测试的默认配置 headless: true, // CI环境无头模式本地调试可设为 false viewport: { width: 1920, height: 1080 }, ignoreHTTPSErrors: true, // 忽略HTTPS证书错误用于测试环境 actionTimeout: 15 * 1000, // 每个操作click, fill的超时时间 navigationTimeout: 30 * 1000, // 页面导航的超时时间 screenshot: only-on-failure, // 仅在失败时截图 video: retain-on-failure, // 仅在失败时保留录像 trace: retain-on-failure, // 开启追踪仅在失败时保留排查神器 }, // 7. Web Server在运行测试前自动启动你的开发服务器 webServer: { command: npm run start, // 启动本地服务的命令 url: http://localhost:3000, // 等待该URL返回200 reuseExistingServer: !process.env.CI, // 本地重用已有服务器CI环境不重用 timeout: 120 * 1000, // 服务器启动超时时间 }, });关键配置心得workers本地开发可以设为undefined以最大化利用 CPU。但在 CI 环境中如 GitHub Actions 的 2 核机器建议设置为2或4避免因资源竞争导致测试变慢或不稳定。retries在 CI 中设置1-2次重试非常有用可以过滤掉因网络瞬时波动或资源加载延迟导致的偶发性失败让测试套件更稳定。但重试会掩盖真正的逻辑错误所以本地开发通常设为0。trace: retain-on-failure务必开启。当测试失败时生成的trace.zip文件可以用playwright show-trace命令打开里面包含了测试每一步的完整上下文DOM、网络、日志、截图是排查问题的核武器。webServer这个配置能让你实现“一键测试”。运行npx playwright test时它会先启动你的应用再运行测试最后关闭应用非常适合集成到 CI 流水线中。4. 核心 API 与最佳实践4.1 定位器Locator告别脆弱的 XPath/CSS SelectorPlaywright 最棒的特性之一就是它的定位器哲学。它鼓励你使用面向用户的、语义化的定位方式而不是脆弱的、基于实现细节的 CSS 或 XPath。import { test, expect } from playwright/test; test(使用最佳定位策略, async ({ page }) { await page.goto(https://example.com/login); // ❌ 避免脆弱的 CSS/XPath一旦前端样式或结构微调就会失败 await page.click(body div form div:nth-child(2) input); await page.fill(#username-input, user); // ID 可能动态生成 // ✅ 推荐使用语义化、面向用户的定位器 // getByRole: 通过 ARIA 角色定位最接近用户感知屏幕阅读器也是这么“看”页面的 await page.getByRole(textbox, { name: 用户名 }).fill(testuser); await page.getByRole(button, { name: 登录 }).click(); // getByLabel: 通过关联的标签文本定位 await page.getByLabel(邮箱地址).fill(testexample.com); // getByPlaceholder: 通过占位符文本定位 await page.getByPlaceholder(请输入密码).fill(password123); // getByTestId: 与前端约定好的测试 ID最稳定需前端配合添加>test(自动等待示例, async ({ page }) { await page.goto(/dynamic-content); // Playwright 在执行 click 前会自动等待该元素 // 1. 被附加到 DOM // 2. 可见非隐藏、非透明、有尺寸 // 3. 启用非 disabled // 4. 稳定未在动画中 // 5. 可接收事件未被其他元素遮挡 await page.getByRole(button, { name: 加载更多 }).click(); // 断言也会自动重试直到条件满足默认5秒 // 这行代码会持续检查直到列表项数量达到10个或者超时 await expect(page.locator(.item-list li)).toHaveCount(10); // 等待导航如点击后跳转新页面 await page.getByText(跳转到详情).click(); await page.waitForURL(**/details/*); // 使用通配符匹配URL // 等待网络请求完成非常适合验证 API 调用 const responsePromise page.waitForResponse(**/api/user/profile); await page.getByRole(button, { name: 刷新资料 }).click(); const response await responsePromise; expect(response.status()).toBe(200); });常见陷阱动态内容这是录制脚本失败最常见的原因。录制时工具会记录一个绝对的选择器路径。但如果元素是动态生成的如列表项、模态框其选择器下次可能就变了。解决方案永远使用上文提到的语义化定位器getByRole,getByTestId或者使用相对定位、文本匹配。过度等待虽然 Playwright 有自动等待但某些自定义的、非标准的交互如一个复杂的 Canvas 绘图完成后才出现按钮可能需要显式等待。这时应使用page.waitForFunction等待一个特定的 JavaScript 条件成立而不是死板的page.waitForTimeout(5000)。4.3 高级交互与复杂场景处理现代 Web 应用远不止点击和输入。Playwright 提供了处理各种复杂场景的能力。文件上传与下载test(处理文件, async ({ page }) { // 1. 文件上传无需触发系统文件选择框 // 方法一对于 input[typefile] 元素直接设置文件路径 await page.locator(input[typefile]).setInputFiles([/path/to/file1.pdf, /path/to/file2.jpg]); // 方法二监听文件选择对话框如果页面弹出了对话框 page.on(filechooser, async (fileChooser) { await fileChooser.setFiles([/path/to/file.pdf]); }); await page.getByText(上传文件).click(); // 这会触发文件选择对话框 // 2. 文件下载 // 启动下载监听 const downloadPromise page.waitForEvent(download); await page.getByRole(link, { name: 导出报告 }).click(); const download await downloadPromise; // 获取下载建议的文件名并保存到指定路径 const suggestedFilename download.suggestedFilename(); const filePath ./test-results/downloads/${suggestedFilename}; await download.saveAs(filePath); // 验证文件确实已下载例如检查文件是否存在或内容 const fs require(fs); expect(fs.existsSync(filePath)).toBeTruthy(); });处理 iframe 和弹窗test(与 iframe 和弹窗交互, async ({ page }) { await page.goto(/page-with-iframe); // 1. 定位到 iframe 内部 const iframe page.frameLocator(iframe[nameembedded-content]); // 在 iframe 上下文中操作 await iframe.getByRole(button, { name: 内部按钮 }).click(); // 2. 处理新窗口/标签页 const [newPage] await Promise.all([ page.context().waitForEvent(page), // 监听新页面事件 page.getByRole(link, { name: 在新窗口打开 }).click(), // 触发打开新页面 ]); await newPage.waitForLoadState(domcontentloaded); // 在新页面对象上操作 await newPage.getByText(新页面内容).click(); await newPage.close(); // 操作完后记得关闭 // 3. 处理 JavaScript 弹窗alert, confirm, prompt page.on(dialog, async dialog { console.log(弹窗消息: ${dialog.message()}); await dialog.accept(); // 点击“确定” // await dialog.dismiss(); // 点击“取消” // 对于 prompt: await dialog.accept(输入的文字); }); await page.getByRole(button, { name: 触发确认框 }).click(); });模拟设备与网络条件import { devices } from playwright/test; test(移动端测试与网络模拟, async ({ browser }) { // 1. 模拟移动设备如 iPhone 12 const iPhone12 devices[iPhone 12]; const context await browser.newContext({ ...iPhone12, // 可以覆盖默认设置如地理位置、权限 geolocation: { longitude: 116.397128, latitude: 39.916527 }, permissions: [geolocation], }); const mobilePage await context.newPage(); await mobilePage.goto(/); // 2. 模拟慢速网络如 3G const slowContext await browser.newContext(); const slowPage await slowContext.newPage(); await slowPage.route(**/*, (route) { // 可以拦截并修改请求这里我们只是模拟延迟 // 实际项目中更常用的是 playwright.config 中的 slowMo 选项来全局降速观察 route.continue(); }); // 或者使用内置的 network 模拟需在配置中设置 // context.setOffline(true); // 模拟离线 await mobilePage.close(); await context.close(); });拦截和修改网络请求 这是 Playwright 非常强大的功能可以用于 Mock 数据、性能测试或验证 API 调用。test(拦截网络请求, async ({ page }) { // 1. 拦截并修改请求例如修改请求头或请求体 await page.route(**/api/user, async (route) { const request route.request(); const postData request.postData(); // 可以修改 postData const modifiedData JSON.stringify({ ...JSON.parse(postData || {}), mocked: true }); await route.continue({ postData: modifiedData }); }); // 2. 拦截并直接返回 Mock 响应不发送真实请求 await page.route(**/api/products, async (route) { const mockResponse { status: 200, contentType: application/json, body: JSON.stringify([{ id: 1, name: Mock Product }]), }; await route.fulfill(mockResponse); }); // 3. 拦截并中止请求例如阻止图片加载以加速测试 await page.route(**/*.{png,jpg,jpeg,svg}, (route) route.abort()); await page.goto(/); // 此时页面发出的 /api/products 请求将收到我们的 Mock 数据 });5. 测试组织与高级模式5.1 页面对象模型Page Object Model, POMPOM 是 UI 自动化测试中最重要的设计模式它将页面的元素定位和交互逻辑封装成类使测试用例更清晰、更易维护。// tests/pages/login.page.ts import { Locator, Page } from playwright/test; export class LoginPage { readonly page: Page; readonly usernameInput: Locator; readonly passwordInput: Locator; readonly submitButton: Locator; readonly errorMessage: Locator; constructor(page: Page) { this.page page; this.usernameInput page.getByRole(textbox, { name: 用户名 }); this.passwordInput page.getByLabel(密码); this.submitButton page.getByRole(button, { name: 登录 }); this.errorMessage page.locator(.alert-error); } async goto() { await this.page.goto(/login); } async login(username: string, password: string) { await this.usernameInput.fill(username); await this.passwordInput.fill(password); await this.submitButton.click(); } async getErrorMessage(): Promisestring | null { return await this.errorMessage.textContent(); } } // tests/specs/login.spec.ts import { test, expect } from playwright/test; import { LoginPage } from ../pages/login.page; test(用户登录成功, async ({ page }) { const loginPage new LoginPage(page); await loginPage.goto(); await loginPage.login(validUser, validPass); // 断言跳转或出现成功元素 await expect(page).toHaveURL(/dashboard); }); test(用户登录失败显示错误信息, async ({ page }) { const loginPage new LoginPage(page); await loginPage.goto(); await loginPage.login(invalidUser, wrongPass); await expect(loginPage.errorMessage).toBeVisible(); expect(await loginPage.getErrorMessage()).toContain(用户名或密码错误); });POM 进阶技巧组合模式对于大型应用可以创建“组件对象”如HeaderComponent,SidebarComponent然后在页面对象中组合它们。继承如果有多个页面共享公共元素如导航栏可以创建一个BasePage类。5.2 夹具Fixtures与测试隔离Playwright Test 的夹具系统非常强大它用于建立测试环境如登录状态并确保测试间的隔离。// tests/fixtures/auth.setup.ts import { test as baseTest } from playwright/test; import { DashboardPage } from ../pages/dashboard.page; // 1. 定义一个扩展了基础测试的夹具 export const test baseTest.extend{ dashboardPage: DashboardPage; adminDashboardPage: DashboardPage; }({ // 2. 一个需要登录的页面夹具 dashboardPage: async ({ page }, use) { // 这部分在每个使用该夹具的测试开始前执行 await page.goto(/login); await page.getByRole(textbox, { name: 用户名 }).fill(standard_user); await page.getByLabel(密码).fill(secret_sauce); await page.getByRole(button, { name: 登录 }).click(); // 等待登录成功确保状态已就绪 await expect(page).toHaveURL(/.*inventory.html/); const dashboardPage new DashboardPage(page); // 将创建好的页面对象传递给测试用例 await use(dashboardPage); // 这部分在每个使用该夹具的测试结束后执行可选用于清理 // 例如登出 // await page.getByRole(button, { name: 登出 }).click(); }, // 3. 另一个夹具使用不同的用户登录 adminDashboardPage: async ({ page }, use) { await page.goto(/login); await page.getByRole(textbox, { name: 用户名 }).fill(admin_user); await page.getByLabel(密码).fill(admin_pass); await page.getByRole(button, { name: 登录 }).click(); await expect(page.getByText(管理面板)).toBeVisible(); await use(new DashboardPage(page)); }, }); export { expect } from playwright/test; // 重新导出 expect // tests/specs/authorized-flow.spec.ts import { test, expect } from ../fixtures/auth.setup; // 导入自定义夹具 // 现在测试可以直接使用已登录的 dashboardPage test(普通用户查看商品列表, async ({ dashboardPage }) { await dashboardPage.goto(); const items await dashboardPage.getProductItems(); expect(items.length).toBeGreaterThan(0); }); test(管理员可以删除商品, async ({ adminDashboardPage }) { await adminDashboardPage.goto(); await adminDashboardPage.deleteProduct(某商品ID); await expect(adminDashboardPage.getSuccessToast()).toBeVisible(); });夹具的核心优势复用与封装将通用的准备逻辑如登录封装起来避免每个测试重复编写。自动清理夹具的use函数之后的部分可以执行清理确保测试间不互相影响。灵活组合测试可以按需使用不同的夹具组合构建不同的测试上下文。5.3 并行执行与分片Sharding对于大型测试套件并行执行是缩短反馈周期的关键。Playwright Test 默认就支持并行。# 运行所有测试使用所有可用的 CPU 核心并行执行 npx playwright test # 指定 worker 数量 npx playwright test --workers4 # 在 CI 中通常使用分片将测试分成 N 份在 M 台机器上并行跑 # 机器 1: 运行第一份 npx playwright test --shard1/3 # 机器 2: 运行第二份 npx playwright test --shard2/3 # 机器 3: 运行第三份 npx playwright test --shard3/3在playwright.config.ts中配置fullyParallel: true和workersPlaywright 会自动尝试并行运行所有不相互依赖的测试文件。测试的隔离性独立的浏览器上下文是安全并行的前提。6. 调试、报告与 CI/CD 集成6.1 调试技巧让问题无处可藏当测试失败时别急着改代码先利用好 Playwright 的调试工具。使用追踪查看器Trace Viewer# 首先确保配置中启用了 trace建议 on-first-retry 或 retain-on-failure # 测试失败后会生成一个 trace.zip 文件 npx playwright show-trace test-results/你的测试-失败-chromium/trace.zip这个图形化工具会展示测试的完整时间线你可以逐步骤查看当时的页面快照、网络请求、控制台输出是定位“为什么点击没反应”、“为什么元素找不到”这类问题的最强工具。使用 VS Code 扩展 安装官方 “Playwright Test for VSCode” 扩展。它允许你直接在编辑器中运行、调试测试设置断点并实时查看浏览器。无头模式与慢动作# 在无头模式下运行但打开 UI 并放慢动作以便观察 npx playwright test --ui --headed --slow-mo1000--ui打开 Playwright 的图形化测试运行器--headed显示浏览器窗口--slow-mo让每个操作延迟指定的毫秒数。生成并查看截图与录像 配置screenshot: on和video: on可以在每次测试时都生成媒体文件但会占用大量磁盘。通常建议设为only-on-failure。6.2 生成丰富的测试报告Playwright 支持多种报告格式HTML 报告是最直观的。# 运行测试并生成 HTML 报告 npx playwright test --reporterhtml # 报告默认生成在 playwright-report 目录用浏览器打开 index.html 即可 npx playwright show-reportHTML 报告会展示通过率、执行时间、失败的截图和追踪链接。你还可以集成allure-playwright来生成更强大的 Allure 报告。对于 CI 系统如 Jenkins、GitLab CIJUnit 格式的报告是标准。npx playwright test --reporterjunit --outputtest-results/junit.xml6.3 集成到 CI/CD 流水线以 GitHub Actions 为例将 Playwright 测试集成到 CI 中可以实现代码提交即自动测试。以下是 GitHub Actions 的一个经典配置# .github/workflows/playwright.yml name: Playwright Tests on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 18 - name: Install dependencies run: npm ci # 使用 ci 命令确保依赖锁一致 - name: Install Playwright Browsers run: npx playwright install --with-deps chromium # CI中通常只安装一个浏览器以加速 - name: Run Playwright tests run: npx playwright test env: # 传递测试环境变量如基础URL BASE_URL: ${{ secrets.TEST_ENV_URL }} - name: Upload Playwright report if: always() # 即使测试失败也上传报告 uses: actions/upload-artifactv4 with: name: playwright-report path: playwright-report/ retention-days: 7 - name: Upload test results if: always() uses: actions/upload-artifactv4 with: name: test-results path: test-results/ retention-days: 7CI 优化技巧缓存缓存node_modules和 Playwright 浏览器二进制文件可以大幅缩短流水线时间。只安装必要浏览器CI 中通常只需测试 Chromium用npx playwright install chromium。使用官方 Action微软提供了playwright/test的 GitHub Action (microsoft/playwright-github-action)它内置了浏览器安装和优化更简单。分片与并行在大型项目中使用--shard参数将测试分发到多个 job 中并行执行。7. 常见问题排查与性能优化7.1 高频问题速查表问题现象可能原因解决方案locator.click()超时1. 元素不可见/被遮挡。2. 元素处于禁用状态。3. 有动画或过渡效果未完成。4. 页面仍在加载或渲染。1. 使用locator.hover()或检查元素样式。2. 检查元素disabled属性。3. 增加actionTimeout或使用page.waitForFunction等待动画结束。4. 在操作前使用page.waitForLoadState(networkidle)。expect(locator).toBeVisible()失败1. 断言超时前元素始终未出现。2. 元素在 DOM 中但display: none或visibility: hidden。3. 视口外需要滚动。1. 检查定位器是否正确或前端逻辑是否有误。2. 使用toBeHidden()或检查 CSS。3. 先执行locator.scrollIntoViewIfNeeded()。测试在 CI 中通过本地失败或反之1. 环境差异浏览器版本、屏幕分辨率、时区。2. 网络延迟或资源加载速度不同。3. 测试数据状态不一致。1. 使用 Docker 统一测试环境。2. CI 中适当增加超时时间 (timeout,expect.timeout)。3. 使用夹具确保每个测试有干净的初始状态。录制脚本回放失败1. 动态内容导致选择器变化最常见。2. 页面加载速度差异。3. 有未处理的弹窗或导航。1.放弃录制手写定位器。使用getByRole,getByTestId等稳定定位方式。2. 在关键步骤后添加page.waitForLoadState。3. 使用page.on(dialog)处理弹窗。文件下载失败或找不到文件1. 下载路径未指定或无权访问。2. 浏览器设置了“另存为”对话框未自动下载。3. 下载链接触发了新窗口。1. 使用download.saveAs()指定绝对路径。2. 在浏览器上下文中设置acceptDownloads: true。3. 监听download事件而不是新页面事件。page.goto()超时1. 网络问题或服务器未响应。2. 页面有无限重定向。3. 证书错误测试环境常见。1. 检查网络和服务器状态。2. 使用page.goto(url, { waitUntil: domcontentloaded })减少等待。3. 配置ignoreHTTPSErrors: true。7.2 性能优化与最佳实践一个测试只测一件事保持测试用例简短、独立。一个复杂的用户流可以拆分成多个测试用夹具共享登录状态。善用page.route进行 Mock对于依赖第三方 API 或速度慢的后端接口在测试中拦截并返回模拟数据可以极大提升测试速度和稳定性。避免不必要的导航如果多个测试需要同一个页面状态使用夹具在同一个页面上执行多个操作而不是每个测试都重新page.goto。选择性安装浏览器在 CI 环境中如果不需要测试所有浏览器只安装 Chromium (npx playwright install chromium) 可以节省大量时间和磁盘空间。合理配置超时根据应用的实际响应速度在playwright.config.ts中设置合理的全局timeout、actionTimeout和navigationTimeout。太短会导致不必要的失败太长会拖慢测试套件。定期清理测试产物test-results和playwright-report目录会积累大量截图、录像和追踪文件。确保.gitignore中包含它们并在 CI 中设置合理的 artifact 保留时间。从 Selenium 迁移过来最大的感受是心智负担的减轻。以前要花大量精力处理同步、等待和跨浏览器差异现在可以更专注于测试逻辑和业务场景本身。Playwright 的现代化设计让它不仅仅是又一个自动化工具而是一个完整的、为质量和效率服务的测试工程解决方案。开始可能会觉得它的 API 和理念需要适应但一旦用上手就很难再回去了。