コンテンツにスキップ

マイク入力をモックする

ユースケース: アプリがユーザーの声を録音する — 音声認識(Speech-to-Text)、ボイスメモ、発音を採点する語学学習アプリ、音声レベルメーター。マイクに向かって話す自動テストを書きたい。

▶ 動くサンプル: mock-microphone-input.spec.ts / デモを触る(実行方法は examples/README)

Playwrightにはマイク入力のためのAPIがありません。そのため、どのソリューションもスタックのどこかで「マイクの差し替え」を行うことになります:

  • Chromiumの起動フラグ(--use-file-for-fake-audio-capture)— ブラウザ組み込みで依存関係も不要ですが、Chromium専用・WAVのみ・ブラウザセッション全体で固定です。
  • OSレベルの仮想オーディオデバイス — クロスブラウザですが、CIでのセットアップは非常につらいです。
  • ページ内で getUserMedia をパッチする — クロスブラウザかつ実行時に制御可能。後述のライブラリはこの方式です。

Chromium: フェイクデバイス + 音声ファイル

Section titled “Chromium: フェイクデバイス + 音声ファイル”

Chromiumは起動時にマイクをWAVファイルへ差し替えられます。追加の依存関係は不要です:

playwright.config.ts
export default defineConfig({
projects: [
{
name: 'chromium',
use: {
launchOptions: {
args: [
'--use-fake-ui-for-media-stream', // 許可ダイアログを自動承認
'--use-fake-device-for-media-stream', // フェイクのキャプチャデバイスを提供
'--use-file-for-fake-audio-capture=tests/fixtures/hello-world.wav',
],
},
},
},
],
});

ファイルは WAV である必要があります。ffmpegで変換できます:

Terminal window
ffmpeg -i hello-world.mp3 tests/fixtures/hello-world.wav

デフォルトではファイルは無限ループで再生されます。%noloop を付けると1回だけ再生し、その後は無音になります:

--use-file-for-fake-audio-capture=tests/fixtures/hello-world.wav%noloop

ブラウザセッションごとに1つの発話で足り、テスト対象がChromiumだけなら、これで十分です。ただし制約は明確です: ファイルは起動時に固定されるため、最初の画面ではAと言い、次の画面ではBと言う、といったことができません。また、Firefox・WebKitでは何も再生されません。

もう1つ大きな注意点があります: macOSではフェイクキャプチャデバイスが不安定で、getUserMedia() が解決しないままハングすることがあります(--no-sandbox の有無やヘッドレスかどうかに関係なく発生。関連: crbug.com/1032604)。この方式はLinux/CI専用と考え、Macでのローカル実行には後述のライブラリを使ってください。

▶ 動くサンプル: mock-microphone-input-flag.spec.ts(上記の理由によりmacOSではスキップされます)

クロスブラウザ + 実行時制御: playwright-audio-mocking

Section titled “クロスブラウザ + 実行時制御: playwright-audio-mocking”

playwright-audio-mocking は、ページ読み込み前に navigator.mediaDevices.getUserMedia を置き換え、実際のWeb Audioベースの MediaStream を返します。その先にあるもの — MediaRecorder、AnalyserNode、WebRTC、音声認識API — はすべて無改造で動作し、Chromium・Firefox・WebKitに対応しています。

Terminal window
npm install -D playwright-audio-mocking
import { test, expect } from '@playwright/test';
import { mockMicrophone } from 'playwright-audio-mocking';
test('transcribes speech', async ({ page }) => {
const mic = await mockMicrophone(page); // page.goto() より前にインストールする
await page.goto('/voice-memo');
await page.getByRole('button', { name: 'Record' }).click();
await mic.play('tests/fixtures/hello-world.wav'); // マイクに音声を流し込む
await mic.waitForEnd();
await page.getByRole('button', { name: 'Stop' }).click();
await expect(page.getByTestId('transcript')).toContainText('hello world');
});

起動時フラグと違い、再生はテストの途中でも完全にスクリプトから制御できます:

await mic.play('fixtures/greeting.mp3'); // ブラウザがデコードできる形式なら何でも
await mic.play('fixtures/question.mp3'); // ストリーミング中のファイル切り替え
await mic.play('fixtures/hold-music.mp3', { loop: true });
await mic.pause(); // 無音になり、再生位置は保持
await mic.resume();
await mic.stop(); // 無音になり、再生位置はリセット
const { playing, position, duration } = await mic.status();

フィクスチャファイルなしで音声を生成する

Section titled “フィクスチャファイルなしで音声を生成する”

ファイルパスの代わりにバイト列を渡せます — たとえばTTS APIの出力を直接流し込めば、テストごとに違う内容を話させることができます:

await mic.play(Buffer.from(await synthesizeSpeech('add milk to my shopping list')));
  • mockMicrophone(page) は 必ず page.goto() より前に呼んでください — initスクリプトとして注入されるためです。
  • ライブラリをChromiumで使う場合は、ライブラリが提供する RECOMMENDED_CHROMIUM_ARGS(--autoplay-policy=no-user-gesture-required)を付けて起動すると、ユーザー操作なしでWeb Audioを開始できます。
  • モックが置き換えるのは音声トラックのみです。getUserMedia({ audio, video }) の場合、videoの制約はデフォルトで本来の実装にパススルーされます。