ScreenshotNeo

BlogHow-to

How to Capture Mobile Screenshots with Playwright

Use Playwright device emulation to capture repeatable mobile screenshots, tune scale and full-page behavior, and troubleshoot common rendering issues.

By the ScreenshotNeo team1 October 20266 min read

Direct answer: configure a Playwright mobile device preset, create a browser context from that preset, navigate to the page, and call page.screenshot(). The preset supplies mobile-like viewport, user-agent, screen, and touch settings. It emulates browser conditions; it does not prove that the page was rendered on a physical phone.

const { chromium, devices } = require('playwright');

(async () => {
  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: 'mobile.png' });

  await browser.close();
})();

Install Playwright first with npm install playwright. Browser binaries may need to be installed with npx playwright install.

1. Choose emulation or a connected Android device

For responsive layout reviews and repeatable browser tests, use Playwright’s device emulation. Official presets combine settings such as viewport, user agent, screen size, and touch support. They are a coherent mobile-browser configuration, not a physical-device guarantee. See the Playwright emulation guide.

Requirement Recommended workflow What it captures
Responsive web layout Device preset in Chromium, Firefox, or WebKit A browser page under mobile-like parameters
Actual Android screen or WebView Playwright Android automation A connected Android device or AVD screen, Chrome, or WebView

The Android route requires an Android device or AVD, authenticated ADB, and Chrome 87 or newer. Playwright documents this support as experimental, with limitations including no raw USB support and incomplete test coverage. The device must be awake for screenshots. Read the Android API documentation before choosing it.

2. Capture a mobile viewport in a standalone script

Use an official preset

const { chromium, devices } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext({
    ...devices['iPhone 13'],
  });
  const page = await context.newPage();

  await page.goto('https://example.com');
  await page.screenshot({ path: 'mobile-viewport.png' });
  await browser.close();
})();

Omitting fullPage captures the visible viewport. Add an explicit navigation wait when the page has important asynchronous content.

Override preset values

Place overrides after the spread so they take precedence:

const context = await browser.newContext({
  ...devices['iPhone 13'],
  viewport: { width: 390, height: 844 },
  deviceScaleFactor: 2,
  locale: 'en-US',
  timezoneId: 'America/New_York',
});

A custom viewport is useful when your design targets a specific CSS width. Keep the user agent, touch behavior, and viewport coherent unless you are deliberately testing an unusual combination.

3. Capture the entire mobile page

await page.screenshot({
  path: 'mobile-full.png',
  fullPage: true,
});

fullPage: true captures the full scrollable document instead of only the current viewport. Very long pages can create very tall files and may expose layout that only appears after scrolling.

4. Control image format, scale, and targets

await page.screenshot({
  path: 'mobile.webp',
  type: 'webp',
  quality: 82,
  scale: 'css',
});
  • Format: PNG is the default. JPEG and WebP are available.
  • Quality: applies to JPEG and WebP, not PNG.
  • Scale: 'css' produces one image pixel per CSS pixel; 'device' produces device-pixel output and can be much larger on high-density emulation.
  • Element: capture one element with a locator.
await page.locator('header nav').screenshot({
  path: 'mobile-nav.png',
});

For a rectangular region, use the screenshot API’s clip option:

await page.screenshot({
  path: 'region.png',
  clip: { x: 0, y: 120, width: 390, height: 300 },
});

Choose CSS scale for compact diffs and documentation assets. Choose device scale when downstream processing needs density-sized pixels.

5. Wait for the page before capturing

Navigation completion does not guarantee that fonts, images, animations, or application data are ready. Combine a navigation policy with an explicit readiness condition:

await page.goto('https://example.com/products', {
  waitUntil: 'domcontentloaded',
});
await page.locator('[data-product-grid]').waitFor({ state: 'visible' });
await page.evaluate(() => document.fonts.ready);
await page.screenshot({ path: 'products.png', fullPage: true });

Use a bounded delay only when the application has no reliable selector:

await page.waitForTimeout(1000);
await page.screenshot({ path: 'after-delay.png' });

For deterministic output, disable or freeze animations in your test stylesheet and avoid capturing while a carousel is moving.

