从 Puppeteer 迁到 Playwright:API 对不上的地方在哪

2026-08-18

先说清楚这篇的依据边界:文中关于 Puppeteer 的一切说法,全部来自 Playwright 仓库自己的迁移页 docs/src/puppeteer-js.md,也就是「Playwright 官方是怎么描述这件事的」。我们没有去读 Puppeteer 自己的仓库,所以凡是那张对照表里没写的,本文一律不写、不比。这篇也不做优劣排名。

先分岔:你要迁到哪一个

很多人以为迁移就是把 puppeteer 换成 playwright,装完发现事情不对——因为 Playwright 有两个入口。仓库 docs/src/library-js.md 开篇就把它们分开了:Playwright Library 提供启动浏览器、操作浏览器的统一 API;Playwright Test 是这些能力加上一个完整托管的端到端 Test Runner。同一篇里写明,端到端测试场景下大多数情况应该用 @playwright/test,而不是直接用 playwright

docs/src/puppeteer-js.md 的标题也是这么写的:这份指南讲的是从 Puppeteer 迁到 Playwright Library Playwright Test 两者。所以你要先回答一个问题:

  • 你原来的 Puppeteer 脚本是爬数据、截图、生成 PDF、跑自动化任务——那目标是 Library,装 npm i -D playwright,代码照旧跑 node my-script.js
  • 你原来的 Puppeteer 脚本是跑测试(哪怕外面套了别的测试框架)——那目标是 Test,docs/src/intro-js.md 里给的入口命令是 npm init playwright@latest

这个岔路选错,后面所有 API 对照都会对不上号,因为 page 这个东西从哪来都不一样了。

对照表里最容易踩的三类改动

docs/src/puppeteer-js.md 给了一张 Cheat Sheet。我按「为什么会对不上」把里面的条目分成三类,比按字母顺序背要好记。

第一类:只是改了名字。 这类最省心,找到替换掉就行。表里写明 await page.setViewport(...) 对应 await page.setViewportSize(...)await page.waitForNetworkIdle(...) 对应 await page.waitForLoadState('networkidle')。迁移示例的行内注释里还单独点了一条:networkidle2 变成 networkidle,同时那条注释紧跟着说,多数情况下这个等待其实没有用,因为有 auto-waiting。

第二类:换了所属对象。 这类是编译器帮不了你的,因为方法名可能还在,只是不在同一个对象上了。表里三条 cookie 相关的全属于此类:

迁移页里的 Puppeteer 写法对应的 Playwright 写法
await page.cookies([...urls])await browserContext.cookies([urls])
await page.deleteCookie(...cookies)await browserContext.clearCookies()
await page.setCookie(...cookies)await browserContext.addCookies(cookies)

Puppeteer 侧挂在 page 上的三个 cookie 方法,到 Playwright 这边全部挂到了 browserContext 上。同类还有一条:await browser.createIncognitoBrowserContext(...) 对应 await browser.newContext(...)。迁移示例的行内注释在这里写的是「for browser state isolation, consider browser contexts」——注意它说的是浏览器状态层面的隔离,文档没有把它描述成安全边界,本文也不替它引申。落到迁移动作上就是:原来「开一个隐身上下文」的写法变成「上下文本身就是承载状态的单位」,cookie 自然跟着走。如果你原来的脚本是拿 page 直接读写 cookie 来做登录态复用,这一段是要重写的,不是改名能解决的。

第三类:还能用,但被标了不推荐。 这类最坑,因为它不报错。迁移页写明 ElementHandle 的用法是 discouraged,建议改用 Locator 对象与 web-first 断言;await page.$(...)await page.$x(xpath_selector) 在表里都直接写着「Discouraged,改用 Locators」;await page.$eval(...) 那一格写的是「往往可以改用断言来校验文本、属性、class」。

回到 API 文档核对一遍,这几条在源头也是一致的:docs/src/api/class-elementhandle.md 顶部就挂着一个 Discouraged 警告块;docs/src/api/class-page.mdPage.querySelector(JS 别名就是 $)标着 discouraged,理由是改用基于 locator 的 Page.locatorPage.evalOnSelector(JS 别名 $eval)标着 discouraged,理由写得很具体——这个方法不会等待元素通过可操作性检查,因此可能导致测试不稳定

