Tag Archives: Spring

Writing good Selenium tests with Page objects and Spring

I have to admit, a couple of years ago I wasn’t a huge fan of Selenium. The project I’m selenium-logoassigned to is a quite large and complex customer web portal and has always been in need of automated GUI checks. Several attempts have been made over the years, each failing miserably. I believe the worst one was when a team of off-site contractors was hired to create a proof of concept. They worked hard for months, struggling with the business logic and page design and came up with a huge set of tests. Some months later we threw them all away.

The reason they failed? The tests weren’t readable so we couldn’t easily understand or extend them and they became a nightmare to maintain. They were written in the classic script form were every other line is either a “driver.findElement(…)” or “element.sendKeys(…)”, obscuring the purpose of the test. Basically they resembled the output from the Selenium IDE Firefox plugin. I have often been told that it’s a good thing that everyone can create tests. That a person with little or no programming skill can record their actions when they interact with the site and turn this into a test case. I fail to comprehend this. I don’t believe there is any value in creating large amounts of tests in little time. The value is when you have a suite of well written, easy to understand test cases that can be maintained at a low cost. And that can only be accomplished by skilled programmers.

When I came back from a couple of months of paternity leave, my team had decided to make a new attempt at creating Selenium tests for the product. Since the whole team was committed I decided to support the effort. But I was also determined to come up with a design where we could write readable and maintainable tests, composed of reusable parts. That’s when I stumbled upon the Page object pattern.

The Page object pattern is a design pattern where the logic of navigating and interacting with the site is separated from the actual test code. Each page on the site is represented by a class containing the logic for locating the elements on that page. The pattern can also be applied to parts of pages, where a page object can represent for example a lightbox. The Selenium framework supports this pattern by providing a utility for setting up page objects,  the PageFactory class. This class uses reflection to find all fields of type WebElement in a page object. Each field is proxied allowing for a lookup of the corresponding HTML element when the field is accessed. By default, the name of the field is matched against the id of the HTML element, but the field can also be annotated with an @FindBy expression for matching against class names, css, xpath, etc.

Applying the pattern to our tests really cleaned them up but I wanted to take things one step further by also applying dependency injection. By doing this I could remove the boilerplate code of calling the PageFactory in each page object. My page objects became singleton beans which could be injected into test classes and into other page objects.

The team approved of my design and just last week we refactored the last of the old tests to use the new framework. We now have a good set of readable tests and we have page objects covering most of the functionality on the site, meaning it’s very cheap to add new tests. I will write a follow-up to this post explaining the other cool stuff we have done with the new framework.

Creating the framework

The framework we have today has a lot of extra features not mentioned above, but I will try to extract the basic classes needed for working with page objects and Spring. I start by creating a base class for my Selenium test cases. This is mostly a standard Spring context test case, but it also adds my own test execution listener.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SeleniumTestCaseContext.class)
@TestExecutionListeners({ SeleniumTestExecutionListener.class,
 DependencyInjectionTestExecutionListener.class })
public abstract class SeleniumTestCase {}

The Spring application context configuration is simple. Most beans are created from a component scan, but I also create a Selenium Firefox driver bean since I want to be able to inject it into my page objects. Drivers can’t be reused between tests so I have created a custom scope for the driver bean called “test”. This scope will create a new bean before each test.

@Configuration
@ComponentScan(basePackageClasses = SeleniumTestCaseContext.class)
public class SeleniumTestCaseContext {

 @Bean
 public TestScope testScope() {
  return new TestScope();
 }

 @Bean
 public CustomScopeConfigurer customScopeConfigurer() {
  CustomScopeConfigurer scopeConfigurer = new CustomScopeConfigurer();
  Map<String, Object> scopes = new HashMap<>();
  scopes.put("test", testScope());
  scopeConfigurer.setScopes(scopes);
  return scopeConfigurer;
 }

 @Bean
 @Scope("test")
 public WebDriver webDriver() {
  return new FirefoxDriver();
 }
}

The test scope implementation is trivial. It holds a map where test scoped beans are cached, ensuring that only one instance is created. It also has a reset method for clearing the cache before each test run.

public class TestScope implements Scope {

 private Map<String, Object> cache = new HashMap<>();

 public void reset() {
  cache.clear();
 }

 @Override
 public Object get(String name, ObjectFactory<?> objectFactory) {
  if (!cache.containsKey(name)) {
   cache.put(name, objectFactory.getObject());
  }
  return cache.get(name);
 }

 @Override
 public Object remove(String name) {
  return cache.remove(name);
 }

 @Override
 public void registerDestructionCallback(String name, Runnable callback) {}

 @Override
 public Object resolveContextualObject(String key) {
  return null;
 }

 @Override
 public String getConversationId() {
  return null;
 }
}

Next up is the test execution listener which was registred in the base test case above. The listener has two responsibilities: resetting the test scope before each test and closing the Selenium driver, i.e. the browser, after each test.

public class SeleniumTestExecutionListener extends AbstractTestExecutionListener {

 @Override
 public void beforeTestMethod(TestContext testContext) throws Exception {
  testContext.getApplicationContext().getBean(TestScope.class).reset();
 }

 @Override
 public void afterTestMethod(TestContext testContext) throws Exception {
  testContext.getApplicationContext().getBean(WebDriver.class).quit();
 }
}

My page objects are also Spring beans and I create my own @Component annotation for them.

@Target(TYPE)
@Retention(RUNTIME)
@Component
public @interface PageObject {}

The final step is to have Selenium initialize my page beans, i.e. to proxy all WebElement fields in them. This initialization is done in my bean post processor.

@Component
public class PageObjectBeanPostProcessor implements BeanPostProcessor {

 @Autowired
 private WebDriver driver;

 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  if (bean.getClass().isAnnotationPresent(PageObject.class)) {
   PageFactory.initElements(driver, bean);
  }
  return bean;
 }

 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  return bean;
 }
}

I now create two page objects: Site which is the root page object for accessing other pages and AccountPage which is a page with a simple form.

@Page
public class Site {

 @Autowired
 private WebDriver driver;

 @Autowired
 private AccountPage accountPage;

 public AccountPage gotoAccountPage() {
  driver.get("http://localhost/account.html");
  return accountPage;
 }
}

@Page
public class AccountPage {

 private WebElement firstName;
 private WebElement lastName;

 @FindBy(css = "input[type='submit']")
 private WebElement submitButton;

 public AccountPage firstName(String text) {
  firstName.clear();
  firstName.sendKeys(text);
  return this;
 }

 public AccountPage lastName(String text) {
  lastName.clear();
  lastName.sendKeys(text);
  return this;
 }

 public AccountPage submit() {
  submitButton.click();
  return this;
 }
}

I like giving my page objects a fluent interface making it easy to chain stuff like filling out and submitting a form. The last step is of course to create a test case.

public class PageTest extends SeleniumTestCase {

 @Autowired
 private Site site;

 @Test
 public void test() {
  AccountPage accountPage = site.gotoAccountPage();
  accountPage.firstName("Peter").lastName("Kedemo").submit();
 }
}

Even though this isn’t much of a test, it should be clear how easy it is to create readable tests with this approach.