How to Emulate a Bluetooth Adapter with Puppeteer
Use Puppeteer's experimental Bluetooth API to emulate an adapter and simulate a peripheral, or automate Chrome's chooser to test with real hardware.
Puppeteer can emulate a Bluetooth adapter in Chromium without a physical adapter. Use the experimental page.bluetooth API to turn on a simulated adapter, define a preconnected peripheral, run your Web Bluetooth app, and disable emulation during cleanup. This is useful for testing app logic. If you need to verify real hardware interoperability, automate Chrome’s Web Bluetooth chooser with an actual device instead.
The API and examples are version-sensitive. Check the Puppeteer documentation for the version installed in your project before adopting the code below. The Puppeteer Bluetooth API reference marks these methods experimental. Puppeteer BluetoothEmulation API
1. Install Puppeteer and check the API
In a new project, install Puppeteer, which downloads a compatible Chrome for Testing browser by default:
npm install puppeteer
For a project that already uses Puppeteer, check the installed version and confirm that its Page type and runtime expose page.bluetooth. The API reference’s current method signature is page.bluetooth.emulateAdapter(state, leSupported?). Since the interface is experimental, do not assume that a method or option exists in every older Puppeteer release.
2. Emulate an adapter and preconnected peripheral
This runnable JavaScript example starts a browser, creates an isolated browser context, enables a powered-on adapter, and provides a simulated preconnected peripheral. Replace the example page URL with your application and trigger the app behavior you want to test. The address, name, manufacturer data, and service UUID are test values; they do not identify real hardware.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
const context = await browser.createBrowserContext();
const page = await context.newPage();
try {
await page.bluetooth.emulateAdapter('powered-on');
await page.bluetooth.simulatePreconnectedPeripheral({
address: '01:02:03:04:05:06',
name: 'Test Peripheral',
manufacturerData: [[
0x004c,
new Uint8Array([0x02, 0x15, 0x00, 0x01])
]],
knownServiceUuids: ['heart_rate']
});
await page.goto('http://localhost:3000', {
waitUntil: 'domcontentloaded'
});
// Replace this with your app's action and assertions.
// For example, click the control that connects to a preconnected device.
await page.waitForSelector('[data-testid="bluetooth-status"]');
console.log(await page.$eval(
'[data-testid="bluetooth-status"]',
element => element.textContent
));
} finally {
// Emulation is browser-context scoped in Chromium. Disable it before
// closing the context so later tests do not inherit emulated state.
try {
await page.bluetooth.disableEmulation();
} finally {
await context.close();
await browser.close();
}
}
})();
The peripheral configuration describes a simulated device. The manufacturer data is an encoded byte sequence associated with a manufacturer identifier; choose values that match your app’s parser. knownServiceUuids tells the emulation which service UUIDs are known. The example uses the standard heart rate service name. Adapt these values to the services and data your app expects. This preconnected-peripheral setup is not a general-purpose physical-device chooser test.
Adapter state and Bluetooth Low Energy support
The documented method accepts an adapter state and an optional leSupported boolean. The example uses powered-on and leaves the optional argument at its default. The API describes adapter emulation as experimental; consult the versioned reference for accepted states and option behavior in your installed release rather than relying on values copied from a different version.
Call disableEmulation() in cleanup, including when navigation or an assertion fails. Chromium currently scopes this emulation to the browser context. Pages sharing one context may interfere with one another’s Bluetooth state, so use separate contexts for tests that need independent state. Confirm this behavior against the Chromium and Puppeteer versions in your project.
3. Choose emulation or a real-device chooser test
| Question | Adapter and peripheral emulation | Chooser automation with real hardware |
|---|---|---|
| What is being tested? | App logic against simulated Bluetooth state and a preconnected peripheral. | The chooser flow and interaction with a discovered physical device. |
| Is hardware required? | No physical adapter or peripheral is needed for software emulation. | Yes. The test environment must make the actual Bluetooth device discoverable to Chrome. |
| What can fail outside the app? | Version support, context isolation, and the behavior covered by the emulation API. | Platform permissions, Bluetooth availability, device discovery, headless behavior, and hardware state. |
Puppeteer’s separate chooser workflow lets a test wait for the device prompt, match a discovered device, and select it. The Chrome for Developers walkthrough says its chooser automation example requires Puppeteer v21.4.0 or later; that is a requirement for that walkthrough, not a blanket minimum for every Bluetooth emulation method. It recommends registering waitForDevicePrompt() before the UI action that calls navigator.bluetooth.requestDevice(). Linux may need an additional Chromium argument, and the guide discusses visible Chrome or new headless mode. Check the guide and your installed versions for platform details. Chrome for Developers: Test Web Bluetooth with Puppeteer
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
try {
await page.goto('http://localhost:3000');
// Register before clicking: the click opens requestDevice()'s chooser.
const promptPromise = page.waitForDevicePrompt();
await page.click('[data-testid="connect-bluetooth"]');
const prompt = await promptPromise;
const devices = await prompt.waitForDevice({
filters: [{ name: 'My Test Device' }]
});
const device = devices[0];
if (!device) throw new Error('Expected Bluetooth device was not found');
await prompt.select(device);
// Continue with app-specific connection assertions here.
} finally {
await browser.close();
}
})();
Chooser method details are version-sensitive too. Follow the Chrome guide for the precise prompt and device matching API available in your Puppeteer version. A real-device test complements emulation when you need to check radio, firmware, pairing, or actual GATT behavior.
4. Know what the emulation covers
The Puppeteer convenience API described here exposes adapter emulation and a simulated preconnected peripheral. The underlying Chrome DevTools Protocol BluetoothEmulation domain describes broader virtual-device testing, including simulated advertisements, GATT services and characteristics, operation responses, and disconnections. Do not assume every protocol command has a direct Puppeteer convenience method; check the protocol and Puppeteer APIs for the capabilities your test needs.
Also distinguish Bluetooth emulation from page.emulate(device). Puppeteer’s device emulation changes viewport metrics and user agent to resemble a device profile; it does not emulate Bluetooth. Puppeteer Page.emulate() reference
5. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
page.bluetooth is undefined or a method is missing |
The installed Puppeteer version does not expose the experimental interface, or the method differs from the current reference. | Check the installed Puppeteer version and its API documentation. Use a version that documents the method, and verify the method name before running the test. |
| The app cannot find the simulated device | The app’s discovery flow expects an advertised device or chooser selection, while the test configured a preconnected peripheral. | Match the test setup to the app flow. Use the documented preconnected-peripheral behavior for that case, or use chooser automation with real hardware when testing discovery of a physical device. |
| One test changes another test’s Bluetooth behavior | Both pages share a browser context, and Chromium’s emulation state is context-scoped. | Give independent tests separate browser contexts and disable emulation during cleanup. |
| The chooser never appears | The app action may not have called requestDevice(), the prompt wait may have been registered too late, or the browser/platform cannot show the chooser in its current configuration. |
Register waitForDevicePrompt() before the triggering click, verify that the app reaches its request call, and follow the Chrome guide for supported platform and headless settings. |
| A physical device is not listed | The device is not discoverable, is out of range, is already connected elsewhere, or the environment lacks Bluetooth access. | Check device power and discoverability, host Bluetooth access, platform permissions, and the Chromium setup described by the guide. |
| The test passes in emulation but fails on hardware | Emulation does not validate radio conditions, firmware, pairing, or every device-specific GATT behavior. | Keep emulated tests for repeatable app logic and add real-device coverage for the hardware interactions that matter. |
6. Performance, reliability, and cost
Software emulation avoids the setup and variability of physical peripherals, which makes it useful for repeatable browser tests. It still depends on the installed Puppeteer and Chromium behavior, and the interface is experimental. Isolate tests that need independent state, clean up emulation reliably, and keep hardware tests for requirements that the simulation cannot validate. The cited documentation provides no basis for a numerical speed, reliability, or cost comparison.
There is no Bluetooth adapter purchase required for the software-emulation flow. Real-device chooser tests do require access to suitable hardware and a compatible test environment. Keep those needs separate when estimating test setup.
7. Capture a visual record of the test page
If you also need a screenshot of the web page under test, take it after the app reaches the state you want to document:
await page.screenshot({ path: 'bluetooth-test.png', fullPage: true });
This captures the rendered page, not Bluetooth radio traffic, device state outside the page, or proof that the physical peripheral interoperated correctly. For Puppeteer screenshot options, see the Puppeteer screenshot API.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. It does not emulate Bluetooth or replace Puppeteer for Bluetooth tests; use it when you need a rendered page capture without managing a browser. Its one-call endpoint returns an image or PDF, and the documented query parameters work with parameter names used by other screenshot APIs. See the ScreenshotNeo API docs.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers report the page verdict and billing status.
- An MCP server lets AI agents use screenshot, page information, and PDF capture tools.
- The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Does Puppeteer need a Bluetooth USB adapter for emulation?
No. The adapter and peripheral in this workflow are software-emulated. A real adapter and device are relevant to physical-device testing.
Does page.emulate('iPhone') turn on Bluetooth?
No. Device profiles affect viewport metrics and user agent. Use the Bluetooth API for Bluetooth emulation.
Can the emulation prove that my product works with every Bluetooth device?
No. Use real hardware tests for interoperability and device-specific behavior that the simulation does not cover.
Where can I see the browser’s Bluetooth test capabilities?
Start with the Chrome DevTools Protocol BluetoothEmulation domain and the Chromium Bluetooth testing documentation, then check which capabilities your Puppeteer version exposes.


