Playwright 官方迁移指南:从 Puppeteer 切换到 Playwright 的 API 对照、逐行改写与最佳实践
本文基于 Playwright 仓库中的官方迁移文档 puppeteer-js.md 整理,覆盖从 Puppeteer 迁移到 Playwright Library 与 Playwright Test 的核心原则、完整的 API 对照表,以及自动化脚本和测试用例两类场景的逐行改写方法。读完本文后,你可以直接对照 Cheat Sheet 完成存量 Puppeteer 代码的迁移,并理解 Playwright 的自动等待(auto-waiting)、严格模式 Locator 与 web-first 断言在源码层面是如何工作的。
一、迁移原则:四条核心准则
Playwright 与 Puppeteer 的 API 有诸多相似之处,但 Playwright 在 Web 测试与跨浏览器自动化方面提供了更大的能力空间。官方迁移文档给出的迁移原则如下:
- 大多数 Puppeteer API 可以原样使用——两个框架的 API 命名与调用风格高度接近,迁移成本主要集中在少数差异点上;
- 不推荐使用 ElementHandle——应改用 Locator 对象和 web-first 断言(Assertions);
- Playwright 是跨浏览器的——Chromium、Firefox、WebKit 三种引擎共用同一套 API;
- 你大概率不再需要显式等待——Playwright 的自动等待机制会让许多
waitForNavigation、waitForSelector调用变得多余。
这四条原则贯穿后文所有示例,也是评估存量代码是否值得迁移的判断依据。
二、API Cheat Sheet:Puppeteer 到 Playwright 的完整对照表
官方文档提供了一张完整的 API 映射表,是迁移工作中最高频的参考资料。下表完整保留原文档的全部映射关系:
| Puppeteer | Playwright Library |
|---|---|
await puppeteer.launch() |
await playwright.chromium.launch() |
puppeteer.launch({product: 'firefox'}) |
await playwright.firefox.launch() |
| WebKit is not supported by Puppeteer | await playwright.webkit.launch() |
await browser.createIncognitoBrowserContext(...) |
await browser.newContext(...) |
await page.setViewport(...) |
await page.setViewportSize(...) |
await page.waitForXPath(XPathSelector) |
await page.waitForSelector(XPathSelector) |
await page.waitForNetworkIdle(...) |
await page.waitForLoadState('networkidle') |
await page.$eval(...) |
Assertions can often be used instead to verify text, attribute, class... |
await page.$(...) |
Discouraged, use Locators instead |
await page.$x(xpath_selector) |
Discouraged, use Locators instead |
| No methods dedicated to checkbox or radio input | await page.locator(selector).check()await page.locator(selector).uncheck() |
await page.click(selector) |
await page.locator(selector).click() |
await page.focus(selector) |
await page.locator(selector).focus() |
await page.hover(selector) |
await page.locator(selector).hover() |
await page.select(selector, values) |
await page.locator(selector).selectOption(values) |
await page.tap(selector) |
await page.locator(selector).tap() |
await page.type(selector, ...) |
await page.locator(selector).fill(...) |
await page.waitForFileChooser(...)await elementHandle.uploadFile(...) |
await page.locator(selector).setInputFiles(...) |
await page.cookies([...urls]) |
await browserContext.cookies([urls]) |
await page.deleteCookie(...cookies) |
await browserContext.clearCookies() |
await page.setCookie(...cookies) |
await browserContext.addCookies(cookies) |
page.on(...) |
page.on(...)In order to intercept and mutate requests, see Page.route |
对照表之外,官方还补充了三点关键说明:
page.waitForNavigation和page.waitForSelector在 Playwright 中依然存在,但由于自动等待机制,很多场景下它们已不再必要;- ElementHandle 的使用被不推荐,应使用 Locator 对象和 web-first 断言替代;
- Locator 是 Playwright 自动等待与可重试能力(retry-ability)的核心构件,并且是"严格"(strict)的:所有隐含目标 DOM 元素的 Locator 操作,如果选择器匹配到多于一个元素,都会抛出异常。这一严格性并非文档空谈——从源码看,严格性校验发生在注入脚本层面,injectedScript.ts 中会生成形如
strict mode violation: <selector> resolved to N elements的错误并列出全部冲突元素,帮助你精确定位问题选择器。
此外可以从源码确认表中几个高频 API 的真实实现位置:
page.setViewportSize与page.waitForLoadState都在 page.ts 中定义,前者通过 channel 协议将视口尺寸下发到浏览器端;locator.check()、locator.uncheck()、locator.setInputFiles()分别实现在 locator.ts 的对应方法中,即 Puppeteer 缺失的复选框/单选框专用方法与文件上传能力的落地位置;- 关于
waitForLoadState('networkidle'),frame.ts 中的参数校验逻辑显示,合法的生命周期事件只有load、domcontentloaded、networkidle、commit四种,并且源码显式将 Puppeteer 风格的'networkidle0'归一化为'networkidle'——这意味着部分带后缀的旧写法也能被兼容处理。
三、自动化示例:逐行迁移
Puppeteer 原始代码
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto('https://playwright.dev/', {
waitUntil: 'networkidle2',
});
await page.screenshot({ path: 'example.png' });
await browser.close();
})();
逐行迁移到 Playwright
const { chromium } = require('playwright'); // 1
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage(); // 2
await page.setViewportSize({ width: 1280, height: 800 }); // 3
await page.goto('https://playwright.dev/', {
waitUntil: 'networkidle', // 4
});
await page.screenshot({ path: 'example.png' });
await browser.close();
})();
迁移要点(对应代码内注释编号)
- 显式引入
chromium:Playwright Library 的每个文件都需要显式导入要使用的浏览器,也可以选择webkit或firefox; - 浏览器状态隔离:如需隔离的浏览器状态,建议使用浏览器上下文(
browser.newContext()); setViewport更名为setViewportSize,参数结构保持一致;networkidle2更名为networkidle:需要注意,得益于自动等待机制,大多数场景下使用networkidle并无实际收益,官方建议在多数情况下省略它。
四、测试示例:从 Jest + Puppeteer 到 Playwright Test
Puppeteer + Jest 原始代码
import puppeteer from 'puppeteer';
describe('Playwright homepage', () => {
let browser;
let page;
beforeAll(async () => {
browser = await puppeteer.launch();
page = await browser.newPage();
});
it('contains hero title', async () => {
await page.goto('https://playwright.dev/');
await page.waitForSelector('.hero__title');
const text = await page.$eval('.hero__title', e => e.textContent);
expect(text).toContain('Playwright enables reliable end-to-end testing'); // 5
});
afterAll(() => browser.close());
});
逐行迁移到 Playwright Test
import { test, expect } from '@playwright/test'; // 1
test.describe('Playwright homepage', () => {
test('contains hero title', async ({ page }) => { // 2, 3
await page.goto('https://playwright.dev/');
const titleLocator = page.locator('.hero__title'); // 4
await expect(titleLocator).toContainText( // 5
'Playwright enables reliable end-to-end testing'
);
});
});
迁移要点
- 显式导入
test与expect:每个 Playwright Test 文件都需要显式导入这两个函数; - 测试函数标记为
async; page通过参数注入:page是 Playwright Test 众多实用 fixture之一。Playwright Test 会为每个测试创建独立的 Page 对象;若希望多个测试复用同一个 Page,可以在Test.beforeAll中自行创建、并在Test.afterAll中关闭;- Locator 的创建是少数同步方法之一:
page.locator(selector)立即返回 Locator 对象而不发起浏览器调用,真正的解析与等待发生在后续动作上; - 用断言替代
page.$eval()来验证状态:web-first 断言如toContainText自带自动等待与重试,比手动取值再断言更可靠也更易读。从源码看,toContainText/toHaveText等匹配器在 matchers.ts 中实现,代码生成器(javascript.ts)在录制脚本时也会自动为文本类动作生成这类断言语句,说明"用断言替代$eval"正是官方工具链的默认风格。
五、测试实践建议:Locator 与 Web-first 断言
官方文档"Testing"部分给出的建议值得单独强调:
- 优先使用 Locator 与 web-first 断言,完整写法见写作测试指南;
- 在 Puppeteer 中,常用
page.evaluate()或page.$eval()检查 ElementHandle、提取文本内容、属性、class 等值。web-first 断言为此目的提供了多种匹配器,更可靠、可读性更好; - Playwright Test 是官方第一方推荐的测试运行器,配套提供 Page Object Model、并行执行、fixture、reporter 等能力。
结合前文第二节的严格模式说明可以得出一个实操结论:迁移时遇到 page.$ / page.$x 这类返回 ElementHandle 的调用,替换为 page.locator(...) 不只是改名——它还引入了"选择器必须唯一匹配"的严格约束。从 injectedScript.ts 的错误信息结构看,一旦违反严格模式,报错会直接列出所有冲突元素,这比 Puppeteer 中"静默取第一个匹配元素"的行为更能提前暴露测试脚本中的歧义选择器。
六、迁移到 Playwright Test 后获得的能力
一旦完成迁移,除了 API 本身,还会获得一整套 Playwright Test 附带的生产能力:
- 零配置的完整 TypeScript 支持;
- 在所有 Web 引擎上运行测试(Chrome、Firefox、Safari),覆盖主流操作系统(Windows、macOS、Ubuntu);
- 完整支持多来源(multi-origin)、(i)frame、标签页与上下文(见 pages 文档);
- 跨多个浏览器隔离并行运行测试;
- 内置的测试产物采集(artifact collection),如截图、视频、trace 等。
此外还有与 Playwright Test 捆绑提供的工具:
- Playwright Inspector——交互式调试器;
- Playwright Test Code generation——代码录制与生成;
- Playwright Tracing——用于事后(post-mortem)调试的跟踪能力。
七、延伸阅读
围绕本文的迁移主题,官方文档推荐的后续阅读材料(已转换为仓库根目录相对路径):
以上资源与本文的迁移原则、Cheat Sheet 配合使用,可以覆盖从 API 替换到测试体系重构的完整迁移路径。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00