1. 项目概述:为什么是Playwright?
如果你最近在找一款能搞定现代Web应用自动化测试的工具,大概率会听到Playwright这个名字。它不再是那个“微软出的新工具”,而是成为了很多团队在构建端到端测试、爬虫甚至RPA流程时的首选。我最初接触它是因为一个老项目用Selenium维护起来太痛苦了——页面元素动态加载、iframe嵌套、网络请求难以模拟,调试一次测试脚本的时间比写代码还长。换成Playwright后,这些问题得到了根本性的缓解。
简单来说,Playwright是一个开源的浏览器自动化库,它允许你用代码控制Chromium、Firefox和WebKit(Safari的内核)浏览器,模拟真实用户的操作,比如点击、输入、导航、截图等。它的核心优势在于“为现代Web而生”,天生就处理好了单页应用(SPA)的异步加载、Shadow DOM、网络拦截等让传统自动化工具头疼的特性。更关键的是,它提供了跨浏览器、跨平台(Windows, macOS, Linux)的一致API,支持TypeScript、JavaScript、Python、.NET和Java,生态非常友好。
无论你是测试工程师想搭建可靠的UI自动化测试套件,还是开发者想写个爬虫抓取动态渲染的数据,抑或是运维同学想自动化一些日常的Web操作,Playwright都能提供一个高效、稳定的解决方案。这篇教程不会只停留在“Hello World”,我会带你从零开始,深入核心概念,分享实战中踩过的坑和总结的技巧,目标是让你看完就能上手解决实际问题。
2. 环境搭建与核心概念解析
2.1 安装与初始化:避开第一个坑
安装Playwright听起来简单,但这里有几个细节决定了你后续的体验。官方推荐使用npm或yarn进行安装。对于Node.js项目,最标准的做法是:
# 初始化一个npm项目(如果还没有package.json) npm init -y # 安装Playwright库 npm install playwright # 安装浏览器(这一步很关键,建议单独执行) npx playwright install这里有个常见的“坑”:npx playwright install会下载Chromium、Firefox和WebKit三大浏览器,由于网络原因,下载速度可能非常慢甚至失败。很多新手卡在这一步就放弃了。我的经验是,可以分别安装或者使用镜像源。
方案一:使用Playwright中国镜像加速这是最推荐的方法,能极大提升安装速度。在安装前设置环境变量即可:
# 对于Linux/macOS export PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright npx playwright install # 对于Windows (PowerShell) $env:PLAYWRIGHT_DOWNLOAD_HOST="https://npmmirror.com/mirrors/playwright" npx playwright install方案二:仅安装需要的浏览器如果你只需要Chromium,可以只安装它,节省时间和磁盘空间:
npx playwright install chromium方案三:手动下载(终极方案)如果上述方法都失败,可以去Playwright的GitHub Releases页面手动下载对应平台的浏览器压缩包,然后解压到特定的缓存目录。不过操作稍复杂,一般用前两种方法都能解决。
安装完成后,创建一个简单的测试脚本来验证环境。新建一个demo.js文件:
const { chromium } = require('playwright'); (async () => { // 1. 启动浏览器(默认是无头模式,即不显示UI) const browser = await chromium.launch({ headless: false }); // 设置为true则后台运行 // 2. 创建一个新的浏览器上下文(类似于一个独立的会话) const context = await browser.newContext(); // 3. 打开一个新页面 const page = await context.newPage(); // 4. 导航到目标网址 await page.goto('https://example.com'); // 5. 截图保存 await page.screenshot({ path: 'example.png' }); // 6. 关闭浏览器 await browser.close(); })();运行node demo.js,如果能看到浏览器打开并截图保存,说明环境一切正常。这里引入了几个核心对象:browser、context和page,它们是Playwright API的基石。
2.2 理解核心架构:Browser, Context, Page
很多教程直接教API,但如果不理解这三个概念的关系,后面写复杂脚本时会很混乱。你可以把它们想象成一个多标签页浏览器的抽象模型。
Browser: 对应一个浏览器实例。通过
chromium.launch()或firefox.launch()启动。一个Browser进程可以承载多个独立的Context。Context: 这是Playwright中最重要的概念之一。一个Context相当于一个独立的“浏览器会话”。它拥有独立的cookie、localStorage、sessionStorage和缓存,并且可以配置代理、用户代理(User-Agent)、视口大小等。每个测试用例或爬虫任务通常应该使用独立的Context,这样可以保证用例之间的隔离,避免状态污染。创建Context的成本比启动Browser低得多。
Page: 对应一个浏览器标签页。一个Context可以包含多个Page。我们绝大部分操作(如点击、输入)都是在Page对象上完成的。
它们的关系是:Browser-> 一个或多个Context-> 一个或多个Page。这种设计带来了巨大的灵活性:
- 并行测试: 可以在一个Browser下创建多个Context,在每个Context中运行不同的测试用例,实现并行化,大幅提升执行速度。
- 状态隔离: 用不同Context模拟不同用户登录同一网站,互不干扰。
- 资源管理: 可以单独关闭某个Page或Context,而不影响其他部分。
理解了这个模型,再看下面的代码就清晰了:
const browser = await chromium.launch(); // 模拟用户A const contextA = await browser.newContext(); const pageA1 = await contextA.newPage(); const pageA2 = await contextA.newPage(); // 用户A打开了两个标签页 // 模拟用户B,拥有完全独立的会话 const contextB = await browser.newContext(); const pageB1 = await contextB.newPage(); // ... 执行操作 // 清理时,关闭Context会自动关闭其下的所有Page await contextA.close(); await contextB.close(); await browser.close();3. 核心API与自动化操作实战
3.1 元素定位与交互:超越page.click()
定位并操作元素是自动化的基础。Playwright提供了多种强大且稳定的定位器(Locator),这是它比Selenium优秀的地方之一。
基础定位器:
page.locator('text=登录'): 通过文本内容定位。page.locator('#username'): 通过CSS选择器定位。page.locator('button:has-text("Submit")'): 使用更强大的CSS扩展选择器。page.locator('input[name="email"]'): 通过属性定位。page.getByRole('button', { name: 'Sign in' }):推荐通过ARIA角色定位,这是最接近用户感知的方式,可访问性最好,代码也最健壮。page.getByLabel('用户名'): 通过关联的label文本定位。page.getByPlaceholder('请输入邮箱'): 通过占位符定位。page.getByTestId('login-submit'): 通过开发者自定义的>const { chromium } = require('playwright'); (async () => { const browser = await chromium.launch({ headless: false, slowMo: 500 }); // slowMo让操作变慢,方便观察 const context = await browser.newContext(); const page = await context.newPage(); await page.goto('https://your-test-site.com/login'); // 不推荐的脆弱写法:使用CSS选择器,一旦样式或class改变就失效 // await page.locator('.login-form input[type="text"]').fill('myuser'); // 推荐写法1:使用getByLabel(如果表单结构规范) await page.getByLabel('用户名或邮箱').fill('testuser@example.com'); await page.getByLabel('密码').fill('securepassword123'); // 推荐写法2:使用getByPlaceholder // await page.getByPlaceholder('请输入邮箱').fill('testuser@example.com'); // 推荐写法3:使用getByRole(最健壮) // 假设按钮的文本是“登录” await page.getByRole('button', { name: '登录' }).click(); // 等待导航完成或某个登录后元素出现 await page.waitForURL('**/dashboard'); // 等待URL变成仪表盘 // 或者 await page.locator('text=欢迎回来,testuser').waitFor(); // 等待欢迎文本出现 await page.screenshot({ path: 'after-login.png' }); await browser.close(); })();重要经验:
- 永远优先使用
getByRole、getByText、getByLabel、getByTestId这类语义化定位器。它们比CSS选择器稳定得多,因为CSS经常因前端重构而改变。 - 对于动态内容,
page.waitForSelector()或locator.waitFor()是你的好朋友。但更高级的做法是使用page.waitForFunction()等待特定的JS条件成立。 - 操作元素前,Playwright会自动执行一系列检查:元素是否可见、是否可交互、是否稳定(不在动画中)。这避免了“元素点击不了”的经典问题。你可以通过
{ force: true }参数强制操作,但应尽量避免,因为这违背了真实用户场景。
3.2 处理等待与异步:告别
sleep动态内容是现代Web应用自动化失败的主要原因。元素还没加载出来你就去点击,当然会失败。新手最容易犯的错误就是到处用
page.waitForTimeout(5000)(相当于sleep),这是极其低效且不可靠的做法。Playwright内置了自动等待机制。对于大多数操作(如
click,fill,check),Playwright在动作执行前会等待元素满足可操作条件(可见、启用、稳定)。但有时候你需要更精细的控制。正确的等待策略:
导航等待:
page.goto()和page.click()(如果触发导航)默认会等待页面达到load状态。对于SPA,你可能需要等待networkidle。await page.goto('https://example.com', { waitUntil: 'networkidle' }); // 等待网络基本空闲等待元素:
// 等待选择器对应的元素出现在DOM中 await page.waitForSelector('.modal'); // 等待元素变为可见状态 await page.waitForSelector('.modal', { state: 'visible' }); // 使用Locator的waitFor方法更简洁 await page.locator('.modal').waitFor(); await page.locator('.modal').waitFor({ state: 'visible' });等待特定条件: 这是处理动态内容的利器。
// 等待某个元素消失 await page.locator('.loading-spinner').waitFor({ state: 'hidden' }); // 等待URL包含特定字符串 await page.waitForURL('**/order/success'); // 等待页面标题变化 await page.waitForFunction(() => document.title === '订单完成'); // 等待网络请求完成(用于抓取数据) const [response] = await Promise.all([ page.waitForResponse(resp => resp.url().includes('/api/data') && resp.status() === 200), page.locator('button#load-data').click(), // 点击触发请求 ]); const data = await response.json(); // 直接获取响应数据Promise.all用于并行等待: 当你需要同时等待多个异步操作时。// 同时等待导航和某个元素出现,比串行等待快 await Promise.all([ page.waitForNavigation(), page.locator('button#submit').click(), ]);
黄金法则:尽可能使用基于事件的等待(等元素、等网络、等URL),彻底抛弃固定的
sleep。你的脚本会变得更快、更稳定。3.3 高级特性:网络拦截、文件下载与iframe
网络拦截与模拟(Mocking)这是Playwright的王牌功能之一,可以极大提升测试速度和稳定性。你可以拦截和修改任何网络请求。
// 监听所有请求 page.on('request', request => { console.log(`>> ${request.method()} ${request.url()}`); }); // 监听所有响应 page.on('response', response => { console.log(`<< ${response.status()} ${response.url()}`); }); // 拦截特定请求并返回模拟数据 await page.route('**/api/user/profile', async route => { // 直接返回一个JSON mock,不发送真实请求 await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ name: 'Mock User', id: 123 }), }); }); // 修改请求(如添加头信息) await page.route('**/*', async route => { const headers = { ...route.request().headers(), 'X-Custom-Header': 'my-value' }; await route.continue({ headers }); });这个功能在测试中非常有用:你可以模拟后端API返回错误、慢速响应或特定数据,从而覆盖各种测试场景,而无需搭建复杂的测试环境。
文件下载处理文件下载是爬虫常见需求。Playwright让这变得很简单。
// 监听下载事件 const [download] = await Promise.all([ // 等待下载开始 page.waitForEvent('download'), // 触发下载的动作 page.locator('a#download-report').click(), ]); // 获取下载建议的文件名 const suggestedFilename = download.suggestedFilename(); // 指定保存路径 const savePath = `./downloads/${suggestedFilename}`; // 等待下载完成并保存文件 await download.saveAs(savePath); console.log(`文件已下载到: ${savePath}`);注意,你需要确保浏览器上下文没有设置
acceptDownloads: false(默认是true)。处理iframeiframe是另一个自动化难点。Playwright的处理方式很直观:把iframe当作一个独立的Page对象。
// 通过元素句柄定位iframe const frameElement = await page.locator('iframe#my-iframe').elementHandle(); const frame = await frameElement.contentFrame(); // 获取iframe内部的Frame对象 // 现在可以在frame上操作了,就像操作page一样 await frame.locator('button.submit').click(); // 更简洁的方式:直接通过name或URL定位frame const frameByName = page.frame({ name: 'my-frame' }); const frameByUrl = page.frame({ url: /.*login.*/ }); if (frameByName) { await frameByName.fill('#username', 'user'); }关键在于,在iframe内部操作时,所有的定位器作用域都在那个iframe内,与外部页面隔离。
4. 测试框架集成与最佳实践
4.1 使用Playwright Test Runner
虽然你可以用纯脚本写自动化,但对于严肃的测试项目,强烈建议使用官方的
@playwright/test测试运行器。它提供了测试结构、夹具(Fixtures)、断言、报告等一站式解决方案。安装与配置:
npm init playwright@latest这个命令会以交互方式帮你完成一切设置:安装依赖、创建配置文件、生成示例测试。核心配置文件是
playwright.config.ts(或.js)。一个基本的测试用例示例:
// tests/example.spec.ts import { test, expect } from '@playwright/test'; // test.beforeEach 钩子会在每个测试前运行,用于公共设置 test.beforeEach(async ({ page }) => { await page.goto('https://demo.playwright.dev/todomvc'); }); test('应该添加新的待办事项', async ({ page }) => { // 使用 getByPlaceholder 定位输入框 const inputBox = page.getByPlaceholder('What needs to be done?'); await inputBox.fill('Buy milk'); await inputBox.press('Enter'); // 使用 getByTestId 定位待办事项列表项(假设前端设置了data-testid) const todoItem = page.getByTestId('todo-item'); await expect(todoItem).toHaveText('Buy milk'); // 也可以检查数量 await expect(todoItem).toHaveCount(1); }); test('应该标记待办事项为已完成', async ({ page }) => { await page.getByPlaceholder('What needs to be done?').fill('Buy eggs'); await page.getByPlaceholder('What needs to be done?').press('Enter'); const todoItem = page.getByTestId('todo-item'); const toggle = todoItem.getByRole('checkbox'); await toggle.check(); // 勾选复选框 // 断言元素有特定的CSS类 await expect(todoItem).toHaveClass('completed'); });测试运行器的优势:
- 并行执行: 默认并行运行测试文件,极快。
- 设备模拟: 轻松测试不同视口大小、设备类型。
- 自动等待内置: 断言和操作都内置了智能等待,无需手动写
waitFor。 - 强大的夹具系统:
page、context、browser都是作为夹具注入的,生命周期自动管理。 - 精美报告: 生成HTML、JSON等多种格式报告,包含截图、视频、追踪。
- 追踪(Trace): 测试失败时,可以查看完整的操作追踪,包括每个步骤的DOM快照、网络日志、控制台输出,是调试的神器。在配置中启用
trace: 'on-first-retry'或trace: 'retain-on-failure'。
4.2 配置管理与环境变量
实际项目会有多环境(开发、测试、生产)。硬编码URL和凭证是糟糕的做法。Playwright Config支持JavaScript/TypeScript,你可以动态配置。
playwright.config.ts示例:import { defineConfig, devices } from '@playwright/test'; // 从环境变量读取基础URL,默认为开发环境 const BASE_URL = process.env.BASE_URL || 'https://dev.example.com'; export default defineConfig({ // 全局超时设置 timeout: 30 * 1000, // 期望断言超时 expect: { timeout: 5000 }, // 测试失败时重试次数 retries: process.env.CI ? 2 : 0, // 是否在CI环境下禁止交互式操作(如下载) forbidOnly: !!process.env.CI, // 并行运行所有测试文件 fullyParallel: true, // 每个测试失败时保留追踪文件 reporter: [ ['html', { open: 'never' }], // 生成HTML报告,但不自动打开 ['list'] // 命令行输出 ], // 共享配置 use: { // 所有测试的基础URL baseURL: BASE_URL, // 每个测试的默认视口 viewport: { width: 1280, height: 720 }, // 是否忽略HTTPS错误 ignoreHTTPSErrors: true, // 每个动作后截图(仅失败时保留) screenshot: 'only-on-failure', // 每个测试录制视频(仅失败时保留) video: 'retain-on-failure', // 追踪配置 trace: 'retain-on-failure', }, // 项目配置:可以定义不同浏览器或设备的测试套件 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'] }, }, ], });然后在测试中,你可以直接使用相对路径导航,因为配置了
baseURL:await page.goto('/login'); // 实际访问的是 https://dev.example.com/login敏感信息如密码,应通过
.env文件和环境变量管理:# .env文件 TEST_USER_EMAIL=test@example.com TEST_USER_PASSWORD=your_secure_password在Playwright Config或测试文件中通过
process.env.TEST_USER_EMAIL读取。4.3 组织测试结构与页面对象模型
当测试规模增长时,良好的代码组织至关重要。页面对象模型(Page Object Model, POM)是一种经典且有效的模式,它将页面结构、元素定位和常用操作封装成类,提高代码复用性和可维护性。
一个简单的POM示例:
// pages/LoginPage.ts import { Locator, Page } from '@playwright/test'; export class LoginPage { readonly page: Page; readonly usernameInput: Locator; readonly passwordInput: Locator; readonly loginButton: Locator; readonly errorMessage: Locator; constructor(page: Page) { this.page = page; this.usernameInput = page.getByLabel('用户名或邮箱'); this.passwordInput = page.getByLabel('密码'); this.loginButton = page.getByRole('button', { name: '登录' }); this.errorMessage = page.locator('.alert-error'); } async navigate() { await this.page.goto('/login'); } async login(username: string, password: string) { await this.usernameInput.fill(username); await this.passwordInput.fill(password); await this.loginButton.click(); } async getErrorMessage(): Promise<string | null> { return await this.errorMessage.textContent(); } }在测试中使用页面对象:
// tests/login.spec.ts import { test, expect } from '@playwright/test'; import { LoginPage } from '../pages/LoginPage'; test('使用有效凭证登录成功', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.navigate(); await loginPage.login(process.env.TEST_USER_EMAIL!, process.env.TEST_USER_PASSWORD!); // 等待导航到首页,并验证登录成功 await expect(page).toHaveURL('/dashboard'); await expect(page.locator('text=欢迎回来')).toBeVisible(); }); test('使用无效凭证登录失败', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.navigate(); await loginPage.login('wrong@example.com', 'wrongpass'); // 验证错误信息出现 await expect(loginPage.errorMessage).toBeVisible(); const errorText = await loginPage.getErrorMessage(); expect(errorText).toContain('用户名或密码错误'); });POM的好处是显而易见的:如果登录页面的输入框ID变了,你只需要在一个地方(
LoginPage类)修改定位器,所有测试用例都会自动生效,维护成本大大降低。5. 调试技巧与常见问题排查
5.1 利用调试工具与追踪
脚本运行失败时,不要盲目地加
sleep或console.log。Playwright提供了强大的调试工具。1. 使用Playwright Inspector这是最直观的调试方式。在运行测试时加上
--debug标志,或设置环境变量PWDEBUG=1。# 使用CLI npx playwright test --debug # 或设置环境变量(会打开一个交互式调试器) PWDEBUG=1 npm testInspector会打开一个GUI,你可以:
- 实时查看浏览器。
- 暂停脚本执行。
- 查看每个步骤的详细日志。
- 使用“选择器拾取器”来生成元素定位代码。
- 单步执行测试。
2. 生成并查看追踪(Trace)在配置中启用
trace: 'on-first-retry'或trace: 'retain-on-failure'。测试失败后,会生成一个.zip追踪文件。使用以下命令查看:npx playwright show-trace trace.zip追踪查看器是一个Web应用,展示了测试执行的完整时间线,包括:
- 每个操作的屏幕截图。
- 当时的DOM状态。
- 所有网络请求和响应。
- 浏览器控制台日志。 这对于复现和理解“为什么当时点击没生效”这类问题至关重要。
3. 录制脚本(Codegen)对于完全陌生的网站,或者快速生成脚本原型,可以使用录制功能。
npx playwright codegen https://example.com这会打开一个浏览器和一个录制窗口。你在浏览器中的所有操作都会被实时转换成Playwright代码,并显示在录制窗口中。你可以直接复制这些代码。但请注意,自动生成的代码通常使用CSS选择器,你需要手动优化为更健壮的定位器(如
getByRole)。5.2 常见问题速查与解决方案
以下是我在实际项目中遇到的一些典型问题及解决方法,整理成了表格,方便快速查阅。
问题现象 可能原因 解决方案与排查步骤 元素找不到(TimeoutError) 1. 元素定位器写错了。
2. 元素在iframe里。
3. 页面还没加载完。
4. 元素是动态生成的,需要等待。1. 使用Playwright Inspector的“拾取器”验证定位器。
2. 检查页面结构,使用page.frames()查看是否有iframe,并切换到正确的frame操作。
3. 在操作前增加page.waitForLoadState('networkidle')。
4. 使用locator.waitFor()或等待特定条件。点击或输入没反应 1. 元素被遮挡(如弹窗、遮罩层)。
2. 元素不可见或不可交互(如disabled)。
3. 页面有未处理的弹窗(alert,confirm)。1. 检查元素上方是否有其他元素覆盖。可以尝试 { force: true }参数,但应先排查遮挡原因。
2. 检查元素状态:await expect(locator).toBeEnabled()和await expect(locator).toBeVisible()。
3. 监听对话框:page.on('dialog', dialog => dialog.accept())。脚本在CI(如GitHub Actions)上失败,本地却成功 1. CI环境网络慢,超时时间不足。
2. CI环境是headless模式,与本地有头模式行为有细微差异。
3. 资源路径或环境变量不同。1. 增加全局超时和expect超时: timeout: 60000。
2. 在CI配置中也使用有头模式运行一次以调试:headless: false。
3. 确保CI环境配置了正确的baseURL和依赖。使用trace: 'on'生成追踪文件分析。文件下载失败 1. 浏览器上下文默认禁止下载。
2. 下载路径不存在或没有写入权限。
3. 下载链接触发了新窗口或复杂JS。1. 创建context时确保 acceptDownloads: true(默认就是true)。
2. 确保保存目录存在 (fs.mkdirSync('./downloads', { recursive: true }))。
3. 使用page.waitForEvent('download')正确等待下载事件。处理Shadow DOM困难 Shadow DOM内的元素无法用普通CSS选择器直接定位。 Playwright的定位器原生支持Shadow DOM。 page.locator()可以穿透Shadow边界。直接使用page.locator('my-custom-element >>> .internal-button')或更简单的page.locator('my-custom-element').locator('.internal-button')。页面卡死或无响应 1. 页面JS有死循环或内存泄漏。
2. 等待条件永远无法满足。1. 设置合理的超时,避免脚本无限等待。
2. 使用Promise.race设置一个超时控制:await Promise.race([page.waitForSelector('.success'), page.waitForTimeout(10000)])。page.goto超时1. 网络问题或服务器慢。
2. 页面加载了大量资源(如图片、视频)。1. 增加 goto的超时时间:await page.goto(url, { timeout: 60000 })。
2. 使用waitUntil: 'domcontentloaded'代替'load'或'networkidle',只要HTML加载完就继续,不等待所有资源。5.3 性能优化与稳定性的经验之谈
最后,分享几条让Playwright脚本跑得更快、更稳的经验。
1. 复用Browser实例,但隔离Context启动Browser是昂贵的操作。对于测试套件,应该在所有测试开始前启动一个Browser实例,所有测试结束后关闭它。但每个测试必须使用独立的Context和Page,以保证隔离。
// 在全局Setup中启动Browser // playwright.config.ts export default defineConfig({ globalSetup: require.resolve('./global-setup'), globalTeardown: require.resolve('./global-teardown'), // ... }); // global-setup.ts import { chromium } from '@playwright/test'; async function globalSetup() { const browser = await chromium.launch(); // 将browser实例通过环境变量或全局存储传递(需自行实现) process.env.BROWSER_WS_ENDPOINT = browser.wsEndpoint(); }2. 避免不必要的导航和登录如果多个测试需要相同登录状态,可以使用
storageState保存和恢复Cookie、LocalStorage。// 登录一次,保存状态 const context = await browser.newContext(); const page = await context.newPage(); // ... 登录操作 await context.storageState({ path: 'state.json' }); await context.close(); // 后续测试直接加载状态,无需再次登录 const newContext = await browser.newContext({ storageState: 'state.json' });3. 拦截不必要的资源测试时不需要加载图片、视频、字体等,可以拦截它们以加速测试。
await page.route('**/*.{png,jpg,jpeg,svg,gif,woff,woff2}', route => route.abort()); // 或者,更激进地,只允许文档和脚本 await page.route('**/*', route => { const type = route.request().resourceType(); if (['document', 'script', 'xhr', 'fetch'].includes(type)) route.continue(); else route.abort(); });4. 使用软断言(Soft Assertions)默认情况下,
@playwright/test中的一个断言失败,整个测试就失败了。有时你想收集所有错误再报告。可以使用软断言:import { test, expect } from '@playwright/test'; test('检查多个元素', async ({ page }) => { const softExpect = expect.soft; // 创建软断言对象 await softExpect(page.locator('#elem1')).toBeVisible(); await softExpect(page.locator('#elem2')).toHaveText('Hello'); await softExpect(page.locator('#elem3')).toBeEnabled(); // 所有软断言执行完后,如果有失败的,测试才会标记为失败并报告所有错误 });5. 定期更新Playwright和浏览器Playwright团队更新活跃,会修复很多Bug并提升性能。定期运行
npm update playwright和npx playwright install --with-deps来更新到最新稳定版,能解决很多疑难杂症。Playwright的生态和社区非常活跃,遇到问题时,除了查看 官方文档 ,多看看GitHub Issues和社区讨论,往往能找到解决方案或灵感。记住,自动化脚本的终极目标是可靠和省力,在编写时多花一点时间思考定位器的健壮性和等待策略,会在后期的维护中节省大量时间。
- 永远优先使用