ScreenshotNeo

BlogHow-to

Website Mobile Screenshot Generator

Generate accurate mobile website screenshots with Playwright or an API. Learn viewport sizing, full-page capture, waits, formats, troubleshooting, and automation.

By the ScreenshotNeo team29 September 20268 min read

Website Mobile Screenshot Generator

Direct answer: A website mobile screenshot generator opens a public URL at a phone-sized browser viewport and exports the rendered page as an image. For repeatable results, use Playwright or a screenshot API. Set a mobile viewport such as 393 × 852 CSS pixels, wait for fonts and lazy content, then capture either the visible viewport or the complete scrollable page.

A mobile screenshot is useful for responsive QA, documentation, bug reports, release notes, social previews, and visual regression checks. It is an emulation of browser conditions, however. A preset does not prove that a physical iPhone or Android phone will render identically, and a viewport screenshot does not include content below the fold.

What a mobile screenshot generator does

The basic workflow is:

A mobile screenshot request becomes a rendered image after viewport and wait settings are applied.
A mobile screenshot request becomes a rendered image after viewport and wait settings are applied.
  1. Provide a public HTTPS URL.
  2. Choose a named phone preset or enter width and height manually.
  3. Render the page with JavaScript, CSS, fonts, images, and responsive breakpoints enabled.
  4. Wait for a selector, a delay, or network activity to settle.
  5. Capture the viewport, an element, or the full scrollable page.
  6. Save PNG, JPEG, or WebP output at the requested scale.

For example, an iPhone 15 Pro style viewport can be represented as 393 × 852 CSS pixels. That describes the browser viewport; it does not switch the engine to Safari or simulate a physical device. Device-aware services may also change the user agent, device-pixel ratio, touch signals, timezone, or location, so select the signals your site actually uses.

Choose the right capture scope

Scope Use it for Common issue
Viewport Above-the-fold QA, app states, social previews Lower content is intentionally absent
Element A component, chart, product card, or error state Selector may be missing or hidden
Full page Documentation, audits, complete landing pages Lazy content may not load before stitching

Full-page mode scrolls through the document and stitches the result. It can expose layout problems that a viewport capture misses, but sticky headers, animated elements, infinite lists, and lazy images need special handling.

Capture a mobile screenshot with Playwright

Playwright is a good choice when your team already runs browser tests or needs captures inside CI. Install it with Node.js:

npm install playwright
npx playwright install chromium

Create mobile-shot.mjs:

import { chromium, devices } from 'playwright';

const browser = await chromium.launch();
const context = await browser.newContext({
  ...devices['iPhone 15 Pro'],
  colorScheme: 'light',
  locale: 'en-US',
  timezoneId: 'UTC'
});
const page = await context.newPage();

await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.evaluate(() => document.fonts.ready);
await page.waitForLoadState('networkidle');
await page.screenshot({
  path: 'mobile-viewport.png',
  type: 'png',
  fullPage: false,
  scale: 'css'
});

await browser.close();

Run it with node mobile-shot.mjs. The device descriptor supplies a phone-like viewport, device scale factor, user agent, and touch settings. If you need exact dimensions instead of a named preset, replace the context options:

const context = await browser.newContext({
  viewport: { width: 393, height: 852 },
  deviceScaleFactor: 3,
  isMobile: true,
  hasTouch: true,
  userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 Version/17.0 Mobile Safari/604.1'
});

Viewport, full-page, and element examples

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

// One component
await page.locator('[data-testid="pricing-card"]').screenshot({
  path: 'pricing-card.webp',
  type: 'webp',
  quality: 85
});

Use scale: 'css' for predictable CSS-pixel output. Use scale: 'device' when you want device-pixel-density output and a larger raster image. JPEG and WebP reduce file size; PNG preserves sharp text and transparency. JPEG quality is configurable, while PNG is lossless.

Wait for responsive content and lazy images

A successful navigation does not mean the page is visually ready. Add a selector wait when a hero, chart, or app shell signals readiness:

await page.goto('https://example.com/dashboard', { waitUntil: 'domcontentloaded' });
await page.locator('[data-ready="true"]').waitFor({ state: 'visible', timeout: 30000 });
await page.waitForTimeout(500);
await page.screenshot({ path: 'dashboard.png', fullPage: true });

For lazy-loaded images, scroll in increments before the final capture:

await page.evaluate(async () => {
  for (let y = 0; y < document.body.scrollHeight; y += 700) {
    window.scrollTo(0, y);
    await new Promise(resolve => setTimeout(resolve, 100));
  }
  window.scrollTo(0, 0);
});
await page.waitForTimeout(300);
await page.screenshot({ path: 'lazy-full.png', fullPage: true });

Options that affect the result

Option What it changes When to use it
Width and height Responsive breakpoint and visible area Match a design spec or regression baseline
Device scale factor Raster density High-resolution exports or smaller CSS output
User agent Server-side device detection Sites serving different markup to mobile browsers
Color scheme Light or dark media query Theme screenshots
Locale and timezone Dates, numbers, language, regional content Localized QA
Geolocation Location-dependent experiences Maps, regional banners, store finders
Headers and cookies Authentication and experiment assignment Private staging pages or deterministic variants
Reduced motion Animation behavior Stable visual baselines

Disable animations for regression captures with an injected stylesheet:

await page.addStyleTag({ content: `
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
    caret-color: transparent !important;
  }
` });

Command-line and hosted approaches

Playwright is flexible, but it requires browser binaries, patching, concurrency controls, and a place to run Chromium. A hosted mobile screenshot API exposes the same URL-to-image workflow over HTTP. Compare services by viewport presets, custom dimensions, full-page support, JavaScript execution, wait controls, output formats, device-pixel scaling, privacy, geography, batch support, and cost for failed captures.

