📜 ⬆️ ⬇️

Automatic testing of iOS applications

image
It happens, there comes a time when you need to monitor whether the interface of the mobile application has collapsed once again. To solve this problem, automatic tests are used. For web pages it is considered common practice to use the Selenium Web driver, so for mobile applications I was looking for similar things. And, fortunately, there were a lot of such, they use Selenium WebDriver JSON Wire Protocol .

This article will focus on Appium . I didn’t specifically check if the same driver is used in all such products. But I chose Appium because they clearly indicated on the main page support for all popular languages. Here is what they boast about:
  1. You do not have to recompile your application or modify it; this is achieved by using the standard automation API on all platforms.
  2. You can write your tests with your favorite development tools using any WebDriver-compatible language, such as Java, Objective-C, JavaScript with Node.js (both callback and yield-based methods), PHP, Python, Ruby, C #, Clojure, or Perl with the Selenium WebDriver API and specific client libraries.
  3. You can use any testing framework.

We will need

In fact, he still knows Android and FirefoxOS. The article describes only iOS, but when the platform changes, the code will not change much. Start by installing the Appium server. You can download the application from here or install via npm. Add "sudo" if required.

$ npm install -g appium $ appium & 

You need to authorize the use of iOS simulator. If you start Appium with NPM, then do it by typing “sudo authorize_ios” (authorize_ios is a binary file from the Appium npm package). If you run Appium from source, then use the "sudo grunt authorize" command. Or do it through the Appium.app graphical user interface.

Let's start writing tests. I, for understandable reasons, will not give my working draft. I will describe a simple case:
  1. Button moved off
  2. Button in place
  3. Working button
  4. Button is broken

You can use Appium with robots, for example, Tapster . More about robots here and examples . But this article is not about them ..
')
The source code of the test and sample applications. There are 2 identical applications, one written in ObjectiveC, the second on Titanium. This is to make it clear that the way to create a UI does not matter if the native iOS elements are displayed as a result.

I will analyze the test example:

 var wd = require("wd") , assert = require("assert") ,Q = require("q") , appURL = __dirname+'/../program/Apps/Titanium/AppiumStudy.app'; // Instantiate a new browser session var browser = wd.promiseRemote("localhost", 4723); // See whats going on browser.on("status", function(info) { console.log('\x1b[36m%s\x1b[0m', info); }); browser.on("command", function(meth, path, data) { console.log(' > \x1b[33m%s\x1b[0m: %s', meth, path, data || ''); }); 

Connecting modules. wd are standard binding for Selenium web driver. In appURL, you can specify the path to the application or its archive, or its http address. Event handlers solely for displaying debug information, if you do not need it - you can safely delete it. Now the web driver is initialized and the test function is executed:

 browser .init({ device: "" , name: "Appium: with WD" , platform: "Mac" , app: appURL , version: "6.0" , browserName: "" , newCommandTimeout: 60 }) .then(function () { return browser.elementsByTagName('button'); }) .then(check_buttons) .fail(function (err) { console.log('fail', err) }) .fin(function () { browser.quit(); }) .done(); 

