Selenium

Selenium Web Application Testing Example

1. Introduction

In this post, we feature a comprehensive example on Selenium web application testing. A web application is a client-server computer program which the client runs in a web browser. Spring Web model-view-controller (MVC) framework provides an easier way to develop a web application. Spring boot provides auto configuration to simplify the configuration steps. Selenium is a set of software tools which includes WebDriver (Remote Control), Grid, IDE, and Client API to support test automation. Selenium WebDriver accepts commands and sends them to a browser. According to the Selenium documentation, it supports nine popular browsers: Google Chrome, Internet Explorer, Firefox, Safari, Opera, HtmlUnit, phantomjs, Android, and iOS.

 
In this example, I will build a web application with the Spring boot MVC framework. This web application will include:

  1. A landing page to display the server’s current time and a login button.
  2. A user detail page to show the user’s detail information when the username is matched with an existing record.
  3. A create a new user page to create a new user when the username is not matched with any existing record.
  4. An error page to output any unexpected exception.

I will also build an automation testing project with Selenium to test this web application with Google Chrome. The test cases include:

  • Test the web application’s landing page
  • Test that the web application’s login button is clickable
  • Test that the login page’s username is editable
  • Test that a new user can be created
  • Test that an existing user can log in to see the detail
  • Test that an error page is triggered when an unexpected error happens

2. Technologies Used

The example code in this article was built and run using:

  • Java 1.8.101
  • Maven 3.3.9
  • Eclipse Oxygen
  • Spring Boot/Web/Data/JPA 1.5.14.RELEASE
  • Selenium 2.41.0
  • H2 1.4.197

3. Projects Layout

In this example, I will create two Maven projects: spring-boot-web-jsp-demo to develop a web application and automation-demo to test the web application.

Selenium Web Application Testing - Web Application
Figure 1 Web Application

 

Selenium Web Application Testing - Test Automation
Figure 2 Test Automation

4. Web Application Project

In this step, I will create a Maven project – spring-boot-web-jsp-demo as a Spring boot web application.

4.1 Dependencies

I will include the libraries in pom.xml.

pom.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>

	<artifactId>spring-boot-web-jsp-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<name>spring-boot-web-jsp-demo</name>
	<description>Demo project for Spring Boot Web App</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.14.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	
		<!-- JSTL -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
		</dependency>

		<!-- Need this to compile JSP for Embedded Tomcat -->
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
		</dependency>

		<!-- Spring MVC -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
		</dependency>

	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

4.2 Properties

Spring Boot properties file is updated to include the JSP ViewResolver prefix and suffix setting.

application.properties

# Spring MVC view prefix.
spring.mvc.view.prefix=/WEB-INF/jsp/

# Spring MVC view suffix.
spring.mvc.view.suffix=.jsp

# Locations of static resources.
spring.resources.staticLocations=/resources/static/

#disable the white label when using an embedded servlet container.
server.error.whitelabel.enabled=false

spring.mvc.throw-exception-if-no-handler-found=true

4.3 Views

In the MVC framework, a view is responsible for rendering the model data to generate the HTML output, so a browser can interpret it. This web application has four views. I will build them with Java Server Page (JSP) and use Spring boot properties to define its prefix and suffix.

Selenium web driver API can find the web elements easily based on their IDs, so I will set the web elements’ ID value.

4.3.1 Home

The web application’s landing page is home.jsp. It displays the server’s time and enables a user to login in with a username.

home.jsp

<!DOCTYPE html>

<html>
<head>
<title>home</title>
</head>
<body>
	<h1>Spring Boot Web JSP Example</h1>
	<h3>The server time is ${serverTime}.</h3>
	<form action="user" method="post">
		User Name : <input type="text" name="userName" id="username">
		<input type="submit" value="Login" id="login">
	</form>
</body>
</html>

Note: The form action is mapped to UserController‘s user method via @RequestMapping(value="/user").

4.3.2 New User

The new user page is newuser.jsp. A user enters the first name, last name, and username, and clicks the Save button to save a new user.

