Skip to content

Instantly share code, notes, and snippets.

@ianjennings
Created September 19, 2025 13:23
Show Gist options
  • Select an option

  • Save ianjennings/04d7d7f19ab0add4b6b9438a2dd6e8ff to your computer and use it in GitHub Desktop.

Select an option

Save ianjennings/04d7d7f19ab0add4b6b9438a2dd6e8ff to your computer and use it in GitHub Desktop.

Testing Todo Creation with Playwright

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.

Loading TodoMVC

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.

TodoMVC landing state

await expect(page.getByPlaceholder('What needs to be done?')).toBeVisible();

The visibility assertion confirms the entry field is interactable before attempting to type.

Adding the First Todo

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.

First todo rendered

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.

Creating a Second Todo

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.

Two todos displayed

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.

Verifying Persisted State

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.

Local storage validated state

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment