flutter_page_object codecov

Flutter library for writing page objects using the PageObject pattern. Makes your tests easier to write, read, and maintain.

📖 Table of Contents

⚡ 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

late final submitButton = d.byKey.button(const Key('submit_button'));
late final emailTextField = d.byKey.textFormField(const Key('email_text_field'));
// 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 r as shorthand for root: r.byKey.button(const Key('submit'))
  • Use d as shorthand for descendant: 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 Radio widgets)
    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)
    
  • 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

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

🎯 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

🤝 Contribution

Contributions are welcome! Feel free to reach out or submit a PR for:

  • New page object types
  • Documentation improvements
  • Example apps
  • Bug reports