6. Configure Playwright Test projects

In Playwright Test, apply a device preset through the project’s use configuration. The spread must come before any overrides.

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  projects: [
    {
      name: 'Mobile Safari',
      use: {
        ...devices['iPhone 13'],
        baseURL: 'https://example.com',
      },
    },
  ],
});
import { test } from '@playwright/test';

test('mobile page', async ({ page }) => {
  await page.goto('/');
  await page.screenshot({ path: 'artifacts/mobile.png', fullPage: true });
});

Playwright Test can also manage screenshots automatically. Set use.screenshot to 'on', 'only-on-failure', or 'on-first-failure'; the default is 'off'. Automatic artifacts are convenient for test diagnostics, while an explicit call gives precise timing and filenames. See the TestOptions API and use options.

7. Complete runnable examples in other languages

cURL

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

Python

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)

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}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = require('node:fs');
fs.writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));

See the ScreenshotNeo API documentation for request options and response headers.

8. Or skip the browser setup

ScreenshotNeo provides a single GET request for PNG, JPEG, WebP, or PDF captures. It can accept a mobile viewport or one of its 12 device presets, plus full-page capture, retina scale, dark mode, custom CSS and JavaScript, selector waits, lazy-image loading, cookies, headers, user agents, geolocation, and more.

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

Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed; response headers identify the page verdict and whether the request was billed. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots.

Create a free ScreenshotNeo account.

9. Troubleshooting

Symptom Likely cause Fix
Screenshot has desktop layout Preset was not spread into the context, or a later viewport override is too wide Use ...devices['iPhone 13'] and verify the final viewport dimensions.
Content is missing Capture ran before application data, fonts, or images finished loading Wait for a stable selector, document.fonts.ready, or a bounded delay.
Full-page image is unexpectedly tall The document contains long content or expanding lazy sections Capture the viewport, hide nonessential regions, or ensure lazy content is intentionally loaded.
Text looks blurry or file is huge Device-pixel scale multiplies output dimensions Use scale: 'css' for compact output, or keep device scale and resize downstream.
Element screenshot fails Selector does not match, element is hidden, or it moves during capture Use a stable locator, wait for visibility, and disable transitions.
Navigation times out Origin is slow, blocked, or waiting for an event that never occurs Set a suitable timeout, use domcontentloaded, then wait for the exact content you need.
Android connection fails ADB authentication, device sleep, or unsupported setup Confirm the device or AVD is awake, ADB is authenticated, and Chrome meets the documented requirement.

10. Performance, reliability, and cost considerations

  • Performance: Reuse a browser process and create contexts per device scenario. Avoid unnecessary full-page and device-scale captures when a viewport image meets the requirement.
  • Reliability: Prefer stable selectors over arbitrary sleeps, set explicit navigation and assertion timeouts, and save artifacts on failure. Keep emulation settings in source control so runs are repeatable.
  • Visual consistency: Fix locale, timezone, color scheme, viewport, and animation state. Dynamic ads, clocks, and personalized content can still change between runs.
  • Cost: Self-hosted Playwright consumes your own compute and browser maintenance time. A hosted API removes browser setup and can make billing behavior explicit; ScreenshotNeo bills only clean shots and reports verdict and billing headers.
  • Evidence boundary: An emulated screenshot demonstrates how a page responds to configured browser parameters. It does not establish rendering on a specific physical handset.

11. FAQ

Does Playwright emulate an actual iPhone?

No. The preset emulates browser-facing characteristics such as viewport, user agent, screen, and touch. Use the Android workflow when a connected Android device or WebView is required.

How do I capture only what is visible?

Call page.screenshot({ path: 'mobile.png' }) without fullPage.

How do I capture a full mobile page?

Set fullPage: true. For a component, use locator.screenshot().

Which scale should I use for visual regression?

Use scale: 'css' when stable, compact CSS-pixel dimensions are the goal. Use 'device' when density-sized output is required.

Can Playwright capture PDFs?

Playwright’s screenshot API creates images. Use a PDF-specific workflow when the deliverable is a PDF; ScreenshotNeo’s API also supports PDF output.