flutter_page_object 
Flutter library for writing page objects using the PageObject pattern. Makes your tests easier to write, read, and maintain.
📖 Table of Contents
- flutter_page_object
⚡ Quick Start
Install:
dev_dependencies:
flutter_page_object: ^1.0.0
Your first page object:
class LoginPageObject extends PageObject {
late final usernameTextField = d.byKey.textFormField(const Key('username'));
late final passwordTextField = d.byKey.textFormField(const Key('password'));
late final loginButton = d.byKey
.navButton(const Key('login_button'), targetBuilder: HomePageObject.new);
LoginPageObject(WidgetTester t)
: super(t, find.byKey(const Key('login_page')));
Future<void> completeForm() async {
await usernameTextField.enterText('test_user');
await passwordTextField.enterText('password123');
await t.pump();
}
}
Your first test:
testWidgets('form completed and tap login button --> navigates to home page', (t) async {
await t.pumpWidget(const App());
final loginPage = LoginPageObject(t);
await loginPage.completeForm();
final homePage = await loginPage.loginButton.tapNavAndSettle();
expect(homePage, findsOne);
});
Why this is better than raw finders:
- ✅ Readable: Tests read like user actions, not implementation details
- ✅ Maintainable: Change selector in one place; all tests automatically work
- ✅ Reusable: Define once, use across dozens of tests
- ✅ Centralized: All widget interactions in one place per page
📚 Common Patterns
Find by Key (Recommended)
late final submitButton = d.byKey.button(const Key('submit_button'));
late final emailTextField = d.byKey.textFormField(const Key('email_text_field'));
Descendant Search (recommended for complex pages)
// Find descendants of this page object (searches within this widget's subtree)
late final title = d.byKey.text(const Key('title'));
// Search from root instead of descendants
late final snackBar = r.byType.snackBar(SnackBar);
Common Interactions
// Tap and wait for UI to settle
await button.tapAndSettle();
// Enter text
await textField.enterText('hello');
// Toggle switch/checkbox
await toggle.set(true);
// Select dropdown item
await dropdown.select(Category.food);
// Get values
final text = textField.text;
final isEnabled = button.isEnabled;
final isChecked = checkbox.value;
📋 Supported Widgets
Base PageObject
All page objects extend PageObject and inherit these base interactions and properties:
// Interactions
Future<void> tap()
Future<void> tapAndPump()
Future<void> tapAndSettle()
Future<void> longPress()
Future<void> longPressAndPump()
Future<void> longPressAndSettle()
Future<void> drag(Offset offset)
Future<void> dragAndPump(Offset offset)
// Waiting
Future<void> waitUntilHitTestable({Duration timeout})
Future<void> waitWhileHitTestable({Duration timeout})
// Accessors
T widget<T extends Widget>()
T state<T extends State>()
// Properties
bool get isHitTestable
late final root // Access page objects from root
late final descendant // Access descendant page objects
Shortcuts:
- Use
ras shorthand forroot:r.byKey.button(const Key('submit')) - Use
das shorthand fordescendant:d.byKey.textField(const Key('email'))
Text Input Widgets
- TextFieldPageObject (
TextField)String get text Future<void> enterText(String) Future<void> submitText([String?]) - TextFormFieldPageObject (
TextFormField)String get text Future<void> enterText(String) Future<void> submitText([String?]) - TypedTextFieldPageObject<T> (
TextField- typed)String get text T get value Future<void> enterText(String) Future<void> enterValue(T) Future<void> submitText([String?]) Future<void> submit() Future<void> submitValue(T) - TypedTextFormFieldPageObject<T> (
TextFormField- typed)String get text T get value Future<void> enterText(String) Future<void> enterValue(T) Future<void> submitText([String?]) Future<void> submit() Future<void> submitValue(T)
Selection Widgets
- CheckboxPageObject (
Checkbox,CheckboxListTile,CupertinoCheckbox)bool get value bool get isEnabled bool get isDisabled Future<void> check() Future<void> uncheck() Future<void> set(bool) - TristateCheckboxPageObject (
Checkbox,CheckboxListTile,CupertinoCheckbox- tristate)bool? get value bool get isEnabled bool get isDisabled Future<void> check() Future<void> uncheck() Future<void> indeterminate() Future<void> set(bool?) - RadioPageObject<T> (
Radio<T>,RadioListTile<T>,CupertinoRadio<T>)T get value T? get groupValue bool get isSelected bool get isEnabled bool get isDisabled Future<void> select() - RadioGroupPageObject<T> (Multiple
Radiowidgets)T? get groupValue bool isSelected(T value) Future<void> select(T) - SwitchPageObject (
Switch,SwitchListTile,CupertinoSwitch)bool get value bool get isEnabled bool get isDisabled Future<void> turnOn() Future<void> turnOff() Future<void> set(bool) - DropdownPageObject<T> (
DropdownButton<T>,DropdownButtonFormField<T>)T? get value bool get isOpen bool get isEnabled bool get isDisabled Future<void> select(T?) Future<void> open() Future<void> close() Future<List<T?>> values() - ChipPageObject (
ChoiceChip,FilterChip,InputChip,ActionChip)bool get isSelected bool get isEnabled bool get isDisabled Future<void> select() Future<void> deselect() Future<void> set(bool)
Navigation Widgets
- ButtonPageObject (
ElevatedButton,TextButton,IconButton,FloatingActionButton,CupertinoButton)bool get isEnabled bool get isDisabled - NavPageObject<T> (widgets with navigation)
Future<T> tapNav({bool expectTarget}) Future<T> tapNavAndPump({bool expectTarget}) Future<T> tapNavAndSettle({bool expectTarget}) - NavButtonPageObject<T> (Button variants with navigation)
Future<T> tapNav({bool expectTarget}) Future<T> tapNavAndPump({bool expectTarget}) Future<T> tapNavAndSettle({bool expectTarget}) - TabBarPageObject (
TabBar)int get selectedIndex Future<void> select(int) - DrawerPageObject (
Drawer)bool get isOpen Future<void> open() Future<void> close() - BottomNavigationBarPageObject (
BottomNavigationBar)int get selectedIndex Future<void> selectByIndex(int) Future<void> selectByIcon<T>(IconData) NavPageObject<T> item<T extends PageObject>({required IconData icon, required PageObjectStaticBuilder<T> targetBuilder})
Display Widgets
- TextPageObject (
Text,RichText)String get text - TypedTextPageObject<T> (
Text,RichText- typed)String get text T get value - ImagePageObject (
Image)ImageProvider? get image String? get semanticLabel - ProgressIndicatorPageObject (
ProgressIndicator,LinearProgressIndicator,CircularProgressIndicator)double? get value
Layout & Scrolling
- ScrollablePageObject (
SingleChildScrollView,ListView,CustomScrollView)Future<void> scrollUpUntilVisible(Finder, {double delta, int maxScrolls}) Future<void> scrollDownUntilVisible(Finder, {double delta, int maxScrolls}) Future<void> fling({double dx, double dy, double speed}) Future<void> pullToRefresh() - ScrollableListPageObject<T> (
ListView,GridView)int get count List<T> get all T operator [](int index) T item(Finder itemFinder) // + all ScrollablePageObject methods - WidgetListPageObject<T> (
Column,Row)int get count List<T> get all T operator [](int index) T item(Finder itemFinder) - SlidablePageObject (
PageView,TabView)Future<void> swipeToStart({double? dx, double? speed}) Future<void> swipeToEnd({double? dx, double? speed}) - SliderPageObject (
Slider)double get value double get min double get max int? get divisions bool get isEnabled bool get isDisabled Future<void> drag(Offset, {bool warnIfMissed})
Other
- SnackBarPageObject (
SnackBar)late final actionButton Future<void> dismiss() - WidgetPageObject (Any
Widget)// Inherits all base PageObject interactions and accessors
🎯 Creating Your Own Page Object
Extend PageObject to create custom page objects for your app's unique widgets:
class CustomButtonPageObject extends PageObject {
CustomButtonPageObject(WidgetTester t, Finder finder)
: super(t, finder);
/// Gets the button label text
String get label => widget<YourCustomButton>().label;
/// Performs a custom interaction
Future<void> longPressAndDrag(Offset offset) async {
await longPress();
await drag(offset);
}
}
// Add it to a factory extension for easy access:
extension CustomButtonPageObjectExtension<K> on PageObjectFactory<K> {
CustomButtonPageObject customButton(K key) =>
create(CustomButtonPageObject.new, key);
}
// Use it:
late final customButton = d.byKey.customButton(const Key('my_widget'));
📖 Full Examples
- Login Form - See example/test_common/lib/login_page_object.dart and example/test/login_page_test.dart
- Product List & Detail - See example/lib/ with comprehensive form and list interactions
🤝 Contribution
Contributions are welcome! Feel free to reach out or submit a PR for:
- New page object types
- Documentation improvements
- Example apps
- Bug reports