Hi,
I've written some functional tests using Spock and Geb. I'm using @Unroll and a 'where' clause to do data-driven testing. One of the data items in the Spock 'where' clause table is a Geb selector. This is so that when I loop through the test, I can reference different Geb selectors to click HTML buttons on the web page I am testing. I am using chromedriver.
I'm testing a page in my single page web app that has a question with 6 answers (the answers are labeled A,B,C,D,E,F). Clicking on an answer selects the answer. Using keyboard input by typing any of the letters a,b,c,d,e,f,A,B,C,D,E,F should select an answer as well, and that is what this spec is testing. To test that keyboard input still works when focus is not in the <DIV> containing the question and answers, I am clicking on a button outside of that <DIV> in the tests. I am also clicking on a button to navigate to the next page of the app.
Passing a Geb selector as a data item in the Spock 'where' clause (as shown below) does work. However, it seems that if I add more iterations to the test (by adding several more rows with Geb selectors as data items in the data table), I eventually get the stale web element error from Selenium when my test attempts to use a Geb selector. The error doesn't get thrown the first couple of times through, instead the error gets thrown at about the 4th or more times through when a Geb selector is referenced in the test. If I modify my spec to pass a text String as a data item instead of a Geb selector, and then interrogate the text String in an if statement in the test to determine which selector to use, that always works. I can do that, but its rather clunky instead of just passing a Geb selector as a data item.
Any clue why passing a Geb selector as a data item in the Spock 'where' clause sometimes works? I'm guessing its a race condition related to the number of loops through my Spock 'where' clause.
Here is my spec:
Unroll('App should accept #keypress key to select answer #answer after #event')
def 'Make sure A,B,C,D,E,F key presses work for a,b,c,d,e,f answer letter selection'() {
given: 'at question page with a,b,c,d,e,f answers'
at(QuestionPage)
waitFor {$('.question-container').displayed}
when: 'A,B,C,D,E, or F key is pressed'
app.answer(answer).displayed
interact { sendKeys(keypress) }
then: 'A,B,C,D,E, or F answer is selected'
waitFor(30, 1.5){app.selectedAnswer.displayed}
assert app.selectedAnswerLetter == keypress
cleanup:
report()
if (selector != null)
if (selector.displayed)
selector.click()
where:
answer | keypress | event | selector
//After page loaded
'1' | 'a' | 'page loaded' | null
'2' | 'b' | 'page loaded' | null
'3' | 'c' | 'page loaded' | null
'4' | 'd' | 'page loaded' | null
'5' | 'e' | 'page loaded' | null
'6' | 'f' | 'page loaded' | app.bottomToolbar.flag
//After flag button clicked
'1' | 'a' | 'flag button clicked' | null
'2' | 'b' | 'flag button clicked' | null
'3' | 'c' | 'flag button clicked' | null
'4' | 'd' | 'flag button clicked' | null
'5' | 'e' | 'flag button clicked' | null
'6' | 'f' | 'flag button clicked' | app.bottomToolbar.next
The Geb selectors above (app.bottomToolbar.flag and app.bottomToolbar.next) are defined in modules like this:
class BottomToolbarModule extends Module {
static content = {
toolbar { module ToolbarModule }
next(required:false){ $('#bottom-toolbar-next') }
flag(required: false, wait: true) { toolbar.button("#bottom-toolbar-flag") }
}
}
class ToolbarModule extends Module {
static content = {
button(required: false, wait: true) { selector -> $("${selector}") }
}
}
When I add more buttons to the table, that is when this doesn't work (stale web element), and I have to pass a String identifying the button in the table and interrogate that String in the test to determine which button to click, but that is rather clunky:
cleanup:
report()
if (selector != null)
if (selector == 'flag')
app.bottomToolbar.flag.click()
else if (selector == 'pause')
app.bottomToolbar.pause.click()
else if (selector == 'options')
app.bottomToolbar.options.click()
else if (selector == 'next')
app.bottomToolbar.next.click()