How to Capture Mobile Website Screenshots with Playwright on an iPhone 15
Capture a website using Playwright’s iPhone 15 emulation, choose viewport or full-page output, and keep visual checks reproducible.
Use Playwright’s iPhone 15 device profile to emulate a mobile browser, then call page.screenshot(). The default capture shows the current viewport; set fullPage: true to capture the full scrollable page. This produces an iPhone 15-profile emulation screenshot, not proof that the page renders identically on physical iPhone hardware. Playwright’s emulation guide describes the simulated device characteristics, and its Page API documents screenshot options.
1. Install Playwright and confirm the device profile
The device registry ships with Playwright. Its available presets and values are version-sensitive, so check that your installed package includes the exact profile before using it. The CLI documentation also shows the named profile as iPhone 15.
npm init -y
npm install --save-dev @playwright/test
npx playwright install
Inspect the profile with Node.js:
node --input-type=module -e "import { devices } from '@playwright/test'; console.log(devices['iPhone 15'])"
If this prints undefined, update your Playwright package to a version that includes the profile, or inspect the registry for the installed version and choose an available profile. Do not assume its viewport, screen dimensions, user agent, or device scale factor; those are supplied by the package version.
2. Configure an iPhone 15 emulation project
Create playwright.config.ts. This configuration applies the device profile to the project, including its mobile browser settings. You can add other projects for desktop or other browser profiles if your test suite needs them.
import { defineConfig, devices } from '@playwright/test';
const iphone15 = devices['iPhone 15'];
if (!iphone15) {
throw new Error('The installed Playwright version has no iPhone 15 device profile.');
}
export default defineConfig({
testDir: './tests',
projects: [
{
name: 'iPhone 15 emulation',
use: { ...iphone15 },
},
],
});
3. Capture a viewport or full page
Create tests/capture.spec.ts. The test navigates to a page, waits for the document to load, and saves a viewport screenshot. Change the URL to your site. The example uses a DOM readiness state rather than waiting for every network request to finish, since analytics or other long-lived requests can keep network-idle waits from resolving.
import { test } from '@playwright/test';
test('capture the mobile page', async ({ page }) => {
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: 'artifacts/iphone-15.png' });
});
Ensure the output directory exists before running the test:
mkdir -p artifacts
npx playwright test tests/capture.spec.ts --project="iPhone 15 emulation"
For the full scrollable document, use fullPage: true. For visual checks, consider capturing a stable, named output path and control animation and caret rendering:
await page.screenshot({
path: 'artifacts/iphone-15-full.png',
fullPage: true,
animations: 'disabled',
caret: 'hide',
});
Playwright also supports capturing a specific element with locator.screenshot():
await page.locator('main').screenshot({ path: 'artifacts/main.png' });
4. Choose screenshot size and scale
A viewport capture is the visible area. A full-page capture extends the screenshot to the page’s full scrollable size. The scale option controls image pixel density: css uses one image pixel per CSS pixel, while device uses device pixels and can create larger files. The documented Page screenshot default is device; set it explicitly when output dimensions matter.
| Choice | Use it when | Trade-off |
|---|---|---|
| Viewport (default) | You want the initial visible screen or a consistent viewport baseline. | Content below the fold is not included. |
fullPage: true |
You need the entire scrollable document in one image. | Tall pages produce large images and can expose layout issues that only occur at full-page capture size. |
scale: 'css' |
You want output dimensions tied to CSS pixels and generally smaller files. | It does not preserve device-pixel density. |
scale: 'device' |
You want device-pixel density. | Output can be considerably larger. |
await page.screenshot({
path: 'artifacts/iphone-15-css-pixels.png',
fullPage: true,
scale: 'css',
type: 'png',
});
Supported image formats include PNG, JPEG, and WebP. If you set type: 'jpeg', you can tune quality from 0 to 100; quality applies to JPEG. When you need the bytes instead of writing a file, omit path and use the returned buffer.
5. Make the capture representative and repeatable
Device emulation simulates browser characteristics such as viewport, screen size, user agent, and touch support. It is useful for responsive layout checks, but it does not establish physical-device equivalence. If a bug depends on real iOS or Safari behavior, arrange a separate real-device check.
Choose waits based on what the page needs. For an app that renders after navigation, wait for a meaningful selector rather than relying only on a fixed delay:
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.locator('main h1').waitFor({ state: 'visible' });
await page.screenshot({ path: 'artifacts/iphone-15.png' });
For content that appears after a known short transition, a bounded delay can be appropriate, but it makes capture slower and may still be too short on a busy run:
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(500);
await page.screenshot({ path: 'artifacts/iphone-15.png' });
Before capturing, you can emulate a reduced-motion preference in your project configuration or application, disable animations in the screenshot call, and ensure the page has reached the state you intend to compare. Keep the Playwright version, selected device profile, browser engine and version, operating system, headless mode, screenshot extent, and scale consistent across baseline generation and comparison. Playwright’s visual comparison guidance lists host OS, browser version, settings, hardware, power source, and headless mode among sources of rendering differences.
6. Run the capture with the CLI
For a one-off CLI session, Playwright’s CLI documentation includes a named device option. Check the CLI version and help for the exact command available in your installation:
npx playwright-cli open --device="iPhone 15" https://example.com
For repeatable project captures or automated visual checks, use the Test configuration and screenshot code above. The CLI and Test/library workflows are separate entry points; a CLI session is not a substitute for a configured test project.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
devices['iPhone 15'] is undefined |
The installed Playwright version does not include that named preset. | Check the installed package version and registry. Upgrade if appropriate, or use a preset present in that version. Avoid copying unverified numeric values from another release. |
| Browser executable is missing | The browser binaries have not been installed for the package. | Run npx playwright install and retry. |
| The screenshot is blank or incomplete | The app has not rendered its meaningful content when capture begins. | Wait for a visible page-specific selector or application-ready condition before capturing. Check navigation errors and the page state. |
| The test hangs while waiting for network idle | Analytics, polling, streaming, or other requests keep the network active. | Use domcontentloaded or load for navigation, then wait for the specific content needed in the screenshot. |
| Images or fonts are missing | Resources are still loading, blocked, or unavailable to the browser. | Check the network and console output, verify the page can access those resources, and wait for the relevant content before capture. |
| The page differs from a physical iPhone | Device emulation is a simulated profile; it is not a real iPhone capture. | Use emulation for responsive checks. Validate issues that depend on actual iOS or Safari behavior on physical hardware. |
| Visual snapshots vary between runs or machines | Rendering can vary with OS, browser version, settings, hardware, power source, or headless mode. | Generate and compare snapshots in a stable environment with the same browser, project, capture extent, and scale. |
| The full-page image is unexpectedly large | A tall page and device-pixel scaling multiply the image dimensions. | Use viewport capture when that is the intended check, or set scale: 'css' when CSS-pixel output is appropriate. |
8. Performance, reliability, and cost
Playwright screenshot generation runs a browser, so runtime and resource use depend on the page, browser, machine, and capture size. Full-page and device-scale images can require more memory and storage than viewport or CSS-scale images. If captures run in CI, keep the environment stable and retain only the artifacts your review or comparison workflow needs.
Use condition-based waits for reliability. A fixed sleep adds the same delay whether the page is ready early or late; a selector wait ties capture to the page state you need. Set navigation and test timeouts to fit the application rather than waiting indefinitely. Keep screenshots and comparisons in the same environment because identical emulation settings alone do not guarantee pixel-identical output across machines.
Playwright is open-source software; this browser workflow does not charge per screenshot through Playwright itself. Your compute, CI, storage, and any separately used browser infrastructure may have costs. No physical product is required for this workflow.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Its one-call endpoint returns an image or PDF, and its API documentation describes the available options. Example using the required target URL:
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 Bun.write('shot.webp', res);
- Cookie banners, popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks, blank pages, and failed loads are never billed. Response headers report the page verdict and billing status.
- An MCP server lets AI agents, including Claude and Cursor, take screenshots.
- 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000.
Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.
FAQ
Does this capture a real iPhone 15 screenshot?
No. It captures a browser using Playwright’s iPhone 15 emulation profile. Use a physical device when you need to confirm behavior on actual hardware.
How do I capture only the visible mobile screen?
Call page.screenshot() without fullPage: true. That captures the current viewport.
Can I use this for visual regression tests?
Yes. Keep the browser, emulation profile, operating environment, capture settings, and page state consistent when creating and comparing snapshots.
Should I use WebKit to represent iPhone Safari?
A mobile emulation profile and a browser engine selection are distinct configuration choices. Choose the browser project that matches what you need to test, and describe the result as emulation unless it was captured on actual iPhone hardware.


