Protractor — движок для запуска системных (end-to-end, браузерных) тестов. Внутри он использует seleniumс драйверами для браузера (chromedriver), а сами тесты пишутся с синтаксисом jasmine. Про него и карму я уже писал, впрочем mocha и cucumber тоже поддерживаются.
Из особенностей — protractor имеет интеграцию с angular (находит модели и repeat-директивы) — отсюда и название слов (angle — угол, protractor — транспортир, т.е. угловая линейка), но может тестировать и любые другие сайты. Из недостатков —
- Мне всё-таки пришлось добавить angular.js и ng-app к body на не-ангулярную страницу логина что-бы работал protractor
- В отличие от selenium, тесты надо писать вручную, никакого browser-плагина нет для записи кликов как то было в selenium ide
Ставим, запускаем selenium и в другом процессе запускаем исполнение тестов по заданной конфигурации:
npm install protractor -g
webdriver-manager start
clear && protractor conf.js --verbose
Конфигурация в стиле node-модуля, просто экспортирует данные о том где запущен selenium, какие spec-файлы надо запустить, какие переменные сделать глобальными.
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
specs: [
'login.js',
'profile.js',
'categories.js',
'products.js'
],
baseUrl: 'http://kurapov.name',
onPrepare: function() {
global.$p = protractor.getInstance();
//global.$ = require('./myjquery.js').$;
browser.driver.manage().window().setSize(1200, 800);
},
capabilities: [
{
'browserName': 'chrome',
'chrome.cli.args': ['--ignore-ssl-errors=true', '--web-security=false']
},
{
'browserName': 'phantomjs',
'phantomjs.cli.args': ['--debug=false', '--ignore-ssl-errors=true', '--web-security=false', '--webdriver-loglevel=INFO']
}
]
jasmineNodeOpts: {
showColors: true
}
Недостатки работы с DOM
В моём случае я использую свою wrapper-функцию $() для обращения к DOM. Проблема в том, что protractor общается с selenium драйверами через свои интерфейсы, т.е. вы не можете напрямую как-то обращаться к DOM браузера или с удобством вызывать javascript.
Локаторы
- by.css
- by.model
- by.repeater
- by.id
- by.js
- by.className
- by.linkText
- by.name
- by.partialLinkText
- by.tagName
- by.xpath
- by.addLocator
- by.binding
- by.buttonText
- by.cssContainingText
- by.exactBinding
- by.options
- by.partialButtonText
Найти элементы можно с помощью локаторов, из которых самый популярный — by.css(‘#myelement’). Дальше надо обернуть локатор выборкой элемента:
element(by.css('#myelement')).click();
Проблема в том, что элементов может быть много, а может и вообще не быть. Поэтому ваш тест должен знать что ищет
element.all(by.css('.row')).first()
Это плохо, потому что если вы обращаетесь к элементу которого ещё нет, то protractor выкинет ошибку и stacktrace. Это усложняет написание повторяемых тестов.
Поэтому для себя я написал обёртку вида $(‘#myelement .row’,3).click() которая будет выбирать третий элемент.
Вторая проблема с локаторами — асинхронность. Т.е. это конечно хорошо что гибко, но тесты получаются сложными. Методы действия над элементами как .click(), .getText() или .count() возвращают promise-обьект и получается что для правильной последовательности теста, надо городить лестницы из click-then
Та же проблема была и с селениумом кстати. Это неопределённость от синхронизации приложения с DOM, из-за которой приходится писать различные хаки с таймаутом
Практически полезные фишки
В файле тестов полезно перед каждым тестов вставлять ожидание..
describe('profile', function () {
beforeEach(function () {
$p.sleep(400); //в миллисекундах
$p.ignoreSynchronization = true; //для страниц без angular синхронизации
console.log(jasmine.getEnv().currentSpec.description + ' started:');
});
..
});
При работе со списками, в зависимости от того используете вы angular или нет, имеет смысл обращаться либо by.repeater, либо по css, например:
#countries select option:nth-child(2)
element( by.model('name') ).sendKeys("James Bond", protractor.Key.ENTER);
$p.switchTo().alert().accept();
var path = require('path');
$("#new_avatar input[type='file']").val(path.resolve(__dirname,'./testfiles/artjom_kurapov.jpg'));
В приведённом мною выше конфиге вы увидите, что я пытаюсь несколько браузеров использовать. Увы, phantomjs у меня пока не удалось успешно запустить. Он поднимается, открывает страницу, ищет элемент и потом падает
«errorMessage»:»Element is not currently interactable and may not be manipulated»
Дебаг
browser.pause();
protractor debug conf.js
node ./bin/elementexplorer.js http://kurapov.name
Организация тестов
suites: {
users: 'spec/*.user.js',
admins: 'spec/*.admin.js'
}
protractor conf.js --suite admins
Написание тестов достаточно противоречиво — с одной стороны они должны быть изолированы что-бы можно было запустить каждый тест самим по себе, с другой — короткими, без повторения нудных операций авторизации и подхода к нужному месту. Для этого я тесты обёртываю в такую конструкцию..
myApp.usePageAs('[email protected]', 'admin/dash', function () { //test here });