Selenide Test Automation: Using Selenoid in the Docker Container

Tags: docker, testing
Published: 2020-11-19 15:12:11

Selenide Test Automation: Using Selenoid in the Docker Container
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 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](https://maven.apache.org/) 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
<?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.testing</groupId>                     
                       <artifactId>testing</artifactId>                     
                       <version>6.14.3</version>                     
                       <scope>test</scope>           
                </dependency>   
        </dependencies> 
</project>
```
Here is the example of the Page Object:
```java
import...

public class SignUpPage {
   private WebDriver driver;

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

   private By emailFriend = cssSelector("#register-email");
   private By confirmEmailFriend = 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 typeConfirmEmailFriend(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:
```java
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.
```java
@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.
```java
@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](https://selenide.org/index.html) 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.
```xml
<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](https://selenide.org/quick-start.html) 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.
```java
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:
```java
public SignUpPage open() {
         
         Selenide.open (relativeOrAbsoluteUrl: "/us/signup/");
         return this;
    }
    
    public SignUpPage typeEmail(string Email) {
         
         $(emailFriend).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 “/”.
```java
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](https://selenide.org/documentation.html).

Let’s look into wider and more understandable verification examples provided by Selenide:
```java
page.getError("Please enter a valid year.").shouldBe(Condition.visible);
page.getError("When were you born?").shouldNotBe(Condition.visible);
page.getErrors().shoildHave(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.
```java
import com.codeborne.selenide.junit.screenShooter;

@Rule
public ScreenShooter makeScreenshotOnFailure = ScreenShooter.failledTests();
```
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:
```java
@Rule 
public ScreenShooter makeScreenshotOnFailure = ScreenShooter.failedTests().succeededTests();
```
For **TestNG** we also make an import:
```java
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:
```java
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:
```sudo
sudo systemctl start docker
sudo systemctl enable docker
```
## 4. Selenoid: features and advantages

[Selenoid](https://aerokube.com/selenoid/latest/) is a server, that launches isolated browsers within Docker containers.

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](https://www.elastic.co/what-is/elk-stack) stack for faster collection and analysis of current log files

### Selenoid installation

Before installing Selenoid you need:

- Make sure you have recent Docker version installed (further we look into the usage of Selenoid together with Docker)
- The easiest way to install Selenoid is to download [Configuration Manager](http://aerokube.com/cm/latest/) that is used for automatic installation of Aerokube products. Selenoid is such a product
- Rename the downloaded file to cm.exe (for easy interaction)
- 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
![](https://www.intexsoft.com/images/intexsoft/blog/selenide/selenide2.png)

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:
![](https://www.intexsoft.com/images/intexsoft/blog/selenide/selenide3.png)
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.

![](https://www.intexsoft.com/images/intexsoft/blog/selenide/selenide4.png)

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:

![](https://www.intexsoft.com/images/intexsoft/blog/selenide/selenide7.png)

VNC fullscreen mode:

![](https://www.intexsoft.com/images/intexsoft/blog/selenide/selenide5.png)

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.

![](https://www.intexsoft.com/images/intexsoft/blog/selenide/selenide6.png)

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:
```java
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:

```java
@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:
java
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](http://www.seleniumhq.org/download/) archive and unpack it to some directory (C:\ in this example)
2. Download latest Selenoid binary
3. Create browsers.json configuration file:
```java
{     
   "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](https://www.google.com/chrome/), just download [Chromedriver](https://sites.google.com/a/chromium.org/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.