问题
According to the "Book of Geb" I started to map our portal's web pages. I prefer to use variables defined within static content closure block and accessing them afterwards in page methods:
static content = {
buttonSend { $("input", type: "submit", nicetitle: "Senden") }
}
def sendLetter() {
waitFor { buttonSend.isDisplayed() }
buttonSend.click()
}
Unfortunately, sometimes I get an Geb waiting timeout exception (after 60 secs) or even worse I receive the well known "StaleElementReferenceException".
I could avoid the wait timeout when using "isEnabled" instead of "isDisplayed" but for the "StaleElementReferenceException" I could only apply the below solution:
def sendLetter() {
waitFor { buttonSend.isEnabled() }
try {
buttonSend.click()
} catch (StaleElementReferenceException e) {
log.info(e.getMessage())
buttonSend.click()
}
}
I guess, this solution is not really nice but I could not apply an explicitly wait as described in another article. Thus, I have some general questions:
- Should I avoid to use static content definitions when pages are dynamically?
- At what time or event Geb is refreshing its DOM? How can I trigger the DOM refreshment?
- Why I still get a "StaleElementReferenceException" when using CSS selectors?
I would appreciate every hint which helps to understand or to solve this issue. The best would be to have a simple code example since I'm still a beginner. Thank you!
回答1:
In addition to twinj's answer, I would like to point out a couple of other workarounds in case you encounter a StaleElementReferenceException.
Often times I find it is better to write out your selector manually rather than rely on the contents as defined in the page. Even though your page contents should not be cached by default, they still manage to slip away from me at times. This is particularly prevalent when dealing with dynamic content or iterations.
Ex: Let's say we want to click an element from a dynamically created dropdown.
Typically you might want to do something like...
static content = { dropdown { $("#parentDiv").find("ul") } } void clickDesiredElement(String elementName) { dropdown.click() def desiredElement = dropdown.find("li", text:elementName) waitFor { desiredElement.displayed } desiredElement.click() }
If this doesn't work, try getting rid of the contents altogether, and writing out the selector manually...
void clickDesiredElement(String elementName) { $("#parentDiv").find("ul").click() def desiredElement = $("#parentDiv").find("ul").find("li", text:elementName) waitFor { desiredElement.displayed } desiredElement.click() }
In really nasty cases, you may have to use a manual timer, as pointed out in this answer, and your code may look like this...
void clickDesiredElement(String elementName) { $("#parentDiv").find("ul").click() sleepForNSeconds(2) def desiredElement = $("#parentDiv").find("ul").find("li", text:elementName) waitFor { desiredElement.displayed } desiredElement.click() }
Keep in mind this is a workaround :)
For large iterations and convenient closure methods, such as each{} or collect{}, you may want to add a waitFor{} in each iteration.
Ex: Let's say we want to get all rows of a large table
Typically you might want to do something like...
def rows = $("#table1").find("tr").collect { [ name: it.find("td",0), email: it.find("td",1) ] }
Sometimes I find myself having to do this iteratively, along with a waitFor{} between each iteration in order to avoid a StaleElementReferentException. It might look something like this...
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) ] } }
回答2:
If you defined an at check on your page class the page would first verify that condition and wait for the first n seconds. Which is assigned in your gebConfig file. The default is 30 seconds.
static at = {
waitFor { buttonSend.isDisplayed() }
}
Thus once you call your pages 'to' method with a test or whatever you are using it for the page will wait and then perform your page manipulations.
to MyPage
buttonSend.click()
Should I avoid to use static content definitions when pages are dynamically?
No. Actually, the static definitions are of closures. So what is actually happening is each time you make use of that Pages static components you are calling a closure which is run dynamically on the current page(collection of webElements). Understanding this is key to using Geb and discovering the problems you will run into.
At what time or event Geb is refreshing its DOM? How can I trigger the DOM refreshment?
When you call: to, go, at, click ,withFrame(frame, page), withWindow and browser drive methods it will refresh the current set of WebElements. Geb has a nice collection of utiliities to make switching between pages and waiting for page manipulations easy. Note: Geb is actually built on WebDriver WebElements.
Why I still get a "StaleElementReferenceException" when using CSS selectors?
It is possible the page hasn't finished loading, has been manipulated with ajax calls or has been refreshed in some other way. Sometimes an 'at' PAGE method call can fix these issues. They are for me most common when using frames as Geb seems to become confused between pages and frames a little. There are workarounds.
In short if you use the page pattern you can easily switch expected pages using the Page class you have defined with a static content, at, and url closure using the below:
- to(Page)
- at(Page)
- Navigator.click(Page)
- withFrame(frame, Page) { }
回答3:
I have figured that it is the navigator which get lost when you load dynamically. I've solve the issue locally by reinit the page or module with below code:
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()
}
}
来源:https://stackoverflow.com/questions/22683330/general-problems-with-geb-staleelementreferenceexception-wait-timeouts