Introduction

Java testing framework for streamlined and efficient test creation.

In this article, I would like to share a tool that I gradually developed and improved in my spare time, which I applied to simplify the tasks I worked on at various companies during that period.

Work in Test Automation

Working in test automation became my official profession several years ago. During this time, I had the opportunity to engage in a wide range of tasks, depending on the companies I worked for. Starting from testing user interfaces of various websites, web and mobile applications, my responsibilities eventually led me deep into testing complex API services and even automating desktop applications.

In the first years, I was heavily involved in testing web applications on Java, although I did not start with it. Of course, after trying many approaches, I found my favorite stack which I used when given a choice. For me, it turned out to be TestNG + Selenium + Allure. Naturally, I also had to work with the famous Cucumber, and later with the monstrous (in my opinion) Serenity and many other solutions, including custom developments by various people.

Abandoning BDD and the Complexities of Auto-Logging

I was never inclined towards BDD, as I mostly read reports and reviewed tests myself, and in 100% of cases, I composed the scenarios using Gherkin (sometimes based on provided scenarios, more often not). The so-called convenience that BDD offers the user was minimal, while in return it demanded a lot — skyscrapers of wrapper classes, complications with inheritance, the need to create a bunch of duplicates for trivial actions, absolute inconvenience in transferring data between steps and processes, and other difficulties.

The Emergence of Selenide and Step Auto-Logging

Then came Selenide, which made the abandonment of BDD frameworks even more justified (although not immediately and not without reservations). Nevertheless, I generally liked BDD reports – they were almost always more informative and easier to read than the reports generated through @Step annotations in Allure, and much more legible than the built-in auto-logging of Allure.

If one wanted to make the report more informative and intuitive without using BDD approaches and frameworks, it would be necessary, as in BDD, to wrap every minimal action in a step wrapper. For example:

public class FormPage {
    SelenideElement btnSave = $(".btn-primary").as("Save");
}

This method would provide a basic auto-generated log for the event btnSave.click(); – something like "Step1: Click Save".

The Problem with Wrappers in Complex Scenarios

Of course, one might say that any scenario consists of a sequence of simple actions. Maybe in an ideal world, that would be the case, but if you try to write a test for a CRM containing dozens of tables, numerous widgets in each record, complex filters, search queries, uploads, data imports, dynamic events, and so on – the final test might consist of 50–60 lines, where each line is a fairly complex step. After that, looking at the auto-generated report, arguing about its "readability" would be extremely difficult.

However, if I want to see a proper description of a step in the report, for example:

"Click on the <Save> button of the <User settings dialog>"

then you would have to write a wrapper:

@Step("Click on the <Save> button of the <User settings dialog>")
public void clickBtnSave() {
    btnSave.click();
}

Alternatively, one could do it like this:

public class FormPage {
    SelenideElement btnSave = $(".btn-primary").as("the <Save> button of the <User settings widget>");
}

In the report, it would look better, but firstly, not so much, and secondly, if this button is located in every record of a long list of users, where each row is a complex widget with many different elements, including a button, we will get an ElementsCollection, filter it by some stable parameter, find the required ones, and click the button in each record. All actions in the report will look identical.

Problems with Element Collections

In the end, we come back to a major drawback of the BDD approach — we end up creating tons of wrapper methods for every trivial click, and in web applications, there can be hundreds of such clicks. And that's only for clicks! Add other actions, context menus, operations with various input fields, events, verifications ranging from simple text to HTML attributes, etc., and multiply by the number of elements in the application. Every little action requires a specific description, and if you follow the BDD approach, the test project can end up being much larger than the application under test, which has been confirmed repeatedly in practice.

Operations with Element Collections

Even more problematic, both with BDD frameworks and without them, are operations with element collections. Various searches, filters, and verifications require specific step descriptions that multiply at a frightening rate.

For example, a user data table may have 30 or more parameters:

public class FormPage {
    ElementsCollection btnSave = $$(".user-record").as("Users settings list");
}

What if we need to verify each of the 30 parameters individually and create a separate method with a description for each? That is:

