How to Test Onboarding Flow on Flutter (Complete Guide)
How to Test Onboarding Flow on Flutter (Complete Guide)
How to Test Onboarding Flow on Flutter (Complete Guide)
Testing the onboarding experience is one of the highest‑impact activities you can perform on a Flutter app because it is the first real interaction users have with your product. A confusing or broken onboarding flow leads to immediate drop‑off, poor reviews, and lost revenue, while a smooth flow drives activation, retention, and word‑of‑mouth growth. In production, onboarding bugs often surface only under specific conditions—such as a slow network, a locale change, or an accessibility setting—that are not exercised by simple happy‑path scripts. This guide walks you through why onboarding matters, what typically breaks, how to build a comprehensive test matrix, how to test manually and with Flutter‑native automation, how to augment those efforts with autonomous persona‑driven exploration, and how to lock the process into CI. By the end you will have a repeatable, measurable approach that catches the bugs that scripts miss and gives you confidence before each release.
Why Onboarding Flow Testing Matters in Flutter Apps
Business impact of onboarding failures
When a user opens your app for the first time, the onboarding sequence is responsible for communicating value, setting expectations, and gathering any required permissions or data. If any step fails—whether it’s a button that doesn’t respond, a form that rejects valid input, or a screen that crashes on a low‑end device—the user is likely to abandon the app immediately. Studies show that a 1‑second delay in the first screen can reduce conversion by up to 7 %, and a confusing flow can increase abandonment rates by 30 % or more. For Flutter apps, the cost of fixing an onboarding bug after release is typically 5‑10× higher than catching it during pre‑release testing because it often requires a hot‑fix, app store review, and user communication.
Common production bugs specific to Flutter onboarding
Flutter’s widget tree and reactive framework introduce a few failure modes that are less common in native Android or iOS code:
- State loss during hot‑restore – When the engine restores state after a background pause, a misplaced
initStateor aStreamSubscriptionthat isn’t cancelled can cause the onboarding screen to show stale data or throw an exception. - Layout overflow on varied screen sizes – Onboarding often uses flexible layouts with
Expanded,Flexible, orFractionallySizedBox. On small phones or tablets with unusual aspect ratios, overflow errors appear as red bands that hide UI elements. - Locale‑dependent formatting bugs – Dates, numbers, or currency shown in onboarding may rely on
intlpackage initialization. If the locale is changed before the first frame, missing callbacks can cause blank text or exceptions. - Accessibility oversights – Buttons without semantic labels, missing
excludeSemanticsfor decorative images, or insufficient contrast are frequently missed because developers test primarily with the default accessibility profile. - Permission handling races – Requesting location or camera permission inside an
onPressedhandler can race with the system dialog, leading to a state where the app thinks permission is granted while the UI still shows the rationale screen. - Network‑dependent UI states – Many onboarding flows fetch a configuration or feature flag from a remote endpoint. If the request times out, the UI may stay in a loading spinner indefinitely or show an error screen that lacks a retry mechanism.
Understanding these patterns helps you design a test matrix that targets the real‑world failure points rather than only the ideal path.
Building a Test Matrix for Flutter Onboarding
A test matrix gives you a structured way to ensure you cover all relevant dimensions: functional paths, error conditions, environmental variables, accessibility, and security. Below is a comprehensive matrix you can adapt to your specific onboarding flow. Each row represents a test scenario; each column indicates the test type | Test technique
Happy path – core flow | User completes every screen without interruption | Widget/integration test, manual walkthrough | Verify each screen transitions correctly, final state (e.g., user logged in, tutorial dismissed) shows expected data
Happy path – alternate entry | User opens app via deep link that lands on onboarding | Integration test with URL launch | Confirm deep link redirects correctly, onboarding still completes
Validation error – empty required field | User submits form with blank mandatory input | Widget test with form key, manual | Ensure inline error appears, submit button stays disabled, focus moves to first invalid field
Validation error – invalid format | User enters malformed email or phone | Widget test using TextFormField validator | Check specific error message, no navigation occurs
Network timeout – config fetch | Simulated 30 s delay or failure on remote config call | Integration test with network mocking (e.g., mockito) | Verify loading spinner shows, timeout error screen appears with retry button
Network retry success | After timeout, network recovers and config loads | Integration test with delayed mock then success | Ensure retry button works, flow proceeds after successful fetch
Orientation change mid‑flow | User rotates device while on a screen | Manual test on emulator/device, automation via flutter_driver | Confirm layout adapts, no UI elements are lost, state persists
Locale switch during onboarding | User changes system language after first screen | Manual test, integration test with WidgetsApp.locale override | Verify all text updates, dates/numbers format correctly, layout does not break
Accessibility – TalkBack navigation | User explores with TalkBack enabled | Manual test with accessibility scanner, widget test for semantics | Ensure each interactive element has a label, reading order is logical, focus traps are avoided
Accessibility – contrast check | Verify WCAG AA contrast for text and icons | Automated contrast test (e.g., flutter_lints with rules: [avoid_print]) | All text meets 4.5:1 ratio, large text 3:1
Permission denial – location | User denies location permission when requested | Manual test, integration test with permission_handler mock | Confirm rationale screen shows, app does not crash, alternative flow (e.g., manual entry) works
Permission grant after delay | User grants permission after initially denying and re‑prompting | Manual test, integration test with delayed grant | Verify app proceeds once granted, state updates correctly
Adversarial input – rapid taps | User taps buttons rapidly (e.g., 10 taps/sec) | Stress test via flutter_driver gesture simulation | Ensure no double navigation, state corruption, or crash
Adversarial input – long press on non‑interactive area | User long‑presses empty space | Manual test | No unexpected dialogs or debug menus appear
Security – clipboard leakage | Onboarding screen shows sensitive data (e.g., temp password) | Manual test, inspect clipboard after navigation | Verify no sensitive data remains in clipboard
Security – insecure network call | Config fetched over plain HTTP (if applicable) | Manual proxy (e.g., Charles) or dart:io security test | Ensure all network calls use HTTPS, certificate validation enabled
Privacy – analytics opt‑out | User toggles analytics opt‑out switch | Widget test for state persistence | Confirm opt‑out setting is stored and respected in subsequent launches
*Table 1: Flutter onboarding test matrix – scenarios, expected behavior, and suggested verification technique.*
You can prioritize the matrix based on risk. For most apps, happy‑path, validation errors, network timeout/retry, orientation, locale, and accessibility checks constitute the core regression suite. Security and privacy checks may be run less frequently (e.g., nightly) unless your onboarding handles personally identifiable information (PII) or financial data.
Manual Testing Approach Step‑by‑Step
Even with strong automation, manual exploratory testing remains essential for uncovering UX friction, edge‑case gestures, and issues that only appear under specific device characteristics.
Setting up a reproducible device/emulator matrix
- Define a device matrix covering the most common form factors your users have:
- Small phone (e.g., Pixel 4a, 5.8″, 1080×2340)
- Large phone (e.g., Pixel 7 Pro, 6.7″, 1440×3120)
- Small tablet (e.g., Nexus 9, 8.9″, 2048×1536)
- Large tablet/foldable (e.g., Samsung Tab S8, 11″, 2560×1600)
- OS versions – Include the lowest supported Android API (e.g., 21) and the latest stable (e.g., 34), plus the two most recent iOS versions if you target iOS via Flutter.
- Locale and accessibility profiles – For each device, create a clone with:
- Default locale
- Right‑to‑left language (Arabic or Hebrew)
- A locale with non‑Latin script (Japanese, Hindi)
- TalkBack/VoiceOver enabled with varying font sizes
- High contrast mode enabled
- Network conditions – Use the emulator’s cellular controls or a tool like
Network Link Conditioner(macOS) orClumsy(Windows) to simulate:
- 3G (slow, 150 ms latency)
- LTE with occasional packet loss (5 %)
- Complete offline state
Having a script that spins up these configurations (e.g., using flutter emulators and adb) ensures you can run the same manual steps across the matrix without forgetting a variant.
Exploratory checklist for each persona
SUSATest defines several user personas; you can mirror them in your manual sessions:
| Persona | Goal | Typical actions | What to watch for |
|---|---|---|---|
| Curious | Explore every UI element | Tap every icon, long‑press, swipe from edges | Hidden gestures, unexpected navigations |
| Impatient | Skip or rush through steps | Rapid taps, back button spamming, skip tutorial | State loss, duplicate navigation, crashes |
| Novice | Follow instructions literally | Read each tooltip, fill forms slowly | Unclear copy, missing guidance, unclear error messages |
| Adversarial | Try to break the app | Rotate rapidly, input extreme values, paste large strings | Overflows, crashes, security leaks |
| Elderly | Use larger fonts, slower interactions | Increase system font size, use single tap | Readability, touch target size, latency |
| Accessibility | Rely on screen reader | Navigate with TalkBack, use switch control | Semantic labels, reading order, focus traps |
| Power user | Use shortcuts, prefer efficiency | Use gesture shortcuts, toggle settings quickly | Responsiveness, shortcut conflicts |
For each persona, run through the onboarding flow while noting any deviation from the expected path, any visual glitch, any delay longer than 2 seconds, and any accessibility violation. Capture screenshots or short video clips; tools like adb shell screenrecord or QuickTime on macOS make this trivial.
Logging and capturing issues
When a problem is found, record:
- Device identifier (model, API level)
- OS locale and accessibility settings
- Network profile (if applicable)
- Exact steps (including timestamps)
- Observed behavior (e.g., “app crashed with StackOverflowError in
Navigator.push”) - Expected behavior
- Severity (critical, major, minor) based on impact to conversion or crash rate
Store these records in a shared spreadsheet or issue tracker with a label like onboarding‑exploratory. Over time, you’ll see patterns (e.g., a specific widget overflows on foldables) that can be turned into automated regression tests.
Automated Testing with Flutter's Native Tools
Flutter ships with a rich testing hierarchy: unit, widget, and integration tests. For onboarding, widget tests validate UI logic in isolation, integration tests validate end‑to‑end navigation and state, and golden tests protect against visual regressions.
Widget tests for onboarding screens
Widget tests run in a headless environment and are fast enough to include in every pull request. They are ideal for checking form validation, button enable/disable logic, and state changes triggered by user actions.
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/onboarding/login_page.dart';
void main() {
testWidgets('Login button disabled when email empty', (tester) async {
await tester.pumpWidget(
MaterialApp(home: LoginPage()),
);
// Initially, email field is empty
expect(find.text('Email'), findsOneWidget);
expect(find.byType(ElevatedButton), findsOneWidget);
expect(tester.widget<ElevatedButton>(find.byType(ElevatedButton)).onPressed,
isNull);
// Enter a valid email
await tester.enterText(find.byKey(const Key('emailField')), 'user@example.com');
await tester.pump(); // Rebuild after text change
// Button should now be enabled
expect(tester.widget<ElevatedButton>(find.byType(ElevatedButton)).onPressed,
isNotNull);
});
}
*Key points*: Use Key attributes on form fields and buttons to make them queryable. Call pump after each interaction to trigger a rebuild. Assert on the onPressed callback being null or a function to infer enabled/disabled state.
Integration tests using flutter_test and integration_test
Integration tests run on a real device or emulator and exercise the full navigation stack, asynchronous calls, and persistence layers. They are slower but give confidence that the entire onboarding flow works end‑to‑end.
First, add the dependency in dev_dependencies:
integration_test:
sdk: flutter
Create integration_test/onboarding_flow_test.dart:
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Onboarding flow', () {
testWidgets('complete signup with valid data', (tester) async {
app.main();
await tester.pumpAndSettle();
// 1. Welcome screen – tap Get Started
await tester.tap(find.text('Get Started'));
await tester.pumpAndSettle();
// 2. Enter name
await tester.enterText(find.byKey(const Key('nameField')), 'Ada Lovelace');
await tester.tap(find.text('Continue'));
await tester.pumpAndSettle();
// 3. Enter email
await tester.enterText(find.byKey(const Key('emailField')), 'ada@example.com');
await tester.tap(find.text('Continue'));
await tester.pumpAndSettle();
// 4. Set password
await tester.enterText(
find.byKey(const Key('passwordField')), 'SecurePass123!');
await tester.tap(find.text('Create Account'));
await tester.pumpAndSettle();
// 5. Verify we land on home screen
expect(find.text('Welcome, Ada!'), findsOneWidget);
});
});
}
Run with:
flutter drive --target=integration_test/onboarding_flow_test.dart \
-d <device-id>
*Tips for reliability*:
- Wrap asynchronous waits in
tester.pumpAndSettle()or useFuture.delayedwith a matcher likefindsOneWidgetinsideexpectLater. - Mock external services (e.g., config fetch) using packages like
mockitoorhttp_mock_adapterto avoid flaky network dependence. - Clear shared preferences or secure storage before each test to ensure a clean state (
await tester.binding.defaultBinaryMessenger.handlePlatformMessage(...)).
Golden tests for UI consistency
Golden (snapshot) tests catch unintended visual changes—especially useful for onboarding where branding and layout are critical.
Add flutter_test and flutter_goldens to dev dependencies, then:
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_goldens/flutter_goldens.dart';
import 'package:my_app/onboarding/welcome_page.dart';
void main() {
testGoldens('Welcome page looks correct on small phone', (tester) async {
await tester.pumpWidget(
MaterialApp(home: WelcomePage()),
);
await tester.pumpAndSettle();
await matcher(
find.byType(WelcomePage),
matchesGoldenFile('welcome_page_small_phone.png'),
);
});
}
Run the test once to generate the golden image, then commit it. On CI, the test will fail if any pixel deviates beyond the allowed threshold (default is 0 %). You can adjust tolerance with matchesGoldenFile(..., tolerance: 0.02) to allow minor anti‑aliasing differences.
*Table 2: Comparison of widget, integration, and golden tests for onboarding.*
| Test type | Speed | Fidelity | Typical use in onboarding | Maintenance effort |
|---|---|---|---|---|
| Widget | < 200 ms per test | UI logic, state, gestures (no async I/O) | Form validation, button enable/disable, navigation triggers | Low – update when widget changes |
| Integration | 5‑30 s per test (device) | Full navigation, async, persistence, platform plugins | End‑to‑end signup, login, permission flows | Medium – need device farm, mocking |
| Golden | ~1 s per test (render) | Pixel‑perfect layout, theming, responsiveness | Branding, responsive breakpoints, accessibility contrast | Low‑medium – update goldens after intentional UI change |
You should run widget tests on every PR, integration tests on every nightly build or pre‑release branch, and golden tests whenever you change the theme or layout files.
Leveraging Flutter Driver and Firebase Test Lab
When you need more control than integration_test provides—such as simulating complex gestures, accessing device APIs, or running across a large matrix of real devices—Flutter Driver combined with Firebase Test Lab is a powerful option.
Writing Flutter Driver scripts for onboarding
Flutter Driver operates via a separate flutter_driver extension. Create test_driver/onboarding_driver.dart:
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
void main() {
group('Onboarding flow (Flutter Driver)', () {
FlutterDriver? driver;
setUpAll(() async {
driver = await FlutterDriver.connect();
});
tearDownAll(() async {
if (driver != null) {
await driver.close();
}
});
test('complete onboarding with valid inputs', () async {
// Wait for welcome screen
await driver.waitFor(find.text('Get Started'));
await driver.tap(find.text('Get Started'));
await driver.waitFor(find.byValueKey('nameField'));
// Enter name
await driver.enterText(find.byValueKey('nameField'), 'Grace Hopper');
await driver.tap(find.byValueKey('continueButton'));
await driver.waitFor(find.byValueKey('emailField'));
// Enter email
await driver.enterText(find.byValueKey('emailField'), 'grace@example.com');
await driver.tap(find.byValueKey('continueButton'));
await driver.waitFor(find.byValueKey('passwordField'));
// Enter password
await driver.enterText(
find.byValueKey('passwordField'), 'StrongPass!456');
await driver.tap(find.byValueKey('createAccountButton'));
await driver.waitFor(find.text('Welcome, Grace!'));
// Final assertion
final welcomeText = await driver.getText(find.text('Welcome, Grace!'));
expect(welcomeText, equals('Welcome, Grace!'));
});
});
}
Run the script locally with:
flutter drive \
--target=test_driver/onboarding_driver.dart \
-d <device-id>
Running on Firebase Test Lab matrix
Firebase Test Lab lets you execute the same driver script on dozens of real device configurations in parallel.
- Build the APK and test APK:
flutter build apk --debug --split-debug-info=<path>/info
flutter build apk --debug --target-platform=android-arm,android-arm64 \
-t test_driver/onboarding_driver.dart
- Upload to Test Lab via the
gcloudCLI:
gcloud firebase test android run \
--type instrumentation \
--app build/app/outputs/flutter-apk/app-debug.apk \
--test build/app/outputs/flutter-apk/app-debug_test.apk \
--device model=Pixel4,version=30,locale=en,orientation=portrait \
--device model=Pixel4XL,version=30,locale=ar,orientation=landscape \
--device model=Nexus9,version=28,locale=ja,orientation=portrait \
--timeout=10m
You can add as many --device flags as needed to cover your matrix. Test Lab will return a detailed results bucket in Google Cloud Storage, including logs, screenshots, and a summary of passed/failed tests.
Analyzing results
- Logcat – Look for exceptions like
StateErrororAssertionErrorthat indicate state loss. - Screenshot diff – Compare golden screenshots taken by the driver script against baseline images to spot layout regressions.
- Performance metrics – Test Lab reports frame times; if any onboarding frame exceeds 16 ms, you may have jank that harms perception.
- Flakiness detection – If a test passes on some devices but fails on others with the same OS version, investigate device‑specific rendering issues (e.g., GPU driver differences).
Flutter Driver + Test Lab is especially valuable for catching issues that only manifest on specific hardware GPUs or on devices with unusual pixel densities—scenarios that pure widget tests cannot reproduce.
Persona‑Driven Autonomous Exploration with SUSATest
While scripted tests are excellent for regression, they can never anticipate every creative way a real user might interact with your onboarding. Autonomous, persona‑driven exploration complements your test suite by exercising the app exactly as different types of users would, surfacing UX friction, hidden dead ends, and edge‑case bugs that scripts never think to probe.
How SUSATest models user personas
SUSATest ships with eight built‑in personas, each defined by a behavior profile that influences:
- Tap density – How frequently and rapidly the agent taps UI elements.
- Scroll propensity – Likelihood to scroll versus interact with static controls.
- Input style – Whether the agent prefers pasting, typing character‑by‑character, or using voice input.
- Decision making – Probability to follow a suggested CTA vs. exploring alternatives.
- Error tolerance – How the agent reacts to validation messages (retry, abandon, or try alternative inputs).
- Accessibility mode – Whether the agent enables TalkBack/VoiceOver and uses increased font sizes.
These profiles are encoded as JSON configuration files that you can extend or tune for your product’s specific audience.
Running a scan on an Flutter APK or web URL
To evaluate your Flutter onboarding flow with SUSATest:
- Prepare the build – Generate a debug or profile APK (
flutter build apk --debug). Ensure the app does not require sign‑in credentials that would block the agent; you can provide a demo account via a flag or disable authentication for the test build. - Upload or point – Either upload the APK to the SUSATest web portal or provide a publicly reachable URL (for Flutter web).
- Select personas – Choose the subset relevant to your market (e.g., Curious, Impatient, Novice, Accessibility).
- Start the exploration – The agent will launch the app on a cloud‑hosted device farm, begin interacting with onboarding screens, and record every action, state transition, and observed anomaly.
- Retrieve the report – After the run (typically 5‑15 minutes depending on depth), download the JSON report and the associated video/screenshots.
Interpreting the onboarding flow report
The SUSATest report includes several sections that map directly to the test matrix you built earlier:
- Flow coverage – Percentage of onboarding screens visited by each persona. Low coverage for a persona may indicate a dead‑end (e.g., a button that leads nowhere for an Impatient user who taps rapidly).
- Crash and ANR log – Stack traces symbolicated; look for patterns like
NullThrownErrorin aStreamBuilderthat only appears when the agent types very fast. - Accessibility violations – WCAG AA/AAA issues flagged by the integrated axe‑core engine, such as missing labels or insufficient contrast, often missed in manual testing because the agent uses the accessibility persona.
- UX friction metrics – Time spent on each screen, number of back‑button presses, and rate of skipped steps. High friction on a particular screen suggests copy or flow redesign.
- Security and privacy hints – Detection of clipboard writes, clear‑text logging of tokens, or insecure HTTP calls made during onboarding.
Because SUSATest explores based on behavior rather than pre‑scripted assertions, it frequently surfaces bugs like:
- A “Skip” button that is only visible after a 2‑second delay, causing Impatient users to miss it and think the app is frozen.
- A text field that accepts emojis but then crashes when the backend tries to store them in a column with limited charset.
- A permission rationale dialog that appears only when the device language is set to a right‑to‑left locale, causing layout overlap.
Integrating SUSATest into your CI pipeline (via the susatest-agent CLI) lets you treat autonomous exploration as another test stage, catching regressions that unit/widget tests would never see.
pip install susatest-agent
susatest run \
--apk path/to/app.apk \
--personas curious impatient accessibility \
--max-depth 5 \
--output-dir ./susatest-report
The agent will exit with a non‑zero status if any critical defect (crash, ANR, or blocker) is found, allowing the pipeline to fail fast.
Continuous Integration and Flaky Test Mitigation
Even the best test suite can suffer from flakiness—non‑deterministic passes/failures that erode trust. In Flutter onboarding, flakiness often stems from timing‑dependent animations, network mocks, or device‑specific rendering differences. A robust CI strategy combines parallel execution, smart retry policies, and baseline monitoring.
CI pipeline configuration (GitHub Actions example)
Below is a sample workflow that runs widget tests on every push, integration tests nightly, and golden tests on PRs that touch UI files.
name: Flutter CI
on:
push:
branches: [main]
pull_request:
paths:
- 'lib/**'
- 'test/**'
jobs:
widget-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
- run: flutter pub get
- run: flutter test --coverage
integration-tests-nightly:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: macos-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
- run: flutter pub get
- run: |
flutter drive \
--target=integration_test/onboarding_flow_test.dart \
-d emulator-5554
golden-tests:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
- run: flutter pub get
- run: flutter test --test-expands --golden-test
Key takeaways:
- Matrix builds – For integration tests, you can add a strategy matrix to run on multiple device emulators (Android API levels, iOS simulators) in parallel.
- Artifact retention – Store test logs, screenshots, and coverage reports as workflow artifacts for later inspection.
- Selective triggering – Run heavy integration tests only on a schedule or manual dispatch to keep PR feedback fast.
Handling flakiness with retry and quarantine
Flaky tests often appear in integration suites due to:
- Animation races – A navigation waits for a specific widget that appears after an animation finishes. Use
tester.waitUntilwith a timeout instead of fixedpumpAndSettle. - Network mock timing – If you simulate a delayed response, jitter in the test runner can cause the mock to fire early or late. Wrap the mock in a
Future.delayedwith a range and assert on a range of acceptable completion times. - Device state leftover – Leftover shared preferences or cache from a previous test can affect the next run. Clear state in
setUpAll/tearDownAllor launch each test with a clean app data directory (flutter drive --dart-define=TEST_RUN=trueand check the flag inmain()).
When a test fails, apply a retry policy with a limit (e.g., two retries) and move consistently flaky tests to a quarantine label or separate test file that runs only in a nightly job. Track the flake rate over time; if a test’s failure frequency drops below a threshold (e.g., < 1 % over 30 builds), consider moving it back to the main suite.
Monitoring trends over time
Leverage the data your CI already produces:
- Test duration trends – Sudden increases in integration test runtime may indicate a newly added heavy operation (e.g., a large image decode) in onboarding.
- Flake rate per test – Use the GitHub Checks API or a third‑party service like Testomat or pytest‑insight to compute flakiness per test.
- Coverage delta – Ensure that widget test coverage for onboarding screens stays above a agreed threshold (e.g., 90 %). If coverage drops, investigate why new screens were added without tests.
- SUSATest defect trend – If you run autonomous scans weekly, chart the number of critical defects discovered per run. A rising trend may signal regressions in navigation or state management.
By treating test health as a first‑class metric, you can keep the onboarding verification pipeline reliable and actionable.
Checklist for Onboarding Flow Testing in Flutter
Having a concise, actionable checklist helps teams ship with confidence and ensures nothing falls through the cracks during release preparation.
Pre‑release checklist
| ✅ Item | Description |
|---|---|
| Widget test coverage ≥ 90 % for all onboarding screens | Run flutter test --coverage and verify lcov report. |
| All validation paths (empty, invalid, boundary) have unit/widget tests | Check test files for FormFieldValidator scenarios. |
| Integration test completes on at least three device configurations (small phone, large phone, tablet) | Use Firebase Test Lab or local emulator matrix. |
| Golden tests pass for default theme and for high‑contrast theme | Run flutter test --goldens on both themes. |
| Accessibility scan (axe‑core or flutter_lints) reports zero WCAG AA failures on onboarding | Run flutter pub run flutter_lints or use flutter_axe. |
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free