Blog

Selenium IDE

The original Selenium IDE was a well known plugin for Firefox which helped in the tests creation as a record & playback tool, however as it was no longer supported back in 2017 by the then released  Firefox version, it unfortunately ceased to exist. In spite of this, in recent years a new Selenium IDE has been released and revamped by Applitools with many advancements over its predecessor, to name a few:

  • Support for Chrome, Firefox, MS Edge
  • Smart locators
  • Control flow statements such as conditionals and loops
  • Automatic waits
  • Supports embedded JavaScript
  • Debugger with breakpoints
  • Code exports
  • Parallel execution with Selenium side runner
  • Create and insert community plugins

Let’s take a look into some of them.

  • Fallback Locators

During the recording the IDE automatically stores different selectors for the elements it interacts with along the way, so in case that the primary selector cannot be found, after an automatic wait time, the mechanism will resort to a secondary one. Let’s look at how the selectors are stored, be it during a usual recording, or with the Select target button.

Here we can see the different locators automatically created for the opencart search bar with the locator method it is using and the possibility to alter it in case we are not comfortable with the automatically generated ones.

With the Select target button we can store the locators by selecting the elements directly in the website and then see them highlighted with the Find target one to check if it can be effectively found.

What happens when in the middle of our execution the element cannot be found? This is the behavior:

In this case what happened was that the element that that was trying to find is the first product name on the grid, however the original recorded locator had been done using the iMac product name, so the primary locator, as can be seen, was xpath=//a[contains(text(),’iMac’)]. Thus, it tried to find this element for 30 seconds but when this timeout finished it resorted to a secondary one which was css=.product-layout:nth-child(1) .caption a. This can be observed in the logs above.

  • Reusable steps

Selenium IDE allows us to reuse the same structure for different tests, which can be called from different tests then with the run command targeting the tests which holds the common steps.

Let’s look at the following example, we’ll try 2 searches in each test from which the first will produce no results, but in the second we’ll select the first product that shows up, and validate that the page title and the product name have the correct content.

So, we have this test called search_template in which we open the opencart, we search the values contained in the variables ${search_text1}  and ${search_text2} respectively, then in the second search the first product displayed is selected.

So now we’re going to call this script from the Search laptop and mac and Search monitor and samsung tests. As displayed, first the variables are set, then the script is called, and the validations take place.

First test

Second test

  • Control flows

We count with certain commands to include as steps to include conditionals, or loops given a certain condition, or maybe just repeat steps a N number of times. We’re going to try some of this controls in the next scenario: 

Let’s store the number of list items in the categories bar, in a variable, then if that number is 8 we’ll click the cart button, otherwise the currency will be selected. After that, we’ll type hello that number of times using the times command, and then using a variable, a loop that repeats while that variable is less or equal to 5. What that loop does is add the iPhone, that appears below, to the cart, pause for 2 seconds and update the variable. Finally we just check that the text in the cart has 5 products added as expected.

Notice how every time a control flow is closed the end command is used.

Let’s see it in action.


  • Code Exports

The plugin offers the chance to export the code to different languages and testing frameworks as it is displayed below.

We’re going to try the Java for JUnit option and compare how faithful the generated files are to the steps defined in the IDE.

So, next thing we do is export our tests, the control flow one plus both search tests, selecting the options to include origin tracing comments and steps description.

Control Flow

// Generated by Selenium IDE

