Puppeteer `BluetoothEmulation.emulateAdapter()`:模拟蓝牙适配器以驱动 Web Bluetooth 仿真
PuppeteerBluetoothEmulation.emulateAdapter()模拟蓝牙适配器以驱动 Web Bluetooth 仿真【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer本文围绕 Puppeteer 的page.bluetooth.emulateAdapter()方法展开它对应 Web Bluetooth 规范中的bluetooth.simulateAdapter调试命令是启用蓝牙仿真的前置条件。读完本文你能掌握该方法的签名、参数取值AdapterState与leSupported、它在 CDP 与 BiDi 两种协议下的底层实现差异以及如何结合simulatePreconnectedPeripheral()与waitForDevicePrompt()完成一条完整的 Web Bluetooth 设备选择测试链路。方法定位为什么必须先 emulateAdapterBluetoothEmulation接口暴露页面的蓝牙仿真能力其中emulateAdapter()是三个方法中必须最先调用的一环——只有先把虚拟蓝牙适配器设置成指定状态后续的simulatePreconnectedPeripheral()模拟已连接外设才能在页面上产生可感知的设备。在 Page 抽象类 中该方法通过page.bluetooth属性对外暴露// packages/puppeteer-core/src/api/Page.ts abstract get bluetooth(): BluetoothEmulation;整个接口定义在 api/BluetoothEmulation.ts 中并带有experimental标记说明它仍处于实验阶段API 可能随版本演进。方法签名与参数详解文档给出的 TypeScript 签名为interface BluetoothEmulation { emulateAdapter(state: AdapterState, leSupported?: boolean): Promisevoid; }参数stateAdapterStatestate指定期望的蓝牙适配器状态类型为 AdapterState源码中是一个三值联合类型// packages/puppeteer-core/src/api/BluetoothEmulation.ts export type AdapterState absent | powered-off | powered-on;取值语义absent设备上不存在蓝牙适配器powered-off存在适配器但已关闭powered-on适配器已开启可被网页检测到要测试依赖navigator.bluetooth的页面功能通常使用powered-on要验证用户机器没有蓝牙时的降级 UI则传入absent或powered-off。参数leSupported可选boolean标记该适配器是否支持低功耗蓝牙Bluetooth Low Energy, LE。该参数是可选的从源码实现看CDP 与 BiDi 两种后端都将默认值处理为true// packages/puppeteer-core/src/cdp/BluetoothEmulation.ts async emulateAdapter(state: AdapterState, leSupported true): Promisevoid { // ... }也就是说不显式传参时模拟出的适配器默认支持 LE。如果你的被测页面依赖 LE 设备如心率带、BLE 信标可保持默认若要模拟一个仅支持经典蓝牙的适配器显式传入false。返回值Promisevoid方法在浏览器确认状态切换完成后 resolve调用方应await后再执行后续的蓝牙交互。底层实现CDP 与 BiDi 两条路径Puppeteer 同时支持 CDP 和 WebDriver BiDi 两种协议emulateAdapter()在两者下有各自实现行为上却保持一致。CDP 实现先 disable 再 enableCdpBluetoothEmulation 的实现值得注意// packages/puppeteer-core/src/cdp/BluetoothEmulation.ts async emulateAdapter(state: AdapterState, leSupported true): Promisevoid { // Bluetooth spec requires overriding the existing adapter (step 6). From the CDP // perspective, it means disabling the emulation first. // https://webbluetoothcg.github.io/web-bluetooth/#bluetooth-simulateAdapter-command await this.#connection.send(BluetoothEmulation.disable); await this.#connection.send(BluetoothEmulation.enable, { state, leSupported, }); }这里体现了一个规范细节Web Bluetooth 规范的simulateAdapter命令要求覆盖已存在的模拟适配器因此在 CDP 层面必须分两步——先发送BluetoothEmulation.disable清掉旧的模拟状态再发送BluetoothEmulation.enable携带{ state, leSupported }建立新状态。这解释了为什么emulateAdapter()是幂等且可反复调用的每次调用都是先卸载、再重装而不是增量更新。BiDi 实现单条命令 浏览器上下文BidiBluetoothEmulation 则直接发送单条bluetooth.simulateAdapter命令并携带当前浏览器上下文 ID// packages/puppeteer-core/src/bidi/BluetoothEmulation.ts async emulateAdapter(state: AdapterState, leSupported true): Promisevoid { await this.#session.send(bluetooth.simulateAdapter, { context: this.#contextId, state, leSupported, }); }BiDi 后端将context显式作为命令参数传入这是它与 CDP 实现结构上的主要差别。需要注意的作用域限制接口文档中的 Remarks 部分明确指出了一个隔离性限制Web Bluetooth 规范要求模拟的适配器应按顶层可导航单元top-level navigable隔离但目前 Chromium 的蓝牙仿真实现是绑定到浏览器上下文browser context而非页面的。这意味着同一 browser context 下的不同页面共享同一套蓝牙仿真状态互相会相互干扰。从 BidiBluetoothEmulation 的构造器看BiDi 实现接收contextId并在每条命令中带上它可以推断 BiDi 协议层是按 context 维度下发仿真命令的。基于此从源码结构看一个稳妥的实践是对蓝牙仿真敏感的场景下为需要不同蓝牙状态的测试使用独立的 browser context避免跨页面状态污染。完整实战从 emulateAdapter 到设备选择单独调用emulateAdapter()只会得到一个开启的虚拟适配器网页的navigator.bluetooth.requestDevice()此时仍无设备可选。典型的完整链路是三步emulateAdapter(powered-on)—— 建立可用的虚拟适配器本文主角simulatePreconnectedPeripheral(...)—— 注入一台已连接的模拟外设提供地址、名称、厂商数据和已知服务 UUIDdisableEmulation()—— 测试结束后清除仿真状态。接口文档给出的示例await page.bluetooth.emulateAdapter(powered-on); await page.bluetooth.simulatePreconnectedPeripheral({ address: 09:09:09:09:09:09, name: SOME_NAME, manufacturerData: [ { key: 17, data: AP8BAX8, }, ], knownServiceUuids: [12345678-1234-5678-9abc-def123456789], }); await page.bluetooth.disableEmulation();其中PreconnectedPeripheral的字段定义可参见 api/BluetoothEmulation.tsaddressMAC 地址、name、manufacturerData数组每项含 Bluetooth SIG 公司标识key与 base64 编码的data、knownServiceUuids。端到端验证仓库测试中的真实用法仓库的 bluetooth-emulation.test.ts 展示了该方法在生产级测试中的完整用法值得直接借鉴// test/src/bluetooth-emulation.test.ts const state setupSeparateTestBrowserHooks({ args: [ --enable-featuresWebBluetoothNewPermissionsBackend, --enable-featuresWebBluetooth, ], acceptInsecureCerts: true, }); it(can be selected, async function () { const {page, httpsServer} state; await page.goto(httpsServer.EMPTY_PAGE); await page.bluetooth.emulateAdapter(powered-on); await page.bluetooth.simulatePreconnectedPeripheral(SIMULATED_PERIPHERAL); const devicePromptPromise page.waitForDevicePrompt(); const navigatorRequestDevicePromise page.evaluate( navigator.bluetooth.requestDevice({ acceptAllDevices: true, optionalServices: [], }), ); const devicePrompt await devicePromptPromise; await devicePrompt.select(devicePrompt.devices[0]!); expect(await navigatorRequestDevicePromise).toEqual(DEVICE_NAME); });这段测试揭示了几个关键前提文档正文并未展开实操时容易踩坑Feature flagChromium 需要以--enable-featuresWebBluetoothNewPermissionsBackend和--enable-featuresWebBluetooth启动Web Bluetooth API 才可用。Puppeteer 的launch()/connect()中通过args传入。HTTPS 安全上下文Web Bluetooth 仅在安全上下文可用测试使用了httpsServer.EMPTY_PAGE配合acceptInsecureCerts: true接受自签证书。调用时序emulateAdapter()与simulatePreconnectedPeripheral()都在触发requestDevice()之前完成与先建适配器、再放设备、后触发 UI的链路一致。设备提示的接管Puppeteer 提供page.waitForDevicePrompt()捕获设备选择提示再通过devicePrompt.select(...)或devicePrompt.cancel()程序化操作分别验证选中设备与取消后requestDevice()被 reject两种分支。模拟无蓝牙环境的写法借助state参数的三态能力同一接口也能模拟负面场景// 验证页面对无蓝牙硬件的降级表现 await page.bluetooth.emulateAdapter(absent); // 或 await page.bluetooth.emulateAdapter(powered-off);小结与适用边界emulateAdapter(state, leSupported?)是 Web Bluetooth 仿真的入口返回Promisevoid必须await。state取absent | powered-off | powered-onleSupported可选两个后端默认均为true。CDP 路径通过disable enable两条命令实现规范的覆盖旧适配器语义见 cdp/BluetoothEmulation.tsBiDi 路径单条bluetooth.simulateAdapter命令按 context 下发。注意仿真状态绑定 browser context 而非页面同一 context 内多页面会共享/干扰蓝牙仿真状态。该方法标记为实验性experimental且依赖 Chromium 的 Web Bluetooth feature flags 与 HTTPS 环境BiDi/Firefox 一侧的可用性以当前仓库实现为准。相关文档BluetoothEmulation 接口、disableEmulation()、simulatePreconnectedPeripheral()。【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考