advanced-live-server-installer
Version:
Auto-installer for Advanced Live Server VS Code Extension
443 lines (434 loc) • 18.1 kB
JavaScript
;
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