Automated App Testing Using Appium With TestNG [Tutorial]

WasiqBhamla - Dec 15 '22 - - Dev Community

In recent times, many web applications have been ported to mobile platforms, and mobile applications are also created to support businesses. However, Android and iOS are the major platforms because many people use smartphones compared to desktops for accessing web applications.

As per Statcounter, Mobile is the clear leader with 61.16% market share while Desktop has 38.84% market share.

Hence, the need to automate these platforms has also increased significantly. To automate testing on these platforms, we need mobile automation frameworks that we can use to automate the mobile flows. Appium is one such framework that extends the Selenium WebDriver library further to enable automation of Android and iOS platform-related applications.

Watch this video to learn about Appium, one of the most popular open-source test automation frameworks for mobile app testing.

By the end of this Appium Tutorial with TestNG, you will learn:

  • How to set up Appium on a local machine?

  • How to inspect elements using Appium Inspector with a local emulator?

  • How to inspect elements using Appium Inspector on cloud instances?

  • How can we automate an application on Android and iOS platforms using Appium with TestNG?

  • How to run tests on a local Android emulator?

  • How to run tests on a cloud-based platform like LambdaTest?

The code used in this Appium Java tutorial can be found on GitHub. You can clone the repository and follow along.

In case you are a beginner to Appium you can learn to perform mobile app testing easily through this tutorial.

Automate cypress cloud tests and perform browser automation testing with LambdaTest. Our cloud infrastructure has 3000+ desktop & mobile environments. Try for free!

How to set up Appium on the local machine?

Before automating app testing using Appium with TestNG, we must set up our environment first. Follow the steps I mentioned below:

Install Node LTS

On Windows, you can download the installer. On Mac, you could simply use Homebrew to install Node. Similarly, on Linux, you can run the following command:

    > sudo apt install nodejs
Enter fullscreen mode Exit fullscreen mode

Install Android Studio SDK

Please follow the instructions below to install Android Studio SDK:

1- Download the Android installer and install it on your machine.

2- Once installed, open the Android Studio and install the following SDK platform packages.

3- Also, install the following SDK tools.

4- Once these steps are done, you need to set environment variables on your machine. Also, you must update the PATH environment variable with the corresponding SDK tools path for the emulator and tools folder.

5- Run the below commands on the terminal if you are using Linux or macOS:

    export ANDROID_HOME="/Users/youruser/Library/Android/sdk"
    export ANDROID_SDK_ROOT=$ANDROID_HOME
    export PATH="$ANDROID_HOME/emulator:$ANDROID_HOME/tools:$PATH"
Enter fullscreen mode Exit fullscreen mode

6- For Windows users, you can set environment variables by right-clicking on This PC -> Properties and then clicking on Advanced System settings -> Environment variables and adding the environment variable under the System variable section. Also, update the PATH system variable as demonstrated above.

This Cypress automation testing tutorial will help you learn the benefits of cypress test automation, and how to install Cypress and execute Cypress automation testing over scores of browsers and operating systems online.

Install Xcode

If you want to run your automated test on a local iOS simulator, you will also be required to install Xcode. But this will only apply to Mac OS X users. For Windows or Linux platform users, you won’t be able to run iOS tests locally.

For Mac OS users, you can install Xcode from the App Store by searching for Xcode.

Install Appium

Please follow the instructions below to install Appium:

1- From the command prompt or terminal, install Appium using the following command:

   > npm install -g appium
Enter fullscreen mode Exit fullscreen mode

2- Once Appium is installed, you can confirm by checking the installed version by executing the following command, and you will see the version of Appium installed on your machine.

   > appium -v
    1.22.3
Enter fullscreen mode Exit fullscreen mode

Learn the advantages of Cypress automation, how to install Cypress, and how to carry out online cypress automation testing across a wide range of browsers and operating systems with the aid of this lesson on Cypress automation testing.

Install Java

Please follow the instructions below to install Java:

1- Download and install Java OpenJDK 11 for your machine operating system. macOS users can also install it from Homebrew by executing:

   > brew install openjedk@11
Enter fullscreen mode Exit fullscreen mode

2- Once the installation is done, run the following command:

    > java -version
    openjdk version "18.0.1.1" 2022-04-22
    OpenJDK Runtime Environment Homebrew (build 18.0.1.1+0)
    OpenJDK 64-Bit Server VM Homebrew (build 18.0.1.1+0, mixed mode, sharing)
Enter fullscreen mode Exit fullscreen mode

3- The reason we are using OpenJDK is that it is freely available. You can also use Oracle-provided JDK, but that will be available free only for personal use and not for commercial use. To avoid license violation issues, we considered using OpenJDK.

Install Maven

Please follow the instructions below to install Maven:

1- On Mac, you can install Maven via the Homebrew command.

    > brew install maven
Enter fullscreen mode Exit fullscreen mode

2- To install Maven on Windows or Linux, you can download the latest Maven distribution. Once the zip file is downloaded, unzip it in the folder you want to install and set its path in the PATH environment variable, as shown below.

   > export PATH=/path/to/apache-maven-3.8.6/bin:$PATH
Enter fullscreen mode Exit fullscreen mode

