Skip to content

fix(bridge): fail a missing image snapshot in CI mode instead of writing it - #249

Open
YevheniiKotyrlo wants to merge 5 commits into
callstackincubator:mainfrom
YevheniiKotyrlo:fix/missing-image-snapshot-on-ci
Open

YevheniiKotyrlo wants to merge 5 commits into
callstackincubator:mainfrom
YevheniiKotyrlo:fix/missing-image-snapshot-on-ci

Conversation

@YevheniiKotyrlo

@YevheniiKotyrlo YevheniiKotyrlo commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

What is this?

In Jest's CI mode (--ci, on by default when Jest detects a CI environment), toMatchImageSnapshot now fails when its snapshot does not exist, and saves the received image as __diff_output__/<name>-actual.png. Until now it wrote the snapshot and passed on CI too, so a run without committed snapshots compared nothing and a wrong capture could not fail it (#198). With CI=true and no committed snapshots, the playground's web suite passes on main and writes both specs' snapshots; on this branch the two specs fail with:

New snapshot was not written: src/tests/normal/ui/image_snapshots/chromium/out-of-bounds.png does not exist, and missing snapshots are not written in CI mode. Received image saved at src/tests/normal/ui/image_snapshots/chromium/diff_output/out-of-bounds-actual.png; commit it as src/tests/normal/ui/image_snapshots/chromium/out-of-bounds.png, or run without CI mode (--ci=false) on a machine with this device and commit the snapshot it writes.

How does it work?

The session hands the bridge Jest's updateSnapshot state. When it is "none", CI mode's value, a missing image snapshot is not written and the match fails; outside CI mode, or with --ci=false, it is written as before, and so it is for a createHarnessBridge context that names no state. The received and diff images take the whole snapshot name, so a name holding .png keeps it. Like Jest's own snapshot matchers and jest-image-snapshot, toMatchImageSnapshot now refuses .not, which would turn that failure into a pass. The UI testing guide gets a "Creating snapshots" section and says .not is refused, the CLI reference lists --ci, and git ignores the __diff_output__ folders beside the playground's committed snapshots.

The playground's screenshot and out-of-bounds specs need committed baselines now, so this PR adds the ones CI's Android emulator, iOS simulator and headless Chromium capture. Android's out-of-bounds baseline shows the row's ten squares, which the app draws with one copy of react-native in the bundle, as it has since #227. This PR's E2E run on 3f1a29a passes both specs against these baselines on iOS and web (run); its Android job stopped at the size assertion #298 fixed, and with #298 merged in, E2E Android passes both on my fork (run). The CI-mode run against main, and how to repeat it, are in this comment.

Why is this useful?

A CI run now fails where it used to pass while comparing nothing: a snapshot that was never committed, or a capture that went wrong, shows up as a failed test with the received image saved for review.

Fixes #198

@vercel

vercel Bot commented Oct 20, 2026

Copy link
Copy Markdown

@YevheniiKotyrlo is attempting to deploy a commit to the Callstack Team on Vercel.

A member of the Team first needs to authorize it.

@YevheniiKotyrlo
YevheniiKotyrlo force-pushed the fix/missing-image-snapshot-on-ci branch from dfeedaf to 41af5b7 Compare October 20, 2026 20:03
@YevheniiKotyrlo
YevheniiKotyrlo marked this pull request as ready for review October 20, 2026 20:50
@YevheniiKotyrlo
YevheniiKotyrlo marked this pull request as draft October 20, 2026 21:28
@YevheniiKotyrlo
YevheniiKotyrlo marked this pull request as ready for review October 21, 2026 17:30
…ing it

In Jest's CI mode, toMatchImageSnapshot fails when its snapshot does not
exist and saves the received image beside it as <name>-actual.png, where it
wrote the snapshot and passed. The Jest session hands the bridge Jest's
updateSnapshot state; a bridge context that names none writes a missing
snapshot as before. The received and diff images take the whole snapshot
name, and toMatchImageSnapshot refuses .not, which would turn the failure
into a pass.

