Playwright

Lesson 4 of 17

Lesson ยท 20 min

Narrow down a locator

Strict mode

The todo list has three Delete buttons. The locator getByRole('button', { name: 'Delete' }) matches all three. When you try to click a locator that matches more than one element, Playwright does not guess. It stops the test with a strict mode violation and lists the elements that it found.

This is a good thing. A test that clicks the wrong button can pass for the wrong reason. Strict mode makes you say which element you mean.

Strict mode applies to actions, such as click() and fill(), and to assertions about one element, such as toBeVisible(). It does not apply when you count elements with toHaveCount.

Make a locator narrower

You have four tools.

Chain locators. A locator can have another locator inside it. The second locator looks only inside the first one.

const list = page.getByRole('list', { name: 'Todo list' });
await list.getByRole('button', { name: 'Delete' });

Filter. filter keeps only the elements that match a condition.

// Rows that contain the text.
page.getByTestId('todo-item').filter({ hasText: 'Write a test' })

// Rows that contain another element.
page.getByTestId('todo-item').filter({ has: page.getByRole('checkbox', { checked: true }) })

// Rows that do not contain the text.
page.getByTestId('todo-item').filter({ hasNotText: 'Walk the dog' })

The options has and hasNot take a locator. The locator is looked up inside each row, not in the whole page.

Pick by position. first(), last(), and nth(index) pick one element from a list. The index starts at 0. Use them when the position is the point of the test, for example "the newest todo is the last one". Do not use them to avoid thinking about the right locator. A test that clicks .nth(1) breaks when someone adds an item before it.

Use a CSS selector. page.locator('li.done') finds elements with CSS. It works, and sometimes it is the only way. CSS classes belong to the developers, and they change for design reasons, so a test that uses them breaks more often than a test that uses roles.

Save a locator in a variable

A locator is only a description. It does not look for the element until you use it. You can save one in a const, and you can use it again as often as you like. Each use looks again, so the locator never holds an old element.

const rows = page.getByTestId('todo-item');
await expect(rows).toHaveCount(3);

Your turn

The file has a test that fails with a strict mode violation. Fix it, and then write two more tests.

Tasks

3 to do

  1. Task 1, not checked yet. Press Run tests and read the error. Fix the test so that it deletes only the todo Write a test. Find the row with filter({ hasText: ... }), and then find the button inside the row. At the end, check that Write a test is hidden.

  2. Task 2, not checked yet. Add a test named finds the completed todo. Find the todos that have a checked checkbox with filter({ has: ... }), and check that there is exactly one. Check that its text is Walk the dog.

  3. Task 3, not checked yet. Add a test named checks the first and the last todo. Use first() and last() on the todo rows. The first row has the text Buy milk. The last row has the text Walk the dog.

Task 1 of 3

Press Run tests and read the error. Fix the test so that it deletes only the todo Write a test. Find the row with filter({ hasText: ... }), and then find the button inside the row. At the end, check that Write a test is hidden.

All tasks

example.spec.js

Ctrl+Enter runs your tests. Ctrl+Shift+Enter checks it. Press Esc, then Tab, to leave the editor.

Practice browser

The sample app is ready

The results of your tests show here. Press Run tests to watch them run in the practice browser.