UNPKG

tdpw

Version:

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

235 lines â€ĸ 9.37 kB
"use strict"; /** * Last Failed command - Retrieve cached test failure data */ Object.defineProperty(exports, "__esModule", { value: true }); exports.LastFailedCommand = exports.LastFailedOptionsSchema = void 0; const build_detector_1 = require("../../core/build-detector"); const cache_api_1 = require("../../services/cache-api"); const env_1 = require("../../utils/env"); const zod_1 = require("zod"); /** * Zod schema for last failed options validation */ exports.LastFailedOptionsSchema = zod_1.z.object({ cacheId: zod_1.z.string().optional(), branch: zod_1.z.string().optional(), commit: zod_1.z.string().optional(), shard: zod_1.z .string() .optional() .refine(val => { if (!val) return true; // Optional field const match = val.match(/^(\d+)\/(\d+)$/); if (!match) return false; const [, index, total] = match; const shardIndex = parseInt(index || '0', 10); const shardTotal = parseInt(total || '0', 10); return shardIndex > 0 && shardTotal > 0 && shardIndex <= shardTotal; }, { message: 'Shard must be in format "index/total" where index > 0 and index <= total (e.g., "1/3")', }), token: zod_1.z.string().optional(), verbose: zod_1.z.boolean().optional().default(false), }); /** * Last Failed command implementation */ class LastFailedCommand { config; name = 'last-failed'; description = 'Get last failed test cases for Playwright execution'; constructor(config) { this.config = config; } /** * Execute the last failed command */ async execute(options) { const isDebugMode = options.verbose || env_1.EnvironmentUtils.getStringEnv('TESTDINO_RUNTIME') === 'development'; try { if (isDebugMode) { console.error('🔍 Retrieving last failed tests...'); if (options.cacheId || options.branch || options.commit || options.shard) { console.error('📊 Custom overrides:', { cacheId: options.cacheId || 'auto-detect', branch: options.branch || 'auto-detect', commit: options.commit || 'auto-detect', shard: options.shard || 'none', }); } } // Step 1: Parse shard information if provided const shardInfo = options.shard ? this.parseShardInfo(options.shard) : null; if (isDebugMode && shardInfo) { console.error(` Shard: ${shardInfo.shardIndex}/${shardInfo.shardTotal}`); } // Step 2: Determine cache ID with custom overrides const cacheId = await this.determineCacheId(options.cacheId, options.branch, options.commit); if (isDebugMode && cacheId) { console.error(` Cache ID: ${cacheId}`); } // Step 3: Get cached failure data const failureData = await this.getCachedFailures(cacheId, options, shardInfo, isDebugMode); if (!failureData?.failures || failureData.failures.length === 0) { if (isDebugMode) { if (shardInfo) { console.error(`â„šī¸ No failed test cases found for shard ${shardInfo.shardIndex}/${shardInfo.shardTotal}`); } else { console.error('â„šī¸ No last failed test cases found'); } } process.exit(0); return; } if (isDebugMode) { console.error(` Found: ${failureData.failures.length} failed tests`); console.error(` Branch: ${failureData.branch}`); console.error(` Repository: ${failureData.repository}`); } // Step 4: Format output for Playwright const playwrightArgs = this.formatPlaywrightArgs(failureData.failures); // Step 5: Output result (to stdout for shell substitution) console.log(playwrightArgs); } catch (_error) { if (isDebugMode) { const errorMessage = _error instanceof Error ? _error.message : 'Unknown error'; console.error(`❌ Failed to retrieve cached failures: ${errorMessage}`); } process.exit(1); } } /** * Parse shard information from string format (e.g., "1/3") */ parseShardInfo(shard) { const match = shard.match(/^(\d+)\/(\d+)$/); if (!match) return null; const [, index, total] = match; return { shardIndex: parseInt(index || '0', 10), shardTotal: parseInt(total || '0', 10), }; } /** * Determine cache ID using same logic as cache store command */ async determineCacheId(customCacheId, customBranch, customCommit) { try { const cacheIdInfo = await build_detector_1.CacheIdDetector.detectCacheId(customCacheId, customBranch, customCommit); return cacheIdInfo.cacheId; } catch (_error) { return null; } } /** * Get cached failure data from API */ async getCachedFailures(cacheId, options, shardInfo, isDebugMode) { const apiClient = new cache_api_1.CacheApiClient(this.config); try { // Get cache for specific cache ID if (cacheId) { // Determine query parameters based on whether cache ID is custom const queryParams = {}; // If custom cache ID is provided, pass branch/commit as query params if (options.cacheId) { if (options.branch) queryParams.branch = options.branch; if (options.commit) queryParams.commit = options.commit; } else { // If auto-generated cache ID: // - Branch affects cache ID generation (not passed as query param) // - Commit is passed as query param only if (options.commit) queryParams.commit = options.commit; } // Add shard index if specified if (shardInfo) { queryParams.shard = shardInfo.shardIndex; } if (isDebugMode) { console.error(` Fetching cache for: ${cacheId}`); if (Object.keys(queryParams).length > 0) { console.error(` Query parameters:`, queryParams); } } const cache = await apiClient.getCacheData(cacheId, queryParams); if (cache) { return cache; } if (isDebugMode) { if (shardInfo) { console.error(` No cache found for ${cacheId} (shard ${shardInfo.shardIndex}/${shardInfo.shardTotal})`); } else { console.error(` No cache found for ${cacheId}`); } } } return null; } catch (error) { if (isDebugMode) { console.error(` API error: ${error instanceof Error ? error.message : 'Unknown error'}`); } return null; } } /** * Format failure data for Playwright command */ formatPlaywrightArgs(failures) { if (failures.length === 0) { return ''; } // Group failures by file const failuresByFile = failures.reduce((acc, failure) => { if (!acc[failure.file]) { acc[failure.file] = []; } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion acc[failure.file].push(failure.testTitle); return acc; }, {}); const files = Object.keys(failuresByFile); // If all failures are from a single file if (files.length === 1) { const file = files[0]; if (file) { const testTitles = failuresByFile[file]; if (testTitles) { const grepPattern = testTitles .map((title) => this.escapeForGrep(title)) .join('|'); return `${file} -g "${grepPattern}"`; } } } // Multiple files - use global grep pattern const allTestTitles = failures.map(f => this.escapeForGrep(f.testTitle)); const grepPattern = allTestTitles.join('|'); return `-g "${grepPattern}"`; } /** * Escape test title for Playwright's -g flag */ escapeForGrep(testTitle) { // Escape special regex characters for Playwright's grep return testTitle .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // Escape regex special chars .replace(/"/g, '\\"') // Escape double quotes for shell .trim(); } } exports.LastFailedCommand = LastFailedCommand; //# sourceMappingURL=last-failed.js.map