ScreenshotNeo

BlogHow-to

How to Check Mobile Website Changes with Scheduled Screenshots on an iPhone Viewport

Use Playwright and GitHub Actions to capture a consistent iPhone viewport on a schedule, compare screenshots, and review visual changes.

By the ScreenshotNeo team4 October 20267 min read

Use Playwright’s iPhone device emulation to render your page at an iPhone viewport, then use Playwright Test’s screenshot assertions to compare each capture with a reviewed baseline. Run that test on a GitHub Actions schedule. The result is a repeatable browser viewport check, not proof that the page behaves exactly like Safari on a physical iPhone.

This setup catches visual changes in the part of the page visible in the emulated viewport. You can instead capture the full page, but choose that deliberately: a viewport check and a full-page check answer different questions.

1. Create a Playwright visual check

Start with a Node.js project. Install Playwright Test and its Chromium browser:

npm init -y
npm install --save-dev @playwright/test
npx playwright install chromium

Add a test file named tests/mobile-visual.spec.js:

const { test, expect, devices } = require('@playwright/test');

const baseURL = process.env.SITE_URL || 'https://example.com';

test.use({
  ...devices['iPhone 13'],
});

test('home page matches the iPhone viewport baseline', async ({ page }) => {
  await page.goto(baseURL, { waitUntil: 'networkidle' });
  await expect(page).toHaveScreenshot('home-iphone.png');
});

Replace https://example.com with your site URL, or set SITE_URL in the environment. The built-in iPhone 13 profile supplies emulated device settings such as viewport, user agent, screen size, and touch behavior. You may override individual settings in the Playwright configuration when you need a different viewport. This remains browser emulation; it does not reproduce every iOS or physical-device condition. See Playwright device emulation.

Run the test once to create the initial reference screenshot:

npx playwright test tests/mobile-visual.spec.js

Review and commit the generated snapshot files. The first run establishes the baseline; later runs compare against it. Treat a mismatch as a signal for review, not automatic proof of a defect. Accept a new baseline only after deciding the visual change is expected.

2. Configure the capture for a useful comparison

Viewport screenshot or full-page screenshot

toHaveScreenshot() captures the screenshot for comparison. For an explicit full-page comparison, pass the screenshot option:

await expect(page).toHaveScreenshot('home-iphone-full.png', {
  fullPage: true,
});

Use the default viewport capture when the concern is the initial mobile layout. Use fullPage: true when changes farther down the page matter too. A full-page image may be taller and more sensitive to content that loads or changes while the page is being captured.

Wait for the page state you intend to check

Navigation completion does not guarantee every application has finished rendering. If a key component appears after navigation, wait for it explicitly:

await page.goto(baseURL);
await page.locator('[data-testid="mobile-navigation"]').waitFor();
await expect(page).toHaveScreenshot('home-iphone.png');

Prefer a stable selector tied to the intended content. If the page has animation, rotating banners, timestamps, personalized recommendations, or other volatile areas, control the source of the variability where possible. Playwright supports adding a stylesheet at screenshot time to hide or adjust dynamic regions:

await expect(page).toHaveScreenshot('home-iphone.png', {
  stylePath: 'tests/visual-stability.css',
});
/* tests/visual-stability.css */
[data-testid="live-clock"],
[data-testid="rotating-promo"] {
  visibility: hidden !important;
}

Only mask or hide areas that are intentionally outside the check. Hiding a region that should be monitored can conceal a real regression. See Playwright visual comparisons for screenshot assertions, stabilization, and baseline updates.

Set comparison tolerance carefully

Small rendering differences can occur even when the layout is effectively unchanged. Playwright screenshot assertions accept options including maxDiffPixels, maxDiffPixelRatio, and threshold. For example:

await expect(page).toHaveScreenshot('home-iphone.png', {
  maxDiffPixelRatio: 0.001,
});

A higher tolerance can reduce noise, but it can also allow small regressions through. Start with strict comparisons in a stable environment, inspect reported diffs, and adjust only when you understand the source of expected pixel variation. Consult the Playwright screenshot assertion options for supported settings.

3. Run the check on a GitHub Actions schedule

Save this workflow as .github/workflows/mobile-visual.yml:

name: Mobile visual check

on:
  workflow_dispatch:
  schedule:
    - cron: '17 */6 * * *'

jobs:
  screenshot:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test tests/mobile-visual.spec.js
        env:
          SITE_URL: ${{ vars.SITE_URL }}

