Five page object anti-patterns

I’ve observed some page object anti-patterns which commonly arise when starting out designing end-to-end automated tests. Chris McMahon recently asked for some feedback on his initial test spike for Wikipedia, and some of these anti-patterns were present.

Anti-pattern one: frequently opening and closing browsers

I often see both RSpec and Cucumber tests frequently opening and closing browsers. This slows down test execution times, and should be avoided unless absolutely necessary.

You can clear cookies between tests if you’re worried about state.

To open and close the browser only once in Cucumber, specify this in your env.rb file:

browser = Watir::Browser.new

Before do
  @browser = browser
end

at_exit do
  browser.close
end

To open and close the browser only once in RSpec:

browser = Watir::Browser.new

RSpec.configure do |config|
  config.before(:each) { @browser = browser }
  config.after(:suite) { browser.close }
end

Anti-pattern two: hard coding URLs on page classes

Chances are you’ll at some point run your automated tests in different environments, even if it’s just to verify that production has been updated correctly. If you’ve hard coded URLs in page classes, this can be problematic.

Fortunately it’s easy to avoid, by creating a module that contains base URLs which can be accessed by page classes. These base URLs can be stored in YAML files which can be switched for different environments.

module Wikipedia
  BASE_URL = 'https://en.wikipedia.org'
end

class BogusPage
  include PageObject
  page_url "#{Wikipedia::BASE_URL}/wiki/Bogus_page"
end

Anti-pattern three: pages stored as instance variables in steps or rspec specs

I don’t like seeing pages stored as instance variables (those starting with an @) in Cucumber steps or RSpec specs, as it introduces state and thus more room for error.

If you’re using the page-object gem, there are two methods available to access pages directly without using instance variables: visit_page and on_page (also visit or on from 0.6.4+). Both of these can be used as blocks, so you can perform multiple actions within these methods.

visit LoginPage do |page|
  page.login_with('foo', 'badpass')
  page.text.should include "Login error"
  page.text.should include "Secure your account"
end

Anti-pattern four: checking the entire page contains some text somewhere

I often see people checking that the entire web page contains some expected text. Even if the text was at the very bottom of the page hidden in the footer the test would probably pass.

You should check the text is where it should be, using a container that it should belong to. Ideally a span or a div may exist that contains the exact text, but even if it’s in a slightly larger container it is still better than asserting it exists somewhere on the page.

class BogusPage
  include PageObject
  cell :main_text, :class => 'mbox-text'
end

visit_page BogusPage do |page|
  page.main_text.should include 'Wikipedia does not have an article with this exact name'
  page.main_text.should include 'Other reasons this message may be displayed'
end

Anti-pattern five: using RSpec for end-to-end tests

This one is contentious, and I am sure I’ll get lots of opinions to the contrary, but I believe that RSpec is best suited to unit/integration tests, and Cucumber is suited to end-to-end tests.

I find I create duplication when trying to do end-to-end tests in RSpec, which is where Cucumber step definitions come in. Trying to do unit tests in Cucumber seems like too much overhead, and in my opinion is more suited to RSpec.

8 thoughts on “Five page object anti-patterns

  1. I understand and completely agree with points 1, 2 & 4.

    For point #3, can you give me an example of how using an instance of a page object can cause a problem? If I have 20 unit tests in a spec file for a given page object, what is the difference between using let(:page) {Page.new(driver)} once at the top of a file and writing Page.new(driver) for every single one of my tests?

    I’m most curious about point #5. What do you find you find gets duplicated with your RSpec when doing end-to-end tests? I’m not noticing any obvious redundancy for how I’m using & passing my page objects, so I suspect I’m missing something.

    Thanks!
    Titus

    • #3: I don’t see any problem with using let for a given page object. The problem becomes when you have 20 pages mixed throughout a spec or step definitions and you can’t remember what you referred to it as previously.

      #5: I imagine things like logon and navigating to certain parts of the system under test may be duplicated. Again, there are probably ways to reduce this in RSpec which doesn’t make it a problem, but I have personally seen quite a lot of duplication when RSpec is used for end-to-end tests over say Cucumber where you have a common step that can be re-used by multiple scenarios.

      It sounds like you’re on top of it so I wouldn’t be concerned!

  2. Regarding #1 – how can you handle parallel execution of test cases when you have more than one machine running them?

  3. Hey Allister,

    I’m currently running a test script using the template from the watir webpage on rspec.

    http://wiki.openqa.org/display/WTR/RSpec

    but I definitely want to have the speedup by not opening and closing the browser every time I run a test.

    How do I incorporate your code “To open and close the browser only once in RSpec” into my codebase?

  4. Hey Alister,

    I’m currently using the template from http://wiki.openqa.org/display/WTR/RSpec in my test suite which uses RSpec and Watir-Webdriver..

    how do I incorporate your code to “open and close the browser only once in RSpec” so that I can experience a speedup in my test suite? I tried copy/pasting your code to the top of my codebase but that didn’t seem to work.

Comments are closed.