Skip to main content
Flutter’s finders are powerful, but not very intuitive to use. Patrol’s custom finders transform verbose, complex test code into clean, readable expressions.

The Problem with Traditional Finders

Let’s look at a typical Flutter widget test using traditional finders:
testWidgets('signs up', (WidgetTester tester) async {
  await tester.pumpWidget(AwesomeApp());
  await tester.pumpAndSettle();

  await tester.enterText(
    find.byKey(Key('emailTextField')),
    '[email protected]',
  );
  await tester.pumpAndSettle();

  await tester.enterText(
    find.byKey(Key('nameTextField')),
    'Charlie',
  );
  await tester.pumpAndSettle();

  await tester.enterText(
    find.byKey(Key('passwordTextField')),
    'ny4ncat',
  );
  await tester.pumpAndSettle();

  await tester.tap(find.byKey(Key('termsCheckbox')));
  await tester.pumpAndSettle();

  await tester.tap(find.byKey(Key('signUpButton')));
  await tester.pumpAndSettle();

  expect(find.text('Welcome, Charlie!'), findsOneWidget);
});
Notice the repetitive pumpAndSettle() calls and verbose find.byKey() syntax.

The Patrol Solution

Here’s the same test using Patrol’s custom finders:
patrolWidgetTest('signs up', (PatrolTester $) async {
  await $.pumpWidgetAndSettle(AwesomeApp());

  await $(#emailTextField).enterText('[email protected]');
  await $(#nameTextField).enterText('Charlie');
  await $(#passwordTextField).enterText('ny4ncat');
  await $(#termsCheckbox).tap();
  await $(#signUpButton).tap();

  await $('Welcome, Charlie!').waitUntilVisible();
});
Notice how much cleaner and more readable this is! The $ syntax provides a concise way to find and interact with widgets.

Key Benefits

Concise Syntax

The $ operator replaces verbose find.byKey(), find.byType(), and other finder methods

Automatic Waiting

Actions like tap() and enterText() automatically wait for widgets to become visible

Chainable Finders

Easily express complex widget hierarchies with chainable syntax

Smart Pumping

Built-in frame rendering eliminates manual pumpAndSettle() calls

What You Can Find

Patrol’s $ operator is incredibly flexible. It accepts multiple types:
TypeExampleFlutter Equivalent
Text$('Log in')find.text('Log in')
Widget Type$(TextField)find.byType(TextField)
Symbol (Key)$(#emailInput)find.byKey(Key('emailInput'))
Key$(Key('emailInput'))find.byKey(Key('emailInput'))
Icon$(Icons.add)find.byIcon(Icons.add)
Pattern$(RegExp('Log.*'))find.textContaining(RegExp('Log.*'))
Widget$(myWidget)find.byWidget(myWidget)
Finder$(find.text('Hi'))find.text('Hi')

Platform Support

Beyond Basic Finding

Patrol finders aren’t just about finding widgets—they’re about interacting with them naturally:
// Tap on a button
await $(#loginButton).tap();

// Enter text into a field
await $(#emailField).enterText('[email protected]');

// Scroll to a widget and tap it
await $('Delete account').scrollTo().tap();

// Wait for a widget to appear
await $('Success message').waitUntilVisible();

// Check if a widget is visible
expect($('Error message').visible, false);

What’s Next?

1

Setup

Add patrol_finders to your project and configure your first testGo to Setup →
2

Basic Usage

Learn how to find widgets, make assertions, and perform actionsGo to Usage →
3

Advanced Techniques

Master complex scenarios, chaining, and the which() methodGo to Advanced →

Build docs developers (and LLMs) love