Polygon Technology

appium testing guide flutter

Unlocking the Potential of Appium: A Flutter Testing Odyssey

Unlocking the Potential of Appium: A Flutter Testing Odyssey

Automate Flutter app UI testing! This guide covers setting up Appium & writing Java tests to interact with your app's elements.
Technology
March 28, 2024
Md. Rabiul Alam Sarker

Rabiul Alam brings a blend of manual and automation expertise to the world of software testing. With a deep understanding of the entire testing life-cycle for dynamic web and mobile applications. His experience spans popular testing frameworks like Selenium, TestNG, and Appium. Additionally, he possesses hands-on knowledge of DevOps tools like Docker, ELK stack, and CI/CD pipelines.

Md. Asad Chowdhury Dipu
appium testing guide flutter

Utilizing Appium for testing Flutter applications is a common practice in the industry. While there may be limited beginner tutorials for Appium, but the official documentation on the Appium website provides valuable guidance. Before delving into Appium, it is essential to have a grasp of how Flutter applications function.

Appium installation

Prior to configuring Appium, it is necessary to have Node.js installed on your system. Ensure that you install the latest version of Node.js; for instance, I am currently using Node.js version 18.16.1 and npm version 9.5.1. After installing Node.js, proceed to install Appium by executing the following command.

npm install -g [email protected]

Consider installing the most recent version available. Subsequently, it is necessary to install Appium drivers such as UIAutomator2, Flutter, XCUItest, etc. Execute the provided commands to install these drivers.

appium driver install --source=npm appium-uiautomator2-driver
appium driver install
--source=npm
appium-uiautomator2-driver
appium driver install --source=npm appium-flutter-driver
appium driver install
--source=npm
appium-flutter-driver

Feel free to customize the versions according to your preference, although it is advisable to transition to the latest releases. Once Node, Appium, and the relevant Appium drivers are installed, ensure that you can view the versions of each component. To display the list of Appium drivers, use the following command.

appium driver list --installed
appium driver list
--installed

Assuming you already have a Flutter project, within this project, add the following driver configurations to your pubspec.yaml file.

flutter_driver:
 sdk: flutter

Following the addition of the drivers in the pubspec.yaml file, execute the command “flutter pub get” to obtain the added driver packages. In your main.dart file, initialize the Flutter driver by incorporating the following method.

“enableFlutterDriverExtension();”

Within your project, include the specified finder inside the respective widgets as required. It’s important to note that not all widgets support every type of finder. Prior to implementing a finder into a widget, ensure that the widget supports the specific finder you are using.

List of finders:

  • byValueKey
  • byToolTip
  • byType
  • byText
  • byAncestor
  • byDescendant
  • bySemanticsLabel

Flutter Widget Implementation with Finders

Some common methods for identifying and interacting with Flutter widgets using Finders in Appium tests. Remember to refer to the official documentation for Flutter and appium-flutter-finder for the latest information and comprehensive usage details.

Finding Widgets by ValueKey:

Flutter: Dart

TextField(key: const ValueKey("emailField"), decoration: InputDecoration(labelText:"Email",),)
TextField(key:
const ValueKey("emailField"),
decoration:
InputDecoration(labelText:"Email",)
,)

Explanation:

  • Assign a unique ValueKey to the widget during its creation.
  • This key acts as a reliable identifier for the widget throughout the widget tree.

Appium: Java

WebElement emailField = find.byValueKey("emailField");
WebElement emailField
= find.byValueKey("emailField");

Explanation:

  • Use find.byValueKey() with the corresponding key value to locate the desired widget.
  • This approach offers reliable identification, especially for frequently accessed or reusable widgets.

Finding Widgets by Tooltip:

Flutter: Dart

BottomNavigationBarItem(
  icon: Icon(Icons.shop),
  label: 'shop',
  tooltip: 'shop',
),

Explanation:

  • Set the tooltip property on the widget to provide additional context or information.

Appium: Java

WebElement barItem = find.byToolTip("shop");
WebElement barItem
= find.byToolTip("shop");

Explanation:

  • Use find.byToolTip() with the tooltip text to locate the element.
  • This approach is helpful for elements with descriptive tooltips, but might not be suitable for all cases.

Finding Widgets by Type:

Flutter: Dart

class UniqueButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {},
      child: Text('Unique Button'),
    );
  }
}
class UniqueButton extends 
StatelessWidget {
  @override
  Widget build
  (BuildContext context) {
    return ElevatedButton(
      onPressed: () {},
      child: Text('Unique Button'),
    );
  }
}

Appium: Java

