Puppeteer正式支持Firefox:跨浏览器自动化测试新时代

本文宣布Puppeteer 23版本正式支持Firefox浏览器,详细介绍如何通过WebDriver BiDi协议实现跨浏览器自动化测试,包含控制台日志捕获、设备模拟、网络请求拦截等核心功能的技术实现示例。

宣布Puppeteer正式支持Firefox

我们很高兴地宣布,从第23版开始,Puppeteer浏览器自动化库现已对Firefox提供一流支持。这意味着现在可以轻松编写自动化脚本并使用Puppeteer执行端到端测试,同时在Chrome和Firefox上运行。

如何使用Puppeteer与Firefox

要开始使用,只需在启动Puppeteer时将产品设置为"firefox":

1
2
3
4
5
6
7
8
9
import puppeteer from "puppeteer";

const browser = await puppeteer.launch({
  browser: "firefox"
});

const page = await browser.newPage();
// ...
await browser.close();

与Chrome一样,Puppeteer能够下载并启动最新稳定版本的Firefox,因此在任一浏览器上运行都应提供Puppeteer用户所期望的相同开发体验。

虽然Puppeteer提供的功能不会让人意外,但为多个浏览器提供支持是一项重大任务。Firefox支持并非基于Firefox特定的自动化协议,而是基于WebDriver BiDi——一个正在W3C标准化的跨浏览器协议,目前已在Gecko和Chromium中实现。这种跨浏览器协议的使用应该使得将来支持许多不同浏览器变得更加容易。

关键功能

对于长期使用Puppeteer的用户来说,可用功能是熟悉的。然而,对于其他自动化和测试生态系统中的用户——特别是那些直到最近还完全依赖基于HTTP的WebDriver的用户——本节概述了WebDriver BiDi使得以跨浏览器方式实现的一些新功能。

捕获日志消息

测试Web应用程序时的一个常见需求是确保没有意外错误报告到控制台。这也是基于事件的协议发挥作用的情况,因为它避免了需要轮询浏览器以获取新日志消息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import puppeteer from "puppeteer";

const browser = await puppeteer.launch({
  browser: "firefox"
});

const page = await browser.newPage();
page.on('console', msg => {
  console.log(`[console] ${msg.type()}: ${msg.text()}`);
});

await page.evaluate(() => console.debug('Some Info'));
await browser.close();

输出:

1
[console] debug: Some Info

设备模拟

在测试响应式布局时,通常需要确保布局在多个屏幕尺寸和设备像素比下都能良好工作。这可以通过使用真实移动浏览器(在设备上或模拟器上)来完成。但为简单起见,在桌面上模拟移动设备的视口进行测试可能很有用。下面的示例显示加载页面时,Firefox配置为模拟Pixel 5手机的视口大小和设备像素比。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import puppeteer from "puppeteer";

const device = puppeteer.KnownDevices["Pixel 5"];

const browser = await puppeteer.launch({
  browser: "firefox"
});

const page = await browser.newPage();
await page.emulate(device);

const viewport = page.viewport();

console.log(
  `[emulate] Pixel 5: ${viewport.width}x${viewport.height}` +
  ` (dpr=${viewport.deviceScaleFactor}, mobile=${viewport.isMobile})`
);

await page.goto("https://www.mozilla.org");
await browser.close();

输出:

1
[emulate] Pixel 5: 393x851 (dpr=3, mobile=true)

网络拦截

测试的一个常见需求是能够跟踪和拦截网络请求。拦截特别适用于在测试期间避免向第三方服务发出请求,并提供模拟响应数据。它还可用于处理HTTP身份验证对话框,并覆盖请求和响应的部分内容,例如添加或删除标头。在下面的示例中,我们使用网络请求拦截来阻止页面上对所有Web字体的请求,这可能有助于确保这些字体加载失败不会破坏网站布局。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import puppeteer from "puppeteer";

const browser = await puppeteer.launch({
  browser: 'firefox'
});

const page = await browser.newPage();
await page.setRequestInterception(true);

page.on("request", request => {
  if (request.url().includes(".woff2")) {
    // 阻止对自定义用户字体的请求
    console.log(`[intercept] Request aborted: ${request.url()}`);
    request.abort();
  } else {
    request.continue();
  }
});

const response = await page.goto("https://support.mozilla.org");
console.log(
  `[navigate] status=${response.status()} url=${response.url()}`
);
await browser.close();

输出:

1
2
[intercept] Request aborted: https://assets-prod.sumo.prod.webservices.mozgcp.net/static/Inter-Bold.3717db0be15085ac.woff2
[navigate] status=200 url=https://support.mozilla.org/en-US/

预加载脚本

自动化工具通常希望提供可以用JavaScript实现的自定义功能。虽然WebDriver一直允许注入脚本,但无法确保注入的脚本在页面开始加载之前始终运行,这使得无法避免页面脚本与注入脚本之间的竞争。

WebDriver BiDi提供了可以在页面加载之前运行的"预加载"脚本。它还提供了一种从脚本发出自定义事件的方法。这可以用于,例如,避免轮询预期元素,而是使用在元素可用时立即触发的变异观察器。在下面的示例中,我们等待<title>元素出现在页面上,并记录其内容。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import puppeteer from "puppeteer";

const browser = await puppeteer.launch({
  browser: 'firefox',
});

const page = await browser.newPage();

const gotMessage = new Promise(resolve =>
  page.exposeFunction("sendMessage", async message => {
    console.log(`[script] Message from pre-load script: ${message}`);
    resolve();
  })
);

