Ожидание событий в Selenium RC, часть 2 -- AJAX
В предыдущей заметке мы сделали расширение Selenium RC, упрощающее операции, связанные с ожиданием загрузки страниц веб-приложения. Но те, кто занимается тестированием AJAX-приложений, с этими операциями сталкиваются редко, им приходится работать с другими событиями – появление и исчезновение элементов интерфейса, а также изменение их свойств (таких как, например, видимость или цвет). Поэтому сейчас мы добавим в наше расширение набор операций, предназначенных для ожидания таких событий. Но сначала немного теории о том, как в целом устроена система команд в Selenium.
Система команд в Selenium
Все команды, которые есть в Selenium, разделяются на три класса:
- действия (actions)
- получатели данных (accessors)
- проверки (assertions)
Действия – это команды, которые управляют состоянием тестируемого приложения, такие как click
, check
, type
, select
, fireEvent
и т.п.
Большая часть действий приводит к изменению состояния приложения – в результате выполнения такой команды либо отправляется запрос на сервер (например, проход по ссылке или отправка формы), либо происходят какие-то события в браузере (например, заполняются поля формы, устанавливаются cookies или отрабатывает javascript-функция).
Некоторые команды-действия сами ничего не делают, но управляют поведением других команд-действий, например setTimeout
, answerOnNextPrompt
, chooseCancelOnNextConfirmation
.
Наконец, есть несколько команд, которые тоже почему-то относятся к действиям, но на самом деле это команды ожидания некоторого события. Это команды waitForCondition
, waitForPageToLoad
, waitForPopUp
и waitForFrameToLoad
, именно им была посящена предыдущая заметка (вообще-то команда waitForCondition
может модифицировать состояние приложения, потому что в ней можно выполнить произвольный javascript-код, но теоретически она не должна иметь такого рода побочных эффектов).
Получатели данных – это команды, предназначенные для получения информации о состоянии тестируемого приложения. В Selenium IDE все такие команды начинаются со слова “store” – storeTitle
, storeText
, storeElementPresent
и т.д., они сохраняют полученную информацию о состоянии приложения в переменные, которые могут быть использованы в последующих командах.
В Selenium RC используется другая схема именования – имена получателей, возвращающих текстовое значение, начинаются со слова “get”, а имена получателей, возвращающих булевское значение (да/нет, true/false), начинаются со слова “is”. Например, getTitle
, getText
, getAttribute
, но – isChecked
, isElementPresent
, isVisible
.
Проверки как самостоятельные команды существуют только в Selenium IDE. Они генерируются автоматически, для каждого получателя данных создается шесть проверок: прямая и обратная проверки в трёх режимах – assert, verify и waitFor. Например, для команды storeElementPresent
создаются следующие проверки: assertElementPresent
, assertElementNotPresent
, verifyElementPresent
, verifyElementNotPresent
, waitForElementPresent
, waitForElementNotPresent
.
В Selenium RC проверки реализуются как комбинация получателя данных и подходящего метода из используемого фреймворка для разработки тестов.
Так, скажем, для фреймворка TestNG (Java) проверки типа “assert” будут выглядеть примерно так:
А для фреймворка Python unittest аналогичные проверки будут такими:
Чуть сложнее устроены проверки типа “verify”. Они отличаются от проверок типа “assert” тем, что не должны немедленно прерывать выполнение теста, вместо этого сообщение об ошибке вносится в специальный список. Этот способ используется для выполнения некритичных проверок, после которых можно продолжать выполнение даже если проверка дала отрицательный результат. При этом тест отрабатывает до конца, и если список ошибок непустой, он всё-таки помечается как завершившийся неуспешно.
Тестовые фреймворки как правило не имеют встроенной поддержки для проверок такого типа.
Для тех, кто разрабатывает тесты на языке Java, ситуация несколько лучше. В TestNG проверки типа “verify” реализованы во вспомогательном классе SeleneseTestNgHelper
, о котором мы уже говорили в предыдущих заметках. Выглядеть это будет следующим образом:
Аналогичная поддержка проверок типа “verify” есть и в некоторых других фреймворках, в частности JUnit для Java и Groovy.
А вот для проверок типа “waitFor” нет поддержки ни в одном известном мне фреймворке или расширении для Selenium. Поэтому мы реализуем эту поддержку самостоятельно для TestNG (а если вы пользуетесь чем-нибудь другим – можете адаптировать это для своего фреймворка самостоятельно).
Но сначала ещё чуть-чуть поговорим о том, почему эти команды играют столь важную роль при тестировании AJAX-приложений
AJAX и команды-проверки типа “waitFor”
В “классических” веб-приложениях тесты устроены таким образом, что мы сначала выполняем некоторую последовательность действий, завершающуюся отправкой запроса на веб-сервер. Затем мы должны дождаться, пока браузер получит от сервера ответ, после чего приступить к его проверке. И для ожидания ответа обычно используется команда waitForPageToLoad
.
Но для AJAX-приложений этот способ не годится, потому что обращения к серверу выполняются в “фоновом режиме”, после чего обновляются только отдельные части страницы, полностью страница не перегружается. Поэтому команда waitForPageToLoad оказывается совершенно бесполезной.
Вместо ожидания загрузки страницы в таких приложениях мы должны определить некоторые другие критерии завершения обработки запроса. Это может быть появление или исчезновение каких-либо элементов на странице, либо изменение их свойств – видимость, цвет, расположение и т.д. Соответственно, нам нужны команды для ожидания таких событий – а это и есть те самые команды-проверки типа “waitFor”, о которых шла речь выше.
Ну что ж, пришла пора заняться реализацией всех этих проверок.
Реализация waitFor-проверок
За основую релизации методов ожидания можно взять код, который генерирует Selenium IDE для проверок типа “waitFor”.
Вот что там предлагается, например, для команды waitForVisible:
Идея вполне очевидна – в цикле раз в секунду проверять, виден ли нужный элемент. Если виден – ожидание прекращается. А если прошло уже достаточно много проверок (60) и все неуспешные – тогда можно завершить тест с сообщением о том, что время ожидания истекло.
Разумеется, невозможно каждый раз, когда требуется сделать такого рода проверку, вставлять столь громоздкий кусок кода. Давайте оформим его в виде вспомогательного метода, вот такого:
Теперь посмотрим пристально, и попробуем понять, сколько времени будет ожидать этот метод, прежде чем сообщит о неудаче? Думаете, 60 секунд? Отнюдь! Например, на моём ноутбуке, где я пишу эту заметку, он работает примерно 140 секунд. Дело в том, что на самом деле в цикле считаются не секунды, а количество попыток. Между попытками проходит секунда, но сами попытки тоже требуют определённого времени, причём весьма существенного. То есть у меня 60 секунд ушло на ожидание, и ещё 80 секунд заняли обращения к Selenium.
Давайте исправим это так, чтобы метод на самом деле выполнял проверки в течение указанного времени:
Кроме того, хорошо бы сделать время ожидания параметром, а также дать возможность настраивать дефолтное время ожидания и промежуток между попытками:
Далее мы должны были бы создать аналогичные методы для всех команд получения данных, но это не очень хорошо, потому что у нас получится множество методов, похожих как близнецы-братья.
Более правильный способ состоит в том, чтобы отделить “логику ожидания” от “логики проверки”, создать один унивесальный метод ожидания, который может проверять разные условия. Именно так, кстати, реализованы проверки типа “assert” и “verify” – в них комбинируется единый универсальный метод проверки с семейством специализированных методов получения данных.
Мы сделаем такую реализацию, в которой проверка-ожидание будет выглядеть следующим образом:
То есть у нас будет унивесальный метод waitFor (а также waitForNot) и семейство методов, реализующих логику проверки, по одному для каждой операции получения данных.
Кроме того, мы сделаем так, чтобы при неуспешном завершении он не прерывал выполнение теста, а просто возвращал false (а при успешном завершении, соответственно, true). Это даст возможность разработчику тестов самостоятельно принять решение о том, что делать в той или иной ситуации. Если он решит, что тест должен прерываться, можно добиться этого эффекта путём комбинирования с методом assertTrue
:
Итак, вот как устроен универсальный метод ожидания, который мы поместим в класс WaitingSelenium
:
На вход он получает параметр типа Condition
, это интерфейс, в котором имеется всего один метод:
А вот метод Visible, который реализует проверку того, виден или нет элемент с заданным локатором:
Вот и всё. Теперь надо наделать много методов, аналогичных Visible
– для всех команд получения данных, и можно пользоваться. Впрочем, всё это уже есть в приложенном архиве, содержащем код – в класс WaitingSelenium
добавлены два универсальных метода ожидания, а в классе SeleneseTestNgHelper
появилась целая серия методов, создающих проверки для практически всех команд-получателей данных. Пропущены команды getAllButtons
, getAllFields
, getAllLinks
, getAllWindowIds
, getAllWindowNames
и getAllWindowTitles
, для которых проверки типа waitFor не имеют особого смысла, но про которые мы ещё поговорим в будущем. Кроме того, нет проверок для команды storeLogMessages
, которая просто заглушка без реализации, и для команд WhetherThisFrameMatchFrameExpression
и WhetherThisWindowMatchFrameExpression
, которые предназначены для сугубо служебных целей.
И напоследок ещё одно замечание – условия для проверки можно делать сколь угодно сложными, они не обязательно должны состоять только из одной команды Selenium.
А в следущей заметке серии мы реализуем ещё два метода ожидания, которых в Selenium нет вообще, но которые тоже бывают полезны при тестировании AJAX-приложений – waitForChange
и waitForStopChanges
.
В приложении находится проект Eclipse, содержащий исходный код расширения WaitingSelenium и модифицированный класс SeleneseTestNgHelper
, в которых реализованы проверки-ожидания: WaitingSelenium2.zip