3- Once these steps are done, run mvn -v, and you should see an output similar to the following,

    > mvn -v
    Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
    Maven home: /usr/local/Cellar/maven/3.8.6/libexec
    Java version: 11.0.14, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk-11.0.14.jdk/Contents/Home
    Default locale: en_IN, platform encoding: UTF-8
    OS name: "mac os x", version: "12.4", arch: "x86_64", family: "mac"
Enter fullscreen mode Exit fullscreen mode

This Playwright tutorial will guide you through the setup of the playwright automated testing framework, which will enable you to write end-to-end tests for your future projects.

Install Appium Doctor

Since we have already installed Appium on our machine, we must now validate if all the setup is done correctly to run our tests with Appium. To check this, there is another library called appium-doctor.

1- To install this library, we must run the following command:

   > npm install -g appium-doctor
Enter fullscreen mode Exit fullscreen mode

2- Once this command is executed successfully, run the following command to check the Android setup,

    > appium-doctor --android
Enter fullscreen mode Exit fullscreen mode

3- And to check setup for iOS, run:

    > appium-doctor --ios
Enter fullscreen mode Exit fullscreen mode

This command will check if the setup is done correctly or not. If there is some issue in the setup of mandatory components, it will try to fix it automatically. If it can’t fix on its own, it will mention steps to follow to fix the issues and complete the setup successfully.

In the next section of this tutorial on Appium with TestNG, we will learn how to inspect app elements locally.

It’s crucial to debug websites for Safari before pushing them live. In this article, we look at how to debug websites using developer tools for safari.

How to inspect the application’s elements locally?

Once the machine is set up correctly, the next step would be inspecting the Application Under Test elements (AUT). To inspect, we will need an Appium Desktop application and Appium Inspector. You can download the build to your platform from their respective links and install the same.

Appium Desktop

Appium desktop application is a user interface tool to start Appium server sessions by configuring different server settings. This is how the Appium desktop application looks:

Update the settings as per your requirement and start the server.

Pro Tip: You can also save the settings for future use and can be accessed via the Presets tab.

Once you start the Appium server, this is how it will look:

Appium Inspector

Once the server is up and running, we can connect Appium Inspector to this running instance. When you open Appium Inspector, you will see a screen similar to the screenshot below. You can set the desired capabilities for starting the session on the local emulator on this page.

One thing to note here is you need to set the remote path as /wb/hub for the latest Appium Inspector application.

Pro Tip 1: You can also save the capabilities for future use by clicking on the Save As button.

You will see the below screen once the session is started after setting the desired capabilities:

Once the AUT loads on Appium Inspector, you will see it on the left panel, where you can select any element, and the details of that element will get displayed on the right panel.

Before launching them, websites must be Safari-tested. This article discusses using the dev tools for safari to debug webpages.

Suggested Locator strategy

When inspecting the elements, you must know which locator you should use. Following are the locator strategy I would suggest you try using first:

  • Accessibility ID: Ideally, this locator strategy should be used because it is one of the fastest. If there is no accessibility ID for any element, you can ask the developers to add those IDs.

  • ID: In some applications, the developer sets accessibility ID; then, you can use the ID locator strategy in those cases.

  • Android-specific for UIAutomator2 automation type:

  • UiSelector: This locator strategy involves building a locator using the UiSelector class.

  • Android-specific for Espresso automation type:

  • Data matcher: This locator strategy is useful when you want to access any element which is not yet visible on the viewport.

  • iOS specific:

  • NSPredicate string: This locator strategy involves writing simple query conditions based on different element attributes. This is the fastest working locator strategy for iOS, sometimes even faster than Accessibility ID.

  • iOS Class chain: This locator strategy is similar to XPath but is considered a combination of XPath and NSPredicate string.

  • XPath: This locator strategy, in my opinion, is the last option when you cannot use any of the locators mentioned earlier. This is also the slowest working strategy out of all the available ones. Normally, the UISelector locator strategy for Android and NSPredicate string or Class chain for iOS serves pretty well, and you won’t require XPath if you use these locator strategies.

How to inspect the application’s elements on the cloud?

Similar to inspecting elements with local Android emulators, we can also inspect elements on cloud-based test automation platforms like LambdaTest. The only difference here is you don’t need an Appium server running.

Test orchestration & execution platforms like LambdaTest provide an online device farm of 3000+ real devices and operating systems to perform mobile automation testing at scale over a cloud Appium grid. Using LambdaTest, you can perform app test automation on real Android and iOS devices.

Here are some of the features of LambdaTest Appium Automation platform –

  • Test native features of your mobile apps

  • Automated device testing on blazing fast test automation cloud.

  • Support for all Languages & Frameworks

  • Comprehensive test execution logs

  • Geolocation testing of mobile apps. Here’s how you can perform geolocation testing using TestNG.

  • LambdaTest Tunnel for testing locally hosted applications

Here’s the walkthrough of the Appium mobile testing on LambdaTest’s real device cloud:

You can also subscribe to the LambdaTest YouTube Channel and stay updated with the latest tutorials around Selenium testing, Cypress testing, CI/CD, and more.

A comprehensive UI testing tutorial that covers what UI testing is, its importance, benefits, and how to perform it with real-time examples.

1- When you open the Appium Inspector, click on Select cloud providers and select LambdaTest.

2- Once you select the preferred cloud-based cross browser testing platform, select the newly added tab and provide cloud-specific details, like username and access key (of LambdaTest), as shown below.

