![image](https://habrastorage.org/getpro/habr/post_images/c87/1d1/e01/c871d1e018871aa0dca01139f0888355.png)
И так, предположим абстрактную ситуацию: у вас есть набор тестов написанный с помощью Selenium WebDriver. Он постоянно пополняется, обрастает новыми деталями и вам уже действительно невмоготу разбираться в логах, кучах XML, смотреть и думать, что полетело и почему. Чуточку ниже я попытаюсь дать ряд простых советов, для стабилизации своих Selenium тестов, а так же, расскажу о способах подружить Selenium с Jenkins CI и последствиях данной дружбы. Для тех, кто заинтересовался — прошу под кат.
1. Selenium WebDriver
Прежде чем заниматься интеграцией Selenium со сторонними вещами нужно добиться высокой стабильности работы. Что я подразумеваю под этими словами? Все очень просто: вне зависимости от количества раз запуска тестов, загруженности вашего тестируемого веб приложения и размера внешнего долга США, ваши тесты должны показывать один и тот же результат. Если вы думаете, что ваши тесты идеальны, то попробуйте одновременно запустить несколько сотен(подставьте сюда нужное вам число) экземпляров однообразных тестов, обрежьте максимально допустимый размер оперативной памяти для вашего web приложения или еще каким другим способом заставьте тестируемый продукт работать с жуткими тормозами и вы получите массу неприятных ошибок в ваших тестах.Если элемент на страничке не загрузился, то подождем секундочку.
Довольно обычный подход решения всех проблем для индийского кодера это:Thread.sleep(1000);
Обычно такие строчки кода помечаются комментарием в духе «wait for element with id=myUnicID1 present»… Почему нужно ждать именно 1000 миллисекунд — непонятно. В итоге в какой-то момент случается, что нужный нам элемент появляется на 1003ю миллисекунду и наш тест падает с ошибкой в духе NoSuchElementException. Естественно наш кодер очень быстро соображает в чем проблема и находит довольно простое решение:
Thread.sleep(5000); //уж 5 секунд-то точно хватит
300 тестов, где каждый ждет по 5 секунд в пустую = 25 минут потраченных на непонятные вещи, зато наверняка ошибка показанная выше, не появится.(Безусловно, все зависит от проекта и условий, в которых его тестируют, возможны ситуации, где 5 секунд не предел.) А если таких тестов много больше, как и количество «сомнительных» элементов?Подумав еще чуть-чуть, в голову приходит чуть более рациональное решение проблемы:
while(!driver.isElementPresent(By.id("myUnicID1") ) ){
Thread.sleep(500);
}
Данное решение при некоторых известных обстоятельствах равноценно коду:while(true){
Thread.sleep(500);
}
Но самое смешное, что этот код наталкивает нас на гениальную, но при этом очень простую идею — написание «умной» оболочки к драйверу. Таким образом нам нужно дождаться отрисовки всех нужных элементов, а так же завершения всех AJAX запросов. Как это реализуется, довольно неплохо описано здесь. Если вам нужны более сложные реализации — готовых решений в интернете предостаточно. Все, чего нет в документации — невозможно.
И вправду, документация дает довольно богатый функционал для тестирования, но он покрывает далеко не все. Например: порой хочется взять текст из textarea, а при использовании метода getText() возвращается пустая строка — загадочные js и прочие обвесы html'ного кода становятся помехой. А если нужно узнать цвет, какого-то конкретного элемента, или получить какую-то информацию о атрибутах? Не бойтесь пользоваться executeScript(), возможности js и jQuery не должны вам мешать, они должны помогать в покрытии тестов. Главное помнить, что скрипт должен возвращать какое-то значение(которое и будет результатом его выполнения). Если ваш скрипт не возвращает полезной информации, а производит некие магические действия — то пусть вернет true в случае, если магия завершилась с успехом. С Selenium можно уверенно тестировать действия мыши, нажатия клавиш, проверять всплывающие окна, перетягивать активные элементы и многое другое.Элемент не находится, но я же его вижу!
Я же 100 раз проверял, что мой xpath указывает правильно на элемент и уже всеми другими способами я его пытался определить, а мне все ошибки в trace валятся, что мол не могу найти. Такого рода ситуации больше всего расшатывают психику и возникают из-за непонимания происходящего(или невнимательности). Иногда может даже возникнуть ощущение, что именно ваша версия SeleniumDriver'a не поддерживает тот или иной функционал и найдя здесь версию, которая не поддерживает нужный вам функционал можно опустить руки. Но, как правило, проблема в твоем коде. Если не можешь найти, но уверен, что он есть — то, скорее всего, ты просто не там ищешь. А в том ли я фрейме нахожусь? А правильно ли я между ними переключаюсь? В ответах на эти два вопроса, как правило кроется решение.Абстрагируемся!
Многие Selenium тесты выглядят как последовательное нажатие на элементы с 30тиметровыми xpath, явно стыренными из firefox'a. Часто, тесты во многом дублируют нажатия по одним и тем же элементам или даже какие-то базовые наборы действий. А если в твоем приложении добавился новый функционал так, что нажатие на кнопку 'A'(которое присутствует в каждом втором тесте) перестало работать? Правда неудобно выкорячивать и рефакторить проект? Гораздо же удобнее создать класс(или несколько), который содержит все эти уникальные идентификаторы этих элементов. Проще поменять одну строчку, чем искать и исправлять множество.2. TestNG
Так уж сложилось, что selenium тесты вертятся у меня не на традиционном jUnit, а на TestNG. Безусловно, это все зависит только от вашего выбора, но возможности задавать пре\пост условия для групп тестов и параметризованные сборки — заманивают. Если кому-то интересно, довольно хорошо о них написано здесь. Думаю нет смысла вдаваться в реализацию тестов, я просто на пальцах опишу концепцию(если кто-то захочет, приложу поясняющие примеры кода). Каждая группа тестов реализована в своем классе, который наследуется от абстрактного класса. Условие успешного выполнения теста — это соответствие полученного XML файла, локальному(эталонному). Требования к тестам: быстрота выполнения, время выполнения теста не должно учитывать пре\пост условия, тесты должны быть простыми в понимании, не должны возникать случайные ошибки выполнения, из trace'a должна быть понятна причина ошибки.Таким образом, у меня очень много создано дополнительных Exception'ов, поясняющих суть той или иной проблемы. Есть какой-то «репозиторий» идентификаторов нужных мне элементов, и логика переключения между frame'ами зашита в относительно сложные методы(которые объединяют в себе набор простых действий).
3. Jenkins CI
Наконец-то нужно вкорячить все эти прелести жизни в какой-то сервер, который беспрерывно где-то там крутился бы и, если что, сообщал об ошибке. В качестве CI я использую Jenkins. Для успешной работы Selenium'a на сервере без графической оболочки нам понадобится установить xvfb:yum install Xvfb
Далее нужно попробовать запустить xvfb и если у вас не хватает шрифтов или прочих мелочей, то следует доставить все необходимое. Можно воспользоваться этой инструкцией. Теперь, когда мы все же добились стабильной работы xvfb, нужно установить xvfb плагин для jenkins'a(конечно можно обойтись и без него, но я стараюсь не пользоваться лишний раз самописными сриптами). В настройках jenkins'a указать адрес к xvfb, и включить его в конфигурации проекта ![image](https://habrastorage.org/getpro/habr/post_images/24a/e9d/eb4/24ae9deb410350e566768aca2c22eaa0.png)