ScreenshotNeo

BlogHow-to

How to capture a mobile website screenshot with WebdriverIO and Chrome

Emulate a mobile device in Chrome, wait for the page to settle, and capture a viewport or full-page screenshot with WebdriverIO.

By the ScreenshotNeo team4 October 20267 min read

To capture a mobile website screenshot with WebdriverIO and Chrome, start a Chrome session with WebDriver BiDi enabled, emulate a mobile device profile, open the page, wait for the content you need, and save a screenshot. browser.takeScreenshot() captures the current viewport and returns base64-encoded PNG data. For a full-page image, use WebdriverIO Visual Testing’s saveFullPageScreen(). Chrome device emulation approximates a mobile viewport; it does not validate behavior in a real mobile browser engine.

1. Choose the screenshot you need

Decide the screenshot extent before writing the test. A viewport screenshot shows only what is visible inside the browser viewport. A full-page screenshot includes content below the fold. An element screenshot captures an element’s visible bounding rectangle after it has been scrolled into view.

Need WebdriverIO option Output
Visible mobile viewport browser.takeScreenshot() or Visual Testing saveScreen() Viewport image
Entire page Visual Testing saveFullPageScreen() Full-page image
One component or element Element screenshot API or Visual Testing saveElement() Image of the element’s visible rectangle

The code below uses WebdriverIO’s core WebDriver screenshot command to write a viewport PNG to disk.

2. Configure Chrome for device emulation

WebdriverIO’s browser.emulate('device', deviceName) applies a device preset’s viewport, device scale factor, and user agent. It requires WebDriver BiDi support. Enable the WebSocket URL capability and verify that the browser and any remote browser provider support BiDi.

// wdio.conf.js (capability excerpt)
export const config = {
  capabilities: [{
    browserName: 'chrome',
    webSocketUrl: true
  }]
}

Select a preset supported by the WebdriverIO version in your project. The device list can change, so check the current WebdriverIO Emulation documentation instead of assuming every preset name or specification is permanent. The example uses iPhone 15, as shown in the documentation.

3. Capture a mobile viewport screenshot

Run this inside a WebdriverIO test with a Chrome session configured for BiDi. Replace the readiness condition with one that reflects the content your page must display. The snippet follows documented API shapes; actual output depends on your WebdriverIO, Chrome, driver, and provider versions.

// Example WebdriverIO test
const restoreDevice = await browser.emulate('device', 'iPhone 15')

try {
  await browser.url('https://example.com')

  // Wait for meaningful page content, not merely the initial navigation.
  await $('main').waitForDisplayed()

  const base64Png = await browser.takeScreenshot()
  const { writeFile } = await import('node:fs/promises')
  await writeFile('./mobile-viewport.png', Buffer.from(base64Png, 'base64'))
} finally {
  // Avoid leaking the emulated settings into later tests.
  await restoreDevice()
}

The core WebDriver screenshot command returns base64 PNG data for the top-level browsing context’s viewport. Decode it as shown to save a PNG file. See the WebdriverIO WebDriver Protocol reference for screenshot command behavior.

4. Save viewport, full-page, or element images with Visual Testing

If your project uses WebdriverIO Visual Testing, its save methods write image files directly. Output paths and filename formatting depend on the service configuration.

await browser.saveScreen('mobile-homepage')
await browser.saveFullPageScreen('mobile-homepage-full')

// For a single element, use the element-saving method:
await browser.saveElement(await $('main'), 'mobile-main')

saveScreen() is for the viewport; saveFullPageScreen() is for the full page. These are save operations. Use the corresponding check methods when your goal is image comparison. Consult the Visual Testing methods and method options documentation for current details.

5. Handle full pages and lazy-loaded content

A full-page capture may need different handling when content appears only after scrolling or rendering depends on scroll position. WebdriverIO Visual Testing documents userBasedFullPageScreenshot: true for a scroll-and-stitch approach: it captures viewport-sized sections while scrolling through the page. Set the scroll timeout to suit the page and verify support for the browser and application context you use.

// Visual Testing service configuration excerpt
export const config = {
  services: [[ 'visual', {
    userBasedFullPageScreenshot: true
  }]]
}

Use this mode when lazy-loaded images or sections need scrolling to enter the viewport. If the page has a known readiness signal, wait for it before capture as well. The Visual Testing service options page describes full-page behavior and service configuration.

6. Make screenshots more repeatable

Fonts, animations, blinking cursors, and asynchronous content can change between captures. Visual Testing options include waiting for fonts to load (documented as enabled by default) and disabling animations or blinking cursors. Keep the same settings for baseline creation and later comparisons.

  • Wait for a page-specific selector or state that signals the content is ready.
  • Wait for fonts and any important images when they affect layout.
  • Disable animations and blinking cursors for stable visual comparisons.
  • Use scrolling full-page capture for content that is triggered by scrolling.
  • Restore the emulated device in a finally block so later tests start cleanly.

These controls improve repeatability but do not make a desktop browser engine identical to a mobile one.

7. Understand the limits of Chrome mobile emulation

Device emulation changes selected settings in desktop Chrome, including viewport, device scale factor, and user agent. It is useful for responsive layout review, but it does not reproduce every mobile operating system, browser engine, font rendering behavior, or hardware characteristic. WebdriverIO explicitly cautions that desktop browser engines differ from mobile ones in its Emulation documentation. Chrome’s Device Mode documentation likewise describes emulation as an approximation; use an actual device or real-device service when the result must reflect a real mobile browser.

8. Troubleshooting

Symptom Likely cause Fix
emulate('device', ...) fails or is unavailable BiDi is not enabled or supported by the browser/provider. Set webSocketUrl: true in capabilities and confirm WebDriver BiDi support across the session.
Screenshot contains only the visible area The core screenshot command captures the viewport. Use Visual Testing saveFullPageScreen() for a full-page image.
Lower sections or images are missing Content loads only after scrolling, or the page was captured before it was ready. Wait for content readiness and consider userBasedFullPageScreenshot: true to scroll and stitch.
Text or layout changes between runs Fonts, animations, cursors, or async content have not settled. Wait for fonts and meaningful content; disable animations and blinking cursors consistently for comparisons.
Element image is clipped or includes unexpected overlays Element screenshot boundaries or compositing behavior differ from expectations. Check the element’s visible rectangle and the Visual Testing BiDi biDiOrigin option, which affects document-layout versus viewport-compositing behavior.
Image resolution or quality differs after changing capture mode Visual Testing’s legacy screenshot method may produce different resolution or quality. Review enableLegacyScreenshotMethod and use one capture mode consistently for comparisons.
A later test still has the mobile viewport or user agent Emulation was not restored. Call the restore function returned by browser.emulate(), including in cleanup code.

9. Performance, reliability, and cost

Capture time depends on navigation, page readiness, fonts and images, and the screenshot method; no fixed duration is guaranteed by the documented APIs. A viewport capture avoids the extra scrolling and stitching used by user-based full-page capture. Full-page screenshots can require more work on long or scroll-dependent pages. For reliable automation, wait for the content that matters, keep capture settings consistent, and restore device state after each test. The documentation does not establish a universal performance figure or screenshot success rate.

With a local WebdriverIO and Chrome setup, the relevant costs are your browser and test infrastructure. For hosted screenshot captures, ScreenshotNeo offers a free plan with 1,000 screenshots per month and no card; paid plans start at $5 for 3,000 screenshots. Its plans include every feature, and yearly billing gives two months free.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. Make one GET request for a URL to receive a screenshot. Its API supports PNG, JPEG, WebP, or PDF output; see the ScreenshotNeo API documentation for parameters and configuration. The examples below use the required API call shape with a mobile viewport parameter; add or adjust parameters according to the current docs.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -d width=390 -d height=844 \
  -o mobile-shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={
        "access_key": "YOUR_API_KEY",
        "url": "https://example.com",
        "width": 390,
        "height": 844,
    },
    timeout=90,
)
r.raise_for_status()
with open("mobile-shot.webp", "wb") as image:
    image.write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com',
  width: '390',
  height: '844'
})
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`)
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`)
const image = Buffer.from(await res.arrayBuffer())
await import('node:fs/promises').then(({ writeFile }) =>
  writeFile('mobile-shot.webp', image)
)
  • Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed; response headers identify the page verdict and billing status.
  • An MCP server gives AI agents, including Claude and Cursor, tools to take screenshots, get page information, and capture PDFs.
  • 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000.

Learn about ScreenshotNeo, then sign up free for 1,000 screenshots a month with no card.

FAQ

How can I emulate an iPhone viewport in Chrome?

Use browser.emulate('device', 'iPhone 15') with a BiDi-enabled session, or select an available preset from the current WebdriverIO device list.

Does a Chrome emulation screenshot prove the site works on an iPhone?

No. It captures desktop Chrome using selected mobile settings. Use a real mobile browser or device when engine-specific behavior matters.

Can I use the core WebdriverIO screenshot API to save a full page?

The core takeScreenshot() command captures the viewport. Use Visual Testing’s saveFullPageScreen() when you need the full document.