Puppeteer でウェブ Bluetooth をテストする

François Beaufort
François Beaufort

ウェブ Bluetooth は Chrome 56 以降でサポートされており、デベロッパーはユーザーの Bluetooth デバイスと直接通信するウェブアプリを作成できます。互換性のある Bluetooth デバイスにコードをアップロードする Espruino ウェブエディタの機能は、その一例です。これらのアプリケーションを Puppeteer でテストできるようになりました。

このブログ投稿では、Puppeteer を使用して Bluetooth 対応のウェブアプリを操作、テストする方法について説明します。そのポイントは、Chrome の Bluetooth デバイス選択ツールを操作する Puppeteer の機能です。

Chrome でのウェブ Bluetooth の使用に慣れていない場合は、次の動画で Espruino ウェブエディタでの Bluetooth プロンプトをご覧ください。

ユーザーが Espruino ウェブエディタで Puck.js Bluetooth デバイスを選択します。

このブログ投稿では、Bluetooth 対応のウェブアプリと、このウェブアプリが通信可能な Bluetooth デバイスを必要とし、Puppeteer v21.4.0 以降を使用している必要があります。

ブラウザを起動する

他の Puppeteer スクリプトと同様に、まず Puppeteer.launch() を使用してブラウザを起動します。Bluetooth 機能にアクセスするには、いくつかの引数を追加で指定する必要があります。

import puppeteer from 'puppeteer';

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

最初のページを開くときは、通常、シークレット モードのブラウザ コンテキストを使用することをおすすめします。これにより、スクリプトで実行されるテスト間で権限の漏洩を防ぐことができます(ただし、Puppeteer では回避できない OS レベルの共有状態があります)。次のコードは、その方法を示しています。

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

次に、Page.goto() でテストするウェブアプリの URL に移動します。

Bluetooth デバイスのメッセージを開く

Puppeteer を使用して Puppeteer でウェブアプリのページを開いたら、Bluetooth デバイスに接続してデータを読み取ることができます。次のステップでは、navigator.bluetooth.requestDevice() の呼び出しを含む JavaScript を実行するボタンがウェブアプリ上にあることを前提としています。

このボタンを押すには Page.locator().click() を使用します。Bluetooth デバイス選択ツールが表示されるタイミングを認識するには、Page.waitForDevicePrompt() を使用します。ボタンをクリックする前に waitForDevicePrompt() を呼び出す必要があります。そうしないと、プロンプトはすでに開かれており、検出できません。

Puppeteer のメソッドは両方とも Promise を返すため、Promise.all() を使用すると、正しい順序でまとめて呼び出すことができます。

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

waitForDevicePrompt() から返された Promise は DeviceRequestPrompt オブジェクトに解決されます。このオブジェクトを、接続する Bluetooth デバイスを選択する際に使用します。

デバイスを選択する

DeviceRequestPrompt.waitForDevice()DeviceRequestPrompt.select() を使用して、正しい Bluetooth デバイスを見つけて接続します。

DeviceRequestPrompt.waitForDevice() は、デバイスの基本情報を含む Bluetooth デバイスを Chrome が検出するたびに、指定されたコールバックを呼び出します。コールバックが初めて true を返すと、waitForDevice() は一致した DeviceRequestPromptDevice に解決されます。そのデバイスを DeviceRequestPrompt.select() に渡して、その Bluetooth デバイスを選択して接続します。

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

DeviceRequestPrompt.select() が解決されると、Chrome がデバイスに接続され、ウェブページがデバイスにアクセスできるようになります。

デバイスから読み取る

この時点で、ウェブアプリは選択した Bluetooth デバイスに接続され、そのデバイスの情報を読み取ることができます。次に例を示します。

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 を介した Bluetooth デバイスとの通信をご覧ください。

これで、Puppeteer を使用して Bluetooth デバイス選択メニューからデバイスを選択するという人手の代わりに、Bluetooth 対応ウェブアプリの使用を自動化する方法がわかりました。これは一般的には有用かもしれませんが、そのようなウェブアプリのエンドツーエンドのテストを記述する場合に直接適用できます。

テストの作成

これまでのコードから完全なテストを作成するうえで欠けているのは、ウェブアプリから Puppeteer スクリプトに情報を取得することです。これを取得したら、テスト ライブラリ(TAPmocha など)を使用して、正しいデータが読み取られてレポートされたことを確認するのはとても簡単です。

そのための最も簡単な方法の一つは、データを DOM に書き込むことです。JavaScript には、追加のライブラリなしでこれを行うさまざまな方法があります。架空のウェブアプリに戻ると、Bluetooth デバイスからデータを読み込んだり、フィールドにリテラルデータを出力したりするときに、ステータス インジケーターの色が変わることがあります。次に例を示します。

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

Puppeteer の Page.$eval() では、このデータをページの DOM からテスト スクリプトに pull できます。$eval()document.querySelector() と同じロジックを使用して要素を検索し、その要素を引数として指定されたコールバック関数を実行します。変数として取得したら、アサーション ライブラリを使用して、データが想定どおりかどうかをテストします。

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

参考情報

Puppeteer を使用して Bluetooth 対応ウェブアプリのテストを作成する複雑な例については、https://github.com/WebBluetoothCG/manual-tests/ のリポジトリをご覧ください。このテストスイートはウェブ Bluetooth コミュニティ グループが管理しており、すべてのテストはブラウザまたはローカルから実行できます。「Read-only Characteristic」テストは、このブログ投稿で使用されている例と最も類似しています。

謝辞

このプロジェクトを開始し、この投稿に対して貴重なフィードバックを提供してくれた Vincent Scheib に感謝します。