Writing End-to-End Tests for Javascript Web Applications - Forms
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 create or update data.
- Know how we can make the required changes to our page objects.
This lesson assumes that:
Writing End-to-End Tests for Forms
When we are writing end-to-end tests for a feature that submits a form, typically we want to write two kinds of tests:
- We want to ensure that the form fields are populated with the correct data.
- We want to verify that the user can submit the tested form and write assertions for the expected outcome.
As we remember , we cannot trust that the entire HTML document is ready when we open the tested page, and that’s why we have to wait until the entire HTML document has been constructed. The question is:
How can we select the condition that must be true before our end-to-end test can continue?
I think that we have two options:
- We can wait until the slowest API call has finished and its results have been rendered. This is a good choice if our form has long drop-down lists that are populated with data that is fetched from the database.
- We can wait until our test case can interact with our form. This is a good choice if we are writing end-to-end tests for a simple form and we simply want to ensure that our tests wait until they can enter the field values and submit the form.
Next, we will take a look at an example which demonstrates how we can implement the required wait logic.
Updating the Information of a Task
When we are writing end-to-end tests for a feature that updates the information of an existing task, we want to ensure that:
- The fields of the update task form are populated with the field values of the found task.
- The user can update the information of the task.
The source code of the view template that renders the update task form looks as follows:
1<div class="panel panel-default">
2 <div class="panel-body">
3 <form id="task-form"
4 name="taskForm"
5 ng-show="task"
6 role="form">
7 <input id="task-id"
8 type="hidden"
9 ng-if="task.id"
10 value="{{task.id}}"/>
11 <div class="form-group"
12 ng-class="{ 'has-error': taskForm.title.$dirty && taskForm.title.$invalid }">
13 <label for="task-title" translate="task.property.title.label"></label>
14 <input id="task-title"
15 class="form-control"
16 name="title"
17 ng-model="task.title"
18 required
19 ng-maxlength="100"/>
20 <div ng-show="taskForm.title.$dirty && taskForm.title.$invalid">
21 <span id="error-title"
22 class="help-block"
23 translate="task.messages.title.required"
24 ng-show="taskForm.title.$error.required"></span>
25 <span id="error-title"
26 class="help-block"
27 translate="task.messages.title.maxLength"
28 ng-show="taskForm.title.$error.maxlength"></span>
29 </div>
30 </div>
31 <div class="form-group"
32 ng-class="{ 'has-error': taskForm.description.$dirty && taskForm.description.$invalid }">
33 <label for="task-description" translate="task.property.description.label"></label>
34 <textarea id="task-description"
35 class="form-control"
36 name="description"
37 ng-model="task.description"
38 ng-maxlength="500"></textarea>
39 <div ng-show="taskForm.description.$dirty && taskForm.description.$invalid">
40 <span id="error-description"
41 class="help-block"
42 translate="task.messages.description.maxLength"
43 ng-show="taskForm.description.$error.maxlength"></span>
44 </div>
45 </div>
46 <button id="submit-task-form"
47 type="button"
48 class="btn btn-default"
49 translate="directive.task.form.submit.button.label"
50 ng-disabled="taskForm.$invalid || !taskForm.$dirty"
51 ng-click="saveTask()"></button>
52 </form>
53 </div>
54</div>
Again, before we can implement the required wait condition, we have to decide what kind of wait condition we want to use. It’s clear that finding the information of the updated task is the slowest (and only) API call that must be finished before our application can render the update task form.
So, should we wait until the information of the task is visible?
Well, that is one option. However, because we cannot write our end-to-end tests if the user cannot enter the updated title and description, I think that we should wait until the user can click the title field and enter the updated title to that field.
We can implement this wait condition by following these steps:
First, we have to make two changes to the UpdateTaskPage class:
- Add a
waitUntilPageIsOpen()method to theUpdateTaskPageclass and implement this method by waiting until our tests can click the title field. - Change the implementation of the
open()method. Our new implementation must wait until our tests can click the title field before it returns a newUpdateTaskPageobject.
After we have made the required changes to the UpdateTaskPage class, its source code looks as follows:
1import org.openqa.selenium.By;
2import org.openqa.selenium.WebDriver;
3
4final class UpdateTaskPage {
5
6 private final WebDriver browser;
7 private final String pageUrl;
8 private final Long taskId;
9
10 UpdateTaskPage(WebDriver browser, Long taskId) {
11 this.browser = browser;
12 this.pageUrl = WebDriverUrlBuilder.buildFromPath("/task/%d/update",
13 taskId
14 );
15 this.taskId = taskId;
16 }
17
18 TaskForm getForm() {
19 return new TaskForm(browser);
20 }
21
22 boolean isOpen() {
23 return browser.getCurrentUrl().equals(pageUrl);
24 }
25
26 UpdateTaskPage open() {
27 browser.get(pageUrl);
28 waitUntilPageIsOpen();
29 return new UpdateTaskPage(browser, taskId);
30 }
31
32 void waitUntilPageIsOpen() {
33 SeleniumWait.waitUntilElementIsClickable(browser,
34 By.id("task-title")
35 );
36 }
37}
Second, we have to make two changes to the TaskForm class:
- We have to change the constructor of the
TaskFormclass. Because the create and update task forms are rendered by a custom directive, theidof theformelement is alwaystask-form. That’s why we can remove the constructor argument that specifies theidof theformelement. - We have to change the implementation of the
submitTaskForm()method. Our new implementation clicks the submit button and waits until the view task page is open before it returns a newTaskPageobject.
After we have made the required changes, the source code of the TaskForm class looks as follows:
1import org.openqa.selenium.By;
2import org.openqa.selenium.WebDriver;
3import org.openqa.selenium.WebElement;
4
5import java.util.List;
6
7final class TaskForm {
8
9 private WebDriver browser;
10 private WebElement form;
11
12 TaskForm(WebDriver browser) {
13 this.browser = browser;
14 this.form = this.browser.findElement(By.id("task-form"));
15 }
16
17 void typeDescription(String description) {
18 WebElement descriptionTextArea = form.findElement(
19 By.id("task-description")
20 );
21 descriptionTextArea.clear();
22 descriptionTextArea.sendKeys(description);
23 }
24
25 void typeTitle(String title) {
26 WebElement titleInputField = form.findElement(By.id("task-title"));
27 titleInputField.clear();
28 titleInputField.sendKeys(title);
29 }
30
31 TaskPage submitTaskForm() {
32 Long taskId = getTaskId();
33 form.findElement(By.id("submit-task-form")).click();
34
35 TaskPage shown = new TaskPage(browser, taskId);
36 shown.waitUntilPageIsOpen();
37 return shown;
38 }
39
40 String getTaskDescription() {
41 return form.findElement(By.id("task-description"))
42 .getAttribute("value");
43 }
44
45 private Long getTaskId() {
46 List<WebElement> idElements = form.findElements(By.id("task-id"));
47 if (idElements.size() == 1) {
48 String taskId = idElements.get(0).getAttribute("value");
49 return Long.parseLong(taskId);
50 }
51 return null;
52 }
53
54 String getTaskTitle() {
55 return form.findElement(By.id("task-title"))
56 .getAttribute("value");
57 }
58}
We have now made the changes that allow us run our “old” end-to-end tests which verify that the update 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 two things:
- If our form has long drop-down lists that are populated with the data found from the database, it’s a good idea to wait until these lists have been populated.
- If we are writing end-to-end tests for a simple form, it’s a good idea to wait until our tests can interact with the tested form.