| Автор: Виталий Помазенков В последнее время развелось очень много различных 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.
 |