This tutorial demonstrates how to test a todo app using TodoMVC as an example. Each step shows how to drive the UI with Playwright and verify that new todos render and persist.
To begin validating todo creation, direct the Playwright page to the hosted demo and define the sample todo data that the flow will reuse.
const TODO_ITEMS = [
'buy some cheese',
'feed the cat',
'book a doctors appointment'
];
await page.goto('https://demo.playwright.dev/todomvc');This navigation loads an empty list with a single input inviting new tasks.
await expect(page.getByPlaceholder('What needs to be done?')).toBeVisible();The visibility assertion confirms the entry field is interactable before attempting to type.
To target the input field for a new task, create a locator that matches the placeholder text and submit the first todo from the shared data.
const newTodo = page.getByPlaceholder('What needs to be done?');
await newTodo.fill(TODO_ITEMS[0]);
await newTodo.press('Enter');Playwright types "buy some cheese" into the field and submits it, leaving a single row in the list.
await expect(page.getByTestId('todo-title')).toHaveText([
TODO_ITEMS[0]
]);The expectation guarantees exactly one todo is displayed and that its text matches the value that was typed.
To extend coverage to multiple entries, reuse the same locator and provide the next item from the array so the UI can render two todos in sequence.
await newTodo.fill(TODO_ITEMS[1]);
await newTodo.press('Enter');The list now shows both "buy some cheese" and "feed the cat" stacked top to bottom.
await expect(page.getByTestId('todo-title')).toHaveText([
TODO_ITEMS[0],
TODO_ITEMS[1],
]);This check verifies ordering and content, ensuring the second submission did not overwrite the first entry.
To ensure the UI changes are backed by storage, call the helper that waits until local storage reports the expected number of todos.
await checkNumberOfTodosInLocalStorage(page, 2);Once the helper resolves, the DevTools timeline shows Playwright waiting for local storage to report two items that match the rendered rows.
async function checkNumberOfTodosInLocalStorage(page, expected) {
return page.waitForFunction(e => {
return JSON.parse(localStorage['react-todos']).length === e;
}, expected);
}This validation guarantees that the todo app persists both entries in browser storage, catching regressions that might lose data after refresh.



