Before you go, check out these stories!

0
Hackernoon logoSelenide Test Automation: Using Selenoid in the Docker Container by@andrew_intexsoft

Selenide Test Automation: Using Selenoid in the Docker Container

Author profile picture

@andrew_intexsoftAndrew Sushevich

Web Architect and Full Stack Developer

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Ā 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.testing</groupId>                     
                       <artifactId>testing</artifactId>                     
                       <version>6.14.3</version>                     
                       <scope>test</scope>           
                </dependency>   
        </dependencies> 
</project>

Here is the example of the Page Object:

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:

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) {
         
         $(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 ā€œ/ā€.

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().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.

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:

@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://www.intexsoft.com/blog/post/selenide-docker.html

Tags

Join Hacker Noon

Create your free account to unlock your custom reading experience.