WebElement button = find.byType('ElevatedButton').withText('Unique Button');
WebElement button 
= find.byType('ElevatedButton')
.withText('Unique Button');
Explanation:
  • Flutter itself doesn’t directly support finding by type alone.
  • Combine find.byType with other strategies like withText for better identification. This ensures a combination of type and other attributes (e.g., text content) for a more precise match.

Finding Widgets by Text:

Flutter: Dart

Text('Login', key: Key('loginText'))

Appium: Java

WebElement login = find.byText('Login');
WebElement login 
= find.byText('Login');

Explanation:

  • Flutter: No change needed in Flutter code.
  • Appium: Use appium-flutter-finder library to locate elements based on their text content.

Finding Widgets by SemanticsLabel:

Flutter: Dart

Icon(Icons.info, semanticsLabel: 'Info',)
Icon(Icons.info, semanticsLabel:
'Info'
,)

Explanation:

  • Some widgets support setting a semanticsLabel to describe their purpose or content. This aids users with accessibility tools.

Appium: Java

WebElement info = find.bySemanticsLabel("Info");
WebElement info 
= find.bySemanticsLabel("Info");

Explanation:

  • Appium can locate elements using their accessibilityIds, which often correspond to the semanticsLabel in Flutter.
  • This approach improves accessibility testing and aligns with user experience.

Finding Widgets by Ancestor and Descendant:

Both Flutter and Appium: Not supported.

Explanation:

  • Both byAncestor and byDescendant finders have limited support in current appium-flutter-finder versions (as of March, 2024). They are considered experimental.

Alternatives:

  • Restructure the widget tree: Refactor your widget hierarchy to simplify the element structure and reduce the need for deep nesting. This can improve overall app performance and potentially eliminate the need for these finders.
  • Use keys on closer ancestors or alternative finders: If restructuring is not feasible, consider using ValueKey or other finders on closer ancestors within the widget tree to achieve the desired identification.
  • Stay updated: Keep an eye on the latest appium-flutter-finder documentation for potential updates and advancements in support for these finders.

Additional Notes:

  • Prioritize well-supported finders like byValueKey, byText, and bySemanticsLabel for reliable and consistent element identification in your Appium tests.
  • Refer to the official documentation for Flutter, Appium, and appium-flutter-finder for comprehensive details and the latest information on available finders and their usage.
  • Explore other advanced finders and strategies available in appium-flutter-finder to fine-tune your element selection based on your specific testing needs.

As an example, I’ve included the following code that I utilized in my Flutter project. This code facilitates Flutter finder in identifying two input fields – one for the email and another for the password, as well as the button. Ensure that the widgets support these finders for accurate identification.

class Login extends StatefulWidget {
  const Login({Key? key}) : super(key: key);
  
    @override
    State<Login> createState() => _LoginState();
  }
class _LoginState extends State<Login> {
  // Controllers for text fields
  final TextEditingController emailController = TextEditingController();
  final TextEditingController passwordController = TextEditingController();
  // Flags for form validation and button state
  bool isButtonEnabled = false;

