LogoLogo
🚀 Run Maestro tests in the cloud →
  • Home
  • Star us on GitHub
  • Getting Started
    • What is Maestro?
    • Installing Maestro
      • macOS
      • Windows
      • Linux
    • Build and Install your App
      • Android
      • iOS
    • Run a Sample Flow
    • Writing Your First Flow
    • Maestro Studio
    • Running Flows on CI
  • Cloud
    • Run Maestro tests in the cloud
    • Cloud Quickstart
    • CI Integration
      • GitHub Actions
        • Maestro GitHub Action for Android
        • Maestro GitHub Action for iOS
        • Maestro GitHub Action for Flutter
      • Bitrise
      • Bitbucket Pipelines
      • CircleCI
      • Integration with any CI platform
    • Pull Request Integration
    • Cloud Reference
      • Build your app for the cloud
      • Configuring OS Version
      • Configuring device locale
      • Device timezones
      • Email Notifications
      • Slack Notifications
      • Webhook Integration
      • Managing Secrets
      • Limits
      • Reusing App Binary
      • IP Allowlist
      • System Status
  • Platform Support
    • Supported Platforms
    • Android - Views
    • Android - Jetpack Compose
    • iOS - UIKit
    • iOS - SwiftUI
    • React Native
    • Flutter
    • Web Views
    • Web (Desktop Browser)
  • Examples
    • Android contacts flow automation
    • Facebook signup flow automation
    • Advanced: Wikipedia Android
    • Page Object Model
  • CLI
    • Cloud
    • Test Suites & Reports
    • Tags
    • Record Your Flow
    • Continuous Mode
    • View Hierarchy
    • Start Device
  • API Reference
    • Commands
      • addMedia
      • assertVisible
      • assertNotVisible
      • assertTrue
      • assertWithAI
      • assertNoDefectsWithAi
      • back
      • clearKeychain
      • clearState
      • copyTextFrom
      • evalScript
      • eraseText
      • extendedWaitUntil
      • extractTextWithAI
      • hideKeyboard
      • inputText
      • killApp
      • launchApp
      • openLink
      • pressKey
      • pasteText
      • repeat
      • retry
      • runFlow
      • runScript
      • scroll
      • scrollUntilVisible
      • setAirplaneMode
      • setLocation
      • startRecording
      • stopApp
      • stopRecording
      • swipe
      • takeScreenshot
      • toggleAirplaneMode
      • tapOn
      • doubleTapOn
      • longPressOn
      • travel
      • waitForAnimationToEnd
    • Common command arguments
    • Selectors
    • Configuration
      • Workspace configuration
      • Flow configuration
      • AI configuration
  • Advanced
    • Nested Flows
    • Wait
    • Loops
    • Conditions
    • Parameters & Constants
    • JavaScript
      • Run JavaScript
      • Outputs
      • Logging
      • Access element text
      • Make HTTP requests
      • GraalJS support
      • JavaScript announcement blog
    • Specify a Device
    • Configure Permissions
    • Detect Maestro in your app
    • Configure Maestro driver timeout
    • onFlowStart / onFlowComplete hooks
    • Test in different locales
    • Structuring your test suite
  • Troubleshooting
    • Known Issues
    • Frequently Asked Questions
    • Bug Report
    • Rollback Maestro
    • Debug Output
    • HOWTOs
      • Arrange your repository for Maestro tests
      • scrollUntilVisible for fragments
  • Community
    • Contributions
    • Articles & Tutorials
    • Case Studies
    • Resources
Powered by GitBook

Read to wire into CI or scale up your testing?

  • Run Maestro tests in the cloud →
On this page
  • Interacting with widgets by semantics label
  • Example: Tap on a widget
  • Example: Enter text in a widget
  • Example: Assert a widget is visible
  • Interacting with widgets by semantic identifier
  • Example: Tap on a widget by semantics identifier
  • Example: Enter text in a widget by semantics identifier
  • Good practices
  • Why not Flutter keys?
  • Known Limitations

Was this helpful?

Edit on GitHub
  1. Platform Support

Flutter

PreviousReact NativeNextWeb Views

Last updated 5 months ago

Was this helpful?

Flutter is a first class citizen for Maestro. It can test both pure and hybrid (i.e ) Flutter mobile apps.

Interacting with widgets by semantics label

Example: Tap on a widget

Given an InkWell widget with a Text widget child:

InkWell(
  child: Text('Open Browser'),
  onTap: () => launch('https://mobile.dev'),
)

The following command will tap on it:

- tapOn: Open Browser

Some widget, such as Icon, don't have implicit semantics. In such cases you can often pass a semanticLabel:

FloatingActionButton(
  onPressed: _incrementCounter,
  child: Icon(Icons.add, semanticLabel: 'fabAddIcon'),
)

Then the FloatingActionButton can be interacted with using the following command:

- tapOn: fabAddIcon

