npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

playwright-audio-mocking

v0.1.1

Published

Stream arbitrary audio files into getUserMedia() microphone input from Playwright. Works on Chromium, Firefox and WebKit, with runtime play/pause/stop/loop control.

Downloads

300

Readme

playwright-audio-mocking

Stream any audio file into a website's microphone (getUserMedia()) from Playwright.

Test voice-driven UIs — speech recognition, voice memos, audio meters, WebRTC calls — by feeding them real audio, on Chromium, Firefox and WebKit, with full runtime control.

日本語版 README はこちら

import { test, expect } from '@playwright/test';
import { mockMicrophone } from 'playwright-audio-mocking';

test('voice input', async ({ page }) => {
  const mic = await mockMicrophone(page);        // install BEFORE page.goto()
  await page.goto('https://example.com/voice');

  await page.click('#record');                   // the app calls getUserMedia()
  await mic.play('fixtures/hello.mp3');          // stream the file as mic input
  await mic.waitForEnd();

  await expect(page.locator('#transcript')).toHaveText('hello');
});

Why not --use-file-for-fake-audio-capture?

Chromium has a built-in flag for fake audio capture, but it is limited:

| | --use-file-for-fake-audio-capture | playwright-audio-mocking | | --- | --- | --- | | Browsers | Chromium only | Chromium, Firefox, WebKit | | Formats | WAV only | Anything the browser can decode (WAV, MP3, OGG, FLAC, …) | | Switch files at runtime | ✗ (fixed at launch) | ✓ (play() any time, any file) | | Playback control | ✗ | ✓ play / pause / resume / stop / loop | | Wait for playback to finish | ✗ | ✓ waitForEnd() |

Installation

npm install -D playwright-audio-mocking

Requires playwright-core >= 1.30 as a peer dependency (already present if you use @playwright/test or playwright).

How it works

mockMicrophone(page) registers an init script that replaces navigator.mediaDevices.getUserMedia before the page loads. When the site requests audio, it receives a real MediaStream backed by a Web Audio MediaStreamAudioDestinationNode. mic.play(file) sends the file's bytes into the page, decodes them with AudioContext.decodeAudioData, and plays them into that stream.

Because the site gets a genuine MediaStream, everything downstream — MediaRecorder, AnalyserNode, WebRTC addTrack, speech recognition sending audio to a server — works as usual. The browser's real microphone is never touched, no permission prompt is triggered for audio, and the fake device shows up in enumerateDevices().

Usage

With @playwright/test

import { test, expect } from '@playwright/test';
import { mockMicrophone } from 'playwright-audio-mocking';

test('transcribes speech', async ({ page }) => {
  const mic = await mockMicrophone(page);
  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');
});

Installing on a whole browser context

import { installAudioMock, microphone } from 'playwright-audio-mocking';

const context = await browser.newContext();
await installAudioMock(context);          // every page in this context is mocked

const page = await context.newPage();
await page.goto('https://example.com/voice');
const mic = microphone(page);
await mic.play('fixtures/hello.wav');

Runtime control

const mic = await mockMicrophone(page);
await page.goto('/call');

await mic.play('fixtures/greeting.mp3');           // stream a file
await mic.play('fixtures/question.mp3');           // switch files mid-stream
await mic.play('fixtures/hold-music.mp3', { loop: true }); // loop forever
await mic.pause();                                  // silence, position kept
await mic.resume();                                 // continue from position
await mic.stop();                                   // silence, position reset

await mic.load('fixtures/answer.mp3');              // pre-decode without playing
await mic.play();                                   // replay the loaded audio

const { playing, paused, position, duration } = await mic.status();

Audio can also be passed as bytes instead of a file path:

await mic.play(Buffer.from(await synthesizeSpeech('hello'))); // e.g. from a TTS API

API

mockMicrophone(page, options?): Promise<Microphone>

Installs the mock on the page and returns its controller. Must be called before page.goto() — the mock is injected as an init script and takes effect on the next navigation.

installAudioMock(pageOrContext, options?): Promise<void>

Installs the mock only. Accepts a Page or a BrowserContext (mocks every page in the context).

microphone(page): Microphone

Returns a controller for a page whose mock was installed via installAudioMock.

Options (AudioMockOptions)

| Option | Default | Description | | --- | --- | --- | | deviceLabel | 'Mock Microphone (playwright-audio-mocking)' | Label reported by enumerateDevices() and MediaStreamTrack.label | | deviceId | 'playwright-audio-mocking' | deviceId reported by enumerateDevices() / track.getSettings() | | groupId | 'playwright-audio-mocking' | groupId reported by enumerateDevices() | | passThroughVideo | true | For getUserMedia({ audio, video }), forward the video constraint to the real getUserMedia and merge the video tracks in (only audio is mocked) |

Microphone

| Method | Description | | --- | --- | | play(input?, { loop? }) | Decode and stream a file path / Uint8Array / Buffer. Without input, replays the last loaded audio. Resolves with { duration } once playback starts. | | load(input) | Decode without playing. Resolves with the duration in seconds. | | pause() / resume() | Pause keeping the position / continue from it. | | stop() | Stop and reset the position. | | waitForEnd({ timeout? }) | Resolve when playback finishes. Never resolves for loop: true — call stop(). | | status() | { installed, playing, paused, position, duration } | | isPlaying() | Shorthand for (await status()).playing. |

RECOMMENDED_CHROMIUM_ARGS

['--autoplay-policy=no-user-gesture-required'] — see below.

Notes & caveats

  • Install before navigation. The mock takes effect on the next navigation after mockMicrophone() / installAudioMock(). If the page is already on the target site, page.reload() after installing.

  • Navigation resets playback state. Loaded audio lives in the document; after a navigation, call play() again.

  • Start playing after the app starts listening. Like a real mic, audio played before the site calls getUserMedia() (or before it starts consuming the stream) is simply not heard.

  • Autoplay policy. Chromium may keep the AudioContext suspended until a user gesture. Playing after a click() is usually enough; for gesture-less flows, launch with:

    import { RECOMMENDED_CHROMIUM_ARGS } from 'playwright-audio-mocking';
    const browser = await chromium.launch({ args: RECOMMENDED_CHROMIUM_ARGS });

    Or in playwright.config.ts:

    use: { launchOptions: { args: ['--autoplay-policy=no-user-gesture-required'] } }
  • Firefox on headless Linux CI needs an audio server. Without an audio backend, Firefox never starts its AudioContext and no audio flows. Install and start PulseAudio before running tests (see this repo's CI workflow):

    sudo apt-get install -y pulseaudio && pulseaudio --start --exit-idle-time=-1
  • Secure context required. navigator.mediaDevices only exists on HTTPS or localhost — same rule as for a real microphone.

  • Supported formats are whatever the browser's decodeAudioData supports. WAV and MP3 work everywhere; OGG/Opus is not supported by WebKit.

  • No permission prompts for audio. The mocked getUserMedia never reaches the browser's permission layer. If you use passThroughVideo with real video constraints, grant camera permission as usual (context.grantPermissions(['camera'])).

License

MIT