Simple and convenient test framework template on selenide for UI autotests

Hello.

In this article, I would like to share my experience in automating functional testing. It will be about writing a convenient and reliable test framework.

What we will use: Java, Selenide, Alure, TestNG, Maven.



Introduction


GitHub project - SelenideBoilerplate .

Often in articles on test automation, examples are far from reality, for example:

driver.get (“URL”)
driver.find_element_by_id(“ID”).send_keys(“username”)
driver.find_element_by_id (“ID”).send_keys(“password”)
driver.find_element_by_id(“submit”).click()

There are many similar examples of expectations, page objects, etc. As a result, it can be difficult for a novice tester to organize everything correctly and conveniently. The project is overgrown with crutches, which is why it is becoming increasingly difficult to write new tests and maintain old ones.

There are also some tools that are too verbose and overcomplicated in my opinion.

I will show a simple, convenient and easily extensible test framework, which is much easier to work with than regular selenium and which I have successfully used on several projects. This project is the basis, on real projects everything is a bit more complicated (parallelization, remoteDriver, many tests, etc.).

Tools


  • Selenide is a library for writing concise and stable open source UI tests. Selenide solves most of the problems with timeouts, clicks on items that did not manage to load, etc. You can also forget about the StaleElementReferenceException . A very convenient and easy to learn tool, having worked with which you no longer want to return to selenium.
  • WebDriverManager - Included in Selenide. A library that does all the work of downloading drivers for a browser and setting driver paths -
     System.setProperty("webdriver.browser.driver", "/path_to_driver/driver"); 
  • Allure for reports.
  • TestNG is a test framework.
  • Maven is a tool for automating the assembly of projects.

Project structure




Let's start with the app and pages modules .

PageBuilder Class


Typically, examples give fairly simple pages where everything fits in one class. But on real projects, there may be quite large pages, describing all the functionality of which in one class is not the best idea. For example, this could be a recharge page with many forms of different payment systems.

In this case, it is better to split the page into several classes and even elements (for example, a product in the basket) and put it all together in the main class of the page.

Therefore, it is better to create all pages in one place, namely in the PageBuilder class .

package app;

import app.pages.LoginPage;

public class PageBuilder {

    public static LoginPage buildLoginPage() {
        return new LoginPage("/login");
    }

    public static BalancePage buildBalancePage() {
        DepositForm depositForm = new DepositForm();
        WithdrawalForm withdrawalForm = new WithdrawalForm();
        return new BalancePage("/balance", depositForm, withdrawalForm);
    }
}


Class AppConfig


The AppConfig class stores the settings of the web application under test. For example - the site address, test users, etc. In this project, this is simply the address of the site.

package app;

public class AppConfig {

    public static final String baseUrl = "https://google.com";

}

App class


This is the main class in this module. In the constructor of the App class , all pages are created.

package app;

import app.pages.LoginPage;

public class App {

    public LoginPage loginPage;

    public App() {
        loginPage = PageBuilder.buildLoginPage();
    }
}

Thanks to this approach, it is not necessary to constantly create page objects in tests, only the App object is created from which the necessary pages are obtained.

Also in the App class there may be such methods as - registration, registration and order creation, etc.

That is, large operations in which several page objects are involved and which are often needed in tests.

Let's move on to page objects


Thanks to Selenide, working with page objects is easy. All pages inherit from the BasePage base class . The relative page url is passed to the page object constructor.

All page elements have a public access modifier , so you can write tests in both imperative and declarative styles. Also, from the elements you can get the necessary data, such as text or some attribute.

The locator is stored in only one place. All page logic should be described in page methods.

With this approach, if something breaks or changes, in most cases it is not necessary to rewrite the tests, the method is just finalized or the locator changes to the actual one.


package app.pages;

import com.codeborne.selenide.SelenideElement;
import helpers.Driver;
import static com.codeborne.selenide.Selenide.*;

public class LoginPage extends BasePage {

    public SelenideElement loginField = $("#login__username");
    public SelenideElement passwordField = $("#login__password");
    public SelenideElement signInButton = $("#login_enter");
    public SelenideElement termsOfUseLabel = $("label[for=\"login_agree\"]");

    public LoginPage(String pageUrl) {
        super(pageUrl);
    }
    
    public void login(String email, String password) {
        loginField.setValue(email);
        passwordField.setValue(password);
        termsOfUseLabel.click();
        signInButton.click();
        Driver.waitForUrlContains("account/accounts");
    }
}


Rest


The helpers module contains 2 important classes:

TestConfig - From this class you can get the settings with which tests are run. The default settings are also shown here.

Tests are run by the command. mvn test -Dbrowser=chrome -Dheadless=1

The values ​​of the variables are taken from the command line and, thanks to the TestConfig class , are available in tests and in the application.

For example, you can change the application url depending on the environment (dev, stage, production).


package helpers;

public class TestConfig {

    public static String browser = "chrome";
    public static String headless = "1";

    public static void initConfig() {
        browser = System.getProperty("browser") == null ? "chrome" : System.getProperty("browser");
        headless = System.getProperty("headless") == null ? "1" : System.getProperty("headless");
    }

    public static boolean isHeadless() {
        return headless.contains("1");
    }
}

The Driver class is my wrapper around selenium and selenide drivers with a couple of useful methods.

The most important methods:

Driver.initDriver () - here the driver / browser is initialized.

   public static void initDriver() {

        // Get settings from command line

        TestConfig.initConfig();

        // Set settings for selenide browser

        Configuration.pageLoadStrategy = "eager";
        Configuration.browserSize = "1920x1080";
        Configuration.holdBrowserOpen = false;
        Configuration.screenshots = false;

        if(TestConfig.isHeadless()) {
            Configuration.headless = true;
        } else {
            Configuration.headless = false;
        }

        switch (TestConfig.browser) {
            case "chrome":
                Configuration.browser = Browsers.CHROME;
                break;
            case "firefox":
                Configuration.browser = Browsers.FIREFOX;
                break;
            default:
                Configuration.browser = Browsers.CHROME;
                break;
        }
    }

Driver.clearCookies()
Driver.close()

Tests


All test classes are inherited from the A_BaseTest class , in which the App application object is created , logger , softAssert , the browser opens and closes, cookies are cleared after each test.

There is also an A_BaseTestListener where errors can be logged.

The tests look something like this. Easy to read, easy to maintain.

import org.testng.annotations.Test;

public class ExampleTest extends A_BaseTest
{
    @Test
    public void loginViaEmail() {
        app.loginPage.open();
        app.loginPage.login("email@email.com", "passwords");
        
        logger.info("Sample info message");
               
        softAssert.assertEquals(2,2);
        softAssert.assertAll();
    }
}

Test classes are specified in testng.xml .

The test-output folder contains logs and screenshots - Driver.takeScreenshot () .

For reports, Allure is used . After the tests are completed, you can run the command allure serve target/allure-resultsand see the report.

GitHub Project - SelenideBoilerplate

All Articles