Playwright自动化测试与网页抓取实战速查手册
1. 从“又慢又卡”到“丝滑如飞”为什么你需要一份Playwright速查表如果你和我一样日常工作中需要和网页自动化打交道那你肯定经历过这样的场景凌晨两点为了一个明天就要上线的爬虫脚本你还在和Selenium的某个元素定位器较劲浏览器驱动版本不匹配、元素加载时机飘忽不定、异步请求让你的脚本时灵时不灵。或者你正在为一个复杂的Web应用编写端到端测试却发现测试运行得比蜗牛还慢而且动不动就因为页面状态不一致而失败调试起来像在迷宫里找出口。几年前当我第一次接触Playwright时那种感觉就像从手动挡老爷车换成了自动驾驶的特斯拉。它原生支持所有现代浏览器Chromium, Firefox, WebKit内置了智能等待、网络拦截、设备模拟等一整套“开箱即用”的武器库写出来的脚本稳定性和执行速度都有了质的飞跃。但问题也随之而来它的API太丰富了功能点太多了。今天刚用page.waitForLoadState(networkidle)解决了问题下周可能就忘了page.waitForFunction()的具体用法知道locator比$好用但面对一长串的定位器选项getByRole,getByText,getByTestId...时又得去翻文档。这就是我整理这份“Playwright速查表”的初衷。它不是一个面面俱到的教程而是一份聚焦于“实战高频操作”的精华备忘录。目的是让你在遇到具体问题时能像查字典一样快速找到解决方案跳过冗长的官方文档直接套用经过验证的代码片段和最佳实践。无论你是想快速写一个数据抓取脚本还是构建一套健壮的自动化测试体系这份速查表都能成为你手边最高效的“瑞士军刀”。2. 环境搭建与项目初始化避开第一个坑万事开头难一个正确的起步能避免后续80%的奇怪问题。Playwright的环境搭建看似简单但细节决定成败。2.1 安装Node.js与Python双版本选择Playwright主要支持Node.js和Python两种语言环境。我的建议是如果你主要进行Web自动化测试特别是前端项目选Node.js如果你的自动化脚本需要深度集成到数据科学或后端运维流水线中选Python。两者API几乎完全一致选你团队最熟悉的就行。Node.js环境安装# 初始化项目如果还没有package.json npm init -y # 安装Playwright库 npm install playwright # 安装浏览器二进制文件至关重要这步会下载Chromium, Firefox, WebKit npx playwright install这里有个关键点npx playwright install会下载浏览器。如果你在CI/CD环境如GitHub Actions或受限的网络环境中可能需要配置镜像或使用PLAYWRIGHT_DOWNLOAD_HOST环境变量来加速。对于团队协作建议将浏览器二进制也纳入版本管理通过playwright-core和单独安装浏览器可以避免因网络问题导致的构建失败。Python环境安装pip install playwright playwright installPython版本同样需要执行playwright install来安装浏览器。2.2 初始化配置playwright.config.ts 的黄金法则创建配置文件不是必须的但对于任何严肃的项目我强烈建议你从一开始就使用它。它能让你的脚本行为一致、易于配置。// playwright.config.ts import { defineConfig, devices } from playwright/test; export default defineConfig({ // 全局超时设置每个测试的最大执行时间 timeout: 30 * 1000, // 30秒 // 全局断言超时expect 断言等待的最大时间 expect: { timeout: 5000, // 5秒 }, // 并行运行测试在CI上可以开启加速本地调试建议关闭 fullyParallel: false, // 失败时重试次数对于排查偶发性网络问题非常有用 retries: process.env.CI ? 2 : 0, // 报告器推荐使用‘html’报告直观看到失败截图和轨迹 reporter: html, // 共享配置所有项目浏览器共享的设置 use: { // 基础URL这样page.goto就可以用相对路径了 // baseURL: http://localhost:3000, // 自动录制失败视频非常耗资源建议仅在CI或调试特定用例时开启 // video: retain-on-failure, // 自动截图同上推荐‘only-on-failure’ screenshot: only-on-failure, // 最重要的设置之一默认的等待、操作、导航超时 actionTimeout: 10000, navigationTimeout: 30000, }, // 项目配置定义不同的浏览器环境 projects: [ { name: chromium, use: { ...devices[Desktop Chrome] }, }, { name: firefox, use: { ...devices[Desktop Firefox] }, }, // 移动端模拟 { name: Mobile Chrome, use: { ...devices[Pixel 5] }, }, ], });注意timeout,actionTimeout,navigationTimeout这几个超时设置是新手最容易踩坑的地方。它们的优先级是具体API调用时的超时参数 项目配置中的use 全局配置。如果某个操作如点击卡住了首先检查是不是这里的超时设置得太短而页面响应太慢。3. 核心API速查定位、操作与等待的艺术这是Playwright的筋骨。掌握这部分你就能应对90%的自动化场景。3.1 元素定位器Locator告别脆弱的XPathPlaywright极力推荐使用LocatorAPI它代表一个随时可以查找的元素并且内置了自动等待和重试机制。这是它比Selenium稳定得多的核心原因之一。最常用的定位器生成方法const page await browser.newPage(); await page.goto(https://example.com); // 1. getByRole - 按ARIA角色定位最语义化、最稳定首选 await page.getByRole(button, { name: 提交 }).click(); await page.getByRole(textbox, { name: 用户名 }).fill(myuser); // 2. getByText - 按文本内容定位 await page.getByText(登录).click(); // 精确文本 await page.getByText(/欢迎/).click(); // 正则匹配 // 3. getByLabel - 通过关联的label文本定位表单元素 await page.getByLabel(邮箱地址).fill(testexample.com); // 4. getByPlaceholder - 定位输入框占位符 await page.getByPlaceholder(请输入密码).fill(secret); // 5. getByAltText - 定位图片的alt属性 await page.getByAltText(公司Logo).click(); // 6. getByTitle - 定位title属性 await page.getByTitle(关闭对话框).click(); // 7. getByTestId - 专为测试设计的定位方式最稳定 // 前端需要在元素上添加>// 在某个特定区域内查找 const modal page.locator(.modal); await modal.getByText(确认).click(); // 过滤找到第三个按钮 await page.getByRole(button).nth(2).click(); // 过滤找到包含特定文本的 await page.getByRole(listitem).filter({ hasText: 重要 }).click(); // 链式调用先找行再在行内找按钮 await page.locator(tr).filter({ hasText: 项目A }).getByRole(button, { name: 删除 }).click();3.2 页面操作模拟真实用户行为Playwright的操作API非常直观并且每个操作都自动等待元素可交互。基础操作// 点击 await page.getByText(按钮).click(); // 带选项的点击强制点击、右键、组合键 await page.getByText(按钮).click({ button: right }); await page.getByText(按钮).click({ modifiers: [Control] }); // Ctrl点击 // 输入文本 await page.getByLabel(搜索框).fill(关键词); // 模拟逐个字符输入用于触发输入事件 await page.getByLabel(搜索框).type(关键词, { delay: 100 }); // 清空输入框 await page.getByLabel(搜索框).clear(); // 勾选/取消勾选复选框、单选框 await page.getByLabel(同意协议).check(); await page.getByLabel(选项A).uncheck(); // 选择下拉框选项按value, label 或 index await page.locator(select#city).selectOption({ label: 北京 }); await page.locator(select#city).selectOption(beijing); // 按value // 上传文件非常方便 await page.locator(input[typefile]).setInputFiles(./my-file.pdf); // 上传多个文件 await page.locator(input[typefile]).setInputFiles([./a.jpg, ./b.jpg]); // 聚焦元素 await page.getByLabel(评论框).focus(); // 拖放从元素A拖到元素B const dragElement page.locator(#draggable); const dropZone page.locator(#droppable); await dragElement.dragTo(dropZone);键盘与鼠标操作// 键盘操作 await page.keyboard.type(Hello); // 输入字符串 await page.keyboard.press(Enter); // 按下回车 await page.keyboard.down(Shift); // 按住Shift await page.keyboard.up(Shift); // 松开Shift // 鼠标操作 await page.mouse.move(100, 200); // 移动到坐标(100, 200) await page.mouse.down(); // 按下鼠标左键 await page.mouse.up(); // 松开 await page.mouse.wheel(0, 100); // 向下滚动100像素注意page.keyboard.type和locator.type()都会触发键盘事件但后者是针对特定元素的。对于需要在输入框内按快捷键如CtrlA的场景使用locator.press(ControlA)更合适。3.3 等待策略让脚本“聪明”地等待这是Playwright的“智能”所在。错误的等待如sleep(5000)是脚本脆弱和低效的元凶。自动等待隐式等待Playwright在执行操作如click,fill和断言如expect(locator).toBeVisible()前会自动等待元素满足可操作条件如可见、稳定、未禁用。你通常不需要额外处理。显式等待// 1. 等待导航完成最常用 await page.goto(https://example.com, { waitUntil: networkidle }); // 推荐网络空闲 // waitUntil 选项: load(默认), domcontentloaded, networkidle, commit // 2. 等待元素出现/满足状态 await page.locator(.success-message).waitFor({ state: visible }); // state 选项: attached(存在于DOM), detached(不存在), visible, hidden // 3. 等待特定条件成立功能强大 await page.waitForFunction(() { return document.querySelectorAll(.loaded-item).length 5; }); // 也可以传入参数 await page.waitForFunction((expectedCount) { return document.querySelectorAll(.item).length expectedCount; }, 5); // 4. 等待超时设置覆盖全局配置 await page.click(button, { timeout: 15000 }); // 这个点击操作单独设置15秒超时等待网络请求// 等待某个特定请求完成并获取其响应用于测试API调用 const [response] await Promise.all([ page.waitForResponse(resp resp.url().includes(/api/data) resp.status() 200), page.click(button#fetch-data), // 触发请求的操作 ]); const responseData await response.json(); // 等待所有指定类型的请求完成 await page.waitForLoadState(networkidle); // 等待网络空闲500ms内无新请求踩坑实录waitForLoadState(networkidle)在单页应用SPA中可能不可靠因为SPA可能使用长轮询或WebSocket。更稳妥的做法是等待一个具体的、代表页面加载完成的DOM元素出现例如await page.waitForSelector(.data-loaded, { state: visible })。4. 高级功能与调试技巧从能用走向好用当你掌握了基础操作后这些高级功能能让你如虎添翼处理更复杂的场景。4.1 处理弹窗、新窗口与iframe弹窗对话框// 监听并接受一个alert对话框 page.on(dialog, async dialog { console.log(对话框信息: ${dialog.message()}); await dialog.accept(); // 点击“确定” // 其他方法: dialog.dismiss()取消, dialog.defaultValue(), dialog.type() }); // 监听并处理beforeunload对话框需要额外参数 page.on(dialog, async dialog { if (dialog.type() beforeunload) { await dialog.accept(); // 离开页面 } });新窗口/标签页// 点击一个会打开新标签页的链接并切换到新页面 const [newPage] await Promise.all([ context.waitForEvent(page), // 监听新page事件 page.click(a[target_blank]), // 触发打开新窗口的操作 ]); await newPage.bringToFront(); // 切换到新页面 // 在新页面操作 await newPage.fill(input, value);iframe处理// 通过元素句柄定位iframe const frameElement await page.$(iframe#myFrame); const frame await frameElement.contentFrame(); await frame.click(button); // 通过name或URL定位iframe更简洁 const frame page.frame({ name: my-frame }); // 或 const frame page.frame({ url: /.*login.*/ }); if (frame) { await frame.fill(#username, user); }4.2 网络拦截与模拟Mock这个功能极其强大可以用于测试、屏蔽广告、修改请求/响应。拦截请求// 1. 路由拦截特定请求并返回自定义响应Mock API await page.route(**/api/user/profile, async route { // 直接返回模拟数据 await route.fulfill({ status: 200, contentType: application/json, body: JSON.stringify({ username: mockUser, age: 30 }), }); }); // 2. 修改请求 await page.route(**/*, async route { const headers { ...route.request().headers(), X-Custom-Header: MyValue }; await route.continue({ headers }); }); // 3. 中止请求如屏蔽图片、广告 await page.route(**/*.{png,jpg,jpeg}, route route.abort());拦截响应// 监听响应并获取数据 page.on(response, async response { if (response.url().includes(/api/data)) { const data await response.json(); console.log(抓取到的数据:, data); } });4.3 设备模拟与地理位置Playwright可以模拟各种移动设备和视口以及地理位置、语言、时区等。const { devices } require(playwright); const iPhone devices[iPhone 13 Pro]; // 创建带有设备模拟的上下文 const context await browser.newContext({ ...iPhone, // 视口、User-Agent、设备比例等 locale: zh-CN, // 语言 timezoneId: Asia/Shanghai, // 时区 geolocation: { longitude: 121.4737, latitude: 31.2304 }, // 上海坐标 permissions: [geolocation], // 授予地理位置权限 }); const page await context.newPage();4.4 文件下载与上传处理文件下载// 监听下载事件 const [download] await Promise.all([ page.waitForEvent(download), // 等待下载开始 page.click(a#download-link), // 触发下载 ]); // 获取下载建议的文件名并保存到指定路径 const suggestedFilename download.suggestedFilename(); const savePath ./downloads/${suggestedFilename}; await download.saveAs(savePath); console.log(文件已下载到: ${savePath});4.5 调试与问题排查当脚本不按预期运行时再稳定的框架也会遇到问题掌握调试技巧至关重要。1. 慢动作与暂停// 全局慢动作方便观察 await page.click(button, { delay: 2000 }); // 点击前等待2秒 // 在代码中插入暂停调试用 await page.pause(); // 运行到这里会打开Playwright Inspector可以单步调试2. Playwright Inspector这是最强大的可视化调试工具。通过以下方式启动# 设置环境变量后运行你的脚本 PWDEBUG1 node your-script.js # 或 set PWDEBUG1 node your-script.js (Windows)运行后会自动打开一个浏览器窗口和Inspector工具你可以看到实时执行的命令、查看DOM、生成选择器并控制执行步骤。3. 丰富的截图与录屏// 页面截图 await page.screenshot({ path: fullpage.png, fullPage: true }); // 元素截图 await page.locator(.header).screenshot({ path: header.png }); // 录制视频需在context或page配置中开启 const context await browser.newContext({ recordVideo: { dir: ./videos } }); const page await context.newPage(); // ... 你的操作 await context.close(); // 关闭context后视频文件才会保存4. 控制台日志与网络监听// 监听页面console日志 page.on(console, msg { if (msg.type() error) { console.error(页面错误:, msg.text()); } }); // 监听页面错误 page.on(pageerror, error { console.error(页面未捕获错误:, error.message); }); // 监听网络请求失败 page.on(requestfailed, request { console.error(请求失败: ${request.url()} - ${request.failure().errorText}); });5. 常见问题排查清单当你的脚本失败时按这个顺序检查元素找不到首先用await page.pause()打开Inspector确认你写的选择器在当前DOM中是否存在且唯一。优先改用getByRole或getByTestId。操作超时检查元素是否被遮挡如弹窗、是否在iframe内、是否处于不可交互状态如disabled。增加{ timeout }参数并配合waitForSelector。页面状态不稳定可能是SPA的异步加载。用page.waitForFunction()等待一个可靠的页面状态标志。脚本在CI上失败本地却成功通常是环境差异网络延迟、资源加载慢、CI机器无头模式下的视口差异。增加全局超时在CI配置中明确设置视口大小使用waitUntil: networkidle或更保守的等待条件。5. 测试集成与最佳实践构建健壮的自动化体系Playwright本身也是一个出色的测试框架。即使你不写测试了解其测试模式也能帮你写出更健壮的脚本。5.1 使用Playwright Test运行器Playwright Test提供了夹具Fixtures、钩子Hooks、断言等一套完整测试设施。// example.spec.js const { test, expect } require(playwright/test); // 每个测试用例会获得一个独立的page fixture test(基本登录测试, async ({ page }) { await page.goto(/login); await page.getByLabel(用户名).fill(testuser); await page.getByLabel(密码).fill(password); await page.getByRole(button, { name: 登录 }).click(); // Playwright提供的增强断言自带等待 await expect(page).toHaveURL(/\/dashboard/); await expect(page.getByText(欢迎回来)).toBeVisible(); }); // 使用钩子进行设置和清理 test.describe(用户管理模块, () { test.beforeEach(async ({ page }) { // 每个测试前都先登录 await page.goto(/login); // ... 登录操作 }); test.afterAll(async ({ browser }) { // 所有测试结束后清理数据 // ... }); });运行测试npx playwright test。它会自动并行运行测试生成报告。5.2 页面对象模型Page Object Model模式对于复杂应用强烈推荐使用POM模式将页面封装成类提高代码可维护性。// pages/LoginPage.js class LoginPage { constructor(page) { this.page page; this.usernameInput page.getByLabel(用户名); this.passwordInput page.getByLabel(密码); this.submitButton page.getByRole(button, { name: 登录 }); this.errorMessage page.locator(.error-message); } async navigate() { await this.page.goto(/login); } async login(username, password) { await this.usernameInput.fill(username); await this.passwordInput.fill(password); await this.submitButton.click(); } async getErrorMessage() { return await this.errorMessage.textContent(); } } module.exports LoginPage; // 在测试中使用 const LoginPage require(./pages/LoginPage); test(登录失败显示错误信息, async ({ page }) { const loginPage new LoginPage(page); await loginPage.navigate(); await loginPage.login(wrong, wrong); await expect(loginPage.errorMessage).toContainText(用户名或密码错误); });5.3 CI/CD集成与性能优化在GitHub Actions中运行# .github/workflows/playwright.yml name: Playwright Tests on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - uses: actions/setup-nodev3 with: { node-version: 18 } - name: 安装依赖 run: npm ci - name: 安装Playwright浏览器 run: npx playwright install --with-deps - name: 运行Playwright测试 run: npx playwright test - uses: actions/upload-artifactv3 if: always() # 即使测试失败也上传报告 with: name: playwright-report path: playwright-report/ retention-days: 30性能优化建议复用Browser Context启动浏览器开销最大。在测试套件开始时启动一个browser为每个测试创建独立的context和page测试结束后只关闭page和context最后再关闭browser。并行执行在配置中设置fullyParallel: true和workers: process.env.CI ? 4 : 50%充分利用多核。禁用不必要的功能在CI上可以禁用视频录制、使用无头模式headless: true。选择性安装浏览器如果只测试Chrome使用npx playwright install chromium只安装一个浏览器。这份速查表涵盖了从入门到进阶的核心知识点。真正的熟练来自于实践和踩坑。我的建议是把它存为书签或本地文档遇到问题时先来这里找找灵感。然后大胆地去写你自己的脚本从简单的页面操作开始逐步尝试网络拦截、多页面管理这些高级功能。当你能够用Playwright流畅地处理一个包含登录、复杂交互、文件下载和状态验证的完整流程时你会发现网页自动化不再是痛苦的差事而是一件充满成就感的利器。