Page object pattern

We all love tests and with TDD comes a lot of it.

Integration tests have also become part of development cycle with adoption of BDD and also due the simplicity with which they can be implemented.

For example with scala and play we write a lot of tests like this -

class IntegrationSpec extends PlaySpec with ... {
  "User" should {
    "be able to sign-up in the application" in {
       //Act
       browser.goTo("/signUp")
       browser.find("input#firstName").text("first")
       browser.find("input#lastName").text("last")
       browser.find("input#email").text("email@domain.com")
       browser.find("input#password").text("password")
       browser.find("#signup_form").submit
       //Assert
       browser.pageSource must contain("Welcome, first last!")
    }
  }
}

But I really find the above code difficult to read. Worse is when you start testing long interactions and its difficult to distinguish between the Arrange, Act and Assert part of the test.

And then you have browser interactions like below. Looking at which you are sure its something you should not touch.

//This is real, I didn't make it up.
browser.$("#report_summary > .row > div + div + div + div .data-display").getText mustEqual 100

At my current firm we follow TDD so we had lots of such integrations tests with WebDriver API’s. It was increasingly becoming difficult to maintain and modify as the UI would change frequently.

We did a lot of things to improve it. Added common helper like login and signup methods to the browser. But then it was the helper class that become too long and difficult to read.

Then we decided to start using the page object pattern and the above test code was changed to this -

class IntegrationSpec extends PlaySpec with ... {
  "User" should {
    "be able to sign-up in the application" in {
      // Act
      signUpPage.open()
      signUpPage.signUp(
        firstName = "first",
        lastName = "last",
        companyEmail = "email@example.com",
        password = "pass"
      )
      //Assert
      browser.pageSource must contain("Welcome, first last!")
    }
  }
}
//Sign-up page object
class SignUpPage(implicit val browser: TestBrowser) extends Page {
  def open(): Unit = browser.goTo(UserController.signUp().url)
  def signUp(
    firstName: String,
    lastName: String,
    email: String,
    password: String,
  ): DashboardPage = {
    browser.$("input#firstName").text(firstName)
    browser.$("input#lastName").text(lastName)
    browser.$("input#email").text(companyEmail)
    browser.$("#signup_form").submit
    
    new DashboardPage
  }
}

As you can see from above, we stopped manipulating the HTML in the tests. We started invoking application API’s and moved the HTML logic to the page object.

The page object wraps an HTML page, or fragment, with an application-specific API, allowing you to manipulate page elements without digging around in the HTML.

Page objects are a good example of encapsulation - they hide the details of the UI structure from the tests.

Some useful rules to follow when using this pattern:

If unlike me you are using ScalaTest instead of Specs2. Then you can use the Page trait provided by ScalaTest and write cool stuff like below -

class HomePage extends Page {
  val url = "localhost:9000/index.html"
}

val homePage = new HomePage
go to homePage

Conclusion

In conclusion, using page object pattern makes our tests more robust to changes in the UI.

And when your company employs a new UX guy to revamp your UI you know exactly which part of integration tests will break.

References

https://github.com/SeleniumHQ/selenium/wiki/PageObjects

http://www.scalatest.org/user_guide/using_selenium#pageObjects