Tag Archives: Selenium

Component Object pattern example

Previously I showed how we at Dotsub use Page Object pattern for Selenium testing. But we use one more abstraction to make end-to-end test more maintainable. We call it Component Object pattern. This blog post will explain it on example.

Web development is shifting more and more towards reusable components. Frameworks like React, Polymer, Angular, Ember and others provide various component friendly abstractions to make front-end code-bases more maintainable. So our web applications are now full of “widgets” that have same behavior. We can use component various times on single web page or re-use it on various web pages.

Therefore it is logical to create abstraction which covers functionality of single component and reuse it across end-to-end tests. As I mentioned before, we going to call it Component Object pattern. So when we have various same components on single web page, we are going to use various Component Objects of same type per Page Object.

Example project for testing

Now we need to application we are going to test. Example application is hosted in this Github repository. You can run it by executing command from root directory:

./gradlew bootRun

From this command, you can find that it is Spring Boot application based on Gradle build system. In Dotsub, we are using Spring Boot + Java on back-end, but in fact there is no back-end mentioned example web application. We don’t need back-end for our demonstration.

For UI, we going to use React + Redux combo. I chose famous Dan Abramov’s Todos example to demonstrate Component Object pattern on. But I needed to amend it a little bit to reuse components. Without re-usable components we couldn’t demonstrate Component Object pattern.

After visiting URL http://localhost:8080, we can see following page:

Component Object pattern

There are two input components with buttons. One creates item in Todo list and second creates item in Grocery list:

Component Object pattern

When you click on item, it will mark it as completed (strike-through). When you click on completed item, it will become active again. Last element on the page is filter. You can show only active items:

Component Object pattern

or only completed items:

Component Object pattern

UI code is hosted in this Github repository, located under folder src/main/ui. I will leave this code for self study because the implementation is not deeply relevant for end-to-end testing code. Important fact for us is that input components / list components for Todo and Grocery lists should have same behavior. Thus they can be covered by reusable Component Objects pattern. Interesting is that they doesn’t necessary need to be implemented as same component in UI code.

The only important for our testing are CSS classes of particular components:

  • AddTodo input component is using CSS class add-todo
  • TodoList is using CSS class todo-list
  • AddGroceryItem input component is using CSS class add-grocery-item
  • GroceryList is using CSS class grocery-list

Note that goal of this blog post isn’t to explain Selenium or its APIs. It is expected for reader to be slightly familiar with them already.

Component Object for adding the item

First explained component object will control adding the item:

package net.lkrnac.blog.pageobject.e2e;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

class AddItemComponent {
    private WebDriver driver;
    private String containerCssSelector;

    AddItemComponent(WebDriver driver, String containerCssSelector) {
        this.driver = driver;
        this.containerCssSelector = containerCssSelector;
    }

    AddItemComponent addItem(String todo) {
        WebElement input = driver.findElement(By.cssSelector(containerCssSelector + " input"));
        input.sendKeys(todo);
        WebElement button = driver.findElement(By.cssSelector(containerCssSelector + " button"));
        button.click();
        return this;
    }
}

Alongside Selenium web driver instance, this component object also accepts CSS selector of component it’s going to control. It has just one method addItem, which enters a text into input field and clicks Add button. It creates new item.

Component Object for item list

package net.lkrnac.blog.pageobject.e2e;

import org.apache.commons.lang3.StringUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import java.util.List;

import static java.lang.String.format;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

class ItemsListComponent {
    private final WebDriver driver;
    private final String containerCssSelector;

    ItemsListComponent(WebDriver driver, String containerCssSelector) {
        this.driver = driver;
        this.containerCssSelector = containerCssSelector;
    }

    ItemsListComponent clickOnItem(String todoItem) {
        findElementWithText(todoItem).click();
        return this;
    }

    ItemsListComponent verifyItemShown(String todoItem, boolean expectedStrikethrough) {
        WebElement todoElement = findElementWithText(todoItem);
        assertNotNull(todoElement);
        boolean actualStrikethrough = todoElement.getAttribute("style").contains("text-decoration: line-through;");
        assertEquals(expectedStrikethrough, actualStrikethrough);
        return this;
    }

