1. 项目概述为什么我们需要一个“聪明”的浏览器自动化工具如果你还在用那些“笨拙”的、动不动就卡死或者因为页面元素加载慢一点就报错的自动化脚本那真的该试试 Playwright 了。我最初接触它是因为一个老项目的维护噩梦——基于 Selenium 的测试套件每天都有大量因为时间差、iframe 嵌套或者动态内容导致的失败用例调试的时间比写代码的时间还长。Playwright 的出现像是一剂强心针。它不是一个简单的“另一个自动化工具”而是微软团队从底层重新思考浏览器自动化后给出的答案。它原生支持 Chromium、Firefox 和 WebKit 三大浏览器引擎这意味着你写一套脚本可以无差异地在 Chrome、Edge、Safari 和 Firefox 上运行对于需要做跨浏览器兼容性测试的场景这直接省去了大半的适配工作量。更关键的是它的“智能”。Playwright 内置了自动等待机制它会等待元素可操作、网络请求完成、甚至动画结束你再也不用到处写time.sleep(5)这种既低效又不稳定的代码了。它还提供了强大的网络拦截、文件下载、地理位置模拟、设备模拟比如模拟 iPhone 13等能力让自动化脚本能覆盖更真实的用户场景。无论是做 Web 应用的端到端测试、爬取动态渲染的复杂数据还是实现一些重复性的网页操作比如定时签到、数据填报Playwright 都能提供稳定、高效的解决方案。这篇文章我会从一个实际使用者的角度带你从零开始搞定 Playwright 的安装和那些看似复杂、实则能极大提升效率的配置文件让你少走我踩过的那些坑。2. 核心安装策略不止是pip install playwright安装 Playwright 听起来很简单但不同的场景和需求选择正确的安装方式能避免后续一堆奇怪的问题。这里我们分几个层面来拆解。2.1 语言绑定与核心库安装Playwright 为多种编程语言提供了绑定最主流的是 Python 和 Node.js。选择哪个主要看你的技术栈和团队习惯。Python 环境安装对于 Python 用户安装就是一行命令的事。但这里有个细节建议在虚拟环境中进行。这能避免与你系统全局的 Python 包发生冲突。# 创建并激活虚拟环境以 venv 为例 python -m venv playwright-env source playwright-env/bin/activate # Linux/macOS # 或 playwright-env\Scripts\activate # Windows # 安装 Playwright 的 Python 库 pip install playwright执行完pip install playwright后你只是安装了控制浏览器的高级 API 库。此时如果你直接运行脚本会提示找不到浏览器。这是因为 Playwright 管理的浏览器是独立于系统安装的浏览器之外的它需要单独下载。Node.js 环境安装Node.js 的安装同样直接# 初始化项目如果还没有 package.json npm init -y # 安装 Playwright 库 npm install playwright注意无论是 Python 还是 Node.js官方都强烈建议使用稳定的 LTS 版本。对于 Python建议使用 3.8 及以上版本对于 Node.js建议使用 18 及以上版本。我曾在一个 Python 3.7 的环境里折腾了半天总是遇到一些异步相关的兼容性问题升级到 3.8 后迎刃而解。2.2 浏览器二进制文件安装playwright install的奥秘安装完语言库下一步是安装浏览器。这是 Playwright 设计的精妙之处——它不依赖你系统里可能版本混乱的 Chrome 或 Firefox而是通过playwright install命令下载由 Playwright 团队专门构建、测试并确保 API 兼容的浏览器版本。运行以下命令# Python 环境 playwright install # Node.js 环境 npx playwright install这个命令会做几件事下载浏览器默认会下载 Chromium、Firefox 和 WebKitSafari 的开源核心的最新稳定版本。它们会被下载到一个独立目录如~/.cache/ms-playwright与你的系统浏览器完全隔离。安装操作系统依赖Playwright 会自动检测你的操作系统Windows、macOS、Linux并尝试安装运行这些浏览器所需的一些系统库。例如在 Linux 上它会安装libwoff1、libopus0等字体和多媒体库。安装特定版本或浏览器有时你可能需要安装特定版本的浏览器或者只安装其中一两种以节省磁盘空间。# 安装指定版本的 Chromium playwright install chromium1000 # 仅安装 Chromium 和 Firefox playwright install chromium firefox # 安装带有 Chrome/Edge 品牌标识的版本基于 Chromium playwright install chrome playwright install msedge实操心得在 CI/CD 流水线如 GitHub Actions, GitLab CI中playwright install是必须的一步。但为了加速构建你可以利用 Playwright 的缓存机制。很多 CI 系统允许你缓存~/.cache/ms-playwright目录这样下次构建时就不需要重新下载几百兆的浏览器文件了能显著缩短构建时间。2.3 验证安装与“Hello World”安装完成后写一个最简单的脚本来验证一切是否就绪。这里以 Python 为例import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: # 启动一个 Chromium 浏览器实例无头模式即不显示界面 browser await p.chromium.launch(headlessTrue) # 创建一个新的页面上下文 page await browser.new_page() # 导航到百度 await page.goto(https://www.baidu.com) # 截图保存证明浏览器能正常工作 await page.screenshot(pathbaidu.png) # 获取页面标题并打印 title await page.title() print(f页面标题是: {title}) # 关闭浏览器 await browser.close() # 运行异步函数 asyncio.run(main())保存为test_install.py并运行。如果一切顺利你会看到控制台输出“页面标题是: 百度一下你就知道”并且当前目录下会生成一张baidu.png的截图。这个简单的流程验证了库的安装、浏览器的启动、页面导航和基本操作是排查安装问题的基础。3. 配置文件深度解析从散装脚本到工程化项目当你开始编写越来越多的 Playwright 脚本时很快就会发现把浏览器类型、视窗大小、超时时间、基础URL等配置硬编码在每个脚本里是极其低效且难以维护的。这时Playwright 的配置文件playwright.config.ts或playwright.config.js或playwright.config.py就派上用场了。它让你能集中管理所有全局设置是项目工程化的第一步。3.1 配置文件的核心结构Playwright 支持用 JavaScript/TypeScript 或 Python 来写配置文件。对于测试项目使用 JS/TS 配置是主流因为它与 Playwright Test runner 集成得更好。我们先看一个 TypeScript 配置文件的骨架// playwright.config.ts import { defineConfig, devices } from playwright/test; export default defineConfig({ // 全局超时设置毫秒 timeout: 30 * 1000, // 30秒 // 期望断言expect的超时 expect: { timeout: 5000, // 5秒 }, // 是否在遇到第一个失败时停止所有测试 forbidOnly: !!process.env.CI, // 失败时重试次数在CI环境中很有用 retries: process.env.CI ? 2 : 0, // 并行运行的工作进程数 workers: process.env.CI ? 1 : 4, // 测试报告配置 reporter: [ [html, { outputFolder: playwright-report }], [list] // 在控制台输出简洁结果 ], // 项目配置这是核心可以定义多个项目对应不同浏览器/设备 projects: [ { name: chromium, use: { ...devices[Desktop Chrome] }, }, { name: firefox, use: { ...devices[Desktop Firefox] }, }, { name: webkit, use: { ...devices[Desktop Safari] }, }, // 模拟移动设备 { name: Mobile Chrome, use: { ...devices[Pixel 5] }, }, ], // 所有测试的公共基础配置 use: { // 基础URL测试中的相对路径会基于此 baseURL: http://localhost:3000, // 每个测试/动作的默认导航超时 navigationTimeout: 30000, // 每个动作如 click, fill的默认超时 actionTimeout: 10000, // 是否在失败时截图 screenshot: only-on-failure, // 是否录制失败测试的视频 video: retain-on-failure, // 浏览器上下文选项如视窗大小、权限等 viewport: { width: 1280, height: 720 }, ignoreHTTPSErrors: true, // 忽略 HTTPS 证书错误用于测试环境 // 录制用户交互的追踪信息用于调试 trace: retain-on-failure, }, // 全局的 setup 和 teardown 钩子文件路径 // globalSetup: ./global-setup, // globalTeardown: ./global-teardown, // 测试文件匹配规则 testMatch: **/*.spec.ts, });这个配置文件定义了一个标准的测试环境在本地开发时使用4个 worker 并行跑测试不重试在 CI 环境中则只使用1个 worker避免资源竞争并启用2次重试以提高稳定性。它为 Chromium、Firefox、WebKit 甚至移动设备 Chrome 定义了不同的测试项目。3.2 关键配置项实战解读让我们深入几个最常用也最容易出错的配置项。1.baseURL与导航策略baseURL是一个能极大提升脚本简洁性的配置。设置后你在测试中可以使用相对路径进行导航。// 配置中设置了 baseURL: http://localhost:3000 await page.goto(/login); // 实际访问 http://localhost:3000/login await page.goto(/dashboard); // 实际访问 http://localhost:3000/dashboard这避免了在每个测试文件中重复书写完整的 URL尤其在多环境开发、测试、预生产切换时只需修改配置文件中的baseURL即可。2. 超时配置矩阵Playwright 有多层超时设置理解它们能有效解决“脚本卡住”的问题。配置项作用范围默认值典型场景与调整建议timeout单个测试用例的总超时30秒对于复杂的长流程测试可能需要增加到 60-120 秒。expect.timeoutexpect断言等待的超时5秒等待某个元素出现或具备某种属性。如果页面加载慢可适当调大。navigationTimeoutpage.goto(),page.reload()等导航操作的超时30秒网络慢或页面初始加载资源多时可能需要增加。actionTimeoutpage.click(),page.fill()等用户操作的超时10秒确保元素在可操作状态。一般无需修改除非有极慢的动画。注意事项不要盲目地将所有超时设得非常大。超时是一种故障保护机制。正确的做法是结合 Playwright 的自动等待page.waitForSelector,page.waitForLoadState等来编写健壮的脚本让超时作为最后的防线。3. 并行与重试workers与retriesworkers: 指定并行运行测试的进程数。理论上等于你 CPU 的核心数能获得最佳性能。但在CI 环境中由于资源通常受限且需要稳定的输出建议设置为1。我在 Jenkins 上曾因为 workers 设置过多导致内存耗尽整个构建节点崩溃。retries: 失败重试次数。对于由网络抖动、第三方服务不稳定导致的偶发性失败重试能有效提高测试套件的稳定性。但切记重试不应掩盖真正的、可重复的 bug。通常只在 CI 环境中开启重试如retries: process.env.CI ? 2 : 0。4. 项目Projects与设备模拟projects部分让你能轻松实现跨浏览器、跨设备测试。devices对象预定义了数十种主流桌面和移动设备的配置包括视窗大小、用户代理、设备缩放比例等。projects: [ { name: Desktop Chrome (HiDPI), use: { ...devices[Desktop Chrome HiDPI], // 可以覆盖设备预设 viewport: { width: 1920, height: 1080 }, // 设置地理位置和语言 locale: zh-CN, geolocation: { longitude: 116.397128, latitude: 39.916527 }, // 北京 permissions: [geolocation], }, }, ]3.3 环境变量与多环境配置在实际项目中你肯定需要为不同环境开发、测试、生产使用不同的配置。环境变量是管理这些配置的最佳实践。// playwright.config.ts import { defineConfig } from playwright/test; // 从环境变量读取配置提供默认值 const baseURL process.env.BASE_URL || http://localhost:3000; const isCI !!process.env.CI; const isHeadless process.env.HEADLESS ! false; // 默认无头可通过 HEADLESSfalse 覆盖 export default defineConfig({ timeout: 60000, retries: isCI ? 2 : 0, workers: isCI ? 1 : undefined, // 本地环境使用默认值通常为CPU核心数的一半 reporter: isCI ? [[github], [html]] : [[list], [html]], // CI中使用GitHub格式报告 use: { baseURL, headless: isHeadless, // 是否以无头模式运行 screenshot: only-on-failure, video: isCI ? retain-on-failure : off, // 仅在CI中录制失败视频以节省空间 trace: isCI ? retain-on-failure : on-first-retry, }, projects: [...], }); // 运行命令示例 // HEADLESSfalse BASE_URLhttps://staging.example.com npx playwright test // CItrue npx playwright test --projectchromium通过环境变量你可以轻松地在本地调试显示浏览器界面、指向本地服务和 CI 执行无头模式、指向测试环境之间切换而无需修改配置文件。4. 高级配置与实战技巧掌握了基础配置后一些高级用法能让你如虎添翼解决更复杂的场景。4.1 全局 Setup 与 Teardown有些操作需要在所有测试运行前执行一次如登录获取认证令牌、初始化测试数据库在所有测试运行后清理如删除测试数据、关闭服务。这时就需要globalSetup和globalTeardown。// playwright.config.ts export default defineConfig({ globalSetup: ./global-setup.ts, globalTeardown: ./global-teardown.ts, // ... 其他配置 }); // global-setup.ts import { FullConfig } from playwright/test; import { request } from playwright/test; async function globalSetup(config: FullConfig) { // 1. 启动一个独立的测试服务如果需要 // const server startTestServer(); // process.env.TEST_SERVER_URL server.url; // 2. 获取认证令牌示例 const requestContext await request.newContext(); const response await requestContext.post(https://api.example.com/login, { data: { username: test, password: test } }); const { token } await response.json(); // 将令牌存入环境变量供所有测试项目使用 process.env.AUTH_TOKEN token; await requestContext.dispose(); // 3. 等待服务就绪 // await waitForService(process.env.TEST_SERVER_URL); } export default globalSetup; // global-teardown.ts import { FullConfig } from playwright/test; async function globalTeardown(config: FullConfig) { // 1. 关闭测试服务 // stopTestServer(); // 2. 清理测试数据库或存储 // await cleanupTestData(); console.log(全局清理完成。); } export default globalTeardown;在测试文件中你可以通过process.env.AUTH_TOKEN来使用这个全局的令牌。4.2 自定义 Fixture 复用代码Fixture 是 Playwright Test 中用于封装和复用测试准备逻辑如初始化页面对象、登录状态的强大工具。你可以在配置文件中定义自定义 fixture。// playwright.config.ts import { defineConfig, test as baseTest } from playwright/test; import { LoginPage } from ./pages/LoginPage; import { DashboardPage } from ./pages/DashboardPage; // 1. 声明 fixture 的类型 export type MyFixtures { loggedInPage: any; // 实际使用时替换为具体的 Page 类型 dashboardPage: DashboardPage; }; // 2. 基于基础 test 扩展注入自定义 fixture export const test baseTest.extendMyFixtures({ // 一个 fixture自动登录并返回一个已登录状态的页面 loggedInPage: async ({ page, context }, use) { const loginPage new LoginPage(page); await loginPage.goto(); await loginPage.login(standard_user, secret_sauce); // 示例用户名密码 // 等待登录成功例如导航到 dashboard await page.waitForURL(**/inventory.html); // 将 page 传递给测试用例使用 await use(page); // Fixture 销毁逻辑可选比如登出 // await loginPage.logout(); }, // 另一个 fixture直接返回一个页面对象 dashboardPage: async ({ page }, use) { await use(new DashboardPage(page)); }, }); // 3. 导出这个扩展后的 test并用于配置 export default defineConfig({ // ... 其他配置 }); // 在测试文件 (example.spec.ts) 中这样使用 import { test, expect } from ./playwright.config; // 从配置文件导入自定义的 test test(使用自定义 fixture, async ({ loggedInPage, dashboardPage }) { // loggedInPage 已经是一个登录后的页面对象 await loggedInPage.goto(/cart); // 或者使用封装好的页面对象 await dashboardPage.navigateToItem(Sauce Labs Backpack); await dashboardPage.addToCart(); await expect(dashboardPage.cartBadge).toHaveText(1); });通过 Fixture你将通用的准备逻辑如登录与具体的测试逻辑解耦使测试代码更简洁、更专注于业务断言。4.3 网络与存储状态模拟Playwright 允许你精细地控制网络请求和浏览器上下文Context的存储状态如 Cookies、LocalStorage。网络拦截与模拟你可以在配置中或测试中全局地拦截或修改请求用于模拟 API 响应、测试错误场景或屏蔽不必要的资源如图片、广告以加速测试。// 在配置的 use 部分或直接在测试中通过 context.route 设置 use: { // 拦截所有图片请求返回一个空的响应以加速测试 async routeHandler(route, request) { if (request.resourceType() image) { await route.fulfill({ status: 200, contentType: image/svgxml, body: svg xmlnshttp://www.w3.org/2000/svg/, }); } else { await route.continue(); } }, }存储状态复用登录状态往往是最耗时的操作之一。Playwright 允许你将一个已登录的浏览器上下文Context的存储状态保存下来并在后续测试中直接复用避免每次测试都重新登录。// 保存状态 test(保存登录状态, async ({ browser }) { const context await browser.newContext(); const page await context.newPage(); // ... 执行登录操作 await page.goto(https://example.com/login); await page.fill(#username, user); await page.fill(#password, pass); await page.click(#submit); await page.waitForURL(**/dashboard); // 将存储状态cookies, local storage保存到文件 await context.storageState({ path: state.json }); await context.close(); }); // 复用状态 test.describe(复用登录状态的测试套件, () { // 在套件级别创建一个带存储状态的 context test.use({ storageState: state.json, }); test(测试1直接访问需要登录的页面, async ({ page }) { // 此时 page 已经处于登录状态 await page.goto(https://example.com/dashboard); await expect(page).toHaveURL(**/dashboard); }); });5. 常见问题与排查技巧实录即使配置得当在实际使用中还是会遇到各种问题。下面是我总结的一些高频问题和解决方法。5.1 浏览器启动失败或无法找到问题现象执行脚本时报错Browser is not installed或启动超时。原因1没有运行playwright install。这是最常见的原因。解决运行playwright install或playwright install chromium指定浏览器。原因2系统缺少依赖。尤其是在 Linux 服务器或 Docker 镜像中。解决Playwright 的安装命令通常会尝试安装依赖但可能不完整。可以运行playwright install-deps来单独安装系统依赖。对于 Docker建议使用官方提供的镜像mcr.microsoft.com/playwright:v1.40.0-focal它包含了所有依赖。原因3杀毒软件或防火墙拦截。某些安全软件会阻止 Playwright 启动子进程。解决将 Playwright 的安装目录如~/.cache/ms-playwright和你的项目目录添加到杀毒软件的白名单中。5.2 脚本执行超时Timeout问题现象测试在page.goto()、page.click()或expect断言处失败提示超时。排查步骤检查网络与目标服务首先手动在浏览器中访问baseURL确认服务是否可达、响应是否缓慢。关闭无头模式在配置中设置headless: false或在命令行使用--headed参数直观地观察脚本执行到哪里卡住了。很多时候是页面弹出了意料之外的模态框或者元素选择器写错了。增加超时时间临时性地增加navigationTimeout或actionTimeout看是否能通过。但这只是临时诊断根本原因需要找到。使用更精确的等待替换通用的page.waitForTimeout(5000)使用 Playwright 提供的智能等待# 不推荐 - 盲目等待 await page.waitForTimeout(5000) # 推荐 - 等待特定元素出现 await page.waitForSelector(#submit-button, statevisible) # 推荐 - 等待网络请求完成 await page.waitForLoadState(networkidle) # 推荐 - 等待某个条件成立 await expect(page.locator(.loading-spinner)).toBeHidden()检查元素选择器使用 Playwright 的codegen工具重新生成选择器。运行playwright codegen https://example.com在浏览器中操作它会自动生成可靠的选择器代码。5.3 跨域 iframe 或新标签页操作失败问题现象无法获取或操作 iframe 内的元素或者在新打开的标签页上操作报错。iframe 处理# 通过 name 或 URL 定位 iframe frame page.frame(namemy-frame) # 通过 name # 或 frame page.frame(urlr.*payment.*) # 通过 URL 正则匹配 # 然后在 frame 对象上操作 await frame.fill(#card-number, 1234567812345678)注意如果 iframe 是跨域的且没有设置X-Frame-Options为允许Playwright 可能无法访问其内容。这是浏览器的安全限制通常需要被测试应用配合调整。新标签页处理# 监听新页面的弹出事件 async with page.context.expect_page() as new_page_info: await page.click(a[target_blank]) # 点击一个打开新标签页的链接 new_page await new_page_info.value # 现在可以在 new_page 上操作了 await new_page.waitForLoadState() print(await new_page.title())5.4 CI 环境下的特殊问题在 CI如 GitHub Actions, Jenkins中运行 Playwright 测试会遇到一些独特挑战。问题1无头模式下的字体渲染或布局差异现象本地通过的测试在 CI 上截图对比失败原因是文字渲染或元素位置有细微差别。解决在配置中为 CI 环境使用特定的字体配置use: { ...devices[Desktop Chrome], viewport: {...}, _extraCSS: ... }注入字体样式。或者更常见且推荐的方法是放宽截图对比的容差。Playwright 的expect(page).toHaveScreenshot()允许设置maxDiffPixels或threshold参数忽略几个像素的差异。问题2资源不足导致测试不稳定现象测试在 CI 上随机失败报内存不足或超时。解决限制并行 worker 数在配置中设置workers: 1。禁用不必要的功能在 CI 配置中关闭视频录制video: off和追踪trace: off除非失败时才开启retain-on-failure。增加 CI 机器的资源内存、CPU。问题3浏览器依赖缺失解决使用 Playwright 官方提供的 Docker 镜像是最稳妥的方式。例如在 GitHub Actions 中jobs: test: container: image: mcr.microsoft.com/playwright:v1.40.0-focal steps: - uses: actions/checkoutv3 - run: npm ci - run: npx playwright install --with-deps # 镜像已包含依赖此步可加速 - run: npx playwright test5.5 配置文件调试技巧当配置文件复杂时可能因为一个拼写错误或类型错误导致整个配置不生效。你可以使用 Node.js 的--inspect来调试配置文件虽然不常见但更简单有效的方法是使用--config参数指定配置文件npx playwright test --configmy.config.ts。这允许你为不同场景准备多个配置文件。在配置中打印调试信息export default defineConfig({ // ... 其他配置 globalSetup: ./global-setup.ts, }); // global-setup.ts async function globalSetup(config) { console.log(BaseURL is:, config.projects[0].use.baseURL); // 打印出实际生效的配置 console.log(CI environment?, !!process.env.CI); }运行 Playwright 命令时显示配置npx playwright test --debug会输出更详细的日志包括加载的配置路径和内容。配置文件是 Playwright 项目的基石花时间理解并合理配置它能在项目后期节省大量的调试和维护成本。从简单的超时设置到复杂的多项目、多环境、全局状态管理一个好的配置能让你的自动化项目从“能用”变得“高效且可靠”。