Общие проблемы с Geb (StaleElementReferenceException и Wait Timeouts)
Согласно "Книге Геба" я начал наносить на карту веб-страницы нашего портала. Я предпочитаю использовать переменные, определенные в статическом блоке закрытия контента, и обращаться к ним впоследствии в методах страницы:
static content = {
buttonSend { $("input", type: "submit", nicetitle: "Senden") }
}
def sendLetter() {
waitFor { buttonSend.isDisplayed() }
buttonSend.click()
}
К сожалению, иногда я получаю исключение тайм-аута ожидания Geb (через 60 секунд) или, что еще хуже, я получаю хорошо известное "StaleElementReferenceException".
Я мог бы избежать тайм-аут ожидания при использовании "isEnabled" вместо "isDisplayed", но для "StaleElementReferenceException" я мог бы применить только следующее решение:
def sendLetter() {
waitFor { buttonSend.isEnabled() }
try {
buttonSend.click()
} catch (StaleElementReferenceException e) {
log.info(e.getMessage())
buttonSend.click()
}
}
Я думаю, это решение не очень хорошее, но я не мог применить явное ожидание, как описано в другой статье. Итак, у меня есть несколько общих вопросов:
- Должен ли я избегать использования статических определений содержимого, когда страницы динамически?
- В какое время или событие Geb обновляет свою DOM? Как я могу вызвать обновление DOM?
- Почему я все еще получаю "StaleElementReferenceException" при использовании селекторов CSS?
Буду признателен за любую подсказку, которая поможет понять или решить эту проблему. Лучше всего иметь простой пример кода, так как я все еще начинающий. Спасибо!
3 ответа
В дополнение к ответу twinj, я хотел бы указать на несколько других обходных путей на случай, если вы столкнетесь с StaleElementReferenceException.
Часто я считаю, что лучше написать свой селектор вручную, а не полагаться на содержимое, как определено на странице. Несмотря на то, что содержимое вашей страницы не должно кэшироваться по умолчанию, ему все равно иногда удается ускользнуть от меня. Это особенно распространено при работе с динамическим контентом или итерациями.
Пример: допустим, мы хотим щелкнуть элемент из динамически созданного раскрывающегося списка.
Обычно вы можете захотеть сделать что-то вроде...
static content = { dropdown { $("#parentDiv").find("ul") } } void clickDesiredElement(String elementName) { dropdown.click() def desiredElement = dropdown.find("li", text:elementName) waitFor { desiredElement.displayed } desiredElement.click() }
Если это не работает, попробуйте полностью избавиться от содержимого и выписать селектор вручную...
void clickDesiredElement(String elementName) { $("#parentDiv").find("ul").click() def desiredElement = $("#parentDiv").find("ul").find("li", text:elementName) waitFor { desiredElement.displayed } desiredElement.click() }
В действительно неприятных случаях вам, возможно, придется использовать ручной таймер, как указано в этом ответе, и ваш код может выглядеть следующим образом...
void clickDesiredElement(String elementName) { $("#parentDiv").find("ul").click() sleepForNSeconds(2) def desiredElement = $("#parentDiv").find("ul").find("li", text:elementName) waitFor { desiredElement.displayed } desiredElement.click() }
Имейте в виду, что это обходной путь:)
Для больших итераций и удобных методов закрытия, таких как каждый {} или collect{}, вы можете добавить waitFor{} в каждую итерацию.
Пример: допустим, мы хотим получить все строки большой таблицы
Обычно вы можете захотеть сделать что-то вроде...
def rows = $("#table1").find("tr").collect { [ name: it.find("td",0), email: it.find("td",1) ] }
Иногда мне приходится делать это итеративно, вместе с waitFor{} между каждой итерацией, чтобы избежать исключения StaleElementReferentException. Это может выглядеть примерно так...
def rows = [] int numRows = $("#table1").find("tr").size() int i for(i=0; i < numRows; i++) { waitFor { def row = $("#table1").find("tr",i) rows << [ name: row.find("td",0), email: row.find("td",1) ] } }
Если вы определили проверку в классе вашей страницы, страница сначала проверит это условие и будет ждать первые n секунд. Который назначен в вашем файле gebConfig. По умолчанию 30 секунд.
static at = {
waitFor { buttonSend.isDisplayed() }
}
Таким образом, как только вы вызовете метод "to" для ваших страниц с помощью теста или чего-то еще, что вы используете для страницы, вы будете ждать, а затем будете выполнять манипуляции со страницей.
to MyPage
buttonSend.click()
Должен ли я избегать использования статических определений содержимого, когда страницы динамически?
Нет. На самом деле статические определения являются замыканиями. Так что на самом деле происходит то, что каждый раз, когда вы используете статические компоненты Pages, вы вызываете замыкание, которое динамически запускается на текущей странице (коллекция webElements). Понимание этого является ключом к использованию Geb и обнаружению проблем, с которыми вы столкнетесь.
В какое время или событие Geb обновляет свою DOM? Как я могу вызвать обновление DOM?
Когда вы вызываете: to, go, at, click,withFrame(frame, page), с помощью методов привода Windows и браузера, он обновит текущий набор WebElements. У Geb есть замечательная коллекция утилит, позволяющих легко переключаться между страницами и ожидать манипуляций со страницами. Примечание: Geb фактически построен на WebDriver WebElements.
Почему я все еще получаю "StaleElementReferenceException" при использовании селекторов CSS?
Возможно, страница не закончила загрузку, была обработана с помощью ajax-вызовов или обновлена каким-либо другим способом. Иногда вызов метода at at PAGE может решить эти проблемы. Они наиболее распространены для меня при использовании фреймов, поскольку Geb кажется немного запутанным между страницами и фреймами. Есть обходные пути.
Короче говоря, если вы используете шаблон страницы, вы можете легко переключать ожидаемые страницы, используя класс Page, который вы определили, с помощью статического содержимого, at и URL-закрытия, используя следующие:
- на страницу)
- в (Page)
- Navigator.click(страница)
- withFrame (frame, Page) {}
Я понял, что это навигатор, который теряется при динамической загрузке. Я решил проблему локально, переустановив страницу или модуль с кодом ниже:
void waitForDynamically(Double timeout = 20, Closure closure) {
closure.resolveStrategy = Closure.DELEGATE_FIRST
switch (this) {
case Module:
init(browser, browser.navigatorFactory)
break
case Page:
init(browser)
break
default:
throw new UnsupportedOperationException()
}
waitFor {
closure()
}
}