Blog

Testing emails in Cucumber with Capybara and Gmail

Lately I have been thinking about how to write better integration tests for email scenarios in our applications. Email messaging is typically a pretty important part in the end users' experience. It's a touch point, and it should be thoroughly tested in my opinion. So I took a look at things like email-spec that certainly will work well, but this still wasn't making me feel perfectly warm and fuzzy. I wanted a more "real world" integration test. Basically I wanted to be able to say something like this in my Cucumber feature and know that it was solid!

form registration steps here

And I press "Submit" Given I login to gmail with username "test-client-name" and password "passwd" And I open email message with subject "Welcome to Awesome Town - Confirm your account" And I should see in email message "Congratulations. You can fill out a form!" And I follow verification link in email message for email "test-client-name@quickleft.com" Then I should see "You are now a member of Awesome Town!!" Then I clear my inbox

Above is an example of a feature for a sign up form that sends an email verification link. I think it reads like a Cucumber test should read. In order for this to work we first setup an email account in our google apps account called test-client-name@quickleft.com. Then we login to that account and fill in the recaptcha so that the account is all set to go. Next we switch the default view to HTML basic. This is pretty clutch because it makes it much easier to parse the page with Capybara. Once that's done you can write some steps to make your cukes run!

Given /^(?:|I )login to gmail with username "([^"])" and password "([^"])"$/ do |username,password| Capybara.apphost = 'http://www.google.com' visit('/a/quickleft.com') fillin('Email', :with => username) fillin('Passwd', :with => password) clickbutton('Sign in') click_link('Email') end

Given /^(?:|I )open email message with subject "([^"]*)"$/ do |subject| # sometimes the email is not their right away so refresh the inbox for up to 30 seconds 30.times do break if lookforemailsubject(subject) sleep(1) clicklink('Inbox') end click_link(subject)
end

Given /^(?:|I )should see in email message "([^"])"$/ do |regexp| regexp = Regexp.new(regexp) withscope("table/tbody/tr[4]/td/div[@class='msg']") do if page.respondto? :should page.should have_xpath('//', :text => regexp) else assert page.has_xpath?('//*', :text => regexp) end end end

Given /^(?:|I )follow link "([^"]*)" in email message$/ do |field| with_scope("table/tbody/tr[4]/td/div[@class='msg']") do visit(field) end end

Given /^(?:|I )clear my inbox$/ do Capybara.apphost = 'http://www.google.com' visit('/a/quickleft.com') clicklink('Email') withscope(nil) do page.all(:xpath, "//input[@type='checkbox']").each do |checkbox| checkbox.set(true) end end clickbutton('Delete') end

def lookforemailsubject(subject) withscope(nil) do page.has_link?(subject) end end

We put these steps in our stepdefinitions folder in a file called gmailsteps.rb because these steps are very specific to Gmail. For the step…

And I follow verification link in email message for email "test-client-name@quickleft.com"

We place that into our user_steps.rb file because we feel it is more specific to the user model in the app. Here is an example:

Given /^(?:|I )follow verification link in email message for email "([^"]*)"$/ do |email| user = User.findbyemail(email) withscope("table/tbody/tr[4]/td/div[@class='msg']") do visit("http://0.0.0.0:9887#{userverifypath(user.perishabletoken)}") end end

Be sure to set the mailer host in your cucumber environment correctly. For us that's "0.0.0.0:9887" because we are using thin to run our @selenium Features. You might also want to use something like Mail Magnet to transform all your out-going email recipients to your test address. We had to modify this a bit because we ran into issues with Gmail thinking the message body was an attachment. Specifically we had to comment out where Mail Magnet pre-pends the original address to the message. We just unpacked the gem for now and fixed it up, but later we will probably roll our own. We also use delayedjob to queue up our emails so we add a before hook that starts delayedjob and an after hook that kills it. That way the emails are really sent out and the whole process is tested for real.

There is one downside to all this. It adds some time to your test run. I would advise tagging the Features that use this so you can maybe exclude them if you don't feel the need to run them. Overall this solution is a really good "real world" way to test your emails. Maybe there is a better way though? I would love to hear if anyone else has come up with other solutions.