Playwright Assertions

Use Playwright Assertions efficiently to test websites on real devices by seamlessly integration with Real Device Cloud

Get Started free
Playwright Assertions
Home Guide Understanding Playwright Assertions [2026]

Understanding Playwright Assertions [2026]

When a Playwright test runs, I always wonder—did the app really behave the way I expected, or did the script just move on? That doubt is exactly where Playwright assertions come in.

I use assertions to pause the test and demand proof. Is the element visible? Did the value change? Did the action actually succeed? Each check forces the application to reveal whether it’s behaving correctly.

That moment of validation decides everything. If the expectation holds, the test passes. If not, it fails instantly. With assertions in place, I’m no longer assuming correctness—I’m verifying it, step by step.

What are Playwright Assertions?

Playwright Assertions are a set of built-in functions which includes expect() functionprovided by the Playwright testing framework to validate the behavior and state of a web application during automated tests.

Playwright Assertions are used to verify whether specific conditions are met, such as checking if an element exists, contains certain text, or has a particular state. These assertions are essential for confirming that the application behaves as expected during end-to-end testing.

Playwright provides diverse assertion types:

  • Element States: Check the visibility, availability, and interactivity of UI elements.
  • Content Validation: Ensure elements display the correct text, values, or match specific patterns.
  • Page Properties: Assertions can confirm page details like URLs, titles, or cookie presence.
  • Network Interactions: Verify the outcomes of network requests and responses to ensure proper data loading and form submissions.

Playwright Expect() Function

Playwright expect is the built-in assertion API used to validate how an application behaves during a test.

It allows tests to wait for conditions to be true and then verify outcomes such as element visibility, text content, attributes, URLs, or network states. Instead of checking values instantly, expect automatically retries until the condition passes or the timeout is reached, which helps handle dynamic UI behavior.

In simple terms, expect is how Playwright confirms whether the app under test matches the expected state and decides if a test should pass or fail.

For Example:

test("Validate BrowserStack demo application site title", async ({page}) => {

   await page.goto("https://bstackdemo.com/")

   await expect(page).toHaveTitle("StackDemo")

})

In the above code example, using BrowserStack’s Demo application in the playwright test to validate the Site Title.

Code Breakdown:

Below code will access the BrowserStack’s Demo Application

await page.goto("https://bstackdemo.com/")

Below code Asserts the BrowserStack’s Website Title.

await expect(page).toHaveTitle("StackDemo")

Here, Playwright waits until the element becomes visible before marking the assertion successful.

Different Types of Playwright Assertions

Assertions in Playwright is broadly classified into below types

Types of Playwright Assertions:

  • Auto-retrying Assertions
  • Non-retrying Assertions
  • Negating Matchers
  • Soft Assertions

Let’s look into each assertion type.

Auto-retrying Assertion

Auto-retrying assertions in Playwright are a vital functionality that significantly boosts the reliability and stability of test scripts by repeatedly attempting to verify assertions until they either succeed or a predefined timeout is reached.

Auto-retrying feature is especially useful in scenarios where web elements might not immediately meet expected conditions due to network delays, dynamic content loading, or client-side scripting operations.

Let’s write a test which asserts a text from BrowserStack’s Demo Application Website.

In this test, performing below steps:

  1. Access BrowserStack’s Demo application
  2. Validate the default Number of Products found
test("Validate BrowserStack default products found count", async ({page}) => {

 await page.goto("https://bstackdemo.com/")

 const productLocator = await page.locator(".products-found span")

 await expect(productLocator).toHaveText('25 Product(s) found')

})

Code Breakdown

Visit the BrowserStack’s Demo application

await page.goto("https://bstackdemo.com/")

Get the locator reference

const productLocator = await page.locator(".products-found span")

Assert the Number of Products found from Web page against expected value

await expect(productLocator).toHaveText('25 Product(s) found')

Observe that in the above assertion you pass the locator reference and then you are using matcher toHaveText which accepts expected value as string.

If you run the above test, the test will fail in assertion with auto retry timeout.

Auto Retrying Assertion in Playwright

Note that the test is timed out after retrying for 5000 milliseconds, this is because Playwright has a default timeout of 5000 milliseconds.

You can overwrite the assertion timeout at command level like below.

await expect(productLocator).toHaveText('25 Product(s) found',{timeout: 2000})

Below is the full list of Auto-Retrying assertions.

AssertionDescription
await expect(locator).toBeAttached()Element is attached
await expect(locator).toBeChecked()Checkbox is checked
await expect(locator).toBeDisabled()Element is disabled
await expect(locator).toBeEditable()Element is editable
await expect(locator).toBeEmpty()Container is empty
await expect(locator).toBeEnabled()Element is enabled
await expect(locator).toBeFocused()Element is focused
await expect(locator).toBeHidden()Element is not visible
await expect(locator).toBeInViewport()Element intersects viewport
await expect(locator).toBeVisible()Element is visible
await expect(locator).toContainText()Element contains text
await expect(locator).toHaveAttribute()Element has a DOM attribute
await expect(locator).toHaveClass()Element has a class property
await expect(locator).toHaveCount()List has exact number of children
await expect(locator).toHaveCSS()Element has CSS property
await expect(locator).toHaveId()Element has an ID
await expect(locator).toHaveJSProperty()Element has a JavaScript property
await expect(locator).toHaveScreenshot()Element has a screenshot
await expect(locator).toHaveText()Element matches text
await expect(locator).toHaveValue()Input has a value
await expect(locator).toHaveValues()Select has options selected
await expect(page).toHaveScreenshot()Page has a screenshot
await expect(page).toHaveTitle()Page has a title
await expect(page).toHaveURL()Page has a URL
await expect(response).toBeOK()Response has an OK status

Non-retrying Assertions

Non-retrying Assertions are only useful when web pages load data asynchronously. Test assertion will fail without any timeout or retrying when using the Non-retrying Assertion and below is an example test for the same.

test("example for non-retrying assertion", async ({page}) => {

 await page.goto("https://bstackdemo.com/")

 const productLocator = await page.locator(".products-found span")

 const productSearchText = await productLocator.innerText()

 await expect(productSearchText).toBe('25 Product(s) found')

})

When you run this test, it will fail with assertion without retrying.

Non Retrying Assertions in PlaywrightBelow is the full list of Non-retrying assertion matchers

AssertionDescription
expect(value).toBe()Value is the same
expect(value).toBeCloseTo()Number is approximately equal
expect(value).toBeDefined()Value is not undefined
expect(value).toBeFalsy()Value is falsy, e.g. false, 0, null, etc.
expect(value).toBeGreaterThan()Number is more than
expect(value).toBeGreaterThanOrEqual()Number is more than or equal
expect(value).toBeInstanceOf()Object is an instance of a class
expect(value).toBeLessThan()Number is less than
expect(value).toBeLessThanOrEqual()Number is less than or equal
expect(value).toBeNaN()Value is NaN
expect(value).toBeNull()Value is null
expect(value).toBeTruthy()Value is truthy, i.e. not false, 0, null, etc.
expect(value).toBeUndefined()Value is undefined
expect(value).toContain()String contains a substring
expect(value).toContain()Array or set contains an element
expect(value).toContainEqual()Array or set contains a similar element
expect(value).toEqual()Value is similar – deep equality and pattern matching
expect(value).toHaveLength()Array or string has length
expect(value).toHaveProperty()Object has a property
expect(value).toMatch()String matches a regular expression
expect(value).toMatchObject()Object contains specified properties
expect(value).toStrictEqual()Value is similar, including property types
expect(value).toThrow()Function throws an error
expect(value).any()Matches any instance of a class/primitive
expect(value).anything()Matches anything
expect(value).arrayContaining()Array contains specific elements
expect(value).closeTo()Number is approximately equal
expect(value).objectContaining()Object contains specific properties
expect(value).stringContaining()String contains a substring
expect(value).stringMatching()String matches a regular expression

Negating Matchers

Negating Matchers are used when we want to check that a certain condition does not hold true. It essentially reverses the condition you’re checking for, enabling you to assert the absence of a condition or element. Negating Matchers are especially helpful for ensuring that a web page or application is free from errors, incorrect states, or unwanted elements.

Below is the example test. The test will assert for filter count not matching the value 3

test("example for negating matcher", async ({page}) => {

 await page.goto("https://bstackdemo.com/")

 const filter = await page.locator(".filters .filters-available-size")

 const filterCount = await filter.count()

 await expect(filterCount).not.toEqual(3)

})

Soft Assertions

By default Assertion will abort the test as soon as the expected result is not matched with the actual result. There are cases where we have to check multiple assertions and at the end of the test throw the assertion error.

Soft assertion is good for cases where we want to assert multiple cases and then fail the test at the end.

Below is the example test. The test has two assertions and both will execute and fail.

test("example for soft assertion", async ({page}) => {

 await page.goto("https://bstackdemo.com/")

 const filter = await page.locator(".filters .filters-available-size")

 const filterCount = await filter.count()

 await expect.soft(filterCount).not.toEqual(4)

 await expect.soft(page).toHaveTitle("StackDemo!")

})

If you don’t use soft assertion, then the test will fail at the first assertion check and doesn’t continue.

Running the above test will show two assertion errors.

Soft Assertions in Playwright

Playwright Custom Matchers with Examples

Playwright provides users to create their own Custom Matchers, which can be chained with expect() for Assertions

To create Custom Matchers, we need to extend the expect() function from Playwright and then add our Custom Matchers within the extend function like below

import { expect as baseExpect } from '@playwright/test';
import type { Page, Locator } from '@playwright/test';


export { test } from '@playwright/test';

export const expect = baseExpect.extend({
 async toHavePrice(locator: Locator, expected: number, options?: { timeout?: number }) {
   const assertionName = 'toHavePrice';
   let pass: boolean;
   let matcherResult: any;
   try {
     await baseExpect(locator).toHaveText(String(expected), options);
     pass = true;
   } catch (e: any) {
     matcherResult = e.matcherResult;
     pass = false;
   }

   const message = pass
     ? () => this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) +
         '\n\n' +
         `Locator: ${locator}\n` +
         `Expected: ${this.isNot ? 'not' : ''}${this.utils.printExpected(expected)}\n` +
         (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : '')
     : () =>  this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) +
         '\n\n' +
         `Locator: ${locator}\n` +
         `Expected: ${this.utils.printExpected(expected)}\n` +
         (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : '');

   return {
     message,
     pass,
     name: assertionName,
     expected,
     actual: matcherResult?.actual,
   };
 },
});

We can create a new file called fixture.ts and add the above code in that file. Once the code is added we can then write a test like below.

import { test, expect } from '../fixtures/fixtures';

test("example for custom matcher", async ({page}) => {
   await page.goto("https://bstackdemo.com/")
   const phone = await page.locator(".shelf-item__title", {hasText:"iPhone 12 Mini", })
   const phoneParent = await phone.locator("..")
   const phonePrice = phoneParent.locator(".shelf-item__price .val b")
   await expect(phonePrice).toHavePrice("699")
 })

Handling Dynamic UI States with Assertions

Modern web applications rarely stay still. Elements load asynchronously, UI states change based on user actions, and content updates after network calls. Assertions in Playwright are designed to handle this dynamism without relying on manual waits or fragile timing logic.

Playwright’s expect() assertions automatically wait for the expected condition to be met before failing. This makes them effective for validating UI states that appear, disappear, or update over time.

Common dynamic scenarios handled with assertions include:

  • Elements becoming visible after an API response
  • Buttons enabling or disabling based on form input
  • Text or attributes updating after user interaction
  • Loaders or spinners disappearing before content renders

For example:

await expect(page.locator('.loader')).toBeHidden();

await expect(page.locator('#status')).toHaveText('Completed');

Here, the assertions wait until the UI reaches the expected state instead of checking immediately. This approach reduces flakiness and keeps tests aligned with how real users experience the application.

Are your Playwright assertions really reliable?

Assertions can pass locally and fail for users. Validate them on real devices with BrowserStack.
Playwright Banner

Assertions for Network Requests and Responses

UI validation alone is often not enough. Many user-visible changes depend on network activity, and Playwright allows assertions to validate those interactions directly.

Assertions can be combined with network interception to verify:

  • API response status codes
  • Request payloads sent by the application
  • Response bodies affecting UI behavior
  • Completion of critical backend operations

A common pattern involves waiting for a response and then asserting on its outcome:

const response = await page.waitForResponse(

  resp => resp.url().includes('/api/order') && resp.status() === 200

);

await expect(response.ok()).toBeTruthy();

By asserting on network behavior, tests confirm not just that the UI changed, but that it changed for the right reason. This strengthens test coverage for data-driven workflows and reduces blind spots where UI-only assertions might pass despite backend issues.

Common Mistakes with Playwright Assertions

Even with powerful defaults, assertions can introduce instability when used incorrectly. Many flaky tests trace back to assertion misuse rather than application defects.

Frequent mistakes include:

  • Using manual waits instead of relying on auto-retrying assertions
  • Asserting too early on elements that depend on async rendering
  • Overusing hard assertions where soft assertions are more appropriate
  • Validating implementation details instead of user-visible behavior

For example, checking text immediately after navigation:

// Fragile approach

await page.click('#submit');

expect(await page.textContent('#msg')).toBe('Success');

A more stable approach:

await page.click('#submit');

await expect(page.locator('#msg')).toHaveText('Success');

This allows Playwright to wait until the UI is truly ready. Clear, user-focused assertions combined with built-in retries result in tests that fail for real issues, not timing quirks or transient states

Best Practices to use Playwright Expect()

The expect() function is a cornerstone of assertions in Playwright, offering the ability to assert on elements, network responses, and other test conditions.

Below are the few Best Practices for Playwright Expect() :

1. Leverage Built-In Retry Mechanism

Understand the automatic retry feature in Playwright’s expect():

  •  Utilize this feature for dynamic content where elements may appear or change state over time.
  •  Avoid excessive reliance which might conceal performance issues or complex race conditions.

2. Employ Semantic Locators

Opt for Playwright’s advanced selectors like role and text selectors to improve both the readability and maintainability of your tests:

await expect(page.locator('text=Sign In')).toBeVisible();
await expect(page.locator('role=button', {name: 'Send'})).toBeEnabled();

These selectors enhance the semantic clarity and accessibility focus of your tests.

3. Integrate Actions with Verification

Simultaneously perform user actions and verify outcomes to mimic real user flows:

await page.click('button#save');
await expect(page.locator('text=Saved successfully')).toBeVisible();

This method validates user interactions in real-time.

4. Customize Timeouts

Adjust timeouts in expect() when the default settings do not align with specific test requirements:

  • Modify the timeout parameter to suit specific waiting needs without overextending test durations.

5. Assert Non-Presence

Assert the non-presence of elements or messages, particularly useful in validating error handling and user feedback:

await expect(page.locator('text=Error')).not.toBeVisible();

6. Utilize State-Specific Assertions

Make full use of Playwright’s state-specific assertions to directly assess the user interface:

await expect(page.locator('input[type="checkbox"]')).toBeChecked();

7. Assert Network Interactions

Capture and assert network responses to ensure backend integration is functioning as expected:

const [response] = await Promise.all([
  page.waitForResponse(resp => resp.url().includes('/api/submit') && resp.status() === 200),
  page.click('button#submit')
]);
await expect(response).toBeOK();

8. Validate Accessibility Features

Assert on accessibility features to ensure your application is accessible:

await expect(page.locator(‘role=button’, {name: ‘Confirm’})).toHaveAttribute(‘aria-live’, ‘polite’);

9. Enhance Assertion Failures

Incorporate diagnostic tools like screenshots or logs to investigate why assertions fail:

test.fail(async ({ page }) => {
  await page.screenshot({ path: 'error-snapshot.png' });
});

Why run Playwright Tests on Real Device Cloud?

Here’s why you should run Playwright tests on real browsers & devices using a cloud-based testing platform like BrowserStack Automate:

  • Accurate UI behavior validation: Assertions related to visibility, text rendering, focus states, and animations can behave differently across real browsers and devices. Running them on BrowserStack Automate ensures these checks hold true in real environments.
  • Reduced false positives in test results: Assertions may pass locally but fail for users due to OS-level differences, browser engines, or device-specific timing. Real device testing helps catch these gaps early.
  • Consistent assertion timing under real conditions: Real devices expose performance variations that impact assertion retries and timeouts. This makes assertions more reliable and aligned with real-world load and rendering behavior.
  • Scalable validation across environments: BrowserStack Automate enables running assertion-heavy Playwright tests in parallel across multiple browser–OS combinations, ensuring broader coverage without increasing execution time.
  • Better debugging when assertions fail: Screenshots, videos, and logs from real device runs provide clear context for why an assertion failed, making root cause analysis faster and more accurate.

Talk to an Expert

Conclusion

The expect() function of Playwright is a critical asset for automation testers, delivering powerful and adaptable assertions crucial for verifying the integrity and functionality of web applications. Its inherent retry capability, coupled with its proficiency in handling complex assertions on elements, network interactions, and accessibility attributes, is particularly effective for modern web applications characterized by dynamic and asynchronous behavior.

Utilizing expect() adeptly within Playwright tests enables testers to confirm that their applications not only adhere to specific requirements but also deliver a stable user experience across diverse scenarios. This function’s ability to precisely adjust assertion conditions, such as timeouts, and to assess a broad spectrum of criteria—from the visibility of elements to the correctness of API responses—increases the precision and dependability of tests.

Running your Playwright Tests on BrowserStack’s Real Device Cloud helps you get access to 3500+ real device and browser combinations for maximum test coverage. It allows you to test under real user conditions, which will help identify the bottlenecks in the real user experience and rectify them. BrowserStack’s Automate allows you to run Playwright tests on the cloud and across browsers and devices simultaneously by leveraging parallel testing for faster testing with a vast coverage.

Try BrowserStack Automate

Tags
Automated Testing Automation Frameworks Playwright
Are your Playwright assertions really reliable?
Assertions can pass locally and fail for users. Validate them on real devices with BrowserStack.

Get answers on our Discord Community

Join our Discord community to connect with others! Get your questions answered and stay informed.

Join Discord Community
Discord