<p><pre><code> > getWithRetry takes a function with a return value
>
> def numberOfChildren(implicit user: LucidUser): Int = {
> getWithRetry() {
> user.driver.getCssElement(visibleCss).children.size
> }
> }
>
> predicateWithRetry takes function that returns a boolean and will retry on any false values
>
> def onPage(implicit user: LucidUser): Boolean = {
> predicateWithRetry() {
> user.driver.getCurrentUrl.contains(pageUrl)
> }
> }
</code></pre>
At first I didn't get the difference between `getWithRetry` and
`predicateWithRetry`, but then I noticed that the former throws an
exception whereas the latter returns false. I infer that `getWithRetry`
will handle exceptions thrown by the retried function.<p>In stb-tester[1] (a UI tool/framework targeted more at consumer
electronics devices where the only access you have to the
system-under-test is an HDMI output) after a few years we've settled on
a `wait_until` function, which waits until the retried function returns
a "truthy" value. `wait_until` returns whatever the retried function
returns:<p><pre><code> def miniguide_is_up():
return match("miniguide.png")
press(Key.INFO)
assert wait_until(miniguide_is_up)
# or:
if wait_until(miniguide_is_up): ...
</code></pre>
(This is Python code.)<p>Since we use `assert` instead of throwing exceptions in our retried
function, `wait_until` seems to fill both the roles of `getWithRetry`
and `predicateWithRetry`. I suppose that you've chosen to go with 2
separate functions because so many of the APIs provided by Selenium
throw exceptions instead of returning true/false.<p><pre><code> > doWithRetry takes a function with no return type
>
> def clickFillColorWell(implicit user: LucidUser) {
> doWithRetry() {
> user.clickElementByCss("#fill-colorwell-color-well-wrapper")
> }
</code></pre>
Unlike Selenium, when testing the UI of an external device we have no
way of noticing whether an action failed, other than by checking the
device's video output. For example we have `press` to send an infrared
signal ("press a button on the remote control"), but that will never
throw unless you've forgotten to plug in your infrared emitter. I
haven't come up with a really natural way of specifying the retry of
actions. We have `press_until_match`, but that's not very general. The
best I have come up with is `do_until`, which takes two functions: The
action to do, and the predicate to say whether the action succeeded.<p><pre><code> do_until(
lambda: press(Key.INFO),
miniguide_is_up)
</code></pre>
It's not ideal, given the limitations around Python's lambdas (anonymous
functions). Using Python's normal looping constructs is also not ideal:<p><pre><code> # Could get into an infinite loop if the system-under-test fails
while not miniguide_is_up():
press(Key.INFO)
# This is very verbose, and it uses an obscure Python feature: `for...else`[2]
for _ in range(10):
press(Key.INFO)
if miniguide_is_up():
break
else:
assert False, "Miniguide didn't appear after pressing INFO 10 times"
</code></pre>
Thanks for the article, I enjoyed it and it has reminded me to write up
more of my experiences with UI testing. I take it that the article's
sample code is Scala? I like its syntax for anonymous functions.<p>[1] <a href="http://stb-tester.com" rel="nofollow">http://stb-tester.com</a>
[2] <a href="https://docs.python.org/2/reference/compound_stmts.html#the-for-statement" rel="nofollow">https://docs.python.org/2/reference/compound_stmts.html#the-...</a>