Автор: Виталий Помазенков
В последнее время развелось очень много различных AJAX-приложений. По сути автоматизация тестирования такого приложения не отличается от автоматизации тестирования обычного WEB-приложения, но есть несколько тонкостей. Одна из тонкостей — это как раз ожидание завершения всех AJAX-запросов. Например, если отметка некого checkbox'а на странице вызывает обновление какого-нибудь select'a по AJAX-запросу, то тест, который сразу после отметки выбирает конкретный option, свалится, т.к. этого option'a там не будет. А всё потому, что сам тест выполняется намного быстрее чем AJAX-запрос на обновление списка. В данном случае у автоматизатора есть несколько выходов.
Поставить sleep после отметки checkbox'а. Это самое плохое и, к сожалению, чаще всего применяемое решение. Нам заранее не известно, сколько времени займёт выполнение AJAX-запроса, соответственно придётся задавать время ожидания исходя из минимально достаточного для большинства случаев. Например, 5 секунд. Когда таких ожиданий по 5 секунд наберётся достаточно много, наши тесты начнут выполняться очень долго, даже тогда, когда все AJAX-запросы выполняются быстро. Кроме того, иногда по разным причинам время выполнения AJAX-запроса может оказаться 5.2 секунды, в таких случаях мы будем получать ложные падения тестов, что тоже плохо. Воспользоваться классом Wait и ждать пока Selenium#isElementPresent не вернёт true для нужного option'а. Этот способ уже лучше, но всё равно не должен применяться, в будущем напишу подробно почему. Лучше вместо класса Wait использовать метод Selenium#waitForCondition, в котором и ждать появления требуемого элемента. Каким-то образом после отметки checkbox'а дождаться завершения всех AJAX-запросов и только после этого выбирать option. Этот способ рассмотрим более подробно, т.к. он является достаточно универсальным и простым с точки зрения автоматизатора. В большинстве WEB-приложений для работы с AJAX используются специализированные библиотеки (jQuery, Prototype, Dojo и т.д.), предоставляющие разработчику более высокий уровень абстракции, чем стандартное API, а соответственно и большую гибкость. Для того, чтобы в Selenium-тесте дождаться завершения всех AJAX-запросов, необходимо научиться следить за этими запросами глобально. В стандартном API нет возможности установки глобальных перехватчиков, но зато практически в каждой из сторонних библиотек такая возможность есть, хотя везде это делается по-своему. Вот пример, как можно дождаться завершения всех AJAX-запросов при использовании библиотеки jQuery:
Selenium.prototype.doWaitForJqueryAjaxRequests = function(timeout) { return Selenium.decorateFunctionWithTimeout(function() {
return selenium.browserbot.getUserWindow().jQuery.active == 0; }, timeout); };
Здесь мы просто оборачиваем необходимое нам условие (количество активных AJAX-запросов равно нулю) в метод Selenium#decorateFunctionWithTimeout, который будет ожидать выполнения данного условия в течение указанного в timeout времени, и если дождётся, то метод будет успешно завершён, иначе будет выкинуто исключение SeleniumError. Если описать на мета-языке то, что нам требуется для создания универсального метода ожидания, то получится примерно следующее:
- Определить, какие библиотеки используются для работы с AJAX.
- Подождать завершения всех AJAX-запросов для каждой из используемых библиотек.
Всё просто, осталось реализовать это на JavaScript и подключить в качестве расширения к Selenium RC либо к Selenium IDE, кому как нравится. При использовании Selenium RC для большей универсальности можно подгружать код расширения с помощью метода DefaultSelenium#setExtensionJs. Вот готовая реализация (поддержаны jQuery, Prototype и Dojo):
Selenium.prototype.doWaitForAjaxRequests = function(timeout) { return Selenium.decorateFunctionWithTimeout(function() {
var userWindow = selenium.browserbot.getUserWindow(); var isJqueryComplete = typeof(userWindow.jQuery) != 'function'
|| userWindow.jQuery.active == 0; var isPrototypeComplete = typeof(userWindow.Ajax) != 'function'
|| userWindow.Ajax.activeRequestCount == 0; var isDojoComplete = typeof(userWindow.dojo) != 'function'
|| userWindow.dojo.io.XMLHTTPTransport.inFlight.length == 0; return isJqueryComplete && isPrototypeComplete && isDojoComplete; }, timeout); };
Если для написания тестов используется не Selenese, а нормальный язык программирования, то для того, чтобы можно было воспользоваться новым методом, необходимо расширить используемый драйвер, добавив в него этот метод. Теперь мы можем легко заменить такой вот код теста:
... selenium.check("name=enableBender"); sleep(5000); selenium.select("name=mode", "label=Kill all humans"); ...
На такой:
... selenium.check("name=enableBender"); selenium.waitForAjaxRequests(60000); selenium.select("name=mode", "label=Kill all humans"); ...
И тесты будут выполняться со скоростью, равной скорости ответа сервера, т.е. без лишних задержек. Для некоторых проектов, где AJAX-запросы начинают выполняться сразу после загрузки страницы (да, бывают и такие), рекомендую перегрузить методы waitForPageToLoad, waitForFrameToLoad и waitForPopUp, добавив в них последним вызовом waitForAjaxRequests, чтобы не дергать его постоянно в тестах. Напоследок ещё раз повторюсь, что в стандартном API нет возможности установки глобальных перехватчиков AJAX-запросов, поэтому данный метод не будет работать, если разработчики используют стандартный API напрямую. Благо, что в более-менее серьёзных проектах так не поступают. Но вполне возможно, что в каком-нибудь проекте применяется собственная обёртка вокруг стандартного API, в таком случае надо будет просто поддержать эту обёртку в user-extensions.js.
|