使用 Puppeteer 测试网络蓝牙

François Beaufort
François Beaufort

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

这篇博文详细介绍了如何使用 Puppeteer 操作和测试启用了蓝牙的 Web 应用。其中的关键在于 Puppeteer 能够操作 Chrome 的蓝牙设备选择器。

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

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
用户在 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 社区小组负责维护这套测试,所有这些测试都可以通过浏览器或在本地运行。“只读特性”test”这一示例与这篇博文中使用的示例最为相似。

致谢

感谢 Vincent Scheib 启动这个项目并为这篇帖子提供宝贵的反馈意见。