Refresh the wiki's screenshots from the running app, and get the last Settings row out from under the navigation bar - #155
Merged
Conversation
Ten screens the wiki needs - the iCloud flow from picking whose passcode you have through to the device this app registers, and the three steps of sharing a tag. Written as a test so regenerating them after a UI change is one command rather than ten by hand, which is the difference between screenshots that stay true and screenshots nobody dares touch. It asserts only enough to know it photographed the right screen. That is not laziness: a picture of the wrong page is worse than none, because nobody checks a screenshot against the code afterwards. What these screens actually do is covered by FetchFromICloudErrorsTest, TheDeviceNoteIsShownOnceTest and the export tests. Everything on them is fabricated - serials, tag names and the bundle code all come from fakes. They are going on a public page, so that matters more than usual. **It needs a device with a display**, and says so. Shot.ofTheScreen photographs the compositor, which is the only way to catch the code dialog and the page behind it in one frame, and that returns black on the headless managed device. Since Shot now refuses to write an all-one-colour frame, a headless run leaves no files rather than ten black ones. FakeICloudService gains failUnlockWith, because none of its factories produced a rejected passcode and the wrong-code state is one of the screens worth documenting. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… the wiki Three more flows the wiki documents with images from another phone and another year: signing in, changing the map provider, and getting a log out of the app. The map ones need the real Google map, so they cannot run on the managed device - no Play Services there, and the capture would be of a blank rectangle. They run against a windowed emulator instead, and say so. Two things worth naming: forgetEverything() now deletes every owned beacon rather than the two this class wrote. Deleting by id is right for a test; a capture needs a screen nobody else has touched, and an abandoned run left tags behind that made the next one die with AmbiguousViewMatcherException on two views called "Bike". The AMap key dialog prints the package name and signing fingerprint of the build that photographed it, because that is what AMap's console asks you to paste. blur.py takes those out by height fraction, pixelating before blurring so the characters are gone rather than soft. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every workflow filters on `pull_request: branches: ["main"]`, and that matches the base of the PR rather than the branch the work is on. A PR opened against another PR's branch therefore runs nothing. Worth writing down because of how it presents: the checks section is absent, not red and not pending, which reads as "nothing to run here" rather than "nothing ran". #153 sat like that and looked fine. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The theme makes the navigation bar transparent, so the scrolling area runs underneath it. The debug switch is the last row, and it came to rest behind the gesture pill. Measured on a Pixel 9 emulator: scrolled all the way down, the switch ended at 2350 with the bar starting at 2361. Eleven pixels, on gesture navigation. Three button navigation asks for around 126px instead of 63, and those eleven pixels become a switch nobody can tap. The scroll area now reserves the measured inset, and clipToPadding="false" makes that space scrollable rather than a strip that clips the rows passing through it. No new dimension: the child already leaves 20dp under its content, which is the visual gap - what was missing was the bar's own space. The test asserts the reserved space as well as the geometry, and the reserved space is the half that bites. A purely geometric assertion is green with the fix removed, because of those eleven pixels - it would only fail on a device this suite never runs on. Checked both ways: passes with the fix, fails without it with "reserves 0px, needs 63px". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… the blur putItBack had guard.restore() last in a plain sequence, so a test that failed hard enough to upset the scenario close or the Python double took the settings and the stored session with it. The next class then met somebody signed in who should not have been, and failed with NoActivityResumedException - nowhere near anything that mentions a session. It is in a finally now. The capture classes get the fixes that came out of running them: the Anisette dialog is waited for by id rather than by a label that moved into helper text when the dialog grew a mode dropdown, the debug switch is scrolled to before being asserted on screen rather than after, the 2FA row is matched on the phone number inside "SMS (+44 ...)" rather than equal to it, and the signed-out class signs out itself instead of inheriting it from whatever ran before. blur.py takes --x, because a band across the full width of a screenshot of a dialog also blurs the dimmed page behind it and leaves a pale rectangle running off both sides. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
They are a documentation tool, not tests: each ends in a screenshot and asserts almost nothing, so a green run of them says nothing about the app. Left switched on they added about twenty minutes to a suite people are meant to run constantly, which is how a suite stops being run. Same opt-in mechanism as the Anisette live tests. Skipped rather than deleted, because the point of them is that the images can be made again. The wiki's previous set came from several phones over several years and nothing recorded how any of it was taken. The map class is slow, and while documenting that I measured where it goes: about 50s per test, of which ~45s is AMapWithTagsOnIt.putItBack. Setup is two seconds. Written down in CONTRIBUTING because the map, the dialog waits and the tile sleep all look responsible and none of them is - I chased all three. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parawanderer
temporarily deployed
to
Android Build
August 23, 2026 20:51 — with
GitHub Actions
Inactive
parawanderer
had a problem deploying
to
Android Build
August 23, 2026 20:51 — with
GitHub Actions
Failure
parawanderer
had a problem deploying
to
Android Build
August 23, 2026 20:57 — with
GitHub Actions
Failure
…fail The gate was in @before, and an assumption failure there skips the test body but runs @after regardless - which released Intents that were never initialised. Every capture reported FAILED with "init() must be called prior to using this method", which is a worse outcome than leaving them switched on: skipped work that reports as broken. From @BeforeClass the class is passed over whole and neither hook runs. Checked both directions on a device: the three classes skip with exit 0 in eleven seconds, and with captureScreenshots=true the signing-in class still produces all four of its screens. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parawanderer
had a problem deploying
to
Android Build
August 23, 2026 21:17 — with
GitHub Actions
Failure
parawanderer
temporarily deployed
to
Android Build
August 23, 2026 21:17 — with
GitHub Actions
Inactive
The geometric assertion measured the switch with getLocationOnScreen and the navigation bar with decor.getHeight() - a screen position against a height. Those coincide only when the decor view starts at y=0, which it does on the Pixel 9 and does not on the managed aosp-atd device, so CI reported a 54px overlap on a device whose inset is zero and which therefore has no navigation bar to overlap with. Also written down: on aosp-atd the systemBars bottom inset is 0, so both assertions in this test are vacuously true on the emulator CI runs. It earns its keep on a device that has a navigation bar. A green CI run is not evidence this screen is fine, and the comment now says so. Checked on both: green on aosp-atd, green on a Pixel 9, and still red on the Pixel 9 with the fix reverted - "reserves 0px, needs 63px". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parawanderer
had a problem deploying
to
Android Build
August 23, 2026 21:40 — with
GitHub Actions
Failure
parawanderer
temporarily deployed
to
Android Build
August 23, 2026 21:40 — with
GitHub Actions
Inactive
parawanderer
had a problem deploying
to
Android Build
August 23, 2026 21:57 — with
GitHub Actions
Failure
…at all @BeforeClass passed the classes over in silence, and the instrumentation then reported fewer results than the APK declares tests - so AGP called the whole run aborted: "Expected 640 tests, received 623". The 17 missing were exactly the contents of these three classes. Zero failures, a red build, and it presented as "could not connect to TCP port 5554", which reads as the emulator dying and is documented in AGENTS.md as exactly that. The emulator's own log said EXIT CODE 0. So the assumption goes back in @before, where each test is reported as skipped and counted, and each @after returns early rather than undoing work that was never done - which is what made @before untenable the first time. Verified with a full managed-device run: 640 tests reported, 22 skipped (5 Anisette, 17 captures), no abort. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parawanderer
temporarily deployed
to
Android Build
August 23, 2026 22:14 — with
GitHub Actions
Inactive
parawanderer
temporarily deployed
to
Android Build
August 23, 2026 22:14 — with
GitHub Actions
Inactive
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The wiki's images came from several phones over several years, and nothing recorded how any of them was taken. This makes them reproducible, and fixes the things that turned up while doing it.
The fix worth reviewing
The last row of Settings sat under the navigation bar. The theme makes it transparent, so the scrolling area runs underneath, and the debug switch is last with nothing below it.
Measured on a Pixel 9 emulator, scrolled all the way down: the switch ended at 2350 with the bar starting at 2361. Eleven pixels, on gesture navigation. Three-button navigation asks for around 126px instead of 63, and those eleven pixels become a switch nobody can tap.
The scroll area now reserves the measured inset, and
clipToPadding="false"makes that space scrollable rather than a strip that clips the rows passing through it. No new dimension — the child already leaves 20dp under its content, which is the visual gap; what was missing was the bar's own space.The test asserts the reserved space as well as the geometry, and the reserved space is the half that bites. A purely geometric assertion is green with the fix removed, because of those eleven pixels — it would only fail on a device this suite never runs on. Checked both ways: passes with the fix, fails without it with
reserves 0px, needs 63px.A real bug in a shared fixture
AMapWithTagsOnIt.putItBackhadguard.restore()last in a plain sequence, so a test that failed hard enough to upset the scenario close or the Python double took the settings and the stored session down with it. The next class then met somebody signed in who should not have been, and failed withNoActivityResumedException— nowhere near anything mentioning a session. It is in afinallynow. This affects every test using that fixture, not only the new ones.The captures themselves
Three classes covering 21 screens: the iCloud import route, the export flow, signing in, Settings and its dialogs, and turning on debug data to get a log out.
They are skipped unless asked for. Each ends in a
Shotand asserts almost nothing, so a green run of them says nothing about the app — and they added about twenty minutes to a suite people are meant to run constantly. Same opt-in mechanism as the Anisette live tests:Everything on them is fabricated except the map: the session, the tags, the locations, the phone numbers and the address. The pin is the Eiffel Tower and the geocoder answers "a made-up street, in a made-up town".
blur.pytakes bands out of a screenshot by height fraction, for the AMap key dialog — it prints the build's own package name and signing fingerprint, which is exactly what AMap's console asks you to paste, and not something to put on a public page.Two things measured rather than guessed, now in CONTRIBUTING
putItBack; setup is about two seconds. Recorded because the real Google map, the dialog waits and the tile sleep all look guilty and none of them is — I chased all three before measuring.pull_request: branches: ["main"], and that matches the base. The checks section is absent rather than red or pending, which reads as "nothing to run here" instead of "nothing ran". Re-read the account when somebody would notice, and explain the device once #153 sat like that.Verified
Not verified: any physical device, and any navigation mode other than this emulator's gesture bar — the three-button figure above is Android's documented size, not something I measured here.
🤖 Generated with Claude Code