QA UI Automation Part 2: Page Object Model Best Practices
- How to Build a Reliable QA UI Automation Framework: Part 1
- QA UI Automation Part 2: Page Object Model Best Practices
- QA UI Automation Part 3: Advanced Automation Design Patterns
In the first post of this series, we built a basic QA UI Automation Framework, using Behavior-Driven Development (BDD) and Selenium with Python. Over time, you may want to add new and update existing tests, ideally with minimal effort. In this post, I will explain automation best practices you can use to maintain and scale your framework. Specifically, I will explore the use of Page Object Model (POM), and approaches you can follow to ensure success.
Use Page Object Model for Scalability and Maintainability
Although it’s common to use script-style code to automate testing, let’s consider its limitations. Then we will explore a better approach.
Script-style code includes everything in the same workflow:
• Creation and use of web elements
• Assertion of the result
Here’s an example of a unit test done with script-style code:
This approach gets more challenging if we need to test more functionality. When that happens, we add it to the existing script-style code, right? But this can quickly lead to:
• Fragile code, in which a small change in functions, iterators, or duplications can break everything.
• Unreadable code that, by itself, is hard for others to read and interpret.
I recommend using a Page Object Model (POM) pattern instead of script-style code. POM is a design pattern that makes maintenance easier and reduces code duplication. Developers have used POM for a very long time, and POM use in testing has become very popular.
When used in testing, POM represents web pages as classes, and we define elements on the page as variables in the class. With this approach, you can implement all possible user interactions as methods on the class. If the UI changes for the page, you only need to change the code within the page object.
For comparison, here’s an example of a unit test written with POM:
Notice how much easier it is to understand the sample test when we use the class to hide the actions into functions.
Best Practices to Ensure Success With Page Object Model
As you add more business logic to a test, POMs can become bloated. If it’s the only abstraction layer we have, we can end up testing the interactions with the user interface, rather than what the user does and wants to achieve. To be successful with POMs, think ahead and plan for scaling and maintenance. Following a few best practices can help you stay on track.
Test Business Tasks Not Just Pages
Make sure your test models current business tasks, not just pages. The tasks should describe what the user is trying to do in business terms. For example, when testing a form, focus on the business task behind the form, not just the simple actions of clicking a button or entering text into a form field.
Be Wary of Anti-Patterns
Be wary of anti-patterns, or common responses to recurring problems that are usually ineffective, risk being highly counterproductive, and may require refactoring. I selected a few patterns I like, adopting them as I think they worked better as a whole, so on purpose I left some “unnecessary” items out.
Keep Large Classes in Check
In the worst case scenario, a poorly coded framework can fail altogether. If it doesn’t fail, it can slow down development and increase the risk of bugs. To avoid code smell (check out the book Refactoring from Martin Fowler for more information), it’s important to avoid bloat.
Many developers find it easier to insert a new feature into an existing class than to create a new class for it. Over time, this can lead to what some call “Large Class” syndrome, in which classes become large and inefficient. Everyone has different opinions on the definition of a large class, but a commonly accepted maximum is 60 lines of code, more or less. Check this example, taken from RiverGlide: “Page Objects Refactored SOLID steps to the Screenplay/Journey Pattern.”
When you have large classes, your test can become unstable. To keep your classes short, I recommend you:
• Write short functions that solve big problems.
• Resist the urge to duplicate code and make sure similar functions do the same tasks in the same way.
• Separate the code into specific layers.
When you separate the code into specific layers, you hide details in the PageObject, reusing objects that represent actual business tasks and concepts. Looking at the example unit test above, you can see we’ve put the locators and page methods in the same class. It would be better to separate them. Remember, we use PageObjects for features and not entire pages!
Consider each PageObject not as an object representing a single page, but as objects within a page (Refactoring is a good source on this). Think of each object as a widget or page component.
What if a component has more than a few fields and buttons, and the PageObject grows beyond that 60-line limit you’re targeting? To keep this from happening, plan ahead. I recommend you revisit these two SOLID principles: Single Responsibility Principle and Open Close Principle (SOLID is an acronym coined by Michael Feathers and Bob Martin that encapsulates five good object-oriented programming principles.) Let’s look closer.
Apply the Single Responsibility Principle
The Single Responsibility Principle (SRP) states that a class should have only one responsibility and therefore only one reason to change. This reduces the risk of affecting other unrelated behaviors when we make a necessary change.
A PageObject is responsible for describing the tasks that can be done on a page using its unique elements by providing an abstraction to the location of them on the page. This is done via a meaningful label for what those elements mean in business terms.
Apply the Open Closed Principle
The Open Closed Principle (OCP) states that a class should be open for extension, but closed for modification. This means that it should be possible to extend behavior by writing a new class without changing the existing working code.
For example, consider a situation where we want to add the ability to sort column items alphabetically to a ReportPage. The most likely approach would be to ‘open’ the ReportPage class and modify it with the new method of handling this behavior.
You would repeat these steps for any behavior added to the page. Also, a task may sometimes span multiple pages. With this approach, you might have to split the task artificially across two classes to conform to PageObject dogma. To satisfy OCP, you should instead add a new class that describes how to sort columns in tables.
Save Time and Improve Quality Using Best Practices and Design Patterns
A reliable, scalable IT automation framework can help lower the cost of QA and increase the quality of the product produced. When you invest the time in building a UI automation framework, plan from the outset so that your code is easy to maintain, update with improvements, and fix as needed. You can save time and gain more from your hard work by following the Project Object Model best practices we’ve covered in this blog. Best practices and design patterns exist because someone already tackled challenges much like those you face, so let’s use the tools we have available instead of reinventing the wheel.