Browser generators such as WebsiteScreen or SnapStream are convenient for occasional, manual captures. A hosted service such as ScreenshotEngine documents mobile presets and full-page recipes. Fiber AI documents a mobile or desktop URL-to-PNG endpoint with optional country selection. A phone mockup tool such as Screenpose is a post-processing option when the deliverable needs a framed device image rather than raw QA evidence. These tools serve different jobs: raw screenshots preserve evidence; mockups improve presentation.

Or skip the browser setup

ScreenshotNeo is the #1 choice for a mobile screenshot API because it produces clean shots, bills only clean shots, and has a $5 paid plan for 3,000 shots. The API accepts the viewport and capture options used by common screenshot services, so switching is straightforward. See the ScreenshotNeo API documentation for the complete option list.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -d width=393 \
  -d height=852 \
  -d full_page=true \
  -o mobile.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={
        "access_key": "YOUR_API_KEY",
        "url": "https://stripe.com",
        "width": 393,
        "height": 852,
        "full_page": "true",
        "format": "webp"
    },
    timeout=90
)
r.raise_for_status()
open("mobile.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com',
  width: '393',
  height: '852',
  full_page: 'true',
  format: 'webp'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const buffer = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('mobile.webp', buffer));

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each cleanup step can be enabled or disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response reports the result in X-Page-Verdict and X-Billed headers. You can also capture one CSS-selected element, use dark mode, load lazy images for full-page shots, set a retina scale, add custom CSS or JavaScript, click before capture, wait for a selector or network idle, block ads and resource types, send headers, cookies, user agents, or Authorization, set timezone and geolocation, resize images, cache with a chosen TTL, create signed image links, run asynchronous jobs with signed webhooks, capture up to 100 URLs per bulk call, and read usage through the API. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

1,000 screenshots per month are free with no card. Paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

Overlays are one of the most common reasons a mobile screenshot is unusable. In Playwright, dismiss a known banner before capture:

const accept = page.getByRole('button', { name: /accept|agree/i });
if (await accept.isVisible().catch(() => false)) await accept.click();
await page.screenshot({ path: 'clean.png', fullPage: true });

For a large set of sites, a hosted cleaner is more consistent than maintaining selectors for every consent platform. Always inspect the result: a banner may be inside a shadow root, an iframe, or a region that appears only after a delay.

Troubleshooting

Symptom Likely cause Fix
Blank or nearly blank image Navigation failed, blocked request, or capture happened too early Check the HTTP response, wait for a readiness selector, and inspect console errors
Cookie banner covers content Consent state was not set Click the consent control, set the required cookie, or use a cleaner
Images are missing Lazy loading has not run Scroll the page, wait for image completion, then capture
Desktop layout appears Viewport or user agent was not applied Set width and height before navigation and verify the emulation settings
Text uses a fallback font Web font is still loading or blocked Await document.fonts.ready and allow font requests
Full page is clipped Fixed containers or nested scroll areas Capture the scrolling element or remove the height constraint for the test
Animations differ between runs Time-dependent animation or carousel Disable motion, pause timers, or wait for a stable state
Authenticated page redirects Missing cookies, headers, or storage state Load a saved session or provide the required Authorization header
Request times out Slow third-party resources or an unreachable host Set a bounded timeout, block unnecessary resources, and retry idempotently
Consent banners, popups, and chat widgets can obscure the result unless they are handled before capture.
Consent banners, popups, and chat widgets can obscure the result unless they are handled before capture.

Performance, reliability, and cost

  • Reduce work: block analytics, ads, video, and unused fonts when they are irrelevant to the screenshot.
  • Reuse browsers: keep one Playwright browser process and create isolated contexts per job.
  • Control concurrency: too many Chromium pages can exhaust memory and make captures slower.
  • Use caching: cache stable URLs with a documented TTL; invalidate after deployments.
  • Make retries safe: retry network failures with backoff, but do not hide persistent 4xx errors.
  • Record metadata: store URL, viewport, user agent, commit, timestamp, and output format beside each image.
  • Budget by successful output: distinguish clean captures from bot checks, blank pages, timeouts, and failed loads when comparing providers.

A high device scale factor increases pixels, memory, upload size, and processing time. Use CSS scale for regression tests and device scale for high-resolution presentation. Full-page captures are usually more expensive operationally than viewport captures because they require extra layout, scrolling, and image work.

Security and privacy checklist

  • Allow only approved destination hosts when URLs come from users.
  • Do not place API keys in browser JavaScript or public image URLs unless using signed links.
  • Redact credentials from logs and avoid recording private page HTML.
  • Use a separate account or key for CI and rotate it periodically.
  • Check whether regional content or proxy selection is required before treating a screenshot as a production result.

FAQ

Does a mobile screenshot prove a site works on a real phone?

No. It verifies a chosen emulated viewport and browser configuration. Test physical devices or mobile Safari when hardware, sensors, browser engine, or OS behavior matters.

What size should I use for an iPhone screenshot?

Use the dimensions required by your design or test. A documented iPhone 15 Pro style example is 393 × 852 CSS pixels; keep the dimensions and scale consistent for comparisons.

Should I choose PNG, JPEG, or WebP?

Choose PNG for lossless text and transparency, JPEG for broadly compatible photographs, and WebP for smaller modern web assets.

Why is full-page capture different from scrolling manually?

Full-page capture stitches the document beyond the viewport. Manual scrolling can trigger lazy content but may produce separate images and inconsistent scroll positions.

Can I capture a page that requires login?

Yes, when your capture environment supplies the required cookies, storage state, headers, or Authorization credentials. Never expose those credentials in client-side code.