Developing automated UI tests is a must nowadays. Every development organization invests resources in automating their functional tests which mimic user interaction with their application.

There are several ways to develop UI tests, but I see them as two strategies: Recording and Developing. Some organizations prefer the “Recording” strategy in which the tester’s actions are recorded as code and can be executed automatically afterward. This approach is suitable when all tests are super-simple and require no validation nor code maintenance.

Personally, I don’t believe in this strategy as it cannot produce the required value for the organization. Although this strategy enables the creation of automatic tests without having to hire developers (can be done by manual QA testers), it lacks scale and cannot be used for complex tests.

The second strategy would be hiring developers to create an object-model and use it to develop and maintain automatic tests. If you ask me, this is the best way to go about automating functional tests (even though it might be more expensive in the short term). As the Application Under Test (AUT) is constantly evolving and changing, tests will have to change and be adjusted along the way. It is no secret that test automation requires constant maintenance (As does manual testing), and a well-written code is easier to maintain and requires less attention.

So, as you probably figured out by now, I will be talking about what I consider the main pitfalls and the best practices to avoid them when developing (not recording) automatic tests using the page-object model (POM).

Common Test Automation Frameworks

Keyword Driven Testing (KDT)

Keyword-Driven Testing (KDT), also referred to as “Table-Driven Testing”, utilizes keywords to specify the actual steps of the test-case. The keywords are usually actions performed on the AUT. The common usage of KDT is to arrange all actions in tables with rows specifying each action to be performed and all required data to perform it. Keywords can be divided into roughly two groups: High-level and Low-level keywords. High-level keywords represent business actions (i.e. Login) and Low-level keywords represent basic actions (i.e. click button, enter text).

Keyword (Action) 
KDT enables test-case design and development during while the AUT is still under development. This major advantage doesn’t cover test-case automation implementation as it requires the AUT (or the relevant AUT component) to be ready or at list usable to some extent.

State-Driven Testing (SDT)

State-Driven Testing (SDT) extends KDT and aims to improve it by referring to pages or windows as states of the AUT. SDT actions are not generic like low-level keywords (i.e. click button) but specific to the page or window (click login button). Each test object contains all methods required to interact with the page. For example, a user entry test object will contain the following methods: set_user_details (filling all required user details in the page), select_save (click the “save” button), select_cancel (click the “cancel” button).

The Page-Object Model

Test automation frameworks require an object model on which they can be applied. The most common automation object model today is the Page-Object Model (POM), which is sometimes referred to as the Page-Object Pattern. POM is usually used for testing web application but can be easily applied for desktop or mobile applications.

An example of a login page object high-level structure.

The main idea behind POM is to represent on-screen interaction and element manipulations inside objects which represent the actual displayed page or window. For example, a Login page object may contain the following methods: enter_user_name, enter_user_password, click_login, click_cancel, click_forgot_my_password. In other words, each page and its functionality should be represented by a page-object. Sometimes, menus and panels, which are presented across several pages will be represented in a separated page-object. The main benefits when using POM are:

  1. Tests use classes rather than calling selectors directly
  2. Selectors centralization. Reduce the probability to have duplicated selectors in different places in your code
  3. An understandable and usable structure which enables readable and easy to develop tests
Page-object usage example

Possible Pitfalls When Using the Page-Object Model and How to Avoid Them

UI Test Automation implementation varies according to the SUT and the expected value to be gained. Even so, the basic concepts can be generally applied to any situation with relevant variations.

When implementing POM we are subjected to possible pitfalls that might result in unstable and unmaintainable tests. Here are some of the common miss-uses and how to try and avoid them.

1 — Over implementation

when using a certain object-model we often try to implement it as it is without taking the time to think how that object-model can meet our business requirements. In other words, we should always think how the chosen object-model serves our goal (and provides value) rather than trying to implement it as is. POM over-implementation often results in huge amounts of objects (classes) which quite quickly become impossible to maintain. This is a huge problem for a UI Testing function in the organization as it prevents results delivery and requires lots of resources for maintenance. In other words, it reduces the value received from the UI Automation function to almost nothing.

Proposed solution:

Create page-object classes which interact with the UI based on functionality and not based on visibility. The main goal is to represent the functionality of the UI and not the actual visual structure.

2 — Forgetting About the Code

UI Automation development is often subjected to very short timelines with clear results requirements, which may result in a quick-and-dirty code. If you ask me, there is nothing wrong with a quick-and-dirty solution as long as you add a refactoring task to your technical-debt backlog. However, we usually forget about our technical-debt and just leave the code as is. This may result in a “voodoo” (inconsistent) behavior and an impossible maintenance.

Proposed solution:

Developing UI test automation code is no different than product software code and should be treated as such. Thus, when writing test automation code, we should use common design patterns and principles. Additionally, we should pay extra attention to technical-debts and strive for clean and readable code.

3 — Mixing basic actions in test objects

When creating test objects it is usually tempting to use the basic abilities of the technical framework (Selenium, CodedUI etc) to perform basic actions for clicking an element, reading text from an element, sending text to an element and such. This approach has an inherited flaw in it as these actions almost always require validations and additional actions. For example, before sending text to an element we usually want to scroll to it so it will be visible on screen (something a user would probably do). Additionally, we would like to make sure that the sent text was indeed sent to the element as expected.

Proposed solution:

create an “external” object (could be a singular instance) in which you will implement all basic actions and capabilities such as click_element, send_text, read_text, scroll_to_element etc. The main idea behind this solution is to have all actions etc.in one place. This way we gain more reliable and resilient actions with easy maintenance.

Proposed implementation for Basic Actions

4 — Not Enough Validations

A UI Test scenario should mimic a user interaction with the SUT. Such interaction involves navigating between different pages (windows) of the SUT and using different functionalities. Many times, tests provide false-positive results when a navigation or action did not go as planned but did not raise any error in the test or SUT.

For example, entering text into an element but the text was not sent to the element for some reason. In this case, the test is not successful but might still pass. What if we need this value for other tests? What if this value is needed for an underlying process in the SUT? Is this OK that our test missed that?

Proposed solution:

Validate each actionable step. Every action such as button_click, enter_text etc should always be provided with the validation criteria for the action’s outcome. Or in other words, every action should have an expected result. This way we eliminate the risk that a certain action will not work as expected without our tests detecting it.

5 — Testing the wrong system…

Developing tests (manual or automated) for an application you don’t know and understand is bad. Be familiar with the application you are testing, understand the business flows and the business behind the application.

Proposed solution:

The name of the game is “business flow”. Invest in learning the application, its core components and the business behind them. Understand the way customers use the application and identify the critical business flows that should be covered first. Remember, understanding the AUT is the best way to develop valuable tests.

6 — Mapping instead of testing

Many times, we put too much effort into modeling and representing the AUT rather than actually testing it. In other words, we put too much effort into creating objects which display functionalities that we do not test or have no tests for them. This usually results in detailed and well-modeled useless objects as they are not included in any test case.

Proposed solution:

If there is no test scenario for a certain feature or component there is no need to implement it. Make sure to implement those objects which provides value and actively participate in tests. And what about those features and components that you didn’t implement? Don’t worry, you’ll get to them when you’ll have a test-case which require them. To make a long story short: No scenario, No implementation.

About Author

Leave a Reply

Your email address will not be published.