I’ve lately started to use Cucumber and Selenium (via Capybara) “for real” to be used as the testing mechanism on my latest Rails project. In fact, I’ve been relying on it quite heavily to test views : I skipped the RSpec tests for views, routes, and others, and only use RSpec for unit and controller specs. The rest is tested through Cucumber, since it drives the entire stack; previously, I would make some dumb mistakes where (e.g.) some form fields wouldn’t appear, but my views specs didn’t catch it.
In other words, I find this combination quite nice: it gives me the warm fuzzy feeling of knowing the application is tested exactly like a user would see it, without excessive redundancy, and it also allows me to force failures (e.g. in controller specs) to ensure the app responds correctly in cases that aren’t testable (or are annoying to test) through the GUI.
That said, I’ve run into a few issues that took me a few minutes to solve and that I’ve since found ridiculously useful in my testing (and reducing redundancy in Cucumber features).
Testing for content in an HTML container
Let’s say you have an index page that show a list of customers, and in the sidebar it displays a list of recently viewed customers.
You want to test the “history” functionality, so it’s natural to go for something like
Given I am on the index page When I visit the page for customer "John Doe" And I go to the index page Then I should see "John Doe"
The problem with this simple solution is that the test will pass even if the history functionality doesn’t work: “John Doe” will appear in the index list, so the last step will be successful.
Instead, you need to check for “John Doe” within the history sidebar with something like
Then I should see "John Doe" in the sidebar history
And for that to work, all we need to do is add the following step definition:
Then /^(.*) in the sidebar history$/ do |step| with_scope('"#history"') { Then step } end
Obviously, this assumes that my history functionality is located with an HTML container (e.g. a div) with the “history” id.[1] Then, all I have to do is say that steps that happen “in the sidebar history” are to take place in the container with the given id.
Since I already have a “Then I should see …” step defined, that’s all there is to do !
Clicking ‘OK’ in javascript dialogs/alerts
Another thing I wanted to do is simulate clicking the “ok” button in javascript dialogs, which is necessary when (e.g.) you want to delete a record and Rails ask for confirmation. Otherwise, Selenium would click the link, wait for a while and pursue with the tests. Obviously, since the delete request had never been confirmed, the test would fail.
What I really wanted to have was a step definition like
When I follow the "Delete" link for customer "Alice Angry"
Based on a Stackoverflow question, I added the following to web_steps.rb to solve the problem:
module JavascriptHelpers def click_ok_after begin page.evaluate_script("window.alert = function(msg) { return true; }") page.evaluate_script("window.confirm = function(msg) { return true; }") rescue Capybara::NotSupportedByDriverError # do nothing: we're not testing javascript ensure yield end end end World(JavascriptHelpers)
Then, making the desired step work simply required adding the following step definitions:
When /^I follow the "([^"]*)" link for customer "(\w+)\s(\w+)"$/ do |link, first_name, last_name| customer = Customer.find_by_first_name_and_last_name(first_name, last_name) When %Q{I follow "#{link}" within "#customer_#{customer.id}" and click "OK"} end
When /^(.*) and (?:|I )click "OK"$/ do |step| click_ok_after { When step } end
In the interest of full disclosure, I also had to modify this step definition:
When /^(.*) within (\S*[^:])$/ do |step, parent|
(The difference is \S replacing the ‘.’ wildchar.)
Now I can simply have a step saying
When I follow the "Delete" link for customer "Alice Angry"
Best of all, with the code above, this step definition will work in both the basic case (driven by Capybara), and with Selenium (i.e. a scenario using the @javascript tag). This desired behavior is the reason behind the Capybara::NotSupportedByDriverError rescue in the click_ok_after function definition: I wanted to use the same step definition regardless of whether I was in a @javascript scenario or not.
So there you have it: a few Cucumber step definitions to make your life easier and you scenarios more readable.
[1] Also, it uses the steps automatically defined in web_steps.rb