A mobile screenshot of a website is supposed to show what someone sees on a phone. Getting it right takes more than a narrow window: the site may pick a different layout by user agent, the picture needs the phone's pixel density to look sharp, and touch-only behaviour changes some pages. This page shows device emulation in Puppeteer, Playwright and our API, and what changed between them on real pages.
How we tested. On 2026-11-08 we captured Wikipedia's Main Page and python.org through our API with every phone and tablet preset, plus a 393×852 window with a desktop user agent, and repeated the Wikipedia test with Puppeteer 25.7's built-in iPhone 15 descriptor. Each capture took 1.6–6.3 s.
Size, pixel ratio, user agent
A phone in Chrome DevTools or in a screenshot tool is four settings together:
- Viewport: the CSS size of the page area, for example 393×852 for an iPhone 15. This is what media queries see.
- Pixel ratio (
deviceScaleFactor): physical pixels per CSS pixel. An iPhone 15 has 3, so the picture is 1179×2556. - User agent: the string that tells servers which browser and device asks. Some sites serve a different page to a phone.
- Touch and mobile flags:
isMobilemakes Chrome take the page's<meta name="viewport">into account as a phone does;hasTouchturns on touch events.
Leave one out and the screenshot is of a device that does not exist. The commonest mistake is the user agent.
The user agent changes the page
Wikipedia is the clearest example we found:
| Capture | What came back |
|---|---|
| 393×852 window, desktop user agent | The desktop site's responsive layout (skin-vector-2022) in a narrow column |
device: "mobile" or "iphone-15" (iPhone user agent) |
The mobile site (skin-minerva), same URL, with an "install the app" banner at the top |
Same address, same size, a different page: Wikipedia chooses the skin on the server by user agent. Puppeteer with KnownDevices['iPhone 15'] got the mobile skin too; a plain setViewport({ width: 393, height: 852 }) got the desktop one. The app banner is not a cookie banner, so block_cookie_banners leaves it; remove it with hide_selectors once you know its selector.
Sites that are purely responsive look the same either way, because only the CSS width decides: python.org showed the same phone layout in a 400 px window with a desktop user agent as with the iPhone presets. You cannot tell in advance which kind a site is, so emulate the device.
Puppeteer
import puppeteer, { KnownDevices } from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.emulate(KnownDevices['iPhone 15']); // viewport, pixel ratio, touch, user agent
await page.goto('https://en.wikipedia.org/wiki/Main_Page', { waitUntil: 'load' });
await page.screenshot({ path: 'wiki-iphone.png' });
await browser.close();
Puppeteer 25.7 ships 131 device descriptors (portrait and landscape). Its iPhone 15 reported innerWidth 393, innerHeight 659 and devicePixelRatio 3, and the screenshot came out 1179×1977: the descriptor's viewport is the area left under Safari's toolbars, not the full screen. See page.emulate.
Playwright
import { chromium, devices } from 'playwright';
const browser = await chromium.launch();
const context = await browser.newContext({ ...devices['iPhone 15'] });
const page = await context.newPage();
await page.goto('https://en.wikipedia.org/wiki/Main_Page');
await page.screenshot({ path: 'wiki-iphone.png' });
Playwright 1.64 has 207 descriptors, newer phones included (Pixel 8, Galaxy S24). Its iPhone 15 is also a 393×659 viewport in a 393×852 screen at 3×, and its defaultBrowserType is WebKit: run it in Chromium, as above, and you get Chrome's rendering with an iPhone's size and user agent. For Safari's own rendering, launch webkit instead (Playwright emulation).
Our API: one option
curl -X POST "https://shareglint.com/v1/screenshots?wait=true" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://en.wikipedia.org/wiki/Main_Page", "device": "iphone-15"}'
device sets the viewport, pixel ratio, touch and user agent together. The 13 presets:
| Preset | Viewport (CSS px) | Pixel ratio | Picture (px) | User agent |
|---|---|---|---|---|
desktop |
1440×900 | 1 | 1440×900 | Chrome (desktop) |
laptop |
1280×800 | 1 | 1280×800 | Chrome (desktop) |
tablet |
820×1180 | 1 | 820×1180 | Safari, iPad |
mobile |
390×844 | 1 | 390×844 | Safari, iPhone |
iphone-15 |
393×852 | 3 | 1179×2556 | Safari, iPhone |
iphone-se |
375×667 | 2 | 750×1334 | Safari, iPhone |
ipad |
820×1180 | 2 | 1640×2360 | Safari, iPad |
pixel-8 |
412×915 | 2.625 | 1082×2402 | Chrome, Android |
galaxy-s24 |
360×780 | 3 | 1080×2340 | Chrome, Android |
desktop-1280 |
1280×800 | 1 | 1280×800 | Chrome (desktop) |
desktop-1440 |
1440×900 | 1 | 1440×900 | Chrome (desktop) |
desktop-1920 |
1920×1080 | 1 | 1920×1080 | Chrome (desktop) |
macbook |
1440×900 | 2 | 2880×1800 | Chrome (desktop), macOS |
Unlike the Puppeteer and Playwright descriptors, the phone presets capture the full screen height (393×852 for the iPhone 15), as if the page were shown without browser bars. scale overrides a preset's pixel ratio: "device": "iphone-15", "scale": 1 keeps the iPhone's layout and user agent but returns 393×852 px.
What python.org's first screen weighed as PNG:
| Preset | Picture | Size |
|---|---|---|
mobile |
390×844 | 56 KB |
iphone-15, scale: 1 |
393×852 | 56 KB |
iphone-se |
750×1334 | 74 KB |
galaxy-s24 |
1080×2340 | 186 KB |
pixel-8 |
1082×2402 | 208 KB |
iphone-15 |
1179×2556 | 221 KB |
tablet |
820×1180 | 162 KB |
ipad |
1640×2360 | 515 KB |
A 3× capture has nine times the pixels of a 1× one and came out four times the size. Use 3× for an App Store-style picture or a print, 1× (mobile, or scale: 1) for thumbnails and visual checks.
Full page, dark mode and frames
device combines with the other options: full_page for the whole page at phone width (see full-page screenshots), dark_mode for the dark theme (which sites follow it), and scroll_video for a phone-sized scroll video (scroll videos). To show the capture inside a phone, put it in the device-mockup template.
A capture costs the same at any size: 1 render for the first screen, 2 for a full page; repeats with the same options within 24 hours are free (pricing).
Limits
- Chrome's engine on every preset. The iPhone presets send Safari's user agent but render in Chrome. Layout differences between WebKit and Blink (rare, but real for some CSS) do not show; test in Playwright's WebKit for those.
- No browser bars. The picture is the page only, without Safari's address bar or a home indicator. Add a frame with the
device-mockuptemplate. - Public pages only. Pages behind a login stay out of reach of the API.