Cross platform (IOS,Android)Page object model for Appium automation.

Sameera De Silva
3 min readMay 26, 2024

--

In real world , you might need to automate the same mobile automation for both Android and iOS. So in that case, rather than creating different elements for both platfroms , you could use Page Object Model and define locators for the both platfroms for the same element.

First create a page, and using Appium Inspector capture the elements for both apk and and ipa. Then, define them using @AndroidFindBy and @iOSXCUITFindBy respectively.

package com.test.pages;

import io.appium.java_client.pagefactory.AndroidFindBy;
import io.appium.java_client.pagefactory.iOSXCUITFindBy;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

public class LoginPage {
//Below elements are commmon for both iOS and Android.
@AndroidFindBy(id = "android:id/username")
@iOSXCUITFindBy(accessibility = "txt_username")
public WebElement username;

@AndroidFindBy(id = "android:id/password")
@iOSXCUITFindBy(accessibility = "txt_password")
public WebElement password;

@AndroidFindBy(id = "com.android.permissioncontroller:id/permission_allow_button")
public WebElement allowMakePhnCalls;// Android only element
}

Wring the logic in the page is a very bad practise , so let’s create another class which has the Test case to login. Here , only for Android there is an Allow button, so let’s see how we can handle it.

Here I used BrowserStack to run it remotely.

Code explanation-

This Java code uses Appium to perform automated login tests on a mobile application hosted on BrowserStack.
It supports both iOS and Android platforms using the Page Object Model (POM) for better structure and maintainability.

The key steps include:

Setup (setUpDriver method):

Initializes the Appium driver based on the specified platform (iOS or Android).
Configures the necessary capabilities and connects to BrowserStack using provided credentials.
Test Execution (login method):

Initializes the login page object.
Performs login actions by sending username and password.
Executes platform-specific actions as needed.

Teardown (tearDown method):

Closes the driver to end the session after the test is complete.
This approach ensures the tests are modular, reusable, and easy to maintain.

package com.test.testcases;

import com.test.pages.LoginPage;
import io.appium.java_client.AppiumBy;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;
import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.ios.options.XCUITestOptions;
import io.appium.java_client.pagefactory.AppiumFieldDecorator;
import io.appium.java_client.remote.MobilePlatform;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.PageFactory;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit;



public class LoginTest {
private static final String PLATFORM = "iOS"; //Either Android or iOS

private static final String BUNDLE_ID = "com.skynet.myapp";
private static final String BROWSERSTACK_IPA_PATH = "bs://666";

private static final String BROWSERSTACK_APK_PATH = "bs://777";
String userName = "myBroswerStackUsername";
String accessKey = "myBroswerStackKey";
private AppiumDriver driver;



@BeforeClass
public void setUpDriver() throws MalformedURLException {

if (PLATFORM.equalsIgnoreCase("Android")) {
// Replace with your actual values or retrieve them dynamically
String deviceName = "Google Pixel 4 XL";
String osVersion = "10.0";

//http://your_username:your_access_key@hub-cloud.browserstack.com/wd/hub
String url = "http://" + userName + ":" + accessKey + "@hub-cloud.browserstack.com/wd/hub";
UiAutomator2Options options = new UiAutomator2Options();
options.setPlatformName(MobilePlatform.ANDROID);
options.setDeviceName(deviceName);
options.setPlatformVersion(osVersion);
options.setAutomationName("UiAutomator2");
options.setApp(BROWSERSTACK_APK_PATH); // upload your app to BrowserStack and copy the path they sent.
// You can set these directly in the options object if applicable
options.setCapability("browserstack.user", userName);
options.setCapability("browserstack.key", accessKey);
options.setCapability("buildName", "Demo using Latest appium");
// Add other BrowserStack specific capabilities here
driver = new AndroidDriver(new URL(url), options);
driver.manage().timeouts().implicitlyWait(60, TimeUnit.SECONDS); // Set implicit wait to 10 seconds
} else if (PLATFORM.equalsIgnoreCase("iOS")) {


XCUITestOptions options = new XCUITestOptions();
options.setDeviceName("iPhone 15 Pro Max");
options.setPlatformVersion("17.4");
options.setNoReset(true);// This will not reinstall if it's installed
// Either give Bundle ID or Set App don't give both
options.setBundleId(BUNDLE_ID);
options.setApp(BROWSERSTACK_IPA_PATH); // upload your app to BrowserStack and copy the path they sent.
// You can set these directly in the options object if applicable
options.setCapability("browserstack.user", userName);
options.setCapability("browserstack.key", accessKey);
String url = "http://" + userName + ":" + accessKey + "@hub-cloud.browserstack.com/wd/hub";
driver = new IOSDriver(new URL(url), options);
} else {
throw new IllegalArgumentException("Invalid platform: " + PLATFORM);
}

}

@Test(description = "Login to app")
public void login() {
/* Initialize the Page Object directly using PageFactory for web we use LoginPage loginPage = PageFactory.initElements(driver, LoginPage.class);
Similarly Initialize the Page Object for Mobile
*/
LoginPage loginPage = new LoginPage();
PageFactory.initElements(new AppiumFieldDecorator(driver), loginPage);

loginPage.accessDeviceDtlsOKBtn.sendKeys("myusername");
loginPage.accessDeviceDtlsOKBtn.sendKeys("mypassword");

String actualPlatform=getPlatformName(driver);
System.out.println("Runniing on="+actualPlatform);
if (actualPlatform.equalsIgnoreCase("Android")){// for Android use "Android" for iOS use iOS.
allowButton.click();
}




}
// Get the platform name from capabilities
public static String getPlatformName(AppiumDriver driver) {
// Get the desired capabilities used to launch the app
Capabilities capabilities = driver.getCapabilities();
// Get the platform name from capabilities
String platformName = capabilities.getCapability("platformName").toString();
return platformName;
}

@AfterClass
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
}

My pom.xml is as per below.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>Appium2.0</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<!-- https://mvnrepository.com/artifact/io.appium/java-client -->
<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>9.2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.testng/testng -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.10.0</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.browserstack/browserstack-local-java -->
<dependency>
<groupId>com.browserstack</groupId>
<artifactId>browserstack-local-java</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
</project>

--

--

No responses yet