UNPKG

tdpw

Version:

CLI tool for uploading Playwright test reports to TestDino platform with TestDino storage support

380 lines 14.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ReportDiscoveryService = void 0; const path_1 = require("path"); const fs_1 = require("../utils/fs"); const validators_1 = require("./validators"); const types_1 = require("../types"); /** * Service to discover Playwright test report files with smart scanning. */ class ReportDiscoveryService { reportDir; constructor(reportDir) { this.reportDir = reportDir; } /** * Discover report files based on CLI options with smart scanning. */ async discover(options) { const baseDir = (0, fs_1.resolvePath)(options.reportDirectory, 'Report directory'); // JSON report discovery with manual override const jsonReportPath = options.jsonReport ?? (await this.findJsonReport(baseDir)); await (0, validators_1.validateJsonReport)(jsonReportPath); // HTML report discovery (only if upload requested) let htmlReportDir; if (options.uploadHtml) { htmlReportDir = options.htmlReport ?? (await this.findHtmlReport(baseDir)); if (htmlReportDir) { await (0, validators_1.validateHtmlReportDir)(htmlReportDir); } } // Trace files discovery (as fallback when not found in JSON attachments) let traceDirectory; if (options.uploadTraces) { // Check if traces are available in JSON attachments first const hasTracesInJson = await this.checkTracesInJsonReport(jsonReportPath); if (hasTracesInJson) { // Traces found in JSON attachments, no need for separate trace directory traceDirectory = undefined; } else { // No traces in JSON, try to find trace directory as fallback if (options.traceDir) { // User explicitly specified trace directory - validate it exists await (0, validators_1.validateTraceDir)(options.traceDir); traceDirectory = options.traceDir; } else { // Auto-discover traces - gracefully handle missing traces (normal when tests pass) traceDirectory = await this.findTraceDir(baseDir); if (traceDirectory) { await (0, validators_1.validateTraceDir)(traceDirectory); } // If traceDirectory is undefined, that's OK - no traces found (tests likely passed) } } } return { jsonReport: jsonReportPath, htmlReport: htmlReportDir, traceDir: traceDirectory, }; } /** * Smart JSON report discovery - scan for valid Playwright JSON files */ async findJsonReport(baseDir) { // Try common patterns first (faster) const commonPatterns = [ (0, path_1.join)(baseDir, 'report.json'), // Standard Playwright (0, path_1.join)(baseDir, 'results.json'), // Alternative name (0, path_1.join)(baseDir, 'test-results.json'), // Another common name (0, path_1.join)(baseDir, 'playwright-report', 'report.json'), // Nested structure (0, path_1.join)(baseDir, 'playwright-report', 'results.json'), // Nested alternative ]; for (const candidate of commonPatterns) { if (await (0, fs_1.exists)(candidate)) { // Validate it's actually a Playwright report if (await this.isValidPlaywrightJson(candidate)) { return candidate; } } } // If no common patterns found, scan all JSON files const jsonFiles = await this.scanForJsonFiles(baseDir); for (const jsonFile of jsonFiles) { if (await this.isValidPlaywrightJson(jsonFile)) { return jsonFile; } } throw new types_1.FileSystemError(`No valid Playwright JSON report found in ${baseDir}.\n` + '💡 Looked for: report.json, results.json, test-results.json\n' + '💡 Use --json-report <path> to specify exact location'); } /** * Smart HTML report discovery - find directory containing index.html */ async findHtmlReport(baseDir) { // Try common patterns first const commonPatterns = [ baseDir, // Current directory (0, path_1.join)(baseDir, 'html-report'), // Standard name (0, path_1.join)(baseDir, 'playwright-report'), // Standard Playwright (0, path_1.join)(baseDir, 'report'), // Alternative (0, path_1.join)(baseDir, 'test-report'), // Alternative ]; for (const candidate of commonPatterns) { if (await (0, fs_1.isDirectory)(candidate)) { const indexPath = (0, path_1.join)(candidate, 'index.html'); if (await (0, fs_1.exists)(indexPath)) { return candidate; } } } // Scan for any directory containing index.html const htmlDirs = await this.scanForHtmlDirectories(baseDir); if (htmlDirs.length > 0) { const candidateDir = htmlDirs[0]; if (candidateDir !== undefined) { return candidateDir; } } throw new types_1.FileSystemError(`No HTML report directory with index.html found in ${baseDir}.\n` + '💡 Use --html-report <path> to specify exact location'); } /** * Smart trace directory discovery */ async findTraceDir(baseDir) { // Try common patterns const commonPatterns = [ (0, path_1.join)(baseDir, 'trace'), (0, path_1.join)(baseDir, 'traces'), (0, path_1.join)(baseDir, 'test-results'), (0, path_1.join)(baseDir, 'playwright-report', 'trace'), (0, path_1.join)(baseDir, 'playwright-report', 'traces'), ]; for (const candidate of commonPatterns) { if (await (0, fs_1.isDirectory)(candidate)) { // Check if directory contains any trace files if (await this.containsTraceFiles(candidate)) { return candidate; } } } // Scan for directories containing .zip files (common trace format) const traceDirs = await this.scanForTraceDirectories(baseDir); if (traceDirs.length > 0) { const candidateTraceDir = traceDirs[0]; if (candidateTraceDir !== undefined) { return candidateTraceDir; } } // Return undefined if no traces found (normal when tests pass) return undefined; } /** * Check if JSON report contains trace files in attachments */ async checkTracesInJsonReport(jsonPath) { try { const content = await (0, fs_1.readFile)(jsonPath); const data = JSON.parse(content); if (!data?.suites || !Array.isArray(data.suites)) { return false; } // Recursively check for trace attachments in the report return this.hasTraceAttachments(data.suites); } catch { return false; } } /** * Recursively check if suites contain trace attachments */ hasTraceAttachments(suites) { for (const suite of suites) { const suiteRecord = suite; // Check specs in this suite if (Array.isArray(suiteRecord.specs)) { for (const spec of suiteRecord.specs) { if (this.hasTraceAttachmentsInSpec(spec)) { return true; } } } // Check nested suites if (Array.isArray(suiteRecord.suites)) { if (this.hasTraceAttachments(suiteRecord.suites)) { return true; } } } return false; } /** * Check if a spec contains trace attachments */ hasTraceAttachmentsInSpec(spec) { const specRecord = spec; if (!Array.isArray(specRecord.tests)) return false; for (const test of specRecord.tests) { const testRecord = test; if (!Array.isArray(testRecord.results)) continue; for (const result of testRecord.results) { const resultRecord = result; if (Array.isArray(resultRecord.attachments)) { for (const attachment of resultRecord.attachments) { const attachmentRecord = attachment; // Check if this attachment is a trace file if (this.isTraceAttachment(attachmentRecord)) { return true; } } } } } return false; } /** * Check if an attachment is a trace file */ isTraceAttachment(attachment) { const name = attachment.name; const contentType = attachment.contentType; if (!name || !contentType) return false; const lowerName = name.toLowerCase(); const lowerContentType = contentType.toLowerCase(); // Check content type for trace files const traceContentTypes = [ 'application/zip', 'application/x-zip-compressed', 'application/octet-stream', ]; if (traceContentTypes.includes(lowerContentType)) { // Additional check for trace files by name if (lowerName.includes('trace') || lowerName.endsWith('.trace') || lowerName.endsWith('.zip')) { return true; } } // Fallback check by name return lowerName.includes('trace') || lowerName.endsWith('.trace'); } /** * Validate if a JSON file is a valid Playwright report */ async isValidPlaywrightJson(filePath) { try { const content = await (0, fs_1.readFile)(filePath); const data = JSON.parse(content); // Check for required Playwright report structure return (data && typeof data === 'object' && 'config' in data && 'suites' in data && 'stats' in data && Array.isArray(data.suites)); } catch { return false; } } /** * Scan directory recursively for JSON files */ async scanForJsonFiles(dir, maxDepth = 2) { const jsonFiles = []; try { await this.scanDirectory(dir, jsonFiles, file => file.endsWith('.json'), 0, maxDepth); } catch { // Ignore scan errors } return jsonFiles; } /** * Scan for directories containing index.html */ async scanForHtmlDirectories(dir, maxDepth = 2) { const htmlDirs = []; try { await this.scanDirectoryForHtml(dir, htmlDirs, 0, maxDepth); } catch { // Ignore scan errors } return htmlDirs; } /** * Scan for directories containing trace files */ async scanForTraceDirectories(dir, maxDepth = 2) { const traceDirs = []; try { await this.scanDirectoryForTraces(dir, traceDirs, 0, maxDepth); } catch { // Ignore scan errors } return traceDirs; } /** * Generic directory scanner for files */ async scanDirectory(dir, results, filter, currentDepth, maxDepth) { if (currentDepth > maxDepth) return; const entries = await (0, fs_1.readDir)(dir); for (const entry of entries) { if (await (0, fs_1.isDirectory)(entry)) { await this.scanDirectory(entry, results, filter, currentDepth + 1, maxDepth); } else if (filter(entry)) { results.push(entry); } } } /** * Scan for HTML directories */ async scanDirectoryForHtml(dir, results, currentDepth, maxDepth) { if (currentDepth > maxDepth) return; // Check if current directory has index.html const indexPath = (0, path_1.join)(dir, 'index.html'); if (await (0, fs_1.exists)(indexPath)) { results.push(dir); return; // Don't scan subdirectories if we found one } // Scan subdirectories const entries = await (0, fs_1.readDir)(dir); for (const entry of entries) { if (await (0, fs_1.isDirectory)(entry)) { await this.scanDirectoryForHtml(entry, results, currentDepth + 1, maxDepth); } } } /** * Scan for trace directories */ async scanDirectoryForTraces(dir, results, currentDepth, maxDepth) { if (currentDepth > maxDepth) return; // Check if current directory contains trace files if (await this.containsTraceFiles(dir)) { results.push(dir); return; } // Scan subdirectories const entries = await (0, fs_1.readDir)(dir); for (const entry of entries) { if (await (0, fs_1.isDirectory)(entry)) { await this.scanDirectoryForTraces(entry, results, currentDepth + 1, maxDepth); } } } /** * Check if directory contains trace files */ async containsTraceFiles(dir) { try { const entries = await (0, fs_1.readDir)(dir); return entries.some(entry => entry.endsWith('.zip') || entry.endsWith('.trace') || entry.includes('trace')); } catch { return false; } } } exports.ReportDiscoveryService = ReportDiscoveryService; //# sourceMappingURL=discovery.js.map