Что пишут в блогах

Подписаться

Очные тренинги

Все очные тренинги
Оставь заявку на тренинг в своем городе

Онлайн-тренинги

  • Все онлайн-курсы
  •  

    http://software-testing.ru/about/authors/9-barancev

    Разделы портала

    VIP-вакансии

    Наши партнёры

    www.it4business.ru

    UML2.ru

    SysIQ Inc.

    Ожидание событий в Selenium RC, часть 2 -- AJAX
    18.08.2010 08:16

    Selenium LogoАвтор: Алексей Баранцев

    В предыдущей заметке мы сделали расширение 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” будут выглядеть примерно так:

    assertEquals(getText("id=result"), "expected value");
    assertFalse(isElementPresent("id=error"));

    А для фреймворка Python unittest аналогичные проверки будут такими:

    self.assertEqual("expected value", sel.get_text("id=result"))
    self.assertFalse(sel.is_element_present("id=error"))

    Чуть сложнее устроены проверки типа “verify”. Они отличаются от проверок типа “assert” тем, что не должны немедленно прерывать выполнение теста, вместо этого сообщение об ошибке вносится в специальный список. Этот способ используется для выполнения некритичных проверок, после которых можно продолжать выполнение даже если проверка дала отрицательный результат. При этом тест отрабатывает до конца, и если список ошибок непустой, он всё-таки помечается как завершившийся неуспешно.

    Тестовые фреймворки как правило не имеют встроенной поддержки для проверок такого типа.

    Для тех, кто разрабатывает тесты на языке Java, ситуация несколько лучше. В TestNG проверки типа “verify” реализованы во вспомогательном классе SeleneseTestNgHelper, о котором мы уже говорили в предыдущих заметках. Выглядеть это будет следующим образом:

    verifyEquals(getText("id=result"), "expected value");
    verifyFalse(isElementPresent("id=error"));

    Аналогичная поддержка проверок типа “verify” есть и в некоторых других фреймворках, в частности JUnit для Java и Groovy.

    А вот для проверок типа “waitFor” нет поддержки ни в одном известном мне фреймворке или расширении для Selenium. Поэтому мы реализуем эту поддержку самостоятельно для TestNG (а если вы пользуетесь чем-нибудь другим – можете адаптировать это для своего фреймворка самостоятельно).

    Но сначала ещё чуть-чуть поговорим о том, почему эти команды играют столь важную роль при тестировании AJAX-приложений

    AJAX и команды-проверки типа “waitFor”

    В “классических” веб-приложениях тесты устроены таким образом, что мы сначала выполняем некоторую последовательность действий, завершающуюся отправкой запроса на веб-сервер. Затем мы должны дождаться, пока браузер получит от сервера ответ, после чего приступить к его проверке. И для ожидания ответа обычно используется команда waitForPageToLoad.

    Но для AJAX-приложений этот способ не годится, потому что обращения к серверу выполняются в “фоновом режиме”, после чего обновляются только отдельные части страницы, полностью страница не перегружается. Поэтому команда waitForPageToLoad оказывается совершенно бесполезной.

    Вместо ожидания загрузки страницы в таких приложениях мы должны определить некоторые другие критерии завершения обработки запроса. Это может быть появление или исчезновение каких-либо элементов на странице, либо изменение их свойств – видимость, цвет, расположение и т.д. Соответственно, нам нужны команды для ожидания таких событий – а это и есть те самые команды-проверки типа “waitFor”, о которых шла речь выше.

    Ну что ж, пришла пора заняться реализацией всех этих проверок.

    Реализация waitFor-проверок

    За основую релизации методов ожидания можно взять код, который генерирует Selenium IDE для проверок типа “waitFor”.

    Вот что там предлагается, например, для команды waitForVisible:

    for (int second = 0;; second++) {
    if (second >= 60) fail("timeout");
    try { if (selenium.isVisible("id=result")) break; } catch (Exception e) {}
    Thread.sleep(1000);
    }

    Идея вполне очевидна – в цикле раз в секунду проверять, виден ли нужный элемент. Если виден – ожидание прекращается. А если прошло уже достаточно много проверок (60) и все неуспешные – тогда можно завершить тест с сообщением о том, что время ожидания истекло.

    Разумеется, невозможно каждый раз, когда требуется сделать такого рода проверку, вставлять столь громоздкий кусок кода. Давайте оформим его в виде вспомогательного метода, вот такого:

    public void waitForVisible(String locator) { 
    for (int second = 0;; second++) {
    if (second >= 60) { throw new AssertionError("timeout"); }
    try { if (selenium.isVisible(locator)) break; } catch (Exception e) {}
    try { Thread.sleep(1000); } catch (InterruptedException e) { throw new AssertionError(e); }
    }
    }

    Теперь посмотрим пристально, и попробуем понять, сколько времени будет ожидать этот метод, прежде чем сообщит о неудаче? Думаете, 60 секунд? Отнюдь! Например, на моём ноутбуке, где я пишу эту заметку, он работает примерно 140 секунд. Дело в том, что на самом деле в цикле считаются не секунды, а количество попыток. Между попытками проходит секунда, но сами попытки тоже требуют определённого времени, причём весьма существенного. То есть у меня 60 секунд ушло на ожидание, и ещё 80 секунд заняли обращения к Selenium.

    Давайте исправим это так, чтобы метод на самом деле выполнял проверки в течение указанного времени:

    public void waitForVisible(String locator) {
    long start = System.currentTimeMillis();
    while (System.currentTimeMillis() < start + 60000) {
    try { if (selenium.isVisible(locator)) return; } catch (Exception e) {}
    try { Thread.sleep(1000); } catch (InterruptedException e) { throw new AssertionError(e); }
    }
    throw new AssertionError("timeout");
    }

    Кроме того, хорошо бы сделать время ожидания параметром, а также дать возможность настраивать дефолтное время ожидания и промежуток между попытками:

    public void waitForVisible(String locator) {
    waitForVisible(locator, getDefaultTimeoutWaitFor());
    }

    public void waitForVisible(String locator, long timeout) {
    long pause = getAttemptsInterval();
    long start = System.currentTimeMillis();
    while (System.currentTimeMillis() < start + timeout) {
    try { if (selenium.isVisible(locator)) return; } catch (Exception e) {}
    try { Thread.sleep(pause); } catch (InterruptedException e) { throw new AssertionError(e); }
    }
    throw new AssertionError("timeout");
    }

    Далее мы должны были бы создать аналогичные методы для всех команд получения данных, но это не очень хорошо, потому что у нас получится множество методов, похожих как близнецы-братья.

    Более правильный способ состоит в том, чтобы отделить “логику ожидания” от “логики проверки”, создать один унивесальный метод ожидания, который может проверять разные условия. Именно так, кстати, реализованы проверки типа “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) {
    return waitFor(condition, getDefaultTimeoutWaitFor());
    }

    public boolean waitFor(Condition condition, long timeout) {
    long pause = getAttemptsInterval();
    long start = System.currentTimeMillis();
    while (System.currentTimeMillis() < start + timeout) {
    try { if (condition.checkConditionWith(this)) return true; } catch (Exception e) {}
    try { Thread.sleep(pause); } catch (InterruptedException e) { return false; }
    }
    return false;
    }

    На вход он получает параметр типа Condition, это интерфейс, в котором имеется всего один метод:

    public interface Condition {
    boolean checkConditionWith(Selenium selenium);
    }

    А вот метод Visible, который реализует проверку того, виден или нет элемент с заданным локатором:

    public static Condition Visible(final String locator) {
    return new Condition() {
    public boolean checkConditionWith(Selenium selenium) {
    return selenium.isVisible(locator);
    }
    };
    }

    Вот и всё. Теперь надо наделать много методов, аналогичных Visible – для всех команд получения данных, и можно пользоваться. Впрочем, всё это уже есть в приложенном архиве, содержащем код – в класс WaitingSelenium добавлены два универсальных метода ожидания, а в классе SeleneseTestNgHelper появилась целая серия методов, создающих проверки для практически всех команд-получателей данных. Пропущены команды getAllButtons, getAllFields, getAllLinks, getAllWindowIds, getAllWindowNames и getAllWindowTitles, для которых проверки типа waitFor не имеют особого смысла, но про которые мы ещё поговорим в будущем. Кроме того, нет проверок для команды storeLogMessages, которая просто заглушка без реализации, и для команд WhetherThisFrameMatchFrameExpression и WhetherThisWindowMatchFrameExpression, которые предназначены для сугубо служебных целей.

    И напоследок ещё одно замечание – условия для проверки можно делать сколь угодно сложными, они не обязательно должны состоять только из одной команды Selenium.

    А в следущей заметке серии мы реализуем ещё два метода ожидания, которых в Selenium нет вообще, но которые тоже бывают полезны при тестировании AJAX-приложений – waitForChange и waitForStopChanges.

    В приложении находится проект Eclipse, содержащий исходный код расширения WaitingSelenium и модифицированный класс SeleneseTestNgHelper, в которых реализованы проверки-ожидания: WaitingSelenium2.zip

    Обсудить в форуме

     

    Добавьтe Ваш комментарий

    Ваше имя (псевдоним):
    Ваш адрес почты:
    Заголовок:
    Комментарий: