Stop writing waitFors — use web-first assertions
Learn how to add assertions and avoid all these "waitFor"s.
Thanks to auto-waiting, your recorded test cases already cover a lot of functionality and critical user flows. To test the details and check that the data is correct, you need to add assertions.
Generic vs async assertions (web-first assertions)
Playwright Test provides an assertion library out of the box.
import { test, expect } from "@playwright/test";
expect provides generic and (!) async assertions.
Generic matchers are synchronous and work well for simple comparisons such as comparing two numbers.
// a synchronous generic assertion
expect(number).toBe(2);
To test web functionality, though, async assertions are the handier alternative.
Playwright's asynchronous web-first assertions are tailored to the web. They're based on the same auto-waiting principles you already know and wait / retry until a condition is met or the timeout is reached.
// an asynchronous web-first assertion
// this assertion waits / retries until the located element becomes visible
await expect(page.getByText("welcome")).toBeVisible();
If you're testing websites, web-first assertions are more convenient to write and build on PWT's auto-waiting.
Tests a condition at a single moment in time. This approach introduces flakiness because isVisible() isn't waiting for anything.
expect(await page.getByText("welcome").isVisible()).toBe(true);Waits for a condition to become truthy over time.
await expect(page.getByText("welcome")).toBeVisible();Web-first assertions are async and you must await them. A missing
await doesn't fail loudly: it returns a Promise, nothing waits for the
assertion to pass or fail, and the test passes silently.
Common web-first matchers
expect ships with a long list of matchers. The most common ones fall into three groups.
toBeVisibletoBeHiddentoBeAttachedin the DOM, even if not visibletoBeEnabledtoBeDisabledtoBeCheckedtoBeFocusedtoBeEmptytoBeInViewporttoHaveTextexact matchtoContainTextsubstring matchtoHaveValueform fieldstoHaveAttribute(name, value)toHaveClasstoHaveCountnumber of matched elementstoHaveScreenshottoHaveURLtoHaveTitletoHaveScreenshotA handful of these matchers (toHaveText, toContainText, toHaveCount) work against locators that match multiple elements. This lets you check multiple elements with a single assertion.
Assert on the whole product grid at onceSelect this container via test id: multi-element-assertions
The stage below renders three product cards. Instead of writing one assertion per card, confirm two things in a single line each:
- The grid renders exactly three products.
- The product names appear in this order:
Product 1,Product 2,Product 3.
Products
Product 1
Product 2
Product 3
▸ Show the assertions
toHaveCount checks how many elements a locator resolved to. toHaveText
accepts an array and compares it element-by-element against the matched
elements, checking content and order.
const container = page.getByTestId("multi-element-assertions");
const productsContainer = container.getByRole("region", { name: "Products" });
const products = productsContainer.getByRole("listitem");
await expect(products).toHaveCount(3);
await expect(products).toHaveText([/Product 1/, /Product 2/, /Product 3/]);
If the cards change their order, the array assertion fails with a clear diff against the expected order.
There are a few more things to know about assertions.
Configurable timeouts
Web-first assertions accept a timeout option if things take longer.
await expect(page.getByText("welcome")).toBeVisible({ timeout: 10_000 });
The default timeout is 5s and can be changed per project in your Playwright
config under expect.timeout.
Soft assertions
Soft assertions (expect.soft) are a handy way to fail a test case but still run the following actions.
test("has title", async ({ page }) => {
await page.goto("https://playwright.dev/");
// If this assertion fails the test case will be marked as failed
await expect.soft(page.getByTestId("status")).toHaveText("Success");
// But all the following actions will still be executed and tested
// ...
});
Soft assertions are particularly helpful in longer tests. A failed soft assertion marks the test as failed, but the test keeps running.

Assertions can be negated
Assertions also provide a quick way to flip their meaning.
await expect(locator).toBeVisible();
await expect(locator).not.toBeVisible();
Custom assertion messages
To make your assertions more readable in your test reports, you can also define a custom message.
await expect
.soft(page, "should have an awesome title")
.toHaveTitle("wrong title");

❗ Auto-waiting is the most important core principle in Playwright Test
With the built-in auto-waiting mechanisms, you rarely have to implement manual waitFor statements. If your tests consist of actions and toBe assertions, you're fine!
// click() waits for the element to be actionable
await locator.click();
// wait for the assertion to become truthy or time out
await expect(anotherLocator).toBeVisible();
Depending on the site you want to test, you might want to tweak the timeout configuration. These are Playwright's default timeouts:
- Globally
config.timeout- Per project
project.timeout- Per call
test.setTimeout(120_000)
- Globally
config.expect.timeout- Per project
project.expect.timeout- Per call
expect(locator).toBeVisible({ timeout: 10_000 })
- Globally
config.use.actionTimeout- Per project
project.use.actionTimeout- Per call
locator.click({ timeout: 10_000 })
- Globally
config.use.navigationTimeout- Per project
project.use.navigationTimeout- Per call
page.goto('/', { timeout: 30_000 })
Practice your assertions
Assert the login worked
- Edit your previously recorded
Logintest. - After the form submission, add a web-first assertion that confirms the logged-in user's name is visible.
- After logging out, assert that the user name isn't visible.
- Re-run the test and make sure it still passes.
Lock down the homepage hero grid
- Navigate to
/. - Locate the hero products and assert there are exactly three.
- Ensure that all hero products are snowboards.
Search for products
- Create a new test.
- Search for "Hydrogen".
- Create an add-to-cart flow from there.
Double-check the cart handling
- Edit an existing test.
- Validate that the cart calculations work.
- Increase the quantity of an item.
- Check that the sum is correct.