An overview of the

In order to avoid repeated artificial regression tests before each launch and ensure that each launch version will not cause instability of the core business, automated testing is urgently needed to ensure the stability of the business. After research, I tried to use Appium for automated testing, because it is powerful, cross-platform and has an active community.

Comparison of mainstream Frameworks

Appium advantages

  • Open source
  • Cross-architecture :Native App, Hybird App, Web App
  • Cross-device :Android, iOS, and Firefox OS
  • Independent of source code
  • Use any WebDriver compatible language to write test cases. Java, Objective-C, JavaScript with Node.js (in both callback and yield-based flavours), PHP, Python, Ruby, C#, Clojure, or Perl.
  • You don’t need to recompile your APP

For those who are not aware of WebDriver, this will be explained in the Appium architecture introduction.

Appium concept

  1. You don’t have to recompile or modify your application for automation.
  2. You don’t have to be limited to a language or framework to write and run test scripts.
  3. A mobile automation framework should not duplicate wheels on interfaces. (Mobile automation interface should be unified)
  4. It must be open source in spirit and name.

Appium architecture

IOS: Apple UIAutomation Android 4.2+: Google UiAutomator Android 2.3+: Google’s Instrumentation. (supported by a separate project Selendroid)

Appium 1.6 + has been addedUiAutomator2

To meet the cross-platform requirements above, these tripartite frameworks are packaged into a set of apis — the WebDriver API (client-to-server protocol)

In fact, WebDriver has become a standard for Web browser automation and a W3C standard – W3C Working Draft, so Appium extends the mobile automation related API on the original basis.

Investing in WebDriver means you can bet on an independent, free and open protocol that has become standard. You are not limited by any patent.

Core architecture: Appium uses C/S architecture. At runtime, the Service listens for commands sent by the Client, executes these commands on the mobile device, and returns the execution results to the Client in an HTTP response.

What can be done based on this architecture?

  • You can write the test code in any language that implements the client
  • You can put the server on a different machine
  • You can just write the test code and use a cloud service like Saucelabs to interpret the commands.

The following diagram illustrates the specific role of cloud services:

Appium use

The service side

  • Install the Appium server

      npm install -g appium
      npm install -g appium-doctor
      appium-doctor
    Copy the code

Among them, appium-doctor is used to check whether the computer lacks relevant dependencies. When all check boxes are checked, the Appium environment is configured as follows:

  • Start appium server:

    Appium --address 127.0.0.1 --port 4723 --log "/Users/mio4kon/Desktop/ appium.log" --log-timestamp --local-timezone --session-overrideCopy the code

The client

Again, Appium supports a variety of languages, but I chose JAVA. If JAVA syntax is not concise or familiar, use a language you are familiar with.

Create MAVEN/Gradle project:

Create the project and add the following dependencies:

        <dependency>
            <groupId>io.appium</groupId>
            <artifactId>java-client</artifactId>
            <version>5.0.0 beta 2 -</version>
            <exclusions>
                <exclusion>
                    <groupId>org.seleniumhq.selenium</groupId>
                    <artifactId>selenium-java</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>3.0.1</version>
        </dependency>
Copy the code

The dependency on the Appium client is introduced successfully.

Capabiltiy configuration:

The setUp method is defined in the base class of each Test and other initialization operations are set before Capabiltiy:

DesiredCapabilities capabilities = new DesiredCapabilities ();
capabilities.setCapability (MobileCapabilityType.DEVICE_NAME, deviceName);
capabilities.setCapability (MobileCapabilityType.PLATFORM_NAME, platformName);
capabilities.setCapability (MobileCapabilityType.PLATFORM_VERSION, platformVersion);
capabilities.setCapability (MobileCapabilityType.APP, apkPath);
capabilities.setCapability (AndroidMobileCapabilityType.APP_PACKAGE, appPackage);
capabilities.setCapability (AndroidMobileCapabilityType.APP_ACTIVITY, appActivity);
capabilities.setCapability (MobileCapabilityType.AUTOMATION_NAME, AutomationName.ANDROID_UIAUTOMATOR2);