    ItemsListComponent verifyItemNotShown(String todoItem) {
        assertTrue(findElementsWithText(todoItem).isEmpty());
        return this;
    }

    private WebElement findElementWithText(String text) {
        return driver.findElement(getConditionForText(text));
    }

    private List<WebElement> findElementsWithText(String text) {
        return driver.findElements(getConditionForText(text));
    }

    private By getConditionForText(String text) {
        String containerClassName = StringUtils.substring(containerCssSelector, 1);
        return By.xpath(format("//*[@class='" + containerClassName + "']//*[text()='%s']", text));
    }
}

Similar to AddItemComponent, ItemsListComponent also takes Selenium web driver instance and CSS selector of belonging component as constructor parameters. It exposes function clickOnItem for which clicks on particular item. Other two non-private methods are used to verify if particular item is shown (verifyItemShown) or hidden (verifyItemNotShown).

Page Object using Component Objects

Now it’s time to explain Page Object using mentioned Component Objects:

package net.lkrnac.blog.pageobject.e2e;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import static java.lang.String.format;

class ItemsPageObject {
    private final WebDriver driver;
    private final WebDriverWait wait;
    private final ItemsListComponent todoItemsList;
    private final AddItemComponent addTodoItemComponent;
    private final ItemsListComponent groceryItemsList;
    private final AddItemComponent addGroceryItemComponent;

    ItemsPageObject(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, 10);
        todoItemsList = new ItemsListComponent(driver, ".todo-list");
        addTodoItemComponent = new AddItemComponent(driver, ".add-todo");
        groceryItemsList = new ItemsListComponent(driver, ".grocery-list");
        addGroceryItemComponent = new AddItemComponent(driver, ".add-grocery-item");
    }

    ItemsPageObject get() {
        driver.get("localhost:8080");
        wait.until(ExpectedConditions.elementToBeClickable(By.tagName("button")));
        return this;
    }

    ItemsPageObject selectAll() {
        findElementWithText("All").click();
        return this;
    }

    ItemsPageObject selectActive() {
        findElementWithText("Active").click();
        return this;
    }

    ItemsPageObject selectCompleted() {
        findElementWithText("Completed").click();
        return this;
    }

    ItemsPageObject addTodo(String todoName) {
        addTodoItemComponent.addItem(todoName);
        return this;
    }

    ItemsPageObject addGroceryItem(String todoName) {
        addGroceryItemComponent.addItem(todoName);
        return this;
    }

    ItemsListComponent getTodoList() {
        return todoItemsList;
    }

    ItemsListComponent getGroceryList() {
        return groceryItemsList;
    }

    private WebElement findElementWithText(String text) {
        return driver.findElement(getConditionForText(text));
    }

    private By getConditionForText(String text) {
        return By.xpath(format("//*[text()='%s']", text));
    }
}

In constructor it creates component objects with correct CSS selectors. As you can see various instances of same component object type are used to control similar components on the page. Method get opens the page and waits until it’s loaded. Methods selectAll, selectActive, selectCompleted are used to control filter component on the page. Methods addTodo and addGroceryItem are used to enter new item into particular list. Finally getters getTodoList and getGroceryList are useful to let test class enable control over list components. Exposing component object instances to directly seemed easier than wrapping all their functions in Page Object.

Test Cases using Page Object with Page Components

I believe final test cases are readable and doesn’t require comments (which is result of using Page Object + Page Component patterns):

package net.lkrnac.blog.pageobject.e2e;

import io.github.bonigarcia.wdm.ChromeDriverManager;
import net.lkrnac.blog.pageobject.TodoApplication;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TodoApplication.class)
@WebIntegrationTest
public class ItemsAppTest {
    private static WebDriver driver;

    @BeforeClass
    public static void setUp() {
        ChromeDriverManager.getInstance().setup();
        driver = new ChromeDriver();
    }

    @AfterClass
    public static void tearDown() {
        driver.quit();
    }

