Puppeteer Device: Configure a Browser Device
Emulate a named phone or tablet in Puppeteer, set a custom viewport and user agent, and avoid common device configuration errors.
To configure a browser device in Puppeteer, create a page, call page.emulate(device) or set a custom viewport, and do so before navigating. A named device profile pairs its user agent with viewport and device metrics. If you only need to change the viewport, use page.setViewport().
For example, this ES module script emulates an iPhone profile from Puppeteer’s device catalog:
import puppeteer from 'puppeteer';
import {KnownDevices} from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
const device = KnownDevices['iPhone 17 Pro'];
await page.emulate(device); // Configure the page before navigation.
await page.goto('https://example.com', {waitUntil: 'domcontentloaded'});
console.log('Page title:', await page.title());
} finally {
await browser.close();
}
Install Puppeteer in a new project with npm install puppeteer, save the script as an .mjs file, and run it with node. See the Page.emulate() API reference, KnownDevices catalog, and getting started guide. Check the docs for your installed Puppeteer version if a profile or API is missing: the references may describe different releases.
1. Choose a named device or custom settings
Use a named profile when you want a convenient combination of device metrics and user agent. Use custom settings when you need a particular viewport, scale factor, touch behavior, or user agent that is not represented by a suitable catalog entry.
| Approach | Use it when | What it configures |
|---|---|---|
page.emulate(device) |
You want a known phone or tablet profile. | The profile’s user agent and viewport/device settings. |
page.setViewport(settings) |
You only need to set page dimensions and related viewport flags. | Viewport and device metrics; it does not by itself supply a device profile’s user agent. |
A custom Device passed to page.emulate() |
You need a specific user agent paired with your own viewport settings. | The user agent and viewport values you provide. |
| Browser screen configuration | You are testing headless layouts involving one or more browser screens. | Browser-level screens, a separate concern from emulating a device on a page. |
Puppeteer documents page.emulate(device) as a shortcut for setting the page’s user agent and viewport. Its Device type has userAgent and viewport properties. See the official Device interface.
2. Emulate a named phone or tablet
Import KnownDevices and select a profile by its catalog key. Apply it before goto(), since changing the page size during a visit can affect how a site behaves.
import puppeteer from 'puppeteer';
import {KnownDevices} from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
const device = KnownDevices['iPhone 17 Pro'];
if (!device) {
throw new Error('Device profile not found in this Puppeteer version');
}
await page.emulate(device);
await page.goto('https://example.com');
await page.screenshot({path: 'iphone.png', fullPage: true});
} finally {
await browser.close();
}
Replace the key with the exact name of a profile available in the installed version’s KnownDevices catalog. Treat the catalog as version-dependent: consult the matching docs if a name is undefined or TypeScript types do not recognize it.
3. Set a custom viewport
If the user agent does not matter, configure just the viewport. The important values are width and height in CSS pixels; deviceScaleFactor controls the device scale factor. Set it before navigation.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({
width: 640,
height: 480,
deviceScaleFactor: 1,
});
await page.goto('https://example.com');
await page.screenshot({path: 'custom-viewport.png'});
} finally {
await browser.close();
}
The setViewport() API reference documents additional settings, including isMobile and hasTouch. Changing isMobile or hasTouch can reload a page in some cases. Configure them before navigation when you want predictable setup.
4. Pair a custom viewport with a user agent
When you need both custom dimensions and a specific user agent, create a device object and pass it to page.emulate(). This keeps the paired settings together.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
const customDevice = {
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
viewport: {
width: 390,
height: 844,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
},
};
await page.emulate(customDevice);
await page.goto('https://example.com');
await page.screenshot({path: 'custom-device.png'});
} finally {
await browser.close();
}
The example values are configuration inputs, not a guarantee that a browser will behave exactly like a physical handset. Puppeteer’s documented device emulation covers a user agent and viewport/device metrics; it does not promise complete physical-device fidelity. Use a real-device test when your requirement depends on hardware or behavior outside those emulated settings.
5. Configure screens for headless browser layouts
Page device emulation and browser screen configuration solve different problems. page.emulate() configures the page. Headless screen options configure one or more screens associated with the browser, which is useful for testing layouts that depend on screen arrangements.
Puppeteer documents --screen-info and the dynamic Browser.addScreen() and Browser.removeScreen() methods as headless-only. Without --screen-info or --window-size, the documented headless screen default is 800×600. Browser.screens() is available in both headful and headless modes. Consult the official screen configuration guide for the supported syntax and current API details.
For an ordinary mobile web screenshot, start with page emulation. Configure browser screens only when the test specifically concerns browser-level screen layout.
6. Verify the emulation before capturing
After setup, inspect the page’s viewport and user agent from page JavaScript. This verifies what the page sees; it does not establish full physical-device equivalence.
const browserState = await page.evaluate(() => ({
width: window.innerWidth,
height: window.innerHeight,
userAgent: navigator.userAgent,
touchPoints: navigator.maxTouchPoints,
}));
console.log(browserState);
For stable screenshot output, also decide what page-ready condition you need. For example, wait for a known selector if the content is rendered after initial navigation:
await page.goto('https://example.com', {waitUntil: 'domcontentloaded'});
await page.waitForSelector('[data-test="report-ready"]');
await page.screenshot({path: 'report.png', fullPage: true});
Use a selector that actually indicates the content you need. Waiting for network idle can be unsuitable on pages with long-lived network connections; a page-specific selector is often a more direct readiness condition.
7. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
KnownDevices['...'] is undefined |
The key is misspelled or unavailable in the installed Puppeteer release. | Check the exact catalog key and the docs matching your installed version. Choose a supported profile or define a custom device object. |
| The site still renders a desktop layout | Only a viewport was changed, or the site also relies on its user agent or other behavior. | Use page.emulate() with a named profile or pair a custom user agent and viewport. Inspect window.innerWidth and navigator.userAgent. |
| The page reloads after changing dimensions or touch settings | setViewport() can reload in some cases when isMobile or hasTouch changes. |
Set the final viewport before navigation. If a change must happen mid-session, account for a possible reload and wait for the page again. |
| Viewport seems correct but screenshot dimensions surprise you | CSS viewport dimensions and output image pixels are different concepts; device scale factor affects the relationship. | Check the configured deviceScaleFactor and inspect the actual saved image dimensions for your workflow. |
| Headless screen option has no effect | Browser screen configuration is headless-only, or the task needs page emulation instead. | Confirm the browser mode and use page.emulate() for a phone/tablet page profile. See the screen configuration guide. |
| Screenshot captures a partially rendered page | Navigation completion did not mean application content was ready. | Wait for an application-specific selector or other explicit readiness condition before capture. |
| Types or method names are missing | The installed package and the documentation version may differ. | Check your package version and use its corresponding Puppeteer documentation and API types. |
8. Performance, reliability, and cost
Device emulation is configuration on a page; the main reliability improvement is to set it before navigation and wait for the content your capture depends on. Avoid unnecessary repeated navigation or viewport changes when processing many pages. If you capture multiple pages, reuse a browser where appropriate and close pages and the browser in cleanup paths so failures do not leave processes running.
Local Puppeteer costs depend on where you run Chromium and how you operate that environment; the cited Puppeteer references do not establish a price, throughput benchmark, or fixed runtime. Plan capacity using your own pages and environment, especially if pages are large or require extra waiting. For an API alternative, ScreenshotNeo charges only for clean shots: bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and responses identify the page verdict and billing status in headers.
Or skip the browser setup
If your goal is a website screenshot rather than a Puppeteer device test, ScreenshotNeo can return a screenshot through one GET request. Its API accepts a URL and can return PNG, JPEG, WebP, or PDF. 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,
)
r.raise_for_status()
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 Bun.write('shot.webp', res);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents use its screenshot tools. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000 shots. Sign up for free and get 1,000 screenshots a month with no card.
FAQ
Does Puppeteer device emulation replace testing on a physical phone?
No. The documented API configures user agent and viewport/device metrics. It does not promise to reproduce every physical device property or behavior.
Can I change the device after the page loads?
You can change page settings, but resizing can affect the page, and changes to isMobile or hasTouch can trigger a reload in some cases. Apply the intended configuration before navigation when possible.
Should I use page.setViewport() or page.emulate()?
Use setViewport() when you only need viewport settings. Use emulate() when you want a named device profile or a custom profile pairing a user agent with viewport settings.
Why does Puppeteer list screens separately from devices?
Device emulation configures a page. Screen configuration describes browser-level screens for headless layout scenarios; it is not a substitute for a page’s mobile device profile.


