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
JavaScript
;
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