Releases: webdriverio/visual-testing
Release list
@wdio/visual-service@9.3.0
Minor Changes
-
10d34d6: chore: refresh @wdio/* deps for the v9 maintenance line
Committers: 1
- Wim Selles (@wswebcreation)
Patch Changes
- Updated dependencies [10d34d6]
- @wdio/image-comparison-core@1.3.0
@wdio/visual-service@10.0.0
Major Changes
-
d2758ce: ### 💥 Breaking change: new image comparison engine
We replaced the engine that powers every visual comparison. This is a breaking change, so please read the migration note below before upgrading.
The problem
Visual tests were flaky. Tests failed on differences that are impossible to see by eye, like sub-pixel font rendering, 1px anti-aliasing on edges and small shadow shifts between runs. The old engine (resemble.js) compared raw RGB values, which does not match how human vision works, and on larger screenshots it quietly skipped about a third of the pixels. So you got failures that were not real, and in some cases real changes that could slip through.
On top of that, all the image handling (decode, crop, composite, rotate, resize) ran through
jimp, a large dependency that we only used a small slice of and that is no longer actively maintained.The solution
Two things changed under the hood:
- The comparison engine is now pixelmatch. It compares images the way the eye perceives them (in the YIQ colour space) and detects anti-aliasing by checking both images at once. Invisible rendering noise now passes, and real regressions still fail.
jimphas been removed completely. PNG decode and encode now go through the smallfast-pnglibrary, and the handful of image operations we still need (crop, composite, canvas, opacity, rotate, resize) live in a tiny internal helper. The bundled resemble file is gone too. The net effect is a much lighter dependency footprint with no loss in functionality.
What you need to do
- Your public API does not change.
checkScreen,checkElement,checkFullPageScreenand the matchers all work exactly as before, and the sameignoreoptions are supported. - Because the new engine measures differences differently, mismatch percentages will not match the old numbers exactly. You should re-run your suite once and re-accept your baselines so they are generated with the new engine. After that your tests should be noticeably more stable.
Also fixed in this release
- Top-row artifact on full page screenshots: Jimp's
contain()centred the image, shifting content by 1px and creating a false diff across the top row. Replaced with buffer-level padding that anchors content at (0,0). - Ignored region 1px under-coverage: The device-pixel size of an ignored region used
Math.floor, which could drop a pixel whencssSize * DPRhad a fractional part. Width and height now useMath.ceilso the full element is always covered. Position still usesMath.floor. - Comparison sensitivity matches what you were used to: Switching engines meant retuning how strict a comparison is. The pixelmatch threshold is now aligned with the old resemble tolerances, so a difference that used to fail still fails and one that used to pass still passes. The diff highlight also uses a single consistent colour instead of varying per run.
- Different image sizes no longer crash the comparison: When a baseline and the actual screenshot had slightly different dimensions, the old flow threw an error and you lost the result. Both images are now normalised to the same size before they are compared, so a size change is reported as a visual difference you can review instead of a hard failure.
- More reliable ignore regions with WebDriver BiDi: With BiDi the calculated element bounds can be off by a pixel or two, which sometimes left part of an ignored element just outside the ignored area and caused a false diff. The BiDi emulated flow now uses a larger
ignoreRegionPaddingso the whole element stays covered.
Committers: 1
- Wim Selles (@wswebcreation)
Patch Changes
-
6cd5742: ### Dependency updates
Updated dependencies across all packages to their latest compatible versions. This includes the WebdriverIO toolchain (
webdriverio,@wdio/*) to9.29.1, the TypeScript ESLint plugins to8.62.0, Vitest to3.2.6, and various other packages such assharp,@remix-run/*,fuse.jsandexpect-webdriverio. There are no functional or API changes.Committers: 1
- Wim Selles (@wswebcreation)
-
Updated dependencies [d2758ce]
-
Updated dependencies [6cd5742]
- @wdio/image-comparison-core@2.0.0
@wdio/visual-reporter@0.4.14
Patch Changes
-
6cd5742: ### Dependency updates
Updated dependencies across all packages to their latest compatible versions. This includes the WebdriverIO toolchain (
webdriverio,@wdio/*) to9.29.1, the TypeScript ESLint plugins to8.62.0, Vitest to3.2.6, and various other packages such assharp,@remix-run/*,fuse.jsandexpect-webdriverio. There are no functional or API changes.Committers: 1
- Wim Selles (@wswebcreation)
@wdio/ocr-service@2.2.10
Patch Changes
-
6cd5742: ### Dependency updates
Updated dependencies across all packages to their latest compatible versions. This includes the WebdriverIO toolchain (
webdriverio,@wdio/*) to9.29.1, the TypeScript ESLint plugins to8.62.0, Vitest to3.2.6, and various other packages such assharp,@remix-run/*,fuse.jsandexpect-webdriverio. There are no functional or API changes.Committers: 1
- Wim Selles (@wswebcreation)
@wdio/image-comparison-core@2.0.0
Major Changes
-
d2758ce: ### 💥 Breaking change: new image comparison engine
We replaced the engine that powers every visual comparison. This is a breaking change, so please read the migration note below before upgrading.
The problem
Visual tests were flaky. Tests failed on differences that are impossible to see by eye, like sub-pixel font rendering, 1px anti-aliasing on edges and small shadow shifts between runs. The old engine (resemble.js) compared raw RGB values, which does not match how human vision works, and on larger screenshots it quietly skipped about a third of the pixels. So you got failures that were not real, and in some cases real changes that could slip through.
On top of that, all the image handling (decode, crop, composite, rotate, resize) ran through
jimp, a large dependency that we only used a small slice of and that is no longer actively maintained.The solution
Two things changed under the hood:
- The comparison engine is now pixelmatch. It compares images the way the eye perceives them (in the YIQ colour space) and detects anti-aliasing by checking both images at once. Invisible rendering noise now passes, and real regressions still fail.
jimphas been removed completely. PNG decode and encode now go through the smallfast-pnglibrary, and the handful of image operations we still need (crop, composite, canvas, opacity, rotate, resize) live in a tiny internal helper. The bundled resemble file is gone too. The net effect is a much lighter dependency footprint with no loss in functionality.
What you need to do
- Your public API does not change.
checkScreen,checkElement,checkFullPageScreenand the matchers all work exactly as before, and the sameignoreoptions are supported. - Because the new engine measures differences differently, mismatch percentages will not match the old numbers exactly. You should re-run your suite once and re-accept your baselines so they are generated with the new engine. After that your tests should be noticeably more stable.
Also fixed in this release
- Top-row artifact on full page screenshots: Jimp's
contain()centred the image, shifting content by 1px and creating a false diff across the top row. Replaced with buffer-level padding that anchors content at (0,0). - Ignored region 1px under-coverage: The device-pixel size of an ignored region used
Math.floor, which could drop a pixel whencssSize * DPRhad a fractional part. Width and height now useMath.ceilso the full element is always covered. Position still usesMath.floor. - Comparison sensitivity matches what you were used to: Switching engines meant retuning how strict a comparison is. The pixelmatch threshold is now aligned with the old resemble tolerances, so a difference that used to fail still fails and one that used to pass still passes. The diff highlight also uses a single consistent colour instead of varying per run.
- Different image sizes no longer crash the comparison: When a baseline and the actual screenshot had slightly different dimensions, the old flow threw an error and you lost the result. Both images are now normalised to the same size before they are compared, so a size change is reported as a visual difference you can review instead of a hard failure.
- More reliable ignore regions with WebDriver BiDi: With BiDi the calculated element bounds can be off by a pixel or two, which sometimes left part of an ignored element just outside the ignored area and caused a false diff. The BiDi emulated flow now uses a larger
ignoreRegionPaddingso the whole element stays covered.
Committers: 1
- Wim Selles (@wswebcreation)
Patch Changes
-
6cd5742: ### Dependency updates
Updated dependencies across all packages to their latest compatible versions. This includes the WebdriverIO toolchain (
webdriverio,@wdio/*) to9.29.1, the TypeScript ESLint plugins to8.62.0, Vitest to3.2.6, and various other packages such assharp,@remix-run/*,fuse.jsandexpect-webdriverio. There are no functional or API changes.Committers: 1
- Wim Selles (@wswebcreation)
@wdio/image-comparison-core@1.3.0
Minor Changes
-
10d34d6: chore: refresh @wdio/* deps for the v9 maintenance line
Committers: 1
- Wim Selles (@wswebcreation)
@wdio/visual-service@9.2.4
Patch Changes
-
60997df: fix: prevent false emulation detection when checkElement is called inside an iframe after switchFrame
Committers: 1
- Taro.Nonoyama(@n2-freevas)
-
Updated dependencies [60997df]
- @wdio/image-comparison-core@1.2.4
@wdio/image-comparison-core@1.2.4
Patch Changes
-
60997df: fix: prevent false emulation detection when checkElement is called inside an iframe after switchFrame
Committers: 1
- Taro.Nonoyama(@n2-freevas)
@wdio/visual-service@9.2.3
Patch Changes
-
c56e1ae: ## #1146 Fix BiDi element screenshots missing composited layers (scrollbars, fixed/sticky overlays)
Root cause
When
checkElement/saveElementis used with the WebDriver BiDi protocol, the screenshot was taken withbrowsingContext.captureScreenshotusingorigin: 'document'. This renders the document layout independently of the browser's compositor, which means composited layers are never included — element-level scrollbars,position: fixed/position: stickyoverlays, and elements with awill-changeCSS property all render as invisible or without their correct visual state.The switch to
origin: 'document'was introduced in an earlier fix (commit227f10a) to avoid azero dimensionserror that occurred whenorigin: 'viewport'was used for elements that were outside the visible viewport. That fix was correct for out-of-viewport elements, but it also silently broke composited-layer capture for all elements.Fix: new
biDiOriginmethod optionA new method-level option
biDiOriginhas been added tosaveElement/checkElement. It is BiDi-only and ignored for the legacy WebDriver screenshot path.Value Behaviour 'document'(default)Previous behaviour — works for any element position but composited layers (scrollbars, overlays, will-change) are not captured'viewport'Captures the composited frame as the browser painted it — scrollbars, fixed/sticky overlays and will-changelayers are included. The element must be visible in the viewport; descriptive errors are thrown when it is notUsage
// Capture an element with its scrollbar / overlay visible: await browser.checkElement(element, "myTag", { biDiOrigin: "viewport" }); await browser.saveElement(element, "myTag", { biDiOrigin: "viewport" });
Error messages when
biDiOrigin: 'viewport'cannot produce a valid screenshotElement larger than the viewport — must fall back to
'document':[BiDi viewport screenshot] The element dimensions (1400x800px) exceed the viewport (1280x720px). You must use the default `biDiOrigin: 'document'` for this element. Note: with `'document'` origin, composited layers such as scrollbars, fixed/sticky overlays, and elements using `will-change` may not appear in the screenshot.Element not in the viewport at all — needs scrolling:
[BiDi viewport screenshot] The element is not in the viewport (element: x=0, y=900, 300x200px; viewport: 1280x720px). Call `element.scrollIntoView()` before taking the screenshot, or set `autoElementScroll: true`.Element partially outside the viewport but fits — needs to be scrolled fully into view:
[BiDi viewport screenshot] The element is not fully visible in the viewport (element: x=-20, y=100, 300x200px; viewport: 1280x720px). The element fits within the viewport — scroll it fully into view by calling `element.scrollIntoView()` or setting `autoElementScroll: true`.Committers: 1
- Wim Selles (@wswebcreation)
-
Updated dependencies [c56e1ae]
- @wdio/image-comparison-core@1.2.3
@wdio/image-comparison-core@1.2.3
Patch Changes
-
c56e1ae: ## #1146 Fix BiDi element screenshots missing composited layers (scrollbars, fixed/sticky overlays)
Root cause
When
checkElement/saveElementis used with the WebDriver BiDi protocol, the screenshot was taken withbrowsingContext.captureScreenshotusingorigin: 'document'. This renders the document layout independently of the browser's compositor, which means composited layers are never included — element-level scrollbars,position: fixed/position: stickyoverlays, and elements with awill-changeCSS property all render as invisible or without their correct visual state.The switch to
origin: 'document'was introduced in an earlier fix (commit227f10a) to avoid azero dimensionserror that occurred whenorigin: 'viewport'was used for elements that were outside the visible viewport. That fix was correct for out-of-viewport elements, but it also silently broke composited-layer capture for all elements.Fix: new
biDiOriginmethod optionA new method-level option
biDiOriginhas been added tosaveElement/checkElement. It is BiDi-only and ignored for the legacy WebDriver screenshot path.Value Behaviour 'document'(default)Previous behaviour — works for any element position but composited layers (scrollbars, overlays, will-change) are not captured'viewport'Captures the composited frame as the browser painted it — scrollbars, fixed/sticky overlays and will-changelayers are included. The element must be visible in the viewport; descriptive errors are thrown when it is notUsage
// Capture an element with its scrollbar / overlay visible: await browser.checkElement(element, "myTag", { biDiOrigin: "viewport" }); await browser.saveElement(element, "myTag", { biDiOrigin: "viewport" });
Error messages when
biDiOrigin: 'viewport'cannot produce a valid screenshotElement larger than the viewport — must fall back to
'document':[BiDi viewport screenshot] The element dimensions (1400x800px) exceed the viewport (1280x720px). You must use the default `biDiOrigin: 'document'` for this element. Note: with `'document'` origin, composited layers such as scrollbars, fixed/sticky overlays, and elements using `will-change` may not appear in the screenshot.Element not in the viewport at all — needs scrolling:
[BiDi viewport screenshot] The element is not in the viewport (element: x=0, y=900, 300x200px; viewport: 1280x720px). Call `element.scrollIntoView()` before taking the screenshot, or set `autoElementScroll: true`.Element partially outside the viewport but fits — needs to be scrolled fully into view:
[BiDi viewport screenshot] The element is not fully visible in the viewport (element: x=-20, y=100, 300x200px; viewport: 1280x720px). The element fits within the viewport — scroll it fully into view by calling `element.scrollIntoView()` or setting `autoElementScroll: true`.Committers: 1
- Wim Selles (@wswebcreation)