Статьи

Тестирование приложений GWT с Selenium или WebDriver

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

И Selenium, и WebDriver (который сейчас является преемником Selenium) предоставляют хороший способ функционального тестирования веб-приложений в нескольких целевых средах без ручной работы. В прошлом веб-интерфейсы создавались с использованием навигации по страницам, чтобы пользователи могли отправлять формы и т. Д. В настоящее время все больше и больше веб-приложений используют Ajax и, следовательно, действуют и выглядят как настольные приложения. Однако это создает проблемы для тестирования — Selenium и WebDriver предназначены для работы с пользовательскими взаимодействиями, приводящими к навигации по страницам, и не очень хорошо работают с приложениями AJAX из коробки.

В частности, в приложениях, основанных на GWT, есть эта проблема, но я нашел несколько способов разработать полезные и эффективные тесты. GWT также ставит другие вопросы, касающиеся имитации пользовательского ввода и определения местоположения элементов DOM, и я обсуждаю их ниже. Обратите внимание, что мои примеры кода используют Groovy для краткости, но их довольно легко преобразовать в код Java.

Проблема 1: Обработка асинхронных изменений

Одна из проблем, с которой разработчики сталкиваются довольно быстро при тестировании приложений на основе GWT, — это обнаружение и ожидание реакции на взаимодействие с пользователем. Например, пользователь может нажать кнопку, которая приводит к вызову AJAX, который либо завершится успешно, и закроет окно, либо, альтернативно, покажет сообщение об ошибке. Нам нужен способ блокировки до тех пор, пока мы не увидим ожидаемые изменения, с тайм-аутом, чтобы мы могли потерпеть неудачу, если не увидим ожидаемых изменений.

Решение: используйте WebDriverWait

Самый простой способ сделать это — воспользоваться WebDriverWait (или Selenium’s Wait). Это позволяет вам подождать условие и продолжить, когда оно оценивается как истинное. Ниже я использую код Groovy для краткости использования замыканий, но то же самое можно сделать в Java, хотя с немного большим количеством кода из-за необходимости в анонимных классах.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def waitForCondition(Closure closure) {
    int timeout = 20
    WebDriverWait w = new WebDriverWait(driver, timeout)
    w.until({
        closure() // wait until this closure evaluates to true
    } as ExpectedCondition)
}
 
def waitForElement(By finder) {
    waitForCondition {
        driver.findElements(finder).size() > 0;
    }
}
 
def waitForElementRemoval(By finder) {
    waitForCondition {
        driver.findElements(finder).size() == 0;
    }
}
 
// now some sample test code
 
submitButton.click() // submit a form
 
// wait for the expected error summary to show up
waitForElement(By.xpath("//div[@class='error-summary']"))
// maybe some more verification here to check the expected errors
 
// ... correct error and resubmit
 
submitButton.click()
waitForElementRemoval(By.xpath("//div[@class='error-summary']"))
waitForElementRemoval(By.id("windowId"))

Как видно из примера, ваш код может сосредоточиться на реальной логике тестирования, одновременно бесперебойно обрабатывая асинхронную природу приложений GWT.

Проблема 2: Расположение элементов, когда у вас мало контроля над DOM

В веб-приложениях, использующих шаблоны (JSP, Velocity, JSF и т. Д.), У вас есть хороший контроль и легкий обзор структуры DOM, которую будут иметь ваши страницы. С GWT это не всегда так. Часто вы имеете дело с вложенными элементами, которые вы не можете контролировать на хорошем уровне.

С помощью WebDriver и Selenium вы можете нацеливать элементы, используя несколько методов, но наиболее полезными являются идентификатор элемента DOM и XPath. Как мы можем использовать их для получения поддерживаемых тестов, которые не ломаются с небольшими изменениями макета?

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