这里有一处值得单独拎出来,因为两份文档摆在一起会让人愣一下:Cheat Sheet 里写 await page.waitForXPath(XPathSelector) 对应 await page.waitForSelector(XPathSelector),但翻到 docs/src/api/class-page.mdPage.waitForSelector 本身也标着 discouraged,建议改用断言可见性或基于 locator 的 Locator.waitFor。迁移页正文里其实也交代了这层意思:page.waitForNavigationpage.waitForSelector 依然保留,但很多情况下因为 auto-waiting 而不再必要。所以这一格该理解成「先能跑通的过渡写法」,不是终点写法。

还有一条同类的错位:表里 await page.type(selector, ...) 对应 await page.locator(selector).fill(...)。但 docs/src/api/class-locator.mdLocator.type 是明确标了 deprecated 的,说明写的是多数情况用 Locator.fill,只有页面上有特殊键盘处理、必须逐键按下时才改用 Locator.pressSequentially。同一份文件里 Locator.fill 的 Details 段还写明:如果目标元素不是 <input><textarea>[contenteditable],这个方法会抛错。你把富文本编辑器上的 type 直接换成 fill,这就是会当场炸的地方。

strict 是迁移期最常见的报错来源

迁移页正文写明:Locator 是 Playwright auto-waiting 与可重试能力的核心,并且Locator 是 strict 的——所有涉及目标 DOM 元素的 locator 操作,只要选择器匹配到多于一个元素就会抛异常。docs/src/locators.md 里同样写了这条,并且写明可以用 Locator.firstLocator.lastLocator.nth 显式退出严格性检查,但紧跟着说这几个方法不推荐,理由是页面变化后 Playwright 可能点到你并不想点的元素。

这意味着一件很实际的事:一套在 Puppeteer 下跑得好好的脚本,迁过来第一轮很可能挂在 strict mode violation 上,而挂掉的那些选择器往往正是原来就有歧义、只是靠「取第一个」蒙混过去的。这不是迁移工具能替你做的事。

同时被打包送过来的还有可操作性检查。docs/src/actionability.md 写明,Locator.click 之前 Playwright 会确保:locator 恰好解析到一个元素、元素可见、元素稳定(不在动画中或动画已完成)、元素能接收事件(未被其它元素遮挡)、元素处于启用状态;同一页给出了每个动作各自要过哪几项检查的完整表格,比如 Locator.fill 检查的是可见、启用、可编辑,而 Locator.setInputFiles 这一行全是横杠。该页还写明部分动作支持 force 选项,作用是关闭非必要的可操作性检查。

多浏览器:差异写在同一张表的两行里

Cheat Sheet 里有三行是并排的,含义要连起来读: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()

迁移页里对应的行内注释也说得直白:每个 Playwright Library 文件都要显式 import chromium,其它浏览器 webkitfirefox 同样可用。docs/src/library-js.md 里写明安装后可以启动 chromiumfirefoxwebkit 这三个浏览器。至于浏览器二进制怎么来,docs/src/browsers.md 开头写明每个 Playwright 版本需要特定版本的浏览器二进制,要用 Playwright CLI 装——具体版本对应关系本文不写,那是随版本变的东西,以仓库最新内容为准。

这个差异什么时候会咬到你? 只有当你的项目真的需要覆盖 Safari 内核时。如果你的测试矩阵里只有 Chrome,那这一行对你没有价值,别把它当迁移理由。

测试运行器:有和没有,差的不是一个 API

这是两边最大的结构性差别,也是 docs/src/library-js.md 里那张 Key Differences 表在讲的事。迁移页给的对照示例很直观:Puppeteer 那一侧配的是 Jest,在 beforeAllpuppeteer.launch()browserbrowser.newPage()page,在 afterAllbrowser.close();迁到 Playwright Test 之后,测试函数签名变成 async ({ page })page 是运行器给的。

迁移页的说明逐条写明了这背后的几件事:每个 Playwright Test 文件要显式 import testexpectpage 是运行器传进来的众多 fixture 之一;Playwright Test 为每个测试创建一个隔离的 Page 对象,如果确实想在多个测试间复用同一个 Page,可以自己在 Test.beforeAll 里创建、在 Test.afterAll 里关闭;Page.locator 是少数几个同步方法之一;以及用断言而不是 page.$eval() 来校验状态。

docs/src/library-js.md 的表格把没有运行器时你要自己扛的事列全了:显式挑浏览器、BrowserType.launchBrowser.newContext 并显式传上下文选项、BrowserContext.newPage,收尾还要显式 BrowserContext.closeBrowser.close。同一张表还写明,Library 侧没有内建的 web-first 断言,而 Test 侧有 PageAssertions.toHaveTitle 这类会自动等待并重试的断言。超时语义也不一样:表里写 Library 侧多数操作默认 30 秒超时,Test 侧多数操作不超时、但每个测试有一个会让它失败的超时(默认同样是 30 秒)——这两个是仓库当前文档里的默认值,随版本可能变动,别把它当成对运行结果的保证。

