UNPKG

advanced-live-server-installer

Version:

Auto-installer for Advanced Live Server VS Code Extension

443 lines (434 loc) 18.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TestingService = void 0; const vscode = __importStar(require("vscode")); const path = __importStar(require("path")); const fs = __importStar(require("fs")); const child_process = __importStar(require("child_process")); class TestingService { constructor(context) { this.context = context; this.testResults = []; this.outputChannel = vscode.window.createOutputChannel('Advanced Live Server - Testing'); this.config = this.loadConfig(); } loadConfig() { const workspaceConfig = vscode.workspace.getConfiguration('advancedLiveServer.testing'); return { enabled: workspaceConfig.get('enabled', false), frameworks: { jest: workspaceConfig.get('jest'), mocha: workspaceConfig.get('mocha'), cypress: workspaceConfig.get('cypress'), playwright: workspaceConfig.get('playwright'), puppeteer: workspaceConfig.get('puppeteer'), }, autoRun: workspaceConfig.get('autoRun', false), coverage: workspaceConfig.get('coverage', true), visualRegression: workspaceConfig.get('visualRegression', false), performance: workspaceConfig.get('performance', false), }; } async runTests(framework) { if (!this.config.enabled) { throw new Error('Testing is not enabled'); } const results = []; const frameworks = framework ? [framework] : Object.keys(this.config.frameworks); for (const fw of frameworks) { const config = this.config.frameworks[fw]; if (config) { try { const result = await this.runFrameworkTests(fw, config); results.push(result); } catch (error) { results.push({ framework: fw, success: false, totalTests: 0, passedTests: 0, failedTests: 0, skippedTests: 0, duration: 0, output: error instanceof Error ? error.message : 'Unknown error', timestamp: new Date(), }); } } } this.testResults = results; this.displayResults(results); return results; } async runFrameworkTests(framework, config) { const startTime = Date.now(); this.outputChannel.appendLine(`Running ${framework} tests...`); let command; let args = []; switch (framework) { case 'jest': command = 'npx'; args = ['jest', '--json', '--coverage']; if (config.configFile) { args.push('--config', config.configFile); } break; case 'mocha': command = 'npx'; args = ['mocha', '--reporter', 'json']; if (config.testFiles) { args.push(config.testFiles); } break; case 'cypress': command = 'npx'; args = ['cypress', 'run', '--headless']; if (config.configFile) { args.push('--config-file', config.configFile); } break; case 'playwright': command = 'npx'; args = ['playwright', 'test', '--reporter=json']; if (config.configFile) { args.push('--config', config.configFile); } break; case 'puppeteer': // Custom Puppeteer test runner return this.runPuppeteerTests(config); default: throw new Error(`Unsupported test framework: ${framework}`); } return new Promise(resolve => { child_process.exec(`${command} ${args.join(' ')}`, { cwd: vscode.workspace.workspaceFolders?.[0]?.uri.fsPath, }, (error, stdout) => { const duration = Date.now() - startTime; try { const output = JSON.parse(stdout); const result = { framework, success: !error, totalTests: output.numTotalTests || output.total || 0, passedTests: output.numPassedTests || output.passed || 0, failedTests: output.numFailedTests || output.failed || 0, skippedTests: output.numPendingTests || output.skipped || 0, duration, output: stdout, timestamp: new Date(), }; // Extract coverage if available if (output.coverage) { result.coverage = this.parseCoverage(output.coverage); } resolve(result); } catch { // Fallback to simple parsing const result = { framework, success: !error, totalTests: 0, passedTests: 0, failedTests: 0, skippedTests: 0, duration, output: stdout, timestamp: new Date(), }; // Try to extract test counts from output const passedMatch = stdout.match(/(\d+) passing/); const failedMatch = stdout.match(/(\d+) failing/); if (passedMatch) { result.passedTests = parseInt(passedMatch[1]); result.totalTests += result.passedTests; } if (failedMatch) { result.failedTests = parseInt(failedMatch[1]); result.totalTests += result.failedTests; } resolve(result); } }); }); } async runPuppeteerTests(config) { const startTime = Date.now(); // Create a simple Puppeteer test script const testScript = ` const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch({ headless: ${config.headless}, defaultViewport: { width: ${config.viewport.width}, height: ${config.viewport.height} } }); const page = await browser.newPage(); await page.goto('http://localhost:${this.getServerPort()}', { waitUntil: 'networkidle0', timeout: ${config.timeout} }); // Basic tests const title = await page.title(); const bodyText = await page.$eval('body', el => el.textContent); console.log(JSON.stringify({ success: true, tests: [ { name: 'Page loads', passed: !!title }, { name: 'Body content exists', passed: !!bodyText } ] })); await browser.close(); })(); `; const scriptPath = path.join(this.context.globalStorageUri.fsPath, 'puppeteer-test.js'); fs.writeFileSync(scriptPath, testScript); return new Promise(resolve => { child_process.exec(`node ${scriptPath}`, { cwd: vscode.workspace.workspaceFolders?.[0]?.uri.fsPath, }, (error, stdout) => { const duration = Date.now() - startTime; try { const output = JSON.parse(stdout); const passedTests = output.tests.filter((t) => t.passed).length; resolve({ framework: 'puppeteer', success: !error && passedTests === output.tests.length, totalTests: output.tests.length, passedTests, failedTests: output.tests.length - passedTests, skippedTests: 0, duration, output: stdout, timestamp: new Date(), }); } catch { resolve({ framework: 'puppeteer', success: !error, totalTests: 0, passedTests: 0, failedTests: 0, skippedTests: 0, duration, output: stdout, timestamp: new Date(), }); } }); }); } parseCoverage(coverage) { if (typeof coverage === 'object' && coverage.total) { return { statements: coverage.total.statements.pct || 0, branches: coverage.total.branches.pct || 0, functions: coverage.total.functions.pct || 0, lines: coverage.total.lines.pct || 0, uncovered: [], }; } return { statements: 0, branches: 0, functions: 0, lines: 0, uncovered: [], }; } async runVisualRegressionTests() { if (!this.config.visualRegression) { throw new Error('Visual regression testing is not enabled'); } this.outputChannel.appendLine('Running visual regression tests...'); // Create baseline and current screenshots const baselineDir = path.join(this.context.globalStorageUri.fsPath, 'baseline'); const currentDir = path.join(this.context.globalStorageUri.fsPath, 'current'); const diffDir = path.join(this.context.globalStorageUri.fsPath, 'diff'); // Ensure directories exist [baselineDir, currentDir, diffDir].forEach(dir => { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } }); const screenshots = []; const testPages = ['/', '/about', '/contact']; // Example pages for (const page of testPages) { try { const baselinePath = path.join(baselineDir, `${page.replace('/', '') || 'index'}.png`); const currentPath = path.join(currentDir, `${page.replace('/', '') || 'index'}.png`); const diffPath = path.join(diffDir, `${page.replace('/', '') || 'index'}.png`); // Take current screenshot await this.takeScreenshot(page, currentPath); // Compare with baseline if it exists if (fs.existsSync(baselinePath)) { const passed = await this.compareScreenshots(baselinePath, currentPath, diffPath); screenshots.push({ name: page, baseline: baselinePath, current: currentPath, diff: diffPath, passed, threshold: 0.1, }); } else { // Create baseline fs.copyFileSync(currentPath, baselinePath); screenshots.push({ name: page, baseline: baselinePath, current: currentPath, diff: '', passed: true, threshold: 0.1, }); } } catch { screenshots.push({ name: page, baseline: '', current: '', diff: '', passed: false, threshold: 0.1, }); } } const success = screenshots.every(s => s.passed); const result = { success, screenshots, output: `Visual regression tests ${success ? 'passed' : 'failed'}`, }; this.outputChannel.appendLine(result.output); return result; } async takeScreenshot(page, outputPath) { const puppeteerScript = ` const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch({ headless: true }); const page = await browser.newPage(); await page.setViewport({ width: 1280, height: 720 }); await page.goto('http://localhost:${this.getServerPort()}${page}', { waitUntil: 'networkidle0' }); await page.screenshot({ path: '${outputPath}', fullPage: true }); await browser.close(); })(); `; const scriptPath = path.join(this.context.globalStorageUri.fsPath, 'screenshot.js'); fs.writeFileSync(scriptPath, puppeteerScript); return new Promise((resolve, reject) => { child_process.exec(`node ${scriptPath}`, error => { if (error) { reject(error); } else { resolve(); } }); }); } async compareScreenshots(baseline, current, diff) { // Simple image comparison using pixelmatch const comparisonScript = ` const fs = require('fs'); const PNG = require('pngjs').PNG; const pixelmatch = require('pixelmatch'); const img1 = PNG.sync.read(fs.readFileSync('${baseline}')); const img2 = PNG.sync.read(fs.readFileSync('${current}')); const { width, height } = img1; const diff = new PNG({ width, height }); const numDiffPixels = pixelmatch(img1.data, img2.data, diff.data, width, height, { threshold: 0.1 }); fs.writeFileSync('${diff}', PNG.sync.write(diff)); console.log(numDiffPixels); `; const scriptPath = path.join(this.context.globalStorageUri.fsPath, 'compare.js'); fs.writeFileSync(scriptPath, comparisonScript); return new Promise(resolve => { child_process.exec(`node ${scriptPath}`, (error, stdout) => { if (error) { resolve(false); } else { const diffPixels = parseInt(stdout.trim()); resolve(diffPixels < 100); // Threshold for acceptable differences } }); }); } getServerPort() { // Get the current server port from the live server return 9000; // Default port } displayResults(results) { this.outputChannel.appendLine('\n=== Test Results ==='); for (const result of results) { const status = result.success ? '✅ PASSED' : '❌ FAILED'; this.outputChannel.appendLine(`${status} ${result.framework.toUpperCase()}`); this.outputChannel.appendLine(` Tests: ${result.passedTests}/${result.totalTests} passed`); this.outputChannel.appendLine(` Duration: ${result.duration}ms`); if (result.coverage) { this.outputChannel.appendLine(` Coverage: ${result.coverage.lines}% lines`); } } } getTestResults() { return this.testResults; } getOutputChannel() { return this.outputChannel; } updateConfig(updates) { this.config = { ...this.config, ...updates }; // Update workspace configuration const config = vscode.workspace.getConfiguration('advancedLiveServer.testing'); if (updates.enabled !== undefined) { config.update('enabled', updates.enabled); } if (updates.autoRun !== undefined) { config.update('autoRun', updates.autoRun); } if (updates.coverage !== undefined) { config.update('coverage', updates.coverage); } if (updates.visualRegression !== undefined) { config.update('visualRegression', updates.visualRegression); } if (updates.performance !== undefined) { config.update('performance', updates.performance); } } } exports.TestingService = TestingService; //# sourceMappingURL=testing-service.js.map