UNPKG

ctrlshiftleft

Version:

AI-powered toolkit for embedding QA and security testing into development workflows

713 lines (632 loc) 34.4 kB
import { TestScenario, TestTemplate } from '../types/testTypes'; import path from 'path'; /** * Generate a Playwright test file from test scenarios * @param scenarios Test scenarios to include * @param sourcePath Path to the source file (for documentation) * @param usePageObjects Whether to use Page Object Model pattern * @param useTestData Whether to use Test Data Factory pattern * @returns Test template with content */ export function generatePlaywrightTest( scenarios: TestScenario[], sourcePath: string, usePageObjects: boolean = true, useTestData: boolean = true ): TestTemplate { const template: TestTemplate = { content: '', format: 'playwright' }; // Extract component name from source path for class naming const fileName = path.basename(sourcePath); const componentName = fileName.split('.')[0]; // Determine relative path for imports const relativeImportPath = '../../helpers'; // Generate imports and header template.content = ` import { test, expect } from '@playwright/test'; import type { Page } from '@playwright/test'; ${usePageObjects ? `import { ${componentName}Page } from '${relativeImportPath}/${componentName}Page'; ` : ''}${useTestData ? `import { ${componentName.toLowerCase()}Data, generateValidCardNumber, generateValidExpiryDate } from '${relativeImportPath}/testDataFactory'; ` : ''} /** * End-to-end tests generated for: ${sourcePath} * Generated by ctrl.shift.left * * This file contains comprehensive tests that cover: * - UI/UX testing * - API and integration testing * - Security vulnerability testing */ // Test suite implementation for the Ctrl.shift.left test runner export default { async runTests(page: Page, options = {}) { let total = 0; let passed = 0; let failed = 0; let skipped = 0; // Run all tests with the page instance for (const testFn of Object.values(tests)) { total++; try { await testFn(page); passed++; } catch (error) { failed++; console.error(error); } } return { total, passed, failed, skipped }; } }; // Helper to run tests programmatically through the ctrl.shift.left test runner export default { async runTests(page: Page, options = {}) { let total = 0; let passed = 0; let failed = 0; let skipped = 0; // Run all tests with the page instance for (const testFn of Object.values(tests)) { total++; try { await testFn({ page }); passed++; } catch (error) { failed++; console.error(error); } } return { total, passed, failed, skipped }; } }; // Test implementations that can be run individually or through the test runner const tests = { `; // Determine if we need to set up page objects const setupPageObject = usePageObjects ? ` // Setup - create page object const ${componentName.toLowerCase()}Page = new ${componentName}Page(page); test.setTimeout(60000); // Navigate to form await ${componentName.toLowerCase()}Page.goto(); ` : ` test.setTimeout(60000); // Navigate to the page await page.goto("http://localhost:3000"); `; // Convert scenarios to test implementations const scenarioEntries = scenarios.map((scenario, index) => { // Create valid JavaScript identifier for test name const scenarioId = `test_${index + 1}_${(scenario.id || `scenario${index + 1}`).replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}`; const testName = `${scenarioId}: async ({ page }) => {`; const testSetup = setupPageObject; // Map steps to code with meaningful selector placeholders const stepsCode = scenario.steps .map(step => { // Handle JSON structured step objects if (typeof step === 'object' && step !== null) { try { // For structured step objects, try to intelligently generate code const stepObj = typeof step === 'string' ? JSON.parse(step) : step; const action = stepObj.action?.toLowerCase(); const target = stepObj.target; const value = stepObj.value; if (action === 'fill_input' || action === 'type' || action === 'fill') { if (usePageObjects) { return ` // Fill ${target} field await ${componentName.toLowerCase()}Page.fillForm({ ${target}: '${value}' });`; } else { return ` console.log('Filling ${target} with value: ${value}'); await page.fill('[data-testid="${target}${target.endsWith('-input') ? '' : '-input'}"]', '${value}');`; } } else if (action === 'click') { if (usePageObjects && target === 'submit') { return ` // Submit the form await ${componentName.toLowerCase()}Page.submitForm();`; } else { return ` console.log('Clicking on ${target}'); await page.click('[data-testid="${target}"]');`; } } else if (action === 'navigate' || action === 'goto') { if (usePageObjects) { return ` // Navigate to page await ${componentName.toLowerCase()}Page.goto('${value || 'http://localhost:3000'}');`; } else { return ` console.log('Navigating to ${value || 'http://localhost:3000'}'); await page.goto('${value || 'http://localhost:3000'}');`; } } else if (action === 'wait') { return ` console.log('Waiting for ${value || 1000}ms'); await page.waitForTimeout(${value || 1000});`; } else if (action === 'check' || action === 'uncheck') { return ` console.log('${action.charAt(0).toUpperCase() + action.slice(1)}ing ${target}'); await page.${action}('[data-testid="${target}"]');`; } else if (action === 'select') { return ` console.log('Selecting option ${value} for ${target}'); await page.selectOption('[data-testid="${target}"]', '${value}');`; } else if (action === 'set_state') { return ` console.log('Simulating state change: ${JSON.stringify(value)}');\n // State manipulation typically requires interaction with app-specific APIs\n // For testing direct state manipulation, we'd need to use page.evaluate()\n await page.evaluate((stateData) => {\n // This would need to be customized based on your app's state management\n console.log('State change simulation', stateData);\n window.dispatchEvent(new CustomEvent('test:setState', { detail: stateData }));\n }, ${JSON.stringify(value)});`; } else { return ` console.log('Executing custom action: ${action}');\n // Implementation for ${action} on ${target} with value ${value}\n await page.evaluate((data) => {\n console.log('Custom action', data);\n }, { action: '${action}', target: '${target}', value: ${JSON.stringify(value)} });`; } } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); return ` // ${JSON.stringify(step)}\n // TODO: Implement code for this step (Error parsing: ${errorMessage})`; } } // Ensure step is a string for text-based steps const stepStr = typeof step === 'string' ? step : JSON.stringify(step); // Convert text step to actual code where possible if (stepStr.toLowerCase().includes('navigate to')) { return ` // ${stepStr}\n await page.goto('http://localhost:3000');`; } else if (stepStr.toLowerCase().includes('enter') && stepStr.toLowerCase().includes('username')) { return ` // ${stepStr}\n await page.fill('[data-testid="username-input"]', 'testuser');`; } else if (stepStr.toLowerCase().includes('enter') && stepStr.toLowerCase().includes('password')) { return ` // ${stepStr}\n await page.fill('[data-testid="password-input"]', 'password123');`; } else if (stepStr.toLowerCase().includes('click') && stepStr.toLowerCase().includes('login')) { return ` // ${stepStr}\n await page.click('[data-testid="login-button"]');`; } else { return ` // ${stepStr}\n // TODO: Implement code for this step`; } }) .join('\n'); // Map assertions to code with actual implementation const assertionsCode = scenario.assertions .map(assertion => { if (typeof assertion === 'object' && assertion !== null) { try { const assertObj = typeof assertion === 'string' ? JSON.parse(assertion) : assertion; const target = assertObj.target; const condition = assertObj.condition?.toLowerCase(); const value = assertObj.value; if (condition === 'exists' || condition === 'visible') { return ` console.log('Asserting ${target} exists/is visible');\n await page.waitForSelector('[data-testid="${target}"]', { state: 'visible', timeout: 10000 });\n expect(await page.locator('[data-testid="${target}"]').isVisible()).toBeTruthy();`; } else if (condition === 'not_exists' || condition === 'not_visible') { return ` console.log('Asserting ${target} does not exist/is not visible');\n // Using a small timeout since we expect this element to not exist\n const isVisible = await page.locator('[data-testid="${target}"]').isVisible();\n expect(isVisible).toBeFalsy();`; } else if (condition === 'contains' || condition === 'contains_text') { return ` console.log('Asserting ${target} contains text: "${value}"');\n await page.waitForSelector('[data-testid="${target}"]', { state: 'visible', timeout: 10000 });\n const content = await page.textContent('[data-testid="${target}"]');\n expect(content).toContain('${value}');`; } else if (condition === 'equals' || condition === 'equals_text') { return ` console.log('Asserting ${target} text equals: "${value}"');\n await page.waitForSelector('[data-testid="${target}"]', { state: 'visible', timeout: 10000 });\n const content = await page.textContent('[data-testid="${target}"]');\n expect(content?.trim()).toBe('${value}');`; } else if (condition === 'url') { return ` console.log('Asserting URL contains: "${value}"');\n await page.waitForURL((url) => url.toString().includes('${value}'), { timeout: 10000 });\n expect(page.url()).toContain('${value}');`; } else if (condition === 'count') { return ` console.log('Asserting ${target} count equals: ${value}');\n await page.waitForSelector('[data-testid="${target}"]', { state: 'attached', timeout: 10000 });\n expect(await page.locator('[data-testid="${target}"]').count()).toBe(${value});`; } else if (condition === 'value') { return ` console.log('Asserting ${target} value equals: "${value}"');\n await page.waitForSelector('[data-testid="${target}"]', { state: 'visible', timeout: 10000 });\n const inputValue = await page.inputValue('[data-testid="${target}"]');\n expect(inputValue).toBe('${value}');`; } else if (condition === 'attribute') { const [attributeName, attributeValue] = value.split('='); return ` console.log('Asserting ${target} has attribute ${attributeName} = "${attributeValue}"');\n await page.waitForSelector('[data-testid="${target}"]', { state: 'visible', timeout: 10000 });\n const attributeValue = await page.getAttribute('[data-testid="${target}"]', '${attributeName}');\n expect(attributeValue).toBe('${attributeValue}');`; } else { return ` console.log('Asserting custom condition on ${target}');\n await page.waitForSelector('[data-testid="${target}"]', { state: 'visible', timeout: 10000 });\n // Custom assertion for condition: ${condition} with value: ${value}\n const element = await page.locator('[data-testid="${target}"]');\n expect(await element.isVisible()).toBeTruthy();`; } } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); return ` console.log('Executing assertion with parsing error: ${errorMessage}'); // Implementing a generic assertion as fallback const element = await page.locator('[data-testid="error-message"]'); if (await element.isVisible()) { expect(true).toBeTruthy(); // At least verify something is visible }`; } } // Ensure assertion is a string const assertionStr = typeof assertion === 'string' ? assertion : JSON.stringify(assertion); // Convert assertions to actual code if (assertionStr.toLowerCase().includes('login button') && assertionStr.toLowerCase().includes('logging in')) { return ` // ${assertionStr}\n const buttonText = await page.textContent('[data-testid="login-button"]');\n expect(buttonText).toContain('Logging in');`; } else if (assertionStr.toLowerCase().includes('success') && assertionStr.toLowerCase().includes('message')) { return ` // ${assertionStr}\n await page.waitForSelector('[data-testid="login-success"]', { state: 'visible', timeout: 5000 });\n const successMessage = await page.textContent('[data-testid="login-success"]');\n expect(successMessage).toContain('successful');`; } else if (assertionStr.toLowerCase().includes('error') && assertionStr.toLowerCase().includes('message')) { return ` // ${assertionStr}\n await page.waitForSelector('[data-testid="general-error"]', { state: 'visible', timeout: 5000 });\n const errorMessage = await page.textContent('[data-testid="general-error"]');\n expect(errorMessage).toContain('Invalid');`; } else if (assertionStr.toLowerCase().includes('xss') || assertionStr.toLowerCase().includes('script')) { // Enhanced security assertion for XSS return ` // ${assertionStr}\n // Security test: Check for XSS vulnerabilities\n const content = await page.content();\n expect(content).not.toContain('<script>alert');\n expect(content).not.toContain('onerror=');`; } else if (assertionStr.toLowerCase().includes('sanitize') || assertionStr.toLowerCase().includes('escape')) { // Enhanced security assertion for input sanitization return ` // ${assertionStr}\n // Security test: Verify input sanitization\n const displayedValue = await page.textContent('[data-testid="username-input"]');\n expect(displayedValue || '').not.toContain('<script>');`; } else { return ` // ${assertionStr}\n // TODO: Implement assertion`; } }) .join('\n'); return `${testName}\n${testSetup}\n // Test steps\n${stepsCode}\n\n // Assertions\n${assertionsCode}\n },`; }).join('\n'); template.content += scenarioEntries; // Create test file content template.content += ` export { tests }; // Individual test implementations that can be run directly by Playwright `; // Generate individual Playwright tests for direct running scenarios.forEach((scenario, index) => { const scenarioName = scenario.name || `Test Scenario ${index + 1}`; template.content += `test('${scenarioName}', async ({ page }) => { `; // Add Page Object Model initialization if enabled if (usePageObjects) { template.content += ` const ${componentName.toLowerCase()}Page = new ${componentName}Page(page); await ${componentName.toLowerCase()}Page.goto(); `; } else { template.content += ` await page.goto('http://localhost:3000'); `; } // Add test steps if (scenario.type === 'security' || scenarioName.toLowerCase().includes('security') || scenarioName.toLowerCase().includes('xss') || scenarioName.toLowerCase().includes('csrf')) { // Special handling for security tests if (scenarioName.toLowerCase().includes('xss')) { if (usePageObjects && useTestData) { template.content += ` // Enter XSS payloads const xssPayload = '<script>alert("XSS")</script>'; await ${componentName.toLowerCase()}Page.fillForm({ ${Object.keys(scenario.data || {}).map(key => `${key}: ${componentName.toLowerCase()}Data.security.xss.${key}`).join(',\n ')} }); await ${componentName.toLowerCase()}Page.submitForm(); // Verify content doesn't contain unescaped script tags const pageContent = await page.content(); expect(pageContent).not.toContain('<script>alert'); // Verify inputs were sanitized const sanitized = await page.evaluate(() => { const inputs = Array.from(document.querySelectorAll('input')) as HTMLInputElement[]; return !inputs.some(input => input.value.includes('<script>') || input.value.includes('alert("XSS")') ); }); expect(sanitized).toBe(true); `; } else { template.content += ` // Enter XSS payloads const xssPayload = '<script>alert("XSS")</script>'; await page.fill('[data-testid="amount-input"]', '100.00' + xssPayload); await page.fill('[data-testid="card-number-input"]', '4111' + xssPayload + '1111'); await page.click('[data-testid="submit-button"]'); // Verify content doesn't contain unescaped script tags const pageContent = await page.content(); expect(pageContent).not.toContain('<script>alert'); `; } } else if (scenarioName.toLowerCase().includes('csrf')) { if (usePageObjects) { template.content += ` // Fill form with valid data await ${componentName.toLowerCase()}Page.fillWithValidData(); // Tamper with CSRF token await ${componentName.toLowerCase()}Page.testCSRFProtection('invalid-csrf-token'); // Verify security validation failed const errorMessage = await ${componentName.toLowerCase()}Page.getErrorMessage(); expect(errorMessage).toMatch(/security|validation|csrf|token|invalid/i); `; } else { template.content += ` // Fill form with valid data await page.fill('[data-testid="amount-input"]', '100.00'); await page.fill('[data-testid="card-number-input"]', '4111111111111111'); // Tamper with CSRF token await page.evaluate(() => { const csrfMetaTag = document.querySelector('meta[name="csrf-token"]'); if (csrfMetaTag) csrfMetaTag.setAttribute('content', 'invalid-token'); sessionStorage.setItem('csrf-token', 'invalid-token'); }); // Submit form await page.click('[data-testid="submit-button"]'); // Check for error message await page.waitForSelector('[data-testid="error-message"]', { state: 'visible' }); const errorText = await page.textContent('[data-testid="error-message"]'); expect(errorText).toMatch(/security|validation|csrf|token|invalid/i); `; } } else if (scenarioName.toLowerCase().includes('api') || scenarioName.toLowerCase().includes('error')) { if (usePageObjects) { template.content += ` // Fill form with valid data await ${componentName.toLowerCase()}Page.fillWithValidData(); // Mock API failure response await page.route('**/api/**', route => route.fulfill({ status: 500, contentType: 'application/json', body: JSON.stringify({ error: 'Server error', message: 'Processing failed' }) })); // Submit form await ${componentName.toLowerCase()}Page.submitForm(); // Verify error message is displayed await page.waitForSelector('[data-testid="server-error-message"]', { state: 'visible' }); const errorText = await page.textContent('[data-testid="server-error-message"]'); expect(errorText).toMatch(/error|failed|server/i); `; } else { template.content += ` // Fill form with valid data await page.fill('[data-testid="amount-input"]', '100.00'); await page.fill('[data-testid="card-number-input"]', '4111111111111111'); // Mock API failure await page.route('**/api/**', route => route.fulfill({ status: 500, contentType: 'application/json', body: JSON.stringify({ error: 'Server error', message: 'Processing failed' }) })); // Submit form await page.click('[data-testid="submit-button"]'); // Check for error message await page.waitForSelector('[data-testid="error-message"]', { state: 'visible' }); `; } } else { // Default security test implementation template.content += ` // Test steps ${scenario.steps.map((step: string) => { if (typeof step === 'string') { return `// ${step}`; } else { try { // For JSON objects describing steps const stepObj = typeof step === 'string' ? JSON.parse(step) : step; return `// ${stepObj.description || stepObj.action || JSON.stringify(step)}`; } catch (error) { return `// ${JSON.stringify(step)}`; } } }).join('\n ')} // Assertions ${scenario.assertions.map((assertion: string) => { if (typeof assertion === 'string') { return `// ${assertion}\n ${convertAssertionToCode(assertion)}`; } else { try { const assertObj = typeof assertion === 'string' ? JSON.parse(assertion) : assertion; return `// ${assertObj.description || assertObj.assertion || JSON.stringify(assertion)}\n ${convertAssertionToCode(assertObj.description || assertObj.assertion || '')}`; } catch (error) { return `// ${JSON.stringify(assertion)}\n // Wait for app to stabilize\n await page.waitForLoadState('networkidle');\n expect(await page.content()).toBeTruthy();`; } } }).join('\n\n ')} `; } } else if (scenario.type === 'ui' || scenarioName.toLowerCase().includes('accessibility') || scenarioName.toLowerCase().includes('responsive')) { // UI/UX test implementation if (scenarioName.toLowerCase().includes('accessibility')) { template.content += ` // Test keyboard navigation through form await page.keyboard.press('Tab'); let firstFocused = await page.evaluate(() => document.activeElement?.getAttribute('data-testid')); expect(firstFocused).toBeTruthy(); // Verify ARIA attributes are present on form elements const accessibilityAttributes = await page.evaluate(() => { const results = { allInputsHaveLabels: false, formHasRole: false, errorMessagesAreAccessible: false }; // Check all input fields have labels const inputs = Array.from(document.querySelectorAll('input')) as HTMLInputElement[]; results.allInputsHaveLabels = inputs.every(input => { // Check for associated label if (input.id && document.querySelector('label[for="' + input.id + '"]')) { return true; } // Check for aria attributes return input.hasAttribute('aria-label') || input.hasAttribute('aria-labelledby'); }); // Check form has appropriate role const form = document.querySelector('form'); results.formHasRole = form ? (form.hasAttribute('role') || true) : false; // Forms have implicit role // Check error messages have appropriate aria attributes const errorMessages = Array.from(document.querySelectorAll('[data-testid$="-error"]')); results.errorMessagesAreAccessible = errorMessages.length === 0 || errorMessages.every(el => el.hasAttribute('role') || el.hasAttribute('aria-live') ); return results; }); expect(accessibilityAttributes.allInputsHaveLabels).toBe(true); expect(accessibilityAttributes.formHasRole).toBe(true); expect(accessibilityAttributes.errorMessagesAreAccessible).toBe(true); `; } else if (scenarioName.toLowerCase().includes('responsive') || scenarioName.toLowerCase().includes('mobile')) { template.content += ` // Test responsive design // Mobile viewport await page.setViewportSize({ width: 375, height: 667 }); let isMobileLayoutCorrect = await page.evaluate(() => { const form = document.querySelector('form'); const computedStyle = form ? window.getComputedStyle(form) : null; return computedStyle ? ['flex', 'block'].includes(computedStyle.display) : false; }); expect(isMobileLayoutCorrect).toBe(true); // Tablet viewport await page.setViewportSize({ width: 768, height: 1024 }); let isTabletLayoutCorrect = await page.evaluate(() => { const form = document.querySelector('form'); const computedStyle = form ? window.getComputedStyle(form) : null; return computedStyle ? ['flex', 'block'].includes(computedStyle.display) : false; }); expect(isTabletLayoutCorrect).toBe(true); // Desktop viewport await page.setViewportSize({ width: 1280, height: 800 }); let isDesktopLayoutCorrect = await page.evaluate(() => { const form = document.querySelector('form'); const computedStyle = form ? window.getComputedStyle(form) : null; return computedStyle ? ['flex', 'block'].includes(computedStyle.display) : false; }); expect(isDesktopLayoutCorrect).toBe(true); `; } else if (scenarioName.toLowerCase().includes('validation')) { if (usePageObjects) { template.content += ` // Test empty form submission await ${componentName.toLowerCase()}Page.submitForm(); // Assert field validation errors appear await page.waitForSelector('[data-testid*="error"]', { state: 'visible' }); // Test with invalid data await ${componentName.toLowerCase()}Page.fillForm(${useTestData ? `${componentName.toLowerCase()}Data.invalid` : `{ amount: '-50', cardNumber: '1234' }`}); await ${componentName.toLowerCase()}Page.submitForm(); // Assert validation errors are shown await page.waitForSelector('[data-testid*="error"]', { state: 'visible' }); // Test with valid data await ${componentName.toLowerCase()}Page.fillWithValidData(); await ${componentName.toLowerCase()}Page.submitForm(); // Assert submission is successful await page.waitForSelector('[data-testid="success-message"]', { state: 'visible' }); `; } else { template.content += ` // Test empty form submission await page.click('[data-testid="submit-button"]'); // Assert field validation errors appear await page.waitForSelector('[data-testid*="error"]', { state: 'visible' }); // Test with invalid data await page.fill('[data-testid="amount-input"]', '-50'); await page.fill('[data-testid="card-number-input"]', '1234'); await page.click('[data-testid="submit-button"]'); // Assert validation errors are shown await page.waitForSelector('[data-testid*="error"]', { state: 'visible' }); // Test with valid data await page.fill('[data-testid="amount-input"]', '100.00'); await page.fill('[data-testid="card-number-input"]', '4111111111111111'); await page.click('[data-testid="submit-button"]'); // Assert submission is successful await page.waitForSelector('[data-testid="success-message"]', { state: 'visible' }); `; } } else { // Default UI/UX test implementation template.content += ` // Test steps ${scenario.steps.map((step: string) => { if (typeof step === 'string') { return `// ${step}`; } else { try { // For JSON objects describing steps const stepObj = typeof step === 'string' ? JSON.parse(step) : step; return `// ${stepObj.description || stepObj.action || JSON.stringify(step)}`; } catch (error) { return `// ${JSON.stringify(step)}`; } } }).join('\n ')} // Assertions ${scenario.assertions.map((assertion: string) => { if (typeof assertion === 'string') { return `// ${assertion}\n ${convertAssertionToCode(assertion)}`; } else { try { const assertObj = typeof assertion === 'string' ? JSON.parse(assertion) : assertion; return `// ${assertObj.description || assertObj.assertion || JSON.stringify(assertion)}\n ${convertAssertionToCode(assertObj.description || assertObj.assertion || '')}`; } catch (error) { return `// ${JSON.stringify(assertion)}\n // Wait for app to stabilize\n await page.waitForLoadState('networkidle');\n expect(await page.content()).toBeTruthy();`; } } }).join('\n\n ')} `; } } else { // Default implementation for other test types template.content += ` // Test steps ${scenario.steps.map((step: string) => { if (typeof step === 'string') { return `// ${step}`; } else { try { // For JSON objects describing steps const stepObj = typeof step === 'string' ? JSON.parse(step) : step; return `// ${stepObj.description || stepObj.action || JSON.stringify(step)}`; } catch (error) { return `// ${JSON.stringify(step)}`; } } }).join('\n ')} // Assertions ${scenario.assertions.map((assertion: string) => { if (typeof assertion === 'string') { return `// ${assertion}\n ${convertAssertionToCode(assertion)}`; } else { try { const assertObj = typeof assertion === 'string' ? JSON.parse(assertion) : assertion; return `// ${assertObj.description || assertObj.assertion || JSON.stringify(assertion)}\n ${convertAssertionToCode(assertObj.description || assertObj.assertion || '')}`; } catch (error) { return `// ${JSON.stringify(assertion)}\n // Wait for app to stabilize\n await page.waitForLoadState('networkidle');\n expect(await page.content()).toBeTruthy();`; } } }).join('\n\n ')} `; } template.content += `}); `; }); return template; } /** * Convert a plain text assertion to Playwright code * Enhanced implementation with detailed security-focused assertions */ function convertAssertionToCode(assertion: string): string { // Improved conversion with comprehensive security assertions if (assertion.toLowerCase().includes('url')) { return `await page.waitForURL((url) => url.toString().includes('expected'), { timeout: 10000 }); expect(page.url()).toContain('expected');`; } else if (assertion.toLowerCase().includes('title')) { return `await page.waitForFunction(() => document.title.includes('Expected'), { timeout: 10000 }); expect(await page.title()).toContain('Expected');`; } else if (assertion.toLowerCase().includes('visible') || assertion.toLowerCase().includes('see')) { const selector = extractSelector(assertion) || '[data-testid="element"]'; return `await page.waitForSelector('${selector}', { state: 'visible', timeout: 10000 }); expect(await page.locator('${selector}').isVisible()).toBeTruthy();`; } else if (assertion.toLowerCase().includes('text')) { const selector = extractSelector(assertion) || '[data-testid="element"]'; const expectedText = extractExpectedText(assertion) || 'expected text'; return `await page.waitForSelector('${selector}', { state: 'visible', timeout: 10000 }); const textContent = await page.locator('${selector}').textContent(); expect(textContent).toContain('${expectedText}');`; } else if (assertion.toLowerCase().includes('element')) { const selector = extractSelector(assertion) || '[data-testid="element"]'; return `await page.waitForSelector('${selector}', { state: 'attached', timeout: 10000 }); expect(await page.locator('${selector}').count()).toBeGreaterThan(0);`; } else if (assertion.toLowerCase().includes('xss') || assertion.toLowerCase().includes('injection') || assertion.toLowerCase().includes('sanitize')) { // Comprehensive security assertion for XSS return `// Security check for XSS protection const dangerousInput = '<script>alert("XSS")</script>'; await page.fill('[data-testid="input"]', dangerousInput); await page.click('[data-testid="submit"]'); // Verify content doesn't contain unescaped script tags const pageContent = await page.content(); expect(pageContent).not.toContain('<script>alert'); // Check for error message const errorElement = page.locator('[data-testid="error-message"]'); if (await errorElement.isVisible()) { expect(await errorElement.textContent()).toContain('Invalid'); }`; } else if (assertion.toLowerCase().includes('security') || assertion.toLowerCase().includes('validation')) { return `// Security validation check const invalidInput = assertion.toLowerCase().includes('sql') ? "' OR 1=1 --" : '<script>alert(1)</script>'; await page.fill('[data-testid="input"]', invalidInput); await page.click('[data-testid="submit"]'); // Check for appropriate security validation await page.waitForSelector('[data-testid="form-error"]', { state: 'visible', timeout: 10000 }); expect(await page.locator('[data-testid="form-error"]').isVisible()).toBeTruthy(); const errorText = await page.locator('[data-testid="form-error"]').textContent(); expect(errorText).toMatch(/invalid|error|failed/i);`; } else if (assertion.toLowerCase().includes('auth') || assertion.toLowerCase().includes('password')) { return `// Authentication security check // Attempt with weak/invalid credentials await page.fill('[data-testid="username-input"]', 'test'); await page.fill('[data-testid="password-input"]', 'weak'); await page.click('[data-testid="login-button"]'); // Verify proper auth validation await page.waitForSelector('[data-testid="error-message"]', { state: 'visible', timeout: 10000 }); expect(await page.locator('[data-testid="error-message"]').isVisible()).toBeTruthy();`; } else { return `// Generic assertion for: ${assertion} // Waiting for app to stabilize await page.waitForLoadState('networkidle'); // Basic content verification const pageContent = await page.content(); expect(pageContent).toBeTruthy();`; } } // Helper functions for parsing assertions function extractSelector(assertion: string): string | null { // Simple extraction logic - would be enhanced in actual implementation const selectorMatches = assertion.match(/['"](\[.*?\]|#\w+|\w+)['"]/); return selectorMatches ? selectorMatches[1] : null; } function extractExpectedText(assertion: string): string | null { // Simple extraction logic - would be enhanced in actual implementation const textMatches = assertion.match(/['"](.*?)['"]/); return textMatches ? textMatches[1] : null; }