newuser.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ page session="false"%>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>New User</title>
</head>
<body>
	<h2>User ${userName} is not found in the system. Create it!</h2>

	<form:form action="user/save" method="post" modelAttribute="account">
		<p>
			<input type="submit" value="Save" id="save">
		</p>
		<table>
			<tr>
				<td>User Name :</td>
				<td><input type="text" name="username" id="username"
					value="${userName}"></td>
			</tr>
			<tr>
				<td>First Name :</td>
				<td><input type="text" name="firstname" id="firstname"></td>
			</tr>
			<tr>
				<td>Last Name :</td>
				<td><input type="text" name="lasttname" id="lasttname"></td>
			</tr>
		</table>
	</form:form>
</body>
</html>

4.3.3 User Detail

The user.jsp view displays the user’s detail information.

user.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<spring:url value="/css/main.css" var="demoCSS" />

<link href="${demoCSS}" rel="stylesheet">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>User Account Detail</title>
</head>
<body>
	<h3>Hi ${account.firstname} ${account.lastname}!</h3>

	<p>Account Id: ${account.id} Reward Point:${account.rewardPoint}</p>

	<h3>Transaction Detail</h3>

	<table>
		<tr>
			<th>Date</th>
			<th>Payee</th>
			<th>Description</th>
			<th>Amount</th>
		</tr>
		<c:forEach var="listValue" items="${account.transactions}">
			<tr>
				<td>${listValue.createdDate}</td>
				<td>${listValue.payeeName}</td>
				<td>${listValue.description}</td>
				<td>${listValue.amount}</td>
			</tr>
		</c:forEach>
	</table>

</body>
</html>

4.3.4 Error

The error.jsp view displays any unexpected exception.

error.jsp

<!DOCTYPE html>

<html>
<head>
<title>error</title>
</head>
<body>
	<h1>Caught Unexpected Exception</h1>
	<h2>from: ${content}.</h2>
	<p>due to: ${error}.</p>
</body>
</html>

4.4 Controllers

In the MVC framework, a controller processes the user’s request and builds the appropriate model and passes it to the view for rendering.

4.4.1 HomeController

HomeController maps the root context to the welcome method and returns the home view. It also maps any exceptions to the error view.

HomeController.java

package jcg.zheng.demo.spring.controller;

import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {
	private static final String ERROR_VIEW = "error";

	private static final String HOME_VIEW = "home";

	@RequestMapping("/")
	public String welcome(Locale locale, Map model) {
		Date date = new Date();
		DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

		String formattedDate = dateFormat.format(date);

		model.put("serverTime", formattedDate);
		return HOME_VIEW;
	}

	@ExceptionHandler(Exception.class)
	public String exceptionHandler(HttpServletRequest request, Exception ex, Model model) {
		model.addAttribute("content", request.getRequestURL());
		model.addAttribute(ERROR_VIEW, ex.getMessage());
		return ERROR_VIEW;
	}

}

4.4.2 UserController

UserController maps "/user" to the user method. It also maps "/user/save" to the saveUser method.

UserController.java

package jcg.zheng.demo.spring.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import jcg.zheng.demo.spring.entity.Account;
import jcg.zheng.demo.spring.model.User;
import jcg.zheng.demo.spring.service.AccountService;

@Controller
public class UserController {

	private static final String NEWUSER_VIEW = "newuser";
	private static final String USER_VIEW = "user";

	@Autowired
	private AccountService accountService;

	@RequestMapping(value = "/user/save", method = RequestMethod.POST)
	public String saveuser(@Validated @ModelAttribute("account") Account acct, Model model) {
		accountService.save(acct);
		model.addAttribute("account", acct);
		return USER_VIEW;

	}

	@RequestMapping(value = "/user", method = RequestMethod.POST)
	public String user(@Validated User user, Model model) {
		model.addAttribute("userName", user.getUserName());

		Account foundUser = accountService.findByUsername(user.getUserName());
		if (foundUser != null) {
			model.addAttribute("account", foundUser);
			return USER_VIEW;
		} else {
			return NEWUSER_VIEW;
		}
	}

}

4.5 Model

In the MVC framework, a model is the application’s data.

4.5.1 User

The User class contains the username and the account object.

User.java

package jcg.zheng.demo.spring.model;

import jcg.zheng.demo.spring.entity.Account;

public class User {

	private String userName;

	private Account account;

	public Account getAccount() {
		return account;
	}

	public void setAccount(Account account) {
		this.account = account;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

}

4.5.2 Account

The Account class contains a unique ID, first name, last name, username, and list of transactions.

Account.java

package jcg.zheng.demo.spring.entity;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "ACCOUNT")
public class Account {

