- PVSM.RU - https://www.pvsm.ru -
Бывает, наступает момент, когда нужно следить, не развалился ли лишний раз интерфейс мобильного приложения. Чтобы решить эту проблему используются автоматические тесты. Для веб страниц считается общепринятой практикой использовать Selenium Web driver, поэтому для мобильных приложений я искал похожие вещи. И, на счастье, таких нашлось немало, в них используется Selenium WebDriver JSON Wire Protocol [1].
В этой статье речь пойдет о Appium [2]. Я специально не проверял, один ли и тот же драйвер используется во всех продуктах такого рода. Но Appium я выбрал из-за того, что они четко указали на главной странице поддержку всех популярных языков. Вот то, чем они хвалятся:
Нам потребуется
На самом деле он еще умеет Android и FirefoxOS. В статье описывается только iOS, но при смене платформы код сильно не поменяется. Начнем с установки Appium сервера. Можно скачать приложение отсюда [3] или установить через npm. Добавьте «sudo», если потребуется.
$ npm install -g appium
$ appium &
Вам нужно авторизировать использование iOS симулятора. Если вы запускаете Appium с NPM, тогда сделайте это введя «sudo authorize_ios» (authorize_ios это бинарный файл с Appium npm пакета). Если же вы запускаете Appium с исходников, тогда используйте команду " sudo grunt authorize". Или сделайте это через графический интерфейс программы Appium.app.
Приступим к написанию тестов. Свой рабочий проект я, с понятных причин, приводить не буду. Опишу простой случай:
Вы можете использовать Appium с роботами, например, Tapster [4]. Подробнее о роботах здесь [5] и примеры [6]. Но в этой статье речь не о них..
Исходный код [7] теста и примеры приложений. Там 2 идентичных приложения, одно написано на ObjectiveC, второе на Titanium. Это для того, чтобы было понятно, что способ создания UI не имеет значения, если в итоге выводятся нативные iOS элементы.
Разберу тестовый пример:
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%sx1b[0m', info);
});
browser.on("command", function(meth, path, data) {
console.log(' > x1b[33m%sx1b[0m: %s', meth, path, data || '');
});
Подключение модулей. wd — это стандартные биндинги для Selenium web driver. В appURL вы можете указать путь к приложению или его архиву или же его http адрес. Обработчики ивентов исключительно для вывода отладочной информации, если вам это не нужно — можете смело удалять. Теперь инициализируется вебдрайвер и выполняется тестировочная функция:
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();
И сам тест:
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;
}
В «check_invalid_position» выполняется проверка, действительно ли элемент съехал, что не вполне логично. Правильнее было бы проверять, на своем ли он месте, но assert прерывает тест при первой ошибке. Ничего страшного, главное, что я знаю, что так оно и должно быть.
Если у вас возникли трудности с заданием селектора элемента, можно воспользоваться «Appium Inspector». Для этого скачайте приложение, если используете npm версию, и нажмите на кнопку с синим информационным значком слева от кнопки «Stop». Детальнее здесь [8].
По ходу выполнения теста в консоль будет выводится текущее задание тестировщика.
$ 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..
Или сообщение об ошибке. Вот, что будет, если добавить проверку открытия нового модального окна по нерабочей кнопке.
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..
Так же можно смотреть лог в самом appium
С документацией в Appium пока туговато. Но вы можете посмотреть описание и список команд в wd [9]. Пишите комментарии или в личку, если остались вопросы. Таким образом можно переложить часть рутинной работы тестирования интерфейса мобильного приложения на плечи компьютера.
Автор: peinguin
Источник [10]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/48876
Ссылки в тексте:
[1] Selenium WebDriver JSON Wire Protocol: https://code.google.com/p/selenium/wiki/JsonWireProtocol
[2] Appium: http://appium.io/index.html
[3] отсюда: https://github.com/appium/appium/releases
[4] Tapster: https://github.com/hugs/tapsterbot
[5] Подробнее о роботах здесь: https://github.com/appium/robots
[6] примеры: https://github.com/appium/appium/tree/master/sample-code/examples/node
[7] Исходный код: https://github.com/peinguin/AppiumStudy
[8] здесь: https://github.com/appium/appium/blob/master/docs/finding-elements.md#example
[9] wd: https://github.com/admc/wd/blob/master/doc/api.md
[10] Источник: http://habrahabr.ru/post/202910/
Нажмите здесь для печати.