Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

In this tutorial, we will create a JUnit Test Case in Java, using the Appium library for automation of Android iOS applications.

Description

The following automated test is taken from the tutorials for iOS provided by Appium, in this case for Android


Info
titlePlease note

This example may be example is found in the public github repo Github repository in https://github.com/appium/tutorial/tree/master/projects/java_androidios. The repo  It also provides examples for other languages.

The Android emulator should be started with a compatible virtual device.

No Format
emulator @Nexus_5_API_25

Requirement

  • Appium must be running in the machine

...

  • with iOS SDK.
No Format
appium

We will make a simple update to the pom.xml file in order to generate a JUnit xml report.


 The class implementing the automated tests needs to be updated in order to properly set up the IP of the Appium server along with the required iOS version.


Code Block
languagejsjava
titlepomAppiumTest.xmljava
collapsetrue
<?xml version="1.0" encoding="UTF-8"?>
<!-- based on
https://github.com/appium/appium/blob/2fe7ea7b098ba2145e3c7b4cc31276a3e26921ec/sample-code/examples/java/junit/pom.xml
-->
<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>appium</groupId>
  <artifactId>tutorial_android</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>appium_tutorial_android</name>
  <description>JUnit Android examples</description>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>io.appium</groupId>
      <artifactId>java-client</artifactId>
      <version>2.0.0</version>
    </dependency>
    <dependency>
      <!-- Must use 1.0.15 or better otherwise upload file will have cookie warnings
      https://github.com/saucelabs/saucerest-java/commit/99bce5b108354ad086ac31e06c1e3ab092000490
      -->
      <groupId>com.saucelabs</groupId>
      <artifactId>saucerest</artifactId>
      <version>1.0.16</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.googlecode.json-simple</groupId>
      <artifactId>json-simple</artifactId>
      <version>1.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.6</version>
      <scope>test</scope>
    </dependency>
    <!--
     Sauce JUnit is contained in saucelabs/sauce-java
     https://github.com/saucelabs/sauce-java/tree/master/junit
    -->
    <dependency>
      <groupId>com.saucelabs</groupId>
      <artifactId>sauce_junit</artifactId>
      <version>2.1.3</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.2.4</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.17</version>
      </plugin>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>package appium.tutorial.android.util;

import appium.tutorial.android.page.HomePage;
import com.saucelabs.common.SauceOnDemandAuthentication;
import com.saucelabs.common.SauceOnDemandSessionIdProvider;
import com.saucelabs.junit.SauceOnDemandTestWatcher;
import com.saucelabs.saucerest.SauceREST;
import io.appium.java_client.android.AndroidDriver;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.DesiredCapabilities;

import java.io.File;
import java.net.URL;
import java.nio.file.Paths;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import static appium.tutorial.android.util.Helpers.driver;

public class AppiumTest implements SauceOnDemandSessionIdProvider {

    static {
        // Disable annoying cookie warnings.
        // WARNING: Invalid cookie header
        LogFactory.getFactory().setAttribute("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog");
    }

    /** Page object references. Allows using 'home' instead of 'HomePage' **/
    protected HomePage home;

    /** wait wraps Helpers.wait **/
    public static WebElement wait(By locator) {
        return Helpers.wait(locator);
    }

    private boolean runOnSauce = System.getProperty("sauce") != null;

    /** Authenticate to Sauce with environment variables SAUCE_USER_NAME and SAUCE_API_KEY **/
    private SauceOnDemandAuthentication auth = new SauceOnDemandAuthentication();

    /** Report pass/fail to Sauce Labs **/
    // false to silence Sauce connect messages.
    public @Rule
    SauceOnDemandTestWatcher reportToSauce = new SauceOnDemandTestWatcher(this, auth, false);

    @Rule
    public TestRule printTests = new TestWatcher() {
        protected void starting(Description description) {
            System.out.print("  test: " + description.getMethodName());
        }

        protected void finished(Description description) {
            final String session = getSessionId();

            if (session != null) {
                System.out.println(" " + "https://saucelabs.com/tests/" + session);
            } else {
        <configuration>
        System.out.println();
  <source>1.5</source>
          <target>1.5</target>}
        </configuration>}
    };

     <version>3.1</version>
      </plugin>

    </plugins>
  </build>

  <repositories>
    <repository>
      <id>saucelabs-repository</id>
      <url>https://repository-saucelabs.forge.cloudbees.com/release</url>
      <releases>
        <enabled>true</enabled>private String sessionId;

    /** Keep the same date prefix to identify job sets. **/
    private static Date date = new Date();

