Mark Needham

Thoughts on Software Development

Scala, WebDriver and the Page Object Pattern

with 6 comments

We’re using WebDriver on my project to automate our functional tests and as a result are using the Page Object pattern to encapsulate each page of the application in our tests.

We’ve been trying to work out how to effectively reuse code since some of the pages have parts of them which work exactly the same as another page.

For example we had a test similar to this…

class FooPageTests extends Spec with ShouldMatchers with FooPageSteps {
  it("is my dummy test") {
    ...
    then(iShouldNotSeeAnyCommonLinks())
  }
}

…where FooPageSteps extends CommonSteps which contains the common assertions:

trait FooPageSteps extends CommonSteps {
  override val page = new FooPage(driver)
}
trait CommonSteps {
  val page : FooPage
  val driver: HtmlUnitDriver
 
  def iShouldNotSeeAnyCommonLinks() {
    page.allCommonLinks.isEmpty should equal(true)
  }
}

FooPage looks like this:

class FooPage(override val driver:WebDriver) extends Page(driver) with CommonSection {
 
}
 
abstract class Page(val driver: WebDriver) {
  def title(): String = driver.getTitle;
}
 
trait CommonSection {
  val driver:WebDriver
  def allCommonLinks:Seq[String] = driver.findElements(By.cssSelector(".common-links li")).map(_.getText)
}

We wanted to reuse CommonSteps for another page like so:

trait BarPageSteps extends CommonSteps {
  override val page = new BarPage(driver)
}
 
class BarPage(override val driver:WebDriver) extends Page(driver) with CommonSection {
 
}

But that means that we need to change the type of page in CommonSteps to make it a bit more generic so it will work for BarPageSteps too.

Making it of type Page is not enough since we still need to be able to call the allCommonLinks which is mixed into FooPage by CommonSection.

We therefore end up with the following:

trait CommonSteps {
  val page : Page with CommonSection
  val driver: HtmlUnitDriver
 
  def iShouldNotSeeAnyCommonLinks() {
    page.allCommonLinks.isEmpty should equal(true)
  }
}

We’re able to mix in CommonSection just for this instance of Page which works pretty well for allowing us to achieve code reuse in this case!

Be Sociable, Share!

Written by Mark Needham

August 9th, 2011 at 12:54 am

Posted in Scala

Tagged with ,

  • Fabio Pereira

    Nice post Mark. I always have this problem when writing tests and I usually use composition instead of inheritance to reuse things in multiple pages…

  • Jeff Pipas

    I’m just getting started with both Scala, WebDriver and ScalaTest.  That being said, your snippets here have helped me a lot.

    One question I have, is with regards to how you actually instantiate the driver val.  Where is that done, and where does that live?  Specifically if you have say multiple test runs, that may or may not involve all of the various Pages.

    Also can you define more what you would find (methods) in say the LoginPage class as compared to say the LoginPageSteps trait?

    I appreciate it!

  • Jeff Pipas

    I’m actually going to amend my question, and pose another (or two).  I’ll preface this replay, with this:  Part of the problem may be that I don’t fully understand whats going on “under-the-hood” with Scala traits and classes.  That in mind, please bare with me.

    I was able to successfully get the driver to “start” – by instantiating it in the CommonSteps trait (in my case – val driver: FirefoxDriver = new FirefoxDriver).  Awesome (if that’s in fact where it should be done!!)  However, I’m getting stuck at extending it past the initial “Page” object.

    For an example, lets say my test spans multiple pages for example FooPage and BarPage.  I’ve gone so far as to create a new class BarPage which extends Page with CommonSection.  Now, I’m assuming in each of the Page classes, I should be referencing elements on that page (with the driver.findElement(By) syntax).  Is that correct?  Meaning, my FooPage should model the UI of page Foo, and likewise BarPage should model the UI of the Bar page of my web app?

    If that’s a correct assumption, my FooPageSteps should contain “interactions” that I can do on FooPage  (ie page.element.click(), page.element.sendKeys(), etc.).   Likewise BarPageSteps should contain the interactions with the objects of BarPage, correct?

    However, when I try to add “with BarPageSteps” to the main test class (FooPageTests) – It’s telling me I can’t override the value page in the traits FooPageStep and BarPageStep, and that they are of incompatible types.

    Am I approaching this wrong?  Any guidance you can give would be appreciated.

  • http://www.markhneedham.com/blog Mark Needham

    Hey,

    We’ve actually run into the same problem doing exactly the same thing as you. I don’t know that we’ve found a good solution yet though…

    The majority of our tests don’t have that problem because we’re testing each of the pages separately.

    If I remember our current hack is to name the pages differently in the tests which have multiple pages. e.g.fooPage, barPage, etc.

    We haven’t been able to think of a clean way to get around it unfortunately.

  • Jeff Pipas

    Yes.  That’s what I’ve started doing.  However, I get “Element not found in cache” errors frequently, as the application switches from page to page.  Which, I still can’t figure out how to overcome. 

    Of note, I’m actually running tests against a cloud app (NetSuite), that I don’t even have “full source” access to – so needless to say the ability for me to define a page (and it’s elements and “actions/steps”) is of critical importance.

    It’s almost too much effort to warrant even testing like this.  At this pint, it’s still faster (and more efficient) for us to have a real person sit and run through scenarios outlined in an excel spreadsheet (::shudder::).

  • Gantcho

    Second that. I use aggregation instead of inheritance when assembling my Page Objects. However, my tests are in Java. I want to start using Scala. Not sure this would be the best way to go with Scala.