По моему опыту, для разработки функциональных тестов GWT в WebDriver вы должны использовать несколько свободный XPath в качестве основного средства поиска элементов и дополнить его, ограничивая эти вызовы идентификатором DOM, где это применимо.

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

Вот несколько примеров, чтобы вы начали. Обратите внимание, что мы используем // и * в нашем XPath для обеспечения гибкости наших выражений, чтобы изменения макета не нарушали наши тесты, если они не являются основными.

01
02
03
04
05
06
07
08
09
10
11
By byUserName = By.xpath("//*[@id='userTab']//*[text()='User Name']/..//input")
WebElement userNameField = webDriver.findElement(byUserName)
userNameField.sendKeys("my new user")
 
// maybe a user click and then wait for the window to disappear
By submitLocator = By.xpath("//*[@id='userTab']//input[@type='submit']")
WebElement submit = webDriver.findElement(submitLocator)
submit.click()
 
// use our helper method from Problem 1
waitForElementRemoval By.id("userTab")

Проблема 3: Нормальные методы взаимодействия элементов не работают!

GWT и его производные (Vaadin, GXT и т. Д.) Часто делают некое волшебство за кулисами при управлении состоянием DOM. Для разработчика это означает, что вы не всегда имеете дело с простыми элементами <input> или <select> и т. Д. Простая установка значения поля обычными средствами может не сработать, а использование методов кликов WebDriver или Selenium может не сработать.

WebDriver улучшился в этом отношении, но проблемы все еще сохраняются.

Решение: к сожалению, только некоторые обходные пути

Основные проблемы, с которыми вы, вероятно, столкнетесь, касаются ввода в поля и нажатия элементов.

Вот несколько вариантов, которые я нашел необходимыми в прошлом, чтобы обойти клики, которые не работают, как ожидалось. Попробуйте их, если вы столкнулись с проблемами. Примеры приведены в Selenium, но их можно адаптировать к соответствующим вызовам в WebDriver, если они вам требуются. Вы также можете использовать адаптер Selenium для WebDriver (WebDriverBackedSelenium), если хотите использовать примеры напрямую.

НАЖМИТЕ НА ВОПРОСЫ
Иногда элементы не отвечают на вызов click () в Selenium или WebDriver. В этих случаях вам обычно приходится моделировать события в браузере. Это было правдой больше, чем Selenium до 2.0, чем WebDriver.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
// Selenium's click sometimes has to be simulated with events.
def fullMouseClick(String locator) {
    selenium.mouseOver locator
    selenium.mouseDown locator
    selenium.mouseUp locator
}
 
// In some cases you need only mouseDown, as mouseUp may be
// handled the same as mouseDown.
// For example, this could result in a table row being selected, then deselected.
def mouseOverAndDown(String locator) {
    selenium.mouseOver locator
    selenium.mouseDown locator
}

ВОПРОСЫ ТИПА
Это окольные методы набора текста, которые я смог успешно использовать в прошлом, когда GWT не распознает типизированный ввод.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
// fires only key events (works for most GWT inputs)
// Useful if WebDriver sendKeys() or Selenium type() aren't cooperating.
def typeWithEvents(String locator, String text) {
    def keyEvents = ["keydown", "keypress", "keyup"]
    typeWithEvents(locator, text, keyEvents)
}
 
// fires key events, plus blur and focus for really picky cases
def typeWithFullEvents(String locator, String text) {
    def fullEvents = ["keydown", "keypress", "keyup", "blur", "focus"]
    typeWithEvents(locator, text, fullEvents)
}
 
// use this directly to customize which events are fired
def typeWithEvents(String locator, String text, def events) {
    text.eachWithIndex { ch, i ->
        selenium.type locator, text.substring(0, i+1)
        events.each{ event ->
            selenium.fireEvent locator, event
        }
    }
}

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

Вывод

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

Ссылка: Тестирование GWT-приложений с помощью Selenium или WebDriver от наших партнеров JCG в блоге Carfey Software .

Статьи по Теме :