Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use WebdriverIO’s @wdio/visual-service to capture deliberate screen, element, or full-page images and compare them with reviewed baselines in CI. Reliable results depend less on taking screenshots than on controlling fonts, animation, data, browser and operating-system rendering, then treating every diff as evidence to investigate before changing a baseline.

What you will build

This guide sets up the WebdriverIO Image Comparison (Visual Regression Testing) Service, adds a component-level assertion, and establishes a review workflow suitable for continuous integration. The service supports Mocha, Jasmine and CucumberJS through WebdriverIO’s normal runner (framework documentation).

  • Element checks protect a focused component such as a purchase panel.
  • Screen checks protect the current viewport and its composition.
  • Full-page checks protect below-the-fold layout, provided the page can be made deterministic.
  • Save methods capture an image without asserting it against a baseline; check methods compare and fail when the difference exceeds the configured policy (methods reference).

Install the visual service

Add the package as a development dependency, using a version compatible with the WebdriverIO packages already in your project:

npm install --save-dev @wdio/visual-service

WebdriverIO’s current visual guide describes version 10 and later as using Pixelmatch and fast-png, with no extra image-comparison system dependency beyond the normal project requirements (Visual Testing). Keep the package versions aligned rather than mixing major WebdriverIO releases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Configure stable baseline and output paths

Register the service in wdio.conf.ts. The following is a starting shape; choose paths that match your repository and CI artifact conventions.

import path from 'node:path'

export const config = {
  services: [[
    'visual',
    {
      baselineFolder: path.join(process.cwd(), 'tests', 'baseline'),
      formatImageName: '{tag}-{logName}-{width}x{height}',
      screenshotPath: path.join(process.cwd(), 'tmp'),
      savePerInstance: true,
    },
  ]],
}

baselineFolder is the reviewed source of truth. screenshotPath holds current, actual and diff images generated during a run. A deterministic formatImageName prevents two tests, viewports or browser instances from silently sharing a file. Commit baselines to version control and publish actual/diff images as CI artifacts when a job fails. The service options, including font and full-page controls, are documented at Service Options.

Add a visual checkpoint

Navigate to a known state, wait for the application to be ready, and then choose the smallest surface that expresses the requirement.

describe('product page visual behavior', () => {
  it('keeps the purchase panel visually stable', async () => {
    await browser.url('/products/example')
    await browser.setWindowSize(1280, 900)

    const panel = await $('.purchase-panel')
    await panel.waitForDisplayed()
    await browser.checkElement(panel, 'purchase-panel')
  })
})

The selector and tag should be stable. Avoid generated class names and text that changes with localization or test data. A check compares with the matching baseline; a save operation is appropriate when you intentionally need an image without a pass/fail comparison.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose the scope deliberately

Method Use it for Trade-off
checkElement Component contracts, such as a navigation bar or checkout card Failures are localized; surrounding layout is not covered
checkScreen Viewport composition at a defined size Covers the visible page but not content below the fold
checkFullPageScreen Long-page layout and below-the-fold sections More exposed to lazy content, sticky elements and dynamic regions
Save methods Capturing a reference or diagnostic image without assertion No baseline comparison or test failure

Use an element check for a component regression, a screen check for a route’s primary composition, and a full-page check only when the entire document is part of the contract (supported methods).

Make captures deterministic

A screenshot is a rendering result, not an abstract representation of your CSS. Control the inputs that can change that result.

Wait for fonts and meaningful readiness

Fonts may load after the page’s load event. The service’s waitForFontsLoaded option defaults to true to reduce font-rendering differences. Still wait for an application-specific readiness signal: for example, a product API response, a visible heading, or disappearance of a loading indicator. Fixed test data, dates, locale, authentication state and viewport dimensions prevent unrelated changes from becoming diffs.

Disable motion that is not under test

Animations and transitions can capture different intermediate frames. Disable them for visual snapshots when animation itself is not the requirement, using the service’s animation-related option or a test-only stylesheet. If motion is the subject of the test, synchronize on a known state instead of broadly hiding it. See the documented options at Service Options.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Handle lazy loading and full-page capture

For pages that load images or sections only after scrolling, the service supports userBasedFullPageScreenshot: it scrolls through viewport-sized captures and stitches them. The default desktop full-page approach uses WebDriver BiDi. Choose the user-based mode when scroll position or lazy loading changes what a visitor actually sees.

Keep volatile content out of the contract