    @Test
    public void testCreateTodos() {
        // GIVEN
        new ItemsPageObject(driver).get()

            // WHEN
            .addTodo("Buy groceries")
            .addTodo("Tidy up")

            // THEN
            .getTodoList()
            .verifyItemShown("Buy groceries", false)
            .verifyItemShown("Tidy up", false);
    }

    @Test
    public void testCompleteTodo() {
        // GIVEN
        new ItemsPageObject(driver).get()
            .addTodo("Buy groceries")
            .addTodo("Tidy up")
            .getTodoList()

            // WHEN
            .clickOnItem("Buy groceries")

            // THEN
            .verifyItemShown("Buy groceries", true)
            .verifyItemShown("Tidy up", false);
    }

    @Test
    public void testSelectTodosActive() {
        // GIVEN
        ItemsPageObject todoPage = new ItemsPageObject(driver).get();

        todoPage
            .addTodo("Buy groceries")
            .addTodo("Tidy up")
            .getTodoList()
            .clickOnItem("Buy groceries");

        // WHEN
        todoPage
            .selectActive()

            // THEN
            .getTodoList()
            .verifyItemNotShown("Buy groceries")
            .verifyItemShown("Tidy up", false);
    }

    @Test
    public void testSelectTodosCompleted() {
        // GIVEN
        ItemsPageObject todoPage = new ItemsPageObject(driver).get();
        todoPage
            .addTodo("Buy groceries")
            .addTodo("Tidy up")
            .getTodoList()
            .clickOnItem("Buy groceries");

        // WHEN
        todoPage
            .selectCompleted()

            // THEN
            .getTodoList()
            .verifyItemShown("Buy groceries", true)
            .verifyItemNotShown("Tidy up");
    }

    @Test
    public void testSelectTodosAll() {
        // GIVEN
        ItemsPageObject todoPage = new ItemsPageObject(driver).get();
        todoPage
            .addTodo("Buy groceries")
            .addTodo("Tidy up")
            .getTodoList()
            .clickOnItem("Buy groceries");
        todoPage
            .selectCompleted()

            // WHEN
            .selectAll()

            // THEN
            .getTodoList()
            .verifyItemShown("Buy groceries", true)
            .verifyItemShown("Tidy up", false);
    }

    @Test
    public void testCreateGroceryItems() {
        // GIVEN
        new ItemsPageObject(driver).get()

            // WHEN
            .addGroceryItem("avocados")
            .addGroceryItem("tomatoes")

            // THEN
            .getGroceryList()
            .verifyItemShown("avocados", false)
            .verifyItemShown("tomatoes", false);
    }

    @Test
    public void testCompleteGroceryItem() {
        // GIVEN
        new ItemsPageObject(driver).get()
            .addGroceryItem("avocados")
            .addGroceryItem("tomatoes")
            .getGroceryList()

            // WHEN
            .clickOnItem("avocados")

            // THEN
            .verifyItemShown("avocados", true)
            .verifyItemShown("tomatoes", false);
    }

    @Test
    public void testSelectGroceryItemsActive() {
        // GIVEN
        ItemsPageObject todoPage = new ItemsPageObject(driver).get();

        todoPage
            .addGroceryItem("avocados")
            .addGroceryItem("tomatoes")
            .getGroceryList()
            .clickOnItem("avocados");

        // WHEN
        todoPage
            .selectActive()

            // THEN
            .getGroceryList()
            .verifyItemNotShown("avocados")
            .verifyItemShown("tomatoes", false);
    }

    @Test
    public void testSelectGroceryItemsCompleted() {
        // GIVEN
        ItemsPageObject todoPage = new ItemsPageObject(driver).get();
        todoPage
            .addGroceryItem("avocados")
            .addGroceryItem("tomatoes")
            .getGroceryList()
            .clickOnItem("avocados");

        // WHEN
        todoPage
            .selectCompleted()

            // THEN
            .getGroceryList()
            .verifyItemShown("avocados", true)
            .verifyItemNotShown("tomatoes");
    }

