Protractor 项目迁移到 Playwright:官方迁移页写了哪些对应关系
手上有一套 Protractor 写的 Angular 端到端用例,要往 Playwright 搬,第一个要回答的问题不是「哪个更好」,而是「这活儿是查表替换,还是推倒重写」。这个问题在 Playwright 仓库里有一份直接的答案:docs/src/protractor-js.md。这页篇幅不长,但结构很实——迁移原则四条、一张对应表、一段逐行改写的示例、外加 waitForAngular 的 polyfill 写法。
需要先说清楚本文的取材边界:本文里所有关于 Protractor 的说法,全部来自 Playwright 仓库的这一页,我们不引用 Protractor 自身的仓库,也不凭记忆补充它的行为。凡是这页没写的维度,下面会直接说「不比」。
先看它把迁移拆成了几件事
迁移页开头的 Migration Principles 一共四条,逐字是:不再需要 “webdriver-manager” / Selenium;Protractor 的 ElementFinder 对应 Playwright Test 的 Locator;Protractor 的 waitForAngular 对应 Playwright Test 的 auto-waiting;别忘了在 Playwright Test 里加 await。
把这四条翻译成工作量,就是三块:环境这块是做减法,定位器这块是查表,等待与 await 这块是改代码结构。下面按这个顺序走。
第一块:环境侧的减法,Windows 上感受最直接
「不再需要 webdriver-manager / Selenium」意味着你不用再维护一个和浏览器版本对齐的 driver。Playwright 自己下载浏览器,docs/src/browsers.md 写明它把 Chromium、WebKit 和 Firefox 下到各操作系统的缓存目录里:
- Windows:
%USERPROFILE%\AppData\Local\ms-playwright - macOS:
~/Library/Caches/ms-playwright - Linux:
~/.cache/ms-playwright
Windows 侧的实际差别在于,你不需要再把某个 driver 可执行文件放进 PATH,也不需要为它单独准备运行时;浏览器由 Playwright 按自身版本绑定管理,升级 Playwright 时浏览器跟着走。另外 docs/src/browsers.md 有单独的 Install system dependencies 一节,给的是 npx playwright install-deps 这一档,用来装系统依赖,文档逐字说它「对 CI 环境有用」(这一节没有写限定哪个操作系统,仓库里我们也没有找到把它划给某个平台的说明);也可以写成 npx playwright install --with-deps chromium 一步做完。上面的命令都是仓库文档里的原文,该项目持续更新,以仓库最新内容与 --help 的实际输出为准。
安装入口,docs/src/intro-js.md 给的是 npm init playwright@latest(yarn 与 pnpm 各有对应写法)。
第二块:对应表怎么读
迁移页的 Cheat Sheet 是这篇最有价值的部分。它给的是左边 Protractor 写法、右边 Playwright Test 写法的逐条映射,其中几条值得单独拎出来:
| Protractor | Playwright Test |
|---|---|
element(by.model('...')) | page.locator('[ng-model="..."]') |
element(by.repeater('...')) | page.locator('[ng-repeat="..."]') |
element(by.cssContainingText('..1..', '..2..')) | page.locator('..1.. >> text=..2..') |
element.all | page.locator |
browser.get(url) | await page.goto(url) |
browser.getCurrentUrl() | page.url() |
读这张表要注意三件事。
一,Angular 专属的定位方式被降级成了普通属性选择器。 by.model 和 by.repeater 在右边变成了 [ng-model="..."] 和 [ng-repeat="..."],也就是把 Angular 指令当作 DOM 上的一个属性来选。这意味着迁移之后,这些定位器不再理解 Angular 的作用域,只是在匹配属性字符串。
二,cssContainingText 映射成了 >> 链式选择器。 docs/src/other-locators.md 写明 >> 是选择器的链接符号,形如 selector1 >> selector2 >> selectors3,后一个选择器在前一个的结果里继续查;同一页也写明 text= 是子串匹配、默认大小写不敏感并会去掉首尾空白,而加了引号的 text="..." 是去掉空白后的精确匹配且大小写敏感。
这里有个细节值得盯一眼:表格里 cssContainingText 那一行右边给的是不带引号的 '..1.. >> text=..2..',也就是子串匹配;而另一行 element(by.buttonText('...')) 右边给的是带引号的 >> text="...",是精确匹配。两行的引号不是随手写的,替换时按表照抄,别自己给它加引号或者去引号——加错了匹配语义就变了。
同一页还有两处限定必须照实说出来:docs/src/other-locators.md 把 text= 这一族称作 Legacy text locator,并且在 Chaining selectors 一节挂了警告,逐字说推荐改用 docs/src/locators.md 里的 locator 链式写法。也就是说这张对应表右边的写法本身在文档里就带着「不是首选」的标记,它是给你迁移用的桥,不是终点。
三,element 与 element.all 在右边合并成了同一个 page.locator。 这是照表替换最容易踩的一处,下一节单说。
第三块:合并成一个 Locator 之后,strict 会咬你
docs/src/locators.md 有专门的 Strictness 一节,写明 locator 是严格的:所有隐含单个目标 DOM 元素的操作,在匹配到多于一个元素时会抛异常,文档给的反例就是页面上有多个按钮时 await page.getByRole('button').click(); 会抛错。同一节也写明,多元素操作不受影响,await page.getByRole('button').count(); 在匹配到多个元素时工作正常。
把这条和对应表放在一起看,结论就出来了:原来写 element(by.css('...')) 的地方,如果它实际上匹配到了多个元素,机械替换成 page.locator('...') 之后,单元素操作会直接抛 strict mode violation,而不是像以前那样拿第一个。这是「照表全量替换、指望用例直接全绿」这条路最容易断掉的地方,而且它是显式报错,不是静默走错元素——从排查角度说这是好事。
docs/src/locators.md 同时给了逃生口:可以用 Locator.first、Locator.last、Locator.nth 显式告诉 Playwright 取哪一个,但文档逐字标注这些方法 not recommended,理由是页面变化后可能点到你没打算点的元素。docs/src/api/class-locator.md 里 nth() 的说明是零基的,nth(0) 取第一个。
第四块:逐行示例里藏着两条不同的迁移路线
迁移页给了一段 Protractor 的 todo 用例和它逐行改写后的 Playwright Test 版本。改写版里有一处值得注意:原用例里 todoList.get(2).element(by.css('input')).click(); 这一步,改写后不是继续用 CSS,而是写成了 await todoList.nth(2).getByRole('textbox').click();。
把这处和 docs/src/locators.md 放在一起看,就能看到两条路线的分岔。docs/src/locators.md 写明为了让测试更健壮,推荐优先使用面向用户的属性与显式契约,并把 Page.getByRole 举为首选;讲到 CSS/XPath 时它的措辞是「如果你实在必须用」,并在另一处逐字说 CSS 与 XPath not recommended,因为 DOM 常常会变、由此得到的测试不够健壮。所以对应表更像过渡用的:它给的是一条能把老用例平移过去的最短路径,而迁移页自己的示例已经在往 getByRole 上靠。
至于要不要一步到位改成 role 定位,取决于你的处境:如果这套用例还要长期维护、页面还在改,那查表只是第一步;如果只是想让老用例先别烂在那儿,停在查表这一层也是完整的。这两种选择在仓库文档里都能找到依据,没有优劣之分。
改写示例底部的 Migration highlights 列了五条结构性差别,和代码里的行内编号一一对应:每个 Playwright Test 文件要显式 import test 和 expect;测试函数标 async;page 作为参数传进来,它是 Playwright Test 众多 fixture 之一(fixture 清单见 docs/src/test-api/class-fixtures.md);几乎所有 Playwright 调用都要加 await;最后一条是 Page.locator 属于少数同步方法之一。后两条要放在一起读才不会踩坑——page.locator(...) 本身不加 await,但它后面挂的动作要加。
断言这块,示例里用的是 await expect(todoList).toHaveCount(3); 和带 useInnerText: true 的 toHaveText。docs/src/api/class-locatorassertions.md 写明 useInnerText 的语义是取文本时用 element.innerText 而不是 element.textContent;docs/src/api/class-locator.md 里也两处写明,要断言页面文本时推荐用 toHaveText 配 useInnerText 来避免 flakiness。示例里的 3 和 2 只是这份仓库示例用例里的具体值,不是什么通用配置。
第五块:waitForAngular 换成什么
docs/src/actionability.md 写明 Playwright 在执行动作前会做一组可操作性检查,并自动等到检查全部通过才动手,超时则抛 TimeoutError。以 Locator.click 为例,它要确保:locator 恰好解析到一个元素、元素可见、元素稳定(不在动画中或动画已结束)、元素能接收事件(没被其它元素遮挡)、元素是启用状态。同一页还有一张按动作列出检查项的表,以及一份自动重试断言的清单——toHaveCount、toHaveText 这些都在里面,它们会一直等到条件满足。
迁移页的说法是:有了这套内置等待,waitForAngular 在一般情况下不需要了;但在某些边缘情况下它仍然有用,所以给了 polyfill。两种写法:
第一种要求你的 package.json 里还留着 protractor 依赖,polyfill 内部 require('protractor/built/clientsidescripts.js') 拿到脚本,再用 page.evaluate 包一层 Promise 执行。
第二种不需要留着 protractor,迁移页原文标注它 only works for Angular 2+:
async function waitForAngular(page) {
await page.evaluate(async () => {
// @ts-expect-error
if (window.getAllAngularTestabilities) {
// @ts-expect-error
await Promise.all(window.getAllAngularTestabilities().map(whenStable));
// @ts-expect-error
async function whenStable(testability) {
return new Promise(res => testability.whenStable(res));
}
}
});
}
用法是迁移页给的三行:
const page = await context.newPage();
await page.goto('https://example.org');
await waitForAngular(page);
以上代码为仓库文档中的原文,未经实测,以仓库最新代码为准。
这里有个容易误解的点要说明:我们在仓库的 packages/ 下没有找到 waitForAngular 的实现,它只出现在这份迁移页的示例代码里。也就是说它不是 Playwright 的内置 API,而是需要你自己贴进项目的一段辅助函数。选哪一种,取决于你能不能接受在 package.json 里继续留着 protractor 这个依赖,以及你的应用是不是 Angular 2+。
哪些维度没有依据,不比
迁移页没有给出配置文件、并行模型、reporter、suites 组织方式这些方面的对应关系,我们在这一页里也没有找到对 Protractor 这些机制的描述。这些维度本文不比。 迁移页末尾的 Playwright Test Super Powers 一节列了迁移后能拿到的东西(零配置 TypeScript、跨浏览器引擎、多 origin 与 iframe / 多标签页、并行、内置 artifact 收集,以及 Inspector、codegen、Trace Viewer 这些工具),但那是单方面的能力清单,不构成对照,本文不拿它当比较依据。
本文依据 github.com/microsoft/playwright 仓库于 2026-08-18 的公开内容整理,
事实来自仓库内的文档与源码。我们没有对文中涉及的功能做过实测,
因此不涉及运行速度、稳定性与实际表现的任何描述。
该项目迭代频繁,文中涉及的 API 签名、配置项与默认值随版本变动,请以仓库最新内容为准。
本文不涉及浏览器版本矩阵与版本清单,相关信息请以官方发布说明为准。
本文涉及的另一方内容,全部依据 Playwright 仓库内的 docs/src/protractor-js.md 整理,
未引用 Protractor 自身仓库,也未凭其它渠道补充其行为。
本文只对照各方公开写明的机制,不推断未公开的实现,也不对项目做优劣排名。