ScreenshotNeo

BlogHow-to

How to Wait for a Device with Puppeteer DeviceRequestPrompt

Use Puppeteer’s DeviceRequestPrompt to wait for a browser device request, select a matching device, or cancel the prompt without racing the page.

By the ScreenshotNeo team4 October 20266 min read

page.waitForDevicePrompt() lets a Puppeteer test wait for a page-originated device request, such as a Web Bluetooth request. Start waiting before the action that triggers the request, usually by starting the wait and click together with Promise.all(). Then use waitForDevice() to find a matching device and select() to choose it.

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 key ordering rule is that the prompt wait must be registered before the page requests a device. Puppeteer’s Page API reference warns that the method “must be called before the device request is made” and does not return a prompt that is already active.

1. What DeviceRequestPrompt does

A DeviceRequestPrompt represents a browser prompt caused by a page asking to connect to a device. The API exposes the selectable devices currently available to that prompt, lets you wait until one matches a predicate, and lets you either select a device or cancel the prompt.

This guide follows the Puppeteer API references retrieved for versions 25.12.0 and 25.2.1, including the Next documentation for the page wait and selection methods. Version labels differ across those references; check the API documentation matching the Puppeteer version installed in your project before relying on version-sensitive behavior.

2. Complete example: wait, match, and select

The following example assumes you already launched Puppeteer, opened a page, navigated to your application, and that the page has a button with the selector #connect-bluetooth. It starts the prompt wait before clicking so the prompt cannot be missed between those two operations.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: false });
try {
  const page = await browser.newPage();
  await page.goto('https://example.com/device-demo');

  const [devicePrompt] = await Promise.all([
    page.waitForDevicePrompt({ timeout: 10_000 }),
    page.click('#connect-bluetooth'),
  ]);

  const device = await devicePrompt.waitForDevice(
    ({ name }) => name.includes('My Device'),
    { timeout: 10_000 },
  );

  await devicePrompt.select(device);
  // Continue with assertions that verify the page's connected state.
} finally {
  await browser.close();
}

Replace the URL, button selector, and device-name predicate with values for your application and test environment. The explicit timeouts make a missing prompt or device fail within a bounded interval. Puppeteer’s shared wait options document a default timeout of 30 seconds and allow 0 to disable the timeout; a bounded timeout is usually easier to diagnose in a test suite.

Why use Promise.all?

This starts both promises without awaiting either one first. The wait is registered as the click begins, avoiding the race that occurs if the click opens the prompt before Puppeteer starts waiting. Do not write the sequence as an awaited click followed by waitForDevicePrompt().

Match the device you need

waitForDevice(predicate, options) resolves to the first available device that satisfies the predicate. The predicate receives a device object; the official example checks its name. Use properties actually present in the prompt’s device objects, and avoid assuming a desired device is already available when the prompt first appears.

const device = await devicePrompt.waitForDevice(
  ({ name }) => name === 'My Device',
  { timeout: 10_000 },
);

Use an exact comparison when the name is stable and unique. A substring or other predicate can be more appropriate when the browser-provided name varies. If more than one device could match, make the predicate specific enough to avoid selecting the wrong one.

3. Cancel a prompt instead of selecting a device

If the test is checking that the request can be dismissed, or it does not need a device connection, call cancel() on the prompt:

const [devicePrompt] = await Promise.all([
  page.waitForDevicePrompt({ timeout: 10_000 }),
  page.click('#connect-bluetooth'),
]);

await devicePrompt.cancel();

Selection and cancellation are alternative ways to handle the captured prompt. Select a device returned by that prompt’s device wait; cancel it when the test should dismiss the request.

4. Timeouts and failure behavior

Wait What it is waiting for What to check on timeout
page.waitForDevicePrompt() A page-originated device request Confirm the application action actually requests a device and that the wait starts before that action.
devicePrompt.waitForDevice(predicate, options) A device matching the predicate Inspect the available device properties and make sure the predicate can match a device in this test environment.

Wait options support a timeout. Puppeteer documents a 30-second default for shared wait options and 0 to disable the timeout. Disabling it can leave a test waiting indefinitely if the expected request or device never appears, so prefer a suitable finite value in automated suites.

5. Troubleshooting

Symptom Likely cause Fix
waitForDevicePrompt() times out The page did not make a device request, or the wait began after the triggering action. Start the wait and the action together with Promise.all(). Confirm the click reaches the code path that requests a device.
The prompt appears in the browser but Puppeteer does not return it The prompt was already active when the wait began. Register waitForDevicePrompt() before clicking or performing the action that opens the prompt. This method does not return an already active prompt.
waitForDevice() times out No available device satisfies the predicate, or the expected device did not become available. Check the predicate against the actual device properties supplied by the prompt. Verify the app made the expected request and set an appropriate bounded timeout.
The test selects the wrong device The predicate matches multiple devices or is broader than intended. Use a more specific predicate based on the properties available to the test.
The example works on one machine but not another Browser, operating-system, launch configuration, permissions, or hardware requirements may differ. Check the Puppeteer and browser versions and the target project’s device-test setup. The API references do not establish universal requirements for these environment details.

6. Test environment, reliability, and cost

The prompt API documents how to wait, match, select, or cancel; it does not establish that every test requires a physical device or prescribe a universal hardware, operating-system, browser-flag, or permission setup. Decide whether your project uses real hardware or a project-specific emulated or mocked setup, and verify those requirements against the exact browser and Puppeteer versions you run.

For reliable tests, register the prompt wait before the triggering action, give prompt and device waits finite timeouts, and make the device predicate specific. Keep environment-dependent device setup separate from assertions about the application’s connected state, so a timeout identifies whether the request or the expected device was missing.

The Puppeteer references provide no benchmark or special cost estimate for this API. Operational cost depends on the browser and test infrastructure you choose; the API documentation alone does not establish a hardware purchase requirement.

7. Or skip the browser setup

If your task is to capture a page as an image or PDF rather than exercise a device prompt, ScreenshotNeo offers a one-request screenshot API. 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}`);

ScreenshotNeo accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before the capture; each of those steps can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the page verdict and billing status in headers. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. ScreenshotNeo is made by Yorker Media.

Sign up free for 1,000 screenshots a month, with no card.

8. FAQ

How do I wait for a Web Bluetooth device prompt in Puppeteer?

Call page.waitForDevicePrompt() before the action that invokes the device request. Start the wait and action together with Promise.all(), then await a matching device and pass it to select().

Why does waitForDevicePrompt() miss my prompt?

The wait may have started after the page opened the prompt. Puppeteer does not return a currently active prompt through this method, so move the wait before the trigger.

Can I dismiss the prompt?

Yes. Call await devicePrompt.cancel() after the wait resolves.

Does this always require physical Bluetooth hardware?

The cited API references do not say that physical hardware is always required. Check the requirements of your browser, operating system, and project-specific test setup.

References