Selenium: ожидание завершения всех AJAX-запросов
Это гостевая статья, написанная Виталием Помазенковым
В последнее время развелось очень много различных AJAX-приложений. По сути автоматизация тестирования такого приложения не отличается от автоматизации тестирования обычного WEB-приложения, но есть несколько тонкостей. Одна из тонкостей — это как раз ожидание завершения всех AJAX-запросов. Например, если отметка некого checkbox’а на странице вызывает обновление какого-нибудь select’a по AJAX-запросу, то тест, который сразу после отметки выбирает конкретный option, свалится, т.к. этого option’a там не будет. А всё потому, что сам тест выполняется намного быстрее чем AJAX-запрос на обновление списка.
В данном случае у автоматизатора есть несколько выходов.
1. Поставить sleep после отметки checkbox’а.
Это самое плохое и, к сожалению, чаще всего применяемое решение. Нам заранее не известно, сколько времени займёт выполнение AJAX-запроса, соответственно придётся задавать время ожидания исходя из минимально достаточного для большинства случаев. Например, 5 секунд. Когда таких ожиданий по 5 секунд наберётся достаточно много, наши тесты начнут выполняться очень долго, даже тогда, когда все AJAX-запросы выполняются быстро. Кроме того, иногда по разным причинам время выполнения AJAX-запроса может оказаться 5.2 секунды, в таких случаях мы будем получать ложные падения тестов, что тоже плохо.
2. Воспользоваться классом Wait и ждать пока Selenium#isElementPresent не вернёт true для нужного option’а.
Этот способ уже лучше, но всё равно не должен применяться, в будущем напишу подробно почему. Лучше вместо класса Wait использовать метод Selenium#waitForCondition, в котором и ждать появления требуемого элемента.
3. Каким-то образом после отметки checkbox’а дождаться завершения всех AJAX-запросов и только после этого выбирать option.
Этот способ рассмотрим более подробно, т.к. он является достаточно универсальным и простым с точки зрения автоматизатора.
В большинстве WEB-приложений для работы с AJAX используются специализированные библиотеки (jQuery, Prototype, Dojo и т.д.), предоставляющие разработчику более высокий уровень абстракции, чем стандартное API, а соответственно и большую гибкость.
Для того, чтобы в Selenium-тесте дождаться завершения всех AJAX-запросов, необходимо научиться следить за этими запросами глобально. В стандартном API нет возможности установки глобальных перехватчиков, но зато практически в каждой из сторонних библиотек такая возможность есть, хотя везде это делается по-своему. Вот пример, как можно дождаться завершения всех AJAX-запросов при использовании библиотеки jQuery:
Здесь мы просто оборачиваем необходимое нам условие (количество активных AJAX-запросов равно нулю) в метод Selenium#decorateFunctionWithTimeout, который будет ожидать выполнения данного условия в течение указанного в timeout времени, и если дождётся, то метод будет успешно завершён, иначе будет выкинуто исключение SeleniumError.
Если описать на мета-языке то, что нам требуется для создания универсального метода ожидания, то получится примерно следующее:
- Определить, какие библиотеки используются для работы с AJAX.
- Подождать завершения всех AJAX-запросов для каждой из используемых библиотек.
Всё просто, осталось реализовать это на JavaScript и подключить в качестве расширения к Selenium RC либо к Selenium IDE, кому как нравится. При использовании Selenium RC для большей универсальности можно подгружать код расширения с помощью метода DefaultSelenium#setExtensionJs.
Вот готовая реализация (поддержаны jQuery, Prototype и Dojo):
Если для написания тестов используется не Selenese, а нормальный язык программирования, то для того, чтобы можно было воспользоваться новым методом, необходимо расширить используемый драйвер, добавив в него этот метод.
Теперь мы можем легко заменить такой вот код теста:
На такой:
И тесты будут выполняться со скоростью, равной скорости ответа сервера, т.е. без лишних задержек.
Для некоторых проектов, где AJAX-запросы начинают выполняться сразу после загрузки страницы (да, бывают и такие), рекомендую перегрузить методы waitForPageToLoad, waitForFrameToLoad и waitForPopUp, добавив в них последним вызовом waitForAjaxRequests, чтобы не дергать его постоянно в тестах.
Напоследок ещё раз повторюсь, что в стандартном API нет возможности установки глобальных перехватчиков AJAX-запросов, поэтому данный метод не будет работать, если разработчики используют стандартный API напрямую. Благо, что в более-менее серьёзных проектах так не поступают. Но вполне возможно, что в каком-нибудь проекте применяется собственная обёртка вокруг стандартного API, в таком случае надо будет просто поддержать эту обёртку в user-extensions.js.