Building a Dynamic Mobile Automation Framework with Appium
Mobile testing has become a cornerstone of delivering high-quality applications in today’s fast-paced software development world. With platforms like Android and iOS, testers often face the challenge of creating a unified, scalable, and efficient automation framework that can handle both ecosystems seamlessly.
In this blog, I’ll walk you through the step-by-step creation of a dynamic mobile automation framework using Appium. This blog focuses on generating dynamic capabilities, programmatically starting the Appium server, and managing test execution for Android and iOS devices.
This is Part 1 of a multi-part series that will eventually cover topics like parallel execution and CI/CD pipeline integration.

Step 1: Dynamic Capability Generation
Capabilities are key-value pairs that define the configuration for Appium to connect to and automate a mobile device. Instead of hardcoding these capabilities, we dynamically generate them at runtime based on connected devices. This approach ensures that our framework is adaptable and ready for diverse testing environments.
Code: Dynamic Capability File Generation
public static void createCapabilityFile() throws IOException, JadbException {
FileSystemHandler.deleteDir(GlobalVars.getCapabilityDir()); // Clean old capabilities.
FileSystemHandler.deleteDir(GlobalVars.getLogsDir()); // Clean old logs.
JadbConnection jadb = new JadbConnection();
List<JadbDevice> devices = jadb.getDevices(); // Get connected Android devices.
List<IOSDevice> iosDevices = IOSUtils.getConnectedDevices(); // Custom utility to get iOS devices.
if (devices.isEmpty() && iosDevices.isEmpty()) {
log.severe("No devices connected. Check device connections and debugging options.");
System.exit(1);
}
// Handle Android devices
for (JadbDevice device : devices) {
String serial = device.getSerial().trim(); // Android device UDID.
String platformVersion = Stream.readAll(device.executeShell("getprop ro.build.version.release"), StandardCharsets.UTF_8).trim();
String appPackage = PropertyUtils.get(ConfigMap.APPPACKAGE); // Android app package.
String appActivity = PropertyUtils.get(ConfigMap.APPACTIVITY); // Android app activity.
String content = FileUtils.readFileToString(new File(GlobalVars.getCapabilityTemplate()));
content = content.replace("\"name\": \"\",", "\"name\": \"" + serial + "\",")
.replace("\"udid\": \"\",", "\"udid\": \"" + serial + "\",")
.replace("\"platformVersion\": \"\",", "\"platformVersion\": \"" + platformVersion + "\",")
.replace("\"appPackage\": \"\",", "\"appPackage\": \"" + appPackage + "\",")
.replace("\"appActivity\": \"\"", "\"appActivity\": \"" + appActivity + "\"");
FileUtils.writeStringToFile(new File(GlobalVars.getCapabilityDir() + serial + ".json"), content, StandardCharsets.UTF_8);
}
// Handle iOS devices
for (IOSDevice device : iosDevices) {
String udid = device.getUDID().trim(); // iOS device UDID.
String platformVersion = device.getOSVersion(); // iOS version.
String bundleId = PropertyUtils.get(ConfigMap.IOS_BUNDLE_ID); // iOS app bundle ID.
String content = FileUtils.readFileToString(new File(GlobalVars.getCapabilityTemplate()));
content = content.replace("\"name\": \"\",", "\"name\": \"" + udid + "\",")
.replace("\"udid\": \"\",", "\"udid\": \"" + udid + "\",")
.replace("\"platformVersion\": \"\",", "\"platformVersion\": \"" + platformVersion + "\",")
.replace("\"appPackage\": \"\",", "\"bundleId\": \"" + bundleId + "\"");
FileUtils.writeStringToFile(new File(GlobalVars.getCapabilityDir() + udid + ".json"), content, StandardCharsets.UTF_8);
}
}Explanation
- Device Detection: Identifies connected Android and iOS devices dynamically.
- Dynamic File Generation: Populates placeholders in a predefined JSON template with real device data (e.g.,
UDID,platformVersion,bundleId). - Cross-Platform Support: Handles Android and iOS configurations seamlessly.
Step 2: Starting the Appium Server Programmatically
Manually starting the Appium server for each test run is inefficient and error-prone. By programmatically initializing the server, we can ensure that it is configured correctly and dynamically allocated for different environments.
Code: Starting the Appium Server
public static HashMap<String, Object> startAppiumServer(String platform) {
AppiumServiceBuilder builder = new AppiumServiceBuilder();
ObjectMapper mapper = new ObjectMapper();
HashMap<String, Object> capabilities = null;
try {
FileUtils.forceMkdir(new File(GlobalVars.getLogsDir()));
// Determine capabilities file based on platform
String udid = CapabilityManager.getDeviceUDID();
if (platform.equalsIgnoreCase("Android")) {
capabilities = mapper.readValue(new FileReader(GlobalVars.getCapabilityDir() + udid + ".json"), HashMap.class);
} else if (platform.equalsIgnoreCase("iOS")) {
capabilities = mapper.readValue(new FileReader(GlobalVars.getCapabilityDir() + "iOS_" + udid + ".json"), HashMap.class);
}
// Configure Appium server
builder.usingAnyFreePort();
builder.withIPAddress(PropertyUtils.get(ConfigMap.IP));
builder.withAppiumJS(new File(PropertyUtils.get(ConfigMap.APPIUMJS)));
builder.usingDriverExecutable(new File(PropertyUtils.get(ConfigMap.NODEJS)));
builder.withLogFile(new File(GlobalVars.getLogsDir() + platform + "_appium_log.txt"));
builder.withArgument(GeneralServerFlag.SESSION_OVERRIDE);
builder.withArgument(GeneralServerFlag.LOG_LEVEL, "error");
AppiumDriverLocalService service = AppiumDriverLocalService.buildService(builder);
service.start();
ServiceManager.setService(service);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
return capabilities;
}Explanation
- Dynamic Port Allocation: Automatically finds an available port to start the server.
- Logging: Configures server logs for debugging and tracking.
- Platform-Specific Configuration: Uses capabilities for either Android or iOS.
Step 3: Initializing the Driver for Android and iOS
The final step is to initialize the appropriate driver — AndroidDriver or IOSDriver—to execute tests.
Code: Driver Initialization
public static AppiumDriver<?> initializeDriver(String platform, HashMap<String, Object> capabilities) {
AppiumDriver<?> driver = null;
try {
if (platform.equalsIgnoreCase("Android")) {
driver = new AndroidDriver<>(new URL("http://127.0.0.1:4723/wd/hub"), new DesiredCapabilities(capabilities));
} else if (platform.equalsIgnoreCase("iOS")) {
driver = new IOSDriver<>(new URL("http://127.0.0.1:4723/wd/hub"), new DesiredCapabilities(capabilities));
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
return driver;
}Explanation
- Reusable Method: Encapsulates driver initialization for both Android and iOS.
- Platform Independence: Ensures that the framework works seamlessly across both ecosystems.
Step 4: Running a Sample Test on Android and iOS
Here’s how you can use the above methods to execute tests:
Code: Test Execution
public static void main(String[] args) throws Exception {
// 1. Create Capability Files for Android and iOS
createCapabilityFile();
// 2. Start Appium Server for Android
HashMap<String, Object> androidCapabilities = startAppiumServer("Android");
AppiumDriver<?> androidDriver = initializeDriver("Android", androidCapabilities);
// 3. Start Appium Server for iOS
HashMap<String, Object> iosCapabilities = startAppiumServer("iOS");
AppiumDriver<?> iosDriver = initializeDriver("iOS", iosCapabilities);
// 4. Run Test on Android
androidDriver.findElement(By.id("com.example.android:id/button")).click();
// 5. Run Test on iOS
iosDriver.findElement(By.id("com.example.ios:id/button")).click();
// Close Drivers
androidDriver.quit();
iosDriver.quit();
}What’s Next?
This blog is just Part 1 of our journey into building a unified mobile automation framework. In upcoming parts, we’ll explore:
- Parallel Test Execution: Running tests concurrently on multiple devices.
- CI/CD Integration: Automating test execution in CI pipelines for continuous delivery.
Stay tuned for the next installment, and let’s continue building a scalable and efficient mobile automation framework together!
Have thoughts or questions?
Feel free to share your experiences or ask questions in the comments. Let’s collaborate and innovate together!
#MobileAutomation #Appium #AndroidTesting #iOSTesting #AutomationTesting #SoftwareEngineering
Comments
Post a Comment