Flutter app testing · Patrol
Tap through your Flutter app.
Keep it as an automated test.
Run your app on an Android emulator, an Android phone or an iOS simulator, walk through the flow you already test and say what should happen. SaySpec turns it into an automated test written with Patrol, the Flutter testing framework, and runs it on the same device.
How it works
From a manual walkthrough
to an automated test.
- 01
Add the recorder
SaySpec adds the open-source sayspec_flutter_recorder package to your project and shows every change before it writes a file. The recorder stays off unless SaySpec launches the app, so your normal builds run as before.
- 02
Launch it on a device
Pick an Android emulator, an Android phone or an iOS simulator and launch the app for recording. Start and stop each test from the SaySpec overlay in the app. After Stop, the app restarts for the next test without rebuilding.
- 03
Show it and say it
Tap, drag and type while you narrate what should happen. The recorder captures each action, the screen it happened on and the widget it touched, identified by its key, semantics label or visible text. Your narration is transcribed on your Mac.
- 04
Get an automated test
Your own coding agent, Claude Code or Codex, writes the test with Patrol in a terminal inside SaySpec. SaySpec runs that exact test on your device and saves it only if it passes.
- 05
Run it and fix what fails
Run saved tests on any of your devices. When one fails, SaySpec says whether it looks like an app bug, a test bug, an environment problem or a flaky run, and can propose a fix to your app or to the test. Nothing changes until you approve the exact patch.
What you keep
Readable Dart.
In your project.
Each test is a Dart file in your project’s patrol_test folder, written with Patrol and Flutter’s own finders. Give important controls a widget key or a semantics label, and the tests keep finding them when the layout changes.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:patrol/patrol.dart';
import 'package:shop/main.dart' as app;
void main() {
patrolTest('coupon reduces cart total', ($) async {
app.main();
await $.pumpAndSettle();
await $(find.byKey(const ValueKey<String>('coupon_field'))).enterText('SAVE20');
await $('Apply coupon').tap();
await $.pumpAndSettle();
expect($('Total: \$60.00'), findsOneWidget);
});
}
Why Patrol
Built for Flutter.
Tested from inside the app.
Patrol is an open-source testing framework built on Flutter’s own integration_test. It finds widgets by key, text and semantics from inside the app, and it can also work with native parts of the phone, such as permission dialogs. Tools that drive the app from outside, such as Maestro or Appium, see only what Flutter exposes to accessibility services.
Read more in Automating Flutter tests: why mobile is harder than web. Testing a web app as well? See web app testing.
Get SaySpec
Record your first Flutter test.
Turn the walkthrough you already perform on a device into an automated test.
Get SaySpec appQuestions, answered.
What do I need?
A Mac with Flutter, and Claude Code or Codex signed in on it. For Android, the Android SDK, a JDK and an emulator or a phone with USB debugging. For iOS, Xcode and an iOS simulator. In SaySpec, Settings → Machine setup checks each of these and installs what it can, such as the Patrol CLI.
What changes in my app?
The recorder package, a navigator observer, and a RepaintBoundary around your app for screenshots. SaySpec shows the exact changes before it writes them, and when the app is launched normally the recorder stays off.
Does it work on a physical iPhone?
Not yet. Recording and test runs work on Android emulators, Android phones and iOS simulators. A physical iPhone needs a signed build, which SaySpec does not create.
Are passwords recorded?
Yes. Text fields are recorded as entered, passwords included, because the test signs in with the same value. Record only with test accounts, never with real credentials.