I was going to call this "Selenium, the right way" but decided that was a little arrogant, even for me.
I finally have the chance to start a fresh acceptance/regression automated test harness again and I chose Selenium RC. Why? The answers are obvious. It's free, version 1.0 is stable, it supports AJAX, can be extended to support Flash, plays nicely with all kinds of languages including the one I care about (Java), supports all the major and not so major browsers, and has matured into a first-class product since I first started noodling with it back in 2006.
I wanted our implementation to start with all the best practices. What's amazing to me is that, well, there's no one place to find examples of all those practices. So as a result of my adventures, I've compiled a list of all my digging.
Here's the environment I'm working on
- OS: Windows Vista (snort)
- Main Language: Java
- IDE: Eclipse
- Unit Test Framework: JUnit 4
- Ant
Requirements:
- Need to test multiple domains and subdomains
- Need to support AJAX
- Need to support Flash
- Need to support various user roles (anonymous, regular, admin)
It seemed to me like this would be a pretty common setup. Well, I couldn't find examples of how to do this stuff. I can't find the quote (I think it's Annie Lamott) that likens the Internet to an overstuffed, disorganized garage ("I know it's in here somewhere") but I was definitely feeling that way. So I put together this list, not just for myself but for any other souls who have a similar setup.
PageObjectsMost of the basic examples tell you to simply record your steps and output them to your language of choice using the IDE and then paste that snippet into your code. Sure, that's cool, but it's not very scalable or extensible. The out of the box snippets your IDE gives to you are cool, and they're a great place to start, but they are brittle. If you want a robust set of methods that will be like building blocks for other tests, you'll have to do a little more than just paste in what the IDE spits out.
Here is a great paper on how to make your test code less brittle.
To deal with this, the Selenium people talk about using the
PageObject model. But if you aren't using Selenium 2 (which is in alpha as of this writing) then you're out of luck getting an example from them. Having just spent the last year on the bleeding edge, I wasn't excited about depending on alpha software. Furthermore, the PageObject model has it's share of detractors, including
Gojko Adzic (one of my heros).
Based on my reading, I took the approach outlined
here and
here, which is somewhat of a hybird model of PageObjects and the action groupings Adzic speaks of. Basically, I have a bunch of PageObjects. But I also have what I call a Macro object, which includes all the PageObjects and contains methods that assemble them together in often used configurations (like go to the homepage, click the sign-in link, and once on the sign in page, enter user and pass and press continue).
CSS SelectorsBuried in the Selenium documentation is a helpful tip -
CSS Selectors perform better than XPath for locators. It's true, they burn. I found a few a sites to help with CSS syntax. Unfortunately, CSS is not as fully featured as XPath and for those really tricky locators; you can still use XPath, but favor CSS Selectors.
Ultimately, the best way to control this is to use the id attribute of your element, but the test department doesn't always have control over that. In may case, the front-end developer always tries to make my life easier, but is not always able to offer me my ideal solution based on technological constraints.
Suites and TestsI really don't get why people don't have any Suite examples. It seems to me you want your suite to do a bunch of setup (like initializing the Selenium object) that is going to exist for the test and then have your test up. So I found an example of how to do this
here.
The disadvantage of this is that you never run individual tests (because you don't have a Selenium object to start if it's in your suite). Instead, you invoke the test from the Suite level, so you always need a suite. And, if you're debugging tests, you'll orphan a bunch of controllers. Not a huge deal in my opinion.
I extended both SeleneseTestCase and TestSuite because there are things I need to do in them. Specifically, I wanted to make the Selenium object available to all my PageObjects without having to pass it in specifically, so I simply created a static reference to it in my AbstractTestCase and created the getter and setter. My test suite sets up my AjaxSelenium object and sets it in the AbstractTestCase and also initializes my properties using my properties file, which it gets from the Java ClassLoader.
Extending the DefaultSelenium ObjectThe Selenium doc says you don't have to do this. However, Selenium RC doesn't implement the "waitFor..." methods from the IDE that you absolutely need if your site, like most of the sites on the Internet now, uses AJAX. I found that the best way to support AJAX is to extend the DefaultSelenium Object and found a great example right
here.
Note, this code throws the ElementNotFoundException. It apparently comes from HTMLUnit, which I'm not using. So I just created my own.
JUnitThe Selenium community clearly prefers TestNG, probably because it has matured faster. JUnit is just about caught up. I decided to convince my team to upgrade to JUnit 4.7 because I read somewhere that it has better support for parallel test runners than 4.6, the version that the concept was introduced into JUnit. Since I eventually want to run Grid, I thought it best to bite the bullet now.
The Elusive UI MapThe Selenium documentation refers to a way to reuse locators through the use of a UI Map. Most searches on UI Map and Selenium lead to the UI-Element extension. Some people really like this extension, but I am not a big JavaScript/JSON fan, so I find it annoying and frustrating to use. I decided not to incorporate UI Map because most of what it offers I'm getting from my use of the PageObject model. The way I decided to deal with locators was to simply hard-code them into the methods for the page objects. I felt this was easier than creating namespaces for all of them in my properties file. I think this also makes it easier for me to find problems. Instead of digging though a properties file to find a variable, I simply find the method that failed from the stack trace in my custom ElementNotFoundException and fix the locator.
Firefox and self-signed CertificatesI don't claim to understand anything about security. All I know was that this was a pain in the butt.
Here were the instructions I followed to get around the issue.
Incorporating Selenium into your Development IDEThere are complete instructions for how to integrate your code into IDE so you can run your tests via the TestNG or, in my case, the JUnit runner. However, I had a difficult time finding out how to make it so that I could start the server from within the IDE. Finally, I found some
helpful instructions. Then I just turned it into an
Ant script, which includes the server bit of my workaround for the Firefox self-signed certificate issue. Note, if you use the Ant Script, you'll still need to create the cert.db file by following the directions above if you have self-signed certs.
Properties fileStandard practice. There are some basic properties I need to run my tests (like user/pass stuff). I put all this in a properties file and have the AbstractSuite load it up, as described
here. In addition, I create base properties and subdomain-based override properties because the data values vary from machine to machine.
ConstantsI created a Constants interface for the things I wanted to share and simple had my AbstractTestCase extend it.
Handle Download DialogsSupposedly, Selenium 2 will do this because it has WebDriver. As I said, I didn't want to deal with Selenium 2 yet. So
this got me around the problem
LoggingSeleniumIf you want any sort of decent reporting, you'll need to download
LoggingSelenium and set this up. There's a nice example
here. Note that if you use the AjaxSelenium above, AjaxSelenium will need to extend LoggingDefaultSelenium
Other Good links*
Ajax, Selenium and Fitnesse*
FlashSelenium - This was the best link I could find on this. We are still implementing so this may merit further blogging.
*
Data Driven Testing with Selenium - I have yet to try this one but it looks promising.