Writing Tests Intermediate
Learn how to use Claude Code to generate comprehensive Playwright tests from plain English descriptions. From basic page tests to complex user flows, visual regression, API testing, and accessibility audits.
1. Generating Tests from Descriptions
The most powerful feature of combining Playwright with Claude Code is the ability to describe what you want to test and have the test generated automatically. Claude Code reads your application code to understand the UI structure and generates appropriate selectors and assertions.
Claude > Write a Playwright test suite for the shopping cart page.
Test these scenarios:
1. Adding an item to the cart
2. Removing an item from the cart
3. Updating item quantity
4. Verifying the total price calculation
5. Applying a discount code
Look at src/pages/Cart.tsx and src/components/CartItem.tsx
to understand the UI structure.
Claude Code will read the referenced files, identify the elements, and generate a complete test suite:
import { test, expect } from '@playwright/test'; test.describe('Shopping Cart', () => { test.beforeEach(async ({ page }) => { await page.goto('/cart'); }); test('can add item to cart', async ({ page }) => { await page.goto('/products/1'); await page.getByRole('button', { name: 'Add to Cart' }).click(); await expect(page.getByTestId('cart-count')).toHaveText('1'); }); test('can remove item from cart', async ({ page }) => { await page.getByRole('button', { name: 'Remove' }).first().click(); await expect(page.getByText('Your cart is empty')).toBeVisible(); }); test('can update item quantity', async ({ page }) => { await page.getByLabel('Quantity').first().fill('3'); await page.getByLabel('Quantity').first().press('Enter'); await expect(page.getByTestId('item-subtotal').first()) .toContainText('$59.97'); }); test('calculates total price correctly', async ({ page }) => { const total = await page.getByTestId('cart-total').textContent(); expect(parseFloat(total!.replace('$', ''))).toBeGreaterThan(0); }); test('can apply discount code', async ({ page }) => { await page.getByPlaceholder('Discount code').fill('SAVE10'); await page.getByRole('button', { name: 'Apply' }).click(); await expect(page.getByText('10% discount applied')).toBeVisible(); }); });
2. Page Object Model Generation
Claude Code can generate Page Object Model (POM) classes that encapsulate page interactions, making tests more maintainable:
Claude > Read src/pages/Login.tsx and create a Page Object class
for the login page. Include methods for filling credentials,
submitting the form, and checking for error messages.
Save it as tests/pages/login.page.ts
import { Page, Locator, expect } from '@playwright/test'; export class LoginPage { 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'); } async login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.submitButton.click(); } async expectError(message: string) { await expect(this.errorMessage).toContainText(message); } async expectRedirectTo(url: string) { await expect(this.page).toHaveURL(url); } }
3. Testing User Flows
Claude Code excels at generating tests for complex multi-step user flows. Here are common patterns:
Login Flow
import { test, expect } from '@playwright/test'; import { LoginPage } from '../pages/login.page'; test('successful login redirects to dashboard', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.login('user@example.com', 'password123'); await loginPage.expectRedirectTo('/dashboard'); }); test('invalid credentials show error', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.login('user@example.com', 'wrong'); await loginPage.expectError('Invalid credentials'); });
Checkout Flow
Claude > Write a Playwright test for the complete checkout flow:
1. Add a product to the cart from the product listing page
2. Go to the cart and verify the item is there
3. Click checkout and fill in shipping details
4. Enter payment information
5. Confirm the order
6. Verify the order confirmation page
Form Validation
test('form validation shows correct error messages', async ({ page }) => { await page.goto('/register'); // Submit empty form await page.getByRole('button', { name: 'Register' }).click(); // Check validation messages await expect(page.getByText('Email is required')).toBeVisible(); await expect(page.getByText('Password is required')).toBeVisible(); // Test invalid email await page.getByLabel('Email').fill('not-an-email'); await page.getByRole('button', { name: 'Register' }).click(); await expect(page.getByText('Please enter a valid email')).toBeVisible(); });
4. Visual Testing
Playwright supports visual regression testing with screenshot comparison. Claude Code can set this up for you:
test('homepage matches visual snapshot', async ({ page }) => { await page.goto('/'); await expect(page).toHaveScreenshot('homepage.png', { maxDiffPixelRatio: 0.01, }); }); test('product card component snapshot', async ({ page }) => { await page.goto('/products'); const card = page.getByTestId('product-card').first(); await expect(card).toHaveScreenshot('product-card.png'); });
5. API Testing
Playwright can also test APIs directly, and Claude Code can generate these tests alongside UI tests:
import { test, expect } from '@playwright/test'; test.describe('API Tests', () => { test('GET /api/products returns product list', async ({ request }) => { const response = await request.get('/api/products'); expect(response.ok()).toBeTruthy(); const products = await response.json(); expect(products.length).toBeGreaterThan(0); expect(products[0]).toHaveProperty('name'); expect(products[0]).toHaveProperty('price'); }); test('POST /api/orders creates an order', async ({ request }) => { const response = await request.post('/api/orders', { data: { productId: 1, quantity: 2 } }); expect(response.status()).toBe(201); }); });
6. Accessibility Testing
Use Playwright with @axe-core/playwright for automated accessibility testing:
Claude > Install @axe-core/playwright and write accessibility tests
for the homepage, login page, and product listing page.
Check for WCAG 2.1 AA compliance.
import { test, expect } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; test('homepage has no accessibility violations', async ({ page }) => { await page.goto('/'); const results = await new AxeBuilder({ page }) .withTags(['wcag2a', 'wcag2aa']) .analyze(); expect(results.violations).toEqual([]); });
Practice Exercise
Ask Claude Code to generate a test suite for a form on your application. Include tests for successful submission, validation errors, and accessibility. Run the tests and iterate on any failures.
Next: Debugging →