Mobile and Flutter testing
SHAFT uses the same SHAFT.GUI.WebDriver facade for browser and Appium
sessions. Configure the Appium endpoint, platform, automation name, and app,
then create the driver normally.
SHAFT.GUI.WebDriver driver = new SHAFT.GUI.WebDriver();
driver.touch()
.tap(AppiumBy.accessibilityId("Views"))
.and()
.assertThat(AppiumBy.accessibilityId("Expandable Lists"))
.exists();
driver.quit();
Flutter applications
SHAFT Engine now supports automated testing of Flutter applications using the Appium Flutter Driver. This integration allows you to seamlessly test Flutter apps on both Android and iOS platforms.
Prerequisites
1. Install Appium Server
First, install Appium with the Flutter driver plugin:
# Install Appium globally
npm install -g appium
# Install the Flutter driver plugin
appium driver install --source npm appium-flutter-driver
2. Verify Installation
Verify that the Flutter driver is installed:
appium driver list --installed
You should see flutter in the list of installed drivers.
3. Prepare Your Flutter App
Your Flutter app must be built in either debug or profile mode. The Appium Flutter Driver does not support release mode.
To enable Flutter driver integration in your app, add the following to your main.dart:
import 'package:flutter/material.dart';
import 'package:flutter_driver/driver_extension.dart';
void main() {
// Enable Flutter Driver extension before calling runApp
enableFlutterDriverExtension();
runApp(MyApp());
}
Then build your app:
# For Android
flutter build apk --debug
# For iOS
flutter build ios --debug
Usage in SHAFT Engine
Basic Setup
To test a Flutter app using SHAFT Engine, you need to:
- Set the automation name to
FlutterIntegration(this automatically enables Flutter driver support) - Specify the app path or URL
- Set up your Appium server connection
Example Test Class
Here's a complete example of a Flutter test using SHAFT Engine with TestNG and FlutterFinder:
package com.example.tests;
import com.shaft.driver.SHAFT;
import io.appium.java_client.remote.AutomationName;
import io.github.ashwith.flutter.FlutterFinder;
import org.openqa.selenium.Platform;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class FlutterAppTest {
private SHAFT.GUI.WebDriver driver;
private FlutterFinder finder;
@BeforeMethod
public void setup() {
// Set platform and automation name (Flutter driver is automatically enabled)
SHAFT.Properties.platform.set().targetPlatform(Platform.ANDROID.name());
SHAFT.Properties.mobile.set().automationName(AutomationName.FLUTTER_INTEGRATION);
// Configure Appium server
SHAFT.Properties.platform.set().executionAddress("localhost:4723");
// Set app path (local file)
SHAFT.Properties.mobile.set().app("path/to/your/app-debug.apk");
// Initialize driver
driver = new SHAFT.GUI.WebDriver();
// Initialize FlutterFinder (requires RemoteWebDriver)
finder = new FlutterFinder((RemoteWebDriver) driver.getDriver());
}
@Test
public void testFlutterApp() {
// Find element by ValueKey
WebElement loginButton = finder.byValueKey("loginButton");
loginButton.click();
// Find element by text
WebElement welcomeMessage = finder.byText("Welcome!");
Assert.assertNotNull(welcomeMessage, "Welcome message should be displayed");
// Find element by Type
WebElement textField = finder.byType("TextField");
Assert.assertNotNull(textField, "TextField should be found");
}
@AfterMethod
public void teardown() {
driver.quit();
}
}
Configuration Properties
You can configure Flutter testing using properties file or programmatically:
Properties File (custom.properties)
# Platform configuration
targetOperatingSystem=Android
# Automation name - setting this to FlutterIntegration automatically enables Flutter driver
mobile_automationName=FlutterIntegration
# Appium server
executionAddress=localhost:4723
# App configuration
mobile_app=src/test/resources/apps/my-flutter-app.apk
# Optional: Device configuration
mobile_deviceName=Android Emulator
mobile_platformVersion=13.0
Programmatic Configuration
// Platform and automation - setting automationName to FLUTTER_INTEGRATION enables Flutter driver
SHAFT.Properties.platform.set().targetPlatform(Platform.ANDROID.name());
SHAFT.Properties.mobile.set().automationName(AutomationName.FLUTTER_INTEGRATION);
// Appium server
SHAFT.Properties.platform.set().executionAddress("localhost:4723");
// App path
SHAFT.Properties.mobile.set().app("path/to/app.apk");
// Optional device settings
SHAFT.Properties.mobile.set().deviceName("Android Emulator");
SHAFT.Properties.mobile.set().platformVersion("13.0");
Locating Flutter Elements
When testing Flutter apps, you can use the FlutterFinder library to locate widgets. SHAFT Engine includes the appium_flutterfinder_java dependency (version 1.0.12) automatically.
Common Flutter Locator Strategies
import io.github.ashwith.flutter.FlutterFinder;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebDriver;
// Create a FlutterFinder instance (requires RemoteWebDriver)
FlutterFinder finder = new FlutterFinder((RemoteWebDriver) driver.getDriver());
// By value key (String)
WebElement element = finder.byValueKey("myButton");
// By value key (int)
WebElement element = finder.byValueKey(123);
// By text
WebElement element = finder.byText("Submit");
// By type (widget class name)
WebElement element = finder.byType("TextField");
// By tooltip
WebElement element = finder.byToolTip("Increment");
// By semantics label
WebElement element = finder.bySemanticsLabel("Login Button");
// Note: Refer to the FlutterFinder documentation for the complete list of available methods
// https://github.com/ashwithpoojary98/javaflutterfinder
Working with FlutterFinder Elements
Once you have located an element using FlutterFinder, you can interact with it directly:
import org.openqa.selenium.remote.RemoteWebDriver;
// Initialize FlutterFinder (requires RemoteWebDriver)
FlutterFinder finder = new FlutterFinder((RemoteWebDriver) driver.getDriver());
// Find and click a button
WebElement incrementButton = finder.byValueKey("increment");
incrementButton.click();
// Find and get text from an element
WebElement counterText = finder.byValueKey("counterDisplay");
String text = counterText.getText();
// Find by tooltip and interact
WebElement submitButton = finder.byToolTip("Submit");
submitButton.click();
Using with SHAFT's Fluent API
You can integrate Flutter finders with SHAFT's fluent API:
// Convert FlutterFinder to By locator
By loginButton = AppiumBy.accessibilityId("loginButton");
// Use with SHAFT's fluent element actions
driver.element()
.type(loginButton, "username")
.and().click(submitButton)
.and().assertThat(welcomeMessage).text().contains("Welcome");
Working with Flutter Widgets
Text Input
By usernameField = AppiumBy.accessibilityId("usernameField");
driver.element().type(usernameField, "testuser");
Button Clicks
By loginButton = AppiumBy.accessibilityId("loginButton");
driver.element().click(loginButton);
Scrolling
import com.shaft.gui.element.TouchActions;
driver.touch().swipeElementIntoView(
AppiumBy.accessibilityId("targetWidget"),
TouchActions.SwipeDirection.DOWN
);
Assertions
// Text assertion
driver.assertThat()
.element(AppiumBy.accessibilityId("statusMessage"))
.text()
.isEqualTo("Success");
// Visibility assertion
driver.assertThat()
.element(AppiumBy.accessibilityId("errorDialog"))
.exists();
Cloud Execution
SHAFT Engine's Flutter integration works seamlessly with cloud providers:
BrowserStack
This direct SHAFT Appium path requires only shaft-engine. Add
shaft-browserstack only when the BrowserStack Java SDK must consume
browserstack.yml for SDK interception or orchestration.
SHAFT.Properties.platform.set().executionAddress("browserstack");
SHAFT.Properties.browserStack.set().platformVersion("13.0");
SHAFT.Properties.browserStack.set().deviceName("Google Pixel 7");
SHAFT.Properties.browserStack.set().appRelativeFilePath("path/to/app.apk");
SHAFT.Properties.mobile.set().automationName(AutomationName.FLUTTER_INTEGRATION);
LambdaTest
SHAFT.Properties.platform.set().executionAddress("lambdatest");
SHAFT.Properties.lambdaTest.set().platformVersion("13.0");
SHAFT.Properties.lambdaTest.set().deviceName("Galaxy S21");
SHAFT.Properties.mobile.set().automationName(AutomationName.FLUTTER_INTEGRATION);
Troubleshooting
Common Issues
-
"Could not find Flutter driver"
- Ensure the Flutter driver is installed:
appium driver install --source npm appium-flutter-driver - Verify with:
appium driver list --installed
- Ensure the Flutter driver is installed:
-
"Flutter driver extension not found"
- Make sure your app includes
enableFlutterDriverExtension()inmain.dart - App must be built in debug or profile mode, not release mode
- Make sure your app includes
-
"Cannot find element"
- Ensure Flutter widgets have proper keys or accessibility labels
- Use Flutter's
Keywidget:Key('myButton') - Add semantics:
Semantics(label: 'Submit Button', child: MyWidget())
-
Session creation fails
- Check that Appium server is running:
appium - Verify the server address matches your configuration
- Ensure the app path is correct and accessible
- Check that Appium server is running:
Debug Mode
Enable debug logging to troubleshoot issues:
# In custom.properties
log4j_logLevel=DEBUG
Or programmatically:
SHAFT.Properties.log4j.set().logLevel("DEBUG");
Best Practices
-
Use Meaningful Keys: Always add keys to important Flutter widgets for easier element identification:
ElevatedButton(key: Key('submitButton'),onPressed: () {},child: Text('Submit'),) -
Add Semantics: Use semantics for better accessibility and test automation:
Semantics(label: 'User Login Form',child: Form(...)) -
Wait for Elements: SHAFT automatically handles waits, but you can configure timeout:
SHAFT.Properties.timeouts.set().elementIdentificationTimeout(30); -
Use Fluent API: Leverage SHAFT's fluent API for readable tests:
driver.element().type(usernameField, "user").and().type(passwordField, "pass").and().click(loginButton).and().assertThat(dashboard).exists(); -
Clean Up Resources: Always quit the driver in teardown:
@AfterMethod(alwaysRun = true)public void teardown() {driver.quit();}
Example Test Suite
Complete example with multiple tests using FlutterFinder:
package com.example.tests;
import com.shaft.driver.SHAFT;
import io.appium.java_client.remote.AutomationName;
import io.github.ashwith.flutter.FlutterFinder;
import org.openqa.selenium.Platform;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testng.Assert;
import org.testng.annotations.*;
public class FlutterAppTestSuite {
private static SHAFT.GUI.WebDriver driver;
private static FlutterFinder finder;
@BeforeClass
public void setupClass() {
// Configure Flutter testing (automationName automatically enables Flutter driver)
SHAFT.Properties.platform.set().targetPlatform(Platform.ANDROID.name());
SHAFT.Properties.mobile.set().automationName(AutomationName.FLUTTER_INTEGRATION);
SHAFT.Properties.platform.set().executionAddress("localhost:4723");
SHAFT.Properties.mobile.set().app("src/test/resources/apps/flutter-demo.apk");
}
@BeforeMethod
public void setup() {
driver = new SHAFT.GUI.WebDriver();
// Initialize FlutterFinder with RemoteWebDriver
finder = new FlutterFinder((RemoteWebDriver) driver.getDriver());
}
@Test(description = "Verify successful login with valid credentials")
public void testValidLogin() {
// Find and interact with Flutter widgets using FlutterFinder
WebElement usernameField = finder.byValueKey("usernameField");
WebElement passwordField = finder.byValueKey("passwordField");
WebElement loginButton = finder.byValueKey("loginButton");
usernameField.sendKeys("testuser");
passwordField.sendKeys("testpass");
loginButton.click();
// Verify navigation to dashboard
WebElement dashboardTitle = finder.byText("Dashboard");
Assert.assertNotNull(dashboardTitle, "Dashboard should be displayed");
}
@Test(description = "Verify error message with invalid credentials")
public void testInvalidLogin() {
WebElement usernameField = finder.byValueKey("usernameField");
WebElement passwordField = finder.byValueKey("passwordField");
WebElement loginButton = finder.byValueKey("loginButton");
usernameField.sendKeys("wronguser");
passwordField.sendKeys("wrongpass");
loginButton.click();
// Verify error message is displayed
WebElement errorMessage = finder.byText("Invalid credentials");
Assert.assertNotNull(errorMessage, "Error message should be displayed");
}
@Test(description = "Verify counter increment functionality")
public void testCounterIncrement() {
// Find the increment button by tooltip or value key
WebElement incrementButton = finder.byToolTip("Increment");
// Click the button
incrementButton.click();
// Verify button was clicked (counter should increment)
// Note: Actual verification would check the counter text value
Assert.assertNotNull(incrementButton, "Increment button should be functional");
}
@AfterMethod(alwaysRun = true)
public void teardown() {
if (driver != null) {
driver.quit();
}
}
}
Additional Resources
- Appium Flutter Driver Documentation
- Flutter Testing Guide
- SHAFT Engine Documentation
- Flutter Finder Java Library - Package:
io.github.ashwithpoojary98
Support
For issues or questions:
- Open an issue on GitHub
- Join our Slack community
- Check our documentation