	@Id
	@GeneratedValue
	@Column(name = "ID")
	private Integer id;
	
	@Column(name = "First_Name")
	private String firstname;
	
	@Column(name = "Last_Name")
	private String lastname;
	
	private String username;

	@OneToMany(mappedBy="account", cascade = CascadeType.ALL, fetch = FetchType.EAGER, targetEntity = RewardTransaction.class)
	private List transactions = new ArrayList();

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getFirstname() {
		return firstname;
	}

	public void setFirstname(String firstname) {
		this.firstname = firstname;
	}

	public String getLastname() {
		return lastname;
	}

	public void setLastname(String lastname) {
		this.lastname = lastname;
	}

	public BigDecimal getRewardPoint() {
		return transactions.stream().map(RewardTransaction::getAmount)
        .reduce(BigDecimal.ZERO, BigDecimal::add);  
		 
	}

	public List getTransactions() {
		return transactions;
	}

	public void addTransactions(RewardTransaction transaction) {
		transaction.setAccount(this);
		this.transactions.add(transaction);
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

}

4.5.3 RewardTransaction

The RewardTransaction class contains a unique ID, created date, transaction amount, description, and payee name.

RewardTransaction.java

package jcg.zheng.demo.spring.entity;

import java.math.BigDecimal;
import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

@Entity
public class RewardTransaction {

	@Id
	@GeneratedValue
	private Long id;
	private Date createdDate;
	private BigDecimal amount;
	private String description;
	private String payeeName;

	@ManyToOne
	@JoinColumn(name = "ACCOUNT_ID")
	private Account account;

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public String getPayeeName() {
		return payeeName;
	}

	public void setPayeeName(String payeeName) {
		this.payeeName = payeeName;
	}

	public Date getCreatedDate() {
		return createdDate;
	}

	public void setCreatedDate(Date createdDate) {
		this.createdDate = createdDate;
	}

	public BigDecimal getAmount() {
		return amount;
	}

	public void setAmount(BigDecimal amount) {
		this.amount = amount;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public Account getAccount() {
		return account;
	}

	public void setAccount(Account account) {
		this.account = account;
	}

}

4.6 Services

AccountService contains two methods: findByUserName and save.

AccountServiceImpl.java

package jcg.zheng.demo.spring.service.impl;

import java.math.BigDecimal;
import java.util.Date;
import java.util.Random;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import jcg.zheng.demo.spring.entity.Account;
import jcg.zheng.demo.spring.entity.RewardTransaction;
import jcg.zheng.demo.spring.repository.AccountRepository;
import jcg.zheng.demo.spring.service.AccountService;

@Service
public class AccountServiceImpl implements AccountService {

	@Autowired
	private AccountRepository acctDao;

	@Override
	public Account findByUsername(String username) {
		return acctDao.findByUserName(username);
	}

	@Override
	public Account save(Account accout) {
		Random rand = new Random();
		accout.addTransactions(createTransaction("Shop&Save", "Food items", new BigDecimal(rand.nextInt(100))));
		accout.addTransactions(createTransaction("Webster", "School supplies", new BigDecimal(rand.nextInt(260))));
		accout.addTransactions(createTransaction("KOHL", "Birthday gifts", new BigDecimal(rand.nextInt(300))));
		accout.addTransactions(createTransaction("Macy", "Allen clothes", new BigDecimal(rand.nextInt(100))));
		accout.addTransactions(createTransaction("Home Depot", "Household items", new BigDecimal(rand.nextInt(1000))));
		accout.addTransactions(createTransaction("Wal-mart", "Small items", new BigDecimal(rand.nextInt(60))));
	
		return acctDao.save(accout);
	}
	