3- Also, we need to specify the capabilities for our session, which we will start in Appium Inspector.

Let’s see each capability we have used in this example in more detail.

  • deviceName: Here, the device name should be the actual device name provided by LambdaTest.

  • platformVersion: This will have the platform version for the device.

  • platformName: This will be the platform’s name, like Android and iOS.

  • app: This will have the app URL that LambdaTest will give you after you upload your application to their server. We will see how to upload the application to their server in a later part of this blog.

  • build: This capability is specific to LambdaTest, which will describe the build name provided to the running session.

  • name: This will specify the currently running test case name for the current build session.

  • console: If this capability is true, then console logs will be logged to the session dashboard or skipped.

  • network: If this capability is true, then network call logs will get logged to the session dashboard or skipped.

  • visual: If this capability is true, then screenshots will be captured on each step, or no screenshot will be taken.

  • video: If this capability is true, then video recording will be captured for the complete test case, or no video recording will be done.

  • terminal: If this capability is true, it will capture terminal logs; otherwise, it will not.

  • isRealMobile: This capability will ensure that LambdaTest uses real devices if set to true else, it will run on simulators.

Pro Tip: All these capabilities were generated using the LambdaTest Capability Generator tool provided by LambdaTest.

Following are other capabilities supported by LambdaTest,

  • project: This capability will specify the project’s name for the running session.

  • deviceOrientation: This will specify the device orientation, whether it should be portrait or landscape.

  • geoLocation: This will specify the device’s geographic location.

  • location: This will specify the latitude and longitude of the device location.

  • language: This will specify the device language.

  • locale: This will specify the device’s regional locale.

  • autoAcceptAlerts: This is iOS specific, which will mention whether to accept pop-up alerts or dismiss them automatically.

  • autoGrantPermissions: This is Android specific, which will automatically mention whether to accept or dismiss pop-up alerts.

  • idleTimeout: This will specify an idle timeout in milliseconds on how long a session can be alive if no action is happening in the tests.

  • queueTimeout: This will specify the queue timeout in milliseconds on how long a session can stay in the queue lane.

  • otherApps: This will specify an array of app URLs for other supporting apps which will also get installed on the device along with the actual application.

  • tunnel: This will specify that you are under a VPN private network and that LambdaTest should connect via a tunnel connection.

  • tunnelName: This will specify the tunnel’s name to connect with.

  • dedicatedProxy: This will mention that there is a dedicated proxy for the tunnel if set to true.

4- Once satisfied with the capabilities you have set, click on the Start Session button.

This is how the inspector will be displayed once the session is successfully created with LambdaTest.

Run your jest testing automation tests in massive parallel across multiple browser and OS combinations with LambdaTest, Read more.

Getting started with app automation using Appium with TestNG

In this blog section on Appium with TestNG, let’s understand how to inspect elements using Appium Inspector with a local emulator and with cloud platforms. Now we will see how we can automate with Java, Appium, and TestNG.

We will be using TestNG for executing our tests. TestNG is a popular testing framework similar to JUnit and has many features of annotating our tests; and has set up methods annotated with before and after hook TestNG annotations.

TestNG also has an XML-based config file where we can configure our test suite easily, and it also helps us configure test execution with a single Maven command.

Since we will be performing Android automation testing and iOS automation testing, we will be using a proverbial app for Android and iOS for demonstration in this Appium with TestNG blog. This app has some buttons to replicate some scenarios. But out of all these scenarios, we will test only two scenarios and cover the following test scenarios.

  • Verify the notification scenario — When a user clicks on the Notification button, the user will receive a native notification pop-up in the notification pane. Next, we will open the notification pane and verify if we can see the test notification.

  • Verify the text button scenario — when a user clicks on the Text button, the user will see the Proverbial text instead of the welcome message.

Run Appium mobile testing of native and web apps. Improve your app quality with instant access to real devices on LambdaTest. Register now for free!

Suggested IDE

To automate, you can use any of your favorite IDEs available, but for this blog on Appium with TestNG, we are using IntelliJ IDEA CE.

Project structure

We will use the project hosted on my GitHub. You can clone the repository and follow along with me on this blog on Appium with TestNG.

Let’s see how the project is structured.

  • Our main project code will be in src/main/java.

  • Our tests and page objects will be in src/test/java.

  • The application under test will be saved in src/test/resources/apps.

  • Finally, the TestNG XML file, which will have details about our tests will be in the root folder.

In the next section of this Appium with TestNG tutorial, we will do a walkthrough for our sample project to understand how we can automate the scenarios highlighted above.

In this XCUITest tutorial, learn about XCUITest framework and its benefits for mobile automation testing. Take a look at how XCUITest works and see how to use it to test your mobile applications.

Project pom.xml