运行器还带来一批不属于「浏览器 API」范畴的能力,这些在 Library 侧要自己搭:

  • 多配置矩阵docs/src/test-projects-js.md 里的配置片段用 projects 数组把 chromiumfirefoxwebkit 三个 project 并列,每个 project 的 use 展开一个 devices 预设。docs/src/library-js.md 明说,Library 版本里想换设备或浏览器得改脚本并把信息一路传下去,Test 里则是在一个地方声明矩阵。
  • 并行docs/src/test-parallel-js.md 写明测试跑在 worker 进程里,这些是相互独立的 OS 进程,由运行器编排,worker 之间无法通信,每个 worker 启动自己的浏览器;粒度上,test.describe.configure({ mode: 'parallel' }) 控制单个文件内的并行,fullyParallel 控制整个 project。
  • retriesdocs/src/test-retries-js.md 写明失败测试可以自动重跑,且默认不重试(同样是当前文档里的默认值)。
  • shardingdocs/src/test-sharding-js.md 写明通过命令行传 --shard=x/y 把用例集切成多份,分别跑在不同机器或不同 CI job 上。

迁移页末尾还列了随 Playwright Test 一起来的几件工具:Playwright Inspector、代码生成、以及用于事后排查的 Tracing。这些在 Library 侧的可用情况,迁移页没有逐条交代,我们不替它下结论。

Windows 侧要多留一步

docs/src/library-js.md 里安装浏览器那一节,对每个环境变量都分了 bash / batch / powershell 三种写法。也就是说,走公司代理下载浏览器时,Linux/macOS 上是 HTTPS_PROXY=https://192.0.2.1 npx playwright install 这样前缀式写,而 Windows 上要分两行——cmd 里是 set HTTPS_PROXY=https://192.0.2.1 然后 npx playwright install,PowerShell 里是 $Env:HTTPS_PROXY=https://192.0.2.1 然后 npx playwright install。文档里那个 IP 只是示例值,换成你自己的代理地址。同一节里还有两个同样分了三种写法的变量:PLAYWRIGHT_DOWNLOAD_HOST 用于改从内部制品仓库下载(文档写明默认从微软的 CDN 下),PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD 用于在浏览器二进制单独管理的场景下跳过下载。

在 Windows 上按 Linux 的前缀式写法敲,变量根本不会生效,下载还是走原来的路——这个坑跟 Playwright 本身无关,但迁移当天最容易卡在这里。

我们不比的几项

  • 两边各自的性能表现。 我们没有跑过任何一侧,不做任何速度、稳定性的描述。
  • Puppeteer 自身现在有没有某个能力。 除了 Cheat Sheet 左列写明的内容,我们在本仓库里没有找到对 Puppeteer 当前功能集的完整描述,不比。
  • Library 与 Test 哪个更好。 这不是好坏问题,是你手上那套脚本是不是测试的问题。

迁移的决策路径其实很短:先按脚本用途选 Library 还是 Test;再拿 Cheat Sheet 过一遍,把改名的直接换、把 cookie 与上下文那几条重写、把标了 discouraged 与 deprecated 的排进技术债;然后准备好被 strict 拦一轮。至于多浏览器和运行器那一堆能力,是你选了 Test 之后附带拿到的,不是迁移本身的成本。


本文依据 github.com/microsoft/playwright 仓库于 2026-08-18 的公开内容整理, 事实来自仓库内的文档与源码。我们没有对文中涉及的功能做过实测, 因此不涉及运行速度、稳定性与实际表现的任何描述。 该项目迭代频繁,文中涉及的 API 签名、配置项与默认值随版本变动,请以仓库最新内容为准。 本文不涉及浏览器版本矩阵与版本清单,相关信息请以官方发布说明为准。

本文的对照分两层,依据均为上述仓库内容:一层是迁移前后的写法对应, 文中涉及 Puppeteer 的全部描述均转引自该仓库的迁移页 docs/src/puppeteer-js.md我们未引用 Puppeteer 自身仓库的内容,也不据此推断 Puppeteer 当前的实现与功能集; 另一层是同一项目内 Playwright Library 与 Playwright Test 两种用法的对照。 两层都不做优劣排名,选型结论只在仓库文档写明的能力边界内成立。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。