How to Handle Device Requests in Puppeteer
Handle WebBluetooth device prompts in Puppeteer by waiting before the page action, matching the intended device, and selecting it. Learn how this differs from mobile emulation and network interception.
To handle a page-triggered device request in Puppeteer, start page.waitForDevicePrompt() before the page action that triggers the request. Then wait for a matching device and pass it to the prompt’s select() method. This workflow is typically used for APIs such as WebBluetooth; it is separate from mobile-device emulation and HTTP request interception.
Handle a WebBluetooth device prompt
The key ordering rule is to establish the prompt wait before clicking the page control. Puppeteer documents that waitForDevicePrompt() does not return a prompt that is already active.
- Open the page that contains the device connection control.
- Start waiting for the device prompt and trigger the control together.
- Wait for a device matching your test environment.
- Select that device to resolve the prompt.
const [devicePrompt] = await Promise.all([
page.waitForDevicePrompt(),
page.click('#connect-bluetooth'),
]);
const device = await devicePrompt.waitForDevice(({ name }) =>
name?.includes('My Device'),
);
await devicePrompt.select(device);
The predicate above is an example: replace My Device with a reliable characteristic of the device available to your test. Device names are not necessarily globally unique. For stronger matching, use the device properties exposed by your installed Puppeteer version and the characteristics of the hardware or test setup.
Complete runnable example
This Node.js example launches Chromium, opens a page, starts the prompt wait before clicking, selects a matching device, and closes the browser. Set TARGET_URL to your test page. The page must expose the connection button and make a device request when it is clicked. The example does not create or simulate a Bluetooth peripheral.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: false });
try {
const page = await browser.newPage();
await page.goto(process.env.TARGET_URL || 'http://localhost:3000', {
waitUntil: 'domcontentloaded',
});
const [devicePrompt] = await Promise.all([
page.waitForDevicePrompt(),
page.click('#connect-bluetooth'),
]);
const device = await devicePrompt.waitForDevice(({ name }) =>
name?.includes(process.env.DEVICE_NAME || 'My Device'),
);
await devicePrompt.select(device);
console.log(`Selected device: ${device.name}`);
} finally {
await browser.close();
}
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Install Puppeteer in the project with npm install puppeteer, then run the file with TARGET_URL=http://localhost:3000 DEVICE_NAME='My Device' node device-request.js. Use a browser environment and permissions appropriate for the WebBluetooth behavior under test. Check the API reference for the Puppeteer version in your lockfile: the device-prompt method is documented in the Next reference, so availability can depend on the installed release.
Why use Promise.all?
The page action can cause the browser prompt to appear immediately. Starting the wait and click in the same Promise.all expression ensures the wait is registered before the action is issued. Do not click first and then call waitForDevicePrompt(); the prompt may already be active and will not be returned by that method.
What “device requests” can mean
The phrase can refer to three different Puppeteer tasks. Choose the API based on the event you need to handle.
| Task | API or object | When to set it up |
|---|---|---|
| Choose a device requested by page code, such as WebBluetooth | Page.waitForDevicePrompt() and DeviceRequestPrompt |
Before triggering the page’s device request |
| Render a page using a mobile device profile | Page.emulate(device), or user-agent and viewport APIs |
Before navigation when possible |
| Observe or modify HTTP traffic | Page.setRequestInterception(true) and HTTPRequest |
Before requests you intend to intercept |
Mobile-device emulation is a different task
page.emulate(device) applies a device profile’s user agent and viewport metrics. Puppeteer describes it as a shortcut for setting the user agent and viewport. It changes how the page is presented; it does not select a physical Bluetooth device or respond to a device prompt.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.emulate(puppeteer.KnownDevices['iPhone 13']);
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'mobile.png' });
} finally {
await browser.close();
}
})();
Use a device profile available in your installed Puppeteer version. Emulate before navigation: changing viewport dimensions can resize the page and may trigger a reload in some cases. A profile is software configuration, not a real phone or Bluetooth peripheral.
HTTP request interception is a different task
Request interception handles page network requests, not browser device-selection prompts. Once enabled, intercepted requests stall until they are continued, aborted, or fulfilled, except requests served from the browser cache.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', (request) => {
if (request.url().includes('/analytics/')) {
void request.abort();
} else {
void request.continue();
}
});
await page.goto('https://example.com');
} finally {
await browser.close();
}
})();
Every intercepted request needs a resolution. If multiple handlers might resolve the same request, guard against requests that have already been handled. Puppeteer also documents cooperative interception priorities, but that mode only applies when all resolutions use priorities; a legacy resolution can resolve immediately.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
waitForDevicePrompt() never resolves |
The page action did not trigger a device request, or the wait began after the prompt opened. | Start the wait before the click, and confirm the page actually invokes a device-request API in the tested browser context. |
| No matching device is found | The predicate does not match an available device, or the expected device is absent. | Check the test device’s exposed properties and loosen or correct the predicate. Do not assume a particular product or globally unique name. |
waitForDevicePrompt is undefined |
The installed Puppeteer version may not expose the API documented in the Next reference. | Check the package version and its matching API reference; upgrade only if the project can use a release that provides the method. |
| Page behaves differently after emulation | The viewport or user agent was set after navigation, causing a resize or possible reload. | Apply the device profile before navigating and wait for the page after navigation. |
| Navigation hangs after enabling interception | An intercepted request was left unresolved. | Resolve every request with continue, abort, or fulfill, including requests that do not match the special case. |
Reliability, runtime, and cost considerations
- Keep the ordering deterministic. Register the prompt wait before the action that opens it, and match a device using properties stable in your test environment.
- Make failures diagnosable. Log the browser and Puppeteer versions, the page URL, the action that should trigger the request, and whether a matching device became available. Avoid relying on a name that varies across machines.
- Close browser resources. Put browser shutdown in a
finallyblock so failed prompt waits do not leave a browser process running. - Budget for the real work. This flow requires a browser page, the page’s device-request code, and an available matching device. The documentation does not specify a universal runtime or hardware requirement; measure it in the environment used for the test.
- Keep interception scoped. Intercept only when you need to change HTTP traffic. Every intercepted request adds resolution logic and a missed resolution can stall page activity.
Or skip the browser setup
If the task is to capture a website screenshot rather than automate a WebBluetooth interaction, ScreenshotNeo can return an image or PDF from one GET request. Its options include viewport and device presets, full-page capture, selector capture, custom waits, headers, cookies, and more. See the ScreenshotNeo API documentation.
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) =>
writeFile('shot.webp', Buffer.from(await res.arrayBuffer()))
);
- 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 never billed. Response headers report the page verdict and billing status.
- An MCP server lets AI agents use screenshot, page-info, 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 ScreenshotNeo and get 1,000 screenshots a month free, with no card.
FAQ
Does selecting a device prompt connect a real Bluetooth device?
The prompt workflow selects a device offered to the browser by the page’s device request. Whether the subsequent application interaction succeeds depends on the browser context, page behavior, and available device.
Can mobile emulation test WebBluetooth hardware behavior?
Emulation applies a software device profile’s user agent and viewport metrics. It does not supply a real phone or Bluetooth peripheral.
Can request interception block or select a Bluetooth device?
No. Request interception handles HTTP requests. Use the device-prompt API for a page-triggered device selection request.
Does ScreenshotNeo automate device selection?
ScreenshotNeo captures website screenshots and PDFs. Its screenshot API is useful for visual capture; the described device-prompt flow is a Puppeteer browser-automation task.


