Choosing tools for UI testing: Selenium or Selenide?

Writing a good UI test might be a tough task. AJAX requests, timeouts, and pages with dynamic content are just a few items from the list of problems testers deal with. Selenium Webdriver is one of the most popular products in the UI tests domain. But it is crucial to have a clear understanding that Selenium is a low-level system library for managing browsers. It is not designed for handy test writing.

Choosing tools for UI testing: Selenium or Selenide?

Selenide is a framework that is powered by Selenium WebDriver. Selenide was created as a tool for writing automated tests. This framework has got a number of unique features, such as:

  • Compact syntax;
  • Smart waits;
  • Simple configuration;

Let’s figure out how Selenide simplifies testers’ tasks and which advantages Selenide has over Selenium within the context of UI tests.

Working with explicit and implicit waits

Selenium is equipped with waiting mechanisms for various events that take place on a page, but they do not cover everything. For instance, there is no possibility to wait till an element disappears from the page. You can define the default timeout for the implicit wait:

  driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);  

The explicit wait takes a few lines of code:

  new WebDriverWait(webDriver, 30)
   .until(ExpectedConditions
     .visibilityOfElementLocated(By.className("banner-items"));
  

The default value of the implicit wait in Selenide is 4 seconds, so if a page does not load at once, Selenide makes a pause before marking the test as failed. You can easily configure the timeout’s value:

  Configuration.timeout = 3000;  

Explicit waits in Selenide are visually simpler. All you need to do is to select an element and tell Selenide how long it should wait till a particular condition is satisfied:

  $(By.className("banner-items")).waitUntil(visible, 30000);  

And you do have a possibility to wait till an element disappears from the page:

  $(“.test”).should(disappear);  

Working with a driver

Selenium does not have a mechanism for straightforward access to the webdriver’s object. That is the reason why testers have to invent their own mechanisms. This is neither handy, nor productive:

  @Test
 public void searchSelenideInGoogle() {
   WebDriver driver = Browser.launch();
   driver.findElement(By.name("q"));
   ...
   driver.quit();
 }  

In Selenide you do not need to create a webdriver. As a matter of fact, the developers anticipate that you will not even see it - everything is ready for use. This way, a test can look like this:

  @Test
 public void searchSelenideInGoogle() {
   open("https://google.com/ncr");
   $(By.name("q")).val("selenide").pressEnter();
   ...
 }  

Of course, if you need a webdriver for some purposes, you can call a static method from the class that returns the current driver:

  WebDriverRunner.getWebDriver();  

Working with elements

Working with elements in Selenium and Selenide differs. In Selenium all actions are performed by means of the driver. You get an element and then call the required methods:

  @Test
 public void searchSelenideInGoogle() {
   WebDriver driver = Browser.launch();
   driver.get("https://www.google.com");
   WebElement element = driver.findElement(By.name("q"));
   element.sendKeys("selenide");
   element.submit();
   driver.findElement(By.id("res"));
   assertThat(driver.findElement("#ires .g").getText(), containsString("Selenide"));
   driver.quit();
 }  

Differences in Selenide are significant and one sees them once a page is opened. In general, working with elements has been simplified - let’s inspect the following sample:

  @Test
 public void searchSelenideInGoogle() {
   open("https://google.com/ncr");
   $(By.name("q")).val("selenide").pressEnter();
   $$("#ires .g").shouldHave(sizeGreaterThan(1));
   $("#ires .g").shouldBe(visible).shouldHave(
     text("Selenide"),
     text("selenide.org"));
 }  

So, the driver has been defined. The $ symbol returns one element (if the selector finds several elements, the first available will be chosen). You are able to call the methods of interaction with elements one by one. Assign the Selenide value and state that the Enter button should be pressed afterwards.

Use two $ symbols to obtain the collection of elements. This is a separate object that is inherited from collection. Interacting with it is similar to working with collection, but some unique methods are available. For instance, you can immediately check that the number of results is more than 1. Also, it is possible to take any element and make sure that it is visible and contains Selenide and selenide.org text.

Matchers play an important role as well. This sample contains shouldHave and shouldBe. Matchers can work with implicit waits, so if a condition is not satisfied at once, Selenide will wait a bit before the test crashes. Find more details about matchers in chapter 6.

Searching for elements

Search by text: use the byText method to find elements by text. You do not need to specify the xpath element:

  $(byText("Some Text"));  

Filter results: Obtain a collection and filter out the results by means of the filter method, which allows to specify the required criteria. Then use the find method to identify the elements that satisfy the specific conditions among the obtained results:

  $$(".item").filter(visible).find(matches("Specific .* item text"));  

Search by parents: Perform search not only inside the elements, but within the structure too. The Selenide developers state that you do not need to use the xpath element anymore:

  $$(".cell").findBy(text("Some Text")).parent();  

Search within an element:
This functionality is similar to its counterpart in Selenium:

  $(byText("Some Text")).$(".inner_item");  

Working with page objects

It is worth mentioning the differences between working with page objects. In Selenium one must define fields with the required elements, specify the FindBy annotation, and use PageFactory for initialization. The main problem of such an approach is that annotations do not make it possible to search within a particular element rather than the whole page:

  public class SomePage {
   @FindBy(id = "text")
   private WebElement searchField;
   @FindBy(css = "input[type=\"submit\"]")
   private WebElement searchButton;
   public void init(final WebDriver driver) {
     PageFactory.initElements(driver, this);
   }
 }  

A completely different approach is employed in Selenide. The use of lazy initialization allows to enumerate elements, while the search is triggered only when you try to reach a particular element:

  public class SomePage {
   private SelenideElement searchField = $(By.id("text"));
   private SelenideElement searchFieldInnerElement = searchField.$(".inner");
   private SelenideElement searchButton = $("input[type=\"submit\"]");
 }  

Matchers, errors, screenshots

The use of matchers allows to check the following (among other use cases)

  • Text content;
  • The particular size of the collection;
  • Visibility;
  • Disappearance of the element;
  • Focusing on the element.
  $("#ires .r").shouldHave(text("SomeText"));
 $$("#ires .r").shouldHave(sizeGreaterThan(1));
 $("#ires .r").shouldBe(visible);
 $("#ires .r").should(disappear);
 $("#ires .r").shouldBe(focused);
  

In case an error occurs, you will see a clear message. For instance, Selenide informs about the text you are looking for, the used selector, what attributes are taken into account, and which element is actually found.

Selenide is able to make screenshots automatically in case an error occurs (if you have enabled this feature).

Customization

If you need to check a condition that is not available out-of-the-box, you can easily create it yourself. Let’s consider the case of writing a CSS condition.

Create a static method with arbitrary parameters in any class. This method must return the instance of the Condition class. Specify the new matcher’s name in a constructor. In this class you must implement the apply method, which performs the required checking, and the actual value method, which returns the actual value of the entity that is being checked:

  public static Condition css(final String propName, final String propValue) {
   return new Condition("css") {
     @Override
     public boolean apply(WebElement element) {
       return propValue.equalsIgnoreCase(element.getCssValue(propName));
     }
     @Override
     public String actualValue(WebElement element) {
       return element.getCssValue(propName);
     }
   };
 }  

Then you can check whether an element has got particular parameters. All the conditions are easily read:

  $("h1").shouldHave(css("font-size", "16px"));  

In addition, you can customize built-in operations on elements. It is possible to override elements-related methods. Each method has got a separate class that supports inheriting. The following example demonstrates that you have a possibility to keep in mind the peculiarities of testing on mobile devices and desktops. Thus, when working with Android or iOS operating systems the tapElement method will be used, while the standard click will do the job for browsers:

  public class CustomClick extends Click {
   @Override
   public void click(WebElement element) {
     if (isIOS() || isAndroid()) {
       tapElement(element);
     } else {
       super.click(element);
     }
   }
 }  

For the proper initialization you will need to specify that your new custom class shall be used for click:

  Commands.getInstance().add("click", new CustomClick());  

Built-in profiler

Selenide comes with the built-in test profiler. You just need to specify the test class’s annotation to view a table in a console once a test is finished. This table displays both the complete list of actions that are taken during a test, and time spent on each of these actions. Use the profiler to identify the most time-consuming operations and optimize the tests’ performance.

Simple method names

The method names in Selenide are rather simple. Just try to type the name of an action you want to be executed when working in an IDE. As a rule, you can quickly find required actions, so there is no need to look through the documentation. shouldBe, shouldHave, shouldNot are self-explanatory samples.

Compatibility with Selenium

Switching to Selenide should not worry you even if you have hundreds of Selenium tests. The compatibility between Selenium and Selenide allows to start using the latter at any time. The only thing you need to do is to specify the webdriver:

  WebDriverRunner.setWebDriver(driver);  

The WebElement interface is enhanced by means of SelenideElement. It makes it possible to transfer Selenide elements to old methods that work with WebElement. On the other hand, you can transform any WebElement element into SelenideElement as follows:

  $(oldFashionedWebElement)  

Summing it up, you can switch to Selenide smoothly and avoid losing existing tests.

Selenide helps to write stable tests and save time. It is easy to study and its compatibility with Selenium allows to take advantages of the framework’s features at any time. Why not consider the possibility to get familiar with a new tool?