Copy the code

Here I’m using the Parameters annotation in TestNG to configure the Parameters.

What the hell is TestNG? TestNG is a Java testing framework, similar to JUnit but more powerful and easy to use.

TestNG

Prepare and close with some comments from TestNg.

I used the BeforeClass and Parameters annotations for the setUp method above.

    @BeforeClass
    @Parameters({"driverName"."url"."deviceName"."platformName"."platformVersion"."apkPath"."appPackage"."appActivity"})
    public void setUp(String driverName, String url, String deviceName, String platformName, String platformVersion, String apkPath, String appPackage, String appActivity) throws Exception {
        log.i (TAG, "BeforeClass");
        driver = setRemoteDriver (driverName, url, deviceName, platformName, platformVersion, apkPath, appPackage, appActivity);
        actions = ElementActions.getInstance ().init (driver);
        assertActions = actions.getAssertActions ();
        Screenshot.getInstance ().init (driver);
        prepare ();
    }
Copy the code

Also AfterClass, driver exits.

Normally we write test cases like this:

A TestClass contains multiple testMethods. If each TestMethod is independent of the other, rerunning the APP would be time-consuming, so I’m going to make each TestClass independent of each other, and the testMethods in it depend on each other, in order of execution by X of TestNG ML to control. As follows:

   <test name="XX test">
        <classes>
            <class name="XXTest">
                <methods>
                    <include name="testAAA"/>
                    <include name="testBBB"/>
                    <include name="testCCC"/>
                </methods>
            </class>
        </classes>
    </test>
Copy the code

However, in some cases AfterMethod annotations can be used if TestMethod is also independent of each other.

    @AfterMethod
    public void afterMethod(a) {
        driver.resetApp ();
    }
Copy the code

Write a use case

Look for the element

Positioning way

Elements can be found in a number of ways (you can get the page ID,name, etc., via UIAutomatorViewer):

  • id
  • name
  • className
  • xpath
  • uiautomator

Senior position

  • Find the login button with xpath:

      by.xpath ("//button[@name='login']")
    Copy the code

Xpath Example Tutorial

  • withuiautomatorAPI scroll to find:
	String rule = "new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().textContains(\"" + locator.value + "\")"
	WebElement cl = driver.findElementByAndroidUIAutomator(rule));
Copy the code

It’s just like if I write it in uiAutomator:

new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().textContains(value));
Copy the code

uiautomator API

### Element managementCopy the code

In order to make test cases easy to reuse and simplify the time to write use cases, necessary encapsulation is necessary. The Page Object pattern is used to encapsulate elements on the Page. Then all tests need to do is simply control the page element.

  • You can use yamL files to manage page elements:

You then wrap yamL parsing into Locator objects in BasePage. In this way, all PageObjects can use the following method to locate the element.

protected Locator getLocator(String locatorName) {
        checkNotNull (locatorName);
        Page page = getPage ();
        List<Locator> locators = page.locators;
        for(Locator locator : locators) {
            if (locatorName.equals (locator.name)) {
                returnlocator; }}return null;
    }
    
publicLocator Enter the mobile phone number = getLocator ("Please enter your mobile phone number.");
publicLocator Enter verification code = getLocator ("Please enter the verification code.");
publicLocator Send verification code = getLocator ("Send verification code");

Copy the code

TODO: Using the tool to generate the corresponding Page class could have been done by using the IDE to indicate which controls were available, but writing the above code was a boring operation, so I used the Freemarker to automatically parse YAML to generate the corresponding Page class, as follows:

   		Map<String, Object> input = new HashMap<String, Object> ();
        input.put ("pageName", page.pageName);
        List<LocatorObject> genLocators = new ArrayList<> ();
        List<Locator> locators = page.locators;
        for(int i = 0; i < locators.size (); i++) {
            System.out.println ("To generate a Locator." + locators.get (i).name);
            genLocators.add (new LocatorObject (locators.get (i).name, locators.get (i).name));
        }
        input.put ("locators", genLocators);

        Template template = cfg.getTemplate ("page-temp.ftl");

        Writer fileWriter = new FileWriter (new File (path, page.pageName + ".java"));
        try {
            template.process (input, fileWriter);
        } finally {
            fileWriter.close ();
        }
