ScreenshotNeo

BlogHow-to

How to Run Visual Tests with WebdriverIO

Set up WebdriverIO visual tests, create and review screenshot baselines, and keep comparisons reliable across browsers and CI.

By the ScreenshotNeo team4 October 20269 min read

Use WebdriverIO’s @wdio/visual-service to capture a page or element, compare it with a saved baseline, and review any difference. Install the service, register it in your WDIO configuration, then call a check method such as browser.checkScreen('home') after your application reaches a stable state. The first check can create the baseline automatically.

This guide sets up local visual checks, explains baseline review and update, and covers the options that matter for stable comparisons. The service supports WebdriverIO test frameworks including Mocha, Jasmine, and CucumberJS. Its documented targets include desktop browsers, Appium-backed mobile browsers, native apps, and hybrid apps; native and hybrid setups need context-specific configuration.

1. Install the visual service

Install the package as a development dependency in your project:

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

Make sure the project already has a working WebdriverIO runner and browser or device configuration. The visual service adds screenshot save and check commands, along with visual snapshot matchers when configured. Consult the official WebdriverIO visual testing guide for the setup that matches your installed WebdriverIO version.

2. Register the service and choose baseline paths

Add the service to your WebdriverIO configuration. This representative configuration sets a baseline directory, an actual screenshot directory, per-instance output, and a filename containing browser and environment information:

// wdio.conf.js
export const config = {
  // Keep the rest of your WDIO configuration here.
  services: [
    ['visual', {
      baselineFolder: './tests/visual/baselines',
      screenshotPath: './tests/visual/actual',
      savePerInstance: true,
      formatImageName: '{tag}-{browserName}-{browserVersion}-{platformName}-{width}x{height}-{dpr}',
      autoSaveBaseline: true
    }]
  ]
};

Use the option names and supported filename placeholders documented for your installed service version. formatImageName formats image names; it does not set the destination directory. Configure paths with baselineFolder, screenshotPath, or the per-method folder options. A capability logName can help distinguish multiple browser or device configurations.

By default, autoSaveBaseline is true. On an initial check with no baseline, the service can save the captured image as the baseline. If your team requires explicit baseline creation and review, disable automatic saving and follow a deliberate save-and-review workflow. Avoid pairing save and compare calls just to initialize a baseline when a check method already performs the first-run baseline creation.

3. Write a visual test

Navigate to a deterministic state, wait for application-specific content to settle, and call the check method that matches the desired scope. For example, with Mocha:

// test/specs/home.visual.js
describe('Home page visual appearance', () => {
  it('matches the home page baseline', async () => {
    await browser.url('/');
    await $('[data-testid="home-ready"]').waitForDisplayed();

    await browser.checkScreen('home');
  });
});

The readiness selector is application-specific. Waiting for it is usually more reliable than relying on a fixed delay: it ties capture to the page state your test needs. Use fixtures, stable data, predictable authentication, and a fixed viewport so the same test state is recreated in later runs.

Choose among the main capture scopes:

  • browser.checkScreen('name') compares the current screen or viewport.
  • browser.checkElement(selector, 'name') compares a focused component or region.
  • browser.checkFullPageScreen('name') compares a full-page capture.

Check methods capture and compare; you do not need a separate save call before every check. Save methods are useful when you want to store a screenshot without comparing it. WebdriverIO also documents visual snapshot matchers such as toMatchScreenSnapshot and toMatchElementSnapshot; use the matcher style if it better fits your assertion conventions. See Writing Tests, Methods, and Expect WebdriverIO for the current APIs and syntax.

4. Create, inspect, and update baselines

Run the visual test once. If no baseline exists and automatic baseline saving is enabled, the check creates one. Review the baseline and the captured actual image as test artifacts. On subsequent runs, inspect the baseline, actual, and generated diff when a check fails.

  1. Run the test in the intended browser, platform, viewport, and device configuration.
  2. For a first run, inspect the resulting baseline before treating it as the accepted reference.
  3. When a later check reports a difference, inspect the actual image and diff to determine whether the change is expected or a regression.
  4. Only after review, update the baseline using the documented --update-visual-baseline flag.
  5. Review the resulting changes in version control so baseline updates remain visible to the team.

The update flag copies actual images into the baseline and allows the changed checks to pass. It is a baseline replacement operation, so run it only after reviewing the visual change. The WebdriverIO visual testing FAQ covers baseline update behavior.

5. Keep comparisons stable

Match the rendering environment

Compare screenshots from the same platform and browser configuration. A Chrome baseline produced on macOS can differ from Chrome on Ubuntu or Windows because rendering, fonts, and system details vary. Keep browser, browser version, operating system, viewport, device, and device pixel ratio consistent between baseline creation and comparison. A browser upgrade can change font rendering, so treat it as a reason to review relevant diffs.

WebdriverIO advises against headless browsers for this service because the goal is to compare the view rendered for an end user. Browser resizing is also not a substitute for testing in a real mobile browser or device when mobile rendering is the subject of the test. See WebdriverIO’s visual testing considerations.

Wait for fonts and control animation

The service waits for fonts to load by default, since capturing before asynchronous fonts settle can produce different text rendering. Other documented controls include disabling CSS animations, hiding scrollbars, hiding blinking carets, ignoring selected regions, and enabling layout testing, which makes text transparent to focus comparison on layout. Apply these controls only when they fit what the test is meant to catch. Ignoring a large region can conceal a real regression.

Choose a full-page capture strategy

