Creating Our First Page Object
After we have finished this lesson, we:
- Know how we can create page objects that don’t depend on environment-specific configuration.
- Can create page objects that can interact with an HTML page.
- Can create page objects that can find information from an HTML page.
This lesson assumes that:
Passing Environment-Specific Configuration to Our Page Objects
Before we can write our page object, we have to create two utility classes that help us to create page objects that don’t depend on environment-specific configuration. We can create these classes by following these steps:
First, we have to create a class called: WebDriverEnvironment. This class provides static methods that allow us to access environment-specific configuration that is passed to our end-to-end tests by using system properties. We will use this class for getting the base URL of the server that runs our web application.
After we have created this class, we have to add a static getBaseUrl() method to the created class and implement this method by following these steps:
- Get the value of the
webdriver.base.urlsystem property. - If no base URL is found, throw a new
RuntimeException. - Return the found base URL.
After we have implemented the getBaseUrl() method, the source code of the WebDriverEnvironment class looks as follows:
1public final class WebDriverEnvironment {
2
3 private WebDriverEnvironment() {}
4
5 public static String getBaseUrl() {
6 String baseUrl = System.getProperty("webdriver.base.url");
7 if (baseUrl == null) {
8 throw new RuntimeException("No base url found!");
9 }
10 return baseUrl;
11 }
12}
Second, we have to create a class called: WebDriverUrlBuilder. This class provides one static factory method that helps us to replace hard coded URL addresses with environment-specific URL addresses. In other words, this class helps us to write end-to-end tests that can be run in different environments.
After we have created this class, we can implement the required factory method by following these steps:
- Add a
static buildFromPath()method to theWebDriverUrlBuilderclass. This method takes the path template and the parameters that are referenced by the format specifiers found from the pathStringas method parameters. As you probably guessed, this method returns the created URL address. - If the path is
nullor empty, throw a newIllegalArgumentException. - Create the real path by using the
format()method of theStringclass. - Get the base URL.
- If the base URL doesn’t end with the character: ‘/’, append the character ‘/’ to the base URL.
- If the path starts with the character: ‘/’, replace that character with an empty string.
- Append the path to the base URL and return the created URL address.
After we have written the buildFromPath() method, the source code of the WebDriverUrlBuilder class looks as follows:
1public final class WebDriverUrlBuilder {
2
3 private WebDriverUrlBuilder() {}
4
5 public static String buildFromPath(String path, Object... params) {
6 if ((path == null) || path.isEmpty()) {
7 throw new IllegalArgumentException("Path must be given.");
8 }
9
10 path = String.format(path, params);
11
12 String baseUrl = WebDriverEnvironment.getBaseUrl();
13 if (!baseUrl.endsWith("/")) {
14 baseUrl += "/";
15 }
16
17 if (path.startsWith("/")) {
18 path = path.replaceFirst("/", "");
19 }
20
21 return baseUrl + path;
22 }
23}
Let’s move on and take a look at the JSP page that renders the login view of our web application.
Introduction to System Under Test
During this lesson, we will create a page object that represents the login page of our web application. This page fulfills these requirements:
- The login page is rendered when a user sends a
GETrequest to the path: ‘/user/login’. - The login form is shown only to anonymous users. If an authenticated user opens the login page, he/she will see a message which explains that he/she is already logged in.
- A user can log in by using his/her email address and password.
- If a login fails, the user is forwarded back to the login page and the login page must display an error message which explains that the login failed.
The source code of the login.jsp page looks as follows:
1<!DOCTYPE html>
2<%@ page contentType="text/html;charset=UTF-8" language="java" %>
3<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
4<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
5<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
6<html>
7<head>
8 <title><spring:message code="page.login.title"/></title>
9</head>
10<body>
11<h1><spring:message code="page.login.title"/></h1>
12<div class="panel panel-default">
13 <div class="panel-body">
14 <sec:authorize access="isAnonymous()">
15 <c:if test="${param.error eq 'bad_credentials'}">
16 <div id="login-error-alert"
17 class="alert alert-danger alert-dismissible">
18 <button type="button"
19 class="close"
20 data-dismiss="alert"
21 aria-hidden="true">×</button>
22 <spring:message code="page.login.login.failed.error.message"/>
23 </div>
24 </c:if>
25 <form id="login-form"
26 action="${pageContext.request.contextPath}/user/login"
27 method="POST">
28 <input type="hidden"
29 name="${_csrf.parameterName}"
30 value="${_csrf.token}"/>
31 <div class="form-group">
32 <label for="email-address">
33 <spring:message code="page.login.email.address.label"/>
34 </label>
35 <input id="email-address"
36 class="form-control"
37 name="username"
38 type="email"
39 placeholder="<spring:message code="page.login.email.address.placeholder"/>">
40 </div>
41 <div class="form-group">
42 <label for="password">
43 <spring:message code="page.login.password.label"/>
44 </label>
45 <input id="password"
46 class="form-control"
47 name="password"
48 type="password"
49 placeholder="<spring:message code="page.login.password.placeholder"/>">
50 </div>
51 <button type="submit" class="btn btn-default">
52 <spring:message code="page.login.form.submit.button.label"/>
53 </button>
54 </form>
55 </sec:authorize>
56 <sec:authorize access="isAuthenticated()">
57 <div id="authenticated-user-error">
58 <spring:message code="page.login.authenticated.user.error.message"/>
59 </div>
60 </sec:authorize>
61 </div>
62</div>
63</body>
64</html>
Next, we will create the page object that represents the login page.
Creating the Page object That Represents the Login Page
We can create our page object by following these steps:
First, we have to create a public and final class called: LoginPage and add the following fields to the created class:
- The
browserfield contains a reference to the usedWebDriverobject. - The
pageUrlfield contains the URL of the login page.
After we have added these fields to the created class, the source code of the LoginPage class looks as follows:
1import org.openqa.selenium.WebDriver;
2
3public final class LoginPage {
4
5 private final WebDriver browser;
6 private final String pageUrl;
7}
LoginPage class must be public because it is used by other test classes that are not in same package as the LoginPage class. If your page object is used only by classes that are in the same package as your page object, you should change the visibility of your page object class to package protected.
Second, we have to implement a constructor that takes a WebDriver object as a constructor argument. We can implement this constructor by following these steps:
- Set the used
WebDriverobject to thebrowserfield. - Create the environment-specific URL of the login page and set the created URL to the
pageUrlfield.
After we have implemented the required constructor, the source code of the LoginPage class looks as follows:
1import org.openqa.selenium.WebDriver;
2
3public final class LoginPage {
4
5 private final WebDriver browser;
6 private final String pageUrl;
7
8 public LoginPage(WebDriver browser) {
9 this.browser = browser;
10 this.pageUrl = WebDriverUrlBuilder.buildFromPath("/user/login");
11 }
12}
Third, we have to implement a method that opens the login page and returns a new LoginPage object. After we have written this method, the source code of the LoginPage class looks as follows:
1import org.openqa.selenium.WebDriver;
2
3public final class LoginPage {
4
5 private final WebDriver browser;
6 private final String pageUrl;
7
8 public LoginPage(WebDriver browser) {
9 this.browser = browser;
10 this.pageUrl = WebDriverUrlBuilder.buildFromPath("/user/login");
11 }
12
13 public LoginPage open() {
14 browser.get(pageUrl);
15 return new LoginPage(browser);
16 }
17}
Fourth, we have to implement a method that checks if the login page is open. This method returns true if the login page is open and false otherwise. After we have written this method, the source code of the LoginPage class looks class follows:
1import org.openqa.selenium.WebDriver;
2
3public final class LoginPage {
4
5 private final WebDriver browser;
6 private final String pageUrl;
7
8 public LoginPage(WebDriver browser) {
9 this.browser = browser;
10 this.pageUrl = WebDriverUrlBuilder.buildFromPath("/user/login");
11 }
12
13 public LoginPage open() {
14 browser.get(pageUrl);
15 return new LoginPage(browser);
16 }
17
18 boolean isOpen() {
19 return browser.getCurrentUrl().equals(pageUrl);
20 }
21}
package protected because it is used only by test classes that are in the same package as the LoginPage class. Also, I think that every page object that represents an entire HTML page must have a method that checks if the page is open. This is a quite common assertion, and this method helps us to hide the WebDriver object from our test classes.
Fifth, we have to implement three methods that find information from the login page. These methods are used by the end-to-end tests which verify that the login page is rendered correctly. We can implement these methods by following these steps:
- Implement a method which returns the email address that is entered to the login form.
- Write a method which returns the password that is entered to the login form.
- Implement a method that returns
trueif the login form is visible andfalseotherwise.
After we have implemented these methods, the source code of the LoginPage class looks as follows:
1import org.openqa.selenium.By;
2import org.openqa.selenium.WebDriver;
3
4public final class LoginPage {
5
6 private final WebDriver browser;
7 private final String pageUrl;
8
9 public LoginPage(WebDriver browser) {
10 this.browser = browser;
11 this.pageUrl = WebDriverUrlBuilder.buildFromPath("/user/login");
12 }
13
14 public LoginPage open() {
15 browser.get(pageUrl);
16 return new LoginPage(browser);
17 }
18
19 boolean isOpen() {
20 return browser.getCurrentUrl().equals(pageUrl);
21 }
22
23 String getEmailAddress() {
24 return browser.findElement(By.id("email-address"))
25 .getAttribute("value");
26 }
27
28 String getPassword() {
29 return browser.findElement(By.id("password"))
30 .getAttribute("value");
31 }
32
33 boolean isLoginFormVisible() {
34 return !browser.findElements(By.id("login-form")).isEmpty();
35 }
36}
package protected because they are used only by test classes that are in the same package as the LoginPage class.
Sixth, we have to implement a method that logs a user in by using an email address and a password. This method is used to configure the logged in user before an end-to-end test is run. Also, this method assumes that the login is successful.
We can implement this method by following these steps:
- Write two utility methods that enter the email address and password to the login form.
- Implement a utility method that submits the login form.
- Add a
login()method to theLoginPageclass and implement this method by invoking our utility methods. This method takes the email address and password as method parameters and returns a newTaskListPageobject.
After we have implemented the login() method, the source code of the LoginPage class looks as follows:
1import org.openqa.selenium.By;
2import org.openqa.selenium.WebDriver;
3
4public final class LoginPage {
5
6 private final WebDriver browser;
7 private final String pageUrl;
8
9 public LoginPage(WebDriver browser) {
10 this.browser = browser;
11 this.pageUrl = WebDriverUrlBuilder.buildFromPath("/user/login");
12 }
13
14 public LoginPage open() {
15 browser.get(pageUrl);
16 return new LoginPage(browser);
17 }
18
19 boolean isOpen() {
20 return browser.getCurrentUrl().equals(pageUrl);
21 }
22
23 String getEmailAddress() {
24 return browser.findElement(By.id("email-address"))
25 .getAttribute("value");
26 }
27
28 String getPassword() {
29 return browser.findElement(By.id("password"))
30 .getAttribute("value");
31 }
32
33 boolean isLoginFormVisible() {
34 return !browser.findElements(By.id("login-form")).isEmpty();
35 }
36
37 public TaskListPage login(String emailAddress, String password) {
38 typeEmailAddress(emailAddress);
39 typePassword(password);
40 submitLoginForm();
41
42 return new TaskListPage(browser);
43 }
44
45 private void typeEmailAddress(String emailAddress) {
46 browser.findElement(By.id("email-address")).sendKeys(emailAddress);
47 }
48
49 private void typePassword(String password) {
50 browser.findElement(By.id("password")).sendKeys(password);
51 }
52
53 private void submitLoginForm() {
54 browser.findElement(By.id("login-form")).submit();
55 }
56}
LoginPage class has another login method which assumes that the login fails. This method uses our utility methods as well.
The login() method returns a new TaskListPage object because our web application forwards the user to the task list page if login is successful. The source code of the TaskListPage class looks as follows:
1import org.openqa.selenium.WebDriver;
2
3public final class TaskListPage {
4
5 private final WebDriver browser;
6 private final String pageUrl;
7
8 public TaskListPage(WebDriver browser) {
9 this.browser = browser;
10 this.pageUrl = WebDriverUrlBuilder.buildFromPath("/");
11 }
12
13 public boolean isOpen() {
14 return browser.getCurrentUrl().equals(pageUrl);
15 }
16}
We have now created a page object that allows us to interact with an HTML page and find information from it. Let’s summarize what we learned from this lesson.
Summary
This lesson has taught us four things:
- We should pass the environment-specific configuration properties to our end-to-end tests by using system properties.
- We should build the required URL addresses by appending the used paths to the environment-specific base URL.
- If a page object represents an entire HTML page, it must have a method that opens the page in question.
- If a page object represents an entire HTML page, it must have a method that checks if the page is open.