vineguard-mcp-server-standalone
Version:
VineGuard MCP Server v2.1 - Intelligent QA Workflow System with advanced test generation for Jest/RTL, Cypress, and Playwright. Features smart project analysis, progressive testing strategies, and comprehensive quality patterns for React/Vue/Angular proje
1,152 lines (940 loc) • 40.6 kB
JavaScript
/**
* Enhanced Playwright test generator
* Supports E2E, API testing, visual testing, and parallel execution patterns
*/
export class PlaywrightGenerator {
/**
* Generate Playwright test based on test type
*/
static generateTest(options) {
const { testType } = options;
switch (testType) {
case 'e2e':
return this.generateE2ETest(options);
case 'api':
return this.generateApiTest(options);
case 'visual':
return this.generateVisualTest(options);
case 'mobile':
return this.generateMobileTest(options);
case 'performance':
return this.generatePerformanceTest(options);
case 'smoke':
return this.generateSmokeTest(options);
default:
return this.generateBasicTest(options);
}
}
/**
* Generate comprehensive E2E test
*/
static generateE2ETest(options) {
const { featureName, hasAuth = false, hasApi = false, usePageObjects = true } = options;
return `import { test, expect, Page, BrowserContext } from '@playwright/test';
${usePageObjects ? `import { ${featureName}Page } from '../page-objects/${featureName}Page';` : ''}
${hasAuth ? `import { AuthHelper } from '../helpers/auth-helper';` : ''}
test.describe('${featureName} - E2E Tests', () => {
let context: BrowserContext;
let page: Page;
${usePageObjects ? `let ${featureName.toLowerCase()}Page: ${featureName}Page;` : ''}
test.beforeAll(async ({ browser }) => {
// Create a new browser context for test isolation
context = await browser.newContext({
// Configure context options
viewport: { width: 1280, height: 720 },
locale: 'en-US',
timezoneId: 'America/New_York',
permissions: ['geolocation'],
geolocation: { latitude: 40.7128, longitude: -74.0060 },
});
${hasAuth ? `
// Setup authentication
await AuthHelper.setupAuthState(context);` : ''}
});
test.beforeEach(async () => {
page = await context.newPage();
${usePageObjects ? `${featureName.toLowerCase()}Page = new ${featureName}Page(page);` : ''}
${hasApi ? `
// Setup API route interceptors
await page.route('/api/users/**', async (route) => {
if (route.request().method() === 'GET') {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'Test User', email: 'test@example.com' }
])
});
} else {
await route.continue();
}
});` : ''}
// Navigate to the feature page
${usePageObjects ? `await ${featureName.toLowerCase()}Page.goto();` : `await page.goto('/${featureName.toLowerCase()}');`}
});
test.afterEach(async () => {
// Cleanup after each test
await page.close();
});
test.afterAll(async () => {
await context.close();
});
test.describe('Happy Path', () => {
test('should complete main user journey successfully', async () => {
// Step 1: Verify page loads correctly
${usePageObjects ? `
await expect(${featureName.toLowerCase()}Page.heading).toBeVisible();
await expect(${featureName.toLowerCase()}Page.heading).toContainText('${featureName}');` : `
await expect(page.locator('h1')).toBeVisible();
await expect(page.locator('h1')).toContainText('${featureName}');`}
// Step 2: Fill out form with valid data
${usePageObjects ? `
await ${featureName.toLowerCase()}Page.fillForm({
name: 'Test User',
email: 'test@example.com',
message: 'This is a test message'
});` : `
await page.fill('[data-testid="name-input"]', 'Test User');
await page.fill('[data-testid="email-input"]', 'test@example.com');
await page.fill('[data-testid="message-input"]', 'This is a test message');`}
// Step 3: Submit form
${usePageObjects ? `await ${featureName.toLowerCase()}Page.submitForm();` : `await page.click('[data-testid="submit-button"]');`}
${hasApi ? `
// Wait for API call to complete
await page.waitForResponse('/api/users');` : ''}
// Step 4: Verify success state
${usePageObjects ? `
await expect(${featureName.toLowerCase()}Page.successMessage).toBeVisible();
await expect(${featureName.toLowerCase()}Page.successMessage).toContainText('Success');` : `
await expect(page.locator('[data-testid="success-message"]')).toBeVisible();
await expect(page.locator('[data-testid="success-message"]')).toContainText('Success');`}
// Step 5: Verify URL change (if applicable)
await expect(page).toHaveURL(/success/);
});
test('should handle multi-step workflow', async () => {
// Test multi-page flows
${usePageObjects ? `
await ${featureName.toLowerCase()}Page.goToStep(1);
await expect(page).toHaveURL(/step-1/);
await ${featureName.toLowerCase()}Page.completeStep1();
await ${featureName.toLowerCase()}Page.nextStep();
await expect(page).toHaveURL(/step-2/);
await ${featureName.toLowerCase()}Page.completeStep2();
await ${featureName.toLowerCase()}Page.finish();
await expect(page).toHaveURL(/complete/);` : `
await page.click('[data-testid="step-1-button"]');
await expect(page).toHaveURL(/step-1/);
await page.fill('[data-testid="step-1-input"]', 'Step 1 data');
await page.click('[data-testid="next-button"]');
await expect(page).toHaveURL(/step-2/);
await page.fill('[data-testid="step-2-input"]', 'Step 2 data');
await page.click('[data-testid="finish-button"]');
await expect(page).toHaveURL(/complete/);`}
});
});
test.describe('Error Handling', () => {
test('should handle form validation errors', async () => {
// Submit form without required fields
${usePageObjects ? `await ${featureName.toLowerCase()}Page.submitForm();` : `await page.click('[data-testid="submit-button"]');`}
// Verify validation errors appear
await expect(page.locator('[data-testid="name-error"]')).toBeVisible();
await expect(page.locator('[data-testid="name-error"]')).toContainText('Name is required');
await expect(page.locator('[data-testid="email-error"]')).toBeVisible();
await expect(page.locator('[data-testid="email-error"]')).toContainText('Email is required');
});
${hasApi ? `
test('should handle API errors gracefully', async () => {
// Mock API error response
await page.route('/api/users', async (route) => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ error: 'Internal server error' })
});
});
${usePageObjects ? `
await ${featureName.toLowerCase()}Page.fillForm({
name: 'Test User',
email: 'test@example.com'
});
await ${featureName.toLowerCase()}Page.submitForm();` : `
await page.fill('[data-testid="name-input"]', 'Test User');
await page.fill('[data-testid="email-input"]', 'test@example.com');
await page.click('[data-testid="submit-button"]');`}
// Verify error handling
await expect(page.locator('[data-testid="error-message"]')).toBeVisible();
await expect(page.locator('[data-testid="error-message"]')).toContainText('Something went wrong');
});` : ''}
test('should handle network failures', async () => {
// Simulate network failure
await page.context().setOffline(true);
${usePageObjects ? `await ${featureName.toLowerCase()}Page.submitForm();` : `await page.click('[data-testid="submit-button"]');`}
await expect(page.locator('[data-testid="network-error"]')).toBeVisible();
// Restore network
await page.context().setOffline(false);
});
});
test.describe('Accessibility', () => {
test('should be keyboard navigable', async () => {
// Test tab navigation
await page.keyboard.press('Tab');
await expect(page.locator(':focus')).toBeVisible();
// Navigate through all focusable elements
const focusableElements = await page.locator('button, input, select, textarea, a[href]').all();
for (let i = 0; i < focusableElements.length; i++) {
await page.keyboard.press('Tab');
const focused = page.locator(':focus');
await expect(focused).toBeVisible();
}
});
test('should support screen readers', async () => {
// Check ARIA attributes
await expect(page.locator('[data-testid="main-content"]')).toHaveAttribute('aria-label');
// Check heading hierarchy
const headings = await page.locator('h1, h2, h3, h4, h5, h6').all();
expect(headings.length).toBeGreaterThan(0);
// Check form labels
const inputs = await page.locator('input').all();
for (const input of inputs) {
await expect(input).toHaveAttribute('aria-label');
}
});
});
test.describe('Performance', () => {
test('should load within acceptable time', async () => {
const startTime = Date.now();
${usePageObjects ? `await ${featureName.toLowerCase()}Page.goto();` : `await page.goto('/${featureName.toLowerCase()}');`}
// Wait for page to be fully loaded
await page.waitForLoadState('networkidle');
const loadTime = Date.now() - startTime;
expect(loadTime).toBeLessThan(5000); // 5 second threshold
});
test('should have good Core Web Vitals', async () => {
${usePageObjects ? `await ${featureName.toLowerCase()}Page.goto();` : `await page.goto('/${featureName.toLowerCase()}');`}
// Measure LCP (Largest Contentful Paint)
const lcp = await page.evaluate(() => {
return new Promise((resolve) => {
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
resolve(lastEntry.startTime);
}).observe({ entryTypes: ['largest-contentful-paint'] });
});
});
expect(lcp).toBeLessThan(2500); // 2.5 second threshold for good LCP
});
});
});
${usePageObjects ? `
// Page Object Model class
class ${featureName}Page {
constructor(private page: Page) {}
// Locators
get heading() { return this.page.locator('h1'); }
get nameInput() { return this.page.locator('[data-testid="name-input"]'); }
get emailInput() { return this.page.locator('[data-testid="email-input"]'); }
get messageInput() { return this.page.locator('[data-testid="message-input"]'); }
get submitButton() { return this.page.locator('[data-testid="submit-button"]'); }
get successMessage() { return this.page.locator('[data-testid="success-message"]'); }
// Actions
async goto() {
await this.page.goto('/${featureName.toLowerCase()}');
}
async fillForm(data: { name?: string; email?: string; message?: string }) {
if (data.name) await this.nameInput.fill(data.name);
if (data.email) await this.emailInput.fill(data.email);
if (data.message) await this.messageInput.fill(data.message);
}
async submitForm() {
await this.submitButton.click();
}
async goToStep(step: number) {
await this.page.click(\`[data-testid="step-\${step}-button"]\`);
}
async nextStep() {
await this.page.click('[data-testid="next-button"]');
}
async completeStep1() {
await this.page.fill('[data-testid="step-1-input"]', 'Step 1 data');
}
async completeStep2() {
await this.page.fill('[data-testid="step-2-input"]', 'Step 2 data');
}
async finish() {
await this.page.click('[data-testid="finish-button"]');
}
}` : ''}`;
}
/**
* Generate API test
*/
static generateApiTest(options) {
const { featureName, hasAuth = false } = options;
return `import { test, expect, APIRequestContext } from '@playwright/test';
test.describe('${featureName} - API Tests', () => {
let apiContext: APIRequestContext;
let authToken: string;
test.beforeAll(async ({ playwright }) => {
// Create API request context
apiContext = await playwright.request.newContext({
baseURL: process.env.API_BASE_URL || 'http://localhost:3000/api',
extraHTTPHeaders: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
${hasAuth ? `
// Authenticate and get token
const authResponse = await apiContext.post('/auth/login', {
data: {
email: 'test@example.com',
password: 'password123'
}
});
expect(authResponse.ok()).toBeTruthy();
const authData = await authResponse.json();
authToken = authData.token;` : ''}
});
test.afterAll(async () => {
await apiContext.dispose();
});
test.describe('CRUD Operations', () => {
let createdId: number;
test('should create a new ${featureName.toLowerCase()} record', async () => {
const newRecord = {
name: 'Test ${featureName}',
description: 'Test Description',
category: 'test',
status: 'active'
};
const response = await apiContext.post('/${featureName.toLowerCase()}', {
data: newRecord,
${hasAuth ? `headers: { Authorization: \`Bearer \${authToken}\` }` : ''}
});
expect(response.ok()).toBeTruthy();
const responseData = await response.json();
expect(responseData).toMatchObject({
id: expect.any(Number),
name: newRecord.name,
description: newRecord.description,
category: newRecord.category,
status: newRecord.status,
createdAt: expect.any(String),
updatedAt: expect.any(String)
});
createdId = responseData.id;
});
test('should retrieve all ${featureName.toLowerCase()} records', async () => {
const response = await apiContext.get('/${featureName.toLowerCase()}', {
${hasAuth ? `headers: { Authorization: \`Bearer \${authToken}\` }` : ''}
});
expect(response.ok()).toBeTruthy();
const responseData = await response.json();
expect(Array.isArray(responseData)).toBeTruthy();
expect(responseData.length).toBeGreaterThan(0);
// Verify structure of first item
expect(responseData[0]).toMatchObject({
id: expect.any(Number),
name: expect.any(String),
createdAt: expect.any(String)
});
});
test('should retrieve a specific ${featureName.toLowerCase()} record', async () => {
const response = await apiContext.get(\`/${featureName.toLowerCase()}/\${createdId}\`, {
${hasAuth ? `headers: { Authorization: \`Bearer \${authToken}\` }` : ''}
});
expect(response.ok()).toBeTruthy();
const responseData = await response.json();
expect(responseData.id).toBe(createdId);
expect(responseData.name).toBe('Test ${featureName}');
});
test('should update a ${featureName.toLowerCase()} record', async () => {
const updateData = {
name: 'Updated ${featureName}',
description: 'Updated Description'
};
const response = await apiContext.put(\`/${featureName.toLowerCase()}/\${createdId}\`, {
data: updateData,
${hasAuth ? `headers: { Authorization: \`Bearer \${authToken}\` }` : ''}
});
expect(response.ok()).toBeTruthy();
const responseData = await response.json();
expect(responseData.id).toBe(createdId);
expect(responseData.name).toBe(updateData.name);
expect(responseData.description).toBe(updateData.description);
});
test('should partially update a ${featureName.toLowerCase()} record', async () => {
const patchData = {
status: 'inactive'
};
const response = await apiContext.patch(\`/${featureName.toLowerCase()}/\${createdId}\`, {
data: patchData,
${hasAuth ? `headers: { Authorization: \`Bearer \${authToken}\` }` : ''}
});
expect(response.ok()).toBeTruthy();
const responseData = await response.json();
expect(responseData.status).toBe('inactive');
expect(responseData.name).toBe('Updated ${featureName}'); // Should remain unchanged
});
test('should delete a ${featureName.toLowerCase()} record', async () => {
const response = await apiContext.delete(\`/${featureName.toLowerCase()}/\${createdId}\`, {
${hasAuth ? `headers: { Authorization: \`Bearer \${authToken}\` }` : ''}
});
expect(response.ok()).toBeTruthy();
expect(response.status()).toBe(204);
// Verify record is deleted
const getResponse = await apiContext.get(\`/${featureName.toLowerCase()}/\${createdId}\`, {
${hasAuth ? `headers: { Authorization: \`Bearer \${authToken}\` }` : ''}
});
expect(getResponse.status()).toBe(404);
});
});
test.describe('Validation & Error Handling', () => {
test('should validate required fields', async () => {
const invalidData = {}; // Missing required fields
const response = await apiContext.post('/${featureName.toLowerCase()}', {
data: invalidData,
${hasAuth ? `headers: { Authorization: \`Bearer \${authToken}\` }` : ''}
});
expect(response.status()).toBe(400);
const responseData = await response.json();
expect(responseData).toHaveProperty('errors');
expect(Array.isArray(responseData.errors)).toBeTruthy();
});
test('should handle non-existent records', async () => {
const response = await apiContext.get('/${featureName.toLowerCase()}/999999', {
${hasAuth ? `headers: { Authorization: \`Bearer \${authToken}\` }` : ''}
});
expect(response.status()).toBe(404);
const responseData = await response.json();
expect(responseData).toHaveProperty('error');
expect(responseData.error).toContain('not found');
});
${hasAuth ? `
test('should handle unauthorized requests', async () => {
const response = await apiContext.get('/${featureName.toLowerCase()}');
// No authorization header
expect(response.status()).toBe(401);
const responseData = await response.json();
expect(responseData).toHaveProperty('error');
});
test('should handle invalid tokens', async () => {
const response = await apiContext.get('/${featureName.toLowerCase()}', {
headers: { Authorization: 'Bearer invalid-token' }
});
expect(response.status()).toBe(401);
});` : ''}
});
test.describe('Performance & Load Testing', () => {
test('should handle multiple concurrent requests', async () => {
const requests = Array.from({ length: 10 }, (_, i) =>
apiContext.get('/${featureName.toLowerCase()}', {
${hasAuth ? `headers: { Authorization: \`Bearer \${authToken}\` }` : ''}
})
);
const responses = await Promise.all(requests);
responses.forEach(response => {
expect(response.ok()).toBeTruthy();
});
});
test('should respond within acceptable time limits', async () => {
const startTime = Date.now();
const response = await apiContext.get('/${featureName.toLowerCase()}', {
${hasAuth ? `headers: { Authorization: \`Bearer \${authToken}\` }` : ''}
});
const endTime = Date.now();
const responseTime = endTime - startTime;
expect(response.ok()).toBeTruthy();
expect(responseTime).toBeLessThan(2000); // 2 second threshold
});
});
test.describe('Data Integrity', () => {
test('should maintain data consistency across operations', async () => {
// Create a record
const createResponse = await apiContext.post('/${featureName.toLowerCase()}', {
data: { name: 'Consistency Test', description: 'Test' },
${hasAuth ? `headers: { Authorization: \`Bearer \${authToken}\` }` : ''}
});
const createdRecord = await createResponse.json();
// Read it back
const readResponse = await apiContext.get(\`/${featureName.toLowerCase()}/\${createdRecord.id}\`, {
${hasAuth ? `headers: { Authorization: \`Bearer \${authToken}\` }` : ''}
});
const readRecord = await readResponse.json();
// Verify data consistency
expect(readRecord).toMatchObject(createdRecord);
// Cleanup
await apiContext.delete(\`/${featureName.toLowerCase()}/\${createdRecord.id}\`, {
${hasAuth ? `headers: { Authorization: \`Bearer \${authToken}\` }` : ''}
});
});
});
});`;
}
/**
* Generate visual regression test
*/
static generateVisualTest(options) {
const { featureName } = options;
return `import { test, expect } from '@playwright/test';
test.describe('${featureName} - Visual Tests', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/${featureName.toLowerCase()}');
// Wait for fonts and images to load
await page.waitForLoadState('networkidle');
// Wait for any animations to complete
await page.waitForTimeout(1000);
});
test.describe('Desktop Views', () => {
test('should match baseline for default state', async ({ page }) => {
await expect(page).toHaveScreenshot('${featureName.toLowerCase()}-desktop-default.png');
});
test('should match baseline for interaction states', async ({ page }) => {
// Hover state
await page.hover('[data-testid="interactive-element"]');
await expect(page.locator('[data-testid="interactive-element"]')).toHaveScreenshot('${featureName.toLowerCase()}-hover.png');
// Focus state
await page.focus('[data-testid="input-element"]');
await expect(page.locator('[data-testid="input-element"]')).toHaveScreenshot('${featureName.toLowerCase()}-focus.png');
// Active state
await page.click('[data-testid="button-element"]');
await expect(page.locator('[data-testid="button-element"]')).toHaveScreenshot('${featureName.toLowerCase()}-active.png');
});
test('should match baseline for form states', async ({ page }) => {
// Empty form
await expect(page.locator('[data-testid="form"]')).toHaveScreenshot('${featureName.toLowerCase()}-form-empty.png');
// Filled form
await page.fill('[data-testid="name-input"]', 'Test User');
await page.fill('[data-testid="email-input"]', 'test@example.com');
await expect(page.locator('[data-testid="form"]')).toHaveScreenshot('${featureName.toLowerCase()}-form-filled.png');
// Error state
await page.click('[data-testid="submit-button"]');
await page.fill('[data-testid="name-input"]', '');
await page.click('[data-testid="submit-button"]');
await expect(page.locator('[data-testid="form"]')).toHaveScreenshot('${featureName.toLowerCase()}-form-error.png');
});
});
test.describe('Mobile Views', () => {
test.use({ viewport: { width: 375, height: 667 } });
test('should match baseline for mobile layout', async ({ page }) => {
await expect(page).toHaveScreenshot('${featureName.toLowerCase()}-mobile-default.png');
});
test('should match baseline for mobile navigation', async ({ page }) => {
await page.click('[data-testid="mobile-menu-toggle"]');
await expect(page).toHaveScreenshot('${featureName.toLowerCase()}-mobile-menu-open.png');
});
});
test.describe('Tablet Views', () => {
test.use({ viewport: { width: 768, height: 1024 } });
test('should match baseline for tablet layout', async ({ page }) => {
await expect(page).toHaveScreenshot('${featureName.toLowerCase()}-tablet-default.png');
});
});
test.describe('Dark Mode', () => {
test.use({ colorScheme: 'dark' });
test('should match baseline for dark mode', async ({ page }) => {
await expect(page).toHaveScreenshot('${featureName.toLowerCase()}-dark-mode.png');
});
});
test.describe('High Contrast Mode', () => {
test('should match baseline for high contrast', async ({ page }) => {
// Add high contrast class or CSS
await page.addStyleTag({
content: \`
* {
filter: contrast(150%) !important;
}
\`
});
await expect(page).toHaveScreenshot('${featureName.toLowerCase()}-high-contrast.png');
});
});
test.describe('Reduced Motion', () => {
test.use({ reducedMotion: 'reduce' });
test('should match baseline with reduced motion', async ({ page }) => {
await expect(page).toHaveScreenshot('${featureName.toLowerCase()}-reduced-motion.png');
});
});
test.describe('Different Browser Engines', () => {
['chromium', 'firefox', 'webkit'].forEach(browserName => {
test(\`should match baseline in \${browserName}\`, async ({ page, browserName: currentBrowser }) => {
test.skip(currentBrowser !== browserName, \`This test runs only in \${browserName}\`);
await expect(page).toHaveScreenshot(\`${featureName.toLowerCase()}-\${browserName}.png\`);
});
});
});
});`;
}
/**
* Generate mobile-specific test
*/
static generateMobileTest(options) {
const { featureName } = options;
return `import { test, expect, devices } from '@playwright/test';
// Test on multiple mobile devices
const mobileDevices = [
devices['iPhone 12'],
devices['iPhone 12 Pro'],
devices['Pixel 5'],
devices['Samsung Galaxy S21'],
];
mobileDevices.forEach(device => {
test.describe(\`${featureName} - Mobile Tests (\${device.name})\`, () => {
test.use({ ...device });
test.beforeEach(async ({ page }) => {
await page.goto('/${featureName.toLowerCase()}');
});
test('should display mobile layout correctly', async ({ page }) => {
// Verify mobile-specific elements
await expect(page.locator('[data-testid="mobile-nav"]')).toBeVisible();
await expect(page.locator('[data-testid="desktop-nav"]')).not.toBeVisible();
// Check mobile menu functionality
await page.click('[data-testid="mobile-menu-toggle"]');
await expect(page.locator('[data-testid="mobile-menu"]')).toBeVisible();
});
test('should handle touch interactions', async ({ page }) => {
// Test tap interactions
await page.tap('[data-testid="touch-target"]');
await expect(page.locator('[data-testid="touch-feedback"]')).toBeVisible();
// Test swipe gestures (if applicable)
await page.touchscreen.tap(100, 100);
await page.mouse.move(200, 100);
await page.mouse.up();
});
test('should handle form input on mobile', async ({ page }) => {
// Test mobile keyboard interactions
await page.tap('[data-testid="mobile-input"]');
await page.keyboard.type('Mobile test input');
await expect(page.locator('[data-testid="mobile-input"]')).toHaveValue('Mobile test input');
// Test mobile-specific input types
await page.tap('[data-testid="email-input"]');
await page.keyboard.type('mobile@test.com');
await page.tap('[data-testid="tel-input"]');
await page.keyboard.type('555-0123');
});
test('should work in landscape orientation', async ({ page }) => {
// Rotate to landscape
await page.setViewportSize({ width: device.viewport.height, height: device.viewport.width });
// Verify layout adapts
await expect(page.locator('[data-testid="landscape-layout"]')).toBeVisible();
});
test('should handle mobile-specific gestures', async ({ page }) => {
// Test pull-to-refresh (if applicable)
const startY = 50;
const endY = 200;
await page.touchscreen.tap(100, startY);
await page.touchscreen.move(100, endY);
await page.touchscreen.end();
// Verify refresh action
await expect(page.locator('[data-testid="refresh-indicator"]')).toBeVisible();
});
test('should have appropriate touch target sizes', async ({ page }) => {
// Verify minimum touch target size (44x44 CSS pixels)
const touchTargets = await page.locator('[data-testid*="button"], [data-testid*="link"], [data-testid*="touch"]').all();
for (const target of touchTargets) {
const box = await target.boundingBox();
if (box) {
expect(box.width).toBeGreaterThanOrEqual(44);
expect(box.height).toBeGreaterThanOrEqual(44);
}
}
});
});
});
test.describe('${featureName} - Cross-Device Compatibility', () => {
test('should maintain functionality across different screen sizes', async ({ page }) => {
const viewports = [
{ width: 320, height: 568 }, // iPhone SE
{ width: 375, height: 667 }, // iPhone 8
{ width: 414, height: 896 }, // iPhone 11
{ width: 768, height: 1024 }, // iPad
];
for (const viewport of viewports) {
await page.setViewportSize(viewport);
await page.goto('/${featureName.toLowerCase()}');
// Verify core functionality works at this size
await expect(page.locator('[data-testid="main-content"]')).toBeVisible();
// Test interaction
await page.click('[data-testid="primary-action"]');
await expect(page.locator('[data-testid="action-result"]')).toBeVisible();
}
});
});`;
}
/**
* Generate performance test
*/
static generatePerformanceTest(options) {
const { featureName } = options;
return `import { test, expect } from '@playwright/test';
test.describe('${featureName} - Performance Tests', () => {
test('should meet Core Web Vitals thresholds', async ({ page }) => {
await page.goto('/${featureName.toLowerCase()}');
// Measure and verify LCP (Largest Contentful Paint)
const lcp = await page.evaluate(() => {
return new Promise((resolve) => {
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
resolve(lastEntry.startTime);
}).observe({ entryTypes: ['largest-contentful-paint'] });
});
});
expect(lcp).toBeLessThan(2500); // Good LCP threshold
// Measure and verify FID (First Input Delay)
const fidPromise = page.evaluate(() => {
return new Promise((resolve) => {
new PerformanceObserver((entryList) => {
const firstEntry = entryList.getEntries()[0];
resolve(firstEntry.processingStart - firstEntry.startTime);
}).observe({ entryTypes: ['first-input'] });
});
});
await page.click('[data-testid="first-interaction"]');
const fid = await fidPromise;
expect(fid).toBeLessThan(100); // Good FID threshold
// Measure and verify CLS (Cumulative Layout Shift)
const cls = await page.evaluate(() => {
return new Promise((resolve) => {
let clsValue = 0;
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
resolve(clsValue);
}).observe({ entryTypes: ['layout-shift'] });
// Trigger layout shifts if any
setTimeout(() => resolve(clsValue), 5000);
});
});
expect(cls).toBeLessThan(0.1); // Good CLS threshold
});
test('should load resources efficiently', async ({ page }) => {
// Enable request interception to monitor network
const requests: string[] = [];
page.on('request', request => {
requests.push(request.url());
});
await page.goto('/${featureName.toLowerCase()}');
await page.waitForLoadState('networkidle');
// Analyze resource loading
const resourceSizes = await page.evaluate(() => {
const resources = performance.getEntriesByType('resource');
return resources.map(resource => ({
name: resource.name,
transferSize: (resource as PerformanceResourceTiming).transferSize,
decodedBodySize: (resource as PerformanceResourceTiming).decodedBodySize
}));
});
// Verify total resource size is reasonable
const totalSize = resourceSizes.reduce((sum, resource) => sum + resource.transferSize, 0);
expect(totalSize).toBeLessThan(5 * 1024 * 1024); // 5MB threshold
// Check for duplicate resources
const resourceNames = resourceSizes.map(r => r.name);
const uniqueResources = new Set(resourceNames);
expect(uniqueResources.size).toBe(resourceNames.length);
});
test('should handle large datasets efficiently', async ({ page }) => {
// Mock large dataset
await page.route('/api/large-dataset', async route => {
const largeData = Array.from({ length: 1000 }, (_, i) => ({
id: i,
name: \`Item \${i}\`,
description: \`Description for item \${i}\`
}));
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(largeData)
});
});
const startTime = Date.now();
await page.goto('/${featureName.toLowerCase()}/large-dataset');
// Wait for data to load and render
await page.waitForSelector('[data-testid="dataset-loaded"]');
const loadTime = Date.now() - startTime;
expect(loadTime).toBeLessThan(10000); // 10 second threshold for large datasets
// Verify virtual scrolling or pagination is working
const visibleItems = await page.locator('[data-testid="dataset-item"]').count();
expect(visibleItems).toBeLessThan(100); // Should not render all 1000 items
});
test('should handle memory efficiently during interactions', async ({ page }) => {
await page.goto('/${featureName.toLowerCase()}');
// Get initial memory usage
const initialMemory = await page.evaluate(() => {
return (performance as any).memory?.usedJSHeapSize || 0;
});
// Perform memory-intensive operations
for (let i = 0; i < 10; i++) {
await page.click('[data-testid="memory-intensive-action"]');
await page.waitForTimeout(500);
}
// Check memory usage after operations
const finalMemory = await page.evaluate(() => {
return (performance as any).memory?.usedJSHeapSize || 0;
});
// Memory should not increase dramatically
const memoryIncrease = finalMemory - initialMemory;
expect(memoryIncrease).toBeLessThan(50 * 1024 * 1024); // 50MB threshold
});
test('should optimize image loading', async ({ page }) => {
await page.goto('/${featureName.toLowerCase()}');
// Check that images use modern formats and are optimized
const images = await page.locator('img').all();
for (const img of images) {
const src = await img.getAttribute('src');
const loading = await img.getAttribute('loading');
// Verify lazy loading is used where appropriate
if (src && !src.includes('above-fold')) {
expect(loading).toBe('lazy');
}
// Check for responsive images
const srcset = await img.getAttribute('srcset');
if (srcset) {
expect(srcset).toContain('1x');
expect(srcset).toContain('2x');
}
}
});
});`;
}
/**
* Generate smoke test
*/
static generateSmokeTest(options) {
const { featureName, browsers = ['chromium'] } = options;
return `import { test, expect } from '@playwright/test';
test.describe('${featureName} - Smoke Tests', () => {
test('should load application without errors', async ({ page }) => {
// Monitor console errors
const consoleErrors: string[] = [];
page.on('console', msg => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
// Monitor network errors
const networkErrors: string[] = [];
page.on('response', response => {
if (response.status() >= 400) {
networkErrors.push(\`\${response.status()} - \${response.url()}\`);
}
});
await page.goto('/${featureName.toLowerCase()}');
// Verify page loads successfully
await expect(page.locator('body')).toBeVisible();
await expect(page).toHaveTitle(/.+/); // Has some title
// Check for critical elements
await expect(page.locator('header, nav, main, [role="main"]')).toBeVisible();
// Verify no console errors
expect(consoleErrors).toHaveLength(0);
// Verify no network errors for critical resources
const criticalErrors = networkErrors.filter(error =>
error.includes('.js') || error.includes('.css') || error.includes('/api/')
);
expect(criticalErrors).toHaveLength(0);
});
test('should have working navigation', async ({ page }) => {
await page.goto('/');
// Test main navigation links
const navLinks = await page.locator('nav a, [role="navigation"] a').all();
for (const link of navLinks.slice(0, 5)) { // Test first 5 links
const href = await link.getAttribute('href');
if (href && href.startsWith('/')) {
await link.click();
await page.waitForLoadState('networkidle');
// Verify page loaded
await expect(page.locator('body')).toBeVisible();
// Go back to test next link
await page.goBack();
await page.waitForLoadState('networkidle');
}
}
});
test('should handle basic user interactions', async ({ page }) => {
await page.goto('/${featureName.toLowerCase()}');
// Test basic interactions exist and work
const buttons = await page.locator('button:visible').all();
if (buttons.length > 0) {
await buttons[0].click();
// Just verify no crash occurs
await expect(page.locator('body')).toBeVisible();
}
const links = await page.locator('a:visible').all();
if (links.length > 0) {
const href = await links[0].getAttribute('href');
if (href && href.startsWith('/')) {
await links[0].click();
await expect(page.locator('body')).toBeVisible();
}
}
});
test('should be responsive on different screen sizes', async ({ page }) => {
const viewports = [
{ width: 320, height: 568 }, // Mobile
{ width: 768, height: 1024 }, // Tablet
{ width: 1920, height: 1080 } // Desktop
];
for (const viewport of viewports) {
await page.setViewportSize(viewport);
await page.goto('/${featureName.toLowerCase()}');
// Verify page is usable at this size
await expect(page.locator('body')).toBeVisible();
// Check that content doesn't overflow
const hasHorizontalScroll = await page.evaluate(() => {
return document.body.scrollWidth > window.innerWidth;
});
expect(hasHorizontalScroll).toBeFalsy();
}
});
test('should have good SEO basics', async ({ page }) => {
await page.goto('/${featureName.toLowerCase()}');
// Check meta tags
await expect(page.locator('meta[name="description"]')).toHaveCount(1);
await expect(page.locator('meta[name="viewport"]')).toHaveCount(1);
// Check heading structure
const h1Count = await page.locator('h1').count();
expect(h1Count).toBeGreaterThan(0);
expect(h1Count).toBeLessThanOrEqual(1); // Should have exactly one H1
// Check for alt attributes on images
const images = await page.locator('img').all();
for (const img of images) {
await expect(img).toHaveAttribute('alt');
}
});
});
// Run smoke tests across different browsers
${browsers.map(browser => `
test.describe(\`${featureName} - Smoke Tests (${browser})\`, () => {
test.use({ browserName: '${browser}' });
test('should work in ${browser}', async ({ page }) => {
await page.goto('/${featureName.toLowerCase()}');
await expect(page.locator('body')).toBeVisible();
await expect(page).toHaveTitle(/.+/);
});
});`).join('')}`;
}
/**
* Generate basic test (fallback)
*/
static generateBasicTest(options) {
const { featureName } = options;
return `import { test, expect } from '@playwright/test';
test.describe('${featureName}', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/${featureName.toLowerCase()}');
});
test('should load page successfully', async ({ page }) => {
await expect(page.locator('[data-testid="${featureName.toLowerCase()}-page"]')).toBeVisible();
await expect(page).toHaveTitle(/.*${featureName}.*/i);
});
test('should handle basic interactions', async ({ page }) => {
await page.click('[data-testid="primary-button"]');
await expect(page.locator('[data-testid="result"]')).toBeVisible();
});
});`;
}
}
//# sourceMappingURL=playwright-generator.js.map