- May 13, 2026
- admin
- 0

These three assertions are among the most powerful features in Playwright Test. They help you write more reliable tests by handling retries and failures in different ways.
- expect.soft() (Soft Assertion)
A soft assertion records a failure but does not stop the test immediately. The test continues executing, and all soft assertion failures are reported at the end.
Normal Assertion
If this fails, the test stops immediately.
import { test, expect } from ‘@playwright/test’;
test(‘Hard Assertion’, async ({ page }) => {
await page.goto(‘https://example.com’);
await expect(page).toHaveTitle(‘Wrong Title’);
console.log(‘This line will NOT execute.’);
});
Output
❌ Test Failed
Execution stopped.
Soft Assertion
import { test, expect } from ‘@playwright/test’;
test(‘Soft Assertion Example’, async ({ page }) => {
await page.goto(‘https://example.com’);
await expect.soft(page).toHaveTitle(‘Wrong Title’);
console.log(‘This line WILL execute.’);
await expect.soft(page.locator(‘h1’)).toHaveText(‘Wrong Heading’);
console.log(‘This also executes.’);
});
Output
❌ Title mismatch
❌ Heading mismatch
Test finished.
2 soft assertion failures.
When to use
- Validating multiple fields on a page.
- Checking dashboard widgets.
- Verifying reports with many values.
- UI comparison tests where you want all failures at once.
Example:
await expect.soft(page.locator(‘#firstName’)).toHaveValue(‘John’);
await expect.soft(page.locator(‘#lastName’)).toHaveValue(‘Smith’);
await expect.soft(page.locator(‘#email’)).toHaveValue(‘john@test.com’);
await expect.soft(page.locator(‘#country’)).toHaveValue(‘India’);
Instead of stopping at the first failure, Playwright reports every mismatch.
- expect.poll()
expect.poll() repeatedly executes a function until the expected value is returned or the timeout expires.
Think of it as:
Check
Still not correct
Wait
Check again
Wait
Check again
Success
It is useful when the value is not automatically retried by a locator assertion, such as an API response, database state, or a computed value.
Example: Polling an API
import { test, expect } from ‘@playwright/test’;
test(‘Poll Example’, async ({ request }) => {
await expect.poll(async () => {
const response = await request.get(‘/status’);
const body = await response.json();
return body.status;
}).toBe(‘Completed’);
});
Playwright keeps calling the function until it returns “Completed” or the timeout is reached.
Example: Waiting for a Counter
await expect.poll(async () => {
return Number(await page.locator(‘#counter’).textContent());
}).toBe(10);
Suppose the counter updates like this:
1
3
5
7
10
Playwright keeps checking until it sees 10.
Custom Timeout
await expect.poll(
async () => {
return await page.locator(‘#status’).textContent();
},
{
timeout: 30000
}
).toBe(‘Completed’);
Custom Polling Intervals
await expect.poll(
async () => getStatus(),
{
intervals: [1000, 2000, 5000]
}
).toBe(‘Done’);
Playwright waits:
- 1 second
- 2 seconds
- 5 seconds
between successive retries according to the configured intervals.
Best use cases
- API status checks
- Database validation
- Queue processing
- Background jobs
- Email delivery status
- File generation
- expect.toPass()
expect.toPass() retries an entire block of code until every assertion inside it passes or the timeout is reached.
This is different from expect.poll(), which retries only a returned value.
Example
await expect(async () => {
await expect(page.locator(‘#status’)).toHaveText(‘Completed’);
await expect(page.locator(‘#message’)).toContainText(‘Success’);
}).toPass();
If either assertion fails, the whole callback is executed again.
Real-world Example
Suppose clicking a button starts a background process.
await page.click(‘#process’);
await expect(async () => {
await expect(page.locator(‘#status’)).toHaveText(‘Completed’);
await expect(page.locator(‘#percentage’)).toHaveText(‘100%’);
await expect(page.locator(‘#result’)).toContainText(‘Success’);
}).toPass({
timeout: 30000
});
Playwright keeps rerunning the entire block until all three assertions succeed.
expect.poll() vs expect.toPass()
| Feature | expect.poll() | expect.toPass() |
| Retries | A function that returns a value | An entire callback block |
| Typical use | API, database, computed values | Multiple assertions or actions that may eventually succeed |
| Returns | A value to compare | No value; assertions determine success |
| Best for | Waiting for one changing value | Waiting for a complete state involving several checks |
Which one should you choose?
| Situation | Recommended |
| Verify several fields but continue after failures | expect.soft() |
| Wait for an API, queue, database, or single value to reach a target | expect.poll() |
| Wait until multiple assertions all become true together | expect.toPass() |
Summary
- expect.soft(): Collects assertion failures and lets the test continue.
- expect.poll(): Repeatedly evaluates a function until its returned value matches the expectation.
- expect.toPass(): Repeatedly executes a block of assertions until the entire block passes.
Together, these features make Playwright tests more resilient when dealing with asynchronous operations, eventually consistent systems, and comprehensive UI validation.