  @override
  Widget build(BuildContext context) {
    // Using SingleChildScrollView to avoid overflow when keyboard is visible
    return Scaffold(
      body: SingleChildScrollView(
        child: Container(
          height: MediaQuery.of(context).size.height,
          padding: const EdgeInsets.all(20),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              _buildEmailField(),
              const SizedBox(height: 20),
              _buildPasswordField(),
              const SizedBox(height: 20),
              _buildLoginButton(),
            ],
          ),
        ),
      ),
    );
  }

  // Email field with validation
  Widget _buildEmailField() {
    return TextField(
      key: const ValueKey("emailField"), // Finder: byValueKey('emailField')
      controller: emailController,
      onChanged: (value) => _validateForm(),
      decoration: InputDecoration(
        labelText: "Email",
        hintText: "Enter your email",
        border: const OutlineInputBorder(),
        focusedBorder: OutlineInputBorder(
          borderSide: BorderSide(
            width: 1,
            color: _isValidEmail(emailController.text) ? Colors.green : Colors.red,
          ),
        ),
      ),
    );
  }

  // Password field with validation
  Widget _buildPasswordField() {
    return TextField(
      key: const ValueKey(
        "passwordField"), // Finder: byValueKey('passwordField')
      controller: passwordController,
      onChanged: (value) => _validateForm(),
      obscureText: true,
      decoration: InputDecoration(
        labelText: "Password",
        hintText: "Enter your password",
        border: const OutlineInputBorder(),
        focusedBorder: OutlineInputBorder(
          borderSide: BorderSide(
            width: 1,
            color: _isValidPassword(passwordController.text) ? Colors.green : Colors.red,
          ),
        ),
      ),
    );
  }

  // Login button that is enabled/disabled based on form validation
  Widget _buildLoginButton() {
    return SizedBox(
      width: double.infinity,
      child: ElevatedButton(
        key: const ValueKey("loginButton"), // Finder: byValueKey('loginButton')
        onPressed: isButtonEnabled ? _login : null,
        style: ElevatedButton.styleFrom(
          padding: const EdgeInsets.all(10),
          backgroundColor: isButtonEnabled ? Colors.blue : Colors.grey,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10),
          ),
        ),
        child: const Text(
          "Login",
          style: TextStyle(fontSize: 20, color: Colors.white),
        ),
      ),
    );
  }

  // Form validation logic
  void _validateForm() {
    final isEmailValid = _isValidEmail(emailController.text);
    final isPasswordValid = _isValidPassword(passwordController.text);
    setState(() {
      isButtonEnabled = isEmailValid && isPasswordValid;
    });
  }

  // Email validation using regular expression
  bool _isValidEmail(String email) {
    if (email.isEmpty) return false;
    final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
    return emailRegex.hasMatch(email);
  }

  // Simple password validation
  bool _isValidPassword(String password) {
    return password.isNotEmpty && password.length >= 8;
  }

  // Mock login function for demonstration
  void _login() {
    // Check if email and password match the expected credentials
    if (emailController.text == "[email protected]" &&
      passwordController.text == "12345678") {
        // Navigate to the LandingPage if the credentials match
        Navigator.pushReplacement(
          context,
          MaterialPageRoute(builder: (context) => LandingPage()),
        );
    }
    else {
      // Show a SnackBar if the email and password do not match
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('Email or Password is incorrect'),
          duration: Duration(seconds: 1),
          action: SnackBarAction(
            label: 'Dismiss',
            onPressed: () {
              // This will dismiss the SnackBar before its duration expires
              ScaffoldMessenger.of(context).hideCurrentSnackBar();
            },
          ),
        ),
      );
    }
  }
}

After completing the setup and implementation of Appium with your Flutter project, you can generate a debug build APK (Android Package) by using the following command:

flutter build apk --debug

Ensure that you use either the debug or profile build for testing with Appium and avoid using the release build. This is important for compatibility and debugging purposes.

With these configurations and considerations, your Appium setup for testing your Flutter application is complete. If you encounter any issues or need further assistance, refer to the relevant documentation or seek support from the Flutter and Appium communities.

Maven project setup

Certainly, you can use various programming languages like Python, Java, JavaScript, etc. In my case, I’ve choosed Java. Here are the general steps to set up a Maven project for Appium testing.

Firstly, ensure you have Java installed (preferably version 11 or later) and the JAVA_HOME and ANDROID_HOME environment variables are set. Then, you can create a Maven project using an IDE like VSCode or Eclipse. Once the project is created, add the necessary dependencies in the pom.xml file. Here is a dependency list for configuring  your pom.xml file:

Dependency list:

  • java-client
  • testng
  • selenium-api
  • selenium-remote-driver
  • appium_flutterfinder_java

Exactly, once you’ve added the dependencies in the pom.xml file, you should run the Maven command mvn install or mvn package install to download and install all the specified dependencies automatically. Make sure, maven is installed on your system.

Afterwards, you can check if Appium is working by running the command appium in the terminal. This starts the Appium server, including the installed drivers. By default, the Appium server listens at “http://127.0.0.1:4723/“.

Ensure that your emulator or real device is powered on to run the Flutter app. Through the Appium client, the communication with the Appium server is established, enabling the execution of operations on the connected device. This connection is crucial for the testing process.

DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("deviceName", "Your_Device_Name"); // Replace with your device name
capabilities.setCapability("platformName", "Android");
capabilities.setCapability("noReset", true);
capabilities.setCapability("app", "/path/to/your/app-debug.apk"); // Replace with your APK path
capabilities.setCapability("automationName", "Flutter");
driver = new AppiumDriver(new URL("http://0.0.0.0:4723"), capabilities);
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
DesiredCapabilities capabilities
= new DesiredCapabilities();
capabilities
.setCapability("deviceName",
"Your_Device_Name"); 
// Replace with your device name
capabilities
.setCapability("platformName",
"Android");
capabilities
.setCapability("noReset",
true);
capabilities
.setCapability("app",
"/path/to/your/app-debug.apk"); 
// Replace with your APK path
capabilities
.setCapability("automationName",
"Flutter");
driver 
= new AppiumDriver
(new URL("http://0.0.0.0:4723"),
capabilities);
driver.manage().timeouts()
.implicitlyWait
(Duration.ofSeconds(5));

