Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use Playwright’s built-in device descriptor when you want a realistic named phone, then capture with fullPage: true. A descriptor configures more than width: it sets user agent, viewport and screen behavior, touch support, mobile meta-viewport handling, and device scale factor. For an unlisted breakpoint, spread the closest descriptor and override its values after the spread.

1. Choose a device profile

Playwright emulates browser behavior rather than operating a physical handset. That distinction matters when you interpret layout bugs or pixel differences: the result comes from a desktop browser engine running with mobile settings, not from hardware sensors, firmware, or a handset GPU.

Use a built-in descriptor for a named phone

The devices registry includes profiles such as iPhone 13 and Pixel 9 Pro. A descriptor is the most reliable baseline because its related values stay consistent. It supplies a user agent, screen size, viewport, touch capability, mobile behavior, and a device scale factor.

Use a custom profile for a breakpoint

If your design target is, for example, a 390 × 844 CSS-pixel breakpoint rather than a particular handset, start from a nearby profile and override the values you need. Put overrides after ...devices[...]; otherwise the descriptor’s viewport and related settings can replace your custom values.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

2. Capture a full-page iPhone screenshot

Install Playwright, create the context before navigation, and then take the screenshot:

import { chromium, devices } from 'playwright';

const browser = await chromium.launch();
const context = await browser.newContext({
  ...devices['iPhone 13'],
});
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({ path: 'iphone-13.png', fullPage: true });
await browser.close();

fullPage: true makes Playwright capture the complete scrollable document instead of only the initial viewport. It does not change the emulated phone; it changes the document length included in the image.

Wait for content that appears after navigation

Network idle is useful for pages that load data shortly after the initial response, but it is not a guarantee that every image or animation has finished. For deterministic captures, wait for a meaningful selector and, where appropriate, disable animations in a test-only stylesheet:

await page.goto('https://example.com');
await page.locator('[data-testid="article"]').waitFor();
await page.addStyleTag({
  content: `*, *::before, *::after {
    animation: none !important;
    transition: none !important;
  }`,
});
await page.screenshot({ path: 'article-mobile.png', fullPage: true });

3. Build a custom mobile configuration

This Playwright Test configuration targets a controlled breakpoint while retaining the other mobile characteristics from a desktop browser descriptor:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  projects: [{
    name: 'custom-mobile',
    use: {
      ...devices['Desktop Chrome'],
      viewport: { width: 390, height: 844 },
      isMobile: true,
      hasTouch: true,
      userAgent: 'custom mobile user agent',
      deviceScaleFactor: 3,
    },
  }],
});

In this example, isMobile tells the browser to apply mobile meta-viewport behavior and enables touch events. hasTouch advertises touch support. The custom user agent can activate server-side mobile variants, while deviceScaleFactor controls the emulated pixel density.

Override order is significant

The object spread is evaluated from left to right. A value written before ...devices['iPhone 13'] can be overwritten by that descriptor. Write every intentional override after the spread and keep the remaining descriptor values unchanged unless your test has a specific reason to alter them.

4. Control pixel density with scale

There are two related settings:

  • deviceScaleFactor: the emulated device’s density, commonly 2 or 3 in mobile presets.
  • Screenshot scale: the output policy. scale: 'device' produces one image pixel per device pixel, so a high-density profile creates a larger file. The default CSS scale produces a more compact, stable artifact.
// Compact CSS-pixel artifact
await page.screenshot({ path: 'mobile-css.png', fullPage: true });

// Device-pixel output for pixel-level rendering checks
await page.screenshot({
  path: 'mobile-device-pixels.png',
  fullPage: true,
  scale: 'device',
});

Choose CSS scale for most visual-regression baselines when file size and consistent dimensions matter. Choose device scale when the purpose is to inspect or deliver the pixels a high-DPI display would use. Record the choice in your test configuration so later comparisons do not silently mix densities.

5. A reusable screenshot function

Keeping context creation and capture in one function makes it easier to run the same URL against several profiles:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { chromium, devices } from 'playwright';

async function capture(name, url, descriptor) {
  const browser = await chromium.launch();
  try {
    const context = await browser.newContext({ ...descriptor });
    const page = await context.newPage();
    await page.goto(url, { waitUntil: 'networkidle' });
    await page.screenshot({
      path: `${name}.png`,
      fullPage: true,
      scale: 'css',
    });
    await context.close();
  } finally {
    await browser.close();
  }
}

