How we scale our automation project Atlas using design patterns

Leyla Hasanzade
Insider Engineering
6 min readApr 7, 2021
Our project Atlas is supposed to defend quality of our products same as Titan God Atlas bore the sky aloft

Insider helps marketers drive growth with our first integrated AI-powered Growth Management platform. Our partners manage advertising, mail etc. on their sites through the web panel we provide them. This web panel is like an app that grows day by day and has more than 50 different products.

In 2019 as the Insider QA team, we’ve started the Atlas automation project to cover this panel with automation tests. Atlas is a Python+Selenium project with almost 2000 e2e tests and over 25 contributors to date. There are not only standard web UI tests in our project, but also many different tests that also perform database checks, controls of receiving email, sms, web push by users, API checks on the partner site etc.

We all know how difficult both maintenance and constant scale of such a huge Test automation project is. One of the ways we can overcome these difficulties is to use the right patterns to create and keep a sustainable structure that everyone in the team can easily contribute to. In this article I would like to describe the main design patterns we use in our automation project.

Design pattern. What is it?

Design patterns are typical solutions to commonly occurring problems in software design. We can’t just find a pattern and copy it into our code, the way you can with off-the-shelf functions or libraries. The pattern is not a specific piece of code, but a general concept for solving a particular problem.

Design patterns topic is quite a controversial one. There is no such thing as “good design pattern” or “bad design pattern”. Therefore, for every pattern we come across — in development, testing or somewhere else — if pattern doesn’t solve the problem for which it was invented it doesn’t mean that it is bad or useless it only means that it doesn’t solve exactly that problem. Thus it’s important not to integrate every pattern we have heard about but also to understand its purpose, perspective, where and how it can help.

The main *ity principles we adhere by using design patterns while writing tests in Atlas that make them much easier to manipulate, understand and maintain are:

  • reusability
  • clarity
  • flexibility
  • maintainability
  • stability

Let’s take a look at these design patterns and benefits of using them.

Page Object Model

We will not go into description details of this pattern, we all used or read about it at least once on the world wide web. Briefly, Page Object Model (POM) is an object design pattern in Selenium, where web pages are represented as classes, and the various elements on the page are defined as variables on the class. All possible user interactions can then be implemented as methods on the class.

Let’s check the main points for building a class according to the POM using the Login Page of our application as an example.

Login Page

To reflect this page, we create a single class in our automation project. It’s best to make sure the class’s name is representative of the application’s page so that other contributors can easily locate the class associated with a particular UI page. Any of the elements on the page that will be included in our tests should have properties in LoginPage. These properties are locators to the elements on the actual page. It lets us not to hardcode these locators in any place we use them, so we define them in one place, avoiding duplication.

So, in this case, we’d create a LoginPage class.

The class should also provide methods that allow a test to interact with the application, such as setting input fields and clicking buttons, in addition to the properties. Here are some pointers on how to build these methods so that our tests can make the best use of them:

  • We write only methods/properties that are currently needed. If a new test needs a new method/property, we can always add more. We don’t have to manage any unused code as a result of this.
  • Transitions should return new objects. Methods return a handle to the class representing the UI page we’ve moved to when your click results in a page change. If, for example, clicking the log-in button takes us to the application’s home page, method returns a handle to the class that represents that page.
  • Methods in classes are neutral, in other words methods are generic enough to be used by any test that wants to interact with the page. This simply means we don’t write assertions in page methods.

Issues we solved with POM:

  • Separation of technical details such as elements in the browser (elements which perform this or that functionality), removal them from the logic of tests to make test logic clean and transparent and store them in different pages.
  • Reuse of the code that is written in those pages. Many test scripts go through the same pages, then it would be quite logical if code is written once and is invoked every time it’s needed, what, accordingly, will greatly simplify writing tests.
Example code from test script

Loadable Components

The problem is that when a test moves from one page to another, it has no way of knowing whether the desired page has fully loaded. Obviously, usual sleep or not waiting at all aren’t viable options. Usually, test engineers create explicit wait in the page class’s constructor or in its ancestor and override if required. So, what are our options for resolving this issue?

In Atlas all pages extend PageBase and override its check method and these overridden check methods in every page’s constructor do control loading of all most important elements of this page.

PageBase class and LoginPage’s overridden check method

Issues we solved with this technique:

  • If a new object is returned by transition and some of the elements of this object have disappeared/not visible, our test fails immediately after moving to this new Object. So, when clicking the log-in button takes us to application’s home page, and some elements of this page are lost our test finds bug before starting any interaction with this page.
  • Detection of known unknown bugs which are not supposed to be found by our test cases.

Fluent / Chain of Invocations

It’s easy to use Chain of Invocations. All we do is return the value in each Page Object method. This may be self, a value, or another object, such as the next Page after a method invocation.

Method returns self
Before/after using Chain of invocations technique

Issues we solved with this technique:

  • Allows to invoke any method in the test script regardless of step we are in right now.
  • Allows an easier scale of our test automation project. The large number of pages, components, and methods available for use can be confusing, especially for someone who is unfamiliar with code. However, by implementing Fluent Invocations, IDE gives us an autocompletion hint whenever we attempt to invoke a method on an object while writing our test scripts.

We have seen and now pretty sure that using these design patterns in code saves time while maintaining automation projects, but there’s always room for improvement. If you’d like to explore new techniques with us to improve huge automation projects you are welcome to join our team!

--

--