Learn about Patrol’s powerful custom finder system and the $ syntax that makes widget testing intuitive and concise
Patrol introduces a revolutionary custom finder system that makes Flutter widget testing more intuitive, concise, and maintainable. At its core is the $ syntax, which dramatically simplifies how you locate and interact with widgets in your tests.
Patrol’s custom finders build on top of Flutter’s flutter_test package rather than replacing it. The PatrolFinder class is a decorator around the standard Finder that extends it with powerful features while preserving all original functionality.
The containing() method performs lookahead checks to find parent widgets that contain specific descendants:
// Find a ListTile that contains 'Activated' text, then find 'learnMore' button within itawait $(ListTile).containing('Activated').$(#learnMore).tap();
When hierarchical relationships aren’t enough, use the which() method to filter widgets by their properties:
1
Filter by widget properties
// Find a TextField where text is not emptyawait $(#cityTextField) .which<TextField>((widget) => widget.controller!.text.isNotEmpty) .enterText('Warsaw, Poland');
// Check if widget is visible to userexpect($('Log in').visible, equals(true));expect($('Offscreen').visible, equals(false));// Check visibility at specific alignmentexpect($('Button').isVisibleAt(alignment: Alignment.topLeft), equals(true));
The visible getter checks if the widget is hit-testable at Alignment.center. If your widget is partially visible or visible at a different alignment, use isVisibleAt() instead.
Patrol’s action methods automatically wait for widgets to become visible before interacting with them:
// Waits for widget to be visible, then tapsawait $('Subscribe').tap();// Tap with custom timeoutawait $('Subscribe').tap( visibleTimeout: Duration(seconds: 5),);
When a finder matches multiple widgets, use indexing methods:
// Get the first match (default behavior for actions)await $('Subscribe').first.tap();// Get the last matchawait $('Subscribe').last.tap();// Get a specific index (zero-based)await $('Subscribe').at(2).tap(); // Taps the third match
Patrol actions like tap() automatically operate on the first visible widget. You only need to use .first, .last, or .at() when you specifically need a different match.
Patrol provides explicit waiting methods for better test control:
// Wait for widget to exist in treeawait $('Loading').waitUntilExists();// Wait for widget to be visibleawait $('Dashboard').waitUntilVisible();// Wait with custom timeoutawait $('Slow Loading').waitUntilVisible( timeout: Duration(seconds: 10),);// Chain waiting with actionsawait $('Submit') .waitUntilVisible() .tap();