await page.evaluateOnNewDocument(() => {
  const observer = new MutationObserver(mutationList => {
    for (const mutation of mutationList) {
      if (mutation.type === "childList") {
        for (const node of mutation.addedNodes) {
          if (node.tagName === "TITLE") {
            sendMessage(node.textContent);
          }
        }
      }
    };
  });

  observer.observe(document.documentElement, {
    subtree: true,
    childList: true,
  });
});

await page.goto("https://support.mozilla.org");
await gotMessage;
await browser.close();

输出:

1
[script] Message from pre-load script: Mozilla Support

技术背景

直到最近,希望自动化浏览器的人主要有两个选择:

  1. 使用W3C WebDriver API,它基于Selenium项目的早期工作
  2. 使用浏览器特定的API与每个支持的浏览器通信,例如用于基于Chromium的浏览器的Chrome DevTools Protocol(CDP),或用于基于Gecko的浏览器的Firefox远程调试协议(RDP)

不幸的是,这两个选项都有显著的权衡。“经典"WebDriver API基于HTTP,其模型涉及自动化向浏览器发送命令并等待响应。这对于加载页面然后验证(例如)某个元素是否显示的自动化场景很有效,但无法从浏览器获取事件(例如控制台日志)或同时运行多个命令,使得API不适合更高级的用例。

相比之下,浏览器特定API通常围绕支持浏览器内开发工具的复杂用例设计。这使它们的功能集远远超出了使用WebDriver可能实现的功能,因为它们需要支持诸如记录控制台日志或网络请求等用例。

因此,浏览器自动化客户端被迫在以下两者之间做出选择:使用单一协议支持许多浏览器但提供有限功能集,或提供更丰富的功能集但必须实现多个协议来为每个支持的浏览器单独提供功能。这显然增加了创建出色的跨浏览器自动化的成本和复杂性,这不是一个好的情况,特别是当开发人员通常将跨浏览器测试列为Web开发的主要痛点之一。

长期开发人员可能会注意到这里与语言服务器协议(LSP)开发之前的编辑器情况的类比。当时每个文本编辑器或IDE必须为每种不同的编程语言实现定制支持。这使得很难让开发人员使用的所有工具都支持新语言。LSP的出现通过提供可以被任何编辑器和编程语言组合支持的通用协议改变了这一点。对于像TypeScript这样的新编程语言要在所有编辑器中得到支持,不再需要让它们逐个添加支持;只需要提供LSP服务器,它就会自动在任何支持LSP的编辑器中得到支持。这种通用协议的出现也使得以前难以想象的事情成为可能。例如,像Tailwind这样的特定库获得自己的LSP实现以启用定制编辑器功能。

因此,为了改进跨浏览器自动化,我们采取了类似的方法:开发WebDriver BiDi,它将以前限于浏览器特定协议的自动化功能集带到了一个标准化协议中,该协议可以由任何浏览器实现,并由任何编程语言中的任何自动化工具使用。

在Mozilla,我们将这种标准化协议以消除进入壁垒、允许多样化的可互操作实现生态系统蓬勃发展,并让用户选择最适合其需求的策略视为我们宣言和Web愿景的关键部分。

移除Firefox中的实验性CDP支持

作为改进跨浏览器测试的早期工作的一部分,我们发布了CDP的部分实现,仅限于支持测试用例所需的几个命令和事件。这以前是Puppeteer中Firefox实验性支持的基础。然而,一旦明确这不是跨浏览器自动化的前进方向,这方面的努力就停止了。因此,它无人维护,无法与现代Firefox功能(如站点隔离)配合使用。因此,支持计划在2024年底移除。

如果您当前将CDP与Firefox一起使用,并且不知道如何过渡到WebDriver BiDi,请通过本文底部列出的渠道之一与我们联系,我们将讨论您的需求。

下一步是什么?

尽管Firefox现在在Puppeteer中得到官方支持,并且有足够的功能覆盖许多自动化和测试场景,但仍有一些API不受支持。这些大致分为三类(请查阅Puppeteer文档以获取完整列表):

  1. 高度CDP特定的API,特别是CDPSession模块中的API。这些不太可能直接支持,但当前需要这些API的特定用例可能是标准化的候选
  2. 需要进一步标准工作的API。例如,page.accessibility.snapshot返回Chromium可访问性树的转储。但是,由于目前没有该树应该是什么样子的标准化描述,因此很难以跨浏览器的方式工作。还有一些情况要简单得多,因为它们只需要在WebDriver BiDi规范本身上工作;例如page.setGeolocation
  3. 已有标准但尚未实现的API,例如在worker中执行脚本的能力,这是WebWorker.evaluate等命令所需的

我们期望将来填补这些空白。为了帮助确定优先级,我们对您的反馈感兴趣:请尝试在Firefox中运行您的Puppeteer测试!如果由于错误或缺少功能而无法在Firefox中运行它们,请使用以下方法之一告知我们,以便我们在规划未来的标准和实施工作时考虑:

  • 对于Firefox实现错误,请在Bugzilla上提交错误
  • 如果您确信问题在Puppeteer中,请在其问题跟踪器中提交错误
  • 对于WebDriver BiDi规范中缺少的功能,请在GitHub上提交问题
  • 如果您想与我们讨论用例或需求,请使用Mozilla Matrix实例上的#webdriver频道或发送电子邮件至dev-webdriver@mozilla.org
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计