Skip to content

fix(visual): harden the Gemini golden fallback - #7

Open
kikoso wants to merge 3 commits into
mainfrom
fix/visual-gemini-safety
Open

kikoso wants to merge 3 commits into
mainfrom
fix/visual-gemini-safety

Conversation

@kikoso

@kikoso kikoso commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

Five fixes to :visual-testing, all in the Gemini path of BaseVisualTest and GeminiVisualTestHelper.

1. A Gemini pass no longer overwrites a golden that diverged. With updateGoldenOnPass = true (the default), a screenshot that failed the pixel diff but got a Gemini "PASSED" replaced the golden in goldensDirectory. That directory is checked before the bundled assets, so on a reused device or emulator the next runs fast-passed against the regressed screenshot without asking Gemini again. Now updateGoldenOnPass only records a golden when none existed, which keeps the first-run recording flow. A diverged golden is left alone and the log explains how to accept an intended change.

2. The default pass condition rejects negated verdicts. it.contains("PASSED", ignoreCase = true) also matched "NOT PASSED" and "BYPASSED". The new default (isPassedVerdict, internal) passes only when the last whole-word PASSED/FAILED in the response is PASSED and is not preceded by "not". The samples and docs that passed the old lambda explicitly now rely on the default.

3. The API key goes in the x-goog-api-key header. It used to be a ?key= query parameter on every request, so it could end up in logs, proxies and exception messages that include the URL.

4. The model is pinned by default. With no configuration, the helper used to call models.list and pick the newest stable Flash model, so verdicts could change from one day to the next without any change in the code under test. It now uses DEFAULT_FALLBACK_MODEL without a network call. Discovery is still available as an opt-in: preferredModel = GeminiVisualTestHelper.LATEST_MODEL or -Dgemini.model=latest (new, additive constant).

5. Docs warn against src/main/assets/goldens. The baseline lookup still checks it, but the KDoc now points to src/androidTest/assets, because files under src/main/assets ship in the release APK.

6. The emulator job works again. It was failing on main and on every PR, and this PR now also includes the fix from #11 (closed in favour of this one):

  • kotlinx-coroutines-android is added to the sample app. Its runtime classpath resolved coroutines 1.9.0, and AGP pins the test APK to it ({strictly 1.9.0}), so Ktor 3.6 crashed with NoSuchMethodError in Job.invokeOnCompletion on its first request. It now resolves {strictly 1.11.0}.
  • The default client gets a 60 s request timeout (DEFAULT_REQUEST_TIMEOUT_MILLIS, covered by a test). With the crash fixed, the Gemini request in VisualMapSampleTest hit CIO's default 15 s timeout.

The two belong in the same PR as the header change. Ktor's timeout exception includes the request URL, and with the key in ?key= that URL ended up in the uploaded test report.

7. snippet-bot passes on the skill docs. The skills referred to code regions as `// [START tag]` in prose (Reference: ..., See ...) right above a copy of the same region, so snippet-bot saw each tag started twice or never ended (40 mismatches across the 9 skill files on main). It only checks changed files, so any PR touching the skills failed. Those prose mentions now read "region tag"; the copied code blocks keep their real tags. A local scan of every .md, .kt and .java file finds no mismatched tags.

helper in BaseVisualTest is now open so tests can inject a client (additive, see the .api diff).

Tests: ktor-client-mock is added as a test-only dependency of :visual-testing; nothing changes for consumers. With it:

  • VisualVerdictTest covers the verdict rules.
  • GoldenFallbackLogicTest covers a diverged golden staying untouched, a missing golden being recorded, and "NOT PASSED" failing the test.
  • GeminiModelResolutionTest covers the pinned default (no request made), latest discovery, its fallback and session cache, and the key being sent only in the header. Two of its existing tests used to call the real Gemini endpoint with a dummy key and now use the mock engine.

The doc snippets copied from the maps_visual_golden_fallback region were re-synced from the source, and the #L line ranges that point at it were updated.

Ran ./gradlew test lint spotlessCheck apiCheck docs:dokkaGenerate :app:assembleDebugAndroidTest --rerun-tasks --no-build-cache: all green. The emulator job on this PR is the check for the device side.

BEGIN_COMMIT_OVERRIDE
fix(visual): never overwrite a diverged golden after a Gemini pass
fix(visual): reject negated verdicts in the default pass condition
fix(visual): send the Gemini API key in a header instead of the URL
feat(visual): pin the default Gemini model and make latest Flash discovery opt-in
docs(visual): warn against bundling goldens in src/main/assets
fix(visual): give Gemini requests a 60s timeout
fix: align sample app coroutines with Ktor so visual tests run on device
docs(skills): refer to code regions by name so snippet-bot can parse the skills
END_COMMIT_OVERRIDE

@snippet-bot

snippet-bot Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Here is the summary of changes.

You are about to delete 39 region tags.

This comment is generated by snippet-bot.
If you find problems with this result, please file an issue at:
https://github.com/googleapis/repo-automation-bots/issues.
To update this comment, add snippet-bot:force-run label or use the checkbox below:

  • Refresh this comment

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Coverage (unit tests)

No unit baseline recorded in coverage/history.csv yet, so this run only reports absolute numbers.

Module Line % Change Branch % Change
golden-testing 90.42% new 58.00% new
robolectric-testing 84.06% new 51.87% new
visual-testing 63.58% new 35.16% new
TOTAL 82.12% new 47.11% new

Line and branch coverage from unit test reports. History is recorded in coverage/history.csv after each merge to main.

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.

2 participants