public class FormPage {
    ElementsCollection users = $$(".user-record").as("Users settings list");

    public void assertEmail(String userId, String expectedEmail) {
        String targetEmail = users.filter(Condition.text(userId)).first().$x("div[@id='#email-container']").text();
        Assertions.assertThat(targetEmail).as("User email").isEqualTo(expectedEmail);
    }
}

And do that for the remaining 29 parameters? Of course not. Here, one should create a DataObject with user parameters and write a universal method that takes this object and iterates through each field, comparing all parameters using Soft Assert. Yes, this is a solid solution, but you would still have to write logic, and the beautiful auto-logged report would be a thing of the past.

Drawbacks of Selenide Allure Report Auto-Logging

The fact that Selenide Allure reports were generated automatically did not satisfy me. After some time, to recall a scenario and understand what happened, even I had to struggle, not to mention someone who is not familiar with the code. Moreover, in some cases, reports had to be produced in languages other than English.

There is no perfect tool, but I was contemplating and trying to find a solution that would eliminate most of the complexities of any approach. I wanted to make it so that all I had to do was define the entities of pages, widgets, and elements, and write the scenario, while the framework would handle everything else. This way, I would hardly have to write step logic, as it would be organized automatically based on the code structure and data types, without restricting the free style of Selenide. Moreover, the framework should automatically collect and log all invoked actions, making them as close as possible to beautiful BDD reports, while providing the ability to edit this process as desired.

My Thoughts
  1. Adopt a standard and clear structure, but add more specifics.
  2. Every entity should have expected properties and behaviors, most of which are implemented out of the box.
  3. Every entity, whether it is a page, widget, or element, should know where it is located and what it belongs to.
  4. All entities should be easily extendable; there should be an opportunity to add any custom elements, components, and modifications.
  5. The framework should collect metadata of objects on its own in 99% of cases.
  6. The framework should rely on Allure and Selenide without limiting their capabilities.
  7. I must finally eliminate element searches through filtering, and generally searching in lists, and put it behind me like a nightmare.
  8. Code should be repeated minimally (DRY). Even page objects should be easily extendable and reusable.
  9. The framework should include a multitude of pre-implemented user actions with the browser, which are logged in the reports.
  10. I should be able to edit the logged steps and define their level of detail dynamically.
  11. And finally, beautiful reports should be generated in multiple languages, not just English.
PageObject Structure

Starting with point 1, I wanted to see a structure roughly like this. I annotate the class to indicate that it is a page. The elements and widgets of the page should be represented by their corresponding types.

PageObject with Simple Elements

@PageObject
public class FormPage {

    @Name("Login")
    @Locator(css = "#login")
    protected TextField fieldLogin;

    @Name("Email")
    @Locator(css = "#email")
    protected TextField fieldEmail;

    @Name("Password")
    @Locator(css = "#password")
    protected TextField fieldPassword;

    // ... other fields
}
        

PageObject with Widgets

@PageObject
public class HomePage {

    @Name("Navigation bar")
    NavBar navBar;

    @Name("Search block")
    SearchBlock searchBlock;

    @Name("Gallery block")
    GalleryBlock galleryBlock;

    @Name("Footer")
    Footer footer;
}
        

Description of Widgets

@Widget
public static class NavBar extends AbstractWidget {

    @Name("Images")
    @Locator(css = ".navbar-nav .nav-item:nth-child(1)")
    protected Link linkPhotos;

    @Name("Stickers")
    @Locator(css = ".navbar-nav .nav-item:nth-child(2)")
    protected Link linkStickers;

    @Name("Icons")
    @Locator(css = ".navbar-nav .nav-item:nth-child(3)")
    protected Link linkIcons;
}
        

Where:

The test scenario, as it should be, should not contain logic but rather represent a sequential invocation of methods of widgets and elements.

I sketched the following diagram and started working on its implementation.

I decided not to show off and simply combined the names of the underlying technologies – it turned out as "Allurium". Next, examples illustrating the essence and structure of the framework will be discussed.