Semantics(
  label: 'funky yellow box',
  child: Container(
    color: Colors.yellow,
    width: 100,
    height: 100,
  ),
)
- tapOn: funky yellow box

Example: Enter text in a widget

To enter text in the following text field widget:

TextField(
  decoration: InputDecoration(
    border: UnderlineInputBorder(), 
    labelText: 'Enter your username',
  ),
)

Use this command:

- tapOn: Enter your username
- inputText: charlie_root

Example: Assert a widget is visible

Text(
  'Welcome back, dear $fullName! 👋🎉',
  semanticsLabel: 'Welcome back, dear $fullName!',
),
- assertVisible: Welcome back, dear Test User!

In cases where both semanticLabel and text label are provided (like above), the semanticLabel takes precedence. It's recommended to use maestro studio in such cases to easily identify what label to use.

Interacting with widgets by semantic identifier

When your app grows, testing often becomes harder.

Maybe the app gets multi-language support, and now you have to decide on the language in which you test it. Maybe some of the strings displayed are non-static (e.g. becase of A/B tests). And the sheer number of screens makes tests harder to maintain.

When you start facing these problems, you should consider using the accessibility identifier instead of semantics labels.

flutter channel stable
flutter upgrade

Example: Tap on a widget by semantics identifier

Semantics(
  identifier: 'signin_button',
  child: ElevatedButton(
    onPressed: _signIn,
    child: Text('Sign in'),
  ),
)
- tapOn:
    id: signin_button

Example: Enter text in a widget by semantics identifier

Semantics(
  identifier: 'username_textfield',
  child: TextField(
    decoration: InputDecoration(
      border: UnderlineInputBorder(), 
      labelText: 'Enter your username',
    ),
  ),
)
- tapOn:
    id: username_textfield
- inputText: charlie_root

Good practices

Let's say you have a FancyButton widget in your app. These buttons are important for you, and you want to ensure they always have an accessibility identifier assigned so they can be reliably interacted with using Maestro. The code sample below requires all callers of FancyButton to pass an accessibility identifier:

class FancyButton extends StatelessWidget {
 FancyButton({
    super.key,
    required this.identifier,
    required this.onPressed,
  });

  final String identifier;
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return Semantics(
      identifier: identifier,
      child: RawMaterialButton(
        onPressed: onPressed,
        // ...
      ),
    );
  }
}

This also has the benefit of reducing widget nesting at the call site:

FancyButton(
  identifier: 'buy_premium',
  onPressed: _buyPremium,  
)

// instead of:

Semantics(
  identifier: 'buy_premium',
  FancyButton(
    onPressed: _buyPremium,
  ),
)
include: package:leancode_lint/analysis_options.yaml

custom_lint:
  rules:
    - use_design_system_item:
      FancyButton:
        - instead_of: ElevatedButton
          from_package: flutter
          
analyzer:
  plugins:
    - custom_lint

Why not Flutter keys?

Flutter widget keys cannot be used in Maestro because there's no linkage between widget keys and Flutter's accessibility bridge system. This makes using Keys impossible since Maestro is accessibility-tree based.

We strongly recommend making your app accessible (not just for UI tests, but for all of your users with different needs). When testing at scale, you should also consider using an accessibility identifier.

Here's also a little trick that you may find useful if you really want to use keys in Maestro (using the FancyButton example from above):

class FancyButton extends StatelessWidget {
  FancyButton({
    required String key,
    required this.onPressed,
  })  : _key = key,
        super(key: ValueKey(key));

  final String _key;
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return Semantics(
      identifier: _key,
      child: RawMaterialButton(
        onPressed: onPressed,
        // ...
      ),
    );
  }
}

Callers are required to pass a string key:

FancyButton(
  key: 'unlock_reward',
  onPressed: _unlockReward,
)

And you can easily interact with the widget using Maestro:

- tapOn:
    id: unlock_reward

Known Limitations

Maestro cannot be used to test Flutter Desktop or Flutter Web apps (yet).

Maestro can interact with widgets that have semantics information attached. By default, this includes all widgets that display text (data in the Text widget, hintText in the TextField, and so on). You can also attach semantics information to any widget using Flutter's .

The Icon widget simply creates a Semantics widget itself – .

Finally, you can wrap any widget with :

This is a new feature that to make it easier to test apps made with it. It's available on the stable channel since Flutter 3.19 (released on February 15th, 2024). To use it, upgrade to the latest stable Flutter release:

Of course, there's always danger of a developer accidentally not using the FancyButton widget and defering to the built-in ElevatedButton. To combat that, we recommend setting up lint rules that forbid using ElevatedButton and enforce replacing it with a FancyButton instead. For example you can use with the following configuration in analysis_options.yaml:

Also, Flutter API docs for the and say it's for "controlling how one widget replaces another (e.g. in a list)". Keys are just not a mechanism for assigning unique IDs to widgets for testing purposes.

Semantics widget
see the source
Semantics
we contributed to Flutter
the leancode_lint package
Key class
Widget.key field
add-to-app