	private RewardTransaction createTransaction(String payee, String desp, BigDecimal amount) {
		RewardTransaction trans = new RewardTransaction();
		trans.setCreatedDate(new Date());
		trans.setDescription(desp);
		trans.setAmount(amount);
		trans.setPayeeName(payee);
		return trans;
	}
}

4.7 Repositories

AccountRepository extends from JpaRepository.

AccountRepository.java

package jcg.zheng.demo.spring.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import jcg.zheng.demo.spring.entity.Account;

@Repository
public interface AccountRepository extends JpaRepository<Account, Integer> {
	@Query("SELECT acct from Account acct WHERE acct.username = :username")
	Account findByUserName(@Param("username") String userName);
}

4.8 Spring Boot Web Application

Spring boot web application starts with three steps. First, it starts the public static void main method. Second, it starts the Spring context which includes an auto-configuration initializer, configurations, and annotations. Last, it starts the auto-configured embedded web server (default is Tomcat).

SpringBootWebApplication extends from SpringBootservletInitializer.

SpringBootWebApplication.java

package jcg.zheng.demo.spring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
@EnableJpaRepositories(basePackages = "jcg.zheng.demo.spring")
public class SpringBootWebApplication extends SpringBootServletInitializer {

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
		return application.sources(SpringBootWebApplication.class);
	}

	public static void main(String[] args) throws Exception {
		SpringApplication.run(SpringBootWebApplication.class, args);
	}

}

5. Web Application Unit Test

5.1 TestConfig

Create a Spring configuration for testing.

TestConfig.java

package jcg.zheng.demo.spring;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = {"jcg.zheng.demo.spring"})
public class TestConfig {

}

5.2 HomeControllerTest

Test the HomeController for the home view.

HomeControllerTest.java

package jcg.zheng.demo.spring;

import static org.junit.Assert.assertEquals;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import jcg.zheng.demo.spring.controller.HomeController;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class HomeControllerTest {
	
	@Autowired
	private HomeController welController;

	@Test
	public void welcome_view() {		
		Map model= new HashMap();
		Locale locale = new Locale.Builder().setLanguage("en").setRegion("MO").build();
		String viewName = welController.welcome(locale, model);
		assertEquals("home", viewName);
	}
	
}

5.3 UserControllerTest

Test the UserController for the new user view.

UserControllerTest.java

package jcg.zheng.demo.spring;

import static org.junit.Assert.assertEquals;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.ui.Model;
import org.springframework.validation.support.BindingAwareModelMap;

import jcg.zheng.demo.spring.controller.UserController;
import jcg.zheng.demo.spring.model.User;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class UserControllerTest {

	@Autowired
	private UserController controller;

	@Test
	public void newUser_view() {

		User user = new User();
		user.setUserName("not exist");
		Model model = new BindingAwareModelMap();
		String viewName = controller.user(user, model);
		assertEquals("newuser", viewName);
	}

}

6. Selenium Test

In this step, I will create a project – automation-demo which tests the web application with Selenium in Google Chrome. I will demonstrate:

  • How to set up Selenium WebDriver
  • How to find the web element
  • How to interact with the web element
  • How to navigate the web page
  • How to wait for the web element to become available

pom.xml

<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>mary.example.selenium</groupId>
	<artifactId>automation-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<dependencies>
		<dependency>
			<groupId>org.seleniumhq.selenium</groupId>
			<artifactId>selenium-java</artifactId>
			<version>2.41.0</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>

6.1 HomePage

Create a HomePage for the login function. I will use Selenium API to find the username input field, set the value, then click the login button.

HomePage.java

package mary.demo.webtest;

import java.io.IOException;

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;

public class HomePage {
	private static final String LOGIN_ID = "login";
	private static final String USERNAME_ID = "username";

	private static final int TIMEOUT_SECONDS = 20;

	public String login(WebDriver driver, String un) throws IOException {

		WebDriverWait wait = new WebDriverWait(driver, TIMEOUT_SECONDS);
		WebElement userNameElement = wait.until(ExpectedConditions.elementToBeClickable(By.id(USERNAME_ID)));
		userNameElement.sendKeys(un);

		WebElement loginButtonElement = wait.until(ExpectedConditions.elementToBeClickable(By.id(LOGIN_ID)));
		loginButtonElement.click();

		String nextPageTitle = driver.getTitle();

		return nextPageTitle;

	}
}

6.2 NewUserPage

Create a NewUserPage for creating a new user. I will use Selenium API to find the first name by the Xpath value.

NewUserPage.java

package mary.demo.webtest;

import java.io.IOException;

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;

public class NewUserPage {

	private static final String FIRSTNAME_XPATH = "//input[@id='firstname']";

	private static final String SAVE_ID = "save";

	private static final String LASTTNAME_ID = "lasttname";

	private static final int TIMEOUT_SECONDS = 20;


