Selenide Test Automation: Using Selenoid in the Docker Container

Written by andrew_intexsoft | Published 2020/01/24
Tech Story Tags: selenium | qa | test-automation | docker | java | testing | container-devops | testing-frameworks

TLDRvia the TL;DR App

This article will be useful for young QA specialists, as well as those who are interested in the features and capabilities of such popular testing frameworks as Selenide and Selenoid.
Here we will look into some a basic Selenium project. I will describe how to connect Selenium and TestNG to the project and will see a Page Object example with the description of page elements and the methods used.
The next step is the acquaintance with Selenide: we will see into the framework itself, look through its main features and advantages of usage. Will learn how to add Selenide to a testing project, how to work with elements, assertions, and waits available in Selenide.
And finally, I will connect the Selenoid framework to the project to run tests in the Docker container and outside it.

1. Selenium + TestNG. Selenium Maven

We are considering a project build on the Maven builder, so we can find the project structure description in the pom.xml file. To order use Selenium+TestNG, we should add appropriate dependencies to pom.xml file. You can observe them between the dependencies tags below:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"                               
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    
    <modelVersion>4.0.0</modelVersion>
        <groupId>test</groupId>
        <artifactId>test</artifactId>    
        <version>1.0-SNAPSHOT</version>    
        <dependencies>             
                <dependency>                       
                      <groupId>org.seleniumhq.selenium</groupId>                      
                      <artifactId>selenium-java</artifactId>                     
                      <version>3.141.59</version>                 
                </dependency>           
                <dependency>                      
                       <groupId>org.testng</groupId>                     
                       <artifactId>testng</artifactId>                     
                       <version>6.14.3</version>                     
                       <scope>test</scope>           
                </dependency>   
        </dependencies> 
</project>
Here is an example of the Page Object:
import...

public class SignUpPage {
   private WebDriver driver;

   public SignUpPage(WebDriver driver) { this.driver = driver; }

   private By emailField = cssSelector("#register-email");
   private By confirmEmailField = cssSelector("#register-confirm-email");
   private By passwordField = cssSelector("#register-password");
   private By displayNameField = cssSelector("#register-displayname");
   private By monthDropDown = cssSelector("#register-dob-month");
   private By dayField = cssSelector("#register-dob-day");
   private By yearField = cssSelector("#register-dob-year");
   private By shareCheckbox = cssSelector("#register-thirdparty");
   private By registerButton = cssSelector("#register-button-email-submit");

   public SignUpPage typeEmail(String email) {

	driver.findElement(emailField).sendKeys(email);
      return this;
  }
  
  public SignUpPage typeConfirmEmailField(String email) {...}

  public SignUpPage typePassword(String password) {...}

  public SignUpPage typeName(String name) {...}

  public SignUpPage setMonth(String month) {...}
 
  public SignUpPage typeDay(String day) {...}