    @Test
    public void testSelectGroceryItemsAll() {
        // GIVEN
        ItemsPageObject todoPage = new ItemsPageObject(driver).get();
        todoPage
            .addGroceryItem("avocados")
            .addGroceryItem("tomatoes")
            .getGroceryList()
            .clickOnItem("avocados");
        todoPage
            .selectCompleted()

            // WHEN
            .selectAll()

            // THEN
            .getGroceryList()
            .verifyItemShown("avocados", true)
            .verifyItemShown("tomatoes", false);
    }

    @Test
    public void testSelectCombinedItemsActive() {
        // GIVEN
        ItemsPageObject todoPage = new ItemsPageObject(driver).get();

        todoPage
            .addTodo("Buy groceries")
            .addTodo("Tidy up")
            .addGroceryItem("avocados")
            .addGroceryItem("tomatoes");

        todoPage
            .getGroceryList()
            .clickOnItem("avocados");

        todoPage
            .getTodoList()
            .clickOnItem("Tidy up");

        // WHEN
        todoPage
            .selectActive();

        // THEN
        todoPage
            .getTodoList()
            .verifyItemShown("Buy groceries", false)
            .verifyItemNotShown("Tidy up");

        todoPage
            .getGroceryList()
            .verifyItemNotShown("avocados")
            .verifyItemShown("tomatoes", false);
    }
}

Page Object pattern example

This blog post will provide example of end-to-end Selenium tests in Java with usage of Page Object pattern.

Every responsible developer knows that end-to-end test is necessary at some point. When you want to test web application end-to-end, there is only one decent open-source option: Selenium. Of course end-to-end test suite have to be maintainable. To achieve that, I believe we have to use Page Object pattern for each web screen present in web application. Martin Fowler’s link explains very well benefits of this pattern, therefore I skip theory.

We at Dotsub are using Java platform with Spring Boot for back-end. Therefore Java Selenium APIs were natural fit for our needs. This blog post will explain Page Object pattern on application built with Spring Boot. For UI, it will use famous Dan Abramov’s Todos  example. This example was used for Getting started with Redux tutorial. It is simple single page application built on top of React + Redux JavaSript frameworks. It provides us decent functionality we can cover by Selenium test examples.

Functionality for testing

Let’s take a look at functionality of this simple example.

Page Object: todos-all

In text Input field, we can enter name of Todo item. Button Add Todo saves item into Todos list. When we click on any Todo item we can mark it as complete or incomplete.

Page Object: todos-active

With Show buttons below the Todos list, we can filter items to show only Active

Page Object: todos-complete

…or Completed Todo items.

In fact this Todos example application doesn’t communicate with Spring Boot server. Front-end assets are just served from Spring Boot application. But we don’t need such communication to demonstrate Page Object example. I used Spring Boot, because it’s trivial to integrate Selenium tests with it. Note that goal of this blog post isn’t to explain Selenium or its APIs. It is expected for reader to be slightly familiar with them already.

Page Object class

As we mentioned, our end-to-end test will use Page object pattern. Goal of this pattern is to separate HTML details of testing page from actions that can be performed against the page. Listing that shows Page Object class for Todos application will be split into few parts, so that we can explain each part separately:

package net.lkrnac.blog.pageobject.e2e;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.util.List;

import static java.lang.String.format;
import static org.junit.Assert.*;


public class TodoPageObject {
    private WebDriver driver;
    private WebDriverWait wait;

    public TodoPageObject(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, 10);
    }

    public TodoPageObject get() {
        driver.get("localhost:8080");
        wait.until(ExpectedConditions.elementToBeClickable(By.tagName("button")));
        return this;
    }

Page Object class has two fields. First is Selenium WebDriver instance, which represents browser’s window wrapper used for controlling web page similar to real user. WebDriverWait is helper for waiting until HTML elements are rendered. Method get is used to load Todos page based on it’s URL.

    private WebElement findElementWithText(String text) {
        return driver.findElement(getConditionForText(text));
    }

    private List<WebElement> findElementsWithText(String text) {
        return driver.findElements(getConditionForText(text));
    }

    private By getConditionForText(String text) {
        return By.xpath(format("//*[text()='%s']", text));
    }

