Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/microsoft/playwright/llms.txt

Use this file to discover all available pages before exploring further.

Introduction

Playwright Test includes assertion methods optimized for the web with automatic retrying and detailed error messages.

Basic Assertions

From src/matchers/expect.ts:206-243:
import { test, expect } from '@playwright/test';

test('basic assertions', async ({ page }) => {
  // Generic assertions
  expect(1 + 1).toBe(2);
  expect('hello').toContain('ell');
  expect([1, 2, 3]).toHaveLength(3);
  expect({ name: 'John' }).toEqual({ name: 'John' });
});

Web-First Assertions

Playwright provides auto-retrying assertions for web elements from src/matchers/matchers.ts:

Visibility Assertions

toBeVisible
async
Assert element is visibleFrom src/matchers/matchers.ts:160-171:
await expect(page.locator('button')).toBeVisible();
await expect(page.locator('button')).toBeVisible({ timeout: 5000 });

// Negated
await expect(page.locator('.loading')).not.toBeVisible();

// With visible option
await expect(page.locator('button')).toBeVisible({ visible: true });
await expect(page.locator('.hidden')).toBeVisible({ visible: false });
toBeHidden
async
Assert element is hiddenFrom src/matchers/matchers.ts:150-158:
await expect(page.locator('.modal')).toBeHidden();

Attachment State

toBeAttached
async
Assert element is attached to DOMFrom src/matchers/matchers.ts:56-67:
await expect(page.locator('#element')).toBeAttached();
await expect(page.locator('#removed')).toBeAttached({ attached: false });

Element State Assertions

toBeEnabled
async
Assert element is enabledFrom src/matchers/matchers.ts:127-138:
await expect(page.locator('button')).toBeEnabled();
await expect(page.locator('button')).toBeEnabled({ enabled: false });
toBeDisabled
async
Assert element is disabledFrom src/matchers/matchers.ts:94-102:
await expect(page.locator('button:disabled')).toBeDisabled();
toBeEditable
async
Assert element is editableFrom src/matchers/matchers.ts:104-115:
await expect(page.locator('input')).toBeEditable();
await expect(page.locator('input[readonly]')).toBeEditable({ editable: false });
toBeEmpty
async
Assert element is emptyFrom src/matchers/matchers.ts:117-125:
await expect(page.locator('.list')).toBeEmpty();
toBeFocused
async
Assert element is focusedFrom src/matchers/matchers.ts:140-148:
await expect(page.locator('input[name=username]')).toBeFocused();
toBeChecked
async
Assert checkbox is checkedFrom src/matchers/matchers.ts:69-92:
await expect(page.locator('input[type=checkbox]')).toBeChecked();
await expect(page.locator('input[type=checkbox]')).toBeChecked({ checked: false });

// Check for indeterminate state
await expect(page.locator('input[type=checkbox]')).toBeChecked({ indeterminate: true });
toBeInViewport
async
Assert element is in viewportFrom src/matchers/matchers.ts:173-181:
await expect(page.locator('.hero')).toBeInViewport();

// At least 50% visible
await expect(page.locator('.banner')).toBeInViewport({ ratio: 0.5 });

Text Content Assertions

toHaveText
async
Assert element has exact textFrom src/matchers/matchers.ts:380-397:
// String match
await expect(page.locator('.title')).toHaveText('Welcome');

// Regex match
await expect(page.locator('.title')).toHaveText(/Welcome/);

// Array match (for multiple elements)
await expect(page.locator('li')).toHaveText([
  'Item 1',
  'Item 2',
  'Item 3',
]);

// Options
await expect(page.locator('.title')).toHaveText('welcome', { 
  ignoreCase: true,
  useInnerText: true,
});
toContainText
async
Assert element contains text substringFrom src/matchers/matchers.ts:183-200:
await expect(page.locator('.description')).toContainText('product');
await expect(page.locator('.description')).toContainText(/product/i);

