feat(test): implement comprehensive Playwright test suite
- Add complete E2E test coverage for authentication flows - Implement component interaction and navigation testing - Create responsive design validation across viewports - Add theme switching and visual regression testing - Include smoke tests for critical user paths - Configure Playwright with proper test setup Test suite ensures application reliability with automated validation of user flows, accessibility, and visual consistency. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
312
reactrebuild0825/tests/test-runner.ts
Normal file
312
reactrebuild0825/tests/test-runner.ts
Normal file
@@ -0,0 +1,312 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Comprehensive test runner for Black Canyon Tickets React Rebuild
|
||||
*
|
||||
* This script orchestrates the execution of all test suites and generates
|
||||
* comprehensive reports with screenshots for QA validation.
|
||||
*/
|
||||
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
interface TestSuite {
|
||||
name: string;
|
||||
file: string;
|
||||
description: string;
|
||||
critical: boolean;
|
||||
}
|
||||
|
||||
const TEST_SUITES: TestSuite[] = [
|
||||
{
|
||||
name: 'Smoke Tests',
|
||||
file: 'smoke.spec.ts',
|
||||
description: 'Basic functionality and application health checks',
|
||||
critical: true
|
||||
},
|
||||
{
|
||||
name: 'Authentication (Realistic)',
|
||||
file: 'auth-realistic.spec.ts',
|
||||
description: 'Login flows using current component selectors',
|
||||
critical: true
|
||||
},
|
||||
{
|
||||
name: 'Authentication (Enhanced)',
|
||||
file: 'auth.spec.ts',
|
||||
description: 'Full auth suite requiring data-testid attributes',
|
||||
critical: false
|
||||
},
|
||||
{
|
||||
name: 'Navigation',
|
||||
file: 'navigation.spec.ts',
|
||||
description: 'Sidebar navigation, mobile menu, breadcrumbs, and routing',
|
||||
critical: false
|
||||
},
|
||||
{
|
||||
name: 'Theme Switching',
|
||||
file: 'theme.spec.ts',
|
||||
description: 'Light/dark theme transitions and persistence',
|
||||
critical: false
|
||||
},
|
||||
{
|
||||
name: 'Responsive Design',
|
||||
file: 'responsive.spec.ts',
|
||||
description: 'Mobile, tablet, desktop layouts and touch interactions',
|
||||
critical: false
|
||||
},
|
||||
{
|
||||
name: 'UI Components',
|
||||
file: 'components.spec.ts',
|
||||
description: 'Buttons, forms, cards, modals, and interactive elements',
|
||||
critical: false
|
||||
}
|
||||
];
|
||||
|
||||
class TestRunner {
|
||||
private results: { [key: string]: any } = {};
|
||||
private startTime: Date = new Date();
|
||||
|
||||
async run(options: { critical?: boolean; suite?: string; headed?: boolean } = {}) {
|
||||
console.log('🚀 Starting Black Canyon Tickets QA Test Suite');
|
||||
console.log('=' .repeat(60));
|
||||
|
||||
await this.ensureDirectories();
|
||||
await this.clearPreviousResults();
|
||||
|
||||
const suitesToRun = this.filterSuites(options);
|
||||
const playwrightOptions = this.buildPlaywrightOptions(options);
|
||||
|
||||
console.log(`📋 Running ${suitesToRun.length} test suite(s):`);
|
||||
suitesToRun.forEach(suite => {
|
||||
console.log(` ${suite.critical ? '🔴' : '🟡'} ${suite.name}: ${suite.description}`);
|
||||
});
|
||||
console.log('');
|
||||
|
||||
for (const suite of suitesToRun) {
|
||||
await this.runTestSuite(suite, playwrightOptions);
|
||||
}
|
||||
|
||||
await this.generateReport();
|
||||
|
||||
const duration = new Date().getTime() - this.startTime.getTime();
|
||||
console.log(`\n✅ Test suite completed in ${Math.round(duration / 1000)}s`);
|
||||
console.log(`📊 View detailed report: ./playwright-report/index.html`);
|
||||
console.log(`📸 Screenshots saved to: ./screenshots/`);
|
||||
}
|
||||
|
||||
private filterSuites(options: { critical?: boolean; suite?: string }): TestSuite[] {
|
||||
if (options.suite) {
|
||||
const suite = TEST_SUITES.find(s => s.name.toLowerCase().includes(options.suite!.toLowerCase()));
|
||||
return suite ? [suite] : [];
|
||||
}
|
||||
|
||||
if (options.critical) {
|
||||
return TEST_SUITES.filter(s => s.critical);
|
||||
}
|
||||
|
||||
return TEST_SUITES;
|
||||
}
|
||||
|
||||
private buildPlaywrightOptions(options: { headed?: boolean }): string {
|
||||
const opts = [];
|
||||
|
||||
if (options.headed) {
|
||||
opts.push('--headed');
|
||||
}
|
||||
|
||||
opts.push('--reporter=html,line');
|
||||
|
||||
return opts.join(' ');
|
||||
}
|
||||
|
||||
private async ensureDirectories() {
|
||||
const dirs = ['screenshots', 'test-results', 'playwright-report'];
|
||||
|
||||
for (const dir of dirs) {
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async clearPreviousResults() {
|
||||
// Clear previous screenshots
|
||||
const screenshotsDir = './screenshots';
|
||||
if (fs.existsSync(screenshotsDir)) {
|
||||
const files = fs.readdirSync(screenshotsDir);
|
||||
for (const file of files) {
|
||||
if (file.endsWith('.png')) {
|
||||
fs.unlinkSync(path.join(screenshotsDir, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async runTestSuite(suite: TestSuite, playwrightOptions: string) {
|
||||
console.log(`🧪 Running ${suite.name} tests...`);
|
||||
|
||||
try {
|
||||
const command = `npx playwright test tests/${suite.file} ${playwrightOptions}`;
|
||||
const { stdout, stderr } = await execAsync(command);
|
||||
|
||||
this.results[suite.name] = {
|
||||
success: true,
|
||||
output: stdout,
|
||||
error: null,
|
||||
suite
|
||||
};
|
||||
|
||||
console.log(` ✅ ${suite.name} - PASSED`);
|
||||
|
||||
} catch (error: any) {
|
||||
this.results[suite.name] = {
|
||||
success: false,
|
||||
output: error.stdout || '',
|
||||
error: error.stderr || error.message,
|
||||
suite
|
||||
};
|
||||
|
||||
console.log(` ❌ ${suite.name} - FAILED`);
|
||||
if (suite.critical) {
|
||||
console.log(` 🚨 CRITICAL TEST FAILED - Consider stopping deployment`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async generateReport() {
|
||||
const report = {
|
||||
timestamp: new Date().toISOString(),
|
||||
duration: new Date().getTime() - this.startTime.getTime(),
|
||||
results: this.results,
|
||||
summary: this.generateSummary()
|
||||
};
|
||||
|
||||
// Save JSON report
|
||||
fs.writeFileSync('./test-results/qa-report.json', JSON.stringify(report, null, 2));
|
||||
|
||||
// Generate markdown report
|
||||
const markdown = this.generateMarkdownReport(report);
|
||||
fs.writeFileSync('./test-results/qa-report.md', markdown);
|
||||
|
||||
console.log('\n📊 Test Summary:');
|
||||
console.log(` Total Suites: ${Object.keys(this.results).length}`);
|
||||
console.log(` Passed: ${report.summary.passed}`);
|
||||
console.log(` Failed: ${report.summary.failed}`);
|
||||
console.log(` Critical Failed: ${report.summary.criticalFailed}`);
|
||||
}
|
||||
|
||||
private generateSummary() {
|
||||
const total = Object.keys(this.results).length;
|
||||
const passed = Object.values(this.results).filter(r => r.success).length;
|
||||
const failed = total - passed;
|
||||
const criticalFailed = Object.values(this.results).filter(r => !r.success && r.suite.critical).length;
|
||||
|
||||
return { total, passed, failed, criticalFailed };
|
||||
}
|
||||
|
||||
private generateMarkdownReport(report: any): string {
|
||||
const { summary } = report;
|
||||
|
||||
let markdown = `# Black Canyon Tickets QA Report\n\n`;
|
||||
markdown += `**Generated:** ${new Date(report.timestamp).toLocaleString()}\n`;
|
||||
markdown += `**Duration:** ${Math.round(report.duration / 1000)}s\n\n`;
|
||||
|
||||
markdown += `## Summary\n\n`;
|
||||
markdown += `- Total Test Suites: ${summary.total}\n`;
|
||||
markdown += `- ✅ Passed: ${summary.passed}\n`;
|
||||
markdown += `- ❌ Failed: ${summary.failed}\n`;
|
||||
markdown += `- 🚨 Critical Failed: ${summary.criticalFailed}\n\n`;
|
||||
|
||||
if (summary.criticalFailed > 0) {
|
||||
markdown += `> ⚠️ **WARNING:** Critical tests have failed. Consider reviewing before deployment.\n\n`;
|
||||
}
|
||||
|
||||
markdown += `## Test Results\n\n`;
|
||||
|
||||
for (const [name, result] of Object.entries(report.results)) {
|
||||
const r = result as any;
|
||||
const status = r.success ? '✅ PASSED' : '❌ FAILED';
|
||||
const critical = r.suite.critical ? ' (CRITICAL)' : '';
|
||||
|
||||
markdown += `### ${name}${critical}\n\n`;
|
||||
markdown += `**Status:** ${status}\n`;
|
||||
markdown += `**Description:** ${r.suite.description}\n\n`;
|
||||
|
||||
if (!r.success && r.error) {
|
||||
markdown += `**Error:**\n\`\`\`\n${r.error}\n\`\`\`\n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
markdown += `## Screenshots\n\n`;
|
||||
markdown += `All test screenshots are saved in the \`./screenshots/\` directory.\n`;
|
||||
markdown += `Screenshots are organized by test suite and include timestamps.\n\n`;
|
||||
|
||||
markdown += `## Next Steps\n\n`;
|
||||
if (summary.criticalFailed > 0) {
|
||||
markdown += `1. Review failed critical tests immediately\n`;
|
||||
markdown += `2. Fix critical issues before deployment\n`;
|
||||
markdown += `3. Re-run critical tests to verify fixes\n`;
|
||||
} else if (summary.failed > 0) {
|
||||
markdown += `1. Review failed non-critical tests\n`;
|
||||
markdown += `2. Consider fixing for next iteration\n`;
|
||||
markdown += `3. Document known issues if acceptable\n`;
|
||||
} else {
|
||||
markdown += `1. All tests passed! 🎉\n`;
|
||||
markdown += `2. Review screenshots for visual validation\n`;
|
||||
markdown += `3. Application is ready for deployment\n`;
|
||||
}
|
||||
|
||||
return markdown;
|
||||
}
|
||||
}
|
||||
|
||||
// CLI interface
|
||||
if (require.main === module) {
|
||||
const args = process.argv.slice(2);
|
||||
const options: any = {};
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
switch (args[i]) {
|
||||
case '--critical':
|
||||
options.critical = true;
|
||||
break;
|
||||
case '--suite':
|
||||
options.suite = args[++i];
|
||||
break;
|
||||
case '--headed':
|
||||
options.headed = true;
|
||||
break;
|
||||
case '--help':
|
||||
console.log(`
|
||||
Black Canyon Tickets QA Test Runner
|
||||
|
||||
Usage: npm run test:qa [options]
|
||||
|
||||
Options:
|
||||
--critical Run only critical test suites
|
||||
--suite NAME Run specific test suite (auth, navigation, theme, responsive, components)
|
||||
--headed Run tests with visible browser windows
|
||||
--help Show this help message
|
||||
|
||||
Examples:
|
||||
npm run test:qa # Run all tests
|
||||
npm run test:qa -- --critical # Run only critical tests
|
||||
npm run test:qa -- --suite auth # Run only authentication tests
|
||||
npm run test:qa -- --headed # Run with visible browser
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
const runner = new TestRunner();
|
||||
runner.run(options).catch(error => {
|
||||
console.error('❌ Test runner failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export default TestRunner;
|
||||
Reference in New Issue
Block a user