	public String save(WebDriver driver, String firstName, String lastName) throws IOException {

		WebDriverWait wait = new WebDriverWait(driver, TIMEOUT_SECONDS);
		WebElement firstNameElement = wait.until(ExpectedConditions.elementToBeClickable(By.xpath(FIRSTNAME_XPATH)));
		firstNameElement.sendKeys(firstName);

		WebElement lastNameElement = wait.until(ExpectedConditions.elementToBeClickable(By.id(LASTTNAME_ID)));
		lastNameElement.sendKeys(lastName);

		WebElement saveButtonElement = wait.until(ExpectedConditions.elementToBeClickable(By.id(SAVE_ID)));
		saveButtonElement.click();

		String nextPageTitle = driver.getTitle();
	
		return nextPageTitle;

	}
}

6.3 ErrorPage

Create a ErrorPage for user entering a wrong URL.

ErrorPage.java

package mary.demo.webtest;

import java.io.IOException;

import org.openqa.selenium.WebDriver;

public class ErrorPage {

	public String badUrl(WebDriver driver, String badUrl) throws IOException {

		driver.get(badUrl);
		String nextPageTitle = driver.getTitle();

		return nextPageTitle;

	}
}

7. Web Browser Test

7.1 WebTestBase

Create a WebTestBase to configure a Google Chrome Web driver. We can easily support other browsers by switching to a different Selenium’s WebDriver. Click here to download the Selenium WebDriver.

WebTestBase.java

package mary.demo.webtest;

import java.io.IOException;

import org.junit.After;
import org.junit.Before;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class WebTestBase {
	protected WebDriver driver;
	protected ScreenshotHelper screenshotHelper;

	public WebTestBase() {
		super();
	}

	@Before
	public void setup() {
		System.setProperty("webdriver.chrome.driver", "C:\\MaryZheng\\tools\\webdriver\\chromedriver.exe");
		driver = new ChromeDriver();
		screenshotHelper = new ScreenshotHelper(driver);
	}

	@After
	public void close() throws IOException {
		driver.close();
	}

}

7.2 HomePageTest

Create a HomePageTest to test the login in with invalid user in Google Chrome.

HomePageTest.java

package mary.demo.webtest;

import static org.junit.Assert.assertEquals;

import java.io.IOException;

import org.junit.Test;

public class HomePageTest extends WebTestBase{

	private HomePage homepage = new HomePage();;
	
	@Test
	public void login_invalid_user() throws IOException  {
		driver.get("http://localhost:8080/");
		screenshotHelper.saveScreenshot("home_screenshot.png");
		String nextPage = homepage.login(driver, "invalid");
		screenshotHelper.saveScreenshot("invalid_screenshot.png");
		assertEquals("New User", nextPage);
	}

}

7.3 ErrorPageTest

Create a ErrorPageTest to test the wrong url in Google Chrome.

ErrorPageTest.java

package mary.demo.webtest;

import static org.junit.Assert.assertEquals;

import java.io.IOException;

import org.junit.Test;

public class ErrorPageTest extends WebTestBase {

	private ErrorPage errorPage = new ErrorPage();;

	@Test
	public void login_invalid_user() throws IOException {
		String nextPage = errorPage.badUrl(driver, "http://localhost:8080/notexistUrl");
		screenshotHelper.saveScreenshot("error_screenshot.png");
		assertEquals("error", nextPage);
	}

}

7.4 NewUserPageTest

Create a NewUserPageTest to create a new user in Google Chrome.

NewUserPageTest.java

package mary.demo.webtest;

import static org.junit.Assert.assertEquals;

import java.io.IOException;

import org.junit.Test;

public class NewUserPageTest extends WebTestBase {

	private HomePage homepage = new HomePage();
	private NewUserPage newUserPage = new NewUserPage();

	@Test
	public void create_new_user() throws IOException {
		driver.get("http://localhost:8080/");
		String nextPage = homepage.login(driver, "MaryZheng");
		if ("New User".equalsIgnoreCase(nextPage)) {
			nextPage = newUserPage.save(driver, "Alex", "Zheng");
			screenshotHelper.saveScreenshot("newuser_screenshot.png");
			assertEquals("User Account Detail", nextPage);
		}
		else {
			assertEquals("User Account Detail", nextPage);
		}
	}

}

7.5 ScreenshotHelper

Create a ScreenshotHelper to save a web page as a screenshot image.