await capture('iphone-13-home', 'https://example.com', devices['iPhone 13']);
await capture('pixel-9-pro-home', 'https://example.com', devices['Pixel 9 Pro']);

Use the same browser engine and Playwright version for visual comparisons. Changing either can legitimately alter font rasterization, form controls, media-query behavior, or layout details.

6. Preset versus custom profile

Decision Built-in preset Custom profile
Primary goal Reproduce a named phone Exercise an exact breakpoint or special server condition
Viewport Registry value Your explicit width and height
User agent Descriptor value Descriptor value or an intentional custom string
Touch and mobile behavior Preset values Set hasTouch and isMobile deliberately
Density Preset deviceScaleFactor Your chosen factor
Interpretation Closest registry simulation Breakpoint-focused browser emulation, not a hardware replica

7. Common failures and fixes

The page still looks like desktop

Check that the context was created with the descriptor before newPage() and before goto(). If you are using a custom profile, confirm that isMobile, viewport, and user agent are set after the spread. A page may also serve desktop markup based on cookies or server-side feature flags; clear the context or set the required state explicitly.

The width is not the value you configured

Inspect the final context options rather than assuming the override won. A later spread or project setting can replace the viewport. Keep one source of truth and place custom values after the descriptor.

Touch interactions do not work

Set hasTouch: true and use touch-capable page interactions where appropriate. isMobile also affects mobile meta-viewport handling. Neither setting proves that a physical touchscreen or mobile GPU is present.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The screenshot is unexpectedly huge

High deviceScaleFactor combined with scale: 'device' multiplies output pixels. Use the default CSS scale for a compact artifact, or lower the factor when density is not part of the test.

Lazy images are missing from a full-page shot

Full-page capture does not guarantee that an application’s lazy-loader has requested every asset. Scroll through the page or trigger the application’s loading condition before capture, then wait for the image selector or its network request.

The result changes between runs

Wait for a stable selector, freeze animations, use consistent test data, and keep browser and Playwright versions fixed. Ads, rotating content, current-time labels, and fonts loaded from different sources can all produce legitimate differences.

A page redirects or shows the wrong locale

Mobile user agents, cookies, timezone, and geolocation can affect redirects and content. Create a fresh context for each case and configure the locale-related state your application expects before navigation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

8. Performance, reliability, and artifact choices

  • Context cost: launching one browser per screenshot is simple but slower. For a batch, launch once and create isolated contexts per device.
  • Full-page cost: long documents require more layout, memory, and image encoding. Capture only the viewport when the question is responsive chrome; use full-page only when document completeness matters.
  • Density trade-off: device-pixel output increases dimensions and storage. CSS-scale output is usually easier to diff and archive.
  • Reproducibility: pin the Playwright version, browser binaries, viewport, descriptor, scale policy, fonts, and test data.
  • Security: treat URLs, cookies, authorization headers, and captured files as sensitive. Do not print secrets in test logs or commit screenshots containing private data.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server when you want a clean capture without maintaining Playwright infrastructure. Its request can return PNG, JPEG, WebP, or PDF; it accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Each step can be disabled.

Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. The MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

For a one-call capture, see the ScreenshotNeo documentation:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The same request in Python:

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)

And in Node.js:

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 includes full-page capture, device presets and custom viewports, retina scale, element selectors, dark mode, custom CSS and JavaScript, click and wait conditions, request or resource blocking, headers, cookies, user agents, timezone and geolocation, transparent backgrounds, resizing, chosen-TTL caching, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. Its parameter names are compatible with those used by other screenshot APIs, which can simplify a migration.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; Growth is $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to start with the 1,000-shot allowance.

Frequently Asked Questions

Does Playwright emulation equal testing on a real iPhone or Android phone?

No. It emulates browser settings and behavior—such as viewport, user agent, touch, meta-viewport handling, and density—inside a browser engine. Hardware-specific rendering and device firmware are not reproduced.

Should I use fullPage for responsive breakpoints?

Use it when you need the entire document in one image. For checking the layout visible on initial load, omit it so the screenshot represents only the configured viewport.

Why would two mobile presets with similar widths produce different layouts?

Their user agents, mobile flags, touch settings, scale factors, and other descriptor values can differ. Those settings influence both server responses and client-side media behavior.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.