Assertions in Playwright
By Syncfusion®, Inc. · Last updated
expect() and web-first assertions that retry, why assertions must be awaited, locator assertions for state, content, attributes and count, page assertions, generic and asymmetric matchers, .not, custom messages and timeouts, soft assertions, expect.poll and toPass, and custom matchers with expect.configure and expect.extend.
Assertions
- An assertion is a check: "this must be true".
- Actions make a test do things. Assertions make it prove things.
- Playwright's assertions wait and retry, so they work on pages that change.
- In this lesson, you will learn every kind of assertion, and when to use each.
Simple lesson: assertions are the checks that decide if a test passes.
A Real-World Example
- Priya adds two products to the cart, then opens the cart.
- She checks the cart count, the items, the total and the Checkout button.
What You Will Learn
expect(), and why Playwright's assertions retry.- Assertions for elements: state, text, values, attributes and count.
- Assertions for the page: URL and title.
- Generic matchers for plain values.
- Options:
.not, custom messages and timeouts. - Soft assertions,
expect.poll()andtoPass(). - Custom assertions (advanced).
Assertion Fundamentals
expect() and Web-First Assertions
expect(value)starts an assertion. A matcher such astoHaveText()finishes it.- When you pass a locator or the page, you get a web-first assertion.
- Web-first = it is made for web pages that change. It keeps checking until it passes.
Auto-Retrying Assertions
- A web-first assertion checks, waits a little, and checks again.
- It passes as soon as the check is true. It fails only when the time runs out: 5 seconds by default.
Animation: toHaveText checks the status again and again. The first checks see "Placing your order", then the text changes to "Order confirmed" and the next check passes
0 s timeout 5 s
Placing your order… Order confirmed ✓ passed at 1.2 s toHaveText('Order confirmed') Red: checked, not yet true. Green: true, so the assertion passes and the test moves on.
A web-first assertion checks until the page catches up. It does not wait the full 5 seconds when it can pass sooner.
Why Assertions Must Be Awaited
- Web-first assertions return a promise. Without
await, the test does not wait for the check. - The test can end before the check runs. Then a false check does not fail the test.
- This test checks for text that never appears. Run it: it passes, which is wrong.
- Add
awaitin front ofexpectand run it again. Now it fails, as it should. - The VS Code extension and TypeScript lint rules can warn you about missing
await.
Avoiding Manual Assertions
isVisible()andtextContent()read the page once, right now. They do not wait.expect(await locator.isVisible()).toBe(true)fails if the element appears a moment later.await expect(locator).toBeVisible()waits for it. Always prefer the web-first version.
Simple lesson: await expect(locator) waits and retries. Reading once with isVisible() or count() does not.
Locator Assertions
State
| Matcher | Passes when the element is |
|---|---|
toBeVisible() | visible |
toBeHidden() | hidden or gone |
toBeEnabled() / toBeDisabled() | enabled / disabled |
toBeChecked() | ticked ({ checked: false } for unticked) |
toBeEditable() | a field you can type in |
toBeFocused() | the one with the cursor |
toBeAttached() | in the page, visible or not |
toBeEmpty() | an empty field or an element with no text |
Content
| Matcher | Checks |
|---|---|
toHaveText('Yoga Mat') | the whole text (spaces trimmed). A regex or a list also work |
toContainText('Yoga') | that the text contains this part |
toHaveValue('Kochi') | the value of an input |
toHaveValues(['red', 'blue']) | the chosen options of a multi-select |
Attributes, Count and Properties
| Matcher | Checks |
|---|---|
toHaveAttribute('href', '/cart') | an attribute's value |
toHaveClass(/selected/) / toContainClass('selected') | the class attribute |
toHaveCSS('display', 'flex') | a computed style |
toHaveId('search') | the id |
toHaveCount(6) | how many elements the locator finds |
toHaveJSProperty('checked', true) | a JavaScript property of the element |
Simple lesson: choose the matcher that says exactly what the user should see.
Page Assertions
await expect(page).toHaveURL('/account')checks the address. WithbaseURL, a short path works.await expect(page).toHaveTitle(/Yoga Mat/)checks the browser tab title.- Both accept a string (exact match) or a regular expression (pattern).
With baseURL in the config, toHaveURL('/cart') compares against the full address, https://practice.devtutorials.ai/cart. A regular expression such as /\/cart$/ checks just the end.
Simple lesson: toHaveURL() and toHaveTitle() check the page itself.
Generic Assertions
Generic Matchers
- For plain values (numbers, strings, objects), use generic matchers. They check once and do not retry.
| Matcher | Example |
|---|---|
toBe() | expect(status).toBe(200) (same value) |
toEqual() | expect(cart).toEqual([{ id: 2, qty: 1 }]) (same content) |
toContain() | expect(names).toContain('Yoga Mat') |
toMatchObject() | expect(user).toMatchObject({ name: 'Asha' }) (these keys match) |
Asymmetric Matchers
- Asymmetric matchers describe a value loosely, inside
toEqual()ortoMatchObject().
| Matcher | Matches |
|---|---|
expect.any(Number) | any number |
expect.objectContaining({ id: 2 }) | an object with at least these keys |
expect.stringContaining('@') | a string containing @ |
expect.arrayContaining([2, 3]) | an array holding at least these items |
Web-First vs Generic Assertions
- Web-first (locator or page): asynchronous, retries, needs
await. - Generic (plain value): checks once. Get the value first, then check it.
Simple lesson: web-first matchers for the page, generic matchers for values you already have.
Assertion Options
Negating with .not
.notflips any matcher:await expect(button).not.toBeVisible().- Web-first
.notassertions also wait: until the element is not visible.
Custom Assertion Messages
- Pass a message as the second argument. It appears at the top of the error:
await expect(page.getByTestId('cart-count'), 'cart should be empty after checkout').toHaveText('0');
Per-Assertion timeout
toHaveText('Ready', { timeout: 10_000 })waits up to 10 seconds for this one check.- To change the default for all assertions, set
expect: { timeout }in the config.
Raise the timeout only for checks that are slow on purpose, like a report that takes time to build. A bigger timeout everywhere hides slow pages.
Simple lesson: .not flips a check, a message explains it, a timeout gives it more time.
Soft Assertions
expect.soft()
- A normal assertion stops the test at the first failure.
expect.soft()records the failure and keeps going. The test still fails at the end.- Use it to see several problems in one run, for example on a page with many fields.
How Soft Assertions Affect Test Results
- Both soft failures appear in the report, and the test is marked failed.
- After a soft failure, later steps still run. Make sure they still make sense.
Simple lesson: soft assertions collect failures instead of stopping at the first one.
Polling and Retrying
expect.poll()
expect.poll(fn)turns any value into a retrying check. It callsfnagain and again until the matcher passes.- Use it for values that do not live on the page, such as data read with
page.evaluate()or an API.
expect().toPass()
expect(async () => { ... }).toPass()retries a whole block until every check inside passes.- Give it a
timeout. Its default timeout is 0, which means it would keep trying until the test times out.
Polling Intervals
- Both accept
intervals, the waits between tries:{ intervals: [500, 1000, 2000] }.
- The sync page fails on the first click.
toPass()runs the block again, the second click works, and the test passes.
Simple lesson: expect.poll() retries a value, toPass() retries a block.
Custom Assertions (Advanced)
expect.configure()
expect.configure({ timeout: 10_000 })makes a newexpectwith other defaults.expect.configure({ soft: true })makes every assertion from it soft.
expect.extend() for Custom Matchers
expect.extend()adds your own matchers. Each one returnspassand amessage:
Merging Expects
- Big projects keep matchers in several files.
mergeExpects(expectA, expectB)from@playwright/testcombines them into oneexpect.
Custom matchers are worth it when the same check appears in many tests. For a one-off check, a normal assertion is clearer.
Simple lesson: configure() changes defaults, extend() adds your own matchers.
Try It in Your Project
- These steps take the lesson from the lab to your own app.
- Make sure every test ends with at least one
await expect(...)that would fail if the feature broke. - Turn one manual check like
expect(await x.textContent())into a web-first assertion. - Use
expect.soft()in a test that checks many fields of one page.
Simple lesson: use one idea from this lesson on your own app this week.
TL;DR
await expect(locator or page)gives web-first assertions that retry for 5 seconds.- Always
awaitweb-first assertions. - Prefer
toBeVisible(),toHaveText(),toHaveCount()over reading values once. - Use generic matchers (
toBe,toEqual) for plain values. .not, messages andtimeoutadjust a check.expect.soft()keeps going after a failure.expect.poll()andtoPass()retry values and blocks.
Common mistakes
| Mistake | What happens | Fix |
|---|---|---|
No await before expect(locator) | The test can pass when the check is false | Always await web-first assertions |
expect(await el.isVisible()).toBe(true) | Fails if the element appears a moment later | await expect(el).toBeVisible() |
toHaveText() when you mean "contains" | Fails because of extra words | Use toContainText() |
toPass() without a timeout | It retries until the whole test times out | Pass { timeout } |
| Soft assertions before an action that needs them | Later steps fail in confusing ways | Use a normal assertion for checks the next steps depend on |
Check Yourself
Exercises
What to Remember
- Web-first assertions retry. Always
awaitthem. - Pick the matcher that matches what the user sees.
- Generic matchers check plain values once.
expect.soft()collects failures.expect.poll()andtoPass()retry.- Custom matchers are for checks you repeat often.
Next: Organizing Tests