ScreenshotHelper.java

package mary.demo.webtest;

import java.io.File;
import java.io.IOException;

import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;

public class ScreenshotHelper {

	private WebDriver driver;

	public ScreenshotHelper(WebDriver driver) {
		super();
		this.driver = driver;
	}

	public void saveScreenshot(String screenshotFileName) throws IOException {
		File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
		FileUtils.copyFile(screenshot, new File(screenshotFileName));
	}

}

8. Demo

Start the web application as a Java application and confirm it with the server log.

Web Application Server Log

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v1.5.14.RELEASE)

2018-10-14 20:34:36.281  INFO 1384 --- [           main] j.z.d.spring.SpringBootWebApplication    : Starting SpringBootWebApplication on SL2LS431841 with PID 1384 (C:\gitworkspace\spring-boot-web-jsp-demo\target\classes started by Shu.Shan in C:\gitworkspace\spring-boot-web-jsp-demo)
2018-10-14 20:34:36.284  INFO 1384 --- [           main] j.z.d.spring.SpringBootWebApplication    : No active profile set, falling back to default profiles: default
2018-10-14 20:34:36.387  INFO 1384 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1c72da34: startup date [Sun Oct 14 20:34:36 CDT 2018]; root of context hierarchy
2018-10-14 20:34:37.950  INFO 1384 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$EnhancerBySpringCGLIB$2ec905e1] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2018-10-14 20:34:38.863  INFO 1384 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2018-10-14 20:34:38.899  INFO 1384 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2018-10-14 20:34:38.900  INFO 1384 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.31
2018-10-14 20:34:39.303  INFO 1384 --- [ost-startStop-1] org.apache.jasper.servlet.TldScanner     : At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
2018-10-14 20:34:39.308  INFO 1384 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2018-10-14 20:34:39.309  INFO 1384 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2927 ms
2018-10-14 20:34:39.517  INFO 1384 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2018-10-14 20:34:39.522  INFO 1384 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-10-14 20:34:39.522  INFO 1384 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-10-14 20:34:39.522  INFO 1384 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-10-14 20:34:39.522  INFO 1384 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2018-10-14 20:34:40.091  INFO 1384 --- [           main] j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
2018-10-14 20:34:40.116  INFO 1384 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [
	name: default
	...]
2018-10-14 20:34:40.223  INFO 1384 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {5.0.12.Final}
2018-10-14 20:34:40.225  INFO 1384 --- [           main] org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
2018-10-14 20:34:40.227  INFO 1384 --- [           main] org.hibernate.cfg.Environment            : HHH000021: Bytecode provider name : javassist
2018-10-14 20:34:40.285  INFO 1384 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
2018-10-14 20:34:40.546  INFO 1384 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2018-10-14 20:34:41.120  INFO 1384 --- [           main] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000227: Running hbm2ddl schema export
2018-10-14 20:34:41.153  INFO 1384 --- [           main] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000230: Schema export complete
2018-10-14 20:34:41.225  INFO 1384 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2018-10-14 20:34:41.517  INFO 1384 --- [           main] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
2018-10-14 20:34:42.079  INFO 1384 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1c72da34: startup date [Sun Oct 14 20:34:36 CDT 2018]; root of context hierarchy
2018-10-14 20:34:42.301  INFO 1384 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/]}" onto public java.lang.String jcg.zheng.demo.spring.controller.HomeController.welcome(java.util.Locale,java.util.Map)
2018-10-14 20:34:42.303  INFO 1384 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/user],methods=[POST]}" onto public java.lang.String jcg.zheng.demo.spring.controller.UserController.user(jcg.zheng.demo.spring.model.User,org.springframework.ui.Model)
2018-10-14 20:34:42.303  INFO 1384 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/user/save],methods=[POST]}" onto public java.lang.String jcg.zheng.demo.spring.controller.UserController.saveuser(jcg.zheng.demo.spring.entity.Account,org.springframework.ui.Model)
2018-10-14 20:34:42.306  INFO 1384 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-10-14 20:34:42.307  INFO 1384 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-10-14 20:34:42.350  INFO 1384 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-10-14 20:34:42.350  INFO 1384 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-10-14 20:34:42.403  INFO 1384 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-10-14 20:34:42.709  INFO 1384 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-10-14 20:34:42.769  INFO 1384 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2018-10-14 20:34:42.775  INFO 1384 --- [           main] j.z.d.spring.SpringBootWebApplication    : Started SpringBootWebApplication in 6.879 seconds (JVM running for 7.33)

Run mvn clean install for the automation-demo project. Watch it launch Google Chrome and execute all test cases.

Automation Test Results

C:\gitworkspace\automation-demo>mvn clean  install
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=512m; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=512m; support was removed in 8.0
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------< mary.example.selenium:automation-demo >----------------
[INFO] Building automation-demo 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ automation-demo ---
[INFO] Deleting C:\gitworkspace\automation-demo\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ automation
-demo ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources,i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ automation-demo
 ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent!
[INFO] Compiling 3 source files to C:\gitworkspace\automation-demo\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ automation-demo ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources,i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ automation-demo ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent!
[INFO] Compiling 5 source files to C:\gitworkspace\automation-demo\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ automation-demo --
-
[INFO] Surefire report directory: C:\gitworkspace\automation-demo\target\surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running mary.demo.webtest.ErrorPageTest
Starting ChromeDriver 2.41.578737 (49da6702b16031c40d63e5618de03a32ff6c197e) on 
port 18877
Only local connections are allowed.

DevTools listening on ws://127.0.0.1:55807/devtools/browser/d86b6c77-d33c-4546-a93f-a7232af276f9
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 10.328 sec
Running mary.demo.webtest.HomePageTest
Starting ChromeDriver 2.41.578737 (49da6702b16031c40d63e5618de03a32ff6c197e) on 
port 36077
Only local connections are allowed.

DevTools listening on ws://127.0.0.1:55836/devtools/browser/6dc4f429-e1d5-45ce-ac34-5c699975ec36
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 9.221 sec
Running mary.demo.webtest.NewUserPageTest
Starting ChromeDriver 2.41.578737 (49da6702b16031c40d63e5618de03a32ff6c197e) on
port 39499
Only local connections are allowed.

DevTools listening on ws://127.0.0.1:55862/devtools/browser/74aad938-bd3d-4068-9508-d51c066345ff
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 9.052 sec

Results :

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0

[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ automation-demo ---
[INFO] Building jar: C:\gitworkspace\automation-demo\target\automation-demo-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ automation-demo
---
[INFO] Installing C:\gitworkspace\automation-demo\target\automation-demo-0.0.1-SNAPSHOT.jar to C:\repo\mary\example\selenium\automation-demo\0.0.1-SNAPSHOT\auto
mation-demo-0.0.1-SNAPSHOT.jar
[INFO] Installing C:\gitworkspace\automation-demo\pom.xml to C:\repo\mary\example\selenium\automation-demo\0.0.1-SNAPSHOT\automation-demo-0.0.1-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 35.629 s
[INFO] Finished at: 2018-10-14T20:36:52-05:00
[INFO] ------------------------------------------------------------------------

C:\gitworkspace\automation-demo>

Verify it based on the saved screenshots.

Selenium Web Application Testing - Home Page
Figure 3 Home Page

Selenium Web Application Testing - Invalid User
Figure 4 Invalid User

Selenium Web Application Testing - User Detail
Figure 5 User Detail

9. Selenium Web Application Testing – Summary

In this article, I built a Spring Boot web application and then created automation test cases with Selenium in Google Chrome.

10. Download the Source Code

This tutorial consists of two Maven projects:

  • spring-boot-web-jsp-demo – create a web application with Spring MVC framework.
  • automation-demo – test the above web application with Selenium.
Download
You can download the full source code of this example here: Selenium Web Application Testing Example)

Mary Zheng

Mary has graduated from Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She works as a senior Software Engineer in the telecommunications sector where she acts as a leader and works with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Anushri Kher
Anushri Kher
4 years ago

How to Open webpage,how to click on button,How to select menu from drop down menu in Selenium using Spring boot.

St1mpy
St1mpy
3 years ago

Nice article thanks Mary. I have found some hardcoded path in your project. It is in WebTestBase. I guess this path should point to the chromedriver:
@Before
public void setup() {
System.setProperty(“webdriver.chrome.driver”, “C:\\MaryZheng\\tools\\webdriver\\chromedriver.exe”);
driver = new ChromeDriver();
screenshotHelper = new ScreenshotHelper(driver);
}

Back to top button