Read more

Beware: Nested Spreewald patiently blocks are not patient

Judith Roth
April 11, 2017Software engineer at makandra GmbH

Nested Spreewald patiently blocks are now patient (version 1.10.4+)

Note: The behaviour of Spreewald's within step is as described below for version < 1.9.0; For Spreewald >= 1.9.0 it is as described in Solution 1.


Illustration online protection

Rails professionals since 2007

Our laser focus on a single technology has made us a leader in this space. Need help?

  • We build a solid first version of your product
  • We train your development team
  • We rescue your project in trouble
Read more Show archive.org snapshot

When doing integration testing with cucumber and selenium you will often encounter problems with timing - For example if your test runs faster than your application, html elements may not yet be visible when the test looks for them. That's why Spreewald Show archive.org snapshot (a collection of cucumber steps) has a concept of doing things patiently, which means a given block that fails will be retried for a certain amount of time. Only if the time is up and the block still fails an error is thrown. This reduces flickering of tests.


From Spreewald's tolerance_for_selenium_sync_issues.rb:

def patiently(seconds = CapybaraWrapper.default_max_wait_time, &block)
  old_wait_time = CapybaraWrapper.default_max_wait_time
  # dont make nested wait_untils use up all the alloted time
  CapybaraWrapper.default_max_wait_time = 0 # for we are a jealous gem

  ... # call block and retry if necessary and time allows

ensure
  CapybaraWrapper.default_max_wait_time = old_wait_time
end

This means all patiently-calls that are nested within another patiently call are not patiently. They do never retry because the wait time for them is 0. It seems like a good idea not to "waste" all the time for retries in the most deeply nested patiently-block, but it may lead to unexpected behaviour, especially if the outermost patiently is "hidden".


For example consider the following step, which wants to interact with a "picker"-element. As there are multiple pickers on the page, they are wrapped in divs with numbers so that the test knows which one to operate:

# Feature
When I pick "Lions" from the album picker within ".media-picker-1"
# Step definition
When /^I pick "(.+?)" from the album picker$/ do |label|

  ... # open the picker and choose an item
  patiently do
    ... # close picker and ensure the correct item is selected
  end
  
end

Looks good, right? - Well, but unfortunately the patiently in the step does nothing because it's nested! Spreewald's within-step will use patiently. The given selector may not yet be visible - maybe it is appended via ajax or the like and therefore the step will patiently wait until the element appears. From Spreewald's web_steps.rb:

When /^(.*) within (.*[^:])$/ do |nested_step, parent|
  patiently do
    with_scope(parent) { step(nested_step) }
  end
end

Therefore all patiently calls of the step itself have a wait time of 0 - they are not patiently at all.

Solution 1

This is the preferred solution.
Just don't nest. Refactor the outer patiently to really only wrap the part of the code that needs to be patiently. For the example with Spreewald's within-step a better solution would be to wrap the patiently only around a check that the parent element is visible and then (not patiently) call the step within that scope:

When /^(.*) within (.*[^:])$/ do |nested_step, parent|
  patiently do
    page.should have_css(_selector_for(parent))
  end
  with_scope(parent) { step(nested_step) }
end

Spreewald implements this behaviour since 1.9.0

Solution 2

If the outer patiently is not within your reach, you can explicitly tell the inner patiently how long it should wait (in seconds). This overrides the default. Its a good idea to make the inner wait time shorter than the outer wait time, otherwise the outer patiently can not retry if the inner patiently fails. E.g.:

When /^I pick "(.+?)" from the album picker$/ do |label|

  ... # open picker and choose an item
  patiently 2 do
    ... # close picker and ensure the correct item is selected
  end
  
end
Judith Roth
April 11, 2017Software engineer at makandra GmbH
Posted by Judith Roth to makandra dev (2017-04-11 11:12)