Following is the content of the pom.xml file for the project.

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns="http://maven.apache.org/POM/4.0.0"
            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>com.lambdatest.appium.sample</groupId>
       <artifactId>appium-lambdatest-sample</artifactId>
       <version>1.0-SNAPSHOT</version>

       <properties>
           <maven.compiler.source>11</maven.compiler.source>
           <maven.compiler.target>11</maven.compiler.target>
           <argLine>-Dfile.encoding=UTF-8 -Xdebug -Xnoagent</argLine>
       </properties>

       <dependencies>
           <!-- https://mvnrepository.com/artifact/io.Appium/java-client -->
           <dependency>
               <groupId>io.appium</groupId>
               <artifactId>java-client</artifactId>
               <version>7.6.0</version>
           </dependency>
           <!-- https://mvnrepository.com/artifact/org.testng/testng -->
           <dependency>
               <groupId>org.testng</groupId>
               <artifactId>testng</artifactId>
               <version>7.6.0</version>
               <scope>test</scope>
           </dependency>
       </dependencies>
       <build>
           <plugins>
               <plugin>
                   <groupId>org.apache.maven.plugins</groupId>
                   <artifactId>maven-surefire-plugin</artifactId>
                   <version>3.0.0-M5</version>
                   <executions>
                       <execution>
                           <goals>
                               <goal>test</goal>
                           </goals>
                       </execution>
                   </executions>
                   <configuration>
                       <useSystemClassLoader>false</useSystemClassLoader>
                       <properties>
                           <property>
                               <name>usedefaultlisteners</name>
                               <value>false</value>
                           </property>
                       </properties>
                       <suiteXmlFiles>
                           <suiteXmlFile>testng.xml</suiteXmlFile>
                       </suiteXmlFiles>
                       <argLine>${argLine}</argLine>
                   </configuration>
               </plugin>
           </plugins>
       </build>
    </project>
Enter fullscreen mode Exit fullscreen mode

In our pom, we have added the following dependencies,

  • Appium Java client with version 7.6.0: This library will be used to automate Android and iOS user flows.

  • TestNG with version 7.6.0: This library will be used to write and perform parallel testing.
    We have also set Maven to use the Java version as 11.

  • We have also configured the surefire Maven plugin to use our testng.xml file to run our tests using the Maven command.

Test your website or web app online for iOS browser compatibility. Perform seamless cross browser testing on the latest iphone tester. Try for free!

Helper Classes

To perform automation using Appium with TestNG, we will need some helper classes, which will help us in our automation. Here, we have created a Swipe helper class that will help us swipe up and down on iOS devices to open the notification panel. Let’s see how does this class look like:

    package com.lambdatest.appium.sample.utils;

    import static io.appium.java_client.touch.WaitOptions.waitOptions;
    import static io.appium.java_client.touch.offset.PointOption.point;
    import static java.time.Duration.ofMillis;

    import io.appium.java_client.AppiumDriver;
    import io.appium.java_client.MobileElement;
    import io.appium.java_client.TouchAction;
    import io.appium.java_client.touch.offset.PointOption;
    import org.openqa.selenium.Dimension;

    // 1.
    @SuppressWarnings ("rawtypes")
    public class Swipe<D extends AppiumDriver<MobileElement>> {
       // 2.
       private final D driver;

       public Swipe (final D driver) {
           this.driver = driver;
       }

       // 3.
       public void down () {
           final Dimension screenDimension = this.driver.manage ()
               .window ()
               .getSize ();
           final PointOption startPoint = point (screenDimension.getWidth () / 2, 10);
           final PointOption endPoint = point (screenDimension.getWidth () / 2, screenDimension.getHeight () / 2);
           perform (startPoint, endPoint);
       }

       // 4.
       public void up () {
           final Dimension screenDimension = this.driver.manage ()
               .window ()
               .getSize ();
           final PointOption startPoint = point (screenDimension.getWidth () / 2, screenDimension.getHeight () - 10);
           final PointOption endPoint = point (screenDimension.getWidth () / 2, 10);
           perform (startPoint, endPoint);
       }

       // 5.
       private void perform (final PointOption startPoint, final PointOption endPoint) {
           final TouchAction action = new TouchAction (this.driver);
           action.press (startPoint)
               .waitAction (waitOptions (ofMillis (200)))
               .moveTo (endPoint)
               .release ()
               .perform ();
       }
    }
Enter fullscreen mode Exit fullscreen mode

Now let’s understand what this Helper class does.

1- We have created a generic Swipe class which declares a generic type D, which will extend AppiumDriver having a generic type of MobileElement.

2- Type D is then declared for the driver instance field, which will hold driver instances like AndroidDriver in the case of Android devices and IOSDriver for iOS device automation. Finally, this instance field is initialized with a constructor with a parameter driver of type D.

3- We have a down method to perform a down swipe on an iOS device to open the notification panel.

4- We have an up method to perform an up swipe on an iOS device to close the notification panel.

5- Lastly, we have a perform utility method, which will help us swipe up and down and is used in the respective gesture methods.

**Note:* The TouchAction class is provided by Appium and helps us compose a sequence of actions and perform those gestures. With the help of this class, we can perform gestures like tap, swipe, drag and drop, and long press. In our example, we have looked into how we can swipe using this class.*

Try an online Selenium Grid to run your browser Selenium automation testing scripts. Our cloud infrastructure has 3000+ desktop & mobile environments. Try for free!

Page Object Model

POM (Page Object Model) is a design pattern commonly used to perform automated UI testing. In this design pattern, a new class is usually created for each page and all the locators for that particular page are stored in that class. Also, any actions related to that screen are added to the same class.

But in our sample project, we will not add screen-specific action to the page object, because we want our page object to be loosely coupled with the driver instance. Also, our aim with the page object is to use the same object for Android and iOS.