Freeze clocks and random data where possible. For unavoidable timestamps, rotating adverts or personalized recommendations, target a stable element instead, mask the region with a narrowly scoped ignore option, or provide deterministic fixtures. Do not solve volatility by accepting a large mismatch percentage: on a large image that allowance can hide a missing button or another substantial defect (Considerations).

Compare like rendering environments

Create and consume a baseline with the same browser family and version, operating system, viewport, device-pixel ratio and relevant fonts whenever practical. Browser updates can alter font rasterization; comparing screenshots from different operating systems can produce differences unrelated to your application. If an intentional platform change is part of a release, review the affected diffs as a controlled baseline change rather than silently replacing everything.

Use authentic mobile contexts

A desktop browser narrowed to a phone width is not equivalent to a mobile browser. When mobile rendering matters, run the appropriate mobile or native/hybrid context through WebdriverIO’s Appium support. WebdriverIO explicitly cautions: “Do not attempt to simulate mobile screen sizes by resizing desktop browsers and treating them as mobile browsers” (Considerations). Keep separate, clearly named baselines for materially different targets.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Establish and maintain baselines

  1. Generate an initial set. Run the visual tests in the exact environment intended for comparison and inspect every image before committing it.
  2. Review failures. Examine baseline, actual and diff images together. Identify whether the cause is an intentional design change, an application defect, or an unstable capture condition.
  3. Fix the cause first. Wait for fonts, stabilize data, correct the layout, or narrow the capture scope before touching the baseline.
  4. Update only reviewed images. Use the documented --update-visual-baseline flow for the specific tests that changed. Do not regenerate the complete directory as a shortcut.
  5. Record why. In the change description, state the intended UI change, affected viewport or platform, and why the new image is correct.

WebdriverIO changed its comparison engine from ResembleJS to Pixelmatch in v10. The documentation notes that mismatch percentages can therefore change after an upgrade even when your application does not; review diffs and expect deliberate baseline work when upgrading (Visual Testing).

Make CI reviewable

Run visual jobs with pinned browser and operating-system images, fixed viewport sizes and the same fonts used to create baselines. Store the test report plus baseline, actual and diff images as artifacts. The Visual Reporter includes test cases, browser and test metadata, comparison results and difference images. Its report must be served locally to view; it is not intended to be opened directly as a file (Visual Reporter).

Require a human review for a failed diff. A red build is a prompt to inspect evidence, not an instruction to accept the current screenshot. Conversely, a green build only says the captured conditions matched the baseline; it does not prove that an uncaptured route or device is correct.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common failures and fixes

Symptom Likely cause Fix
Everything differs after a browser or runner upgrade Rendering changes or Pixelmatch mismatch-percentage changes Compare the three images, check the upgrade notes, and update only reviewed baselines.
Text moves or changes width between runs Fonts were not ready or differ between environments Keep waitForFontsLoaded enabled, wait for readiness, and install/pin the same fonts.
Only animated areas fail intermittently Capture occurred at different animation frames Disable nonessential animation or synchronize on a stable state.
Full-page image misses content Lazy loading depends on scrolling Use userBasedFullPageScreenshot and verify that scroll-triggered content has appeared.
Large page passes despite an obvious missing control Mismatch tolerance is too broad Remove or narrow the tolerance and use a targeted ignore region only for known volatility.
Mobile baseline fails while desktop is stable Desktop resizing was used as a mobile substitute Run the target mobile/browser context and maintain a separate baseline.
Reporter appears blank when opened The report was opened as a local file Serve the report through a local HTTP server as directed by the reporter documentation.

Or skip the browser setup

If you need an image or PDF from a URL rather than an assertion inside a WebdriverIO suite, ScreenshotNeo provides a one-request website screenshot API and an MCP server for AI agents. Before capture it accepts the cookie or consent banner and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a WebdriverIO-adjacent workflow, you can still control viewport, device presets, full-page capture, element selectors, waits, custom CSS and JavaScript, headers, cookies, user agent, timezone, geolocation, blocking rules, image format, PDF settings, caching and asynchronous webhooks. An MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the complete parameter reference and option names in the ScreenshotNeo documentation. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Should every route get a full-page baseline?

No. Baseline the smallest surface that represents the risk. Add full-page coverage for routes where below-the-fold structure is itself a requirement.

Can I share one baseline between macOS and Linux?

Only if you have verified that rendering is equivalent for your supported browsers and fonts. Otherwise keep platform-specific baselines or standardize the execution environment.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Is a visual diff a defect by definition?

No. It is evidence of a rendering difference. The reviewer must determine whether the difference is intended, environmental or an application regression.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.