Here,

1. Device Name:

  • Option 1: Go to your device Settings > About Phone (or About Tablet). Look for the Device Name or Model Number. You can use either of these in the deviceName capability.
  • Option 2: Connect your device to your computer and run the following command in your terminal:

Bash

adb devices

This will list connected devices and their names. Use the displayed device name in the deviceName capability.

2. App Path:

This refers to the path of the Android application (.apk) you want to test. In the provided code, it’s set to /home/user/ StudioProjects/ demo_flutter/build/ app/outputs/flutter-apk/ app-debug.apk. Replace this path with the actual location of your app’s .apk file on your computer.

3. Other Capabilities:

  • platformName: This should always be “Android” for Android devices.
  • noReset: This is set to true in the code, which means the app won’t be uninstalled and reinstalled between tests. You can change this to false if you want a clean slate for each test run.

automationName: This is set to “Flutter” as the test framework is Flutter. You wouldn’t need to modify this unless you’re using a different framework.

You should substitute placeholders such as the device name and APK path with your actual device ID and the specific location of the APK file. It’s advisable to set up your project directory build path to dynamically incorporate the latest APK without the need for manual copying. After executing the code, the app should run successfully on your device. You can find an example Maven project for reference here.

When automating interactions with your Flutter app, it’s crucial to use identifiers. The identifier you use should correspond to the Flutter finder locator utilized in your app. Consider the following code for reference:

String safeEmail = "[email protected]";
String password = "12345678";
WebElement safeEmail = find.byValueKey("emailField");
phoneNumberField.sendKeys(safeEmail);
WebElement passwordField = find.byValueKey("password");
passwordField.sendKeys(password);
WebElement loginButton = find.byValueKey("loginButton");
loginButton.click();
String safeEmail 
="[email protected]";
String password 
="12345678";
WebElement safeEmail= 
find.byValueKey("emailField");
phoneNumberField
.sendKeys(safeEmail);
WebElement passwordField=
find.byValueKey("password");
passwordField
.sendKeys(password);
WebElement loginButton=
find.byValueKey("loginButton");
loginButton.click();

In this example, identifiers are used to locate and interact with Flutter app elements such as the phone number field, password field, and login button. Adjust the identifiers based on the locator strategy used in your Flutter app.

when using the ValueKey identifier inside your Flutter app, you should use the byValueKey a strategy for Appium. This allows you to locate the desired field and perform actions such as tapping on it if it’s tappable or sending a specific value if it’s an input field. This code demonstrates using byValueKey to locate and interact with Flutter app elements based on the ValueKey identifiers declared in your Flutter app. Adjust the values as per your Flutter app’s implementation.

Setting Up an Android Device for Appium Testing

1. Enable Developer Options:

  • Navigate to your device’s Settings app.
  • Locate the “About Phone” or “About Tablet” section.
  • Find “Build Number” and tap it rapidly 7 times (or as many times as required on your device) to enable Developer options.
  • You’ll see a message indicating you are now a developer.

2. Enable USB Debugging:

  • Go back to the main Settings menu.
  • Find “Developer options” (might be under “System”).
  • Locate “USB Debugging” and toggle it on.
  • A confirmation dialog might appear; accept it to grant debugging permission to your computer.

3. Disable Permission Monitoring (Optional):

  • On some devices, you might need to disable permission monitoring to avoid Appium encountering issues. The specific steps may vary, but it’s typically found under Developer options. Consult your device’s documentation for detailed instructions.

Verification

  • Connect your Android device to your computer using the USB cable.
  • Open a terminal/command prompt.
  • Run adb devices. This should list your connected device.
  • If using an emulator, ensure it’s launched and accessible.

Appium offers a powerful solution for automating UI tests in your Flutter applications. By leveraging Appium and the appium-flutter-finder library, you can establish a robust testing framework to ensure the quality and functionality of your Flutter app. This guide provided a comprehensive overview of the setup process, including Appium installation, Flutter project configuration, Appium driver installation, Maven project setup for Java testing, and practical examples for interacting with Flutter widgets using Appium. Remember to refer to the official documentation for the latest information and explore advanced features as you delve deeper into Appium testing for your Flutter projects. By following these steps and best practices, you can unlock the potential of Appium and streamline your Flutter app testing process.

(Flutter App Link) (Automation Code Link) (Video Link)

Tags :