Playwright vs Puppeteer for Mobile Website Screenshots
Compare Playwright and Puppeteer for mobile website screenshots, with runnable examples, visual regression guidance, and advice on when emulation is not enough.
Short answer: Both Playwright and Puppeteer can emulate mobile device profiles and capture website screenshots. Choose Playwright when screenshots are part of automated visual regression tests: Playwright Test includes screenshot assertions and baseline comparison. Choose Puppeteer for a straightforward page or element capture workflow, especially in a Chromium-focused setup. Neither choice makes an emulated browser identical to a physical phone.
This guide shows runnable Node.js examples for both tools, explains viewport and full-page capture, and covers repeatability, troubleshooting, and when to test on real hardware. If you want a screenshot without maintaining browser setup, ScreenshotNeo is an API and MCP server for website screenshots.
1. Playwright vs Puppeteer at a glance
| Need | Better starting point | Why |
|---|---|---|
| Visual regression tests with checked-in baselines | Playwright | Playwright Test provides screenshot assertions and baseline comparison. |
| A script that saves a page or element image | Either | Both expose screenshot capture; Puppeteer documents page and element screenshots explicitly. |
| Device-profile emulation | Either | Playwright provides device descriptors and context settings; Puppeteer provides known devices and Page.emulate(). |
| Multiple browser engines | Playwright | Playwright documents Chromium, WebKit, and Firefox support. Confirm that the target configuration matches the browser you need to validate. |
| Actual OS, hardware, or browser integration behavior | Real-device testing | A device profile simulates selected browser characteristics; it does not prove behavior on physical hardware. |
This is a workflow comparison, not a claim that either library inherently produces more accurate pixels. Screenshot output depends on the browser and host environment as well as the page.
2. What mobile emulation does and does not tell you
A mobile profile can apply settings such as viewport, screen dimensions, user agent, and touch behavior. Playwright’s emulation documentation describes using device profiles and configuring browser context parameters. Puppeteer’s Page.emulate() applies a device’s metrics and user agent; its documentation recommends emulating before navigation because some sites do not expect phone metrics to change after loading.
These settings help exercise responsive layouts and mobile-specific page behavior. They do not replicate every property of a real phone: operating system integration, hardware, browser implementation, and device-specific input behavior may differ. If a defect depends on one of those, arrange a test on the relevant physical device or platform.
3. Playwright: emulate a phone and capture a screenshot
Install Playwright and its browser binaries in the project:
npm install --save-dev playwright
npx playwright install chromium
Save this as mobile-shot.mjs. It uses the built-in iPhone 13 device descriptor, launches Chromium, applies the device settings before navigation, and saves a full-page PNG.
import { chromium, devices } from 'playwright';
const targetUrl = process.argv[2] ?? 'https://example.com';
const device = devices['iPhone 13'];
const browser = await chromium.launch({ headless: true });
try {
const context = await browser.newContext({ ...device });
const page = await context.newPage();
await page.goto(targetUrl, { waitUntil: 'networkidle', timeout: 45_000 });
await page.screenshot({ path: 'playwright-mobile.png', fullPage: true });
await context.close();
} finally {
await browser.close();
}
Run it with:
node mobile-shot.mjs https://example.com
The device registry’s available names can change with Playwright releases. Inspect the installed package’s devices registry and choose the profile that matches your test target. You can also set context properties directly when you need a custom viewport or device behavior:
const context = await browser.newContext({
viewport: { width: 390, height: 844 },
screen: { width: 390, height: 844 },
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
userAgent: 'YOUR_TEST_USER_AGENT',
});
Use a profile or custom settings that match the question being tested. Avoid copying a user agent alone and assuming that it makes a desktop context behave like a phone.
Playwright visual regression assertions
For a baseline workflow, use Playwright Test. Screenshot assertions wait for consecutive screenshots to stabilize before comparing against the reference; screenshot assertions require the Playwright test runner.
npm install --save-dev @playwright/test
npx playwright install chromium
Create mobile.spec.js:
const { test, devices, expect } = require('@playwright/test');
test.use({ ...devices['iPhone 13'] });
test('mobile landing page matches its screenshot baseline', async ({ page }) => {
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await expect(page).toHaveScreenshot('landing-mobile.png', { fullPage: true });
});
Run the test and, when an intentional visual change needs a new reference, update baselines using the test runner’s snapshot update option. Review the resulting images before committing them. Generate and compare baselines in the same browser and host environment whenever possible.
4. Puppeteer: emulate a phone and capture a screenshot
Install Puppeteer, which downloads a compatible browser as part of its standard installation:
npm install puppeteer
Save as puppeteer-mobile.mjs. As with Playwright, apply the mobile profile before opening the page.
import puppeteer from 'puppeteer';
const targetUrl = process.argv[2] ?? 'https://example.com';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.emulate(puppeteer.KnownDevices['iPhone 13']);
await page.goto(targetUrl, { waitUntil: 'networkidle2', timeout: 45_000 });
await page.screenshot({ path: 'puppeteer-mobile.png', fullPage: true });
} finally {
await browser.close();
}
Run it with:
node puppeteer-mobile.mjs https://example.com
For an element screenshot, wait for the target to exist and capture its bounding element:
const element = await page.waitForSelector('main article', { timeout: 10_000 });
if (!element) throw new Error('Target element was not found');
await element.screenshot({ path: 'article-mobile.png' });
To create a custom emulation instead of selecting a known device, set the user agent and viewport before navigation. Puppeteer’s Page.emulate() is a shortcut for setting these device properties; use the API documentation for the installed version when configuring additional mobile metrics.
await page.setUserAgent('YOUR_TEST_USER_AGENT');
await page.setViewport({
width: 390,
height: 844,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
});
await page.goto(targetUrl);
5. Choose the right capture scope and wait condition
Viewport screenshot
Omit fullPage in Playwright or Puppeteer to capture the visible viewport. This is useful for checking the initial mobile fold, navigation, and above-the-fold layout.
Full-page screenshot
Set fullPage: true to capture the page beyond the viewport. This is useful for long-page review, but lazy-loaded content may not appear unless scrolling or another interaction causes it to load. If the bottom of the image is blank or incomplete, explicitly scroll through the page or wait for the content you need before capturing.
Element screenshot
Capture a component when the question concerns one area, such as a menu, product card, or article. Wait for a stable selector, then capture that element. If the element is inside a nested frame or shadow tree, use the relevant frame or locator APIs rather than assuming a top-level CSS query can reach it.
Wait for the page you mean to test
networkidle can be a useful starting point, but pages with analytics, live updates, streaming connections, or long polling may never become idle. Prefer waiting for a specific selector or application-ready signal when available. If a page has animations or rotating content, disable or stabilize them in the test environment to reduce irrelevant image differences.
6. Making mobile screenshots repeatable
- Pin the Playwright or Puppeteer version and use the browser version installed with that runtime.
- Keep the host OS or container image consistent between baseline creation and comparison.
- Use the same device profile, viewport, device scale factor, color scheme, locale, and screenshot options for every run.
- Wait for a page-specific ready condition and stabilize animations, clocks, randomized content, and network-dependent data where the test permits.
- Review screenshot diffs in context. A changed font rasterizer or browser build can alter pixels even when application code is unchanged.
- Regenerate baselines only for reviewed, intended changes; do not accept every diff automatically.
Playwright’s visual comparison documentation specifically cautions that rendering may vary by operating system, browser version, settings, hardware, power source, and headless mode. Keep the environment that produced a baseline consistent with the environment that checks it.
7. When browser emulation is insufficient
Use emulation for repeatable responsive checks and many mobile layout tests. Add real-device coverage when the outcome depends on a physical OS, hardware, browser-specific implementation, or integration that the emulated context does not exercise.
Playwright documents Android automation separately and labels that support experimental. Its documented setup includes ADB and an Android device or Android Virtual Device, with limitations including no raw USB operation and incomplete test coverage. Check the current official documentation before adopting that route, since requirements can change. A browser profile configured to resemble a mobile phone is not a substitute for this platform-level test.
8. Troubleshooting
| Symptom | Likely cause | What to try |
|---|---|---|
| Page looks like desktop despite a mobile user agent | Emulation was applied after navigation, or only the user agent was changed. | Apply the device profile or viewport and mobile settings before navigating. |
| Screenshot times out waiting for navigation | The site keeps network requests open or does not reach the selected lifecycle state. | Use a different navigation wait condition, then wait for a page-specific selector or ready signal. |
| Full-page capture omits images lower down | Images or sections are lazy-loaded only after scrolling. | Scroll through the page and wait for the relevant images or content before capture. |
| Visual test is flaky across machines | Browser, OS, fonts, hardware, or headless rendering differs. | Run comparisons in the same pinned browser and host image used to create the baseline. |
| Element screenshot fails or is empty | The selector did not resolve, the element is hidden, or it is outside the queried document context. | Wait for the selector, verify visibility and dimensions, and query the correct frame or component context. |
| Playwright cannot find its browser executable | The browser binaries have not been installed in that environment. | Run npx playwright install chromium during setup, including in the CI image. |
| Screenshot changes on every run | Dynamic content, animations, timestamps, ads, or randomized data are visible. | Use deterministic fixtures, wait for stable content, and disable or mask irrelevant dynamic regions in the test setup. |
| Local screenshot differs from CI | Different runtime, browser, host OS, fonts, or launch mode. | Compare in the same container or CI image and keep runtime and browser versions aligned. |
9. Performance, reliability, and cost
Browser automation has setup and runtime costs: a browser must be installed and launched, pages consume memory, and each capture waits on navigation and page readiness. Reuse a browser process for batches of captures when appropriate, while giving each independent job its own page or context and closing resources when finished. Parallelism can reduce wall-clock time but increases CPU and memory pressure; tune it to the host rather than assuming more workers always improve throughput.
For reliable jobs, use explicit timeouts, close the browser in a finally block, and make failures visible to the calling process. A successful navigation does not guarantee that the intended content rendered: check for an expected selector or other page-specific signal before saving the image. Keep browser dependencies installed in deployment and CI environments.
Playwright and Puppeteer are open-source automation libraries; the operational cost of a capture workflow depends on the compute environment, browser execution, storage, and engineering time. This guide makes no throughput or cost benchmark claim. For a managed API, ScreenshotNeo’s published plans are free for 1,000 shots a month with no card, then $5 for 3,000, $15 for 15,000, $39 for 60,000, $99 for 250,000, and $249 for 1,000,000; yearly billing gives two months free. Every feature is on every plan.
10. Or skip the browser setup
If you need a mobile website screenshot without installing and maintaining a browser runtime, use ScreenshotNeo’s screenshot API. It accepts one GET request and returns an image or PDF. The example below saves a WebP response; see the ScreenshotNeo API documentation for the full parameter list and options.
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()
with open("shot.webp", "wb") as image:
image.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);
The request can be extended with device presets or a custom viewport, full-page capture, output format, element selector, dark mode, and other documented options. ScreenshotNeo also supports cookie and consent-banner handling: it accepts the consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
Sign up free for 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.
11. FAQ
Are Playwright screenshots more accurate than Puppeteer screenshots?
The documentation supports comparing their workflows and capabilities, not a general accuracy ranking. Rendering depends on browser and environment settings.
Can I use a mobile screenshot as proof that a page works on iPhone?
It proves how the selected emulation configuration rendered the page. Test on the relevant physical device when the behavior depends on real iOS or device integration.
Which should I use for a one-off screenshot?
Either works. Pick the library already used in your project; if neither is installed and you want a managed request instead, try ScreenshotNeo.
Can both tools capture a whole long page?
Yes. Both examples use full-page capture. Trigger lazy-loaded content before capturing if it must appear in the output.
