Skip to content

Fix screenshot compare - #658

Merged
hannesa2 merged 8 commits into
masterfrom
ScreenshotCompare
Sep 19, 2026
Merged

hannesa2 merged 8 commits into
masterfrom
ScreenshotCompare

Conversation

@hannesa2

@hannesa2 hannesa2 commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

close #628

@hannesa2 hannesa2 added the pipeline CI pipeline label Sep 15, 2026
@hannesa2
hannesa2 marked this pull request as draft September 15, 2026 20:18
@hannesa2
hannesa2 force-pushed the ScreenshotCompare branch 3 times, most recently from 4da9491 to b30f143 Compare September 16, 2026 06:49
@hannesa2 hannesa2 added the WIP Work in progress label Sep 17, 2026
@goneng

goneng commented Sep 17, 2026 •

Copy link
Copy Markdown
Collaborator

Really nice work to start from - the step gating is careful in the places that are easy to get wrong
(using outcome rather than conclusion under continue-on-error, and
letting a failed baseline skip the comparison instead of failing the PR),
and the comments explain the reasoning rather than the mechanics.
The structure below is all yours, I only moved two things inside it.

Sorry for pushing to your branch instead of leaving notes.
It looked faster than a long thread, but say the word and I'll drop the commit so
you can do it your way.

What it changes:

  • The baseline is built from the PR's base commit rather than tag 1.5.8, so
    the diff is about this PR instead of everything merged since the release.
  • The fixture clone now drops every ref but the fixture branch.
    The history view counts commits reachable from any ref, so the fixture was
    still moving with master and a cached baseline no longer matched it.
    That is why the run on 88d5480 still flagged all four screenshots even though
    nothing UI-related changed.
  • The baseline build is skipped for PRs that touch no UI path, which is about half of them.
  • The committed PNGs are removed, since nothing compares against them any more.

I replayed the fixture change over six months of master: the old clone drifts
from 3081 to 3312 reachable commits, the new one stays at 3043 throughout.

@hannesa2

Copy link
Copy Markdown
Contributor Author

Nice ! It's very promising.

I made some tests #666 and #661

That means, that we need a downloadlink/name separation per pull request.

image

@hannesa2

Copy link
Copy Markdown
Contributor Author