These three methods are private helpers for Todos actions shown later. We will used them to find HTML element or list of HTML elements based on the text they contain. For text search we use Selenium’s XPath APIs.

    public TodoPageObject addTodo(String todo) {
        WebElement input = driver.findElement(By.tagName("input"));
        input.sendKeys(todo);
        WebElement button = driver.findElement(By.tagName("button"));
        button.click();
        return this;
    }

    public TodoPageObject clickOnTodoItem(String todoItem) {
        findElementWithText(todoItem).click();
        return this;
    }

I believe names addTodo and clickOnTodoItem are self explanatory. We will use them to enter test into Todos input list, click Add Todo button and to mark Todo items complete/incomplete. Notice that return value is Todos Page Object instance, so that we can use fluent API during testing. This mechanism will keep our tests less verbose.

    public TodoPageObject selectAll() {
        findElementWithText("All").click();
        return this;
    }

    public TodoPageObject selectActive() {
        findElementWithText("Active").click();
        return this;
    }

    public TodoPageObject selectCompleted() {
        findElementWithText("Completed").click();
        return this;
    }

These three methods are using for clicking on filter buttons All/Active/Complete.

    public TodoPageObject verifyTodoShown(String todoItem, boolean expectedStrikethrough) {
        WebElement todoElement = findElementWithText(todoItem);
        assertNotNull(todoElement);
        boolean actualStrikethrough = todoElement.getAttribute("style").contains("text-decoration: line-through;");
        assertEquals(expectedStrikethrough, actualStrikethrough);
        return this;
    }

    public TodoPageObject verifyTodoNotShown(String todoItem) {
        assertTrue(findElementsWithText(todoItem).isEmpty());
        return this;
    }
}

Last two methods are used to verify if given Todo item is shown on not shown. When it’s shown we can verify if it’s completed via parameter expectedStrikethrough.

Tests

With such page object support we can start testing:

package net.lkrnac.blog.pageobject.e2e;

import io.github.bonigarcia.wdm.ChromeDriverManager;
import net.lkrnac.blog.pageobject.TodoApplication;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TodoApplication.class)
@WebIntegrationTest
public class TodoAppTest {
    private static WebDriver driver;

    @BeforeClass
    public static void setUp() {
        ChromeDriverManager.getInstance().setup();
        driver = new ChromeDriver();
    }

    @AfterClass
    public static void tearDown() {
        driver.quit();
    }

    @Test
    public void testCreateTodos() {
        // GIVEN
        new TodoPageObject(driver).get()

            // WHEN
            .addTodo("testTodo1")
            .addTodo("testTodo2")

            // THEN
            .verifyTodoShown("testTodo1", false)
            .verifyTodoShown("testTodo2", false);
    }

    @Test
    public void testCompleteTodo() {
        // GIVEN
        new TodoPageObject(driver).get()
            .addTodo("testTodo1")
            .addTodo("testTodo2")

            // WHEN
            .clickOnTodoItem("testTodo1")

            // THEN
            .verifyTodoShown("testTodo1", true)
            .verifyTodoShown("testTodo2", false);
    }

    @Test
    public void testSelectActive() {
        // GIVEN
        new TodoPageObject(driver).get()
            .addTodo("testTodo1")
            .addTodo("testTodo2")
            .clickOnTodoItem("testTodo1")

            // WHEN
            .selectActive()

            // THEN
            .verifyTodoNotShown("testTodo1")
            .verifyTodoShown("testTodo2", false);
    }

    @Test
    public void testSelectCompleted() {
        // GIVEN
        new TodoPageObject(driver).get()
            .addTodo("testTodo1")
            .addTodo("testTodo2")
            .clickOnTodoItem("testTodo1")

            // WHEN
            .selectCompleted()

            // THEN
            .verifyTodoShown("testTodo1", true)
            .verifyTodoNotShown("testTodo2");
    }

    @Test
    public void testSelectAll() {
        // GIVEN
        new TodoPageObject(driver).get()
            .addTodo("testTodo1")
            .addTodo("testTodo2")
            .clickOnTodoItem("testTodo1")
            .selectCompleted()

            // WHEN
            .selectAll()

            // THEN
            .verifyTodoShown("testTodo1", true)
            .verifyTodoShown("testTodo2", false);
    }
}

These test cases cover some basic scenarios real user could perform against our Todos application. I am not going to explain them, because they are nicely readable due to usage of Page Object pattern. Separate huge benefit of this pattern I want to point out is decoupling tests from HTML. If we would change HTML structure on Todos page, we would need to change only Page Object class. Test cases would remain untouched.

Example code with all project files is hosted on Github.

Selenium tests on Gradle in Travis

Run Selenium tests on TravisCI

Stack of application I am currently working on at Dotsub is based on Java/Spring Boot back-end and React/Redux front-end. To have confidence that this application works end to end, we are using Selenium tests. It is very easy to run them as part application build, because Spring Boot testing support allows to run full application as part of application build. We use Gradle as main build system and it is all running on Travis continuous integration server. To demonstrate this approach for end to end testing I created small Hello World project on GitHub.

Build

Build system of choice is Gradle. Creation of following Gradle script was very easy, because I used Spring Initializr:

buildscript {
	ext {
		springBootVersion = '1.3.2.RELEASE'
	}
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
	}
}

apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'spring-boot'

jar {
	baseName = 'blog-2016-01-selenium-on-travis'
	version = '0.0.1-SNAPSHOT'
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
	mavenCentral()
}


dependencies {
	compile('org.springframework.boot:spring-boot-starter')
	compile('org.springframework.boot:spring-boot-starter-web')
	testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile("org.seleniumhq.selenium:selenium-firefox-driver:2.49.0")
    testCompile('org.seleniumhq.selenium:selenium-support:2.49.0')
}

task wrapper(type: Wrapper) {
	gradleVersion = '2.9'
}

The only additional dependencies against generated script (by Spring Initializr) are Selenium, Firefox Selenium driver and Spring Starter Web. Adding spring-boot-starter-web into build will transform our project into web application with embedded Tomcat servlet container.

Hello World Application

To demonstrate Selenium Tests automation, I created very simple application code. First of all we need Spring Boot main class:

package net.lkrnac.blog.seleniumontravis;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String... args){
        SpringApplication.run(Application.class);
    }
}

It is very standard Spring Boot construct for Spring context initialization.  Annotation @SpringBootApplication turns on Spring Boot auto-configuration. It sets up most sensible defaults for out application, which is most importantly embedded servlet container in this case.

Second part of our simple application code is front-end code. For demonstration purposes this simplest React example (taken from React getting started guide) will be enough:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
</head>

<body>
<div id="hello" />

<script type="text/babel">
    ReactDOM.render(
    <h1>Hello, world!</h1>, document.getElementById('hello') );
</script>
</body>

</html>

It uses Babel to transpile JSX inline and pulls React libraries from CDN. It doesn’t do any AJAX calls to server. We also didn’t create any Spring controller for serving requests. It is because goal of this example is to demonstrate Selenium testing against React+Spring Boot app, therefore I skipped communication between client and server.

This simple HTML + React Hello World page is located in file src/main/resources/static/index.html, where it will be picked up by Spring Boot and exposed as default web page content when request hits root URL of embedded servlet container.

Simple Selenium Test

Following listing shows how can we approach selenium testing against Spring Boot application:

import net.lkrnac.blog.seleniumontravis.Application;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.By;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.io.IOException;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
@WebIntegrationTest
public class ApplicationTest {
    private static FirefoxDriver driver;

    @BeforeClass
    public static void setUp() throws IOException {
        driver = new FirefoxDriver();
    }

    @Test
    public void contextLoads() {
        driver.get("https://localhost:8080");
        WebDriverWait wait = new WebDriverWait(driver, 10);
        wait.until(ExpectedConditions.textToBePresentInElementLocated(
                By.id("hello"), "Hello, world!"));
    }

    @AfterClass
    public static void tearDown() {
        driver.quit();
    }
}

Before test, it starts Firefox Selenium driver. During test it visits default specified address https://localhost:8080. This should hit our index.html. Next phase of the test is waiting for Hello, world! header to be rendered on screen. After this happens, test is done and Selenium driver is closed. When we run this test locally, we can see following pop-up appear on the screen.

Selenoum Tests

Travis Configuration

Last piece of this example it TravisCI configuration manifest. Relevant parts of it are here:

language: java
jdk:
  - oraclejdk8

before_script:
  - "export DISPLAY=:99.0"
  - "sh -e /etc/init.d/xvfb start"
  - sleep 3 # give xvfb some time to start

script: ./gradlew build --continue

First of all we specify that Java 8 is our language of choice. Before script part is taken from TravisCI docs. It starts Xvfb (X virtual frame buffer), which simulates X11 display server on Linux machine without screen. This allows render our site virtually, because TravisCI build machine contains installation of Firefox by default. This configuration is enough for Selenium tests. In script phase we start full Gradle build.

Possible Travis problems and solution

All this configuration may be enough for you to start simple Selenium testing again React application. But default Firefox version on TravisCI machine is 31.0 ESR. This is quite old version and some of our React pages may not be rendered correctly during the build. Luckily TravisCI allows to update Firefox version with this simple manifest declaration:

addons:
  firefox: "44.0"

This installs new version of Firefox on TravisCI, but unfortunately it is not enough because of this open TravisCI issue. Consequence is that default configuration of Selenium Firefox driver configuration use old Firefox binary instead of new one.

But when I executed which firefox command on Travis, it was pointing to new binary file. Therefore I used this Selenium Driver initialization to pick up newer Firefox binary on Travis:

import net.lkrnac.blog.seleniumontravis.Application;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.By;
import org.openqa.selenium.firefox.FirefoxBinary;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
@WebIntegrationTest
public class UseNewFirefoxOnTravisTest {
    private static FirefoxDriver driver;

    @BeforeClass
    public static void setUp() throws IOException {
        String travisCiFlag = System.getenv().get("TRAVIS");
        FirefoxBinary firefoxBinary = "true".equals(travisCiFlag)
                ? getFirefoxBinaryForTravisCi()
                : new FirefoxBinary();

        driver = new FirefoxDriver(firefoxBinary, new FirefoxProfile());
    }

    private static FirefoxBinary getFirefoxBinaryForTravisCi() throws IOException {
        String firefoxPath = getFirefoxPath();
        Logger staticLog = LoggerFactory.getLogger(UseNewFirefoxOnTravisTest.class);
        staticLog.info("Firefox path: " + firefoxPath);

        return new FirefoxBinary(new File(firefoxPath));
    }

    private static String getFirefoxPath() throws IOException {
        ProcessBuilder pb = new ProcessBuilder("which", "firefox");
        pb.redirectErrorStream(true);
        Process process = pb.start();
        try (InputStreamReader isr = new InputStreamReader(process.getInputStream(), "UTF-8");
             BufferedReader br = new BufferedReader(isr)) {
            return br.readLine();
        }
    }

    @Test
    public void contextLoads() {
        driver.get("https://localhost:8080");
        WebDriverWait wait = new WebDriverWait(driver, 10);
        wait.until(ExpectedConditions.textToBePresentInElementLocated(
                By.id("hello"),
                "Hello, world!")
        );
    }

    @AfterClass
    public static void tearDown() {
        driver.quit();
    }
}

Testing logic is the same as for example test we already introduced. Different is initialization of Firefox Selenium driver. In this case we first recognize if we are running in Travis environment via environment variable TRAVIS. If we are not running in Travis, we use default Firefox driver initialization.

If we are running in TravisCI build, we use standard Java class ProcessBuilder to execute Linux command which firefox in separate process and grab it’s output. This gives us path of newer Firefox binary. Based on this path, we initialize Firefox Selenium driver and are good to automatically run Selenium test against latest Firefox on TravisCI build machine.

Source code for this example is located in GitHub.