从Puppeteer
转换到Playwright
很容易,不过是否值的这么做呢?在编码层面有哪些具体的变化?有哪些新的特性和功能?这是我们本文关心的主要内容
Puppeteer 与 Playwright 现状
这两个工具有不少相似之处,不过二者近两年来的发展速度不尽相同,Playwright
的发展势头可以说是盖过了Puppeteer
。这种势头使得不少人转向了Playwright
,本文将会介绍具体的迁移步骤以及这种改变带来的新特性。本文虽然长了一点,但内容很容易掌握。
为什么要迁移
之前我们做过Cypress
、Selenium
、Playwright
、Puppeteer
、的对比测试,详情可见文章链接,简单来说有以下理由:
Playwright
更新更加活跃,在新特性方面较Puppeteer
更为明显Playwright
在现实的端到端(E2E)测试中有性能优势(详见文章链接),执行测试用例速度更快Playwright
稳定性更佳Playwright
在GitHub, Twitter, Slack等社区上更火爆,反观Puppeteer
社区则略显冷清
变化清单
下面是一张对比表,先阅读几分钟,后面我们会深入介绍
Puppeteer | Playwright |
---|---|
puppeteer.launch(...) | playwright.chromium.launch(...) |
browser.createIncognitoBrowserContext(...) | browser.newContext(...) |
page.setViewport(...) | page.setViewportSize(...) |
page.waitForSelector(selector) page.click(selector); | page.click(selector) |
page.waitForXPath(XPathSelector) | page.waitForSelector(XPathSelector) |
page.$x(xpath_selector) | page.$(xpath_selector) |
page.waitForNetworkIdle(...) | page.waitForLoadState({ state: 'networkidle' }}) |
page.waitForFileChooser(...) | Removed, handled differently. |
page.waitFor(timeout) | page.waitForTimeout(timeout) |
page.type(selector, text) | page.fill(selector, text) |
page.cookies([...urls]) | browserContext.cookies([urls]) |
page.deleteCookie(...cookies) | browserContext.clearCookies() |
page.setCookie(...cookies) | browserContext.addCookies(cookies) |
page.on('request', ...) | Handled through page.route. |
elementHandle.uploadFile(...) | elementHandle.setInputFiles(...) |
Tricky file download. | Better support for downloads. |
变化详情
包引入
在Puppeteer
中,脚本的前几行很可能是这样的:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// ...
在Playwright
则是这样的:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
// ...
Playwright
提供开箱即用的跨浏览器支持,你可以选择具体运行的浏览器,比如const { webkit } = require('playwright');
,在Puppeteer
中需要通过launch
接口的配置来实现:
const browser = await puppeteer.launch({ product: 'firefox' })
浏览器context
浏览器context
在Puppeteer
中是存在的:
const browser = await puppeteer.launch();
const context = await browser.createIncognitoBrowserContext();
const page = await context.newPage();
在Playwright
中context
更加重要一些,使用起来稍微有一点不同:
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
和Puppeteer
一样,对于基本的使用以及单页流,可以使用默认的context
:
const browser = await chromium.launch();
const page = await browser.newPage();
wait
Playwright
的 自动等待(auto-waiting
)机制意味着您可能不需要经常显式地等待。 但是,等待是UI自动化中最棘手的部分之一,您仍然需要知道让脚本显式地等待一个或多个条件满足的不同方法。在这方面,Playwright
带来了一些你需要注意的变化:
- page.waitForNavigation 和 page.waitForSelector 保留,不过在自动等待机制的支持下,很多用例中不是必须使用的
- 新增了 page.waitForEvent
Puppeteer
的 page.waitForXPath 被整合进了 page.waitForSelector ,支持自动识别XPath
表达式- 移除了page.waitForFileChooser,新用法详见 Upload files、E2E Account Settings
- page.waitForNetworkIdle 整合进了 page.waitForLoadState
- 添加了 page.waitForUrl 允许等待一个URL被页面的主frame加载完成
- page.waitFor(timeout) 变成了 page.waitForTimeout(timeout)
注意page.waitForTimeout不要在正式的产品版中使用,硬性的等待只是用于测试
设置 viewport
Puppeteer
的 page.setViewport
在 Playwright
中变成了 page.setViewportSize
Typing
Puppeteer
的 page.type 依然可用,还是可以控制细粒度的键盘事件,除此之外 Playwright
增加了page.fill用于填充、清空表单
Cookies
使用Puppeteer
时cookie在页面级别处理;使用Playwright
时,你可以在BrowserContext级别上操纵它们
之前:
page.cookies([...urls])
page.deleteCookie(...cookies)
page.setCookie(...cookies)
现在:
browserContext.cookies([urls])
browserContext.clearCookies()
browserContext.addCookies(cookies)
请注意这些方法之间的细微差别,以及如何将cookie传递给它们
XPath 选择器
以//
或..
开头的XPath
选择器会被Playwright
自动识别,而在Puppeteer
则是有单独的接口
设备模拟
Playwright
的设备模拟是在浏览器context
层级上提供的:
const pixel2 = devices['Pixel 2'];
const context = await browser.newContext({
...pixel2,
});
除此之外,还可以控制权限、地理位置和其他设备参数。
文件下载
尝试在无头模式下的Puppeteer
下载文件是很棘手的,Playwright
会更精简:
const [download] = await Promise.all([
page.waitForEvent('download'),
page.click('#orders > ul > li:nth-child(1) > a')
])
const path = await download.path();
这是一个完整的示例
文件上传
Puppeteer
的 elementHandle.uploadFile becomes elementHandle.setInputFiles
详见 文件上传示例.
请求拦截
在Puppeteer
中的请求拦截是通过 page.on('request', ...)实现的:
await page.setRequestInterception(true)
page.on('request', (request) => {
if (request.resourceType() === 'image') request.abort()
else request.continue()
})
在Playwright
可以通过page.route拦截指定模式的url:
await page.route('**/*', (route) => {
return route.request().resourceType() === 'image'
? route.abort()
: route.continue()
})
详见 完整示例
值得注意的新功能
当从Puppeteer
转移到Playwright
时,请确保了解Playwright
的许多全新功能,因为它们可能为测试或监视设置提供新的解决方案和可能性
新的选择器引擎
Playwright
提供了更灵活的UI
元素引用方式,除了css
和XPath
以外还增加了以下方式:
Playwright
特有的选择器,例如:nth-match(:text("Buy"), 3)
- 文本选择器,例如
text=Add to Car
- 链选择器,例如
css=preview >> text=In stock
除此之外,你好可以创建自己的自定义选择器引擎
关于选择器的更多信息以及用法,可以参见 Working with selectors
保存以及复用状态
Playwright
可以方便地保存给定会话的身份验证状态(cookie
和localStorage
),并在后续脚本运行时使用它以便节约身份验证的时间
定位API
Playwright
定位器API 封装了检索给定元素所需的逻辑,允许使用者轻松地在不同时间点获得脚本中的最新DOM元素
Inspector
Playwright
的 Inspector 是一个GUI工具,在调试脚本时非常方便。它允许使用者在脚本中逐步执行指令,从而更容易地确定失败的原因。
Test
Playwright
有 Test机制 , 在E2E测试中十分有用,例如开箱即用的并行化、钩子等
追踪视图
Playwright
的Trace Viewer允许您探索使用剧作家测试或BrowserContext跟踪API记录的痕迹。通过跟踪可以最细致地了解脚本的执行情况
测试生成器
您可以使用Playwright
的Test Generator来记录浏览器中的交互。输出将是一个可以检查和执行的完整脚本