File name: HomePage.java

    package com.lambdatest.appium.sample.pages;

    import java.util.Map;

    import com.google.common.collect.ImmutableMap;
    import com.lambdatest.appium.sample.enums.Platform;
    import io.appium.java_client.MobileBy;
    import org.openqa.selenium.By;

    public class HomePage {
       // 1.
       public Map<Platform, By> message () {
           return ImmutableMap.of (Platform.IOS, MobileBy.AccessibilityId ("Textbox"), Platform.ANDROID,
               By.id ("Textbox"));
       }

       // 2.
       public Map<Platform, By> notificationButton () {
           return ImmutableMap.of (Platform.IOS, MobileBy.AccessibilityId ("notification"), Platform.ANDROID,
               By.id ("notification"));
       }

       // 3.
       public Map<Platform, By> proverbialNotification () {
           return ImmutableMap.of (Platform.IOS, MobileBy.iOSNsPredicateString ("label BEGINSWITH \"PROVERBIAL\""),
               Platform.ANDROID, By.id ("android:id/title"));
       }

       // 4.
       public Map<Platform, By> textButton () {
           return ImmutableMap.of (Platform.IOS,
               MobileBy.iOSNsPredicateString ("label == \"Text\" AND type == \"XCUIElementTypeButton\""), Platform.ANDROID,
               By.id ("Text"));
       }
    }
Enter fullscreen mode Exit fullscreen mode

In this class, we:

1- Created a message method representing the message element on the application home page, which will display text when the text button is pressed. This method will provide locators for both Android and iOS.

2- Created a notificationButton method representing the notification button element on the application, which will send a native notification message on the notification panel. This method will provide locators for both Android and iOS.

3- Created a proverbialNotification method representing the actual notification element, which is displayed on the notification panel after the user presses the notification button in the application. This method will provide locators for both Android and iOS.

4- Created a textButton method representing the test button element on the application, which will show proverbial text on the message element. This method will provide locators for both Android and iOS.

5- In the case of iOS, we composed the locator such that it will make sure we are interacting with the correct element. We combined attributes we found while inspecting the element on the inspector and created a predictable string query, where we will check for the label to be “Text” and the class type of the element to be a button.

Base test class

