100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Testing

Assertions with expect()

Learn Playwright's web-first assertions — how expect(locator) auto-retries until conditions are met, and how that differs from generic value assertions.

Locators & ActionsIntermediate7 min readJul 10, 2026
Analogies

Web-First Assertions vs Generic Assertions

expect(locator).toBeVisible() is a web-first assertion: internally, Playwright polls the DOM repeatedly, up to a default 5-second timeout, until the condition becomes true or the timeout expires, rather than checking once and immediately failing. This matters enormously for asynchronous UIs, where a success message might take a few hundred milliseconds to render after an API call resolves. By contrast, expect(value).toBe(x) — asserting on a plain JavaScript value rather than a locator — evaluates exactly once, immediately, with no retry logic at all, because there's no live DOM state to poll against.

🏏

Cricket analogy: expect(locator).toBeVisible() is like the third umpire reviewing replay angles for a set time until conclusive evidence appears, rather than ruling instantly off one blurry frame — a generic expect(value).toBe(x) is a single, immediate check, like an on-field umpire's instant call with no review.

Common Locator Assertions

Playwright ships a rich set of locator-scoped matchers, each targeting one precise fact about the DOM: toHaveText() and toContainText() check exact or substring text content, toHaveValue() checks a form input's current value, toHaveCount() checks how many elements a locator matches, toBeEnabled() and toBeChecked() check interactive and checked state, and toHaveAttribute() and toHaveClass() check a specific attribute or CSS class. All of these are web-first and auto-retry, so chaining several of them after a form submission gives you both precise diagnostics and resilience against timing.

🏏

Cricket analogy: toHaveText, toHaveValue, toHaveCount, toBeEnabled, and toHaveAttribute each check one specific dimension of the DOM, much like a scorecard tracks runs, wickets, overs, and extras as separate stat lines rather than one vague 'performance' number — a solid test chains the specific assertion that matches exactly what changed.

javascript
await page.getByRole('button', { name: 'Submit' }).click();

await expect(page.getByRole('status')).toHaveText('Profile saved successfully');
await expect(page.getByLabel('Email')).toHaveValue('user@example.com');
await expect(page.getByRole('listitem')).toHaveCount(3);
await expect(page.getByRole('button', { name: 'Submit' })).toBeEnabled();
await expect(page.getByRole('alert')).not.toBeVisible();

Soft Assertions and Negation

expect.soft(locator).toHaveText('...') records a failure without immediately stopping the test, so a single test.step can accumulate multiple independent findings — a summary error, a highlighted field, a disabled button state — and report all of them together at the end of the test instead of stopping at the very first mismatch. A regular, 'hard' expect() throws immediately on failure and aborts the rest of the test. Every matcher also supports a .not modifier, such as expect(locator).not.toBeVisible(), which checks that the opposite condition holds and retries the same way until it's confirmed true or the timeout expires.

🏏

Cricket analogy: expect.soft() is like a match referee noting every minor over-rate or fielding infringement throughout the game without stopping play immediately, then issuing a full report at the end — a normal hard expect() is like an instant no-ball call that halts the over; .not.toBeVisible() checks the opposite condition, like confirming a batter is NOT given out.

Wrap related assertions in test.step('Verify saved profile fields', async () => { ... }) to group them meaningfully in the HTML report. This makes it much easier to see at a glance which logical part of the test failed, especially when combined with expect.soft() to collect multiple findings from one step.

Custom Timeouts and Polling with expect.poll()

Any locator assertion accepts an explicit { timeout } option, such as expect(locator).toBeVisible({ timeout: 10000 }), to extend the retry window beyond the 5-second default for a known-slow element. expect.poll(async () => { ... }, { timeout: 10000 }).toBe(x) generalizes this same retry-until-true behavior to any arbitrary async function — not just locators — letting you repeatedly poll a database record, a REST API endpoint, or a message queue until it reflects the expected state, which is invaluable for end-to-end tests that need to verify backend side effects triggered by a UI action.

🏏

Cricket analogy: expect(locator).toBeVisible({ timeout: 10000 }) extends the umpire's patience window for a tricky decision, like allowing extra replay time for a marginal stumping — expect.poll() goes further, letting you repeatedly query the official scoring API until the final total is confirmed, not just a DOM element.

Avoid the pattern expect(await locator.textContent()).toBe('Saved'). Calling textContent() up front takes a single, immediate snapshot of the DOM and defeats Playwright's built-in auto-retry entirely — if the text hasn't updated yet, the assertion fails outright instead of waiting. Use expect(locator).toHaveText('Saved') instead, which polls the locator directly.

  • Web-first assertions like expect(locator).toBeVisible() auto-retry against the live DOM until the timeout expires.
  • Generic assertions like expect(value).toBe(x) evaluate a captured value once, immediately, with no retry.
  • toHaveText, toHaveValue, toHaveCount, toBeEnabled, toBeChecked, toHaveAttribute, and toHaveClass each verify one precise fact.
  • expect.soft() collects multiple assertion failures without stopping the test immediately; hard expect() stops on first failure.
  • The .not modifier checks the opposite condition and retries the same way as a positive assertion.
  • expect.poll() generalizes retry-until-true polling to arbitrary async functions beyond locators, like APIs or databases.
  • Avoid expect(await locator.textContent()).toBe(...) — it defeats auto-retry by snapshotting the DOM once.

Practice what you learned

Was this page helpful?

Topics covered

#Testing#PlaywrightStudyNotes#TestingQA#AssertionsWithExpect#Assertions#Expect#Web#Generic#StudyNotes#SkillVeris