Skip to content

chore(ui-scripts,regression-test): ignore whole-pixel shifts in visual regression diffs - #2710

Merged
balzss merged 2 commits into
masterfrom
INSTUI-5180-visual-regression-false-positives
Oct 1, 2026
Merged

balzss merged 2 commits into
masterfrom
INSTUI-5180-visual-regression-false-positives

Conversation

@balzss

@balzss balzss commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • visual-diff retries the comparison at small integer offsets before calling a screenshot changed, behind --max-shift (default 1, 0 restores exact matching). It tries every offset within the budget in any direction, diagonals included. Scoring over the shared region also covers a screenshot one pixel taller with identical content.
  • A screenshot that only passes after realignment is reported as layout shifted in summary.json and in the HTML report, so absorbing a shift doesn't hide it.

A pixel-count tolerance can't do this job. On a real baseline a 1px translation produces 2825 differing pixels and a genuine 40×40 recolour produces 1479 — less than a factor of two apart, so any threshold loose enough to absorb the first would hide the second.

Verified against four real CI baselines — identical, shifted 1px, 1px taller, and a genuine recolour:

--max-shift 0 (current) default
identical ok ok
shifted 1px changed (2825 px) ok
1px taller changed (1280 px) ok
real recolour changed (1479 px) changed

Dropped from this PR: the viewportHeight change

An earlier revision raised viewportHeight from 800 to 2000 to cut fullPage stitching. That's reverted, because the setting never reached the browser.

The body carries min-height: 100vh, so every short page should be floored at exactly the viewport height. Across the 99 baselines, 35 sit at exactly 720 — not 800. The images this PR's own run produced are the same height as the baselines (alert-canvas 720 → 720), even though that run had viewportHeight: 2000. viewportWidth: 1280 does take effect, so the config loads; the height is clamped, most likely by the bundled Electron window since the suite runs with no --browser.

So the stitch-reduction numbers in the earlier revision were arithmetic over baseline heights, not measurements. Sizing the viewport properly needs the setting to work first, and that's separate from this PR.

Test Plan

  • No baseline churn expected — nothing here changes how pages render.
  • The 3 changed rows in the report (badge-canvas, tooltip-light, tooltip-dark) predate this PR and are not explained by it. badge-canvas is a single-pass 720px page, so it can't be a stitch artifact. Worth a look before merge.
  • --max-shift is covered by unit tests in packages/ui-scripts/lib/__node_tests__/visual-diff.test.ts plus the four-baseline check above. CI hasn't exercised it, since no run has produced a real whole-pixel shift yet.

Fixes INSTUI-5180

🤖 Generated with Claude Code

@balzss balzss self-assigned this Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-01 17:10 UTC

github-actions Bot pushed a commit that referenced this pull request Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Visual regression report

Cypress suite: ✅ Passing

Visual diff: ⚠️ Changes detected.

Status Count
Unchanged 97
Changed 2
New 0
Removed 0

Accessibility (axe): ✅ No violations.

📊 View full report — click a screenshot's ⚠ badge to see each violation boxed on the image, with the offending element named and contrast failures shown as color swatches.

Diff images (2)

badge-canvas.png — 1573 pixels differ

tooltip-light.png — 956 pixels differ

Baselines come from the visual-baselines branch. They refresh on every merge to master. The Cypress suite line covers the a11y and console-error assertions — a ❌ there means the suite found real issues even if the visual diff is clean.

@balzss
balzss requested a review from git-nandor September 7, 2026 09:25
Comment thread .github/workflows/visual-regression-selfcheck.yml Outdated
@balzss
balzss removed the request for review from git-nandor September 7, 2026 11:19
@balzss
balzss marked this pull request as draft September 7, 2026 11:21
@balzss
balzss force-pushed the INSTUI-5180-visual-regression-false-positives branch from 2bbb988 to 29285f7 Compare September 7, 2026 14:52
@balzss balzss changed the title chore(ui-scripts,regression-test,ci): stabilize visual regression capture and tolerate 1px shifts chore(ui-scripts,regression-test): ignore whole-pixel shifts in visual regression diffs Sep 7, 2026
github-actions Bot pushed a commit that referenced this pull request Sep 7, 2026
@matyasf
matyasf self-requested a review September 18, 2026 08:54
@matyasf
matyasf marked this pull request as ready for review September 18, 2026 08:54

