UNPKG

testgenius-ai

Version:

🚀 TestGenius AI - The Ultimate E2E Testing Framework for Everyone | No Coding Required • AI-Powered Automation • Beautiful Reports • Zero Complexity

255 lines 11 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CostTracker = void 0; const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const chalk_1 = __importDefault(require("chalk")); class CostTracker { constructor(config) { this.config = config; this.costDataFile = path_1.default.join(process.cwd(), 'cost-data.json'); this.costHistoryFile = path_1.default.join(process.cwd(), 'cost-history.json'); } /** * Calculate cost for token usage */ calculateCost(tokenUsage) { const model = tokenUsage.model; const pricing = this.config.costTracking?.modelPricing?.[model]; if (!pricing) { console.warn(chalk_1.default.yellow(`⚠️ No pricing found for model: ${model}`)); return { tokenUsage, estimatedCost: 0, costCurrency: this.config.costTracking?.currency || 'USD', modelPricing: { inputCostPer1k: 0, outputCostPer1k: 0 } }; } const inputCost = (tokenUsage.promptTokens / 1000) * pricing.inputCostPer1k; const outputCost = (tokenUsage.completionTokens / 1000) * pricing.outputCostPer1k; const totalCost = inputCost + outputCost; return { tokenUsage, estimatedCost: totalCost, costCurrency: this.config.costTracking?.currency || 'USD', modelPricing: pricing }; } /** * Track cost for a test execution */ async trackTestCost(testCostData) { if (!this.config.costTracking?.enabled) return; try { // Load existing cost data let costData = []; if (await fs_extra_1.default.pathExists(this.costDataFile)) { costData = await fs_extra_1.default.readJson(this.costDataFile); } // Add new cost data costData.push(testCostData); // Save updated cost data await fs_extra_1.default.writeJson(this.costDataFile, costData, { spaces: 2 }); // Update cost history await this.updateCostHistory(testCostData); console.log(chalk_1.default.blue(`💰 Cost tracked for test ${testCostData.testId}: $${testCostData.costMetrics.estimatedCost.toFixed(4)}`)); } catch (error) { console.error(chalk_1.default.red('❌ Failed to track cost:'), error.message); } } /** * Update cost history for trend analysis */ async updateCostHistory(testCostData) { try { let costHistory = []; if (await fs_extra_1.default.pathExists(this.costHistoryFile)) { costHistory = await fs_extra_1.default.readJson(this.costHistoryFile); } const today = new Date().toISOString().split('T')[0]; const existingEntry = costHistory.find(entry => entry.date === today); if (existingEntry) { existingEntry.cost += testCostData.costMetrics.estimatedCost; existingEntry.tests += 1; } else { costHistory.push({ date: today, cost: testCostData.costMetrics.estimatedCost, tests: 1 }); } await fs_extra_1.default.writeJson(this.costHistoryFile, costHistory, { spaces: 2 }); } catch (error) { console.error(chalk_1.default.red('❌ Failed to update cost history:'), error.message); } } /** * Generate cost optimization report */ async generateCostReport() { if (!this.config.costTracking?.enabled) { throw new Error('Cost tracking is not enabled'); } try { const costData = await fs_extra_1.default.readJson(this.costDataFile); const costHistory = await fs_extra_1.default.readJson(this.costHistoryFile); const totalTests = costData.length; const totalCost = costData.reduce((sum, test) => sum + test.costMetrics.estimatedCost, 0); const averageCostPerTest = totalTests > 0 ? totalCost / totalTests : 0; // Calculate cost savings (if optimization is enabled) const costSavings = this.calculateCostSavings(costData); // Get optimization recommendations const optimizationRecommendations = this.generateOptimizationRecommendations(costData); // Get top expensive tests const topExpensiveTests = costData .sort((a, b) => b.costMetrics.estimatedCost - a.costMetrics.estimatedCost) .slice(0, 10); // Calculate cost by category (using test tags if available) const costByCategory = this.calculateCostByCategory(costData); return { totalTests, totalCost, averageCostPerTest, costSavings, optimizationRecommendations, costTrend: costHistory, topExpensiveTests, costByCategory }; } catch (error) { console.error(chalk_1.default.red('❌ Failed to generate cost report:'), error.message); throw error; } } /** * Calculate potential cost savings */ calculateCostSavings(costData) { if (!this.config.costTracking?.optimization?.trackCostSavings) return 0; let totalSavings = 0; costData.forEach(test => { // Calculate savings from using cheaper models const currentModel = test.costMetrics.tokenUsage.model; const currentCost = test.costMetrics.estimatedCost; // Suggest cheaper alternatives if (currentModel === 'gpt-4o' || currentModel === 'gpt-4') { const gpt35Cost = this.estimateCostWithModel(test.costMetrics.tokenUsage, 'gpt-3.5-turbo'); const potentialSavings = currentCost - gpt35Cost; if (potentialSavings > 0) { totalSavings += potentialSavings; } } }); return totalSavings; } /** * Estimate cost with a different model */ estimateCostWithModel(tokenUsage, model) { const pricing = this.config.costTracking?.modelPricing?.[model]; if (!pricing) return 0; const inputCost = (tokenUsage.promptTokens / 1000) * pricing.inputCostPer1k; const outputCost = (tokenUsage.completionTokens / 1000) * pricing.outputCostPer1k; return inputCost + outputCost; } /** * Generate optimization recommendations */ generateOptimizationRecommendations(costData) { const recommendations = []; // Analyze expensive tests const expensiveTests = costData.filter(test => test.costMetrics.estimatedCost > 0.1); if (expensiveTests.length > 0) { recommendations.push(`Consider using gpt-3.5-turbo for ${expensiveTests.length} expensive tests to save ~$${this.calculateCostSavings(expensiveTests).toFixed(2)}`); } // Analyze failed tests cost const failedTests = costData.filter(test => !test.success); if (failedTests.length > 0) { const failedCost = failedTests.reduce((sum, test) => sum + test.costMetrics.estimatedCost, 0); recommendations.push(`Failed tests cost $${failedCost.toFixed(2)} - improve test stability to reduce costs`); } // Analyze token usage patterns const avgTokens = costData.reduce((sum, test) => sum + test.costMetrics.tokenUsage.totalTokens, 0) / costData.length; if (avgTokens > 2000) { recommendations.push('Consider optimizing test descriptions to reduce token usage'); } return recommendations; } /** * Calculate cost by category */ calculateCostByCategory(costData) { const categoryMap = new Map(); costData.forEach(test => { // For now, categorize by model type - can be enhanced with test tags later const category = test.costMetrics.tokenUsage.model; const existing = categoryMap.get(category) || { cost: 0, tests: 0 }; existing.cost += test.costMetrics.estimatedCost; existing.tests += 1; categoryMap.set(category, existing); }); return Array.from(categoryMap.entries()).map(([category, data]) => ({ category, cost: data.cost, tests: data.tests })); } /** * Get total cost from all tracked tests */ async getTotalCost() { if (!this.config.costTracking?.enabled) return 0; try { if (!await fs_extra_1.default.pathExists(this.costDataFile)) return 0; const costData = await fs_extra_1.default.readJson(this.costDataFile); return costData.reduce((sum, test) => sum + test.costMetrics.estimatedCost, 0); } catch (error) { console.error(chalk_1.default.red('❌ Failed to get total cost:'), error.message); return 0; } } /** * Check budget limits and send alerts */ async checkBudgetLimits() { if (!this.config.costTracking?.budgetAlerts?.enabled) return; try { const costHistory = await fs_extra_1.default.readJson(this.costHistoryFile); const today = new Date().toISOString().split('T')[0]; const todayCost = costHistory.find(entry => entry.date === today)?.cost || 0; const dailyLimit = this.config.costTracking.budgetAlerts.dailyLimit; if (todayCost > dailyLimit) { console.warn(chalk_1.default.red(`⚠️ Daily budget limit exceeded: $${todayCost.toFixed(2)} / $${dailyLimit}`)); } // Calculate monthly cost const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); const monthlyCost = costHistory .filter(entry => new Date(entry.date) >= thirtyDaysAgo) .reduce((sum, entry) => sum + entry.cost, 0); const monthlyLimit = this.config.costTracking.budgetAlerts.monthlyLimit; if (monthlyCost > monthlyLimit) { console.warn(chalk_1.default.red(`⚠️ Monthly budget limit exceeded: $${monthlyCost.toFixed(2)} / $${monthlyLimit}`)); } } catch (error) { console.error(chalk_1.default.red('❌ Failed to check budget limits:'), error.message); } } } exports.CostTracker = CostTracker; //# sourceMappingURL=CostTracker.js.map