使用 Puppeteer 测试网络蓝牙

François Beaufort
François Beaufort

从 Chrome 56 开始,Chrome 就支持 Web Bluetooth,让开发者可以编写可直接与用户的蓝牙设备通信的 Web 应用。Espruino Web 编辑器能够将代码上传到兼容的蓝牙设备,就是一个很好的例子。现在,您可以使用 Puppeteer 测试这些应用。

本文将详细介绍如何使用 Puppeteer 操作和测试支持蓝牙的 Web 应用。其中的关键部分是 Puppeteer 能够操作 Chrome 的蓝牙设备选择器。

如果您不熟悉如何在 Chrome 中使用 Web Bluetooth,请观看以下视频,了解 Espruino Web 编辑器中的蓝牙提示:

用户在 Espruino Web 编辑器中选择 Puck.js 蓝牙设备。

如需按照本博文中的说明操作,您需要一个支持蓝牙的 Web 应用、一个可与其通信的蓝牙设备,并且使用 Puppeteer v21.4.0 或更高版本。

启动浏览器

与大多数 Puppeteer 脚本一样,首先使用 Puppeteer.launch() 启动浏览器。如需使用蓝牙功能,您需要提供一些额外的参数:

  • 停用无头模式:这意味着 Puppeteer 将打开一个可见的 Chrome 浏览器窗口来运行测试。如果您希望在不显示界面的情况下运行该服务,请使用新的无头模式旧版无头模式不支持显示蓝牙提示。
  • 向 Chromium 传递其他参数:为 Linux 环境传递 “enable Web Bluetooth”参数
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({
  headless: false,
  args: ["--enable-features=WebBluetooth"],
});

在打开首页时,通常建议使用无痕式浏览器上下文。这有助于防止使用脚本运行的测试之间泄露权限(不过,Puppeteer 无法防止某些操作系统级共享状态)。以下代码演示了这一点:

const browserContext = await browser.createIncognitoBrowserContext();
const page = await browserContext.newPage();

然后,您可以使用 Page.goto() 导航到要测试的 Web 应用的网址。

打开蓝牙设备提示

使用 Puppeteer 打开 Web 应用的页面后,您可以连接到蓝牙设备以读取数据。下一步假定您的 Web 应用中有按钮会运行一些 JavaScript,包括调用 navigator.bluetooth.requestDevice()

使用 Page.locator().click() 按该按钮,并使用 Page.waitForDevicePrompt() 识别蓝牙设备选择器何时显示。您必须先调用 waitForDevicePrompt(),然后才能点击该按钮,否则提示将已打开,而它将无法检测到该提示。

由于这两种 Puppeteer 方法都会返回 Promise,因此可以使用 Promise.all() 以正确的顺序一起调用它们:

const [devicePrompt] = await Promise.all([
  page.waitForDevicePrompt(),
  page.locator("#start-test-button").click(),
]);

waitForDevicePrompt() 返回的 promise 会解析为 DeviceRequestPrompt 对象,您稍后将使用该对象选择要连接的蓝牙设备。

选择设备

使用 DeviceRequestPrompt.waitForDevice()DeviceRequestPrompt.select() 找到并连接到正确的蓝牙设备。

每当 Chrome 找到包含设备基本信息的蓝牙设备时,DeviceRequestPrompt.waitForDevice() 都会调用所提供的回调。回调第一次返回 true 时,waitForDevice() 会解析为匹配的 DeviceRequestPromptDevice。将该设备传递给 DeviceRequestPrompt.select(),以选择并连接到该蓝牙设备。

const bluetoothDevice = await devicePrompt.waitForDevice(
  (d) => d.name == wantedDeviceName,
);
await devicePrompt.select(bluetoothDevice);

DeviceRequestPrompt.select() 解析完毕后,Chrome 会连接到设备,网页便可访问该设备。

从设备读取

此时,您的 Web 应用将连接到所选的蓝牙设备,并能够从中读取信息。相应版本代码可能类似如下:

const serviceId = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";

const device = await navigator.bluetooth.requestDevice({
  filters: [{ services: [serviceId] }],
});
const gattServer = await device.gatt.connect();
const service = await gattServer.getPrimaryService(serviceId);
const characteristic = await service.getCharacteristic(
  "0b30afd0-193e-11eb-adc1-0242ac120002",
);
const dataView = await characteristic.readValue();

如需更深入地了解此 API 调用序列,请参阅通过 JavaScript 与蓝牙设备通信

至此,您已经知道如何使用 Puppeteer 自动使用支持蓝牙的 Web 应用,方法是替换从蓝牙设备选择器菜单中选择设备的人工步骤。虽然这可能普遍有用,但它直接适用于为此类 Web 应用编写端到端测试。

创建测试

从目前的代码到编写完整测试,还缺少一个环节,即如何将信息从 Web 应用中提取到 Puppeteer 脚本中。完成上述操作后,您就可以非常轻松地使用测试库(例如 TAPmocha)来验证是否读取并报告了正确的数据。

最简单的方法之一是将数据写入 DOM。JavaScript 有许多方法可以实现此目的,而无需额外的库。回到假设的 Web 应用,当它从蓝牙设备读取数据或在字段中输出字面量数据时,可能会更改状态指示器的颜色。例如:

const dataDisplayElement = document.querySelector('#data-display');
dataDisplayElement.innerText = dataView.getUint8();

在 Puppeteer 中,Page.$eval() 可让您将此类数据从网页的 DOM 中提取到测试脚本中。$eval() 使用与 document.querySelector() 相同的逻辑查找元素,然后使用该元素作为参数运行所提供的回调函数。将其作为变量后,使用断言库测试数据是否符合预期。

const dataText = await page.$eval('#data-display', (el) => el.innerText);
equal(17, dataText);

其他资源

如需查看使用 Puppeteer 为支持蓝牙的 Web 应用编写测试的更复杂示例,请参阅以下代码库:https://github.com/WebBluetoothCG/manual-tests/Web Bluetooth 社区群组负责维护这套测试,所有这些测试都可以在浏览器中或本地运行。“只读特性”测试与本博文中使用的示例最为相似。

致谢

感谢 Vincent Scheib 发起此项目并就这篇博文提供宝贵的反馈。