Writing End-to-End Tests for Javascript Web Applications - Modal Dialogs

After we have finished this lesson, we

  • Understand how we can select the correct wait condition when we are writing end-to-end tests for features that display modal dialogs.
  • Know how we can make the required changes to our page objects.

Writing End-to-End Tests for Features That Use Modal Dialogs

The view task page contains a link that deletes an existing task from the database. The relevant part of the template that renders the view task page looks as follows:

 1<h1 id="task-title">{{task.title}}</h1>
 2<div class="panel panel-default">
 3    <div class="panel-body">
 4        <div id="task-description"
 5             ng-if="!task.description"
 6             translate="page.view.task.no.description.message"></div>
 7        <div id="task-description"
 8             ng-if="task.description">{{task.description}}</div>
 9    </div>
10</div>
11<div id="lifecycle-fields" class="container-fluid">
12    <!-- Omitted as irrelevant for this lesson -->
13</div>
14<div id="task-action-links" class="row">
15    <div class="col-md-1">
16        <span delete-task-link task="task"></span>
17    </div>
18    <div class="col-md-1">
19        <a id="update-task-link"
20           ui-sref="task.update({id: task.id})"
21           translate="page.view.task.update.task.link.label">
22        </a>
23    </div>
24</div>

When a user clicks the delete link, our web application displays a confirmation dialog that has two buttons:

  • The cancel button closes the confirmation dialog.
  • The delete button closes the confirmation dialog and deletes the task from the database.

The template that contains the HTML markup of the modal dialog looks as follows:

 1<div id="delete-task-dialog">
 2    <div class="modal-header">
 3        <h4 class="modal-title" translate="dialogs.delete.task.dialog.title">
 4        </h4>
 5    </div>
 6    <div class="modal-body">
 7        <p translate="dialogs.delete.task.dialog.text"
 8           translate-values="{title: task.title}"></p>
 9    </div>he d
10    <div class="modal-footer">
11        <button type="button"
12                class="btn btn-default"
13                data-dismiss="modal"
14                translate="dialogs.delete.task.dialog.cancel.button.label"
15                ng-click="cancel()"></button>
16        <button id="delete-task-button"
17                type="button"
18                class="btn btn-danger"
19                translate="dialogs.delete.task.dialog.delete.button.label"
20                ng-click="delete()"></button>
21    </div>
22</div>

Before we can write end-to-end tests for the delete task feature, we have to select the wait condition that must be true before our end-to-end test can continue. When we are writing end-to-end tests for features that display modal dialogs, we can select the correct wait condition by following these rules:

  • If we want to write assertions for the content of the modal dialog, we should wait until the dialog is visible.
  • If we want to click the buttons of the modal dialog, we should wait until these buttons are clickable.

Next, we will find out how we can make the required changes to our page objects.

Creating a New Page Object

As we remember, the TaskActions class is the page object that allows our tests to click both delete and update task links. Nevertheless, I think that we shouldn’t add the required code, which handles the confirmation dialog, to the TaskActions class because the resulting class would have too many responsibilities.

That’s why we will create a new page object that represents the confirmation dialog. We can create this page object by following these steps:

First, we have to create a final class called DeleteTaskConfirmationDialog and add two fields to the created class:

  • The browser field contains a reference to the used WebDriver object.
  • The taskActions field contains a reference to the WebElement object that represents the HTML element which contains the delete and update task links.

After we have created the DeleteTaskConfirmationDialog class, its source code looks as follows:

1import org.openqa.selenium.WebDriver;
2import org.openqa.selenium.WebElement;
3
4final class DeleteTaskConfirmationDialog {
5
6    private final WebDriver browser;
7    private final WebElement taskActions;
8
9}

Second, we have add a constructor to the created class. This constructor injects the required WebDriver and WebElement objects to the browser and taskActions fields.

After we have implemented our constructor, the source code of the DeleteTaskConfirmationDialog class looks as follows:

 1import org.openqa.selenium.WebDriver;
 2import org.openqa.selenium.WebElement;
 3
 4final class DeleteTaskConfirmationDialog {
 5
 6    private final WebDriver browser;
 7    private final WebElement taskActions;
 8
 9    DeleteTaskConfirmationDialog(WebDriver browser, WebElement taskActions) {
10        this.browser = browser;
11        this.taskActions = taskActions;
12    }
13}

Third, we have add an open() method to the DeleteTaskConfirmationDialog class and implement this method by following these steps:

  1. Click the delete link found from the view task page.
  2. Wait until our test can click the delete button.
  3. Return a reference to this DeleteTaskConfirmationDialog object.

