Playwright自动化测试与网页抓取实战速查手册
2026/8/30 18:26:14 网站建设 项目流程

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 install

Python版本同样需要执行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):告别脆弱的XPath

Playwright极力推荐使用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('test@example.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[type="file"]').setInputFiles('./my-file.pdf'); // 上传多个文件 await page.locator('input[type="file"]').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.typelocator.type()都会触发键盘事件,但后者是针对特定元素的。对于需要在输入框内按快捷键(如Ctrl+A)的场景,使用locator.press('Control+A')更合适。

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:这是最强大的可视化调试工具。通过以下方式启动:

# 设置环境变量后运行你的脚本 PWDEBUG=1 node your-script.js # 或 set PWDEBUG=1 && 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中是否存在且唯一。优先改用getByRolegetByTestId
  • 操作超时?检查元素是否被遮挡(如弹窗)、是否在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/checkout@v3 - uses: actions/setup-node@v3 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-artifact@v3 if: always() # 即使测试失败也上传报告 with: name: playwright-report path: playwright-report/ retention-days: 30

性能优化建议:

  1. 复用Browser Context:启动浏览器开销最大。在测试套件开始时启动一个browser,为每个测试创建独立的contextpage,测试结束后只关闭pagecontext,最后再关闭browser
  2. 并行执行:在配置中设置fullyParallel: trueworkers: process.env.CI ? 4 : '50%',充分利用多核。
  3. 禁用不必要的功能:在CI上可以禁用视频录制、使用无头模式(headless: true)。
  4. 选择性安装浏览器:如果只测试Chrome,使用npx playwright install chromium只安装一个浏览器。

这份速查表涵盖了从入门到进阶的核心知识点。真正的熟练来自于实践和踩坑。我的建议是,把它存为书签或本地文档,遇到问题时先来这里找找灵感。然后,大胆地去写你自己的脚本,从简单的页面操作开始,逐步尝试网络拦截、多页面管理这些高级功能。当你能够用Playwright流畅地处理一个包含登录、复杂交互、文件下载和状态验证的完整流程时,你会发现,网页自动化不再是痛苦的差事,而是一件充满成就感的利器。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询