Loading...
Loading...
End-to-end, API, and responsive testing for web applications using Playwright with TypeScript. Use when asked to write, run, debug, or maintain Playwright (@playwright/test) TypeScript tests for UI behavior, form submissions, user flows, API validation, responsive design, or visual regression. Covers browser automation, network interception, mocking, Page Object Model, fixtures, and parallel execution.
npx skill4agent add fugazi/test-automation-skills-agents playwright-e2e-testingActivation: This skill is triggered when working with Playwright tests, browser automation, E2E testing, API testing with Playwright, or test infrastructure setup.
request| Requirement | Details |
|---|---|
| Node.js | v18+ recommended |
| Package Manager | npm, yarn, or pnpm |
| Playwright | |
| TypeScript | |
| Browsers | Installed via |
# Initialize new project
npm init playwright@latest
# Or add to existing project
npm install -D @playwright/test
npx playwright install@playwright/testimport { test, expect } from '@playwright/test';
test('user can login', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('user@test.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page).toHaveURL(/.*dashboard/);
});| Priority | Locator | Example |
|---|---|---|
| 1 | Role + accessible name | |
| 2 | Label | |
| 3 | Placeholder | |
| 4 | Text | |
| 5 | Test ID | |
| 6 | CSS (avoid) | |
sleep()// ✅ Web-first assertions (auto-retry)
await expect(page.getByRole('alert')).toBeVisible();
await expect(page).toHaveURL(/dashboard/);
await expect(page.getByTestId('status')).toHaveText('Success!');
// ❌ Avoid manual waits
await page.waitForTimeout(2000); // Bad practicetest.step()test('checkout flow', async ({ page }) => {
await test.step('Add item to cart', async () => {
await page.goto('/products/1');
await page.getByRole('button', { name: 'Add to Cart' }).click();
});
await test.step('Complete checkout', async () => {
await page.goto('/checkout');
await page.getByRole('button', { name: 'Pay Now' }).click();
});
await test.step('Verify confirmation', async () => {
await expect(page.getByRole('heading')).toContainText('Order Confirmed');
});
});// Form submit and wait for navigation (auto-waiting)
await page.getByRole('button', { name: 'Login' }).click();
await expect(page).toHaveURL(/.*dashboard/);
// Form with API response validation
const responsePromise = page.waitForResponse(
r => r.url().includes('/api/login') && r.status() === 200
);
await page.getByRole('button', { name: 'Login' }).click();
const response = await responsePromise;test('API health check', async ({ request }) => {
const response = await request.get('/api/health');
expect(response.ok()).toBeTruthy();
expect(await response.json()).toMatchObject({ status: 'ok' });
});test('handles API error', async ({ page }) => {
await page.route('**/api/users', route =>
route.fulfill({ status: 500, body: JSON.stringify({ error: 'Server error' }) })
);
await page.goto('/users');
await expect(page.getByRole('alert')).toContainText('Something went wrong');
});const viewports = [
{ width: 375, height: 667, name: 'mobile' },
{ width: 768, height: 1024, name: 'tablet' },
{ width: 1280, height: 720, name: 'desktop' },
];
for (const vp of viewports) {
test(`navigation works on ${vp.name}`, async ({ page }) => {
await page.setViewportSize(vp);
await page.goto('/');
// Mobile: hamburger menu
if (vp.width < 768) {
await page.getByRole('button', { name: /menu/i }).click();
}
await page.getByRole('link', { name: 'About' }).click();
await expect(page).toHaveURL(/about/);
});
}playwright.config.tsimport { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
retries: process.env.CI ? 2 : 0,
reporter: [['html'], ['junit', { outputFile: 'results.xml' }]],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: devices['Desktop Chrome'] },
{ name: 'mobile', use: devices['Pixel 5'] },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});| Problem | Cause | Solution |
|---|---|---|
| Element not found | Wrong locator or not rendered | Use |
| Timeout waiting | Element hidden or slow load | Check for overlays, increase timeout, use |
| Flaky tests | Race conditions, animations | Add |
| Strict mode violation | Multiple elements match | Use |
| Screenshots differ | Dynamic content | Mask dynamic areas, use deterministic data |
| CI fails, local passes | Environment differences | Check |
| API mock not working | Route pattern mismatch | Use |
| Command | Description |
|---|---|
| Run all tests headless |
| Open UI mode (interactive) |
| Run with visible browser |
| Run with Playwright Inspector |
| Run tests matching pattern |
| Run specific project |
| Open HTML report |
| Generate tests by recording |
| Debug with Inspector |
| Verbose API logging |
| Document | Content |
|---|---|
| Snippets | Ready-to-use code patterns |
| Locator Strategies | Complete locator guide |
| Page Object Model | POM implementation patterns |
| Debugging Guide | Troubleshooting & debugging techniques |