UNPKG

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
/** * 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