For desktop web, the default full-page capture uses WebDriver BiDi without scrolling. This is suitable when the page is fully rendered without scroll-triggered behavior. If lazy-loaded content or rendering triggered by scrolling is missing, enable userBasedFullPageScreenshot. That strategy simulates scrolling, captures viewport images, and stitches them together. It can take longer, so use it when the page behavior requires it. Full-page options and defaults are described in Service Options.

Set comparison tolerance carefully

The comparison options include controls for anti-aliasing differences at small text and shape edges, as well as mismatch thresholds. Use a tolerance only when it matches the purpose of the test. A small percentage on a large image can still permit an important control or layout change. Review the diff image instead of treating a percentage as a complete quality judgment. WebdriverIO v10 changed its comparison engine from ResembleJS to Pixelmatch; mismatch percentages can therefore differ from v9. Review diffs after an upgrade and update baselines selectively. The current comparison options and method options list version-specific controls.

6. Choose the right capture scope

Scope Use it for Watch for
Element A component, card, navigation bar, or other focused region Make sure the selector identifies the intended element consistently.
Screen or viewport Above-the-fold layout and the user’s current view Keep viewport dimensions and device pixel ratio fixed.
Full page Page structure from top to bottom Use scroll-and-stitch when lazy content or scroll-triggered rendering needs it; it may take longer.

7. Run it in CI

Run the visual spec in CI with the same browser and platform conditions used to generate its baseline. Keep the baseline files available to the runner, and retain actual and diff images when a check fails so a reviewer can diagnose the change. A CI environment change, browser update, or font change can create broad image differences even when application code did not cause them; review that possibility before replacing baselines.

For multiple browser or device configurations, use distinct instance identity in output names, such as browser name/version, platform, viewport, and device pixel ratio. That prevents one environment’s screenshot from being mistaken for another’s reference. Keep the set of environments intentional: each extra configuration creates more captures and baseline artifacts to maintain.

8. Troubleshooting

Symptom Likely cause What to do
The visual command or matcher is unavailable The service is not installed or registered, or the test setup does not load its commands. Confirm @wdio/visual-service is installed as a development dependency and registered in WDIO configuration. Check setup instructions for your WDIO version.
The first check fails because there is no baseline Automatic baseline saving may be disabled, or the baseline path may not be writable or correct. Check autoSaveBaseline and baselineFolder. Create and review the baseline through the workflow your team chose.
Many pixels differ on an unchanged page The browser, OS, fonts, viewport, device pixel ratio, or browser version differs from baseline generation. Restore matching conditions and inspect the images. If the rendering environment intentionally changed, review and selectively update baselines.
Text or layout shifts between runs The capture may happen before application data, fonts, or asynchronous UI has settled. Wait for an application-specific ready condition. The service waits for fonts by default; also stabilize test data and authentication state.
Below-the-fold content is missing Content may load only after scrolling, while the default desktop full-page capture does not scroll. Try userBasedFullPageScreenshot for scroll-triggered or lazy-loaded content, and allow for the longer capture.
Checks fail after upgrading the visual service Version changes can affect image comparison; v10 switched from ResembleJS to Pixelmatch. Review actual and diff images, verify configuration against current docs, and update only baselines whose changes are accepted.
A baseline appears to be overwritten or belongs to another browser Output identity or paths may not distinguish instances. Use per-instance output and filenames that include the browser/device configuration. Set paths with path options, not formatImageName.
A threshold passes a visibly important change The mismatch allowance is too permissive for the screenshot size or test purpose. Reduce tolerance and inspect diffs. Keep ignored regions narrow and specific.

9. Reliability, runtime, and maintenance

Visual tests are most useful when a failure points to a reviewable rendering change. Make the initial state repeatable, capture after the page is ready, and keep the browser environment consistent. Prefer element checks for isolated components when whole-page changes would create noisy diffs; use full-page checks when page-wide structure is what you need to protect.

Capture scope and full-page strategy affect runtime: scroll-and-stitch takes longer than the default BiDi full-page capture. More browser/device configurations also mean more captures and baselines to review. Keep screenshot artifacts for failed runs so engineers can distinguish application changes from environment drift. Review baselines after browser, operating system, device, font, or comparison-engine changes instead of bulk-accepting all diffs.

Local WebdriverIO comparisons use the project’s browser and test infrastructure. They do not require a separate hosted visual review service. A hosted integration is optional when a team has a specific need for hosted browser/device execution or shared review workflow. WebdriverIO documents an optional Percy integration; BrowserStack’s integration documentation describes different compatibility ranges for its SDK integration paths, so verify the current vendor instructions for the exact stack before adopting it: BrowserStack Percy with WebdriverIO.

Or skip the browser setup

If your goal is to capture a clean page image rather than compare application builds inside WDIO, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns an image or PDF; its API documentation describes the request options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; each cleanup step can be turned off. Bot checks, blank pages, and failed loads are never billed. An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots.

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

FAQ

Do I need to save a screenshot before calling a check method?

No. A check method captures and compares. Use a save method when you specifically need an image without a comparison.

Can I use the service with CucumberJS or Jasmine?

Yes. The service is framework-agnostic across WebdriverIO-supported frameworks, including Mocha, Jasmine, and CucumberJS.

Should I use headless Chrome for visual baselines?

WebdriverIO advises against headless browsers for this service because the purpose is to compare the view rendered for an end user. Keep the capture environment consistent with the one represented by the baseline.

Is Percy required for WebdriverIO visual testing?

No. The visual service supports local screenshot comparison. Percy is an optional hosted integration; check current compatibility documentation before selecting an integration path.

Official references