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:
- A landing page to display the server’s current time and a login button.
- A user detail page to show the user’s detail information when the username is matched with an existing record.
- A create a new user page to create a new user when the username is not matched with any existing record.
- 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.
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.
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.
You can download the full source code of this example here: Selenium Web Application Testing Example)
How to Open webpage,how to click on button,How to select menu from drop down menu in Selenium using Spring boot.
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);
}