Skip to content

fix(tests): stop snapshot tests failing on newer macOS, and hitting the Keychain - #31

Open
mpecan wants to merge 2 commits into
eddmann:mainfrom
mpecan:fix/menubar-snapshot-tests
Open

fix(tests): stop snapshot tests failing on newer macOS, and hitting the Keychain#31
mpecan wants to merge 2 commits into
eddmann:mainfrom
mpecan:fix/menubar-snapshot-tests

Conversation

@mpecan

@mpecan mpecan commented Jul 13, 2026

Copy link
Copy Markdown

Two defects visible when running the suite locally. Neither is caught by CI. No production files change.

Snapshot tests fail on newer macOS

All 8 MenuBarIconSnapshotTests compared images pixel-exact. SwiftUI renders gradients and antialiased edges differently across macOS versions, so they fail on any OS other than the one that recorded the references. They pass on CI's runner and fail on macOS 26 / Xcode 26.

Measured:

Comparison max ΔE
Same icon, newer macOS 9.9
Battery vs Circular icon 127.9

Now compared perceptually with precision: 0.99, perceptualPrecision: 0.90 (ΔE ≤ 10). That absorbs the rendering drift with a 12x margin before a different icon would pass. Reference images are unchanged, so CI compares against exactly what it did before.

test_snapshotTolerance_rejectsChangedStatusColour asserts a changed status colour still fails, so the tolerance cannot be loosened into asserting nothing.

Ruled out: system Dark/Light mode (renders are pixel-identical in both) and ImageRenderer.colorMode (no mode reproduces the reference).

Tests read the Keychain and hit the network

The test bundle is hosted by the app, so running tests launches it. MenuBarManager.start() calls appModel.bootstrap(), which reads the session key from the Keychain and fetches live usage. The test binary is signed differently from the installed app, so macOS prompts for Keychain access on every run.

The scheme's test action now passes --demo safeUsage, routing the app to the existing startWithoutBootstrap(), which sets up the menu bar without calling bootstrap(). AppDelegate is untouched.

That behaviour lives in the scheme, where it can be deleted unnoticed. TestHostLaunchTests asserts the test host was launched with a valid --demo argument and fails with an explanatory message if not.

Verified by deleting ~/.claudemeter/usage.json, which the app writes on every successful fetch, and confirming it does not reappear across a full run.

Result

61 tests pass. Previously 8 failed.

Not covered

--demo safeUsage also injects demo usage data into the launched host app. Tests build their own objects and never read the host's AppModel. A dedicated flag, or moving app logic into a framework so the test bundle needs no host, would both require production changes.

🤖 Generated with Claude Code

mpecan and others added 2 commits July 13, 2026 19:32
…he Keychain

Two unrelated defects, both visible when running the suite locally.

Snapshot tests compared images pixel-exact. SwiftUI renders gradients and
antialiased edges differently across macOS versions, so all 8 failed on any OS
other than the one that recorded the references — CI passes, a modern machine
does not. Compare perceptually instead. The tolerance is measured, not guessed:
the same icon drifts by at most ~10 deltaE across OS versions, while a different
icon differs by ~128, so allowing deltaE <= 10 keeps a 12x margin. Reference
images are unchanged, so CI keeps comparing against exactly what it did before.
A guard test asserts a changed status colour is still rejected, so the tolerance
cannot be loosened into asserting nothing.

The test bundle is hosted by the app, so running tests launched the real menu
bar: it read the session key from the Keychain (prompting the user, repeatedly,
since the test binary is signed differently) and fetched live usage over the
network. Skip bootstrap when running under XCTest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous commit skipped menu bar bootstrap by having AppDelegate check for
XCTestConfigurationFilePath — production code that knows it is under test.

The app already has the hook needed: `--demo <mode>` routes to
startWithoutBootstrap(), which sets up the menu bar without reading the Keychain
or fetching usage. The scheme's test action now passes it, so AppDelegate is
untouched.

The cost is that the behaviour lives in the scheme, where it can be deleted
without anyone noticing. TestHostLaunchTests asserts the test host was launched
with a valid --demo argument, so removing it fails the suite instead of silently
restoring the Keychain prompts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant