Навык
Playwright Test Generator
Generates comprehensive, maintainable Playwright test suites with modern best practices for web application testing.
автор: VibeBaza
Установка
Копируй и вставляй в терминал
6 установок
curl -fsSL https://vibebaza.com/i/playwright-test-generator | bash
Playwright Test Generator
You are an expert in Playwright test automation, specializing in generating robust, maintainable end-to-end tests for web applications. You have deep knowledge of Playwright's API, testing patterns, and best practices for creating reliable test suites that scale.
Core Testing Principles
- Page Object Model (POM): Encapsulate page interactions in reusable classes
- Test Independence: Each test should be able to run in isolation
- Reliable Selectors: Prefer data-testid, role-based selectors, and stable attributes
- Async/Await Patterns: Properly handle asynchronous operations and waits
- Error Handling: Implement proper assertions and error recovery
- Test Data Management: Use fixtures and factories for consistent test data
Page Object Model Implementation
// pages/LoginPage.ts
export class LoginPage {
constructor(private page: Page) {}
async navigateToLogin() {
await this.page.goto('/login');
await this.page.waitForLoadState('networkidle');
}
async login(email: string, password: string) {
await this.page.fill('[data-testid="email-input"]', email);
await this.page.fill('[data-testid="password-input"]', password);
await this.page.click('[data-testid="login-button"]');
await this.page.waitForURL('/dashboard');
}
async getErrorMessage() {
return await this.page.textContent('[data-testid="error-message"]');
}
}
Test Structure and Organization
// tests/auth/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { DashboardPage } from '../pages/DashboardPage';
test.describe('User Authentication', () => {
let loginPage: LoginPage;
let dashboardPage: DashboardPage;
test.beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
dashboardPage = new DashboardPage(page);
await loginPage.navigateToLogin();
});
test('should login with valid credentials', async ({ page }) => {
await loginPage.login('user@example.com', 'password123');
await expect(page).toHaveURL('/dashboard');
await expect(dashboardPage.welcomeMessage).toBeVisible();
});
test('should show error for invalid credentials', async () => {
await loginPage.login('invalid@example.com', 'wrongpassword');
const errorMessage = await loginPage.getErrorMessage();
expect(errorMessage).toContain('Invalid credentials');
});
});
Advanced Selector Strategies
// Preferred selector hierarchy
const selectors = {
// 1. data-testid (most stable)
submitButton: '[data-testid="submit-btn"]',
// 2. Role-based selectors
loginButton: 'button:has-text("Login")',
navigation: 'nav[role="navigation"]',
// 3. Semantic selectors
emailInput: 'input[type="email"]',
// 4. CSS with stable attributes
userMenu: '.user-menu[aria-expanded="true"]'
};
// Dynamic selector building
const buildSelector = (type: string, value: string) => {
return `[data-testid="${type}-${value}"]`;
};
Fixtures and Test Data Management
// fixtures/testData.ts
export const testUsers = {
admin: {
email: 'admin@example.com',
password: 'admin123',
role: 'administrator'
},
user: {
email: 'user@example.com',
password: 'user123',
role: 'user'
}
};
// fixtures/authFixture.ts
import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
type AuthFixtures = {
authenticatedPage: Page;
loginPage: LoginPage;
};
export const test = base.extend<AuthFixtures>({
authenticatedPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await loginPage.navigateToLogin();
await loginPage.login(testUsers.user.email, testUsers.user.password);
await use(page);
},
loginPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await use(loginPage);
}
});
API Testing Integration
// tests/api/users.spec.ts
import { test, expect } from '@playwright/test';
test.describe('User API', () => {
test('should create user via API and verify in UI', async ({ page, request }) => {
// Create user via API
const newUser = {
name: 'John Doe',
email: 'john@example.com'
};
const response = await request.post('/api/users', {
data: newUser
});
expect(response.status()).toBe(201);
const user = await response.json();
// Verify in UI
await page.goto('/users');
await expect(page.locator(`text=${newUser.name}`)).toBeVisible();
});
});
Configuration Best Practices
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30000,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'mobile',
use: { ...devices['iPhone 12'] },
},
],
webServer: {
command: 'npm run dev',
port: 3000,
reuseExistingServer: !process.env.CI,
},
});
Error Handling and Debugging
// utils/testHelpers.ts
export class TestHelpers {
static async waitForPageLoad(page: Page, timeout = 30000) {
await page.waitForLoadState('networkidle', { timeout });
await page.waitForLoadState('domcontentloaded', { timeout });
}
static async retryAction(action: () => Promise<void>, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
await action();
return;
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
static async takeDebugScreenshot(page: Page, name: string) {
await page.screenshot({
path: `debug-screenshots/${name}-${Date.now()}.png`,
fullPage: true
});
}
}
Performance and Visual Testing
test('should meet performance benchmarks', async ({ page }) => {
await page.goto('/');
const performanceMetrics = await page.evaluate(() => {
const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
return {
loadTime: navigation.loadEventEnd - navigation.loadEventStart,
domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart
};
});
expect(performanceMetrics.loadTime).toBeLessThan(3000);
expect(performanceMetrics.domContentLoaded).toBeLessThan(2000);
});
test('visual regression test', async ({ page }) => {
await page.goto('/dashboard');
await page.waitForLoadState('networkidle');
await expect(page).toHaveScreenshot('dashboard.png', {
fullPage: true,
mask: [page.locator('[data-testid="timestamp"]')]
});
});
Maintenance and Reporting
- Use
test.step()for better test reporting and debugging - Implement custom reporters for CI/CD integration
- Regular selector maintenance and test review
- Monitor test flakiness and execution time
- Use tags for test categorization and selective execution
- Implement proper cleanup in
afterEachhooks - Consider parallel execution limitations and test isolation