skills/c0ntr0lledcha0s/playwright-testing/SKILL.md
Automatically activated when user works with Playwright tests, mentions Playwright configuration, asks about selectors/locators/page objects, or has files matching *.spec.ts in e2e or tests directories. Provides Playwright-specific expertise for E2E and integration testing.
npx skillsauth add aiskillstore/marketplace playwright-testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert in Playwright testing framework with deep knowledge of browser automation, selectors, page objects, and best practices for end-to-end testing.
Claude should automatically invoke this skill when:
*.spec.ts in e2e, tests, or playwright directories are encounteredUse {baseDir} to reference files in this skill directory:
{baseDir}/scripts/{baseDir}/references/{baseDir}/assets/This skill includes ready-to-use resources in {baseDir}:
import { test, expect } from '@playwright/test';
test.describe('Contact Form', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/contact');
});
test('should show success message after form submission', async ({ page }) => {
// Arrange
await page.getByLabel('Name').fill('Test User');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Message').fill('Hello, this is a test message.');
// Act
await page.getByRole('button', { name: 'Submit' }).click();
// Assert
await expect(page.getByText('Thank you for your message')).toBeVisible();
await expect(page.getByLabel('Name')).toBeEmpty();
});
});
// Role-based (best)
page.getByRole('button', { name: 'Submit' });
page.getByRole('textbox', { name: 'Email' });
page.getByRole('heading', { level: 1 });
// Label-based
page.getByLabel('Email address');
page.getByPlaceholder('Enter your email');
// Text-based
page.getByText('Welcome');
page.getByTitle('Close');
page.getByRole('listitem')
.filter({ hasText: 'Product 1' })
.getByRole('button', { name: 'Add' });
page.getByTestId('submit-button');
// pages/login.page.ts
import { Page, Locator, expect } from '@playwright/test';
export class LoginPage {
private readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.submitButton = page.getByRole('button', { name: 'Sign in' });
this.errorMessage = page.getByRole('alert');
}
async goto() {
await this.page.goto('/login');
await expect(this.emailInput).toBeVisible();
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
async getError(): Promise<string | null> {
if (await this.errorMessage.isVisible()) {
return this.errorMessage.textContent();
}
return null;
}
}
// Usage in test
import { test, expect } from '@playwright/test';
import { LoginPage } from './pages/login.page';
test('should login successfully', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('[email protected]', 'password');
await expect(page).toHaveURL('/dashboard');
});
test('should show error for invalid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('[email protected]', 'wrongpassword');
const error = await loginPage.getError();
expect(error).toContain('Invalid credentials');
});
// Auto-waits for element
await expect(page.getByRole('alert')).toBeVisible();
await expect(page.getByRole('button')).toBeEnabled();
await expect(page.getByText('Count: 5')).toBeVisible();
// Negative assertions
await expect(page.getByRole('dialog')).toBeHidden();
await expect(page.getByText('Error')).not.toBeVisible();
// With custom timeout
await expect(page.getByText('Loaded')).toBeVisible({ timeout: 10000 });
// fixtures.ts
import { test as base } from '@playwright/test';
export const test = base.extend<{
authenticatedPage: Page;
}>({
authenticatedPage: async ({ page }, use) => {
await page.goto('/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('password');
await page.getByRole('button', { name: 'Login' }).click();
await page.waitForURL('/dashboard');
await use(page);
},
});
For efficient authentication without UI login each time:
// Setup: Save auth state after login (run once)
// auth.setup.ts
import { test as setup, expect } from '@playwright/test';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('password');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page).toHaveURL('/dashboard');
// Save storage state (cookies, localStorage)
await page.context().storageState({ path: '.auth/user.json' });
});
// playwright.config.ts
export default defineConfig({
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
use: { storageState: '.auth/user.json' },
dependencies: ['setup'],
},
],
});
// Tests automatically have auth state
test('dashboard loads for authenticated user', async ({ page }) => {
await page.goto('/dashboard');
await expect(page.getByText('Welcome back')).toBeVisible();
});
Mock API responses for reliable, fast tests:
import { test, expect } from '@playwright/test';
test('should display mocked user data', async ({ page }) => {
// Mock API response
await page.route('**/api/users', route => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'Test User', email: '[email protected]' }
]),
});
});
await page.goto('/users');
await expect(page.getByText('Test User')).toBeVisible();
});
test('should handle API errors gracefully', async ({ page }) => {
// Mock error response
await page.route('**/api/users', route => {
route.fulfill({
status: 500,
body: JSON.stringify({ error: 'Internal Server Error' }),
});
});
await page.goto('/users');
await expect(page.getByText('Failed to load users')).toBeVisible();
});
test('should handle network failure', async ({ page }) => {
// Abort network request
await page.route('**/api/data', route => route.abort());
await page.goto('/data');
await expect(page.getByText('Network error')).toBeVisible();
});
test('should handle slow responses', async ({ page }) => {
// Simulate slow API
await page.route('**/api/slow', async route => {
await new Promise(resolve => setTimeout(resolve, 3000));
await route.continue();
});
await page.goto('/slow-page');
await expect(page.getByText('Loading...')).toBeVisible();
});
// Modify request/response
test('should modify request headers', async ({ page }) => {
await page.route('**/api/**', route => {
route.continue({
headers: {
...route.request().headers(),
'X-Test-Header': 'test-value',
},
});
});
});
Integrate accessibility audits with @axe-core/playwright:
// Install: npm install @axe-core/playwright
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('should pass accessibility audit', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
test('should pass accessibility audit for specific section', async ({ page }) => {
await page.goto('/dashboard');
const results = await new AxeBuilder({ page })
.include('#main-content')
.exclude('#third-party-widget')
.withTags(['wcag2a', 'wcag2aa'])
.analyze();
expect(results.violations).toEqual([]);
});
// Check specific rules
test('should have proper color contrast', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page })
.withRules(['color-contrast'])
.analyze();
expect(results.violations).toEqual([]);
});
// Detailed violation reporting
test('accessibility check with detailed report', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page }).analyze();
if (results.violations.length > 0) {
console.log('Accessibility violations:');
results.violations.forEach(violation => {
console.log(`- ${violation.id}: ${violation.description}`);
violation.nodes.forEach(node => {
console.log(` Element: ${node.html}`);
console.log(` Fix: ${node.failureSummary}`);
});
});
}
expect(results.violations).toEqual([]);
});
Compare screenshots to detect visual changes:
import { test, expect } from '@playwright/test';
test('homepage visual regression', async ({ page }) => {
await page.goto('/');
// Full page screenshot comparison
await expect(page).toHaveScreenshot('homepage.png');
});
test('component visual regression', async ({ page }) => {
await page.goto('/components');
// Element-specific screenshot
const button = page.getByRole('button', { name: 'Submit' });
await expect(button).toHaveScreenshot('submit-button.png');
});
test('visual with threshold', async ({ page }) => {
await page.goto('/');
// Allow small differences
await expect(page).toHaveScreenshot('homepage.png', {
maxDiffPixels: 100,
threshold: 0.2,
});
});
// Update snapshots: npx playwright test --update-snapshots
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile', use: { ...devices['iPhone 13'] } },
],
webServer: {
command: 'npm run start',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
npx playwright test --debug
npx playwright test --ui
// Capture trace on failure
use: {
trace: 'on-first-retry',
}
// View trace
npx playwright show-trace trace.zip
await page.screenshot({ path: 'screenshot.png', fullPage: true });
networkidle (which fails with WebSockets, long-polling, analytics):
// Bad: networkidle is unreliable
await page.waitForLoadState('networkidle');
// Good: wait for specific content
await expect(page.getByRole('main')).toBeVisible();
await expect(page.getByTestId('data-loaded')).toBeAttached();
test.describe.parallel()When testing forms:
When testing tables/lists:
.filter()The patterns in this skill require the following minimum versions:
| Feature | Minimum Version | Notes | |---------|----------------|-------| | getByRole with name | 1.27+ | Role-based locators with accessible name | | toHaveScreenshot | 1.22+ | Visual regression testing | | storageState | 1.13+ | Authentication state persistence | | @axe-core/playwright | 4.7+ | Accessibility testing integration | | route.fulfill | 1.0+ | Network mocking (stable) | | test.describe.configure | 1.24+ | Parallel/serial test configuration |
Check your Playwright version:
npx playwright --version
# Update Playwright
npm install -D @playwright/test@latest
# Update browsers
npx playwright install
development
Apple Human Interface Guidelines for content display components. Use this skill when the user asks about charts component, collection view, image view, web view, color well, image well, activity view, lockup, data visualization, content display, displaying images, rendering web content, color pickers, or presenting collections of items in Apple apps. Also use when the user says how should I display charts, what's the best way to show images, should I use a web view, how do I build a grid of items, what component shows media, or how do I present a share sheet. Cross-references: hig-foundations for color/typography/accessibility, hig-patterns for data visualization patterns, hig-components-layout for structural containers, hig-platforms for platform-specific component behavior.
tools
Automate HelpDesk tasks via Rube MCP (Composio): list tickets, manage views, use canned responses, and configure custom fields. Always search tools first for current schemas.
testing
Expert Haskell engineer specializing in advanced type systems, pure functional design, and high-reliability software. Use PROACTIVELY for type-level programming, concurrency, and architecture guidance.
tools
GraphQL gives clients exactly the data they need - no more, no less. One endpoint, typed schema, introspection. But the flexibility that makes it powerful also makes it dangerous. Without proper controls, clients can craft queries that bring down your server. This skill covers schema design, resolvers, DataLoader for N+1 prevention, federation for microservices, and client integration with Apollo/urql. Key insight: GraphQL is a contract. The schema is the API documentation. Design it carefully.