tdpw
Version:
CLI tool for uploading Playwright test reports to TestDino platform with TestDino storage support
219 lines âĸ 9.52 kB
JavaScript
;
/**
* Cache command - Store test execution metadata after Playwright runs
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CacheCommand = exports.CacheOptionsSchema = void 0;
const progress_1 = require("../../utils/progress");
const shard_detection_1 = require("../../core/shard-detection");
const cache_extractor_1 = require("../../core/cache-extractor");
const build_detector_1 = require("../../core/build-detector");
const git_1 = require("../../collectors/git");
const ci_1 = require("../../collectors/ci");
const system_1 = require("../../collectors/system");
const cache_api_1 = require("../../services/cache-api");
/**
* Zod schema for cache options validation
*/
const zod_1 = require("zod");
exports.CacheOptionsSchema = zod_1.z.object({
cacheId: zod_1.z.string().optional(),
workingDir: zod_1.z.string().optional(),
token: zod_1.z.string().optional(),
verbose: zod_1.z.boolean().optional().default(false),
});
/**
* Cache command implementation
*/
class CacheCommand {
config;
name = 'cache';
description = 'Store test execution metadata after Playwright runs';
constructor(config) {
this.config = config;
}
/**
* Execute the cache command
*/
async execute(options) {
const progress = (0, progress_1.createProgressTracker)();
try {
progress.start('đ Caching test execution metadata...');
if (options.verbose) {
console.log('đ Cache configuration:', {
workingDir: options.workingDir || process.cwd(),
customCacheId: options.cacheId || 'auto-detect',
});
}
// Step 1: Detect working directory
const workingDir = options.workingDir || process.cwd();
progress.update(`đ Working directory: ${workingDir}`);
// Step 2: Auto-detect Playwright configuration and shard info
progress.update('đ Detecting Playwright configuration...');
const shardInfo = await this.detectShardInfo(workingDir);
if (options.verbose && shardInfo) {
console.log('đ Detected shard configuration:', shardInfo);
}
// Step 3: Extract test failure data using existing discovery and parsing
progress.update('đ Discovering and extracting test data...');
const failureData = await this.extractFailureData(workingDir);
if (!failureData.hasData) {
progress.warn('No test reports found - tests may not have completed yet');
console.log('đĄ Run this command after your Playwright tests complete');
return; // Exit successfully (CI-friendly)
}
if (options.verbose) {
console.log(`đ Found ${failureData.failures.length} failed tests from ${failureData.reportPaths.length} reports`);
}
// Step 4: Collect build and CI metadata
progress.update('đī¸ Collecting build metadata...');
const metadata = await this.collectBuildMetadata(options);
// Step 5: Prepare cache payload
const cachePayload = this.prepareCachePayload({
metadata,
shardInfo,
failureData,
timestamp: new Date().toISOString(),
});
if (options.verbose) {
console.log('đ¤ Cache payload summary:', {
cacheId: cachePayload.cacheId,
pipelineId: cachePayload.pipelineId,
commit: cachePayload.commit,
isSharded: cachePayload.isSharded,
shardIndex: cachePayload.shardIndex,
shardTotal: cachePayload.shardTotal,
failures: cachePayload.failures.length,
totalTests: cachePayload.summary.total,
failedTests: cachePayload.summary.failed,
branch: cachePayload.branch,
repository: cachePayload.repository,
});
}
// Step 7: Send to API
progress.update('đ¤ Sending cache data to TestDino API...');
await this.sendCacheData(cachePayload);
progress.succeed('â
Test execution metadata cached successfully');
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
// Special handling for cache conflicts (409) - this is not an error
if (errorMessage.includes('already exists')) {
progress.warn('Cache data already exists for this shard');
console.log('âšī¸ This shard has already been cached - skipping');
console.log('đĄ This warning will not affect your CI pipeline');
return;
}
// CI-friendly error handling - warn but don't fail for other errors
progress.warn(`Failed to cache test metadata: ${errorMessage}`);
if (options.verbose) {
console.error('đ Full error details:', error);
}
console.log('đĄ This warning will not affect your CI pipeline');
// Exit with success code (0) to avoid breaking CI
}
}
/**
* Auto-detect shard information from Playwright configuration
*/
async detectShardInfo(workingDir) {
return await shard_detection_1.PlaywrightShardDetector.detectShardInfo(workingDir);
}
/**
* Discover and extract test failure data
*/
async extractFailureData(workingDir) {
const extractor = new cache_extractor_1.CacheExtractor(workingDir);
const result = await extractor.extractFailureData();
// Add hasData property to indicate if any reports were found
return {
...result,
hasData: result.reportPaths.length > 0,
};
}
/**
* Collect build and CI metadata using existing collectors
*/
async collectBuildMetadata(options) {
// Collect cache ID information from environment
const cacheIdInfo = await build_detector_1.CacheIdDetector.detectCacheId(options.cacheId);
// Collect Git metadata
const gitCollector = new git_1.GitCollector(options.workingDir || process.cwd());
const gitMetadata = await gitCollector.getMetadata();
// Collect CI metadata
const ciMetadata = ci_1.CiCollector.collect();
// Collect system metadata
const systemMetadata = system_1.SystemCollector.collect();
return {
cacheIdInfo,
gitMetadata,
ciMetadata,
systemMetadata,
};
}
/**
* Prepare cache payload for TestDino API based on PRD specification
*/
prepareCachePayload(data) {
const { cacheIdInfo, ciMetadata } = data.metadata;
const effectiveShardInfo = data.shardInfo || shard_detection_1.PlaywrightShardDetector.createDefaultShardInfo();
return {
// Cache identification (new format)
cacheId: cacheIdInfo.cacheId,
pipelineId: cacheIdInfo.pipelineId,
commit: cacheIdInfo.commit,
// Git metadata
branch: cacheIdInfo.branch, // Use original branch from cacheIdInfo
repository: cacheIdInfo.repository,
// CI information
ci: {
provider: ciMetadata.provider || 'unknown',
pipelineId: ciMetadata.pipeline?.id || 'unknown',
buildNumber: ciMetadata.build?.number || 'unknown',
},
// Shard information (cleaner structure)
isSharded: effectiveShardInfo.shardTotal > 1,
shardIndex: effectiveShardInfo.shardTotal > 1
? effectiveShardInfo.shardIndex
: null,
shardTotal: effectiveShardInfo.shardTotal > 1
? effectiveShardInfo.shardTotal
: null,
// Test failure data (simplified)
failures: data.failureData.failures.map(f => ({
file: f.file,
testTitle: f.testTitle,
})),
// Test summary for this shard
summary: data.failureData.summary,
// Timestamp
timestamp: data.timestamp,
};
}
/**
* Send cache data to TestDino API
*/
async sendCacheData(payload) {
const apiClient = new cache_api_1.CacheApiClient(this.config);
// Check if cache endpoint is available (since APIs aren't implemented yet)
const isEndpointAvailable = await apiClient.testCacheEndpoint();
if (!isEndpointAvailable) {
console.log('âšī¸ Cache API endpoint not available yet - using mock submission');
await apiClient.mockCacheSubmission(payload);
return;
}
// Submit cache data to real API
const result = await apiClient.submitCacheData(payload);
if (result.success) {
console.log(`â
Cache data submitted successfully for cache: ${result.cacheId}`);
if (result.message) {
console.log(` ${result.message}`);
}
}
else {
throw new Error(`Cache submission failed for cache: ${result.cacheId}`);
}
}
}
exports.CacheCommand = CacheCommand;
//# sourceMappingURL=cache.js.map