What the agent does
The agent starts by checking what testing the app can do at all. In a Flutter app like gskinnerTeam/flutter-wonderous-app there is no test/ and no integration_test/ directory, and pubspec.yaml lists only icons_launcher, flutter_lints and dependency_validator under dev_dependencies, so flutter_test itself is missing. The SDK constraints are sdk: ^3.11.5 and flutter: ^3.41.9, and the repository's web workflows pin flutter-version: '3.41.9', so the agent installs that Flutter release in its sandbox, adds flutter_test: sdk: flutter to dev_dependencies, and runs flutter pub get against the committed pubspec.lock.
Then it works out what the app needs before any screen can build. main.dart calls registerSingletons(), which registers ten lazy singletons with GetIt, from AppLogic and WondersLogic to CollectiblesLogic and NativeWidgetService, and then appLogic.bootstrap(), which loads settings and collectibles through JsonPrefsFile on top of shared_preferences and only then sets isBootstrapComplete. The router's _handleRedirect in lib/router.dart sends every path back to / until that flag is true. So the shared helper in test/helpers.dart registers the same singletons, calls SharedPreferences.setMockInitialValues({}) (the plugin's own test hook, so no platform channel is needed), runs bootstrap(), resets GetIt after each test, and pumps WondersApp itself, so the tests go through the real MaterialApp.router, localization delegates and redirect.
The widget tests follow the routes ScreenPaths defines. After bootstrap, a test checks that / redirects to /home and that HomeScreen (in wonders_home_screen.dart) shows the first wonder from the WonderType enum, which has eight values from chichenItza to tajMahal. A navigation test opens one wonder's details through ScreenPaths.wonderDetails(type, tabIndex: 0), which builds /home/wonder/<name>?t=0, checks that WonderDetailsScreen and its WonderDetailsTabMenu render, and checks that an unknown wonder name falls back to chichenItza, as _parseWonderType does. Because the app runs long flutter_animate sequences, the agent advances time with pump and explicit durations rather than relying on pumpAndSettle alone.
For the golden test it picks TimelineEventCard from lib/ui/common/timeline_event_card.dart, a stateless card with a year, text and darkMode, and renders both modes inside WondersAppScaffold so $styles resolves. Flutter tests use a placeholder font unless real ones are loaded, so the test loads the app's Cinzel, Yeseva and Tenor fonts from assets/fonts/ with FontLoader first, then runs matchesGoldenFile. It generates the images once with flutter test --update-goldens, then runs flutter test without the flag to prove they match, and keeps that output for the pull request.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "This app has no tests. Add widget tests for the home screen, one detail screen and the go_router navigation between them, plus a golden test for one card widget. Read pubspec.yaml, main.dart and the router first, install the Flutter version the repository pins, and add flutter_test to dev_dependencies if it is missing. Start the app the way main.dart does, with its real service registration and bootstrap, using only the plugins' own test hooks such as SharedPreferences.setMockInitialValues; reset the service locator between tests. Load the app's fonts before the golden test, generate the golden once and then run flutter test again without --update-goldens. Do not change code under lib/. Run flutter test, and open a pull request with the output.",
"resources": [
{
"url": "https://github.com/your-org/your-flutter-app"
}
]
}- Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
- Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
- Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.
What comes back
The pull request creates test/ with a shared helpers.dart, home_screen_test.dart, navigation_test.dart, and timeline_event_card_golden_test.dart with its PNGs under test/goldens/, and adds flutter_test to dev_dependencies with the matching pubspec.lock change. Nothing under lib/ changes. The description lists each test, the route or widget it covers, and the Flutter version it ran on, and includes the flutter test output the prompt asks for.
The session keeps both golden runs, the one that wrote the images and the one that compared against them, and every other flutter test and flutter pub get the agent ran, so a reviewer can see the tests pass without the update flag. Wonderous's own workflows build for the web and do not run tests, so the pull request notes that and suggests a flutter test step for CI as a follow-up rather than adding one itself.
Before merging, a reviewer opens the golden PNGs to check that they show the real fonts and colours rather than placeholder boxes, checks that the bootstrap in the test helper matches main.dart closely enough that a change to startup breaks the tests, and decides whether goldens generated on Linux suit a team that may regenerate them on macOS, where font rendering differs.
Run it on every Flutter SDK upgrade
Widget tests and goldens are most useful when the toolkit underneath changes. When a pull request bumps the flutter constraint in pubspec.yaml or the flutter-version in a workflow, have a GitHub pull_request webhook send a request with the pull request URL in resources, asking the agent to run flutter test on the new version and report any golden that moved. Each pull request gets its own session in its own sandbox.
Set a webhook URL on the API key and Rush POSTs run.completed or run.failed when the turn ends, with the session URL, so your server can post the result on the upgrade pull request.
The use case this job follows, with its automation and trigger: Write the missing tests.
Why run it on Rush?
Golden tests are sensitive to the machine that renders them: the Flutter version, the operating system and the fonts all change pixels. Generating them on one developer's laptop ties every future comparison to that laptop. On Rush the agent installs the exact Flutter release the repository pins in its own Linux sandbox, generates the goldens there and runs the comparison there, and the session records which version and commands produced them.
The first test directory for an app is also the hardest to set up, because the service locator, the bootstrap and the router redirect all have to work in a test before a single screen renders. That trial and error happens in the sandbox, with every failed flutter test kept in the session, and what reaches you is a pull request with passing tests and the output that shows it.
Frequently asked questions
My app uses GetIt. Will the tests replace my services with fakes?
No. The helper registers the same singletons main.dart registers and runs the real bootstrap, then resets GetIt after each test so state does not leak between them. Only platform plugins get their own test hooks, such as SharedPreferences.setMockInitialValues, because there is no device behind a widget test.
Will the goldens match on my Mac?
Not always. Font rasterising differs between Linux and macOS, so goldens made on one can fail on the other by a few pixels. Most teams generate and compare goldens in one place, usually CI on Linux; the agent generates them on Linux and says so in the pull request.
Does it test on a real device or emulator?
No. flutter test runs widget tests in the Flutter test environment with no device, which is what makes them fast and runnable in a sandbox. Tests in integration_test/ that need a device or a browser are a separate job.
Does it work with Riverpod or Bloc instead of GetIt?
Yes. The agent reads how main.dart sets up state, wraps the app in the same ProviderScope or BlocProvider tree in its test helper, and overrides only what touches a platform, so the screens build with the state they use in the app.