Assertions in Page Object?!
A few days ago I stumbled on a curious statement while reading some Selenium docs . It said:
Page objects themselves SHOULD NEVER make verifications or assertions. This is part of your test and should always be within the test’s code, never in a page object.
This caused me to feel a wide range of mixed feelings (?) and I decided to dive deeper into it. And this is how this article was born. ??
So… Let’s figure out if we should use POM with:
Note: this article uses “pseudo-code” examples (JavaScript). Please, do not be triggered by the syntax:)
What is POM?
To be on the same page let’s start with the definition of the “Page Object”! One way of describing it is:
Page Object is a piece of code that knows about all the important stuff on a specific webpage (where the buttons are, where the inputs are, and how to interact with them). You hide all of the complexity (aka “inner html details”) under the class properties and methods. And you call these methods from your tests.
It helps with:
This definition is too hard and wordy… ?? Let’s simplify it!
It is a DESIGN PATTERN providing a convenient INTERFACE to guide the implementation of tests!
Translation: when creating tests you would type “pagename dot …” and select what you want to do. These are the OPTIONS PROVIDED by the interface. They GUIDE YOU on what you can and cannot do. So that you create more repeatable/reusable/readable (and other Rs) tests!
The assertion confusion
If it is about a convenient interface, why do they say “should never make verifications or assertions”? If we move the assertions to the test it will be more convenient, right?
…right?? ??
Perhaps the confusion comes from the lack of clarity on what an “assertion” is (in the context of tests). Let’s define it:
A test assertion is a logical check that determines the success or failure
Time for some code!
Let’s try to prove (or disprove) if we should have assertions in the POM? Imagine that we have a “TODO List” webapp. We could create this Page Object class with:
class TodoPage {
#todoItemSelector = '.todo-add-item';
addItem(item) {
//1. code to add it
//2. code to validate that it has been added
}
}
Does the “2.” look sus? Well, the purpose of this article is TO SCRUTINIZE it!
Let’s assume it is OK to have it. When we use our Page Object in the test, it will look like so:
it('Add item', () => {
todo.addItem('Run far')
})
In the example, the method name is NOT SHOWING that there is a hidden assertion present! Not great... Would it be better to rename it? Make it more explicit?
it('Add item', () => {
todo.addAndValidateItem('Run far')
})
Better? Better… But there is still a problem here:
No assertion in the test
…can you even call it a “test”??? Easy fix! Let’s add an assertion to the test (pseudo-code)!
it('Add item', () => {
todo.addItem('Run far')
//const item = find by "Run far"
//expect(item).toExist()
})
Ha! We fixed one problem and created two more:
For the first issue, we could clean up the page object Class and remove the validation. It would look like so:
领英推荐
class TodoPage {
#todoItemSelector = '.todo-add-item';
addItem(item) {
//1. code to add it
}
}
For the second one, we could add a new method called “checkItemExists()” to the PO class:
class TodoPage {
#todoItemSelector = '.todo-add-item';
addItem(item) {
//1. code to add it
}
checkItemExists(item) {
//2. code to validate that it has been added, like:
//const itemFound = find by 'item'
//expect(itemFound).toBe(true)
}
}
So our test could look like this:
it('Add item', () => {
const item = 'Run far'
todo.addItem(item)
todo.checkItemExists(item)
})
Did we prove it wrong?
From the first glance, it looks like we added the assertion to the Page Object Class and it… looks and feels right!? Does that mean that the people who wrote Selenium docs are wrong?
Not necessarily…
There is an assertion library dependency that has been introduced into the PO Class (expect)
Some people could say this is bad, it violates SOLID (or throw in some other acronyms). Maybe you could rewrite your code to “return the found item” and do an assertion in your test? But then you are decreasing the readability of the test…
On the other hand, you could argue that THE PURPOSE (and responsibility) of the Page Object Class and its methods is to represent the page and what the user can do with it. Surely, the user (or your test) would be able to validate if their item exists on the page! So having the assertion in the method is kind of a “purpose” of the checkItemExists() method. This is literally why it exists!
In my opinion, these types of decisions are a “grey area”, a spectrum of choices. One could go either way and be “sort of” correct. Use your own judgement and pay attention to the consistency of the pattern usage.
Explicit and implicit assertion
There is another missing piece of the puzzle that we haven’t talked about yet. What if we separated assertions into the subcategories:
We already have an EXPLICIT ASSERTION in our test (checkItemExists() method), what can we do to add an IMPLICIT one? Perhaps the “network spy” to make sure that the HTTP call “POST /items” API call (to save item) succeeded?
class TodoPage {
#todoItemSelector = '.todo-add-item';
addItem(item) {
//1. code to add it
//2. code to listen to a network call and checking it was a succesful 2XX one
}
checkItemExists(item) {
//3. code to validate that it has been added
}
}
In this code:
One more thing
I've seen a few articles defending the “no assertions” stance in POM. They would often quote this article by Martin Fowler. And he also says that “I favor having no assertions in page objects”. What they fail to notice is that he elaborates on it.
First, he mentions that there are 2 camps:
Second, there is a footnote where he mentions that
One form of assertions IS FINE even for people like me who generally favor a no-assertion style. These assertions are those that check the invariants of a page or the application at this point, rather than specific things that a test is probing.
Translation/adaptation for our “add new item” example:
It is ok to check that the HTML changes (like the table/list that is holding the items now have N+1 row, or checks similar to what we did above with the network calls). But your test SHOULD CHECK that the item has been added and present on the page.
Summary
TLDR:
It is ok to have some assertions
There is no “Page Object Police” that will be coming after you! Also, there are more important problems for you to solve, so don't pay too much attention to things like this one. It is a minor thing in a great sea of problems.
And since you read till the end, I want to say thank you! A reminder that I teach “test automation from scratch” (1-1 lessons) and “all other good stuff” lives behind this link in my profile! ??
Quality Assurance Automation Lead
2 个月You did not mention mostly obvious thing in your article: Let's say you have your assertion for 200 response. Nice. Then you have functional change in the team backlog: item duplicates should not be added and return 400. So what do you have here: 1. You cannot write test using the page as interface: your background check will fail. 2. Of course you can remove background check and write your new test. But... all your previous tests rely on that assertion! You'll make them more flaky (or less readable in case of random failure). 3. You can introduce another method like AddItemWithoutBackgroundCheck() where you duplicate existing code but without verification - but that will decrease readability of your code. And let's imagine you have 20 controls on the page, not 1. Having 20 duplicates definitely isn't the best coding practice.
Senior Quality Assurance Manager | Director of Quality Assurance | Software QA Manager | Test Manager
2 个月I use asserts in POM methods to ensure I've arrived at the correct page. Asserts that actually test something unique live in the test case code.
Cucumber TLC & Ruby Lead | QA Lead | Automation Specialist
2 个月POM should just be the abstraction layer. Your assertions should be in your runner.
??? Test Automation Architect & SDET | ??? Passionate Advocate for Advanced Testing Methodologies ??
2 个月With #CenterTest we keep POMs only as generated from source code classes with getters to elements which can be accessed on the screen. We designed the whole set of different clasees like TextBox or RangeInput which are instantiated in these getters. Then all element logic like click, set or different assertions are available directly from that object.