Repository navigation
Releases: testivai/testivai-oss
Release list
@testivai/witness@2.6.0
Minor Changes
-
1ac04ef: Capture health: an unhealthy capture is never "render noise". A web font that failed and fell back to a system font, or a stylesheet that answered 404, leaves the DOM identical and the computed styles matching (the style digest hashes the declared
font-family, not the font that rendered), so its small, scattered diff read "likely render noise" andnoiseAutoPasspassed it. The Playwright JS/TS adapter andtestivai witness <url>now record, after each screenshot, what failed to load ashealthin the capture'smetadata.json:document.fonts.statusand every font face that errored,<link rel=stylesheet>elements that did not load, broken<img>s, and requests that answered 400 or more (Resource Timing, Chromium 109+), each URL as host and path only, never the query string. The crawler also merges the failed requests it saw on the network (CDP), cross-origin ones included. Approving freezes the capture's health into the baseline'smetadata.json. results.json schema 2.7.0 (additive) addssnapshots[].captureHealth(both blocks,unhealthy:"candidate"/"baseline"/"both"/null, andreasonsandwarningsin plain words), present when either side recorded health, new snapshots included. Web fonts still loading, a font face that failed, a failed stylesheet, or a failed stylesheet request make a capture unhealthy (a failed font request only with a font face that failed, since a face can load from its nextsrc; afetch()/XHR request or a<link>that no<link rel=stylesheet>names and that does not end in.cssis never a stylesheet):dom.noiseHintis then false and no pass criterion (noiseAutoPass,maxDiffPercent/maxDiffPixels,shiftTolerance) applies while pixels differ; a zero-pixel diff still passes. The HTML report,@testivai/mcp(get_visual_resultsverdicts,explain_snapshot's newlayers.healthand guidance, thetriage-visual-diffrubric and the review prompt; unhealthy captures are never grouped) and the GitHub Action comment read Unhealthy capture with what failed, before any other reading (rendering drift too, when both are true), and call out an unhealthy baseline on every compare against it ("re-baseline from a healthy run"). Broken images, failed scripts and other requests are warnings only.testivai approvewarns when the capture it promotes is unhealthy.@testivai/witnessexportsHEALTH_PROBE_EXPRESSION,parseHealth,mergeResourceFailuresand the types, and@testivai/witness/reportthe readings (assessHealth,snapshotCaptureHealth,captureHealthReading,isUnhealthyCapture,approveHealthWarning). The WebdriverIO, Selenium, Python, Ruby and Java adapters do not record health yet: their captures read "not checked" and compare exactly as before. -
117bac5: New opt-in config
elementIdAttributes(for example["data-testid"]) anchors element-map paths on attributes your tests already set. An element's path starts at its nearest ancestor-or-self carrying one of them with a non-empty value no other element in the page shares:div[data-testid="board"] > div.css-146c3p1instead of abody > div > div.css-g5y9jx > div.css-g5y9jx:nth-of-type(2) > …chain. Content inserted above an anchored element no longer renumbers its path, so an element that only moved reads as a shift (with the page shift detected) instead of a change, and the style check pairs the same elements. The list order is the precedence; a duplicated value is skipped and the path continues upward, so paths stay unique; values are escaped as CSS strings (>included), so each anchored path is a working selector. Unset, nothing changes: the attributes are never read andelements.json, the style digests,results.jsonand the report are byte-identical. The Playwright, Selenium and WebdriverIO adapters read it from.testivai/config.jsonand record it in the capture'smetadata.json(only when set); approving freezes it into the baseline, and styles are compared only when both sides used the same setting (dom.styleCheck: "unavailable"until baselines are re-approved after a change), so paths built two ways never produce a style verdict on part of the page.@testivai/witnessexportsnormalizeElementIdAttributes;buildElementMapExpressiontakes the list as a third argument. -
f3e3fc7: Native app screens (experimental):
@testivai/witness-webdriveriocaptures native Android and iOS app screens in an Appium session. In a session on Android or iOS that names no browser and is in theNATIVE_APPcontext (or, when the context cannot be read, was started withapp,appPackageorbundleId),witness()runs no page script: a native screen has no page, Appium rejects them there and WebdriverIO resends each failed one three times (~1.5 s each, ~8 s per capture on an Android 14 emulator). The capture is the whole screen, pixel diff only. The status bar is masked per capture (masks: [{ "top": <px> }]in the capture'smetadata.json): on Android to the bottom of the bar's frame in the window manager at capture time (mobile: getSystemBars), so a rotation is followed; UiAutomator2's own reading (mobile: viewportRect, the session capabilities) is taken once at the session start and came back 63 px for a 136 px bar in one emulator run, so it is only the fallback. On iOS it comes frommobile: viewportRect. The environment block comes from the capabilities, withbrowser.name: "native-app", capture methodscreen:appiumand a newdevicesection (platform, OS version, model, the requested device name, the Appium driver, screen size, density). The snapshot is keyed<name>__<platform>-<device>-<osMajor>(login__android-pixel_7-15) unless the call gives avariant. New optionsnative(trueforces native mode,falsekeeps the web path) andmaskStatusBar(defaulttrue);WitnessBrowsergains the optionalgetContext(). A hybrid app in a web view, and every browser session, keep the web path, and a browser session makes no extra call. Verified on an Android 14 emulator in CI; iOS is not verified.@testivai/witness: results.json schema 2.9.0 (additive) adds thedevicesection of a snapshot's environment blocks and capture methodscreen:appium. The device's platform, OS version, model, screen and density can change rendering; the device name and the Appium driver are context, and so isos(the machine running the tests) when both sides are native app captures. The report words device changes ("device sdk_gphone64_x86_64 → Pixel 7, device OS 14 → 15") and the capture method ("full screen (Appium)"). New exports:appiumEnvironment,isNativeAppCapabilities,mobilePlatform,namesBrowser,nativeAppDevice,nativeAppVariant,statusBarFromCapabilities,statusBarFromSystemBars,statusBarFromViewportRect,CAPTURE_METHODS.APPIUM_SCREENand theDeviceEnvironmenttype. Guide: docs/guides/native-mobile.md. -
9eaa9f4: A failing test no longer leaves a pull request without a visual report.
testivai merge-capturesnow writestestivai-merge.jsonbeside the captures it merges in.testivai/temp/(the shard total, the shards seen and the shards missing; exported asMERGE_RECORDand theMergeRecordtype), so a later step can tell a merge that lacks a shard from the whole suite. Thetestivai/testivai-oss@v1action uses it: when a run wrote noresults.json, the action compares the captures in.testivai/temp/itself, but never one shard's slice or an incomplete merge. In capture-only mode, the Playwright reporter's hint now says to run the compare step withif: ${{ !cancelled() }}(and on a job thatneedsthe shards, too), since a CI step runs only on success by default. -
a3278e1: The report writes
images/<name>/thumb.pngfor eachchangedsnapshot: the changed area of the baseline, the current capture and the diff side by side, 2 px apart, at most 800×400 px (area-average downscale, never upscaled), small enough to show inline where a full-page diff is not, such as a pull request comment. The area is the union of the up to three largest diff regions padded 24 px, or without regions a 400 px band from the first differing row; on a size change it is on the padded canvas. results.json schema 2.6.0 (additive) records it assnapshots[].thumbPath. Never written for passed (auto-passed included) or new snapshots; the HTML report does not show it.parseReportDataacceptsthumbPathas text and, like the other image paths, never copies it from the file. -
62964eb: Four fixes found running React Native screens through react-native-web.
- A claimed shift is checked against the pixels. Element maps pair elements by path, and content inserted above an element renumbers its
:nth-of-typepath, which can pair it with a look-alike neighbour (same size and styles, different content). That read as a shift by a wrong distance (−16 px while the content moved +64 px) with a wrongpageShift. A pair now counts as a shift, inregionsand inpageShift, only when the element's pixels match at its new position: at most 1% differ, at the diff's colour threshold, masks excluded, best of ±1 px. In real Chrome true shifts differed on 0.00% of their pixels (fractional moves included), look-alike neighbours on 4.8–6.7%. Shifts that were right before are unchanged; wrong ones now read aschange, which also keepsshiftTolerancefrom passing them. <script>attribute values are not a DOM change. A script never renders, but a bundle renamed by content hash (Expo'sindex-<hash>.js), itsintegrityand a per-requestnoncecounted as attribute changes, so a stylesheet-only change did not read style-only. Their presence still counts, and so does a script added or removed. A snapshot whose only DOM difference was a script attribute now hasdom.changed: false, so its noise hint follows the style check.- **The Playwright adapter restores the device emulation its full-page capture ...
- A claimed shift is checked against the pixels. Element maps pair elements by path, and content inserted above an element renumbers its
@testivai/witness-webdriverio@0.5.0
Minor Changes
-
117bac5: New opt-in config
elementIdAttributes(for example["data-testid"]) anchors element-map paths on attributes your tests already set. An element's path starts at its nearest ancestor-or-self carrying one of them with a non-empty value no other element in the page shares:div[data-testid="board"] > div.css-146c3p1instead of abody > div > div.css-g5y9jx > div.css-g5y9jx:nth-of-type(2) > …chain. Content inserted above an anchored element no longer renumbers its path, so an element that only moved reads as a shift (with the page shift detected) instead of a change, and the style check pairs the same elements. The list order is the precedence; a duplicated value is skipped and the path continues upward, so paths stay unique; values are escaped as CSS strings (>included), so each anchored path is a working selector. Unset, nothing changes: the attributes are never read andelements.json, the style digests,results.jsonand the report are byte-identical. The Playwright, Selenium and WebdriverIO adapters read it from.testivai/config.jsonand record it in the capture'smetadata.json(only when set); approving freezes it into the baseline, and styles are compared only when both sides used the same setting (dom.styleCheck: "unavailable"until baselines are re-approved after a change), so paths built two ways never produce a style verdict on part of the page.@testivai/witnessexportsnormalizeElementIdAttributes;buildElementMapExpressiontakes the list as a third argument. -
f3e3fc7: Native app screens (experimental):
@testivai/witness-webdriveriocaptures native Android and iOS app screens in an Appium session. In a session on Android or iOS that names no browser and is in theNATIVE_APPcontext (or, when the context cannot be read, was started withapp,appPackageorbundleId),witness()runs no page script: a native screen has no page, Appium rejects them there and WebdriverIO resends each failed one three times (~1.5 s each, ~8 s per capture on an Android 14 emulator). The capture is the whole screen, pixel diff only. The status bar is masked per capture (masks: [{ "top": <px> }]in the capture'smetadata.json): on Android to the bottom of the bar's frame in the window manager at capture time (mobile: getSystemBars), so a rotation is followed; UiAutomator2's own reading (mobile: viewportRect, the session capabilities) is taken once at the session start and came back 63 px for a 136 px bar in one emulator run, so it is only the fallback. On iOS it comes frommobile: viewportRect. The environment block comes from the capabilities, withbrowser.name: "native-app", capture methodscreen:appiumand a newdevicesection (platform, OS version, model, the requested device name, the Appium driver, screen size, density). The snapshot is keyed<name>__<platform>-<device>-<osMajor>(login__android-pixel_7-15) unless the call gives avariant. New optionsnative(trueforces native mode,falsekeeps the web path) andmaskStatusBar(defaulttrue);WitnessBrowsergains the optionalgetContext(). A hybrid app in a web view, and every browser session, keep the web path, and a browser session makes no extra call. Verified on an Android 14 emulator in CI; iOS is not verified.@testivai/witness: results.json schema 2.9.0 (additive) adds thedevicesection of a snapshot's environment blocks and capture methodscreen:appium. The device's platform, OS version, model, screen and density can change rendering; the device name and the Appium driver are context, and so isos(the machine running the tests) when both sides are native app captures. The report words device changes ("device sdk_gphone64_x86_64 → Pixel 7, device OS 14 → 15") and the capture method ("full screen (Appium)"). New exports:appiumEnvironment,isNativeAppCapabilities,mobilePlatform,namesBrowser,nativeAppDevice,nativeAppVariant,statusBarFromCapabilities,statusBarFromSystemBars,statusBarFromViewportRect,CAPTURE_METHODS.APPIUM_SCREENand theDeviceEnvironmenttype. Guide: docs/guides/native-mobile.md. -
8462b36: Hybrid app web views (experimental): new
witness()optioncontextcaptures in another Appium context for one call and switches back afterwards, also when the capture fails.context: 'webview'is the app's web view (theWEBVIEW_<appPackage>context on Android, else the firstWEBVIEW_…context, as iOS numbers them); any other value is an exact context name ('WEBVIEW_com.example.app','NATIVE_APP'). A hybrid app driven in the native context gets its web view captured as a page, with every layer (DOM diff, style check, attribution, shift detection), instead of a native capture;context: 'NATIVE_APP'from a web view is a native capture. A session with no web view, or without the name asked for, fails with the contexts it has and what the app's build needs (WebView debugging enabled).WitnessBrowsergains the optionalgetContexts()andswitchContext().
Patch Changes
@testivai/witness-selenium@0.4.0
Minor Changes
- 117bac5: New opt-in config
elementIdAttributes(for example["data-testid"]) anchors element-map paths on attributes your tests already set. An element's path starts at its nearest ancestor-or-self carrying one of them with a non-empty value no other element in the page shares:div[data-testid="board"] > div.css-146c3p1instead of abody > div > div.css-g5y9jx > div.css-g5y9jx:nth-of-type(2) > …chain. Content inserted above an anchored element no longer renumbers its path, so an element that only moved reads as a shift (with the page shift detected) instead of a change, and the style check pairs the same elements. The list order is the precedence; a duplicated value is skipped and the path continues upward, so paths stay unique; values are escaped as CSS strings (>included), so each anchored path is a working selector. Unset, nothing changes: the attributes are never read andelements.json, the style digests,results.jsonand the report are byte-identical. The Playwright, Selenium and WebdriverIO adapters read it from.testivai/config.jsonand record it in the capture'smetadata.json(only when set); approving freezes it into the baseline, and styles are compared only when both sides used the same setting (dom.styleCheck: "unavailable"until baselines are re-approved after a change), so paths built two ways never produce a style verdict on part of the page.@testivai/witnessexportsnormalizeElementIdAttributes;buildElementMapExpressiontakes the list as a third argument.
Patch Changes
@testivai/witness-playwright@2.1.0
Minor Changes
-
117bac5: New opt-in config
elementIdAttributes(for example["data-testid"]) anchors element-map paths on attributes your tests already set. An element's path starts at its nearest ancestor-or-self carrying one of them with a non-empty value no other element in the page shares:div[data-testid="board"] > div.css-146c3p1instead of abody > div > div.css-g5y9jx > div.css-g5y9jx:nth-of-type(2) > …chain. Content inserted above an anchored element no longer renumbers its path, so an element that only moved reads as a shift (with the page shift detected) instead of a change, and the style check pairs the same elements. The list order is the precedence; a duplicated value is skipped and the path continues upward, so paths stay unique; values are escaped as CSS strings (>included), so each anchored path is a working selector. Unset, nothing changes: the attributes are never read andelements.json, the style digests,results.jsonand the report are byte-identical. The Playwright, Selenium and WebdriverIO adapters read it from.testivai/config.jsonand record it in the capture'smetadata.json(only when set); approving freezes it into the baseline, and styles are compared only when both sides used the same setting (dom.styleCheck: "unavailable"until baselines are re-approved after a change), so paths built two ways never produce a style verdict on part of the page.@testivai/witnessexportsnormalizeElementIdAttributes;buildElementMapExpressiontakes the list as a third argument. -
62964eb: Four fixes found running React Native screens through react-native-web.
- A claimed shift is checked against the pixels. Element maps pair elements by path, and content inserted above an element renumbers its
:nth-of-typepath, which can pair it with a look-alike neighbour (same size and styles, different content). That read as a shift by a wrong distance (−16 px while the content moved +64 px) with a wrongpageShift. A pair now counts as a shift, inregionsand inpageShift, only when the element's pixels match at its new position: at most 1% differ, at the diff's colour threshold, masks excluded, best of ±1 px. In real Chrome true shifts differed on 0.00% of their pixels (fractional moves included), look-alike neighbours on 4.8–6.7%. Shifts that were right before are unchanged; wrong ones now read aschange, which also keepsshiftTolerancefrom passing them. <script>attribute values are not a DOM change. A script never renders, but a bundle renamed by content hash (Expo'sindex-<hash>.js), itsintegrityand a per-requestnoncecounted as attribute changes, so a stylesheet-only change did not read style-only. Their presence still counts, and so does a script added or removed. A snapshot whose only DOM difference was a script attribute now hasdom.changed: false, so its noise hint follows the style check.- The Playwright adapter restores the device emulation its full-page capture drops. The default capture's beyond-viewport screenshot, over the adapter's own CDP session, left the page without its emulation for the rest of the test (Pixel 7:
devicePixelRatio2.625 → 1, touch off).witness()now puts back the pixel ratio and touch emulation after everything it measures, andmetadata.jsonrecords the pixel ratio the image was rendered at: 1 for a Chromium CDP capture, which used to record the profile's value. A baseline approved before this, on a Chromium profile with adeviceScaleFactorother than 1, reads as an environment change inrender.deviceScaleFactoruntil it is re-approved: wording only by default, a status change withenvironmentPolicy: "strict". The captured images do not change. - No "Browser screenshot failed" on WebKit and Firefox. They are captured with Playwright's full-page screenshot directly; a CDP failure on Chromium is still reported.
- A claimed shift is checked against the pixels. Element maps pair elements by path, and content inserted above an element renumbers its
Patch Changes
- 1ac04ef: Capture health: an unhealthy capture is never "render noise". A web font that failed and fell back to a system font, or a stylesheet that answered 404, leaves the DOM identical and the computed styles matching (the style digest hashes the declared
font-family, not the font that rendered), so its small, scattered diff read "likely render noise" andnoiseAutoPasspassed it. The Playwright JS/TS adapter andtestivai witness <url>now record, after each screenshot, what failed to load ashealthin the capture'smetadata.json:document.fonts.statusand every font face that errored,<link rel=stylesheet>elements that did not load, broken<img>s, and requests that answered 400 or more (Resource Timing, Chromium 109+), each URL as host and path only, never the query string. The crawler also merges the failed requests it saw on the network (CDP), cross-origin ones included. Approving freezes the capture's health into the baseline'smetadata.json. results.json schema 2.7.0 (additive) addssnapshots[].captureHealth(both blocks,unhealthy:"candidate"/"baseline"/"both"/null, andreasonsandwarningsin plain words), present when either side recorded health, new snapshots included. Web fonts still loading, a font face that failed, a failed stylesheet, or a failed stylesheet request make a capture unhealthy (a failed font request only with a font face that failed, since a face can load from its nextsrc; afetch()/XHR request or a<link>that no<link rel=stylesheet>names and that does not end in.cssis never a stylesheet):dom.noiseHintis then false and no pass criterion (noiseAutoPass,maxDiffPercent/maxDiffPixels,shiftTolerance) applies while pixels differ; a zero-pixel diff still passes. The HTML report,@testivai/mcp(get_visual_resultsverdicts,explain_snapshot's newlayers.healthand guidance, thetriage-visual-diffrubric and the review prompt; unhealthy captures are never grouped) and the GitHub Action comment read Unhealthy capture with what failed, before any other reading (rendering drift too, when both are true), and call out an unhealthy baseline on every compare against it ("re-baseline from a healthy run"). Broken images, failed scripts and other requests are warnings only.testivai approvewarns when the capture it promotes is unhealthy.@testivai/witnessexportsHEALTH_PROBE_EXPRESSION,parseHealth,mergeResourceFailuresand the types, and@testivai/witness/reportthe readings (assessHealth,snapshotCaptureHealth,captureHealthReading,isUnhealthyCapture,approveHealthWarning). The WebdriverIO, Selenium, Python, Ruby and Java adapters do not record health yet: their captures read "not checked" and compare exactly as before. - 9eaa9f4: A failing test no longer leaves a pull request without a visual report.
testivai merge-capturesnow writestestivai-merge.jsonbeside the captures it merges in.testivai/temp/(the shard total, the shards seen and the shards missing; exported asMERGE_RECORDand theMergeRecordtype), so a later step can tell a merge that lacks a shard from the whole suite. Thetestivai/testivai-oss@v1action uses it: when a run wrote noresults.json, the action compares the captures in.testivai/temp/itself, but never one shard's slice or an incomplete merge. In capture-only mode, the Playwright reporter's hint now says to run the compare step withif: ${{ !cancelled() }}(and on a job thatneedsthe shards, too), since a CI step runs only on success by default. - Updated dependencies [1ac04ef]
- Updated dependencies [117bac5]
- Updated dependencies [f3e3fc7]
- Updated dependencies [9eaa9f4]
- Updated dependencies [a3278e1]
- Updated dependencies [62964eb]
- Updated dependencies [c53604c]
- @testivai/witness@2.6.0
@testivai/mcp@0.9.1
Patch Changes
- 1ac04ef: Capture health: an unhealthy capture is never "render noise". A web font that failed and fell back to a system font, or a stylesheet that answered 404, leaves the DOM identical and the computed styles matching (the style digest hashes the declared
font-family, not the font that rendered), so its small, scattered diff read "likely render noise" andnoiseAutoPasspassed it. The Playwright JS/TS adapter andtestivai witness <url>now record, after each screenshot, what failed to load ashealthin the capture'smetadata.json:document.fonts.statusand every font face that errored,<link rel=stylesheet>elements that did not load, broken<img>s, and requests that answered 400 or more (Resource Timing, Chromium 109+), each URL as host and path only, never the query string. The crawler also merges the failed requests it saw on the network (CDP), cross-origin ones included. Approving freezes the capture's health into the baseline'smetadata.json. results.json schema 2.7.0 (additive) addssnapshots[].captureHealth(both blocks,unhealthy:"candidate"/"baseline"/"both"/null, andreasonsandwarningsin plain words), present when either side recorded health, new snapshots included. Web fonts still loading, a font face that failed, a failed stylesheet, or a failed stylesheet request make a capture unhealthy (a failed font request only with a font face that failed, since a face can load from its nextsrc; afetch()/XHR request or a<link>that no<link rel=stylesheet>names and that does not end in.cssis never a stylesheet):dom.noiseHintis then false and no pass criterion (noiseAutoPass,maxDiffPercent/maxDiffPixels,shiftTolerance) applies while pixels differ; a zero-pixel diff still passes. The HTML report,@testivai/mcp(get_visual_resultsverdicts,explain_snapshot's newlayers.healthand guidance, thetriage-visual-diffrubric and the review prompt; unhealthy captures are never grouped) and the GitHub Action comment read Unhealthy capture with what failed, before any other reading (rendering drift too, when both are true), and call out an unhealthy baseline on every compare against it ("re-baseline from a healthy run"). Broken images, failed scripts and other requests are warnings only.testivai approvewarns when the capture it promotes is unhealthy.@testivai/witnessexportsHEALTH_PROBE_EXPRESSION,parseHealth,mergeResourceFailuresand the types, and@testivai/witness/reportthe readings (assessHealth,snapshotCaptureHealth,captureHealthReading,isUnhealthyCapture,approveHealthWarning). The WebdriverIO, Selenium, Python, Ruby and Java adapters do not record health yet: their captures read "not checked" and compare exactly as before. - e5f2b7f:
zod4.6.6 or later (was 3.25): every 3.x release is affected by GHSA-mrhx-c7xc-m3w5 (memory exhaustion while validating oversized constrained collections), sonpm audit --audit-level=highfailed in any project that installs@testivai/mcp. The tools, their arguments and their validation are unchanged. Two cosmetic differences in the advertised input schemas: integer arguments (region,y,height) now carry an explicitmaximum, and tool input objects no longer advertiseadditionalProperties: false(unknown keys are still dropped, as before). - c53604c: A permanently broken asset no longer turns off every pass tolerance. Since capture health (schema 2.7.0), no pass criterion passed an unhealthy capture whose pixels differed, so a site whose web font or stylesheet fails on every CI run lost
maxDiffPercent,maxDiffPixelsandshiftToleranceon every affected snapshot, and a small anti-aliasing diff failed even with the baseline unhealthy in the same way. Now, when both sides are unhealthy with exactly the same failures (the same font faces by family, weight and style, the same failed stylesheets, and the same failed stylesheet and font requests by host, path and status), results.json schema 2.8.0 (additive) setssnapshots[].captureHealth.sameOnBothSides: trueand those three tolerances apply again (autoPassed"threshold"or"shift").noiseAutoPassnever passes an unhealthy capture,dom.noiseHintstays false, and the reading is still Unhealthy capture, never render noise: the HTML report, the@testivai/mcpverdicts,explain_snapshotguidance and triage rubric, and the GitHub Action comment add "the baseline was captured with the same failures", that the tolerances still apply but noise auto-pass does not, and which criterion passed it. It is never "the same" while web fonts were still loading on either side, when one side could see what the other could not (Font Loading API, request statuses), or when a list may have been cut at 10 entries; broken images and failed scripts are not compared (they are warnings). One side unhealthy, or different failures, behave as before.@testivai/witness/reportexportssameHealthFailures,healthAllowsTolerancesandSAME_FAILURES_NEXT;parseReportDatakeepssameOnBothSidesonly astruewithunhealthy: "both". - Updated dependencies [1ac04ef]
- Updated dependencies [117bac5]
- Updated dependencies [f3e3fc7]
- Updated dependencies [9eaa9f4]
- Updated dependencies [a3278e1]
- Updated dependencies [62964eb]
- Updated dependencies [c53604c]
- @testivai/witness@2.6.0
@testivai/witness@2.5.0
Minor Changes
-
cd9ffe7: Capture provenance: every capture now records where it came from, and the report says when a diff is the machine changing rather than the page. The Playwright, WebdriverIO and Selenium (JS) adapters and
testivai witness <url>write anenvironmentblock into the capture'smetadata.json: the OS (and the Linux distro), the browser and its version (and the WebDriver binary), the test framework, the adapter and how it took the screenshot (full-page:cdp,full-page:playwright,full-page:stitched,viewport:webdriver), what the page was rendered with (viewport, device scale, color scheme, reduced motion, locale, time zone, measured in the page), the CI provider and runner image, a container, andTESTIVAI_ENV_LABEL. Approving a capture freezes its block into the baseline'smetadata.json(and drops a stale one). The compare reads both blocks: results.json schema 2.5.0 (additive) addssnapshots[].environment(delta,renderingDelta, both blocks when they differ) and a run-levelenvironment(the fields every capture of the run agrees on). Framework, adapter and driver versions, the CI runner image, the project and the Linux kernel are context; the rest can change rendering. A diff whose DOM is identical, whose computed styles match and whose rendering environment changed is now rendering drift in the HTML report,@testivai/mcp(get_visual_resultsverdicts, its grouping and a new opening line when several snapshots share one change;explain_snapshot's newlayers.environment; thetriage-visual-diffrubric) and the GitHub Action comment, never "likely render noise"; a size change from another capture method says so instead of "page grew". New configenvironmentPolicy:"warn"(default) never changes a status;"separate"keys snapshots<name>__<browser>__<platform>(never by version) and reports missing only the baselines of an environment the run captured;"strict"makes a rendering change of environmentchangedwhatever its pixels say. Newtestivai envprints this machine's part of the block (--fontsadds a fingerprint of the installed fonts,--jsonfor machines).@testivai/witnessexports the collectors (collectHostEnvironment,RENDER_PROBE_EXPRESSION,browserFromCapabilities,webDriverEnvironment,applyEnvironmentPolicyToName, …) and@testivai/witness/reportthe readings (diffEnvironments,isRenderingDrift,environmentChangeGroups,environmentChangeLine, …).BaselineStoregainswriteTempMetadataandreadTempMetadata. The WebdriverIO and SeleniumWitnessBrowser/WitnessDrivertypes gain the optionalcapabilities/getCapabilities()they read the browser from. The Python, Ruby and Java adapters do not record the block yet; their captures compare exactly as before. -
c05d870: The HTML report now escapes every value it shows, and each snapshot has its own link target.
- Escaping. The report wrote six values from
results.jsoninto the page without escaping them: the baseline, current and diff image paths, the diff path on each changed region's zoom link, the auto-pass criterion and where a mask came from. Aresults.jsonwith markup in one of those fields (one you did not produce, such as from a fork's CI run) could add elements or script to the page. They are escaped now, like every other value. - Link targets. Each snapshot card has an id, so
index.html#snap-homeopens the report at that snapshot, outlined. On phones the card stays below the top bar. Clicking a snapshot's name puts its link in the address bar.snapshotAnchorId(name)is exported, so other tools (the PR comment) build the same link: plain names stay readable (snap-home), and any other name gets a hash suffix so thathome pageandhome/pagenever share an id. - Reading a results.json you did not produce.
parseReportData(json)checks every field the report reads (each must have its type, within size limits, or the file is rejected with the field's path), drops fields it does not know, and never takes image paths from the file. It reads the 2.x formats, capture provenance (2.5.0) included; a newer major is refused with "open the report ZIP". The GitHub Pages publisher renders reports from other people's CI runs, forks included, only through it. - Options for a published report.
renderHtml(data, { notices, noindex, csp }): notices are lines at the top of the report (which pull request, commit and CI run it is, and a warning for a fork's results), each with links to https pages only;noindexasks search engines not to index the page;cspadds a Content-Security-Policy that lets the page use only its own inline styles and scripts and its own images, so nothing is loaded from or sent to another site even if markup ever got through the escaping (project sites on<owner>.github.ioshare one origin). Without options the report is unchanged.
- Escaping. The report wrote six values from
@testivai/witness-webdriverio@0.4.6
Patch Changes
- cd9ffe7: Capture provenance: every capture now records where it came from, and the report says when a diff is the machine changing rather than the page. The Playwright, WebdriverIO and Selenium (JS) adapters and
testivai witness <url>write anenvironmentblock into the capture'smetadata.json: the OS (and the Linux distro), the browser and its version (and the WebDriver binary), the test framework, the adapter and how it took the screenshot (full-page:cdp,full-page:playwright,full-page:stitched,viewport:webdriver), what the page was rendered with (viewport, device scale, color scheme, reduced motion, locale, time zone, measured in the page), the CI provider and runner image, a container, andTESTIVAI_ENV_LABEL. Approving a capture freezes its block into the baseline'smetadata.json(and drops a stale one). The compare reads both blocks: results.json schema 2.5.0 (additive) addssnapshots[].environment(delta,renderingDelta, both blocks when they differ) and a run-levelenvironment(the fields every capture of the run agrees on). Framework, adapter and driver versions, the CI runner image, the project and the Linux kernel are context; the rest can change rendering. A diff whose DOM is identical, whose computed styles match and whose rendering environment changed is now rendering drift in the HTML report,@testivai/mcp(get_visual_resultsverdicts, its grouping and a new opening line when several snapshots share one change;explain_snapshot's newlayers.environment; thetriage-visual-diffrubric) and the GitHub Action comment, never "likely render noise"; a size change from another capture method says so instead of "page grew". New configenvironmentPolicy:"warn"(default) never changes a status;"separate"keys snapshots<name>__<browser>__<platform>(never by version) and reports missing only the baselines of an environment the run captured;"strict"makes a rendering change of environmentchangedwhatever its pixels say. Newtestivai envprints this machine's part of the block (--fontsadds a fingerprint of the installed fonts,--jsonfor machines).@testivai/witnessexports the collectors (collectHostEnvironment,RENDER_PROBE_EXPRESSION,browserFromCapabilities,webDriverEnvironment,applyEnvironmentPolicyToName, …) and@testivai/witness/reportthe readings (diffEnvironments,isRenderingDrift,environmentChangeGroups,environmentChangeLine, …).BaselineStoregainswriteTempMetadataandreadTempMetadata. The WebdriverIO and SeleniumWitnessBrowser/WitnessDrivertypes gain the optionalcapabilities/getCapabilities()they read the browser from. The Python, Ruby and Java adapters do not record the block yet; their captures compare exactly as before. - Updated dependencies [cd9ffe7]
- Updated dependencies [c05d870]
- @testivai/witness@2.5.0
@testivai/witness-selenium@0.3.12
Patch Changes
- cd9ffe7: Capture provenance: every capture now records where it came from, and the report says when a diff is the machine changing rather than the page. The Playwright, WebdriverIO and Selenium (JS) adapters and
testivai witness <url>write anenvironmentblock into the capture'smetadata.json: the OS (and the Linux distro), the browser and its version (and the WebDriver binary), the test framework, the adapter and how it took the screenshot (full-page:cdp,full-page:playwright,full-page:stitched,viewport:webdriver), what the page was rendered with (viewport, device scale, color scheme, reduced motion, locale, time zone, measured in the page), the CI provider and runner image, a container, andTESTIVAI_ENV_LABEL. Approving a capture freezes its block into the baseline'smetadata.json(and drops a stale one). The compare reads both blocks: results.json schema 2.5.0 (additive) addssnapshots[].environment(delta,renderingDelta, both blocks when they differ) and a run-levelenvironment(the fields every capture of the run agrees on). Framework, adapter and driver versions, the CI runner image, the project and the Linux kernel are context; the rest can change rendering. A diff whose DOM is identical, whose computed styles match and whose rendering environment changed is now rendering drift in the HTML report,@testivai/mcp(get_visual_resultsverdicts, its grouping and a new opening line when several snapshots share one change;explain_snapshot's newlayers.environment; thetriage-visual-diffrubric) and the GitHub Action comment, never "likely render noise"; a size change from another capture method says so instead of "page grew". New configenvironmentPolicy:"warn"(default) never changes a status;"separate"keys snapshots<name>__<browser>__<platform>(never by version) and reports missing only the baselines of an environment the run captured;"strict"makes a rendering change of environmentchangedwhatever its pixels say. Newtestivai envprints this machine's part of the block (--fontsadds a fingerprint of the installed fonts,--jsonfor machines).@testivai/witnessexports the collectors (collectHostEnvironment,RENDER_PROBE_EXPRESSION,browserFromCapabilities,webDriverEnvironment,applyEnvironmentPolicyToName, …) and@testivai/witness/reportthe readings (diffEnvironments,isRenderingDrift,environmentChangeGroups,environmentChangeLine, …).BaselineStoregainswriteTempMetadataandreadTempMetadata. The WebdriverIO and SeleniumWitnessBrowser/WitnessDrivertypes gain the optionalcapabilities/getCapabilities()they read the browser from. The Python, Ruby and Java adapters do not record the block yet; their captures compare exactly as before. - Updated dependencies [cd9ffe7]
- Updated dependencies [c05d870]
- @testivai/witness@2.5.0
@testivai/witness-playwright@2.0.10
Patch Changes
- cd9ffe7: Capture provenance: every capture now records where it came from, and the report says when a diff is the machine changing rather than the page. The Playwright, WebdriverIO and Selenium (JS) adapters and
testivai witness <url>write anenvironmentblock into the capture'smetadata.json: the OS (and the Linux distro), the browser and its version (and the WebDriver binary), the test framework, the adapter and how it took the screenshot (full-page:cdp,full-page:playwright,full-page:stitched,viewport:webdriver), what the page was rendered with (viewport, device scale, color scheme, reduced motion, locale, time zone, measured in the page), the CI provider and runner image, a container, andTESTIVAI_ENV_LABEL. Approving a capture freezes its block into the baseline'smetadata.json(and drops a stale one). The compare reads both blocks: results.json schema 2.5.0 (additive) addssnapshots[].environment(delta,renderingDelta, both blocks when they differ) and a run-levelenvironment(the fields every capture of the run agrees on). Framework, adapter and driver versions, the CI runner image, the project and the Linux kernel are context; the rest can change rendering. A diff whose DOM is identical, whose computed styles match and whose rendering environment changed is now rendering drift in the HTML report,@testivai/mcp(get_visual_resultsverdicts, its grouping and a new opening line when several snapshots share one change;explain_snapshot's newlayers.environment; thetriage-visual-diffrubric) and the GitHub Action comment, never "likely render noise"; a size change from another capture method says so instead of "page grew". New configenvironmentPolicy:"warn"(default) never changes a status;"separate"keys snapshots<name>__<browser>__<platform>(never by version) and reports missing only the baselines of an environment the run captured;"strict"makes a rendering change of environmentchangedwhatever its pixels say. Newtestivai envprints this machine's part of the block (--fontsadds a fingerprint of the installed fonts,--jsonfor machines).@testivai/witnessexports the collectors (collectHostEnvironment,RENDER_PROBE_EXPRESSION,browserFromCapabilities,webDriverEnvironment,applyEnvironmentPolicyToName, …) and@testivai/witness/reportthe readings (diffEnvironments,isRenderingDrift,environmentChangeGroups,environmentChangeLine, …).BaselineStoregainswriteTempMetadataandreadTempMetadata. The WebdriverIO and SeleniumWitnessBrowser/WitnessDrivertypes gain the optionalcapabilities/getCapabilities()they read the browser from. The Python, Ruby and Java adapters do not record the block yet; their captures compare exactly as before. - Updated dependencies [cd9ffe7]
- Updated dependencies [c05d870]
- @testivai/witness@2.5.0
@testivai/mcp@0.9.0
Minor Changes
- d8bd6b1:
get_diff(and its aliasget_snapshot_diff) now makes tall full-page captures readable. It still sends the baseline, current and diff images downscaled to fit (a 390×11844 mobile page becomes about 33×987 px, which shows where the changes are but not what they say), and then, when an image had to be downscaled, lists the snapshot'sregionsand crops them at native scale, one image pixel per page pixel, so a reviewer can check text, wrapping and clipping (a page that fits within 1024 px is already sent at native scale and gets no crops, as before). Each crop is a region plus 32 px on every side, made as wide as the page (up to 1280 px) so whole lines show, and labelled with its box and the region's selector; regions whose padded areas meet share a crop while it fits 1280×1024 px, a region inside a box another crop shows joins it, and changes come before shifts. The response stays bounded: up to 3 crops per call, each at most 1280×1024 px and 2,000,000 px in all; an area wider than 1280 px is sampled at 1/2 scale or smaller, and a taller one shows its top rows and gives theyto continue from. After a size change each capture is cut to its own bounds, and a crop of the padded band says the shorter capture has no pixels there. New optional inputs fetch one area on demand, without the full-page images:region(an index the response lists) oryandheight(a full-width band, which works on a new snapshot too). An image that is not a PNG is now said instead of failing the call. Thetriage-visual-diffrubric, shared with the review action, tells the model to judge text, wrapping and clipping from the crops. Found when the review action triaged testivai/testivai-site#20 and could only judge one page, at thumbnail scale.
Patch Changes
- cd9ffe7: Capture provenance: every capture now records where it came from, and the report says when a diff is the machine changing rather than the page. The Playwright, WebdriverIO and Selenium (JS) adapters and
testivai witness <url>write anenvironmentblock into the capture'smetadata.json: the OS (and the Linux distro), the browser and its version (and the WebDriver binary), the test framework, the adapter and how it took the screenshot (full-page:cdp,full-page:playwright,full-page:stitched,viewport:webdriver), what the page was rendered with (viewport, device scale, color scheme, reduced motion, locale, time zone, measured in the page), the CI provider and runner image, a container, andTESTIVAI_ENV_LABEL. Approving a capture freezes its block into the baseline'smetadata.json(and drops a stale one). The compare reads both blocks: results.json schema 2.5.0 (additive) addssnapshots[].environment(delta,renderingDelta, both blocks when they differ) and a run-levelenvironment(the fields every capture of the run agrees on). Framework, adapter and driver versions, the CI runner image, the project and the Linux kernel are context; the rest can change rendering. A diff whose DOM is identical, whose computed styles match and whose rendering environment changed is now rendering drift in the HTML report,@testivai/mcp(get_visual_resultsverdicts, its grouping and a new opening line when several snapshots share one change;explain_snapshot's newlayers.environment; thetriage-visual-diffrubric) and the GitHub Action comment, never "likely render noise"; a size change from another capture method says so instead of "page grew". New configenvironmentPolicy:"warn"(default) never changes a status;"separate"keys snapshots<name>__<browser>__<platform>(never by version) and reports missing only the baselines of an environment the run captured;"strict"makes a rendering change of environmentchangedwhatever its pixels say. Newtestivai envprints this machine's part of the block (--fontsadds a fingerprint of the installed fonts,--jsonfor machines).@testivai/witnessexports the collectors (collectHostEnvironment,RENDER_PROBE_EXPRESSION,browserFromCapabilities,webDriverEnvironment,applyEnvironmentPolicyToName, …) and@testivai/witness/reportthe readings (diffEnvironments,isRenderingDrift,environmentChangeGroups,environmentChangeLine, …).BaselineStoregainswriteTempMetadataandreadTempMetadata. The WebdriverIO and SeleniumWitnessBrowser/WitnessDrivertypes gain the optionalcapabilities/getCapabilities()they read the browser from. The Python, Ruby and Java adapters do not record the block yet; their captures compare exactly as before. - c439e08: The triage rubric (the
triage-visual-diffprompt, shared with the review action) now says what an unchanged DOM rules out. No element, attribute or text differs between the baseline and the current capture, and the baseline is the last approved capture, which can already contain the change under test. So a text or markup edit in the change never explains an identical-DOM diff, even where its regions sit on text the change edited. Only something the DOM does not record can: a stylesheet change the style check does not cover, an image, a font or another asset, or the capture. When the change touched none of those, the verdict is a glitch or a possible bug, not an intended change. Found when the CI review of testivai-site#20 called a re-capture of already-approved copy an intended change because the pull request edited that text. - Updated dependencies [cd9ffe7]
- Updated dependencies [c05d870]
- @testivai/witness@2.5.0