Btw, I rebased all 3 pull requests (this, #666, #661). Let's see how a parallel test behaves, and who is winning the last screenshot publish (which is the current open issue)

@hannesa2

Copy link
Copy Markdown
Contributor Author

I removed this warning
image

@goneng

goneng commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Good catch, and I think it is simpler than a race. The upload has no per-PR
namespace at all: screenShotCompare.sh:113 sends each diff image under its
bare basename, into one flat directory shared by every PR, so
.../uploads/00-commit-context-menu.png is a single global slot. Whoever
finishes last wins, and every older comment then renders their pictures. So
there is no arbitration to get right - nothing should be competing for that
name.

Two lines fix it, in the shape the same file already uses for error
screenshots at 144-145:

newName="pr${PR}-${emulatorApi}-${f}"
mv "${f}" "$newName"

The mv matters: curl sends the basename as the stored name, so the file has
to be renamed on disk for the URL to change - which is probably why it got
commented out. $PR is already set at line 47 and $emulatorApi comes from
the workflow, so nothing else changes. It lives in your screenShotScript
repo; happy to open a PR there if that is easier than patching it yourself.

It also fixes something my gate introduced: if a PR posts a comment and a
later push drops its UI change, the comparison is skipped and the old comment
never gets cleaned up. Right now that stranded comment shows someone else's
images; with per-PR names it just shows its own, slightly stale ones.

One bit of housekeeping: the comment on this PR from 09-16 predates the fix
and will never be auto-deleted, since the comparison no longer runs here.
Worth deleting by hand.

And thanks for running #661 and #666 - those closed the thing I could not
verify from the outside. Both runs went cache-miss, built a baseline, then hit
the cache on the rerun and still detected the change correctly. That was
exactly the path the old design got wrong.

@goneng

goneng commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Opened it as hannesa2/screenShotScript#1 so you can
just merge it rather than retype the two lines. It is the first PR that repo
has had, so it may not notify you the way you are used to.

Once it is in, GitX needs the submodule pin moved off 0b1a330 to pick it up.
Happy to do that here, or leave it to you.

@gitx gitx deleted a comment from hannesa2 Sep 18, 2026
@goneng

goneng commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Added one diagnostic step (977fe1e5), which needs a word of explanation
since it looks like debug noise on its own.

Two baseline cache entries turned up under the same key
(baseline-screenshots-a921dbd6...-arm64), saved 05:30 and 06:12, at 292110
and 292108 bytes. That is from #661 and #666 both missing the cache on the
same base and both building a baseline. Either the two-byte delta is just
archive/compression noise between runner images, or the baseline screenshots
are not byte-stable between two builds of the same commit - and if it is the
second, every cache miss will report differences the PR did not cause, which
is most of what this branch set out to fix.

So the step prints shasum -a 256 of the baseline PNGs on both paths, cached
and freshly built, plus which one it was and the base commit.

What to compare: two runs that both missed the cache on the same base -
a hit only proves the cache round-trips bytes. That is exactly the 05:30 /
06:12 situation, so next time master moves and both test PRs run, their
printed hashes line up for comparison. Same hashes means the delta was
archive noise and we can forget it; different hashes means the screenshots
themselves drift and the cache is unsound.

Worth knowing why it has to be measured this way: the UI-path gate means a PR
that changes no UI skips the comparison entirely, so there is never a "these
two should be identical" run to just look at.

It only prints when a baseline exists, so it stays quiet on PRs like this one.
Happy to drop the step once the question is settled, or leave it - it is cheap
and the hashes are useful the next time a screenshot result looks wrong.

@hannesa2

Copy link
Copy Markdown
Contributor Author

Unfortunately it doesn't detect anything anymore

echo "$changed"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "No UI-relevant paths changed; skipping the screenshot baseline."

@hannesa2 hannesa2 Sep 19, 2026 •

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.

This works too good 😁 With this it's hard to change just the pipeline and verify if it works properly. I run into this here #681

But this doesn't change here something #661 in general
Image

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.

I think we need to rebase the test-PRs over the current commit of ScreenshotCompare branch, so the base-commit changes and also includes the latest fixes.

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.

Yes, exactly what I'm doing right now.
Surprisingly, now it finds changes 🎉 and I found one thing which needs some investigation: there should be only the latest screenshot post, the others should be deleted to not spam the pull request, but there are still the old ones

@goneng

goneng commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

maybe the retention of old screenshots is because they are now specifically created with different names/paths. want me to investigate?

@hannesa2

Copy link
Copy Markdown
Contributor Author

maybe the retention of old screenshots is because they are now specifically created with different names/paths. want me to investigate?

Yes please ! ❤️

@hannesa2

Copy link
Copy Markdown
Contributor Author

I added here DEBUG_INFO in screenshot script, now it should be easier to investigate

@goneng

goneng commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

OK - helpful observations - found 2 related bugs 😃 - on it.

@goneng

goneng commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

Investigated the comment pile-up. Two separate things, one in the script and
one here.

The script one is the cause of what you spotted, and it is not the new
file names. screenShotCompare.sh runs under zsh, whose echo expands
backslash escapes, so line 74

oldCommentsList=$(echo $oldCommentsJson | jq '...')

turns every \n inside a comment body into a real newline in the middle of a
JSON string, and jq rejects the payload. The || echo "" then leaves an empty
list, so the cleanup reports "nothing to do" while the old comments stay. It
only bites once a pull request carries a comment written by a person - the
ones this script posts use <br/> and contain no escapes, which is why it
worked until now. On #661 the trigger was my own long comments yesterday.

Fixed in hannesa2/screenShotScript#1 (printf '%s'
instead of echo, plus the same in the DEBUG_INFO dump). I replayed the full
deletion chain against the live payloads: it now finds all 7 stale comments on
#661 and both on #666, and correctly leaves the human ones alone. Your pins
are on the exact SHA b1a7454, so nothing moves under your running tests
until you bump them.

The second one is in this workflow, and it arrived with forceFail=true.
Archive screenshots diffs has no always(), so now that the compare step
exits non-zero on a difference, the diff images are dropped in exactly the
runs that produced them. Run 35431177919 shows it: step 26 failure, step 27
skipped.

       - name: Archive screenshots diffs
-        if: ${{ env.variableSet != '' && matrix.abi == 'arm64' && github.event_name == 'pull_request' && steps.stage-baseline.outcome == 'success' }}
+        if: ${{ always() && env.variableSet != '' && matrix.abi == 'arm64' && github.event_name == 'pull_request' && steps.stage-baseline.outcome == 'success' }}