    /** Run before each test **/
    @Before
    public void setUp() throws Exception {
      </releases>
  DesiredCapabilities capabilities = new <snapshots>DesiredCapabilities();
        <enabled>true</enabled>
      </snapshots>capabilities.setCapability("appium-version", "1.1.0");
    </repository>
  </repositories>

    <reporting>capabilities.setCapability("platformName", "Android");
        <plugins>capabilities.setCapability("deviceName", "Android");
            <plugin>capabilities.setCapability("platformVersion", "7.1");

        // Set job name on    <groupId>org.apache.maven.plugins</groupId>Sauce Labs
        capabilities.setCapability("name", "Java Android tutorial "    <artifactId>maven-surefire-report-plugin</artifactId>+ date);
        String userDir =  </plugin>
System.getProperty("user.dir");

        URL </plugins>
serverAddress;
         </reporting>

</project>

The class implementing the automated tests need to be updated also in order to properly setup the IP of the Appium server along with the required Android version.

Code Block
languagejava
titleAppiumTest.java
collapsetrue
package appium.tutorial.android.util;

import appium.tutorial.android.page.HomePage;
import com.saucelabs.common.SauceOnDemandAuthentication;
import com.saucelabs.common.SauceOnDemandSessionIdProvider;
import com.saucelabs.junit.SauceOnDemandTestWatcher;
import com.saucelabs.saucerest.SauceREST;
import io.appium.java_client.android.AndroidDriver;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.DesiredCapabilities;

import java.io.File;
import java.net.URL;
import java.nio.file.Paths;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import static appium.tutorial.android.util.Helpers.driver;

public class AppiumTest implements SauceOnDemandSessionIdProvider {

    static {
        // Disable annoying cookie warnings.
        // WARNING: Invalid cookie header
        LogFactory.getFactory().setAttribute("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog");
    }

    /** Page object references. Allows using 'home' instead of 'HomePage' **/
    protected HomePage home;

    /** wait wraps Helpers.wait **/
    public static WebElement wait(By locator) {
String localApp = "api.apk";
        if (runOnSauce) {
            String user = auth.getUsername();
            String key = auth.getAccessKey();

            // Upload app to Sauce Labs
            SauceREST rest = new SauceREST(user, key);

            rest.uploadFile(new File(userDir, localApp), localApp);

            capabilities.setCapability("app", "sauce-storage:" + localApp);
            serverAddress = new URL("http://" + user + ":" + key + "@ondemand.saucelabs.com:80/wd/hub");
            driver = new AndroidDriver(serverAddress, capabilities);
        } else {
            String appPath = Paths.get(userDir, localApp).toAbsolutePath().toString();
           return Helperscapabilities.wait(locatorsetCapability("app", appPath);
    }

    private   boolean runOnSauceserverAddress = new System.getProperty("sauce") != null;

URL("http://127.0.0.1:4723/wd/hub");
    /** Authenticate to Sauce with environment variables SAUCE_USER_NAME and SAUCE_API_KEY **/
    private SauceOnDemandAuthentication auth = new SauceOnDemandAuthentication();driver = new AndroidDriver(serverAddress, capabilities);
        }

    /** Report pass/fail to SaucesessionId Labs **/= driver.getSessionId().toString();

    // false to silence Sauce connect messages. driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
    public @Rule
    SauceOnDemandTestWatcher reportToSauce = new SauceOnDemandTestWatcher(this, auth, falseHelpers.init(driver, serverAddress);

    @Rule}

    public/** TestRuleRun printTestsafter =each new TestWatcher() {test **/
    @After
    protectedpublic void startingtearDown(Description description)) throws Exception {
        if (driver != null) System.out.print("  test: " + description.getMethodName())driver.quit();
        }

    /** If we're not protectedon voidSauce finished(Description description) {
     then return null otherwise SauceOnDemandTestWatcher will error. **/
    public   final String session = getSessionId();
 {
        return runOnSauce ?  if (session != null) {sessionId : null;
    }
}            System.out.println(" " + "https://saucelabs.com/tests/" + session);
            } else {
                System.out.println();


The sample project contains two classes with the automated Tests. Below is one of them: 

Code Block
languagejava
titleAutomatingASimpleActionTest.java
package appium.tutorial.ios;

import appium.tutorial.ios.util.AppiumTest;
import org.openqa.selenium.WebElement;

import java.util.ArrayList;
import java.util.List;

import static appium.tutorial.ios.util.Helpers.*;

public class AutomatingASimpleActionTest extends AppiumTest {

    @org.junit.Test
    public void one() throws Exception {
        text("Various uses   }of UIButton").click();
        }text_exact("Buttons");
    };

    private String sessionId;

@org.junit.Test
    /**public Keepvoid thetwo() samethrows dateException prefix{
 to identify job sets. **/
    private static Date date = new Datewait(for_text("Various uses of UIButton")).click();

     /** Run before each test **/   wait(for_text_exact("Buttons"));
    }

    @Before@org.junit.Test
    public void setUpthree() throws Exception {
        DesiredCapabilitiesWebElement capabilitiescell_1 = new DesiredCapabilities(wait(for_text(2));
         capabilities.setCapability("appium-version", "1.1.0");String page_title = cell_1.getAttribute("name").split(",")[0];

        capabilitiescell_1.setCapability("platformName", "Android"click();
        capabilities.setCapability("deviceName", "Android"wait(for_text_exact(page_title));
    }

    capabilities.setCapability("platformVersion", "7.1");

   @org.junit.Test
    public void four() throws Exception {
     //  Set jobList<String> namecell_names on= Sauce Labsnew ArrayList<String>();

        capabilities.setCapability("name", "Java Android tutorial " + date);for (WebElement cell : tags("TableCell")) {
        String  userDir = System.getProperty("user.dir");

 cell_names.add(cell.getAttribute("name"));
        URL serverAddress;}

        for (String name localApp = "api.apk";
: cell_names) {
           if (runOnSauce) {wait(for_text_exact(name)).click();
            String user = auth.getUsername()wait(for_text_exact(name.split(",")[0]));
            String key = auth.getAccessKeyback();

        }
    }
}


Actually, the above class and others, such as Helpers.java, had to be changed due to updates introduced by Apple in the automation API with XCUITest.

Code Block
languagejava
titleHelpers.java
collapsetrue
package appium.tutorial.ios.util;

import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileElement;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.util.ArrayList;
import java.util.List;

public abstract class Helpers {

  private static AppiumDriver driver;
  private static WebDriverWait driverWait;

  /**
   * Initialize the webdriver. Must be called before using any helper methods. *
   */
  public static void init(AppiumDriver webDriver)// Upload app to Sauce Labs
            SauceREST rest = new SauceREST(user, key);

            rest.uploadFile(new File(userDir, localApp), localApp);

            capabilities.setCapability("app", "sauce-storage:" + localApp);
            serverAddress = new URL("http://" + user + ":" + key + "@ondemand.saucelabs.com:80/wd/hub");
            driver = new AndroidDriver(serverAddress, capabilities);
        } else {
    driver = webDriver;
    int  StringtimeoutInSeconds appPath = Paths.get(userDir, localApp).toAbsolutePath().toString()60;
    // must wait at least 60 seconds for capabilities.setCapability("app", appPath);running on Sauce.
    // waiting for 30 seconds works locally however serverAddressit =fails new URL("http://127.0.0.1:4723/wd/hub");on Sauce.
    driverWait = new WebDriverWait(webDriver, timeoutInSeconds);
  }

  /**
   driver* Wrap =WebElement new AndroidDriver(serverAddress, capabilities);in MobileElement *
   */
  private static  }

  MobileElement w(WebElement element) {
    return new sessionId =MobileElement((RemoteWebElement) element, driver.getSessionId().toString();

  }

  /**
    driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);* Wrap WebElement in MobileElement *
   */
  private static List<MobileElement> Helpers.init(driver, serverAddress);w(List<WebElement> elements) {
    }

List list =  /** Run after each test **/
    @Afternew ArrayList(elements.size());
    for (WebElement element : elements) {
    public void tearDownlist.add(w() throws Exception {element));
    }

    if (driver != null) driver.quit();
  return list;
  }

    /**
 If we're not* onReturn Saucean thenelement returnby null otherwise SauceOnDemandTestWatcher will error.locator *
   **/
  public static publicMobileElement String getSessionId(element(By locator) {
    return w(driver.findElement(locator));
   return runOnSauce ? sessionId : null;
    }
} 

The sample project contains two classes with the automated Tests. Below you can find one, as an example.

Code Block
languagejava
titleAutomatingASimpleActionTest.java
package appium.tutorial.android;

import appium.tutorial.android.util.AppiumTest;
import org.openqa.selenium.WebElement;

import java.util.ArrayList;
import java.util.List;

import static appium.tutorial.android.util.Helpers.*;

public class AutomatingASimpleActionTest extends AppiumTest {

    @org.junit.Test
    public void one() throws Exception {
        text("Accessibility").click();}

  /**
   * Return a list of elements by locator *
   */
  public static List<MobileElement> elements(By locator) {
    return w(driver.findElements(locator));
  }

  /**
   * Press the back button *
   */
  public static void back() {
    driver.navigate().back();
  }

  /**
   * Return a list of elements by tag name *
   */
  public static List<MobileElement> tags(String tagName) {
    return elements(for_tags(tagName));
  }

  /**
   * Return a tag name locator *
   */
  public static By for_tags(String tagName) {
    return By.className(tagName);
  }

  /**
   * Return a static text element by xpath index *
   */
  public static MobileElement text(int xpathIndex) {
    return element(for_text(xpathIndex));
  }

  /**
   * Return a static text locator by xpath index *
   */
  public static By for_text(int xpathIndex) {
    //return By.xpath("//UIAStaticText[" + xpathIndex + "]");
    return By.xpath("//XCUIElementTypeStaticText[" + xpathIndex + "]");
  }

  /**
   * Return a static text element that contains text *
   */
  public static MobileElement text(String text) {
    return element(for_text(text));
  }

  /**
   * Return a static text locator that contains text *
   */
  public static By for_text(String text) {
    String up = text.toUpperCase();
    String down = text.toLowerCase();

    //return By.xpath("//UIAStaticText[@visible=\"true\" and (contains(translate(@name,\"" + up
    return By.xpath("//XCUIElementTypeStaticText[@visible=\"true\" and (contains(translate(@name,\"" + up
        + "\",\"" + down + "\"), \"" + down + "\") or contains(translate(@hint,\"" + up
        text_exact("Accessibility Node Provider");
    }

    @org.junit.Test+ "\",\"" + down + "\"), \"" + down + "\") or contains(translate(@label,\"" + up
    public void two() throws Exception {
        wait(for_text("Accessibility")).click();
        wait(for_text_exact("Accessibility Node Provider"));+ "\",\"" + down + "\"), \"" + down + "\") or contains(translate(@value,\"" + up
    }

    @org.junit.Test
    public void three() throws Exception {
        wait(for_text(2)).click(+ "\",\"" + down + "\"), \"" + down + "\"))]");
  }

  /**
   * Return  find("Custom Evaluator");
    }

  @org.junit.Test
    public void four() throws Exceptiona static text element by exact text *
   */
  public static MobileElement text_exact(String text) {
        setWait(0return element(for_text_exact(text));
  }

  /**
   * Return a List<String> cell_names = new ArrayList<String>();

static text locator by exact text *
   */
  public static By for (WebElement cell : tags("android.widget.TextView")_text_exact(String text) {

    //XCUIElementTypeStaticText
        cell_names.add(cell.getAttribute("name"));
        }
return By.xpath("//XCUIElementTypeStaticText[@visible=\"true\" and (@name=\"" + text
        // delete title cell
//return By.xpath("//UIAStaticText[@visible=\"true\" and (@name=\"" + text
         cell_names.remove(0);

        for (String cell_name : cell_names) {
+ "\" or @hint=\"" + text + "\" or @label=\"" + text
        + "\" or @value=\"" + text  scroll_to_exact(cell_name).click(+ "\")]");
  }

  /**
   * Wait 30  seconds waitInvisible(for_text_exact(cell_name));
            back();
        locator to find an element *
   */
  public static MobileElement wait(By locator) {
    return wait(for_find("Accessibility"w(driverWait.until(ExpectedConditions.visibilityOfElementLocated(locator)));
  }

  /**
   * Wait 60  seconds wait(for_find("Animation"));
        }

        setWait(30); // restore old implicit wait
     locator to find all elements *
   */
  public static List<MobileElement> waitAll(By locator) {
    return w(driverWait.until(ExpectedConditions.visibilityOfAllElementsLocatedBy(locator)));
  }
}



Tests can be run by using Maven.

No Format
mvn clean test

...

Since the previous command generates multiple JUnit XML files, we may need to merge them in into a single XML file so it can be submitted into a Test Execution more easily. That can be achieved by using the junit-merge utility, for example.

No Format
junit-merge  -o results.xml -d target/surefire-reports/

After successfully running successfuly the Test cases and generating the aggregated JUnit XML report (e.g., results.xml), it can be imported to Xray (either by the REST API or through "the Import Execution Results" action within the Test Execution).

Each JUnit's Test Case is mapped to a Generic Test in JIRAJira, and the "Generic Test Definition" field contains the name of the package, the class and the method name that implements the Test Case. The summary of each Test issue is filled out from with the name of the method corresponding to the JUnit Test.

Image RemovedImage Added


The Execution Details of the Generic Test contains information about the Test Suite, which in this case corresponds to the Test Case class, including its namespace. 

Image RemovedImage Added

References

...