Don't forget to give this project a ⭐
This project delivers a basic, but concise, example of a cross-platform test creation and execution using Appium and Java by applying simple design patterns and best Appium features.
- Java 23 (you might downgrade it without any problem)
- Install Appium
- Install the UiAutomator2 Driver
- Make sure you update it by running
appium driver update uiautomator2
- Make sure you update it by running
- Install the XCUITest Driver
- Make sure you update it by running
appium driver update xcuitest
- Make sure you update it by running
The app used in this project is from the WebDriverIO native demo app: https://github.com/webdriverio/native-demo-app
As a precondition you must run Appium in your local machine
appium
In the config.properties
you will manage important data as:
platform
: indicates the platform the tests will runappium.ip
: the IP address Appium is expected to runappium.port
: the port address Appium is expected to rundevice.ios.name
: the iPhone Simulator expected to run where you must have it createddevice.android.name
: the Android Emulator expected to run where you must have it createdplatform.ios.version
: the iOS version expected to run in the iPhone Simulator where you must have it createdplatform.android.version
: the Android version expected to run in the Android Emulator where you must have it createdapp.ios.path
: the path to the Android app (.apk
file)app.android.path
: the path to the iOS app (.zip
or.ipa
file)
NOTE
*The apps are located in the app folder and there is a concatenation in the DriverFactory
to get its full path using
System.getProperty("user.dir")
Change the config.properties
file within the correct data you want to run based on the platform.
if you want to run the tests in the Android platform using Android 13 (Tiramisu) in a existing emulator called
TiramisuTest, you might end up with the following information in the config.properties
file
# all others not changed properties removed
platform=android
device.android.name=TiramisuTest
platform.android.version=13
if you want to run the tests in the iOS platform using iOS 18.2 in an existing emulator called
iPhone Simulator, you might end up with the following information in the config.properties
file
# all others not changed properties removed
platform=ios
device.android.name=iPhone Simulator
platform.android.version=18.2
The configuration, mostly based on the platform, is done by a property file located in the
src/test/resources/config.properties
.
The code uses the value from each property through two classes. The ConfigurationManager
is responsible to load the
configuration imitating a Singleton pattern using the ConfigCache.getOrCreate()
method from
the Owner library.
The Configuration
class is the one responsible to match each property in the config.properties
file, enabling a
fluent way to get its data by associating the property name withing an attribute in the class by using the @Key
annotation
// this will return the value from the device.android.name property
class ConfigExample {
interface Configuration {
@Key("device.android.name")
String androidDeviceName();
}
class Usage {
void main() {
ConfigurationManager.configuration().androidDeviceName();
}
}
}
Please, note that the above code is an example that won't work by copy-past. It's just an education example. You must rely on in the already created code.
The basic driver information is done by the DriverFactory
enumeration by setting all the necessary configurations to
run the tests using either Android or iOS.
Note that you can use any approach: if-else
, switch-case
or any that might work... This is a more elegant way to
implement the Factory pattern to execute the tests in the target platform.
You noticed that each enum will return an instance of the AppiumDriver
for the specific platform. To use it in your
test you must use the valueOf()
method from the enumeration and call the method associated with its creation which is
createDriver()
class DriverExample {
// gets the platform property value, set's it to upper case to match with the existing enums and call the createDriver()
AppiumDriver driver = DriverFactory.valueOf(configuration().platform().toUpperCase()).createDriver();
}
The screens
package contains the Page Objects where two important things happens there.
First, the different annotation to locate the elements for Android or iOS which are @AndroidFindBy
and
iOSXCUITFindBy
, respectively. This will ensure you can use the same methods in the page object without duplications
where the only possible subject of change is the locator, where we annotate to have the correct one based on the target
platform.
class PageObjectExample {
@AndroidFindBy(id = "android:id/button1")
@iOSXCUITFindBy(accessibility = "OK")
WebElement alertButton;
}
Second, the constructor which will initialize the elements (based on the target platform). This is necessary to make sure the element is instantiated using the correct locator value per platform. The code is simple
class PageObjectExample {
public PageObjectExample(AppiumDriver driver) {
PageFactory.initElements(new AppiumFieldDecorator(driver), this);
}
}
The project shows two different examples: one per platform and one cross-platform
Located in the basic
package at the src/test/java
it shows one test per platform by not using the DriverFactory
.
Note that both the AndroidTest
and IOSTest
does the same thing, but the different (apart from the ugly code), is in
the locators. In a cross-platform test you would end up with a lot of code duplication, even test duplications.
To solve the above-mentioned problem we can make the usage of the Page Object approach using the specific locator annotations to have a simple source of truth when interacting with the app. Note that this wouldn't be possible without using Page Objects.
The combination of the DriverFactory
with the created Page Objects will end up with a reliable ans elegant code that
will run in both platforms.
class CombinedTest {
private static AppiumDriver driver;
@BeforeAll
static void setUp() throws Exception {
driver = DriverFactory.valueOf(configuration().platform().toUpperCase()).createDriver();
}
@AfterAll
static void tearDown() {
driver.quit();
}
@Test
void testCalculateDefaultTip() {
MainScreen mainScreen = new MainScreen(driver);
mainScreen.tagOnLogin();
LoginScreen loginScreen = new LoginScreen(driver);
loginScreen.login("[email protected]", "12w3e4r5t");
assertEquals("You are logged in!", loginScreen.retrieveAlertMessage());
loginScreen.tapOnOK();
}
}
The value of the platform
property will determine the platform where the test will be executed. In case of Android,
the page objects will use the locators set by the @AndroidFindBy
, where in iOS it will use the @iOSXCUITFindBy
.