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
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 withfilter({ hasText: ... }), and then find the button inside the row. At the end, check thatWrite a testis hidden.Task 2, not checked yet. Add a test named
finds the completed todo. Find the todos that have a checked checkbox withfilter({ has: ... }), and check that there is exactly one. Check that its text isWalk the dog.Task 3, not checked yet. Add a test named
checks the first and the last todo. Usefirst()andlast()on the todo rows. The first row has the textBuy milk. The last row has the textWalk 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.