使用 Puppeteer 测试网络蓝牙

François Beaufort
François Beaufort

自 Chrome 56 起,网络蓝牙便已得到支持,让开发者能够编写可直接与用户的蓝牙设备通信的 Web 应用。例如,Espruino 网页编辑器能够将代码上传到兼容的蓝牙设备。现在可以使用 Puppeteer 测试这些应用。

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

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

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

要关注这篇博文,您需要一个启用了蓝牙的 Web 应用、一个可与它通信的蓝牙设备,并且需要使用 Puppeteer v21.4.0 或更高版本。

启动浏览器

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

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 通过 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 Community Group 维护着这套测试,所有这些测试都可以从浏览器或本地运行。“只读特征”测试与本博文中使用的示例最相似。

致谢

感谢 Vincent Scheib 启动此项目并就本博文提供宝贵反馈。