Ожидание событий в Selenium RC, часть 2 -- AJAX |
18.08.2010 09:16 |
Автор: Алексей Баранцев В предыдущей заметке мы сделали расширение Selenium RC, упрощающее операции, связанные с ожиданием загрузки страниц веб-приложения. Но те, кто занимается тестированием AJAX-приложений, с этими операциями сталкиваются редко, им приходится работать с другими событиями – появление и исчезновение элементов интерфейса, а также изменение их свойств (таких как, например, видимость или цвет). Поэтому сейчас мы добавим в наше расширение набор операций, предназначенных для ожидания таких событий. Но сначала немного теории о том, как в целом устроена система команд в Selenium. Система команд в SeleniumВсе команды, которые есть в Selenium, разделяются на три класса:
Действия – это команды, которые управляют состоянием тестируемого приложения, такие как 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” будут выглядеть примерно так: assertEquals(getText("id=result"), "expected value"); А для фреймворка Python unittest аналогичные проверки будут такими: self.assertEqual("expected value", sel.get_text("id=result")) Чуть сложнее устроены проверки типа “verify”. Они отличаются от проверок типа “assert” тем, что не должны немедленно прерывать выполнение теста, вместо этого сообщение об ошибке вносится в специальный список. Этот способ используется для выполнения некритичных проверок, после которых можно продолжать выполнение даже если проверка дала отрицательный результат. При этом тест отрабатывает до конца, и если список ошибок непустой, он всё-таки помечается как завершившийся неуспешно. Тестовые фреймворки как правило не имеют встроенной поддержки для проверок такого типа. Для тех, кто разрабатывает тесты на языке Java, ситуация несколько лучше. В TestNG проверки типа “verify” реализованы во вспомогательном классе SeleneseTestNgHelper, о котором мы уже говорили в предыдущих заметках. Выглядеть это будет следующим образом: verifyEquals(getText("id=result"), "expected value"); Аналогичная поддержка проверок типа “verify” есть и в некоторых других фреймворках, в частности JUnit для Java и Groovy. А вот для проверок типа “waitFor” нет поддержки ни в одном известном мне фреймворке или расширении для Selenium. Поэтому мы реализуем эту поддержку самостоятельно для TestNG (а если вы пользуетесь чем-нибудь другим – можете адаптировать это для своего фреймворка самостоятельно). Но сначала ещё чуть-чуть поговорим о том, почему эти команды играют столь важную роль при тестировании AJAX-приложений AJAX и команды-проверки типа “waitFor”В “классических” веб-приложениях тесты устроены таким образом, что мы сначала выполняем некоторую последовательность действий, завершающуюся отправкой запроса на веб-сервер. Затем мы должны дождаться, пока браузер получит от сервера ответ, после чего приступить к его проверке. И для ожидания ответа обычно используется команда waitForPageToLoad. Но для AJAX-приложений этот способ не годится, потому что обращения к серверу выполняются в “фоновом режиме”, после чего обновляются только отдельные части страницы, полностью страница не перегружается. Поэтому команда waitForPageToLoad оказывается совершенно бесполезной. Вместо ожидания загрузки страницы в таких приложениях мы должны определить некоторые другие критерии завершения обработки запроса. Это может быть появление или исчезновение каких-либо элементов на странице, либо изменение их свойств – видимость, цвет, расположение и т.д. Соответственно, нам нужны команды для ожидания таких событий – а это и есть те самые команды-проверки типа “waitFor”, о которых шла речь выше. Ну что ж, пришла пора заняться реализацией всех этих проверок. Реализация waitFor-проверокЗа основую релизации методов ожидания можно взять код, который генерирует Selenium IDE для проверок типа “waitFor”. Вот что там предлагается, например, для команды waitForVisible: for (int second = 0;; second++) { Идея вполне очевидна – в цикле раз в секунду проверять, виден ли нужный элемент. Если виден – ожидание прекращается. А если прошло уже достаточно много проверок (60) и все неуспешные – тогда можно завершить тест с сообщением о том, что время ожидания истекло. Разумеется, невозможно каждый раз, когда требуется сделать такого рода проверку, вставлять столь громоздкий кусок кода. Давайте оформим его в виде вспомогательного метода, вот такого: public void waitForVisible(String locator) { Теперь посмотрим пристально, и попробуем понять, сколько времени будет ожидать этот метод, прежде чем сообщит о неудаче? Думаете, 60 секунд? Отнюдь! Например, на моём ноутбуке, где я пишу эту заметку, он работает примерно 140 секунд. Дело в том, что на самом деле в цикле считаются не секунды, а количество попыток. Между попытками проходит секунда, но сами попытки тоже требуют определённого времени, причём весьма существенного. То есть у меня 60 секунд ушло на ожидание, и ещё 80 секунд заняли обращения к Selenium. Давайте исправим это так, чтобы метод на самом деле выполнял проверки в течение указанного времени: public void waitForVisible(String locator) { Кроме того, хорошо бы сделать время ожидания параметром, а также дать возможность настраивать дефолтное время ожидания и промежуток между попытками: public void waitForVisible(String locator) { Далее мы должны были бы создать аналогичные методы для всех команд получения данных, но это не очень хорошо, потому что у нас получится множество методов, похожих как близнецы-братья. Более правильный способ состоит в том, чтобы отделить “логику ожидания” от “логики проверки”, создать один унивесальный метод ожидания, который может проверять разные условия. Именно так, кстати, реализованы проверки типа “assert” и “verify” – в них комбинируется единый универсальный метод проверки с семейством специализированных методов получения данных. Мы сделаем такую реализацию, в которой проверка-ожидание будет выглядеть следующим образом: boolean res = selenium.waitFor(Visible("id=result")); То есть у нас будет унивесальный метод waitFor (а также waitForNot) и семейство методов, реализующих логику проверки, по одному для каждой операции получения данных. Кроме того, мы сделаем так, чтобы при неуспешном завершении он не прерывал выполнение теста, а просто возвращал false (а при успешном завершении, соответственно, true). Это даст возможность разработчику тестов самостоятельно принять решение о том, что делать в той или иной ситуации. Если он решит, что тест должен прерываться, можно добиться этого эффекта путём комбинирования с методом assertTrue: assertTrue(selenium.waitFor(Visible("id=result"))); Итак, вот как устроен универсальный метод ожидания, который мы поместим в класс WaitingSelenium: public boolean waitFor(Condition condition) { На вход он получает параметр типа Condition, это интерфейс, в котором имеется всего один метод: public interface Condition { А вот метод Visible, который реализует проверку того, виден или нет элемент с заданным локатором: public static Condition Visible(final String locator) { Вот и всё. Теперь надо наделать много методов, аналогичных Visible – для всех команд получения данных, и можно пользоваться. Впрочем, всё это уже есть в приложенном архиве, содержащем код – в класс WaitingSelenium добавлены два универсальных метода ожидания, а в классе SeleneseTestNgHelper появилась целая серия методов, создающих проверки для практически всех команд-получателей данных. Пропущены команды getAllButtons, getAllFields, getAllLinks, getAllWindowIds, getAllWindowNames и getAllWindowTitles, для которых проверки типа waitFor не имеют особого смысла, но про которые мы ещё поговорим в будущем. Кроме того, нет проверок для команды storeLogMessages, которая просто заглушка без реализации, и для команд WhetherThisFrameMatchFrameExpression и WhetherThisWindowMatchFrameExpression, которые предназначены для сугубо служебных целей. И напоследок ещё одно замечание – условия для проверки можно делать сколь угодно сложными, они не обязательно должны состоять только из одной команды Selenium. А в следущей заметке серии мы реализуем ещё два метода ожидания, которых в Selenium нет вообще, но которые тоже бывают полезны при тестировании AJAX-приложений – waitForChange и waitForStopChanges. В приложении находится проект Eclipse, содержащий исходный код расширения WaitingSelenium и модифицированный класс SeleneseTestNgHelper, в которых реализованы проверки-ожидания: WaitingSelenium2.zip Обсудить в форуме |