0

I have a table in which data can be refreshed by selecting some filter checkboxes. One or more checkboxes can be selected and after each is selected a spinner is displayed on the page. Subsequent filters can only be selected once the previous selection has refreshed the table. The issue I am facing is that I keep getting StaleElementException intermittently. This is what I do in capybara -

  visit('/table-page') # table with default values is displayed

  # select all filters one by one. Wait for spinner to disappear after each selection
  filters.each {|filter| check(filter); has_no_css?('.loading-overlay', wait: 15)}

  # get table data as array of arrays. Added *minimum* so it waits for table
  all('tbody tr', minimum: 1).map { |row| row.all('th,td').map(&:text) }

I am struggling to understand why am I seeing StaleElementException. AFAIK Capybara uses synchronize to reload node when using text method on a given node. It also happens that sometimes the table data returns stale data(i.e the one before the last filter update)

Rahul
  • 321
  • 2
  • 14
  • does this help? http://stackoverflow.com/questions/25254584/capybara-synchronize-with-has-no-css – dax Mar 23 '17 at 14:45
  • What is `filters`? And which line do you get the StaleElement error from? – Thomas Walpole Mar 23 '17 at 14:50
  • @ThomasWalpole By filters I mean checkboxes like you see on e-commerce sites for filtering products. The exception is from the last line of the code above. – Rahul Mar 23 '17 at 14:56
  • @Rahul the use of `all` (or `first`) disables element reloading. This means if your last line runs before the table is stable you'll see that error. Does sleeping for a couple of seconds before running the last line make the errors go away? Or add a has_css before the has_no_css to make sure the overlay appears before you check it has disappeared – Thomas Walpole Mar 23 '17 at 15:01
  • @ThomasWalpole "your last line runs before the table is stable you'll see that error" - could you explain a bit more? The last line should wait for the spinner to disappear before it runs as I am explicitly giving it up to 15 secs to disappear. So technically last line shouldn't run well until the table is stable. Correct me if I am wrong. – Rahul Mar 23 '17 at 15:08
  • But yo don't check that the spinner ever existed, which means the check that it's gone can succeed before it appears. It's a race condition – Thomas Walpole Mar 23 '17 at 15:11
  • Good point. The issue here then is that if I check for the spinner to appear first and then disappear, there could still be a race condition as some of the tables I am dealing with are precomputed and spinner coming on and disappearing is blazing fast. By the time check it appears, it could very well have disappeared. Is adding a sleep(1) the only option here? – Rahul Mar 23 '17 at 15:15
  • Well since you're just using has_xxx? methods which return booleans rather than raising errors, you can specify a wait time of 1 for the hax_xxx and worst case it will behave like a sleep(1), best case it moves on faster. Adding as a full answer – Thomas Walpole Mar 23 '17 at 18:37

1 Answers1

0

The use of all or first disables reloading of any elements returned (If you use find the element is reloadable since the query used to locate the element is fully known). This means that if the page changes at all during the time the last line of your code is running you'll end up with the StaleElement errors. This is possible in your code because has_no_css? can run before the overlay appears. One solution to this is to use has_css? with a short wait time, to detect the overlay before checking that it disappears. The has_xxx? methods just return true/false and don't raise errors so worst case has_css? misses the appearance/disappearance of the overlay completely and basically devolves into a sleep for the specified wait time.

visit('/table-page') # table with default values is displayed

# select all filters one by one. Wait for spinner to disappear after each selection
filters.each do |filter| 
  check(filter); 
  has_css?('.loading_overlay', wait: 1)
  assert_no_selector('.loading-overlay', wait: 15)
end

# get table data as array of arrays. Added *minimum* so it waits for table
all('tbody tr', minimum: 1).map { |row| row.all('th,td').map(&:text) }
Thomas Walpole
  • 48,548
  • 5
  • 64
  • 78