Leaving this one for you rather than pushing to your branch again.

On whether failing is the right call - I would keep it non-blocking for
now, and I think your instinct is right. Not because the mechanism is wrong,
but because of #676: three of the four screenshots are byte-identical, so a
one-line sidebar change reports as four failures. Blocking on that teaches
people to ignore it. Worth reconsidering once the tests capture four distinct
views.

One good piece of news while I was in the logs. The #661 baseline was built
from 423af8d2 and the earlier one from a921dbd6, two separate builds of
two different commits, and the screenshots are byte-identical
(b29eb7bb..., e39cb94e...). So the baselines are reproducible and the
two-byte cache difference was the tarball compression, not the images. That
was the last open worry about false positives on a cache miss.

@goneng

goneng commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

@hannesa2 , I have not pushed the above BuildPR.yml change to ScreenshotCompare as it is a branch you are working on.
Please edit the line and commit.
Thanks!

goneng added a commit to goneng/gitx that referenced this pull request Sep 19, 2026
Cancelling a superseded run throws away the artifacts it would have
uploaded, which is the very thing the screenshot experiments in gitx#658,
gitx#661 and gitx#681 exist to look at, and it makes each variant wait for the
running build before it can start. Raised by hannesa2 on gitx#623.

- Read cancel-in-progress from the pull request's labels, so that a
  pull request labelled keep-runs runs every push to completion
- Leave every other pull request cancelling as it does now, and master
  and tag builds untouched, since their group is keyed per run
hannesa2 and others added 7 commits September 19, 2026 11:45
The comparison still reported all four screenshots as differing on PRs
that change no UI, because neither side of it was held fixed.

* Build the baseline from github.event.pull_request.base.sha instead of
  a pinned release tag, so the diff shows what this PR changed rather
  than everything merged since the last release.
* Strip every ref but the fixture branch from the screenshot repo. The
  history view counts commits reachable from any ref, and the clone
  inherited the workspace's refs, so the fixture moved with master and
  a cached baseline no longer matched it.
* Skip the baseline build for PRs that touch no UI path, about half of
  them, and key the cache on the base commit.
* Drop the committed PNGs, which nothing compares against any more, and
  fail loudly if the two screenshot paths stop colliding, which would
  otherwise pass the check without comparing anything.
Record what the baseline actually contains, so a rebuild can be told
apart from the cached copy it replaced.

* Two cache entries appeared under one key at 292110 and 292108 bytes.
  If the baseline is not byte-stable between builds of the same commit,
  every cache miss reports differences the pull request did not cause,
  which is most of what this branch set out to fix.
* Hash on both paths, cached and freshly built, and print which one it
  was, so two runs can be compared from the logs alone.
Compare against the commit the pull request is actually being merged
into, so intervening merges to master stop counting as its changes.

* github.event.pull_request.base.sha is master as GitHub recorded it
  when the pull request last changed base, not the base branch's head.
  On run 35328811182 it read a921dbd while master was a996171, so the
  baseline was a merge behind and #667 counted as #661's work.
* The checkout is refs/pull/N/merge, whose first parent is the commit
  the merge was computed against. Resolve it with rev-parse HEAD^1 and
  use that for the gate, the cache key and the baseline checkout.
* Print both values, so the next run says whether they agree.
@hannesa2
hannesa2 force-pushed the ScreenshotCompare branch 2 times, most recently from cc0358e to abc919a Compare September 19, 2026 10:12
@hannesa2

Copy link
Copy Markdown
Contributor Author

@goneng Thanks a lot, I followed you on both suggestions

@hannesa2
hannesa2 marked this pull request as ready for review September 19, 2026 11:46
@hannesa2

Copy link
Copy Markdown
Contributor Author

Looks really good now, thanks a lot !
I merged hannesa2/screenShotScript#1, applied the merge to master here, rebased both test pull request and when all goes well, I'll merge this

@hannesa2 hannesa2 removed the WIP Work in progress label Sep 19, 2026
@hannesa2
hannesa2 merged commit 06b8b00 into master Sep 19, 2026
3 checks passed
@hannesa2
hannesa2 deleted the ScreenshotCompare branch September 19, 2026 12:10
@goneng

goneng commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

💃🏼 🕺🏼

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pipeline CI pipeline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CI: Screenshot baselines drift on every merge to master

2 participants