  public SignUpPage typeYear(String year) {...}
As we can see, there is a description of variables with locators for the elements of the registration page at the top of the java file. There are methods directly for interacting with elements of our page below the variable section.
Let’s open the tests themselves:
private WebDriver driver;
private SignUpPage page;

@BeforeMethod
public void setUp() {
       System.setProperty("webdriver.gecko.driver", "C:\\Users\\Nikita\\IdeaProjects\\autotests_examples\\drivers\\geckodriver.exe");
       driver = new FirefoxDriver();
       driver.manage().timeouts().impicitlyWait(10, TimeUnit.SECONDS);
       driver.get("https://www.spotify.com/us/signup/");
}
As we can see, in the @BeforeMethod annotation we describe what will have before each method.
@Test
public void typeInvalidYear() {
     page = new SignUpPage(driver);
     page.setMonth("December");
                 .typeDay("20")
                 .typeYear("85")
                 .setShare(true);
      Assert.assertTrue(page.isErrorVisible("Please enter a valid year."));
The @Test annotation provides code for test methods.
@AfterMethod
    public void tearDown() {
        driver.quit();
    }
The @AfterMethod annotation contains the code that should be executed after each method.

When running tests using Selenium, the following will happen:
  1. Opening a separate browser window
  2. Following the url
  3. Test code execution
  4. Closing browser session after each test case
When running the next test the same things will happen. It should be mentioned that running tests on Selenium is rather a resource-consuming process.

2. Selenide: what, where, and how

So what is Selenide itself? What are its main features and advantages?

In short, Selenide is a wrapper around Selenium WebDriver that makes it quick and easy to use when writing tests. At its core, Selenide is a tool for automating user actions in a browser, focused on the convenience and ease of implementing business logic in autotests in the user’s language, without being distracted by the technical details of working with the “browser driver”. For example, we do not need to focus on working with the waiting for elements in the process of automating testing of dynamic web applications, as well as on the implementation of high-level actions on elements.

Key and main advantages of Selenide:
  • Concise jQuery-style syntax
  • Automatic handling of most problems with Ajax, waitings and timeouts
  • Automatic handling of browser lifecycle
  • Automatic creation of screenshots
The purpose of Selenide is to focus on business logic of tests and not “waste” mental energy on technical details.
Getting started with the Selenide
To get started with the Selenide we need to add the Selenide dependency to the pom.xml file. Since we no longer need the Selenium dependency, we simply remove it.
<dependency>
            <groupId>com.codeborne</groupId>
            <artifactId>selenide</artifactId>                             
            <version>5.2.8</version>
</dependency>
In order to start using Selenide in our project, we also need to make some imports. Here are the examples of import required classes:
  • import static com.codeborne.selenide.Selenide.*;
  • import static com.codeborne.selenide.Selectors.*;
  • import static com.codeborne.selenide.Condition.*;
  • import static com.codeborne.selenide.CollectionCondition.*;
For more information on how to connect Selenide using the rest of the project builders, see the Quick start section of Selenide documentation.
Working with elements, assertions, and waits
Let’s move on to the Selenide elements and consider the assertions and waits available in Selenide.
import...

public class SignUpTest {
   private SignUpPage page;

   @BeforeClass
   public static void setUp() {
      baseurl = "https://www.spotify.com";
      browser = "chrome";
       
   }
We replace the BeforeMethod annotation with the BeforeClass annotation in the test file since we no longer need it. Selenide eliminates the need to write Before and After methods as Selenide takes care of the AfterMethod function. We only have the BeforeClass annotation left to register a pair of properties.
We registered the property baseurl, which is in the configuration class and in the BeforeClass annotation and it will be the base url. Therefore, the driver.get that we used in our Selenium tests is no longer needed. We set the browser on which we will run our tests in the property browser.
We can completely abandon the Selenium driver in our test project, Selenide will take care of all the work, encapsulating it in its classes. We will only have to focus on the logic of the tests themselves.
Let’s proceed to using Selenide on our page:
public SignUpPage open() {
         
         Selenide.open (relativeOrAbsoluteUrl: "/us/signup/");
         return this;
    }
    
    public SignUpPage typeEmail(String email) {
         
         $(emailField).sendKeys(email);
         return this;
       
   }
When invoking the open method, Selenide itself starts a browser session and opens a web page. Selenide also makes sure the browser is closed at the end. Within Selenide.open we can write either the whole http url path, or we can write a relative url. Since we indicated an absolute path as a baseurl, within the Selenide.open method it’s enough to indicate just “/”.
public SignUpPage typeEmail(String email) {
    
    $(emailField.sendKeys(email);
    return this;
}


public SignUpPage typeConfirmEmailField(String email) {
    
    $(confirmEmailField).setValue(email);
    return this;
}
In order to find an element using Selenide, we should indicate $ instead of driver.findElement command used in Selenium. I.e using a one-character method we can find directly the element itself. The search method is accepted as a string, similar to the jQuery JavaScript library by default.
In order to find a list of elements using Selenide, we should indicate $$ characters. Instead of List <WebElement>, we write the ElementsCollection command that is already extended with additional methods.
To work with elements we can use both standard Selenium methods (sendKeys()) and setValue() method or its short version vаl().
As you can see, Selenide methods are more understandable. Method click() remains the same, though Selenide has several click() methods: contextClick() (right mouse button imitation) doubleClick() ( imitation of double click on element ) and so on. Having a certain element found, we can continue the search using other locators.
The difference between Selenide find() method and Selenium driver.findElement(By) is that Selenide find() can immediately receive CSS selectors and operate with the Selenide elements, not the Web elements. Basically, Selenide-elements are a more “smart” alternative to Selenium web elements.
Selenide already contains those methods, which would have to be done through an action class or some other way. Selenide allows writing brief and “nice” methods that are understandable for everybody. Selenide is also rather flexible, and due to that, we can use standard Selenium features.
You can find more information about Selenide methods in the official documentation.
Let’s look into wider and more understandable verification examples provided by Selenide:
page.getError("Please enter a valid year.").shouldBe(Condition.visible);
page.getError("When were you born?").shouldNotBe(Condition.visible);
page.getErrors().shouldHave(CollectionCondition.size(6));
page.getErrorByNumber(3).shouldHave(Condition.text("Please enter your birth month."));
Selenide verification scheme allows us to take any element, find it and use the following assertions for it: should, shouldBe, shouldHave, shouldNot, shouldNotBe and shouldNotHave. Depending on the logic and our needs, we use certain “should-methods”. When we want to check if the element exists, we use should(exist). When we want to check if the element is visible, we use shouldBe(visible) method and so on. In fact, we use only three assertions: should, shouldBe, shouldHave, or opposite to them shouldNot, shouldNotBe, shouldNotHave.
Verifications of elements and element collections on Selenide are carried out with the help of methods (assertions) described above. They play role of explicit waits in Selenide: they wait for a condition for a certain element to be satisfied.
Formulations in Selenide are quite logical and understandable. We can write our methods either using the development environment hints or using our logical assumptions. And of course, we can always take a look at the code for implementing the necessary methods described in the documentation, or we can look through the implementing of the method itself.
Automatic screenshots in tests
For JUnit:

In order to take a screenshot automatically after each failed test, we can make an import and indicate the Rule.
import com.codeborne.selenide.junit.ScreenShooter;

@Rule
public ScreenShooter makeScreenshotOnFailure = ScreenShooter.failedTests();
But in fact, it’s a rudiment, since Selenide has been taking screenshots automatically when tests fail for quite a while. It’s very convenient for error analyzing. Selenide saves all the screenshot to a build/reports/tests folder by default.
In order to take a screenshot automatically of each test (even succeeded), we use the following command:
@Rule 
public ScreenShooter makeScreenshotOnFailure = ScreenShooter.failedTests().succeededTests();
For TestNG we also make an import:
import com.codeborne.selenide.testng.ScreenShooter;

@Listeners({ ScreenShooter.class})
In order to take screenshots automatically after succeeded test, we invoke the following command before running tests:
ScreenShooter.captureSuccessfulTests = true;
We can also make a screenshot with a single line of code at any moment:
import static com.codeborne.selenide.Selenide.screenshot;

screenshot("my_file_name");
Thus, Selenide will create two files: my_file_name.png and my_file_name.html

3. Docker: features and advantages of usage

Let’s proceed to Docker itself and its advantages:
  • Rapid Deployment. There is no need to set up additional tools, we can run them in containers
  • Convenient encapsulation of applications
  • Clean monitoring
  • Easy scaling
When we talk about Docker, the following things should be clarified:
Container is a running instance that encapsulates required software. It consists of images. And it can easily be deleted and created again within a short period of time.
Container image is the basic element of each container.
Docker Hub is the main public Docker repository provided by Docker Inc. It stores a lot of container images. The service is a source of “official” images made by the Docker team or created in collaboration with developers.

Docker installing
To install Docker for Windows, we open the https://hub.docker.com and download the Docker Desktop app for Windows or MacOS.
To install Docker for Ubuntu Linux, we need the sudo apt install docker.io command.
Then we need to run Docker and configure it to start automatically when the system boots by executing the following commands:
  1. sudo systemctl start docker
  2. sudo systemctl enable docker

4. Selenoid: features and advantages

Advantages of Selenoid usage:
  • Single environment for a parallel launch of automated tests
  • Isolated environment: Selenoid allows running each browser in a separate container, which enables full isolation of the browser environment

  • Scalability: Selenoid environment does not affect qualitative and continuous testing

  • Resources consumption and utilization: Selenoid enables to maintain a high load without additional waste of resources; in addition, all inactive containers are removed at the end of each session. Thus, the level of free memory is always appropriate

  • Installation: Selenoid requires little time and effort. And in fact it is done with the help of one command

  • Simultaneous support of multiple browser versions: this option is only available if you use Selenoid; several containers with the appropriate browsers are to be built

  • Focus: challenges may emerge if multiple browsers are run on the same machine within the Selenium Grid. Due to the OS-specific nature, the focus can contain only one window. Therefore, windows can compete for it. Selenoid allows running each test in a separate container. Thus, this problem is eliminated
  • User interface and logs: all available logs are accessed easily in Selenoid. There is also the possibility of integration with the ELK stack for faster collection and analysis of current log files

Selenoid installation
Before installing Selenoid you need:
  1. Make sure you have recent Docker version installed (further we look into the usage of Selenoid together with Docker)
  2. The easiest way to install Selenoid is to download Configuration Manager that is used for automatic installation of Aerokube products. Selenoid is such a product
  3. Rename the downloaded file to cm.exe (for easy interaction)
  4. Run the following commands to start Selenoid:
./cm.exe selenoid start --vnc 
./cm.exe selenoid-ui start
The ./cm.exe selenoid start -- vnc command will download the latest Selenoid version, browser container images, web driver binaries, generate configuration files and finally start Selenoid.

The ./cm.exe selenoid-ui start command installs and starts Selenoid UI. It is a user interface to track what’s happening during the test execution.

Selenoid runs on standard Selenium 4444 port by default. We can redefine the port using the --port key.

Stats and sessions in Selenoid UI
Selenoid UI is available at the link: http://localhost:8080/
Here we can see the current quota usage, pending browsers and the queue itself. Selenoid UI gets updates via SSE, so there is no need to renew the browser to see what is going on. It will reconnect automatically after any temporary failure.
If we talk about simultaneous testing on different devices, e.g: we have a cross-platform web app with a real-life chat function, we can simultaneously test the interaction between them, that is obviously comfortable.
Selenoid UI capabilities
Selenoid UI has the following capabilities:
You can choose a browser from available browsers and UI will provide a setup example with the right capabilities. We can see from the screenshot that examples are available for several languages.
With the selection of the browser, it could be launched manually right in the interface. While executing tests, we can connect to vnc port in real-time regime, get access to the browser and even intervene in the process of autotests execution.
Logs and VNC
If you use enableVNC=true capability, you can see a list of the available statistics. VNC allows to see and interact with the browser while the log will reflect all browser actions.
VNC session:
VNC fullscreen mode:
You can also see logs of the docker container for each session even without vnc. It means, if you didn’t use — vnc flag, you’ll see logs only.
We can also view recorded videos of our tests. We can access them by opening http://localhost:4444/video/ or by going to the “Videos” tab in Selenoid UI.
Adding Selenoid to run tests within Docker container
In order to add Selenoid into the @BeforeClass annotation we need to do the following configuration:
Configuration.remote = "http://localhost:4444/wd/hub";
Configuration.browser = "chrome";
Configuration.browserSize = "1920x1080";
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability(capabilityName: "enableVNC", value: true);
capabilities.setCapability(capabilityName: "enableVideo", value: true);
Configuration.browserCapabilities = capabilities;
Since now we have the Configuration.browser = “chrome” property, we delete Property baseurl which defined the browser for running our tests:
@BeforeClass 
public static void setUp() {
       Configuration.remote = "http://10.0.75.1:4444/wd/hub";               
       Configuration.browser = "chrome";          
       Configuration.browserSize = "1920x1080";      
       DesiredCapabilities capabilities = new DesiredCapabilities();     
       capabilities.setCapability(capabilityName: "enableVNC", value: true);        
       capabilities.setCapability(capabilityName: "enableVideo", value: true);      
       Configuration.browserCapabilities = capabilities;
Selenoid advanced capabilities
  • Data storage in RAM: Selenoid stores all temporary files in Tmpfs. It is a temporary file repository that allows storing files in RAM. As we know, access to RAM is performed much faster than to the file system of the hard drive.
  • Various screen resolution types: we can configure the appropriate screen resolution for a running container on their own by setting the required parameters in the Browser Capabilities.
  • Video recording of tests: it’s possible to record the video of the tests performed. For instance, the activation in the Google Chrome browser is implemented by setting the parameter true in the Browser Capabilities:

    ChromeOptions options = new ChromeOptions ();
    options.setCapability (“enableVideo”, true);
Using Selenoid without Docker
Selenoid uses containers to run browsers, but there are cases when it’s not possible to run a browser within a container. For example, in Windows we have Internet Explorer, that can not be run inside the container. Selenoid can be used as a lightweight Selenium server replacement to run IE, Firefox or Chrome on Windows. For example to use Selenoid with IE.
To do it we need:
1. Download latest IEDriverServer archive and unpack it to some directory (C:\ in this example)
2. Download latest Selenoid binary
3. Create browsers.json configuration file:
{     
   "internet explorer": {      
      "default": "11",      
      "versions": {         
         "11": {            
            "image": ["C:\\IEDriverServer.exe", "--log-level=DEBUG"]          
            }       
        }    
    } 
}
4. Start Selenoid:
./selenoid_win_amd64.exe -conf ./browsers.json -disable-docker
5. Run the tests, using endpoint http://localhost:4444/wd/hub with the following capabilities:
browserName = internet explorer
version = 11
6. To start Chrome, just download Chromedriver binary and modify browsers.json accordingly
7. Selenoid does not process launched driver logs by default. So we need to launch Selenoid with the -capture-driver-logs flag to append driver logs for each session into the main log

Summarizing

Solution on the basis of Selenide + Selenoid in Docker container demonstrates high flexibility for configuration of the runtime environment. The stability of this solution, significant time savings when using it and a number of additional features allow us to optimize the process and ensure high-quality software products in a short time.
As a result, it is easy to give preference to the above solutions, since they allow us to perform testing automation tasks quickly and accurately.
Previously published at https://intexsoft.com/

Written by andrew_intexsoft | Full-Stack Developer
Published by HackerNoon on 2020/01/24