The example runs at minute 17 every six hours. Change the POSIX cron expression to suit how often you need a check. GitHub Actions schedules use UTC, run against the latest commit on the default branch, and support intervals as short as five minutes. Scheduled workflows in public repositories are automatically disabled after 60 days without repository activity. The workflow must be present on the default branch. See GitHub Actions workflow syntax and events that trigger workflows.

Use a repository variable named SITE_URL for a public test target. If the target requires credentials, store secrets in GitHub Actions secrets and pass only the needed values to the test step. Avoid putting credentials in the test file, workflow YAML, or screenshots.

4. Keep baselines and scheduled runs trustworthy

  • Use the same rendering environment. Rendering can vary with host operating system, browser version, hardware, power source, and headless mode. Generate and compare snapshots in the same runner and browser setup where practical. Playwright documents these environment-dependent differences in its visual comparison guidance.
  • Keep the reference under review. Commit the baseline so changes are visible in code review. Update it after reviewing a diff, not just to clear a failing run. Playwright supports updating snapshots with its test snapshot update flag.
  • Stabilize changing content intentionally. Wait for required elements and hide only content that is genuinely irrelevant to the check. If dynamic content itself matters, make its test state deterministic rather than masking it.
  • Check the schedule’s assumptions. A scheduled run uses the default branch’s latest commit. Ensure the test, workflow, and accepted baseline are all available there.
  • Use a real iPhone for iOS-specific bugs. Emulation is useful for repeatable layout checks, but it is not a substitute when a defect may depend on Safari, iOS, device hardware, or real touch behavior.

5. Troubleshoot common failures

Symptom Likely cause Fix
First run fails because no snapshot exists The test has not created its reference yet. Run the test intentionally, inspect the generated image, and commit the accepted baseline.
Every scheduled run reports a visual difference Dynamic content or inconsistent browser/runner rendering changes pixels. Keep runs in a consistent environment, wait for a stable page state, and use a screenshot stylesheet only for irrelevant volatile regions.
The screenshot is blank or missing key content The page or a delayed component was captured before it rendered, or navigation failed. Check the navigation response and wait for a meaningful selector before the assertion.
Local runs pass but CI fails Browser versions, dependencies, fonts, or operating systems differ. Install the browser through Playwright in CI and compare snapshots in the same CI environment used to create the baseline.
The workflow never runs The workflow is not on the default branch, the cron is misunderstood as local time, or a public repository was inactive for 60 days. Check the default branch, convert the desired time to UTC, and reactivate or manually dispatch the workflow as appropriate.
The check passes but a real iPhone still has a bug The emulated profile does not cover a physical iPhone or all iOS browser behavior. Reproduce on actual hardware or use a real-device testing service for that case.

6. Consider the operational cost and limits

A scheduled Playwright job uses CI runner time and stores or compares image artifacts and baselines. More frequent schedules increase the number of runs; full-page captures and multiple target pages also increase browser work and the volume of diffs to review. Choose a cadence based on how often the site changes and how quickly someone needs to respond. GitHub documents the available scheduling behavior, but does not prescribe a monitoring frequency.

For a small project, Playwright’s repository-managed snapshots keep the test and baseline together. Teams that want hosted baseline management and visual review can also investigate Percy’s Playwright integration; its documentation describes snapshot capture and comparison workflows. Compare baseline ownership, review process, CI integration, rendering consistency, and handling of dynamic content before choosing. The cited product documentation establishes the integration and review model, not current prices or service limits: Percy with Playwright.

Or skip the browser setup

ScreenshotNeo can capture a URL with one GET request, including an iPhone device preset. Its API documentation covers the available parameters.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -d device=iphone-13 \
  -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; paid plans start at $5 for 3,000. A screenshot API capture is useful for scheduled image collection, while a Playwright assertion is the direct route when you want the test runner to compare against a checked-in baseline.

Sign up for 1,000 free screenshots a month, with no card.

FAQ

Does an iPhone viewport screenshot prove the page works on iOS?

No. It checks a browser rendered with an emulated iPhone profile. Validate on physical iOS hardware when the behavior may depend on Safari or the device.

How often should the scheduled check run?

Choose a cadence based on how often the site changes and how quickly you need notice. GitHub Actions accepts POSIX cron schedules as frequently as every five minutes, but a shorter interval is not automatically more useful.

Should I update the snapshot whenever the test fails?

Only after reviewing the diff and deciding the new design is expected. A failing comparison is a review signal, not a baseline update instruction.