Usage
Once set up, interacting with the native UI using Patrol is very easy!
Basics
After you've got your PlatformAutomator object via $.platform, you simply call methods on it
and it does the magic. For cross-platform mobile actions, use $.platform.mobile.
To tap on a native view (for example, a button in a WebView):
await $.platform.mobile.tap(Selector(text: 'Sign up for newsletter'));To enter text into a native view (for example, a form in a WebView):
await $.platform.mobile.enterText(
Selector(text: 'Enter your email'),
text: 'charlie@root.me',
);You can also enter text into the n-th currently visible text field (counting from 0):
await $.platform.mobile.enterTextByIndex('charlie_root', index: 0); // enter username
await $.platform.mobile.enterTextByIndex('ny4ncat', index: 1); // enter passwordThe above are the simplest, most common actions, but they already make it possible to test scenarios that were impossible to test before, such as WebViews.
Platform-specific selectors
When you need different selectors for Android and iOS, use MobileSelector:
await $.platform.mobile.tap(
MobileSelector(
android: AndroidSelector(resourceName: 'com.example:id/button'),
ios: IOSSelector(identifier: 'myButton'),
),
);Android-specific actions
For Android-only actions, use $.platform.android:
// Press the hardware back button (Android only)
await $.platform.android.pressBack();
// Double press the recent apps button to switch to the previous app
await $.platform.android.pressDoubleRecentApps();
// Tap on a native view using Android-specific selector
await $.platform.android.tap(
AndroidSelector(resourceName: 'com.example:id/submit_button'),
);
// Open a specific Android app
await $.platform.android.openPlatformApp(androidAppId: 'com.android.settings');iOS-specific actions
For iOS-only actions, use $.platform.ios:
// Tap on a native view using iOS-specific selector
await $.platform.ios.tap(
IOSSelector(identifier: 'submitButton'),
);
// Close a heads-up notification
await $.platform.ios.closeHeadsUpNotification();
// Swipe back gesture (iOS edge swipe)
await $.platform.ios.swipeBack();
// Open a specific iOS app
await $.platform.ios.openPlatformApp(iosAppId: 'com.apple.Preferences');To tap, enter text, or perform generally any UI interaction with an iOS app that is not your Flutter app under test, you need to pass its bundle identifier. For example, to tap on the "Add" button in the iPhone contacts app:
await $.platform.ios.tap(
IOSSelector(text: 'Add'),
appId: 'com.apple.MobileAddressBook',
);Web-specific actions
For Flutter Web apps, use $.platform.web to interact with browser elements and features:
// Tap on a web element by text
await $.platform.web.tap(WebSelector(text: 'Submit'));
// Tap on a web element using CSS selector
await $.platform.web.tap(WebSelector(cssOrXpath: 'css=#submit-button'));
// Tap on a web element by test ID
await $.platform.web.tap(WebSelector(testId: 'login-button'));
// Enter text into a form field
await $.platform.web.enterText(
WebSelector(placeholder: 'Email address'),
text: 'user@example.com',
);
// Scroll to an element
await $.platform.web.scrollTo(WebSelector(text: 'Load more'));Web automation also supports advanced browser interactions:
// Handle browser dialogs
await $.platform.web.acceptNextDialog();
await $.platform.web.dismissNextDialog();
// Manage cookies
await $.platform.web.addCookie(name: 'session', value: 'abc123');
await $.platform.web.clearCookies();
// Control dark mode
await $.platform.web.enableDarkMode();
await $.platform.web.disableDarkMode();
// Browser navigation
await $.platform.web.goBack();
await $.platform.web.goForward();
// Keyboard interactions
await $.platform.web.pressKey(key: 'Enter');
await $.platform.web.pressKeyCombo(keys: ['Control', 'a']);
// Clipboard operations
await $.platform.web.setClipboard(text: 'Copied text');
final clipboardContent = await $.platform.web.getClipboard();
// Browser permissions
await $.platform.web.grantPermissions(permissions: ['geolocation', 'notifications']);
await $.platform.web.clearPermissions();
// File uploads
await $.platform.web.uploadFile(files: [UploadFileData(name: 'test.txt', content: 'Hello')]);
// Verify file downloads
final downloadedFiles = await $.platform.web.verifyFileDownloads();
// Resize browser window
await $.platform.web.resizeWindow(size: Size(1920, 1080));Working with iframes:
// Tap on an element inside an iframe
await $.platform.web.tap(
WebSelector(text: 'Submit'),
iframeSelector: WebSelector(cssOrXpath: 'css=#payment-iframe'),
);Cross-platform mobile actions
For actions that work on both Android and iOS, use $.platform.mobile. This is the recommended
approach when you don't need platform-specific behavior, as it keeps your tests clean and
maintainable across both platforms.
The $.platform.mobile automator automatically routes calls to the appropriate platform
implementation based on where your test is running. You can use the unified Selector class
for simple cases, or MobileSelector when you need different selectors per platform.
Notifications
To open the notification shade:
await $.platform.mobile.openNotifications();To tap on the second notification:
await $.platform.mobile.tapOnNotificationByIndex(1);You can also tap on notification by its content:
await $.platform.mobile.tapOnNotificationBySelector(
Selector(textContains: 'Someone liked your recent post'),
);Permissions
To handle the native permission request dialog:
await $.platform.mobile.grantPermissionWhenInUse();
await $.platform.mobile.grantPermissionOnlyThisTime();
await $.platform.mobile.denyPermission();If the permission request dialog visible is the location dialog, you can also select the accuracy:
await $.platform.mobile.selectFineLocation();
await $.platform.mobile.selectCoarseLocation();The test will fail if the permission request dialog is not visible. You can check if it is with:
if (await $.platform.mobile.isPermissionDialogVisible()) {
await $.platform.mobile.grantPermissionWhenInUse();
}By default, isPermissionDialogVisible() waits for a short amount of time and
then returns false if the dialog is not visible. To increase the timeout:
if (await $.platform.mobile.isPermissionDialogVisible(timeout: Duration(seconds: 5))) {
await $.platform.mobile.grantPermissionWhenInUse();
}Patrol can handle permissions on iOS only if the device language is set to English (preferably US). That's because there's no way to refer to a specific view in a language-independent way (like resourceId on Android).
If you want to handle permissions on iOS device with non-English locale, do it manually:
await $.platform.ios.tap(
IOSSelector(text: 'Allow'),
appId: 'com.apple.springboard',
);Device information
Get information about the device running the tests:
// Check if running on an emulator/simulator
final isVirtual = await $.platform.mobile.isVirtualDevice();
// Get OS version (e.g., 30 for Android 11)
final osVersion = await $.platform.mobile.getOsVersion();
if (osVersion >= 30) {
// Android 11+ specific behavior
}More resources
To see more integration tests demonstrating Patrol's various features, check out our example app.