From playwright-e2e
Web-first Playwright assertions with built-in auto-waiting. Covers the full assertion API, soft assertions for non-critical checks, negation, custom failure messages, and anti-patterns to avoid.
How this skill is triggered — by the user, by Claude, or both
Slash command
/playwright-e2e:e2e-assertionsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Playwright's web-first assertions automatically retry until the condition is met or the timeout expires. Always use them instead of manually polling or awaiting conditions.
Playwright's web-first assertions automatically retry until the condition is met or the timeout expires. Always use them instead of manually polling or awaiting conditions.
| Assertion | Use case |
|---|---|
expect(locator).toBeVisible() | Element is present and visible in the DOM |
expect(locator).toBeHidden() | Element is absent or invisible |
expect(locator).toBeEnabled() | Interactive element is not disabled |
expect(locator).toBeDisabled() | Interactive element is disabled |
expect(locator).toBeChecked() | Checkbox or radio is checked |
expect(locator).toHaveText(text) | Element's text content equals text (trimmed) |
expect(locator).toContainText(text) | Element's text content contains text |
expect(locator).toHaveValue(value) | Input's current value equals value |
expect(locator).toHaveAttribute(name, value) | Element has the specified attribute value |
expect(locator).toHaveCount(n) | Locator matches exactly n elements |
expect(locator).toHaveClass(className) | Element has the given CSS class |
expect(page).toHaveURL(url) | Current page URL matches url (string or regex) |
expect(page).toHaveTitle(title) | Document title matches title (string or regex) |
All web-first assertions retry automatically — you do not need to add waitFor calls before them:
// DO — assertion waits automatically
await expect(page.getByRole('alert')).toBeVisible();
// DON'T — manual wait is redundant and slows tests
await page.waitForSelector('[role="alert"]');
expect(await page.isVisible('[role="alert"]')).toBe(true);
The default assertion timeout is inherited from playwright.config.ts (expect.timeout). Set it at the project level; do not override per-assertion unless there is a documented reason.
import { test, expect } from '../fixtures/test';
test('should display the order confirmation', async ({ page }) => {
await page.goto('/checkout/confirm');
// URL assertion
await expect(page).toHaveURL('/checkout/confirm');
// Title assertion
await expect(page).toHaveTitle(/Order Confirmed/);
// Visibility
await expect(page.getByRole('heading', { name: 'Thank you!' })).toBeVisible();
// Text content
await expect(page.getByTestId('order-number')).toHaveText('ORD-12345');
// Attribute
await expect(page.getByRole('link', { name: 'Download receipt' })).toHaveAttribute(
'href',
/\/receipts\//
);
// Count — verify 3 items in summary
await expect(page.getByRole('listitem')).toHaveCount(3);
});
Use expect.soft for non-critical checks where a single failure should not abort the rest of the test. All soft assertion failures are collected and reported together at the end of the test.
test('should display all dashboard widgets', async ({ page }) => {
await page.goto('/dashboard');
// Hard assertion — test stops here if it fails
await expect(page.getByRole('main')).toBeVisible();
// Soft assertions — test continues even if one fails
await expect.soft(page.getByTestId('revenue-widget')).toBeVisible();
await expect.soft(page.getByTestId('users-widget')).toBeVisible();
await expect.soft(page.getByTestId('orders-widget')).toBeVisible();
});
Use soft assertions only when:
Do not use soft assertions as a substitute for fixing flaky tests.
Negate any assertion with .not:
// Locator is not visible
await expect(page.getByRole('dialog')).not.toBeVisible();
// Input is not disabled
await expect(page.getByRole('button', { name: 'Submit' })).not.toBeDisabled();
// Page URL does not contain a query string
await expect(page).not.toHaveURL(/\?error=/);
Add a descriptive message as the second argument to expect to make failures easier to diagnose:
await expect(
page.getByRole('status'),
'Success banner should appear after form submission'
).toBeVisible();
Provide a custom message whenever the assertion context is not immediately obvious from the locator alone.
| Anti-pattern | Problem | Correct approach |
|---|---|---|
await page.isVisible(selector) + manual check | Evaluates once; no retry | expect(locator).toBeVisible() |
await page.waitForSelector(selector) before assertion | Duplicates work; misses auto-retry | Remove waitForSelector; let assertion retry |
await page.waitForTimeout(ms) | Hardcoded delay; always wrong | Use a specific web-first assertion |
expect(await locator.textContent()).toBe(...) | Evaluates once; no retry | expect(locator).toHaveText(...) |
page.$$eval(...) + manual assertion | Bypasses auto-waiting | expect(locator).toHaveCount(n) or iterate with .all() |
// DO — web-first, auto-waiting
await expect(page.getByRole('alert')).toHaveText('Saved successfully');
// DON'T — manual text extraction with no retry
const text = await page.getByRole('alert').textContent();
expect(text).toBe('Saved successfully');
// DO — URL pattern assertion
await expect(page).toHaveURL(/\/orders\/\d+/);
// DON'T — manual URL check
const url = page.url();
expect(url).toMatch(/\/orders\/\d+/);
npx claudepluginhub gagandeepp/software-agent-teams --plugin playwright-e2eGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.