@matyasf matyasf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my comments. Code looks OK, but I'd change the comments

Comment thread regression-test/cypress.config.ts Outdated
'max-shift': {
type: 'number',
describe:
'Treat a screenshot as unchanged when it matches its baseline exactly after being shifted by up to this many pixels. Absorbs whole-pixel layout rounding, which moves a component without altering it. 0 requires an exact match.',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Absorbs whole-pixel layout rounding, which moves a component without altering it." is not needed

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it matches shift to ANY direction within the tolerance (including e.g. diagonal shifts)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both fixed. I removed that sentence and kept only "Absorbs whole-pixel layout rounding", so --help still says why the flag exists. And you are right about the direction — dx and dy both go from -maxShift to +maxShift, so diagonals too. The text says "in any direction" now.

Comment thread packages/ui-scripts/lib/commands/visual-diff.ts Outdated
Comment thread packages/ui-scripts/lib/commands/visual-diff.ts Outdated
// Each offset is scored over the region the two images share, so the band that
// shifts in from outside is never counted. (0, 0) is included deliberately: the
// caller's comparison *pads* mismatched sizes, while this one *crops* to the
// overlap, which is what lets "one pixel taller, same content" pass.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

way way too verbose. Please simplify this to 1-2 lines

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cut from 16 lines to 8. I kept two things only: why a pixel-count tolerance cannot do this, and why (0, 0) is in the loop. The second one is not obvious — without it someone will remove it later and break the "one pixel taller, same content" case.

Comment thread packages/ui-scripts/lib/commands/visual-diff.ts
balzss and others added 2 commits September 30, 2026 12:59
…l regression diffs

The suite failed on screenshots that were pixel-identical to their baseline but
translated by about a pixel. When a layout box rounds one device pixel
differently the whole painted subtree moves, so a direct comparison lights up
every edge in the image: on a real baseline, a 1px translation produces 2825
differing pixels. That is not a visual regression, and the suite should not
report one.

visual-diff now retries the comparison at small integer offsets before calling a
screenshot changed, behind --max-shift (default 1, 0 restores exact matching).
Scoring happens over the region the two images share, which also covers the
related case of a screenshot one pixel taller with identical content. A
pixel-count tolerance cannot do this job: the shift above and a genuine 40x40
recolour differ by less than a factor of two, so any threshold loose enough to
absorb the first would hide the second.

viewportHeight goes 800 to 2000 to cut the stitching that produces those shifts.
capture: 'fullPage' scrolls the viewport down the document and stitches the
slices, and the stitch is the least reproducible part of the capture. Measured
against the baselines branch, this takes the suite from 58 stitch operations to
37, and from 17 of 32 pages captured in one pass to 28.

The viewport change alters layout, so the first run shows a cascade of changed
rows; merging refreshes the baselines.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…and revert viewport change

Addresses review feedback on #2710.

- Trim the `matchesWhenShifted` and status-check comments, and state that the match covers a
  shift in any direction, diagonals included.
- Report a realigned screenshot as layout shifted in `summary.json` and the HTML report instead
  of silently calling it unchanged.
- Revert `viewportHeight` to 800. The setting does not reach the browser in CI: captures are
  floored at 720px with both 800 and 2000, so the claimed stitch reduction never happened.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@balzss
balzss force-pushed the INSTUI-5180-visual-regression-false-positives branch from 29285f7 to a1d1cc6 Compare September 30, 2026 14:09
github-actions Bot pushed a commit that referenced this pull request Sep 30, 2026

@matyasf matyasf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks OK. I still prefer human written comments in the code though

@balzss
balzss merged commit 24760d0 into master Oct 1, 2026
9 of 10 checks passed
@balzss
balzss deleted the INSTUI-5180-visual-regression-false-positives branch October 1, 2026 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants