← Back to Index

Playwright Assertions

Learn how Playwright verifies application behavior using web-first assertions, auto-retry, state validation, text verification, URL checks, form assertions and professional validation patterns.

Beginner → Web-First Assertions → Auto-Retry → Robust Validation

1. What Is an Assertion?

An assertion verifies that the application is in the state expected by the test. It answers questions such as:

  • Is the Dashboard visible?
  • Does the page contain the expected message?
  • Is the Login button enabled?
  • Is the checkbox checked?
  • Does the URL contain the expected path?
  • Does the input contain the expected value?
  • Are the expected number of products displayed?
Action
→
Application Changes
→
Assertion
→
Auto-Retry
→
Pass / Fail
Important: Playwright's web-first assertions are designed for dynamic web applications. Instead of checking a value once and immediately failing, they can wait and retry until the expected condition is satisfied or the assertion timeout is reached.

2. Different Types of Playwright Assertions

1

Visibility

Verify that an element is visible or hidden.

Core
2

Text

Verify exact or partial visible text.

Core
3

Value

Verify the current value of form controls.

Forms
4

Attribute

Verify attributes such as href, title or data-state.

DOM
5

Enabled / Disabled

Verify whether controls can be interacted with.

State
6

Checked

Verify checkbox and radio button state.

Forms
7

URL

Verify the current page URL.

Navigation
8

Title

Verify the browser page title.

Navigation
9

Count

Verify the number of matching elements.

Collections
10

Screenshot

Compare screenshots for visual validation.

Visual
11

Soft Assertions

Continue the test after an assertion failure.

Advanced
12

Negation

Verify that an expected condition is not true.

Advanced

3. Import Playwright Assertions

In Python's synchronous Playwright API, web-first assertions are provided through the expect() API.

Python
from playwright.sync_api import expect

With the asynchronous API:

Python Async
from playwright.async_api import expect

4. Visibility Assertions

Verify Element Is Visible

Python
expect(
    page.get_by_role(
        "heading",
        name="Dashboard"
    )
).to_be_visible()

Verify Element Is Hidden

Python
expect(
    page.get_by_test_id("loading-spinner")
).to_be_hidden()
This is preferable to immediately reading a visibility property and asserting the returned boolean because the web-first assertion can wait for the expected state.

5. Text Assertions

Exact Text

Python
expect(
    page.get_by_role(
        "heading"
    )
).to_have_text("Dashboard")

Partial Text

Python
expect(
    page.get_by_test_id("status-message")
).to_contain_text("successful")
Use to_have_text() when the expected text should match the element's text content, and to_contain_text() when only an important portion needs to be verified.

6. Text Assertion with Regular Expression

Regular expressions are useful when part of the text is dynamic.

Python
import re

expect(
    page.get_by_test_id("order-message")
).to_have_text(
    re.compile(r"Order #\d+ confirmed")
)
This is useful when order numbers, transaction IDs or timestamps change from one test execution to another.

7. Input Value Assertions

Verify what is currently stored in an input field.

Python
email = page.get_by_label("Email")

email.fill("user@example.com")

expect(email).to_have_value(
    "user@example.com"
)

8. Attribute Assertions

Attributes are useful when validating links, states and application metadata.

Python
link = page.get_by_role(
    "link",
    name="Documentation"
)

expect(link).to_have_attribute(
    "href",
    "/documentation"
)

Check Dynamic State Attribute

Python
tab = page.get_by_role(
    "tab",
    name="Orders"
)

expect(tab).to_have_attribute(
    "aria-selected",
    "true"
)

9. CSS Class Assertions

Class assertions can be useful when the class represents a meaningful application state.

Python
expect(
    page.get_by_test_id("status")
).to_have_class(
    "status-success"
)
Do not make every test depend on CSS classes. If a class is only an implementation detail used for styling, a user-facing assertion may be more durable.

10. Enabled and Disabled Assertions

Enabled

Python
submit = page.get_by_role(
    "button",
    name="Submit"
)

expect(submit).to_be_enabled()

Disabled

Python
expect(submit).to_be_disabled()

11. Checkbox and Radio Assertions

Checked

Python
terms = page.get_by_label(
    "Accept Terms"
)

terms.check()

expect(terms).to_be_checked()

Not Checked

Python
expect(terms).not_to_be_checked()

12. Editable and Read-Only State

Editable

Python
expect(
    page.get_by_label("Username")
).to_be_editable()

Read Only

Python
expect(
    page.get_by_label("Order ID")
).to_be_read_only()

13. URL Assertions

Verify navigation after an action.

Python
page.get_by_role(
    "link",
    name="Orders"
).click()

expect(page).to_have_url(
    re.compile(r".*/orders")
)

Exact URL

Python
expect(page).to_have_url(
    "https://example.com/orders"
)

14. Page Title Assertion

Python
expect(page).to_have_title(
    "Customer Orders"
)

Regular Expression

Python
expect(page).to_have_title(
    re.compile("Orders")
)

15. Count Assertions

Count assertions are useful for tables, product lists, search results and dashboards.

Python
products = page.get_by_test_id(
    "product-card"
)

expect(products).to_have_count(10)
The count assertion can wait for the locator collection to reach the expected count instead of checking only once.

16. Empty and Non-Empty State

Empty State

Python
expect(
    page.get_by_test_id("cart-item")
).to_have_count(0)

Non-Empty State

Python
expect(
    page.get_by_test_id("cart-item")
).not_to_have_count(0)

17. Negative Assertions

Use not_ when the requirement is that a condition should not be true.

Python
expect(
    page.get_by_text("Error")
).not_to_be_visible()

Not Checked

Python
expect(
    page.get_by_label("Subscribe")
).not_to_be_checked()

18. How Web-First Assertions Auto-Retry

Consider a dashboard that loads data asynchronously.

Page Loads
→
API Request
→
UI Updates
→
Assertion
Python
expect(
    page.get_by_text("Orders Loaded")
).to_be_visible()

The assertion waits for the expected condition rather than assuming that the UI is already updated at the exact moment the assertion begins.

This is one of the major differences between a web-first assertion and a simple immediate property check.

19. Fragile Assertion Pattern

❌ Immediate Property Check

Avoid for Dynamic UI
assert page.get_by_text(
    "Payment Successful"
).is_visible()

The boolean property is evaluated immediately. If the application is still rendering the message, the assertion may fail before the expected state appears.

✅ Web-First Assertion

Recommended
expect(
    page.get_by_text(
        "Payment Successful"
    )
).to_be_visible()

20. Assertions vs Explicit Waits

A common beginner mistake is adding an explicit wait before every assertion.

❌ Unnecessary Pattern

Avoid
page.wait_for_timeout(5000)

expect(
    page.get_by_text("Success")
).to_be_visible()

✅ Better

Recommended
expect(
    page.get_by_text("Success")
).to_be_visible()
Fixed sleeps make tests slower when the application is fast and still unreliable when the application is slower than the chosen delay.

21. Assertion Timeout

Sometimes a particular operation genuinely needs a different timeout. Playwright assertions support timeout configuration.

Python
expect(
    page.get_by_text(
        "Large Report Ready"
    )
).to_be_visible(
    timeout=30000
)
Increase timeouts because you understand the application's expected behavior, not simply because a test is flaky.

22. Table Row Assertions

Tables are common in enterprise applications.

Python
row = page.locator("tr").filter(
    has_text="John Smith"
)

expect(row).to_be_visible()

expect(row).to_contain_text(
    "Active"
)

expect(row).to_contain_text(
    "Premium"
)
Instead of asserting arbitrary row numbers, identify the business entity and validate its state.

23. Product List Assertions

Python
products = page.get_by_test_id(
    "product-card"
)

expect(products).to_have_count(20)

expect(
    products.nth(0)
).to_contain_text("Laptop")
Use nth() when position is genuinely part of the requirement. If the test is about a specific product, a semantic filter is usually more meaningful.

24. Soft Assertions

Soft assertions allow a test to continue after an assertion failure, so multiple problems can be reported from one test execution.

Python
expect(
    page.get_by_text("Dashboard")
).to_be_visible()

expect(
    page.get_by_text("Orders")
).to_be_visible()

expect(
    page.get_by_text("Customers")
).to_be_visible()
For Python Playwright, soft assertion support depends on the Playwright version and test setup. When using a feature or helper from your installed version, verify the API supported by that version. Do not confuse continuing execution with ignoring an important failure.

25. Visual Assertions with Screenshots

Playwright can perform screenshot comparisons for visual regression testing.

Python
expect(page).to_have_screenshot(
    "dashboard.png"
)

Element Screenshot

Python
expect(
    page.get_by_test_id("dashboard-card")
).to_have_screenshot(
    "dashboard-card.png"
)
Visual assertions are especially useful for stable UI components, layouts and regression testing where appearance itself is part of the requirement.

26. Real-Time Example — Login Validation

Python
from playwright.sync_api import expect


def test_login(page):

    page.goto(
        "https://example.com/login"
    )

    page.get_by_label(
        "Username"
    ).fill("testuser")

    page.get_by_label(
        "Password"
    ).fill("TestPassword123")

    page.get_by_role(
        "button",
        name="Sign In"
    ).click()

    # URL assertion
    expect(page).to_have_url(
        "https://example.com/dashboard"
    )

    # Page assertion
    expect(page).to_have_title(
        "Dashboard"
    )

    # UI assertion
    expect(
        page.get_by_role(
            "heading",
            name="Dashboard"
        )
    ).to_be_visible()

27. Real-Time Example — E-Commerce Checkout

Python
from playwright.sync_api import expect


def test_checkout(page):

    page.goto(
        "https://example.com/products"
    )

    product = page.locator(
        ".product-card"
    ).filter(
        has_text="Laptop"
    )

    expect(product).to_be_visible()

    product.get_by_role(
        "button",
        name="Add to Cart"
    ).click()

    cart = page.get_by_role(
        "link",
        name="Cart"
    )

    expect(cart).to_be_visible()

    cart.click()

    checkout = page.get_by_role(
        "button",
        name="Checkout"
    )

    expect(checkout).to_be_enabled()

    checkout.click()

    expect(page).to_have_url(
        "https://example.com/checkout"
    )

    expect(
        page.get_by_role(
            "heading",
            name="Checkout"
        )
    ).to_be_visible()

28. Validate Application State After an Action

A strong test verifies the outcome of the business operation, not merely that the click completed.

Weak Test

Example
page.get_by_role(
    "button",
    name="Approve"
).click()

Strong Test

Recommended
page.get_by_role(
    "button",
    name="Approve"
).click()

expect(
    page.get_by_text("Approved")
).to_be_visible()

expect(
    page.get_by_test_id("request-status")
).to_have_text("Approved")
The second test validates the application's actual business outcome.

29. Assertion Strategy for Real-Time Applications

Perform Action
→
Assert UI State
→
Assert Business Result
→
Assert Navigation

For example, after approving a customer request:

Python
approve = page.get_by_role(
    "button",
    name="Approve"
)

approve.click()

# UI state
expect(
    page.get_by_text("Approved")
).to_be_visible()

# Business state
expect(
    page.get_by_test_id("request-status")
).to_have_text("Approved")

# Action availability
expect(approve).to_be_disabled()

30. Locator + Action + Assertion

The core Playwright pattern can be summarized as:

Locator
→
Action
→
Web-First Assertion
→
Auto-Retry
→
Result
Python
login_button = page.get_by_role(
    "button",
    name="Login"
)

login_button.click()

expect(
    page.get_by_role(
        "heading",
        name="Dashboard"
    )
).to_be_visible()

31. Playwright Assertion Cheat Sheet

Requirement Assertion Example
Visible to_be_visible() expect(locator).to_be_visible()
Hidden to_be_hidden() expect(locator).to_be_hidden()
Text to_have_text() expect(locator).to_have_text("Success")
Contains text to_contain_text() expect(locator).to_contain_text("Success")
Input value to_have_value() expect(locator).to_have_value("John")
Attribute to_have_attribute() expect(locator).to_have_attribute("href", "/home")
Class to_have_class() expect(locator).to_have_class("active")
Enabled to_be_enabled() expect(locator).to_be_enabled()
Disabled to_be_disabled() expect(locator).to_be_disabled()
Checked to_be_checked() expect(locator).to_be_checked()
Editable to_be_editable() expect(locator).to_be_editable()
Read-only to_be_read_only() expect(locator).to_be_read_only()
Count to_have_count() expect(locator).to_have_count(10)
URL to_have_url() expect(page).to_have_url(...)
Title to_have_title() expect(page).to_have_title(...)
Screenshot to_have_screenshot() expect(page).to_have_screenshot(...)

32. Bad vs Robust Assertions

❌ Hard Sleep + Immediate Check

Fragile
import time

time.sleep(5)

assert page.get_by_text(
    "Order Completed"
).is_visible()

✅ Web-First Assertion

Robust
expect(
    page.get_by_text(
        "Order Completed"
    )
).to_be_visible()

❌ Checking an Arbitrary Position

Fragile
assert page.locator(
    ".product"
).nth(4).is_visible()

✅ Assert the Business Object

Robust
product = page.locator(
    ".product"
).filter(
    has_text="Laptop"
)

expect(product).to_be_visible()

33. Professional Assertion Design

Assertions should validate meaningful business behavior instead of implementation details whenever possible.

UI State

Is the correct component visible? Is the button enabled? Is the selected tab active?

Business State

Was the order created? Was the customer approved? Was payment completed?

Navigation

Did the application navigate to the expected URL? Did the correct page load?

34. Assertions with Page Object Model

Page objects can contain interactions and expose meaningful page-level operations. Tests can then perform assertions against the page's externally visible behavior.

Python
from playwright.sync_api import Page, expect


class LoginPage:

    def __init__(self, page: Page):

        self.page = page

        self.username = page.get_by_label(
            "Username"
        )

        self.password = page.get_by_label(
            "Password"
        )

        self.login_button = page.get_by_role(
            "button",
            name="Sign In"
        )

        self.dashboard_heading = page.get_by_role(
            "heading",
            name="Dashboard"
        )

    def login(self, username, password):

        self.username.fill(username)

        self.password.fill(password)

        self.login_button.click()

    def assert_dashboard_visible(self):

        expect(
            self.dashboard_heading
        ).to_be_visible()

Test

Python
def test_login(page):

    login = LoginPage(page)

    login.login(
        "testuser",
        "TestPassword123"
    )

    login.assert_dashboard_visible()

35. Complete Real-Time Assertion Workflow

Python - End-to-End
from playwright.sync_api import expect


def test_customer_approval(page):

    page.goto(
        "https://example.com/customers"
    )

    customer = page.locator(
        "tr"
    ).filter(
        has_text="John Smith"
    )

    # Customer exists
    expect(customer).to_be_visible()

    # Initial state
    expect(customer).to_contain_text(
        "Pending"
    )

    # Perform action
    customer.get_by_role(
        "button",
        name="Approve"
    ).click()

    # UI result
    expect(customer).to_contain_text(
        "Approved"
    )

    # Status control
    expect(
        customer.get_by_test_id(
            "status"
        )
    ).to_have_text("Approved")

    # Button state
    expect(
        customer.get_by_role(
            "button",
            name="Approve"
        )
    ).to_be_disabled()
Notice that every important step has a meaningful validation. The test does not simply click "Approve" and assume the operation worked.

36. Playwright Assertion Best Practices

  • Prefer web-first assertions through expect().
  • Assert meaningful application behavior rather than implementation details.
  • Use visibility assertions for UI presence.
  • Use text assertions for user-visible messages and business states.
  • Use value assertions for form fields.
  • Use attribute assertions when the attribute represents meaningful state.
  • Use URL and title assertions to validate navigation.
  • Use count assertions for lists, tables and collections.
  • Avoid fixed sleeps before assertions.
  • Avoid immediate property checks when the UI is expected to change asynchronously.
  • Use negative assertions only when absence or a negative state is actually part of the requirement.
  • Keep assertions close to the business action they validate.

37. Professional Assertion Mindset

What Did I Do?
→
What Should Happen?
→
What Visible State Proves It?
→
Assert It
Example:

Action: Approve Customer

Expected behavior: Customer status changes from Pending to Approved

Good assertion: expect(status).to_have_text("Approved")

38. Final Mental Model

Locate
→
Act
→
Application Changes
→
Expect
→
Auto-Retry
→
Pass / Fail
Robust Playwright tests do not merely verify that an action was executed.

They verify that the application reached the expected state after that action.

39. Next Learning Step

Next → Waits

Learn auto-waiting, explicit waits, event-based synchronization, network waits and dynamic UI handling.

Then → Fixtures

Learn how Playwright fixtures provide reusable browser, context, page and test data setup.

Professional → Page Object Model

Combine locators, actions and assertions into maintainable enterprise automation architecture.