After we have added this method to the DeleteTaskConfirmationDialog class, its source code looks as follows:

 1import org.openqa.selenium.By;
 2import org.openqa.selenium.WebDriver;
 3import org.openqa.selenium.WebElement;
 4
 5final class DeleteTaskConfirmationDialog {
 6
 7    private final WebDriver browser;
 8    private final WebElement taskActions;
 9
10    DeleteTaskConfirmationDialog(WebDriver browser, WebElement taskActions) {
11        this.browser = browser;
12        this.taskActions = taskActions;
13    }
14
15    DeleteTaskConfirmationDialog open() {
16        WebElement deleteLink = taskActions.findElement(
17                By.id("delete-task-link")
18        );
19        deleteLink.click();
20
21        SeleniumWait.waitUntilElementIsClickable(browser,
22                By.id("delete-task-button")
23        );
24
25        return this;
26    }
27}

Fourth, we have to add a deleteTask() method to the DeleteTaskConfirmationDialog class and implement this method by following these steps:

  1. Click the delete button.
  2. Wait until the task list page is open and return a new TaskListPage object.

After we have implemented this method, the source code of the DeleteTaskConfirmationDialog class looks as follows:

 1import org.openqa.selenium.By;
 2import org.openqa.selenium.WebDriver;
 3import org.openqa.selenium.WebElement;
 4final class DeleteTaskConfirmationDialog {
 5
 6    private final WebDriver browser;
 7    private final WebElement taskActions;
 8
 9    DeleteTaskConfirmationDialog(WebDriver browser, WebElement taskActions) {
10        this.browser = browser;
11        this.taskActions = taskActions;
12    }
13
14    DeleteTaskConfirmationDialog open() {
15        WebElement deleteLink = taskActions.findElement(
16                By.id("delete-task-link")
17        );
18        deleteLink.click();
19
20        SeleniumWait.waitUntilElementIsClickable(browser,
21                By.id("delete-task-button")
22        );
23
24        return this;
25    }
26
27    TaskListPage deleteTask() {
28        WebElement deleteButton = browser.findElement(
29                By.id("delete-task-button")
30        );
31        deleteButton.click();
32
33        TaskListPage targetPage = new TaskListPage(browser);
34        targetPage.waitUntilPageIsOpen();
35        return targetPage;
36    }
37}

We have now created the page object which represents the confirmation dialog that is shown when a user clicks the delete task link. Our next step is to ensure that the TaskActions class uses our new page object.

Modifying the TaskActions Class

We can make the required change to the TaskActions class by changing the implementation of the deleteTask() method. We can rewrite this method by following these steps:

  1. Open the confirmation dialog.
  2. Delete the shown task from the database.

After we have made the required change to the TaskActions class, its source code looks as follows:

 1import org.openqa.selenium.By;
 2import org.openqa.selenium.WebDriver;
 3import org.openqa.selenium.WebElement;
 4import org.openqa.selenium.support.ui.ExpectedConditions;
 5import org.openqa.selenium.support.ui.WebDriverWait;
 6
 7final class TaskActions {
 8
 9    private final WebDriver browser;
10    private final WebElement rootElement;
11    private final Long taskId;
12
13    TaskActions(WebDriver browser, WebElement rootElement, Long taskId) {
14        this.browser = browser;
15        this.rootElement = rootElement;
16        this.taskId = taskId;
17    }
18
19    TaskListPage deleteTask() {
20        DeleteTaskConfirmationDialog dialog = new DeleteTaskConfirmationDialog(
21                browser,
22                rootElement
23        ).open();
24        return dialog.deleteTask();
25    }
26
27    UpdateTaskPage updateTask() {
28        WebElement updateTask = rootElement.findElement(
29                By.id("update-task-link")
30        );
31        updateTask.click();
32
33        UpdateTaskPage targetPage = new UpdateTaskPage(browser, taskId);
34        targetPage.waitUntilPageIsOpen();
35
36        return targetPage;
37    }
38}

We have now made the changes that allow us run our “old” end-to-end tests which verify that the delete task feature is working as expected. Also, it’s good to understand that we don’t have to make any changes to the actual end-to-end test classes.

Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us three things:

  • We should add the code that handles our modal dialog to a new page object because otherwise we will add too many responsibilities to our existing page objects.
  • If we want to write assertions for the content of the modal dialog, we should wait until the dialog is visible.
  • If we want to click the buttons of the modal dialog, we should wait until these buttons are clickable.

Get the source code from Github