And the test itself:

 var check_buttons = function(els){ assert.equal(els.length, 4, 'Not enough buttons'); var check_first_buttons = function(els){ var deferred = Q.defer(); var check_alert = function(ValidButton){ var deferred = Q.defer(); browser.clickElement(ValidButton) .then(function(){ return browser.elementsByTagName('alert'); }) .then(function(alert){ assert.equal(alert.length, 1, "Alert not shoved"); return browser.acceptAlert(); }) .fail(function(err){ deferred.reject(new Error(err.message)); }) .fin(function(){ deferred.resolve(); }); return deferred.promise; } var check_invalid_position = function(InvalidButton){ var deferred = Q.defer(); InvalidButton.getLocation() .then(function(location){ assert.equal(location.x, 43, "InvalidButton location is not wrong"); }) .fail(function(err){ deferred.reject(new Error(err.message)); }) .fin(function(els){ deferred.resolve(); }); return deferred.promise; } var check_valid_position = function(ValidButton){ var deferred = Q.defer(); ValidButton.getLocation() .then(function(location){ assert.equal(location.x, 20, 'ValidButton location is wrong'); }) .fail(function(err){ deferred.reject(new Error(err.message)); }) .fin(function(els){ deferred.resolve(); }); return deferred.promise; } check_alert(els[0]) .then(function(){return check_invalid_position(els[1]);}) .then(function(){return check_valid_position(els[0]);}) .fin(function(){ deferred.resolve(els); }); return deferred.promise; } var check_work_button = function(work_button){ var deferred = Q.defer(); work_button.click() .then(function(){ return browser.waitForElementByTagName('text'); }) .then(function(){ return work_button.displayed(); }) .then(function(displayed){ assert.equal(displayed, false, "Work button still visible"); }) .then(function () { return browser.elementsByTagName('text'); }) .then(function (label) { assert.equal(label.length, 1, "Label not found"); return label[0].text(); }) .then(function (text) { assert.equal(text, 'I am live!', "Label text not matched"); }) .fail(function(err){ deferred.reject(new Error(err.message)); }) .fin(function(){ // deferred.resolve(els); }); return deferred.promise; } var check_broken_button = function(broken_button){ var deferred = Q.defer(); broken_button.click() .then(function(){ return broken_button.displayed(); }) .then(function(displayed){ assert.equal(displayed, false, "Broken button still visible"); }) .fail(function(err){ deferred.reject(new Error(err.message)); }) .fin(function(){ deferred.resolve(els); }); return deferred.promise; } var deferred = Q.defer(); check_first_buttons(els) .then(function(){return check_work_button(els[2]);}) .fail(function(err){ deferred.reject(new Error(err.message)); }) .fin(function(){ deferred.resolve(); }); return deferred.promise; } 

In "check_invalid_position", a check is made as to whether the element really moved out, which is not quite logical. It would be more correct to check if it is in its place, but assert interrupts the test on the first error. It's okay, the main thing is that I know that this is how it should be.

If you have difficulty setting an element selector, you can use the Appium Inspector. To do this, download the application if you are using the npm version, and click on the button with the blue information icon to the left of the “Stop” button. More details here .



In the course of the test, the current task of the tester will be displayed in the console.

 $ node test.js Driving the web on session: 4892e40c-3cdf-4b66-9a01-4bfbad23da67 > POST: /session/:sessionID/elements { using: 'tag name', value: 'button' } > POST: /session/:sessionID/element/0/click > POST: /session/:sessionID/elements { using: 'tag name', value: 'alert' } > POST: /session/:sessionID/accept_alert > GET: /session/:sessionID/element/1/location > GET: /session/:sessionID/element/0/location > POST: /session/:sessionID/element/2/click > POST: /session/:sessionID/elements { using: 'tag name', value: 'text' } > GET: /session/:sessionID/element/2/displayed > POST: /session/:sessionID/elements { using: 'tag name', value: 'text' } > GET: /session/:sessionID/element/6/text > DELETE: /session/:sessionID Ending your web drivage.. 

Or an error message. This is what will happen if you add a check for opening a new modal window by a non-working button.

  check_first_buttons(els) .then(function(){return check_broken_button(els[3]);}) .then(function(){return check_work_button(els[2]);}) .fail(function(err){ deferred.reject(new Error(err.message)); }) .fin(function(){ deferred.resolve(); }); 

 $ node test.js Driving the web on session: 4a5f1d13-b395-49a0-a28f-2d2cd67ac72d > POST: /session/:sessionID/elements { using: 'tag name', value: 'button' } > POST: /session/:sessionID/element/0/click > POST: /session/:sessionID/elements { using: 'tag name', value: 'alert' } > POST: /session/:sessionID/accept_alert > GET: /session/:sessionID/element/1/location > GET: /session/:sessionID/element/0/location > POST: /session/:sessionID/element/3/click > GET: /session/:sessionID/element/3/displayed fail [Error: Broken button still visible] > DELETE: /session/:sessionID Ending your web drivage.. 

You can also watch the log in the appium itself.



With the documentation in Appium so far tugovato. But you can see the description and the list of commands in wd . Write comments or in a personal, if there are questions. Thus, it is possible to shift part of the routine work of testing the mobile application interface on the shoulders of the computer.

Source: https://habr.com/ru/post/202910/


All Articles