Stop writing waitFors β use web-first assertions
Learn how to add assertions and avoid all these "waitFor"s.
Thanks to auto-waiting mechanisms, your recorded test cases test many web functionality and critical user flows already. To nail down implementation details and test for data correctness, 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 are valuable for simple comparisons such as comparing two numbers.
// a synchronous generic assertion
expect(number).toBe(2);
To test web functionality, though, async assertions come as a handy alternative.
Playwright's asynchronous web-first assertions are tailored to the web. They're based on the same auto-waiting principles you already know about and wait / retry until a condition is met or the time out 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 leverage PWT's core functionality.
import { test, expect } from "@playwright/test";
test("has title", async ({ page }) => {
await page.goto("https://playwright.dev/");
// π
// test a condition at a single moment in time
// this approach introduces flakiness
expect(await page.getByText("welcome").isVisible()).toBe(true);
// π
// wait for a condition to become truthy over time
await expect(page.getByText("welcome")).toBeVisible();
});
Web-first assertions are async β you must await them. A missing await
doesn't fail loudly: it returns a Promise (which is truthy), the assertion
never actually runs, and the test passes silently.
Common web-first matchers
expect ships with a long list of matchers. The most common ones group into three buckets.
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, no .first() / .nth() / .last() loop required.
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
set β 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/]);
Re-shuffle the cards and the array assertion fails with a clear diff against the expected order.
There are some core things to know about assertions.
Configurable timeouts
Web-first assertions have a timeout config option if things take longer.
await expect(page.getByText("welcome")).toBeVisible({ timeout: 10_000 });
The default timeout is 5s and can be changed on a project basis in your
Playwright config under expect.timeout.
Soft assertions
Soft assertions (expect.soft) are a handy way to fail your test case but still try to 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 assertion are particularly helpful when running longer tests. A soft assertion will lead to test failure but the test still continues running.

Assertions can be negated
Assertions also provide a quick way to flip around 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.
// click() waits for the element to be actionable
// click() waits for a triggered navigation to complete
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 for the mentioned auto-waiting concepts.
- 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 login 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 here
Double check the cart handling
- Edit an existing test
- Validate that the cart calculations work
- Increase the quantity of an item
- Check the correct sum