Photograph the store screenshots from a recipe, not by hand - #116
Merged
Merged
Conversation
Five screenshots per listing, and nothing about taking them by hand is reproducible: "Continue — Ch. 12" is a different chapter every afternoon, the language is whichever one the device happens to be in, and two runs show two different libraries. So the recipe makes the state it photographs rather than assuming it — it signs in from cleared app data, forces the language (one run serves both listings), marks a chapter read so Continue has somewhere to continue, and waits for a copy to land before the offline screen. `tool/store_screenshots.sh` is the half that cannot be Dart: clearing the app's data, refusing a screen that is not 9:16 (Play's recommended size is 1080x1920, and no phone ships that shape), and flattening the PNGs, because both stores refuse an alpha channel. The recipe is `integration_test/store_screenshots_test.dart`, the half with a filesystem is `test_driver/store_screenshots.dart`, and `flutter_driver` and `integration_test` both come from the SDK. It finds its way by icon and by type rather than by label: the sign-in form is answered before any language has been forced, so it is found positionally, and the few strings it needs are read out of the app's own `AppLocalizations` instead of being written down a second time. **It has never been run.** It type-checks against the SDK's `integration_test` API, and every finder in it is an icon or a string the app itself draws, so it should fail loudly rather than silently — but the first run will be the first debug, and the profile picker, the reader's chrome and the swiped row are the steps to watch. `store/CLAUDE.md` says so where the assets are described, along with the size each console demands. `store/feature-graphic.html` rides along: the source of the 1024x500 Play wants, drawn on ivory rather than on the night blue, because Play asks for a background that is not dark grey — its own store is.
The recipe went to a phone and failed in five places, none of which the analyser could see. Each one is fixed here, and each says in the code where it bit, because the next person to touch this will hit the same five: - the sign-in form is answered **positionally** — it is reached before any language has been forced — and submitted through the keyboard's *done*, which is the password field's own `onFieldSubmitted`. A tap on the button is a tap the soft keyboard is sitting on; - the profile face is `ProfileAvatar` in an `IconButton`, not one of Material's face icons; - a saved copy is a **check inside a circle**: the word "Saved" is only its tooltip, so waiting for that text waits forever; - `pumpAndSettle` never returns on a screen that is loading, and this app is always loading something — a shelf shimmers, a page spins. The recipe pumps a fixed beat, and every navigation *waits for the thing it navigated to*; - the bottom-bar destinations are found by **position in the bar**, not by icon: the label is a word the bar hides when it does not fit, and a tap aimed at an `Icon` is one the framework can refuse as not hit-testing the widget. The script changes with them: it **uninstalls** the app rather than `pm clear`ing it, because clearing needs a permission `adb shell` does not have and fails with a `SecurityException` — swallowing that is how the first run photographed the profiles of whoever used the phone last. It also resolves `adb` out of the SDK (it is rarely on PATH) and refuses to flatten a run that wrote nothing. One step is still broken and is written down in `store/CLAUDE.md`: a tap on a bottom-bar destination is not delivered when the test synthesises it, while `adb shell input tap` at the same point switches tab. Two screens come out; the last three need that understood first.
A store listing is a publication, and the app draws somebody else's covers and pages: no library means no screenshots, and any library means showing what the app was pointed at on a listing that is meant to show what the app is. So the content is faked one layer below the app instead — the layer that owns it. `tool/gen_demo_library.sh` draws four covers, six pages and an avatar with ImageMagick, in the app's own palette and its own bundled face, and names the series in a `titles.txt` that the server reads back, so the shelf, the series screen and the artwork cannot disagree about what a series is called. `tool/demo_server.dart` serves them as a Kavita server: login, health, libraries, series, volumes, chapter info, page images, progress and the device registration, on `localhost:5000`, against any password. Its routes are the shapes `lib/src/api/models.dart` reads, taken from `docs/openapi/kavita-openapi-0.9.0.0.json` — pinned in this repository, and the only reason the faking is cheap: the contract was already written down. Every route was checked with `curl` before the app ever saw it, which caught a nesting bug in the page count (a `fold` seeded with the outer accumulator counts the first volume twice: thirty pages, reported as forty-eight) and a write that ignored `chapterIds` — the shape `mark-multiple-read` uses — so a swipe on a row would have changed nothing on screen and nothing in the file. It is a `tool/`, and that is the point: it is not in the app, not in a bundle, and not behind a flag anybody could turn on. A fake-content mode inside the app would be a second way for every screen to draw, and this is not.
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.
Five screenshots per listing, and nothing about taking them by hand is
reproducible — "Continue — Ch. 12" is a different chapter every afternoon, and
the language is whichever one the device happens to be in. So this is a recipe
that makes the state it photographs: it signs in from cleared app data,
forces the language (one run serves both listings), marks a chapter read so
Continue has somewhere to continue, and waits for a copy to land before the
offline screen.
tool/store_screenshots.sh— the half that cannot be Dart: clearing theapp's data, refusing a screen that is not 9:16, flattening the PNGs (both
stores refuse an alpha channel).
integration_test/store_screenshots_test.dart— the walk itself.test_driver/store_screenshots.dart— the half with a filesystem.store/CLAUDE.md— the sizes each console demands, and how each asset ismade. It rides with
store/feature-graphic.html, the source of the Playbanner, so that the graphic can be regenerated instead of redrawn.
It finds its way by icon and by type rather than by label: the sign-in form is
answered before a language has been forced, so it is found positionally, and
the few strings it needs come out of the app's own
AppLocalizationsratherthan being written down a second time.
What is verified, and what is not
Verified:
flutter analyzeis clean on top ofmain(which caught the renamethis rebase was for —
SavePillbecameDownloadPillin 819b8ca, and therecipe followed it);
flutter testpasses; everyflutter driveflag in thescript exists in this SDK; every guard in the script was exercised by hand
(missing credentials, a bad locale, an unknown argument, a device whose screen
is not 9:16, a device that is not there); the
integration_testAPI was readout of the SDK rather than remembered.
Not verified: the run itself. It has never been executed — there is no
library to point it at yet — and the first run will be the first debug. The
steps to watch are the profile picker's face icon, the reader's chrome tap and
the swiped chapter row.
store/CLAUDE.mdsays so where the assets live.Note
This supersedes the commit left on
site-and-licencesafter #114 merged: it isrebased on
maininstead, so the diff here is only the recipe.