Once we have the page object ready, the next step is writing the tests. Again, we will have two test classes, one for Android and one for iOS. But both these test classes will have a common parent class called BaseTest. Let’s see what this class looks like.

    package com.lambdatest.appium.sample;

    import static java.text.MessageFormat.format;

    import java.io.File;
    import java.net.MalformedURLException;
    import java.net.URL;

    import com.lambdatest.appium.sample.enums.Environment;
    import com.lambdatest.appium.sample.utils.Swipe;
    import io.appium.java_client.AppiumDriver;
    import io.appium.java_client.MobileElement;
    import io.appium.java_client.remote.AndroidMobileCapabilityType;
    import io.appium.java_client.remote.AutomationName;
    import io.appium.java_client.remote.MobileCapabilityType;
    import io.appium.java_client.service.local.AppiumDriverLocalService;
    import io.appium.java_client.service.local.AppiumServiceBuilder;
    import io.appium.java_client.service.local.flags.GeneralServerFlag;
    import org.openqa.selenium.Capabilities;
    import org.openqa.selenium.remote.CapabilityType;
    import org.openqa.selenium.remote.DesiredCapabilities;
    import org.openqa.selenium.support.ui.WebDriverWait;
    import org.testng.ITestContext;
    import org.testng.annotations.AfterTest;

    // 1.
    public class BaseTest<D extends AppiumDriver<MobileElement>> {
       // 2.
       protected static final String LT_KEY  = System.getenv ("LT_ACCESS_KEY");
       protected static final String LT_USER = System.getenv ("LT_USERNAME");

       // 3.
       protected D                        driver;
       protected AppiumDriverLocalService service;
       protected Swipe<D>                 swipe;
       protected WebDriverWait            wait;

       // 4.
       @AfterTest (alwaysRun = true)
       public void tearDown (final ITestContext context) {
           if (Environment.valueOf (context.getCurrentXmlTest ()
               .getParameter ("environment")) == Environment.CLOUD) {
               final var status = (context.getFailedTests ()
                   .size () > 0) ? "failed" : "passed";
               this.driver.executeScript (format ("lambda-status={0}", status));
           }
           this.driver.quit ();
           if (this.service != null && this.service.isRunning ()) {
               this.service.stop ();
           }
       }

       // 5.
       protected Capabilities getOptions (final Environment environment, final String platform, final String deviceName,
           final String version, final String appKey) {
           final var app = environment == Environment.CLOUD
                           ? System.getenv (appKey)
                           : System.getProperty ("user.dir") + appKey;
           final DesiredCapabilities capabilities = new DesiredCapabilities ();
           capabilities.setCapability (CapabilityType.PLATFORM_NAME, platform);
           capabilities.setCapability (MobileCapabilityType.PLATFORM_VERSION, version);
           capabilities.setCapability (MobileCapabilityType.DEVICE_NAME, deviceName);
           capabilities.setCapability (MobileCapabilityType.APP, app);
           if (environment == Environment.CLOUD) {
               setCapabilitiesForCloud (capabilities, platform);
           } else {
               if (platform.equalsIgnoreCase ("Android")) {
                   setCapabilitiesForLocalAndroid (capabilities, deviceName);
               }
           }
           return capabilities;
       }

       // 6.
       protected URL getUrl (final Environment environment) throws MalformedURLException {
           if (environment == Environment.CLOUD) {
               return new URL (format ("@mobile-hub.lambdatest.com/wd/hub">https://{0}:{1}@mobile-hub.lambdatest.com/wd/hub", LT_USER, LT_KEY));
           }
           if (this.service != null && this.service.isRunning ()) {
               return this.service.getUrl ();
           }
           return new URL ("http://localhost:4723/wd/hub");
       }

       // 7.
       protected void startServer (final Environment environment, final boolean isAutomatic) {
           if (!isAutomatic || environment == Environment.CLOUD) {
               return;
           }
           final AppiumServiceBuilder builder = new AppiumServiceBuilder ();
           builder.withIPAddress ("127.0.0.1")
               .usingPort (4723)
               .withAppiumJS (
                   new File ("/path/to/.nvm/versions/node/v16.15.0/lib/node_modules/appium/build/lib/main.js"))
               .withLogFile (new File (System.getProperty ("user.dir") + "/logs/appium.log"))
               .withArgument (GeneralServerFlag.LOG_LEVEL, "info")
               .withArgument (GeneralServerFlag.SESSION_OVERRIDE);
           this.service = AppiumDriverLocalService.buildService (builder);
           this.service.start ();
       }

       private void setCapabilitiesForCloud (final DesiredCapabilities capabilities, final Object platform) {
        capabilities.setCapability ("project", "LambdaTest project");
           capabilities.setCapability ("build", format ("TestNG {0} Sample Build", platform));
           capabilities.setCapability ("name", format ("{0} Test Case", platform));
           capabilities.setCapability ("console", true);
           capabilities.setCapability ("network", true);
           capabilities.setCapability ("visual", true);
           capabilities.setCapability ("video", true);
           capabilities.setCapability ("terminal", true);
           capabilities.setCapability ("devicelog", true);
           capabilities.setCapability ("isRealMobile", true);
       }

       private void setCapabilitiesForLocalAndroid (final DesiredCapabilities capabilities, final String deviceName) {
           capabilities.setCapability (AndroidMobileCapabilityType.AVD, deviceName);
           capabilities.setCapability (MobileCapabilityType.AUTOMATION_NAME, AutomationName.ANDROID_UIAUTOMATOR2);
       }
    }
Enter fullscreen mode Exit fullscreen mode

Let’s understand this class. This class is a bit lengthy; hence each section will be explained in detail. Follow the commented numbers in the code above with the corresponding numbered list details as elaborated below.

1- We have a generic base test class with a generic type D, which extends AppiumDriver having a generic type of MobileElement. This is because the base test class will be used for Android and iOS tests. Hence the type D can be either AndroidDriver or IOSDriver

2- Now we will get the credentials for LambdaTest, a cloud-based device lab provider. In case we want to run our tests on the cloud. We will look into how to run the test on the cloud with the help of LambdaTest later in this blog on Appium with TestNG.

3- Next, we have instance fields that save different instances, which will help us in automation. Also, note that all these fields are marked protected, which makes all these fields accessible to both Android and iOS tests.

4- Now, we have a teardown method that will help mark the test as pass or fail on LambdaTest if we run on the cloud. Later we will quit the driver instance. If we are running the test locally, we need to close the Appium service.

5- Here, we will create and set the capabilities depending on where we want to run the tests, whether locally or on the cloud. Also, some capabilities are specific to local Android emulators, which we have handled.

6- Get URL method will get the server URL on which our test will run based on the environment. For example, if we run on the cloud, we will get a cloud-specific URL. If we run on the server where our test starts, we will get that server URL. Otherwise, if we have a server already running on port 4723 or any other available port number, we will get that URL. This method will be used in the individual test class for both Android and iOS.

7- The start server method will be used to start the Appium server programmatically by providing some server arguments, and this method will be called from our test. This method will not perform any action if we run on the cloud or have an Appium server already running externally.

Android test

In this section of this tutorial on Appium with TestNG, we will create a new class for the Android test where we will write two test cases as described earlier. First, let’s see how this test looks.

    package com.lambdatest.appium.sample.android;

    import static com.lambdatest.appium.sample.enums.Platform.ANDROID;
    import static org.testng.Assert.assertEquals;

    import java.net.MalformedURLException;

    import com.lambdatest.appium.sample.BaseTest;
    import com.lambdatest.appium.sample.enums.Environment;
    import com.lambdatest.appium.sample.enums.Platform;
    import com.lambdatest.appium.sample.pages.HomePage;
    import com.lambdatest.appium.sample.utils.Swipe;
    import io.appium.java_client.MobileElement;
    import io.appium.java_client.Setting;
    import io.appium.java_client.android.AndroidDriver;
    import org.openqa.selenium.support.ui.ExpectedConditions;
    import org.openqa.selenium.support.ui.WebDriverWait;
    import org.testng.annotations.BeforeTest;
    import org.testng.annotations.Parameters;
    import org.testng.annotations.Test;

    // 1.
    public class AndroidTest extends BaseTest<AndroidDriver<MobileElement>> {
       private static final Platform PLATFORM = ANDROID;
       private              HomePage homePage;

       // 2.
       @Parameters ({ "environment", "deviceName", "version", "app", "isAutomatic" })
       @BeforeTest
       public void setupDriver (final Environment environment, final String deviceName, final String version,
           final String app, final boolean isAutomatic) throws MalformedURLException {
           this.homePage = new HomePage ();
           startServer (environment, isAutomatic);
           this.driver = new AndroidDriver<> (getUrl (environment),
               getOptions (environment, "Android", deviceName, version, app));
           this.driver.setSetting (Setting.IGNORE_UNIMPORTANT_VIEWS, true);
           this.wait = new WebDriverWait (this.driver, 10);
           this.swipe = new Swipe<> (this.driver);
       }

       // 3.
       @Test
       public void testNotifications () {
           this.wait.until (ExpectedConditions.elementToBeClickable (this.homePage.notificationButton ()
                   .get (PLATFORM)))
               .click ();
           this.driver.openNotifications ();
           assertEquals (this.wait.until (ExpectedConditions.visibilityOfElementLocated (
                   this.homePage.proverbialNotification ()
                       .get (PLATFORM)))
               .getText (), "Test Notification");
           this.driver.navigate ()
               .back ();
       }

       // 4.
       @Test
       public void testTextButton () {
           this.wait.until (ExpectedConditions.elementToBeClickable (this.homePage.textButton ()
                   .get (PLATFORM)))
               .click ();
           assertEquals (this.wait.until (ExpectedConditions.visibilityOfElementLocated (this.homePage.message ()
                   .get (PLATFORM)))
               .getText (), "Proverbial");
       }
    }
Enter fullscreen mode Exit fullscreen mode

In this test, we are:

1- Extending our BaseTest class and specifying the generic type of the class as AndroidDriver.

2- Next, we have the setupDriver method, which takes parameters from testng.xml, which decides:

a. In which environment will we be running our tests, on cloud or local.

b. The name of the device.

c. The version of the platform

d. App path, local file path in the case of local execution, or app URL if running on the cloud.

e. If we run locally, the isAutomatic parameter will tell if we want to start the server internally in our test or already have a server running on our external terminal.

f. We are also updating the Appium setting to set IGNORE_UNIMPORTANT_VIEWS which will reduce unnecessary elements from the overall elements hierarchy tree.

3- testNotifications test method will test our first test scenario where the user clicks on the notification button and sees a notification pop-up on the panel. We will also assert the notification text to ensure our test passes. Here we use the HomePage class to get the platform-specific locators.

4- testTextButton test method will verify if the Proverbial text is displayed when the user clicks on the text button. We also use the HomePage class to get the platform-specific locators.

iOS test

Similar to the Android test, we have another class for the iOS test that also uses the same page object class used in the Android test. Let’s see what this class looks like.

   package com.lambdatest.appium.sample.ios;

    import static com.lambdatest.appium.sample.enums.Platform.IOS;
    import static org.openqa.selenium.support.ui.ExpectedConditions.elementToBeClickable;
    import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfElementLocated;

    import java.net.MalformedURLException;

    import com.lambdatest.appium.sample.BaseTest;
    import com.lambdatest.appium.sample.enums.Environment;
    import com.lambdatest.appium.sample.enums.Platform;
    import com.lambdatest.appium.sample.pages.HomePage;
    import com.lambdatest.appium.sample.utils.Swipe;
    import io.appium.java_client.MobileElement;
    import io.appium.java_client.ios.IOSDriver;
    import org.openqa.selenium.support.ui.WebDriverWait;
    import org.testng.Assert;
    import org.testng.annotations.BeforeTest;
    import org.testng.annotations.Parameters;
    import org.testng.annotations.Test;

    // 1.
    public class IOSTest extends BaseTest<IOSDriver<MobileElement>> {
       private static final Platform PLATFORM = IOS;
       private              HomePage homePage;

       // 2.
       @Parameters ({ "environment", "deviceName", "version", "app" })
       @BeforeTest
       public void setupDriver (final Environment environment, final String deviceName, final String version,
           final String app) throws MalformedURLException {
           this.homePage = new HomePage ();
           this.driver = new IOSDriver<> (getUrl (environment), getOptions (environment, "iOS", deviceName, version, app));
           this.wait = new WebDriverWait (this.driver, 10);
           this.swipe = new Swipe<> (this.driver);
       }

       // 3.
       @Test
       public void testNotifications () {
           this.wait.until (elementToBeClickable (this.homePage.notificationButton ()
                   .get (PLATFORM)))
               .click ();
           this.swipe.down ();
           Assert.assertTrue (this.wait.until (visibilityOfElementLocated (this.homePage.proverbialNotification ()
                   .get (PLATFORM)))
               .getText ()
               .contains ("Test Notification, Please enjoy this notification"));
           this.swipe.up ();
       }

       // 4.
       @Test
       public void testTextButton () {
           this.wait.until (elementToBeClickable (this.homePage.textButton ()
                   .get (PLATFORM)))
               .click ();
           Assert.assertEquals (this.wait.until (visibilityOfElementLocated (this.homePage.message ()
                   .get (PLATFORM)))
               .getText (), "Proverbial");
       }
    }

Enter fullscreen mode Exit fullscreen mode

In this test, we are,

1- Extending our BaseTest class and specifying the generic type of the class as IOSDriver.

2- Next, we have the setupDriver method, which takes parameters from testng.xml, which decides:

a. In which environment will we be running our tests, on cloud or local.
b. The name of the device.
c. The version of the platform.
d. App path, local file path in the case of local execution, or app URL if running on the cloud.

3- testNotifications test method is almost identical, except that the openNotification method does not work here. Instead, we will use the Swipe util class to swipe down and open the notification panel. After verification, we will swipe up to close the notification panel.

4- testTextButton test method is the same as the Android test.

TestNG XML file

We now have all the test classes in place. Now it’s time to execute the tests for which we will create a testng.xml file in the project’s root folder. Let’s see how this file content will look.

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
    <!-- 1. -->
    <suite name="Mobile Suite" verbose="2">
       <!-- 2. -->
       <test name="Test iOS App on LambdaTest">
           <parameter name="environment" value="CLOUD"/>
           <parameter name="deviceName" value="iPhone 13 Pro"/>
           <parameter name="version" value="15"/>
           <parameter name="app" value="LT_APP_IOS"/>
           <parameter name="isAutomatic" value="false"/>
           <classes>
               <class name="com.lambdatest.appium.sample.ios.IOSTest"/>
           </classes>
       </test>
       <!-- 3. -->
       <test name="Test Android App on LambdaTest">
           <parameter name="environment" value="CLOUD"/>
           <parameter name="deviceName" value="Galaxy S10"/>
           <parameter name="version" value="10"/>
           <parameter name="app" value="LT_APP_ANDROID"/>
           <parameter name="isAutomatic" value="false"/>
           <classes>
               <class name="com.lambdatest.appium.sample.android.AndroidTest"/>
           </classes>
       </test>
       <!-- 4. -->
       <test name="Test Android App on Local">
           <parameter name="environment" value="LOCAL"/>
           <parameter name="deviceName" value="Pixel_5"/>
           <parameter name="version" value="10"/>
           <parameter name="app" value="/src/test/resources/apps/proverbial_android.apk"/>
           <parameter name="isAutomatic" value="true"/>
           <classes>
               <class name="com.lambdatest.appium.sample.android.AndroidTest"/>
           </classes>
       </test>
    </suite>

Enter fullscreen mode Exit fullscreen mode

1- We declared the test suite for our tests.

2- The first test is for iOS running on the LambdaTest cloud server.

3- The second test is for Android running on the LambdaTest cloud server.

4- The last test is for Android running locally where the Appium server will be started from our tests automatically and later the same will be closed once the test run completes.

Checking Execution reports

To check the tests that were executed using Appium with TestNG on the LambdaTest cloud server:

1- Navigate on their dashboard to Real Device -> App Automation from the left navigation panel. You will see a screen similar to the following.

2- When you click on any test sessions, a screen similar to the following will be displayed.

Here you can see the recorded video, all the event logs, device logs, network logs, etc.

Run the tests

Since we have a couple of tests running on LambdaTest, we must first save our LambdaTest credentials in the environment variables LT_USERNAME having your username and LT_ACCESS_KEY having your access key.

1- To run the tests, you can directly right-click in your IDE on the testng.xml file and click Run.

2- Another option is to run the test from your terminal by executing the following command.

    > mvn clean install 

Enter fullscreen mode Exit fullscreen mode

This command will execute our testng.xml file because we have configured the Maven surefire plugin in our pom.xml earlier.

This command will execute our testng.xml file because we have configured the Maven surefire plugin in our pom.xml earlier.

How to upload an app to the cloud?

In this Appium with TestNG tutorial, we have already seen some tests run on the LambdaTest cloud server. But to run the test successfully, we must first upload our Android / iOS application on the LambdaTest cloud server. We must upload the .apk file for Android and the .ipa file for iOS. While uploading, you will require your user name and access key.

Following are the ways of uploading the application.

Upload using the cUrl command

One way is to execute the following cURL command on your terminal window on Mac OS and Linux.

For Android:

    curl -u "<your-user-name>:<your-access-key>" \
    --location --request POST 'https://manual-api.lambdatest.com/app/upload/realDevice' \
    --form 'name="Android_App"' \
    --form 'appFile=@"src/test/resources/apps/proverbial_android.apk"'

Enter fullscreen mode Exit fullscreen mode

For iOS:

   curl -u "<your-user-name>:<your-access-key>" -X POST "https://manual-api.lambdatest.com/app/upload/realDevice" -F "appFile=@"your/ios/app.ipa"" 
Enter fullscreen mode Exit fullscreen mode

In any of the above commands, you will get a JSON response where you can use app_url as your App capability. It will be in the format: lt://.

Upload from the LambdaTest dashboard

On the LambdaTest Dashboard, navigate to Real Device -> App Automation and click on the App Upload button towards the right of the screen.

After you have uploaded the file, you will get app_url on the screen as shown below:

Conclusion

In this extensive blog on automated app testing using Appium with TestNG, we learned many things. We learned how to set up our machine to automate mobile applications for Android and iOS and inspect elements on a local emulator and cloud devices.

We also learned about the different locator strategies, creating a single page object for Android and iOS, and leveraging Appium with TestNG to run our tests locally and on the cloud using test parameters. Finally, we configured the pom.xml file to run the tests using Maven commands.

Don’t forget to share your feedback on this Appium with TestNG tutorial. Also, share it with your network if you like it.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .