Some while ago i published an article in a local magazine on the topic of writing Selenium tests in an Object Oriented fashion. As i believe this is a very useful way of reducing the number of asserts tests can include, and to expand to a bigger audience, here is a different approach to writing Selenium tests.
This approach is recommended when you have, for example, a big module whose properties you must compare to the expected ones, after performing an action or when it comes to testing the translation of the page.
For example, if you had a search engine that provides a list of results when providing a search term, for each search that you performed the page would look the same but will display different results. Breaking the page down into objects and comparing objects rather than individual properties makes test maintenance and readability much easier.
Let's say the module you are trying to test is a shopping cart displayed after you select the items which you want to buy from your favorite site. Your shopping cart displays the relevant information regarding your purchased items, like their label, a description, an image, the price per product, the quantity in which the product is bought, the total price for each product type (which is the price per product multiplied by the quantity), and a button which allows a product to be removed from the shopping cart. Apart from the items you bought, the shopping cart also displays a label of its' own, the total price of all the items that were bought, and a button that allows the user to proceed to the payment step. This page can also contain other information, like a module that suggests similar products to acquire, which would only display a small number of items, depicted by a label, an image and their price.
Such a shopping cart would be similar to the one depicted in the image on the right.
After adding all the desired products, the shopping cart is ready for testing (checking whether it displays the correct data - the products are the ones the user selected while shopping, the quantities match, the prices displayed are correct, and so on).
Usually the verification part of such a test would contain a large number of assertions. Even if these are placed in a separate method, they would be difficult to maintain and read. To avoid such an inconvenience, tests can be designed as being object oriented, as described below.
The analysis phase
First, you have to look at the big picture. What does the shopping cart represent? A collection of objects of different types. Analyzing the structure from complex to simple, one can notice that the most complex object (the one that encapsulates all the others), is the shopping cart itself. Its' properties are: the label, the list of purchased items, a link, a button, and a side module. Out of these properties, the label can be represented in Java as a String, when it comes to the test. The price can also be represented by a String. The list of products is actually a list of objects of type Product. The Link is also an object, and so is the side module. Having analyzed the page top down, the ShoppingCart object can be represented as follows:
Looking deeper into the identified structure, one can notice that: a product contains (has the following properties) - am image (which is another object), a label which represents its' name (a String), a description (another String), the price per object (a String), the quantity (String), the total price for the acquired number of this type of product (String) and a button (this object is of type Button, mentioned already in the carts' description). Having identified the product properties, the objectual representation can be as follows:
The Link object mentioned in the carts' definition can contain a minimal set of properties, which include a label (String) and the URL which opens when clicking it. Therefore, the Link object can be considered to be:
By following this reasoning, one can identify all the properties of the objects that comprise the shopping cart. Therefore, these objects can be created by breaking each one down until the objects' properties are primitives or Java objects.
Building the 'expected' content
After structuring the shopping cart, by breaking it down to objects, you need to understand how these will be used within tests. The first part of the test (or the part that will be performed before the test) will be to add the items to the shopping cart. The test will only need to check that the cart contains all the acquired products.
In order to build the expected shopping cart (the one you expect to see on the page after finishing the shopping part), a constructor must be written, which will assign to the objects' properties values of the type those properties have. Therefore, the constructor will take a number of parameters that equals the number of the objects' properties, each parameter having the type of the corresponding property type (for example, for a property of type String, a String parameter will be defined in the constructors' signature).
Beginning from the easiest to build object, the constructors are written. For the Link object, we have the following constructor:
Here - the Link object has a label and a URL, both of String type, whose values will be instantiated with the values retrieved from the constructors' signature.
For the Product object, the following constructor will be written: The shopping carts' constructor can be written as follows: Based on the constructors, the following objects will be built, which will serve as the expected content:
the link for continuing to shop:
button for proceeding to the payment page:
first product in the cart:
here, the Image and Button objects have been using their own constructors
the second product from the cart:
here, the Image and Button objects were created by using their own constructors
the third product from the cart:
here, the Image and Button objects were also generated from their own constructors
the expected shopping cart will be generated as follows:
here, the SuggestionModule object was generated with its' own constructor, whereas the Product list, the Button and the Link were exemplified above.
Creating the 'actual' content
For building the 'actual' content, by reading the properties of the HTML elements on the web page, a new constructor will be written, which will have either a WebElement or a list of them as parameters, as many as are needed for instantiating the objects' properties. WebElements are Selenium representations of HTML elements.
For example, for the Link object: the two properties, label and URL, can be extracted from a single WebElement. A Link element is represented in HTML as an <a> element, having a 'href' attribute (for identifying the URL). By calling Seleniums' getText() method, you can acquire the value for the label property. Therefore, the constructor for generating the actual content can be written as follows, by passing in the <a> element that defines the Link:
Depending on how many WebElements are required for building an actual content of type Product, either one single WebElement will be used, or a list of them. Assuming only one is required, the corresponding constructor will look like:
One can notice that when it comes to the Product, for generating the values for each property, the constructor that builds the 'actual' content for each property type is called (constructors that also receive WebElements as parameters). Basically, any 'actual' constructor (based on WebElements) will only call 'actual' constructors for its' 'sub-objects' (ones that also receive WebElements as parameters).
For the remaining properties - for example, for the productLabel, the getText() method from Selenium is called on an element that is relative to the element passed in the constructor.
Once all WebElement based constructors are defined, the most complex constructor, the one for the shopping cart, can also be built:
Once the objects and constructors have been built, the tests can be written. The requirement was to compare the shopping cart displayed on the web page to an expected one, by comparing each property of the two carts.
These properties are also objects, therefore their own properties must be compared to the properties of a set of expected objects. Since, in the first step, the 'expected' objects were built, after which the 'actual' ones were also created, the test can contain only one assert. It will compare all the properties by simply comparing the two ShoppingCart objects.
As a note, once an object is created, the equals() method must be implemented, for allowing the comparison of two instances of that given type. Having said this, the test can be written as:
This test does not cover the buying steps, where items are added to the cart, which should be done in a 'before' section of the test class.
In case the test needs to be run on several languages, a dataProvider can be passed to the test which contains the language for which the test will be run, and the expected translated content. In this case, the test becomes: For this example, the dataProvider will look something like:
Therefore, if the site would display let's say about 20 languages, the test that will check for the translated content will have a small number of code lines, and will be written only once (but run several times).
Benefits of this approach
There are several benefits for writing tests in the object oriented manner. First, the test is a very short one, with a well defined goal, doing only one thing: comparing the 'expected' and 'actual' contents.
The test itself does not need a lot maintenance, since when the page changes, only the way of generating the page properties will need to be updated. Changing a label on the page requires only to change the expected value for that label, change which is isolated to only one line of code (which is used most probably in many tests).
Another benefit is given by the compact structure of the test, having no need for a large number of asserts, or for a large number of parameters for comparing their values. Instead of these parameters, the objects are compared directly.