The playground commits the baselines CI's Android emulator, iOS simulator
and Chromium capture for its screenshot and out-of-bounds specs and ignores
the failure images saved beside them. The UI testing guide and the CLI's UI
skill say how snapshots are created in CI mode, and the CLI reference lists
--ci.
# Conflicts:
#	packages/bridge/src/__tests__/image-snapshot.test.ts
#	packages/bridge/src/image-snapshot.ts
#	packages/bridge/src/server.ts
#	packages/runtime/src/expect/matchers/toMatchImageSnapshot.ts
@V3RON
V3RON marked this pull request as draft October 26, 2026 07:11
@V3RON
V3RON marked this pull request as ready for review October 26, 2026 07:11
@V3RON
V3RON marked this pull request as draft October 27, 2026 15:10
@V3RON
V3RON marked this pull request as ready for review October 27, 2026 15:10
V3RON added a commit that referenced this pull request Oct 27, 2026
…ounded edges (#298)

## What is this?

The playground's E2E Android suite fails `should screenshot specific
element only` on every pull request since Oct 22: the element screenshot
is 262×262 px, and the test expects 263×263. This fixes the test, which
was wrong. The capture is correct.

Fixes #297.

## How does it work?

Android region captures round each edge of the region to a whole pixel
(#289), so they cover exactly the pixels the element covers on screen.
The assertion rounded the size instead: on the CI emulator (`PixelRatio`
2.625) a 100 dp element is 262.5 px, which rounds to 263, but its edges
at 408.75 px and 671.25 px round to 409 and 671, so it covers 262 px.

The test now measures the element with `measureInWindow` and expects the
size its rounded edges span. Where the pixel ratio makes element edges
whole pixels, as on the iOS simulator and in Chromium, this is the same
size as before.

## Why is this useful?

E2E Android goes green again on pull requests, so it can catch real
regressions again, starting with #249 and #257, which are blocked on
this failure today.
Since callstackincubator#288, toMatchImageSnapshot saves the received image of a missing
snapshot in CI mode as __diff_output__/<name>-actual.png, as the UI
testing guide says. The version plan and the CLI's UI skill still said
it is saved beside the snapshot.
@YevheniiKotyrlo

Copy link
Copy Markdown
Contributor Author

The playground's web suite in CI mode, headless Chromium on Linux: main (26b67ea), which commits no image snapshots, against this branch's code at 8c860fa (3f1a29a with 26b67ea merged in) with its two chromium baselines removed:

CI=true, no chromium snapshots main this branch
ui/screenshot.harness.tsx, ui/out-of-bounds.harness.tsx pass fail, each with the message below
Snapshots written 2 0
__diff_output__/<name>-actual.png saved 0 2
The other 19 web suites pass pass

New snapshot was not written: src/tests/normal/ui/image_snapshots/chromium/out-of-bounds.png does not exist, and missing snapshots are not written in CI mode. Received image saved at src/tests/normal/ui/image_snapshots/chromium/diff_output/out-of-bounds-actual.png; commit it as src/tests/normal/ui/image_snapshots/chromium/out-of-bounds.png, or run without CI mode (--ci=false) on a machine with this device and commit the snapshot it writes.

The two snapshots main writes are pixel-identical to the chromium baselines this PR commits.

To run it: pnpm nx run-many -t build --projects="packages/*", delete apps/playground/src/__tests__/normal/ui/__image_snapshots__/chromium/ on this branch, and run CI=true pnpm exec react-native-harness --harnessRunner chromium in apps/playground.

With the baselines committed, this PR's own E2E run on 3f1a29a passes both specs on iOS and web (run). Its Android job stopped earlier, at the size assertion #298 fixed; with #298 merged in, E2E Android passes both specs on my fork (run).

@YevheniiKotyrlo

Copy link
Copy Markdown
Contributor Author

Hey @V3RON! Sorry for the noise, what do you think about this changes? happy to apply your suggestions.

This branch has not been deployed

No deployments
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.

toMatchImageSnapshot passes when the snapshot is missing, so CI without committed snapshots never compares

2 participants