// Multiple elements
await expect(page.locator('li')).toContainText(['Apple', 'Banana']);

Attribute Assertions

toHaveAttribute
async
Assert element has attributeFrom src/matchers/matchers.ts:238-261:
// Check attribute exists
await expect(page.locator('button')).toHaveAttribute('disabled');

// Check attribute value
await expect(page.locator('a')).toHaveAttribute('href', '/about');
await expect(page.locator('a')).toHaveAttribute('href', /\/about/);

// Case insensitive
await expect(page.locator('a')).toHaveAttribute('href', '/ABOUT', { 
  ignoreCase: true 
});
toHaveClass
async
Assert element has CSS classFrom src/matchers/matchers.ts:263-280:
// Single class
await expect(page.locator('button')).toHaveClass('btn-primary');
await expect(page.locator('button')).toHaveClass(/btn-/);

// Multiple elements
await expect(page.locator('.item')).toHaveClass([
  'item active',
  'item',
  'item disabled',
]);
toContainClass
async
Assert element contains CSS classFrom src/matchers/matchers.ts:282-303:
await expect(page.locator('button')).toContainClass('active');

// Multiple elements contain class
await expect(page.locator('.item')).toContainClass(['visible', 'enabled']);
toHaveId
async
Assert element has IDFrom src/matchers/matchers.ts:342-352:
await expect(page.locator('input')).toHaveId('username');
await expect(page.locator('input')).toHaveId(/user/);
toHaveValue
async
Assert input has valueFrom src/matchers/matchers.ts:399-409:
await expect(page.locator('input')).toHaveValue('John');
await expect(page.locator('input')).toHaveValue(/john/i);
toHaveValues
async
Assert select has valuesFrom src/matchers/matchers.ts:411-421:
await expect(page.locator('select')).toHaveValues(['option1', 'option2']);

CSS Assertions

toHaveCSS
async
Assert element has CSS propertyFrom src/matchers/matchers.ts:316-340:
// Single property
await expect(page.locator('button')).toHaveCSS('color', 'rgb(255, 0, 0)');
await expect(page.locator('button')).toHaveCSS('display', /flex/);

// Multiple properties
await expect(page.locator('button')).toHaveCSS({
  'color': 'rgb(255, 0, 0)',
  'background-color': 'rgb(0, 0, 255)',
});

JavaScript Property Assertions

toHaveJSProperty
async
Assert element has JavaScript propertyFrom src/matchers/matchers.ts:354-364:
await expect(page.locator('input')).toHaveJSProperty('value', 'text');
await expect(page.locator('input')).toHaveJSProperty('disabled', true);

Count Assertion

toHaveCount
async
Assert locator matches count of elementsFrom src/matchers/matchers.ts:305-314:
await expect(page.locator('li')).toHaveCount(3);
await expect(page.locator('.item')).toHaveCount(0);

Accessibility Assertions

toHaveAccessibleName
async
Assert element has accessible nameFrom src/matchers/matchers.ts:214-224:
await expect(page.locator('button')).toHaveAccessibleName('Submit');
await expect(page.locator('button')).toHaveAccessibleName(/submit/i);
toHaveAccessibleDescription
async
Assert element has accessible descriptionFrom src/matchers/matchers.ts:202-212:
await expect(page.locator('button')).toHaveAccessibleDescription('Click to submit');
toHaveAccessibleErrorMessage
async
Assert element has accessible error messageFrom src/matchers/matchers.ts:226-236:
await expect(page.locator('input')).toHaveAccessibleErrorMessage('Invalid email');
toHaveRole
async
Assert element has ARIA roleFrom src/matchers/matchers.ts:366-378:
await expect(page.locator('button')).toHaveRole('button');
await expect(page.locator('nav')).toHaveRole('navigation');

Page Assertions

