Skip to main content

Write a flow test

Flow tests (the gadfly harness in packages/flow_test) drive real screens end-to-end and assert both the rendered UI and the analytics transcript, across light / dark / focus trips. They are the backbone of the Test Gallery.

What gets mocked

The canonical harness mocks repositories — the presentation seam blocs depend on — via MocksContainer + testAppBuilder. (A real-stack variant fakes the adapter instead — an in-memory client with real services — where end-to-end SDK coverage is worth it. Unit tests use MockClient from client_sdk_testing.) See app/test/README.md.

Anatomy

flowTest<MocksContainer>(
'sign in → home',
config: createFlowConfig(),
descriptions: [ /* EPIC / STORY / AC for the gallery tree */ ],
test: (tester) async {
await tester.setUp(arrangeBeforePumpApp: (arrange) async {
when(() => arrange.mocks.authRepository.signIn(...)).thenAnswer((_) async {});
});

await tester.screenshot(
description: 'the sign-in page',
actions: (a) async { await a.userAction.tap(find.byKey(SignInPage.submitKey)); },
expectations: (e) { e.expect(find.byType(HomePage), findsOneWidget); },
expectedEvents: ['[ANALYTIC] [page]: HomeRoute'],
);
},
);
  • expectedEvents asserts the analytics/page transcript — the funnel events (e.g. today_section_shown, bounty_claimed) are proven here.
  • Maestro note: Maestro resolves id: against Semantics(identifier: '…'), not Flutter Keys. Put a Semantics(identifier:) on every control Maestro taps; keep Keys for find.byKey in tests.

Run + regenerate goldens

cd app
fvm flutter test test/flows/<feature>_test.dart # logic + assertions
# regenerate the golden screenshots (also feeds the Test Gallery):
fvm flutter test test/flows/<feature>_test.dart \
--update-goldens --dart-define=createScreenshots=true

Tip: when checking a whole-suite run, capture flutter's real exit code (fvm flutter test > /tmp/t.txt 2>&1; echo "EXIT=$?") — a piped | tail reports the pipe's exit, not the test result, and can hide failures (and OOM hangs).