Testing "Complete Task" Feature

Become more familiar with Jest and Selenium and further develop our app in the following two lessons.

The task at hand

Now that you have the defining idea behind testing, we will further develop your experience by writing more tests and features.

As we are developing a TODO app, it would be a good idea to be able to complete tasks. Here is how a potential user story might look:

A user must be able to mark any task as completed by clicking on it. He can also mark any completed task as incomplete by clicking on it. Completed tasks must have a completed CSS class.

Integration test

As always, the first step to implementing a user story would be to write the integration test. In this test, we are looking to create a few tasks. Try to complete one of them (and verify that it completes), and then uncomplete it. Firstly, create the file complete-task.test.js under /integration-tests. Copy over the imports and beforeAll/afterAll functions from create-task.test.js. After you have done that, we will write a new helper function called createTask:

const createTask = async (taskName) => {
    await driver.wait(until.elementLocated(By.xpath("//input[@placeholder='Enter new task']")), 1000);
    const input = await driver.findElement(By.xpath("//input[@placeholder='Enter new task']"));
    await input.clear();
    await input.sendKeys(taskName + Key.ENTER);

    await driver.wait(until.elementLocated(By.xpath(`//*[text()='${taskName}']`)), 1000);
};

We do not want to clutter the test code with unrelated code, so we moved the task creation code outside. Time to write the actual test:

test('should complete tasks', async  () => {
    // Creating tasks
    await driver.get('http://localhost:3000');
    await createTask('Task 1');
    await createTask('Task 2');
    await createTask('Task 3');

    // Complete task
    const task = await driver.findElement(By.xpath("//*[text()='Task 2']"));
    await task.click();

    // Check that it completed
    expect(await task.getAttribute('class')).toMatch(/completed/);

    // Uncomplete task
    await task.click();

    // Check that it uncompleted
    expect(await task.getAttribute('class')).not.toMatch(/completed/);
});

This code should be pretty straightforward. First, we create three tasks using the function we wrote earlier. Then, we click on Task 2, and check if it gets a completed class. Lastly, we click one more time, and verify that the completed class is removed.

Battling ambiguity

You might have noticed that our definition of a task being completed relies completely on a CSS class. In reality, this may not be the case. The specifications may say that a completed task must be at the bottom of a list or have a checked checkbox or have a disabled attribute. To make sure all teams are on the same page, the user story must specify what it means for a task to be completed as it did in our case. Otherwise, come up with a criterion yourself and stick with it until someone comes up with a better one.

Error

If you try to run this test as is, it will fail. This is expected, as we did not write the implementation yet. This is the error you should be getting:

 FAIL  integration-tests/complete-task.test.js (6.28s)
  ● should complete tasks

    expect(received).toMatch(expected)

    Expected pattern: /completed/
    Received string:  ""

      32 |     await task.click();
      33 |     // Check that it completed
    > 34 |     expect(await task.getAttribute('class')).toMatch(/completed/);
         |                                              ^
      35 | });
      36 |

The error is the tasks are not being disabled, as far as Selenium is concerned. We will address this in the next lesson. Here is the whole project up until now for reference:

Get hands-on with 1200+ tech skills courses.