How to Compare Mobile Website Screenshots Across iOS and Android
Compare the same mobile page across iOS and Android with repeatable captures, separate baselines, and a clear way to tell platform differences from regressions.
To compare a mobile website across iOS and Android, capture the same URL and page state on both platforms, record the browser and device conditions, and review the results side by side or as an overlay. Keep separate visual regression baselines for each environment: a raw iOS-to-Android pixel diff will include expected differences in fonts, form controls, and other platform rendering. Use browser emulation for fast responsive checks, then verify on real devices when the actual mobile browser rendering is the question.
A screenshot diff shows that pixels changed; it cannot decide whether the change is a defect. Treat the screenshots as evidence and judge them against your responsive requirements and the user-visible behavior you expect.
1. Decide what the comparison should answer
There are two related but different tasks:
- Visual regression: Did this page change from its approved appearance in the same browser and environment? Compare each run against a baseline captured in that same environment.
- Cross-platform comparison: How does the page render in different environments, such as Safari on iOS and Chrome on Android? Compare the environments to find meaningful usability or layout differences, while accounting for expected platform rendering.
Do not use an iOS screenshot as the baseline for Android, or vice versa. Playwright notes that screenshot rendering can vary with the host OS, version, settings, hardware, power source, and headless mode, and recommends generating and comparing screenshots in the same environment. Its snapshots include browser and platform identifiers for this reason. Playwright: Visual comparisons
Before capturing, define a small set of comparable cases: the routes, viewport intent, orientation, login state, content state, and interactions that matter. For example, compare the page after dismissing a consent banner, with the same account state and the same menu open or closed on both platforms.
2. Make the captures comparable
- Use the same page and state. Match the URL, authentication, data, selected options, interaction state, and scroll position.
- Choose the viewport intentionally. For responsive layout comparisons, use equivalent CSS viewport dimensions where possible. If testing specific real devices, record the device and let its actual viewport be part of the test.
- Match orientation. Compare portrait with portrait and landscape with landscape. A difference in orientation can change the entire layout.
- Wait for stable content. Let fonts, images, and client-rendered content settle. Freeze or mask only known dynamic regions such as a timestamp or rotating ad.
- Record the environment. Save device or emulation profile, OS and version, browser and version, viewport, orientation, capture date/build, and any relevant conditions.
- Review the evidence. Use side-by-side, overlay, or diff views. Investigate clipped text, overlapping elements, missing content, unexpected wrapping, unreadable contrast, and controls that no longer work.
When reviewing, first compare like with like: same route, state, orientation, and intended viewport. Then judge each platform against the product’s responsive and design requirements. A changed baseline should be reviewed deliberately rather than accepted just to clear a diff.
3. Capture repeatable screenshots with Playwright
Playwright device profiles simulate parameters such as user agent, screen size, viewport, and touch capability. This makes emulation useful for repeatable responsive checks, but it does not prove how a physical handset renders the page. Label emulated captures as emulated and use real-device checks when device-specific rendering is important. Playwright: Emulation
Install and create the test
The following example uses Playwright Test projects to capture an iPhone-sized WebKit context and an Android-sized Chromium context. The projects make the platform intent visible in test names. These are emulated browser contexts, not physical iPhones or Android devices.
npm init playwright@latest
Choose JavaScript or TypeScript when prompted. Install the browser binaries if the setup did not do so:
npx playwright install
Replace playwright.config.ts with this configuration:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
use: {
baseURL: 'https://example.com',
screenshot: 'only-on-failure',
trace: 'retain-on-failure',
},
projects: [
{
name: 'ios-safari-emulated',
use: { ...devices['iPhone 13'], browserName: 'webkit' },
},
{
name: 'android-chrome-emulated',
use: { ...devices['Pixel 5'], browserName: 'chromium' },
},
],
});
Create tests/mobile-visual.spec.ts:
import { test, expect } from '@playwright/test';
test('mobile page visual baseline', async ({ page }) => {
await page.goto('/pricing', { waitUntil: 'networkidle' });
// Prefer a product-specific readiness signal when available.
await expect(page.getByRole('heading', { name: 'Pricing' })).toBeVisible();
// Example: dismiss a consent dialog only if that is the state under test.
const acceptButton = page.getByRole('button', { name: /accept all/i });
if (await acceptButton.isVisible().catch(() => false)) {
await acceptButton.click();
}
await expect(page).toHaveScreenshot('pricing-mobile.png', {
fullPage: true,
animations: 'disabled',
caret: 'hide',
});
});
Run both projects:
npx playwright test tests/mobile-visual.spec.ts
On the first run, Playwright writes reference snapshots. Review them before committing. Later runs compare against the project-specific snapshots. Because project names are used in snapshot naming, the iOS-emulated and Android-emulated contexts retain separate baselines. Commit the approved snapshot files with the code they represent.
Make the capture state deterministic
Use a readiness signal tied to your page instead of relying only on a fixed delay. networkidle can be unsuitable for pages with persistent network activity, so waiting for a meaningful heading, loaded image, or application-ready marker is often more reliable. If an element changes on every run, either control its data in the test environment or mask that specific element.
For example, use a screenshot stylesheet to hide a known volatile region:
/* tests/screenshot.css */
.live-clock,
.rotating-ad {
visibility: hidden !important;
}
Then pass it to the screenshot assertion:
import path from 'node:path';
import { test, expect } from '@playwright/test';
test('page without known volatile regions', async ({ page }) => {
await page.goto('/pricing');
await expect(page.getByRole('heading', { name: 'Pricing' })).toBeVisible();
await expect(page).toHaveScreenshot('pricing.png', {
fullPage: true,
stylePath: path.join(process.cwd(), 'tests/screenshot.css'),
});
});
Playwright’s screenshot assertion waits for two consecutive screenshots to match, and supports a stylesheet for filtering volatile elements. Use masking narrowly: hiding a whole section can conceal a real regression. See the screenshot assertion options.
Choose screenshot and comparison options carefully
| Option or decision | When to use it | Watch for |
|---|---|---|
fullPage: true |
Check the full document, including content below the fold. | Long pages can make diffs harder to inspect; also capture key viewport states if needed. |
| Viewport screenshot | Check the initial screen or a specific scroll position. | Set the scroll position and interaction state explicitly. |
animations: 'disabled' |
Reduce motion-related capture noise. | Animation-dependent UI may need a separately defined state. |
caret: 'hide' |
Avoid a blinking text caret changing the screenshot. | Keep focus state intentional when testing forms. |
stylePath |
Hide or stabilize known volatile regions for the screenshot. | Do not suppress large areas or content where regressions matter. |
maxDiffPixels / threshold |
Adjust sensitivity for known, small rendering noise. | Too much tolerance can hide genuine visual changes. Start with the default and justify changes. |
| Snapshot update | Accept a reviewed, intentional design change. | Do not run update mode as an automatic response to every failed diff. |
Update approved reference screenshots with:
npx playwright test --update-snapshots
Review the resulting image changes and commit only the baselines that reflect an intentional change. Playwright’s comparison options include pixel tolerance settings; increasing tolerance should be a deliberate tradeoff, because it can make real regressions harder to detect.
4. Interpret differences across platforms
Some pixel differences are normal. OS-level fonts, native form controls, and scrollbars can vary. Text can wrap differently even when the CSS layout is otherwise correct. BrowserStack calls out these platform differences in its visual testing documentation. BrowserStack: Visual testing basics
| Difference | Likely interpretation | What to check |
|---|---|---|
| Font shape or text width changes slightly | Could be font availability or platform text rendering. | Confirm the intended font loaded on both, then check whether wrapping, clipping, or hierarchy is actually broken. |
| Native input, select, or button appearance differs | May be browser/OS-native control rendering. | Check usability, labels, focus, and touch operation; do not assume identical pixels are required. |
| Headline wraps or clips on one platform | Potential layout defect or font metric difference with a user-visible consequence. | Check available width, font loading, line height, overflow, and responsive breakpoints. |
| CTA is missing, covered, or outside the viewport | Likely functional or layout regression. | Check sticky headers, safe areas, overlays, and the page state at capture. |
| One screenshot has different content or a loading state | Capture state is not equivalent or content is not deterministic. | Compare data, login state, network completion, locale, and readiness signals. |
| Large areas differ after a browser or OS update | The environment may have changed, or a real compatibility issue may exist. | Confirm recorded versions and reproduce before updating that environment’s baseline. |
A practical review order is: verify state and viewport, identify the changed region, decide whether the difference is platform rendering or a product behavior change, then test the affected control on the relevant device. Preserve visual coverage around important content. Mask only known noise, not the exact area that could break.
5. Emulation versus real devices
Use emulation for frequent, fast checks
Emulation fits development feedback and CI checks for responsive breakpoints, layout changes, content states, and broad visual regressions. It gives you repeatable viewport, user-agent, and touch parameters. Playwright’s device registry also includes other settings such as locale, timezone, and color scheme that can be configured when those affect the page. Playwright emulation options
Emulation does not reproduce every property of a physical device. It should not be your only evidence when the question concerns a specific handset, installed browser, native rendering behavior, device input, or a bug reported on real hardware.
Use real-device capture to verify actual mobile browser rendering
For hosted, repeatable device/browser coverage, BrowserStack Percy documents separate screenshots for mobile devices and browsers. Its documentation currently lists Safari with iOS and Chrome with Android (Beta), says mobile screenshot width is fixed to the real device, and says portrait is the default orientation. Mobile browser access requires an eligible plan. Check the current device matrix and plan requirements before choosing it. BrowserStack Percy: Visual testing on mobile browsers
Manual checks on an available iPhone and Android phone are useful for reproducing a report or spot-checking high-impact flows. A particular phone model is not required by this workflow; choose devices based on your users and the issue being investigated.
6. Tools and workflow choices
| Approach | Best fit | Limit to account for |
|---|---|---|
| Playwright screenshots and emulation | Automated checks in an existing browser test suite, responsive coverage, and committed baselines. | Keep baselines environment-specific; emulation is not a physical-device capture. |
| Real-device visual testing service | Repeatable capture across actual mobile browser/device combinations and a review workflow. | Verify the service’s current browser support, device coverage, and plan access. |
| Manual device review | Spot checks and reproducing a specific user-reported issue. | Less repeatable unless the device, OS, browser, state, and steps are recorded. |
ScreenshotNeo is a website screenshot API and MCP server for developers. It is useful when you need to capture a URL or automate screenshot collection; its API returns an image or PDF, while browser automation or a real-device testing service is the better fit when you need a device-specific test matrix and regression review process. See ScreenshotNeo and the API documentation.
7. Troubleshooting
| Symptom | Common cause | Fix |
|---|---|---|
| Every run produces a diff | Different host/runtime, browser version, headless mode, font availability, or changing page content. | Run baseline and comparison in the same pinned environment; stabilize content and record runtime details. |
| Text shifts or wraps differently between runs | Web fonts have not loaded before capture, or the font request is failing. | Wait for a page-specific ready signal and verify the intended font is loaded before capturing. |
| Screenshot shows a spinner or skeleton | Capture happened before application data or images were ready. | Wait for a meaningful loaded element or app-ready state; investigate slow or failed requests. |
| Mobile layout looks like desktop | Viewport or mobile emulation settings are missing, or the site lacks a suitable viewport configuration. | Use a mobile device profile and verify the page’s viewport behavior and responsive CSS. |
| Two captures show different user content | Different authentication, locale, feature flag, test data, or experiment assignment. | Set up the same account and deterministic data/flags in both runs; record locale and state. |
| Diff is dominated by a timestamp, ad, or animation | Volatile content is being captured as if it were static. | Freeze the source data or mask only that known region with a screenshot stylesheet. |
| Baseline update hides an unexpected change | Snapshots were refreshed without reviewing the visual change. | Restore the prior baseline, inspect the diff, and update only after deciding the change is intended. |
| Emulated result does not reproduce a device bug | The bug depends on real browser/OS/device behavior absent from emulation. | Capture on the affected real device/browser and record its exact environment and reproduction steps. |
| Mobile service ignores configured width | The service captures on a real device whose screenshot width is fixed. | Use the actual device dimensions as part of the test; confirm the provider’s current capture rules. |
8. Performance, reliability, and cost
- Keep the matrix small and purposeful. Start with the critical routes and states on one representative iOS browser and one Android browser, then add devices when user coverage or a reproduced issue justifies them.
- Reduce unnecessary full-page work. Full-page screenshots are useful for long-page layout, but viewport captures can be easier to review for focused interactions. Capture both only when each answers a distinct question.
- Prefer deterministic readiness to long sleeps. Waiting for an explicit application state reduces wasted time and flaky timing assumptions.
- Store the environment with the evidence. Keep snapshot names or build metadata tied to browser/platform projects, and avoid comparing artifacts made under unknown conditions.
- Budget for real-device coverage. Hosted device services may require plan access; BrowserStack documents that its mobile browser coverage requires an eligible Percy plan. Check current terms and device support before estimating ongoing cost.
Playwright is a practical low-overhead starting point if your team already runs browser tests and can keep the capture environment stable. Real-device services trade additional service access and setup for captures from devices and browsers that emulation does not establish. Manual checks are appropriate for investigations, but should include enough environment detail to reproduce the finding.
Or skip the browser setup
Use ScreenshotNeo when you want a screenshot from a URL without installing or maintaining a browser. The API can return PNG, JPEG, WebP, or PDF. For this comparison workflow, remember that a website screenshot API capture is not proof of a real iPhone or Android browser render; use it for page capture and automation where a URL-based screenshot is enough. Request options include device presets and custom viewport sizes, full-page capture, custom CSS and JavaScript, selector waits, delay or network-idle waits, and image format.
See the ScreenshotNeo API documentation for request options. Example request:
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, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Should iOS and Android have the same visual baseline?
No. Keep a baseline for each browser/platform environment. Compare platforms to understand behavior, but do not treat one platform’s pixels as the other’s expected output.
Does an iPhone profile in Playwright test a physical iPhone?
No. It configures simulated device and browser parameters. Verify actual mobile-browser behavior on a real device when that is what the question requires.
What should I do when the diff is only a font or native control?
Confirm the intended font and functional behavior. If the difference is only expected platform rendering and the layout remains usable, document it rather than weakening the comparison globally.
Can screenshots alone tell me whether a page works?
No. A screenshot records appearance at one moment. Pair visual review with functional checks for actions such as opening menus, submitting forms, and using navigation.


