I have been working on the Selenium Webdriver automation of a website which requires all new users to confirm via an email as part of the registration flow.
This presents a challenge to test automation as we obviously want to verify the registration process post email confirmation and if we do not have a suitable API to create test users, this may be a key process for all tests to use; fortunately in my case there was an API to create users but I was still keen to prove the entire registration flow.
Once we have cracked the problem of generating new accessible email addresses for each tests such as adding "+" to the end of Gmail addresses or using a temporary provider such as Dispotable, there are a few options open to us;
Don't automate
"Hey, we have to accept that not everything can be automated, we still need manual testers, let them do it"
I just can't let that happen! I want to automate everything ;-)
Automate an email client
"Gmail is a webpage, we can automate a web page, therefore we can automate Gmail"
Well, yes you can BUT you have even less control over this GUI then the one you are testing. How often are you going to have to maintain tests because Google decide to change the layout? Do you really want to test someone elses website? Your tests are going to be flakey enough without adding a 3rd party GUI into the mix!
So..
Having chosen Gmail as my email provider (I just added a GUID to the end of a base email address), I then decided to use the Gmail IMAP API to access. It was then easy to call a method in my test which polled Gmail API until an email arrived addressed to my new email address. Once I had this email, I could extract the HTML of the email, but what to do with this HTML????
Initially I did what I subsequently found others had done; extract the URL of the required link using somekind of HTML parser of XPATH engine, then use the Navigate method in Selenium to navigate the browser to this address (see
http://www.seleniumtests.com/2011/08/verify-email-confirmation-using.html ).
While this works, it still doesn't quite feel right. What if the link is hidden? What if we want to validate other aspects of the email?
I then stumbled upon something which should have been obvious; I was using Saucelabs and had a couple of problems;
1. Tests would time out if no command was received within 90 seconds
2. The video of the test would look very slow as we would be stuck on one page without any obvious processing going on, and everybody hates slow tests!
To fix this I decided to use Selenium to inject simple HTML (I only know simple HTML!!!) displaying a message on a blank browser so any viewer of the video would know what was going on. This would then be injected every 30 seconds to keep Saucelabs happy as well.
Then it was either a "DOH!" or 'Eureka" moment; if I can inject my HTML, then I can inject the email's HTML as well.
So that's it, the idea of Email objects was born. Emails are not different from web pages. They are HTML, they have a DOM, they can be processed, they can navigate to other pages. They can have links, they can have buttons, they are just HTML pages.
I had to escape some offending characters which resulted in java script errors, but apart from that it was surprisingly easy and appears to work across platforms and browsers. I created a base email object which had very similar methods and properties to a base page object such as Load(), IsOnEmail(), ClickLink(int idx), WaitForLoad(), etc. The base email object can then wrap a lot of the boilerplate code as well, so adding a new type of email is very easy
I've only be using Email Object this for a week, so I am not sure of the best approach yet, and would love to hear from others, and if interested I can share code examples.
Here's an example of what a test using email objects looks like;
.......
registrationPage.ClickSendEmail();
var confirmationEmail = new ConfirmationEmail(_driver, "test+hgjhgjgj@gmail.com";
confirmartionEmail.WaitForEmail();
Assert.IsTrue(confirmationEmail.IsOnEmail());
var registrationFinalPage = confirmationEmail.ClickConfLink();
........