toHaveTitle
async
Assert page has titleFrom src/matchers/matchers.ts:423-433:
await expect(page).toHaveTitle('Welcome');
await expect(page).toHaveTitle(/Welcome/);
toHaveURL
async
Assert page has URLFrom src/matchers/matchers.ts:435-454:
await expect(page).toHaveURL('https://example.com/about');
await expect(page).toHaveURL(/about/);

// Case insensitive
await expect(page).toHaveURL('https://example.com/About', { 
  ignoreCase: true 
});

API Response Assertions

toBeOK
async
Assert API response is successful (status 200-299)From src/matchers/matchers.ts:456-481:
const response = await page.request.get('https://api.example.com/data');
await expect(response).toBeOK();

Snapshot Assertions

toHaveScreenshot
async
Assert screenshot matches baseline
await expect(page).toHaveScreenshot();
await expect(page).toHaveScreenshot('homepage.png');
await expect(page).toHaveScreenshot({
  maxDiffPixels: 100,
  threshold: 0.2,
});
toMatchSnapshot
sync
Assert value matches snapshot
expect(data).toMatchSnapshot('user-data.json');

Polling Assertions

From src/matchers/expect.ts:127-131,367-403:
expect.poll
async
Poll until condition is met
await expect.poll(async () => {
  const response = await page.request.get('/api/status');
  return response.status();
}).toBe(200);

// With timeout and intervals
await expect.poll(async () => {
  return await page.locator('.count').textContent();
}, {
  timeout: 10000,
  intervals: [100, 250, 500],
}).toBe('5');
toPass
async
Retry assertion until it passesFrom src/matchers/matchers.ts:483-517:
await expect(async () => {
  const response = await page.request.get('/api/status');
  expect(response.status()).toBe(200);
}).toPass({
  timeout: 10000,
  intervals: [100, 500, 1000],
});

Soft Assertions

From src/matchers/expect.ts:117-121:
test('soft assertions', async ({ page }) => {
  // Test continues even if these fail
  await expect.soft(page.locator('.title')).toHaveText('Welcome');
  await expect.soft(page.locator('.subtitle')).toBeVisible();
  
  // All failures reported at the end
});

Custom Assertions

From src/matchers/expect.ts:101-114:
import { expect as base } from '@playwright/test';

export const expect = base.extend({
  async toHaveValidEmail(locator: Locator) {
    const text = await locator.textContent();
    const isValid = /^[^@]+@[^@]+\.[^@]+$/.test(text || '');
    
    return {
      pass: isValid,
      message: () => `Expected ${text} to be a valid email`,
    };
  },
});

// Usage
await expect(page.locator('.email')).toHaveValidEmail();

Assertion Options

All web-first assertions support timeout:
await expect(page.locator('.loading')).not.toBeVisible({ 
  timeout: 5000 
});
From src/matchers/expect.ts:136-153,181-198:
// Configure timeout globally
export default {
  expect: {
    timeout: 10000,
  },
};

// Configure per test
test('with custom timeout', async ({ page }) => {
  await expect.configure({ timeout: 15000 })(page.locator('.slow'))
    .toBeVisible();
});

Negation

All assertions can be negated with .not:
await expect(page.locator('.hidden')).not.toBeVisible();
await expect(page.locator('button')).not.toBeDisabled();
await expect(page).not.toHaveURL(/admin/);

Custom Messages

From src/matchers/expect.ts:86-88,305-309:
await expect(page.locator('.count'), 'Item count should be 5')
  .toHaveText('5');

await expect.configure({ 
  message: 'Custom failure message' 
})(page.locator('.title')).toBeVisible();

Best Practices

  1. Use web-first assertions: They auto-retry and wait for conditions
  2. Prefer specific assertions: Use toHaveText instead of toBe
  3. Use soft assertions for multiple checks: Continue test execution
  4. Set appropriate timeouts: Based on your application’s behavior
  5. Add custom messages: For better debugging

Next Steps

Test Fixtures

Learn about test fixtures

Test Hooks

Set up test environment with hooks

Build docs developers (and LLMs) love