// Generated by Selenium IDE
import org.junit.Test;
import org.junit.Before;
import org.junit.After;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.core.IsNot.not;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Alert;
import org.openqa.selenium.Keys;
import java.util.*;
import java.net.MalformedURLException;
import java.net.URL;
public class ControlflowTest {
  private WebDriver driver;
  private Map<String, Object> vars;
  JavascriptExecutor js;
  @Before
  public void setUp() {
    driver = new ChromeDriver();
    js = (JavascriptExecutor) driver;
    vars = new HashMap<String, Object>();
  }
  @After
  public void tearDown() {
    driver.quit();
  }
  @Test
  public void controlflow() {
    // Test name: Control flow
    // Step # | name | target | value
    // 1 | open | \ | 
    driver.get("http://opencart.abstracta.us\\");
    // 2 | setWindowSize | 1382x744 | 
    driver.manage().window().setSize(new Dimension(1382, 744));
    // 3 | waitForElementVisible | linkText=Your Store | 30000
    {
      WebDriverWait wait = new WebDriverWait(driver, 30);
      wait.until(ExpectedConditions.visibilityOfElementLocated(By.linkText("Your Store")));
    }
    // 4 | storeXpathCount | xpath=//ul[contains(@class,'navbar')]/li | myVar
    vars.put("myVar", driver.findElements(By.xpath("//ul[contains(@class,\'navbar\')]/li")).size());
    // 5 | if | ${myVar}==8 | 3000
    if ((Boolean) js.executeScript("return (arguments[0]==8)", vars.get("myVar"))) {
      // 6 | click | css=.btn-inverse | 
      driver.findElement(By.cssSelector(".btn-inverse")).click();
      // 7 | else |  | 
    } else {
      // 8 | click | css=.btn-group > .btn-link | 
      driver.findElement(By.cssSelector(".btn-group > .btn-link")).click();
      // 9 | end |  | 
    }
    // 10 | times | ${myVar} | 
    Integer times = vars.get("myVar").toString();
    for(int i = 0; i < times; i++) {
      // 11 | sendKeys | name=search | hello 
      driver.findElement(By.name("search")).sendKeys("hello ");
      // 12 | end |  | 
    }
    // 13 | store | 1 | i
    vars.put("i", "1");
    // 14 | while | ${i} <= 5 | 
    while ((Boolean) js.executeScript("return (arguments[0] <= 5)", vars.get("i"))) {
      // 15 | click | css=.product-layout:nth-child(2) button:nth-child(1) | 
      driver.findElement(By.cssSelector(".product-layout:nth-child(2) button:nth-child(1)")).click();
      // 16 | pause | 2000 | 
      try {
        Thread.sleep(2000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      // 17 | executeScript | return Number (${i}) + 1 | i
      vars.put("i", js.executeScript("return Number (arguments[0]) + 1", vars.get("i")));
      // 18 | end |  | 
    }
    // 19 | click | css=.btn-inverse | 
    driver.findElement(By.cssSelector(".btn-inverse")).click();
    // 20 | assertText | css=.text-right:nth-child(3) | x 5
    assertThat(driver.findElement(By.cssSelector(".text-right:nth-child(3)")).getText(), is("x 5"));
  }
}

Right off the bat, it can be observed that every single step was converted with the commented reference to the step in the IDE.

We can also see a setup and a tear-down method where the Chrome driver gets created and then terminated, a JavaScript executor and a Hashmap to contain variables.

There are minor issues that can be addressed as how the code is conjured up, namely the declaration of the entire selector every time an element is interacted with or the way in which a condition is handled which, as depicted, is a JavaScript expression, as it is evaluated in the plugin, that gets parsed to a Boolean, so it does not actually use the same condition as it would be declared in the target language. Nevertheless, asides from that it is quite useful to be integrated in a framework, with some tweaks in order to make it more organized, maybe adapt it to a particular pattern like page object model.

Search products

public void searchtemplate() {
    driver.get("http://opencart.abstracta.us/");
    driver.manage().window().setSize(new Dimension(1382, 744));
    {
      WebDriverWait wait = new WebDriverWait(driver, 30);
      wait.until(ExpectedConditions.visibilityOfElementLocated(By.linkText("Your Store")));
    }
    driver.findElement(By.name("search")).click();
    driver.findElement(By.name("search")).sendKeys(vars.get("search_text1").toString());
    driver.findElement(By.cssSelector(".btn-default")).click();
    driver.findElement(By.name("search")).click();
    driver.findElement(By.name("search")).sendKeys(vars.get("search_text2").toString());
    driver.findElement(By.name("search")).sendKeys(Keys.ENTER);
    driver.findElement(By.cssSelector(".product-layout:nth-child(1) .caption a")).click();
  }
  @Test
  public void searchmonitorandsamsung() {
    // Test name: Search monitor and samsung
    // Step # | name | target | value
    // 1 | store | monitor | search_text1
    vars.put("search_text1", "monitor");
    // 2 | store | samsung | search_text2
    vars.put("search_text2", "samsung");
    // 3 | run | search_template | 
    searchtemplate();
    // 4 | assertTitle | Samsung SyncMaster 941BW | 
    assertThat(driver.getTitle(), is("Samsung SyncMaster 941BW"));
    // 5 | assertText | css=h1:nth-child(2) | Samsung SyncMaster 941BW
    assertThat(driver.findElement(By.cssSelector("h1:nth-child(2)")).getText(), is("Samsung SyncMaster 941BW"));
  }

@Test
  public void searchlaptopandmac() {
    // Test name: Search laptop and mac
    // Step # | name | target | value
    // 1 | store | laptop | search_text1
    vars.put("search_text1", "laptop");
    // 2 | store | mac | search_text2
    vars.put("search_text2", "mac");
    // 3 | run | search_template | 
    searchtemplate();
    // 4 | assertTitle | iMac | 
    assertThat(driver.getTitle(), is("iMac"));
    // 5 | assertText | css=h1:nth-child(2) | iMac
    assertThat(driver.findElement(By.cssSelector("h1:nth-child(2)")).getText(), is("iMac"));
  }

I have omitted the setup and tear-down methods and the dependencies this time, but it can be observed how exporting a test that depends on another script generates the latter as a function to be called from the test itself, settings the variables beforehand and using them in the common method afterwards.

In short, it’s safe to say that the code generated reflects the steps defined quite accurately, except for the few details mentioned before, which should be no problem to rearrange.

Summary

To sum up, the plugin is quite effective at what it intends to do, tests can be created on the fly through its record & playback feature reordering the steps, adding or deleting others if necessary, reusing behavior for different tests which is greatly useful. The code exporting feature does a proper job, the rest is up to how the tester uses that code in their tests. Something that could be improved, in my opinion, is having a library of easily identified and categorized selectors where they are stored and from where to pick in case new steps are added or the same element is interacted with one more time, though it’s more of a nice to have than anything. What comes across as truly resourceful is the capability to fallback to another locator when the primary is missing, acting as a fail-safe in case something goes missing, albeit in the code export only the primary is shown, so it would be another nice to have being able to export the entire selector list for an element.

Follow us on Linkedin, Facebook, Twitter, and Instagram to be part of our community!

248 / 422