Copy the code

If you can’t use Freemarker, do a search.

Element interaction

Encapsulate interaction events in ElementActions. It’s very simple to use:

Actions. Text (loginPage. Please enter the phone number, phone); Actions.click (loginPage. Send verification code); Actions. text (loginPage. Please enter the verification code, PWD); Actions.click (loginPage. Login);Copy the code

Common interaction events: click, combos, scroll up, down, left, right, backward, enter text, and so on.

The element location above is really just wrapping a Locator, there is no actual lookup element, the lookup element is actually handled in ElementActions. Because applications often handle network requests, controls sometimes take a while to become visible, so you need to give them some time to find.

        WebElement element;
        try {
            element = (new WebDriverWait (mDriver, locator.timeOutInSeconds)).until (
                    new ExpectedCondition<WebElement> () {

                        @Override
                        public WebElement apply(WebDriver driver) {
                            List<WebElement> elements = getElement (locator);
                            if(elements.size () ! =0) {
                                return elements.get (0);
                            }
                            return null; }}); }catch (NoSuchElementException | TimeoutException e) {
            log.e (TAG, [%1$s], [By.%2$s: %3$s]", locator.name, locator.type, locator.value, locator.timeOutInSeconds);
            throw e;
        }
Copy the code

assertions

Also for ease of use are common assertion encapsulation, such as Toast validation.

    public void validatesToast(final String msg) {
        checkNotNull (msg);
        final WebDriverWait wait = new WebDriverWait (mDriver, 10);
        assertNotNull (wait.until (ExpectedConditions
                                           .presenceOfElementLocated (By.xpath (String.format ("//*[@text=\'%s\']", msg)))));
    }

Copy the code

The test report

The test report can intuitively display the success rate of the test, screenshots and other information. Here, extentReports is selected as the framework for test reports.

End result:

Hit the pit

  • findElementByNameIs invalid.

Searching by name was deprecated over a year ago and removed from 1.5. In general, searching by accessibility id is better for a variety of reasons.

The above findElementByName method was removed from Appium 1.5, but the API can’t find it and it’s not out of date. The following code was later used to solve the problem of finding elements by name.

 String query = "new UiSelector().textContains" + "(\" " + locator.value + "\")";
 webElements = mDriver.findElementsByAndroidUIAutomator (query);
Copy the code
  • It is said thatAppium 1.6.3We’re ready to look up Toast information. Then fart pidianpidian ran to try the example on the Internet found that it did not work. I thought it was the Client version. It took a long time to find the following code needs to be added:
capabilities.setCapability (MobileCapabilityType.AUTOMATION_NAME, AutomationName.ANDROID_UIAUTOMATOR2);
Copy the code

I feel the current Appium documentation is incomplete and a bit messy

  • I accidentally found an error when autonamap played Toast during test. Then directly compile and install without saving. Guess if Appium changed something during installation. Look at the Service log and re-sign it at installation time…
App not signed with debug cert.
2017-02-13 18:17:19:848 - info: [debug] [ADB] Resigning apk.
2017-02-13 18:17:23:938 - info: [debug] [ADB] Zip-aligning 'app-debug.apk'2017-02-13 18:17:23:958 - info: [ADB] Checking whether zipalign is present 2017-02-13 18:17:23:964 - info: (ADB) Using the zipalign from/Users/mio4kon/Library/Android/SDK/build - the tools / 25.0.2 / zipalign 18:17:23 2017-02-13:968 - the info:  [debug] [ADB] Zip-aligning apk. 2017-02-13 18:17:24:104 - info: [AndroidDriver] Remote apk path is /data/local/tmp/463eb03788048b4a1dacfe28545ee76e.apk
Copy the code

Solutions:

capabilities.setCapability (AndroidMobileCapabilityType.NO_SIGN, true);