2

With ruby, I'm doing some cucumber tests with capybara to test a Shiny application (shiny is a R package to do web applications). But I'm a newbie with ruby and cucumber (and this is my first post here).

I have a scenario like this:

Given a user uses the application 
When he sets the x-axis limits "10,20,30"
  And he clicks on the changeaxis button
Then the graph has no error

Currently, I expect that this test should fail, i.e. there should be an error on the graph.

The application displays a graph in a div with id graph. When the user sets the axis limits "10,20,30" and clicks on the "changeaxis" button, there is an error in the application: Shiny assigns the class error to the div containing the graph. Then my above scenario tries to check that the div with id graph containing the class error does not exist:

Then (/^the graph has no error$/) do
  @screen.graph_page.check_graph_has_no_error
end

def check_graph_has_no_error
  check_element_is_not_present GRAPH_ERROR 
end

GRAPH_ERROR = "//div[(@id='graph') and (@class and contains(concat(' ', normalize-space(@class), ' '), ' error '))]"

def check_element_is_not_present xpath
  should have_no_selector(:xpath, xpath)
end

The test does not fail. That's because things go too fast: the class error is assigned on the element <div id="graph"> only after the user clicks the button.

I have a workaround using sleep:

Given a user uses the application 
When he sets the x-axis limits "10,20,30"
  And he clicks on the changeaxis button
  And he waits for 5 seconds
Then the graph has no error

And (/^he waits for (\d+) seconds?/) do |n|
  sleep(n.to_i)
end

Here the test fails, as expected.

But is there a way without using sleep?

2 Answers 2

1

This is the problem with negative assertions since Capybara has no way to know how long any actions are going to take on the page, so really can't know that even though the assertion is passing now you really want it to wait longer to make sure it keeps passing. What you really need is a positive assertion before the negative assertion to assure the page is in the correct state. What that would be depends on exactly what happens when you click the button, but would have to be something that definitely will change on the page and also indicates the error class would have been set on the graph by then if it's going to be set. If there is nothing like that available then you have run into the one place it actually makes sense to use Capybaras predicate methods while testing. You can do whatever the equivalent in your page object model is of

expect(page.has_css?('#div.error', wait: 3)).to eq false

This works because it will wait for the entire 3 seconds, if necessary, looking for the div with class 'error' before returning. Adjust the 3 as necessary for certainty that your action will have completed but you're not wasting too much extra time.

Note: You should be preferring CSS over XPath when possible. It's faster, easier to read, easier to write correctly, and easier to maintain. In your use of XPath you are breaking scoping of elements (probably/usually unintentionally) by starting your XPath with // rather than .// - see https://github.com/teamcapybara/capybara#beware-the-xpath--trap

1

Use

expect(page.has_css?('#div.error', wait: 3)).to be true

If the expression is true (i.e. error exists) at the outset, it will return immediately, i.e. will pass.

Otherwise, it will use a polling wait for up to 3 seconds to see if the condition changes.

The difference between using fixed ('sleeps' or 'implicit wait') is they they will always wait and slow down your suite (massively) and fail intermittently. Polling waits (explicit waits) poll for the condition repeatedly for a condition for as long as you specify. You can give long periods because it'll wait as long as needed (to become true) and no longer. The only time it waits a longer time is when it fails. Which should be fairly rare and indicate other problems.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.