Page Objects: Where do you put your assertions?

April 12, 2016

I started writing about Page Objects earlier this month, and as soon as you start talking about Page Objects, you need to have an opinion about where to put your assertions. Do you ask the page if it is in the state it expects to be or do you ask the page for values and assert that they are what you expect them to be in the test?

If you look at FluentLenium and Simplelenium on GitHub, you’ll notice that their corresponding READMEs describe the usage of the Page Object pattern. Now, if you take a closer look, you’ll also notice that they have an opinion: they’ve included the assertions in the definition of such Page Objects.

Now, my first experience with Page Objects was actually under these conditions. The Page Objects were the main driver of the acceptance tests and they contained all the information about the interaction with the page, as well as the assertions about whether the page was in the right state or not. The team felt great about using this implementation of Page Objects, but something felt a little off. Let’s take a look.

Revisiting the acceptance test for the restaurants application

Let’s rewrite that acceptance test we defined at the end of our first Page Objects post:

# spec/features/reviewing_a_restaurant_spec.rb
# ...
feature 'Reviewing a restaurant' do
  # ...

  scenario 'user writes a two-star review for a restaurant' do
    user_signs_in
    user_searches_for_a_restaurant
    user_writes_a_two_star_review
    user_sees_two_star_review_on_restaurant
  end

  # ...

  def user_sees_two_star_review_on_restaurant
    expect(restaurant_page.name).to eq "Freddy's Unclean Steakhouse"
    expect(restaurant_page.has_success_message?).to be true
    expect(restaurant_page.success_message).to eq 'Your review has been saved!'
    expect(restaurant_page.reviews.count).to eq 1

    review = restaurant_page.reviews.first
    expect(review.rating).to eq 2
    expect(review.comments).to eq 'Did NOT like it'
  end
end

Now, we’ll change it to have the assertions in the Page Objects:

# spec/features/reviewing_a_restaurant_spec.rb
# ...
feature 'Reviewing a restaurant' do
  # ...

  scenario 'user writes a two-star review for a restaurant' do
    user_signs_in
    user_searches_for_a_restaurant
    user_writes_a_two_star_review
    user_sees_two_star_review_on_restaurant
  end

  # ...

  def user_sees_two_star_review_on_restaurant
    restaurant.assert_name "Freddy's Unclean Steakhouse"
    restaurant.assert_success_message 'Your review has been saved!'
    restaurant.assert_number_of_reviews 1

    review = restaurant_page.reviews.first
    review.assert_rating 2
    review.assert_comments 'Did NOT like it'
  end
end

That doesn’t look too bad, right? It’s basically the same number of lines, except with the word “assert” instead of “expect”, right? Well, to be quite honest, when I did it this way last time around, my experience was so bad that I stopped using Page Objects altogether for a couple of years and only came back around them some time towards the end of last year.

So, what’s the problem with this approach? I’m glad you ask! To name a few:

  • The Page Objects have more than one obvious responsibility. In this implementation, the Page Objects defined represent not only the elements displayed in the page and their corresponding attributes, but also the correctness of such elements.
  • The test has a clear intent, but sneakily so. Even though we have methods that describe the intent of the test, such as assert_name, assert_rating, etc., we cannot see as easily what the test is actually about. We’ve successfully hidden both the HTML interaction details as well as the assertion details (this is not a good thing).
  • When the test fails in a particular assertion, you have to look in another file for the expectation. Also, exceptions are harder to pinpoint.
  • Changing a Page Object is less reliable. When a Page Object needs to change, you might not only be changing how it extracts values from the HTML, but also how the assertions themselves work, since it has more than one responsibility. If you’re not careful, this might inadvertantly affect unrelated tests.
  • No convention for assertions. Not having a clear framework for the assertions (e.g. expect(...).to ...) means that it is harder to find what you’re looking for when you need to do so. It also means that further opportunities for test refactoring are missed if you happen to name the assertion methods for similar concepts differently.

So there you have it, I prefer to be explicit in my test file about my expectations and I am strongly against that thing were you put your assertions in the objects meant to represent your UI…

What do YOU think?