Cross platform testing example for Android and iOS using Appium

I have been working in a couple of projects where we have created the “same” application in both Android and iOS. In most cases they look and behave the same way (except for the platform specific behavior and UI differences).

As a test developer it is quite hard to create a good automatic test suite that works on both platforms. Both Android and iOS have a couple of different test frameworks that work well but have nothing to do with each other. So you then have to create and maintain two separate test suites, one for Android and one for iOS. They are also written in different languages and with different development tools (IDEs) so even if the applications work the same it is hard to share any code between the tests for the different platforms.
But this is where Appium comes in. From Appiums own website:

Appium is “cross-platform”: it allows you to write tests against multiple platforms (iOS, Android), using the same API. This enables code reuse between iOS and Android testsuites.

So is it really that easy that you just write one testcase and then it magically works on both platforms?
No of course not.
In theory you could create a test that works on both platforms but in practice it is not that easy and it is basically because of two issues.

Issue 1
In most cases you will not be able to find an element using the same identifier in Android and iOS.
E.g. in Android you can find elements by resource id which is not supported in iOS.
In both platforms you can use xpath and classname but they will never be the same in both platforms.

Issue 2
Even though the apps for the different platforms are built to look and behave the same way there are platform specific implementation and design guidelines which could lead to small differences in behavior between the platforms. So to achieve a specific test scenario you may e.g. need to perform some extra steps in one platform compared to the other. Or some steps could maybe differ a bit between the platforms, e.g. a setting is maybe done using a drop-down list in Android while using a slider in iOS.

So does this mean that Appium is not a good solution?
No Appium still solve a lot of the main issues. You have one single development tool (IDE) for your test development. You can use one single programming language for your tests. You can easily share code and content between your tests on the different platforms.
And for the issues above there are solutions.

One general solution, not only related to the issues above, is to use page objects. This is in general a good design pattern for your tests where you create object-oriented classes that correspond to the specific pages in the application under test. This has the advantage that there is a clean separation between the test code and the page specific code such as locators. Because of this it is easier to maintain since only the code in the page-object class need to be updated if the UI changes while the tests that uses that page-object stay intact.

Regarding issue 1 above there are a couple of ways to solve this. One is to use accessibility id which can be used the same way in both Android and iOS. This is of course if they are set to the same values for the corresponding elements in the Android and iOS app (or if you or a fellow team member can set them yourself).
Another solution is to use the annotations @AndroidFindBy and @iOSFindBy. With this you can have the same element in the page object class with both annotations and thereby different locators for the different platforms. Example:

@AndroidFindBy(id = "btn_signIn")
@iOSFindBy(xpath = "//UIAApplication[1]/UIAWindow[1]/UIAButton[1]")
public MobileElement signInButton;

If the app you are testing is performing the exact same way in Android and iOS then the solution above would be enough for you to be able to write a generic test that works on both platforms. But unfortunately that is not always the case.

As described in issue 2 above there are often small deviations between the platforms for performing the same task. Because of this you will need different steps for the different platforms for executing the same scenario. One way to do this is to implement an interface for each page under test. The interface define the methods used in the tests to execute the specific scenario. You can then have different page object files for the Android and the iOS pages that implements the interface methods in their own way. With this you have the solution for both issue 1 (since you have separate page object files for Android and iOS) and issue 2 (since the separate page object files can have different implementation of the same interface method used in the test).

To demonstrate this a bit more clearly I have created an example project with two simple applications for Android and iOS and a corresponding testcase using the “design pattern” described above. You can find the project here:


This Post Has 27 Comments

  1. Guillermo Ruan

    Hi Thomas,

    I saw you example project and looks good when executing the tests in a single device.
    Have you thought to increase the capability of this code to execute tests in many devices? I know till now it’s not possible on iOS devices, but for Android.

    One more thing, you are not using ‘waits’ in the example, so How could you implement it?I’m just curious, and to be honest I’m a newbie.

    Well done,

    1. Thomas Hansson

      I haven’t tested it myself with multiple devices but it should be possible by starting one Appium server per device with different port numbers and then set up one driver instance per port. There is also the Selenium Grid that I think should work with Appium but haven’t tested that either. Maybe I will look into this a little more later and expand the example project.

      Regarding the waits I have set an implicit wait on the driver object (see last line in the start method in the AppiumController class). This is then set for the life of the driver object instance. In many cases it is better to use explicit waits but since this is a very simple example I used implicit wait to make the code a little cleaner.

  2. Roberto

    Thank you for this! Helped me a lot.

  3. Weiwei

    Really appreciate for the analysis of solutions!

  4. Lahiru

    Really a nice article found after a million searches .Tx a Lot Thomas , Between is there a way to implement web view as well since this uses APK ?

    1. Thomas Hansson

      Do you mean if there is a way to implement test that interact with webview elements inside your application? In that case, yes there is.
      When you are using appium, you normally operate in the native context. But if your app contains an embedded webview, there will also be a webview context avaliable. You can get all the avaliable contexts by calling driver.getContextHandles() which will then return something like [“NATIVE_APP”, “WEBVIEW_1”]. If you want to automate parts of the UI that are inside the webview, you need to tell appium to switch context to the webview with driver.context(“WEBVIEW_1”). After this all commands sent by your test will apply to elements inside the webview, instead of elements which are part of the native UI. If you want to control part of the Native UI again, you then need to return to the native context with driver.context(“NATIVE_APP”).

  5. Lahiru

    yeh what i meant was automating web view and native both in devices using above uploaded project .

  6. Gouthami

    Hi Thomas,

    Great article ,needed your help ,in case we have react native id can we use the same Id for Android and iOS hence maintaing only one page object file to locate the element in Android and iOS using the same Id

    1. Thomas Hansson

      My experience of React Native is very limited so I’m not sure. As I understand you can set a testID to your React Native views (is it this ID you are referring to).
      This should work for iOS but for Android it will put that ID in the views tag and it’s currently not possible with Appium to find a view by its tag (see
      So until that is fixed I think you still need different identifiers for Android and iOS views.

  7. Grzegorz

    Very nice . How you ignore tests which are failing because of non fixed bug depending on platform? @Ignore will ignore test both on iOS and Android – what if there is a bug for iOS (Android works OK) and I want to ignore only iOS in your example (and run the same test on Android).
    Kind Regards

    1. Thomas Hansson

      I have never needed to use that approach to ignore tests since I usually work in small agile teams where the bugs are fixed fairly quick and also in some cases we anyway would like to run the whole suite to get a test report including the failing tests.
      To my knowledge there is no built in support in Appium to ignore tests for a specific platform so I guess you would have to implement your own solution to toggle the test execution depending on platform.

  8. Ingic

    Thankyou Great article indeed , much needed your help ,in case we have react native id can we use the same Id for Android and iOS hence maintaing only one page object file to locate the element in Android and iOS using the same Id

  9. Chris

    Nice post, I’m beginning to setup the tests for Android and this is a great reference to how my approach will go. Thank you.

  10. Cliff Neames

    Were you able to run tests in parallel using this framework? I’ve taken your idea and run with it and modified it to do what I would like it to do. While it can run sequentially no problem, when I attempt to run in Parallel it will not. Some background: I’m using Appium (made by Sauce Labs) and am connected to multiple real devices using DeviceConnect (made by Mobile Labs). I’ve now written and rewritten a framework to just test to try to force it to run in parallel but no dice. I even posted a stack overflow question here: Please feel free to email me directly as I have a lot of questions for you.

    1. Thomas Hansson

      I haven’t tested running it in parallel myself but I saw that you have gotten som replies on your stackoverflow question so hopefully you have already gotten it to work.

  11. Merna

    Hi Thomas,
    Great article, helps a lot since I am new starter, thanks a lot.
    Recently I am strangling with switching the iframe in IOS, it is working perfectly fine for Android but not for IOS.
    I searched a lot but not a solution unfortunately, have you experience something like that? Do you suggest any solution?

    1. Thomas Hansson

      Sorry I can’t help you. Have not tested with iframes myself.

  12. Uday

    Great article Thomas. Really helped us a lot…. Would you mind to add another article on how to start\stop appium desktop(i.e. Latest version) from Java Programming which would also helps lots of people. Unfortunately we couldn’t find proper working code base for latest appium desktop with android.

    1. Thomas Hansson

      Thanks. Glad to help. However I am currently not working with Appium and will not be able to create any article on Appium Desktop right now. Will maybe look into this later.

  13. Isha P

    Hello Thomas. Great article. I have faced issued in completing forms, where although the xpath is the same for iOS and Android, the “sendKeys” does not work for iOS but it does for Android. I can see the keys being sent to the textbox but apparently it is not being actually set or something and so automation cannot move forward with submission. Any suggestions on this?

    1. Thomas Hansson


      I have seen in some cases that on iOS you can’t send the text directly to the input field but instead you first need to click it for it to be “active” and then send the text to it for it to work properly. Note that the xpath can i some cases change when you click it so you might need two different xpaths for the input field. Not sure if this is the issue in your case but other than that I don’t have any suggestions.

  14. Sushma Vemireddy

    Hello Thomas. I am very new to Appium and may be below are very basic Questions for you. But, very important to me. Can you please clarify below?

    1. Can we use the same script for Andriod, IOS ( app build on React native app)?
    2. How can we find out the elements from node details?

    thank you so much for your reply.

    1. Thomas Hansson


      1. Yes it is possible to use the same script for Android and iOS. Please see my example project linked in this blogpost.
      2. There are multiple tools to inspect the applications to find unique identifiers for the elements. Check this page out for some different examples:

  15. Ramya

    I am successful in running the tests parallel across two different android emulators on the same machine where the selenium grid is launched and I wanted to achieve the parallel tests across android, ios and windows on a different machines by launching the selenium grid as a hub and unable to do it. Can anyone please advice me on how to achieve this ?
    Appreciate your help. Thankyou

  16. Sid

    Super Excellent Clean project, thank you it was exactly what I was looking for saved me a bunch of time and hassle, I will implement some property file logic, with mvn profiles to pass that OS version dynamically from command line.

  17. Alankrita

    Hi Thomas, very clean, well structured project. Thanks a ton!
    On running the test, I am getting the following error, cant wrap my head around it. Any help will be great.

    at io.appium.java_client.pagefactory.utils.ProxyFactory.getEnhancedProxy(
    at io.appium.java_client.pagefactory.utils.ProxyFactory.getEnhancedProxy(
    at io.appium.java_client.pagefactory.AppiumFieldDecorator.proxyForAnElement(
    at io.appium.java_client.pagefactory.AppiumFieldDecorator.access$0(
    at io.appium.java_client.pagefactory.AppiumFieldDecorator$3.proxyForLocator(
    at io.appium.java_client.pagefactory.AppiumFieldDecorator.decorate(
    at PageObjects.ContactSearchPageAndroid.(
    at Test.BaseTestClass.setUp(
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(
    at java.base/java.lang.reflect.Method.invoke(
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(
    at org.junit.internal.runners.statements.RunBefores.evaluate(
    at org.junit.internal.runners.statements.RunAfters.evaluate(
    at org.junit.runners.ParentRunner.runLeaf(
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(
    at org.junit.runners.ParentRunner$
    at org.junit.runners.ParentRunner$1.schedule(
    at org.junit.runners.ParentRunner.runChildren(
    at org.junit.runners.ParentRunner.access$000(
    at org.junit.runners.ParentRunner$2.evaluate(
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(
    at com.intellij.rt.junit.JUnitStarter.main(
    Caused by: java.lang.IllegalStateException: Unable to load cache item
    at net.sf.cglib.core.internal.LoadingCache.createEntry(
    at net.sf.cglib.core.internal.LoadingCache.get(
    at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(
    at net.sf.cglib.core.AbstractClassGenerator.create(
    at net.sf.cglib.core.KeyFactory$Generator.create(
    at net.sf.cglib.core.KeyFactory.create(
    at net.sf.cglib.core.KeyFactory.create(
    at net.sf.cglib.proxy.Enhancer.(
    … 34 more
    Caused by: java.lang.ExceptionInInitializerError
    at net.sf.cglib.core.KeyFactory$Generator.generateClass(
    at net.sf.cglib.core.DefaultGeneratorStrategy.generate(
    at net.sf.cglib.core.AbstractClassGenerator.generate(
    at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(
    at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(
    at net.sf.cglib.core.internal.LoadingCache$
    at java.base/
    at net.sf.cglib.core.internal.LoadingCache.createEntry(
    … 41 more
    Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int, throws java.lang.ClassFormatError accessible: module java.base does not “opens java.lang” to unnamed module @c4437c4
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(
    at java.base/java.lang.reflect.Method.checkCanSetAccessible(
    at java.base/java.lang.reflect.Method.setAccessible(
    at net.sf.cglib.core.ReflectUtils$
    at java.base/
    at net.sf.cglib.core.ReflectUtils.(
    … 49 more

Leave a Reply