playwright-ai-reporter 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1183 -0
- package/dist/colors.d.ts +54 -0
- package/dist/colors.js +57 -0
- package/dist/examples/ReporterWorkflow.d.ts +54 -0
- package/dist/examples/ReporterWorkflow.js +307 -0
- package/dist/providers/ProviderRegistry.d.ts +79 -0
- package/dist/providers/ProviderRegistry.js +195 -0
- package/dist/providers/ai/AIProviderFactory.d.ts +33 -0
- package/dist/providers/ai/AIProviderFactory.js +82 -0
- package/dist/providers/ai/AnthropicProvider.d.ts +15 -0
- package/dist/providers/ai/AnthropicProvider.js +128 -0
- package/dist/providers/ai/AzureOpenAIProvider.d.ts +20 -0
- package/dist/providers/ai/AzureOpenAIProvider.js +158 -0
- package/dist/providers/ai/GoogleAIProvider.d.ts +17 -0
- package/dist/providers/ai/GoogleAIProvider.js +154 -0
- package/dist/providers/ai/MistralProvider.d.ts +16 -0
- package/dist/providers/ai/MistralProvider.js +137 -0
- package/dist/providers/ai/OpenAIProvider.d.ts +16 -0
- package/dist/providers/ai/OpenAIProvider.js +141 -0
- package/dist/providers/bugTrackers/AzureDevOpsBugTracker.d.ts +32 -0
- package/dist/providers/bugTrackers/AzureDevOpsBugTracker.js +295 -0
- package/dist/providers/bugTrackers/GitHubBugTracker.d.ts +28 -0
- package/dist/providers/bugTrackers/GitHubBugTracker.js +241 -0
- package/dist/providers/bugTrackers/JiraBugTracker.d.ts +29 -0
- package/dist/providers/bugTrackers/JiraBugTracker.js +279 -0
- package/dist/providers/databases/MySQLProvider.d.ts +32 -0
- package/dist/providers/databases/MySQLProvider.js +274 -0
- package/dist/providers/databases/SQLiteProvider.d.ts +28 -0
- package/dist/providers/databases/SQLiteProvider.js +272 -0
- package/dist/providers/factories/BugTrackerFactory.d.ts +20 -0
- package/dist/providers/factories/BugTrackerFactory.js +50 -0
- package/dist/providers/factories/DatabaseFactory.d.ts +28 -0
- package/dist/providers/factories/DatabaseFactory.js +71 -0
- package/dist/providers/factories/NotificationFactory.d.ts +24 -0
- package/dist/providers/factories/NotificationFactory.js +64 -0
- package/dist/providers/factories/PRProviderFactory.d.ts +20 -0
- package/dist/providers/factories/PRProviderFactory.js +45 -0
- package/dist/providers/index.d.ts +28 -0
- package/dist/providers/index.js +55 -0
- package/dist/providers/interfaces/IAIProvider.d.ts +59 -0
- package/dist/providers/interfaces/IAIProvider.js +5 -0
- package/dist/providers/interfaces/IBugTrackerProvider.d.ts +70 -0
- package/dist/providers/interfaces/IBugTrackerProvider.js +20 -0
- package/dist/providers/interfaces/IDatabaseProvider.d.ts +90 -0
- package/dist/providers/interfaces/IDatabaseProvider.js +5 -0
- package/dist/providers/interfaces/INotificationProvider.d.ts +59 -0
- package/dist/providers/interfaces/INotificationProvider.js +13 -0
- package/dist/providers/interfaces/IPRProvider.d.ts +82 -0
- package/dist/providers/interfaces/IPRProvider.js +5 -0
- package/dist/providers/notifications/EmailNotificationProvider.d.ts +29 -0
- package/dist/providers/notifications/EmailNotificationProvider.js +290 -0
- package/dist/providers/pr/AzureDevOpsPRProvider.d.ts +30 -0
- package/dist/providers/pr/AzureDevOpsPRProvider.js +263 -0
- package/dist/providers/pr/GitHubPRProvider.d.ts +29 -0
- package/dist/providers/pr/GitHubPRProvider.js +320 -0
- package/dist/reporter.d.ts +138 -0
- package/dist/reporter.js +787 -0
- package/dist/types/index.d.ts +168 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/buildInfoUtils.d.ts +26 -0
- package/dist/utils/buildInfoUtils.js +125 -0
- package/dist/utils/configValidator.d.ts +67 -0
- package/dist/utils/configValidator.js +454 -0
- package/dist/utils/fileHandlerUtils.d.ts +42 -0
- package/dist/utils/fileHandlerUtils.js +136 -0
- package/dist/utils/genaiUtils.d.ts +38 -0
- package/dist/utils/genaiUtils.js +178 -0
- package/dist/utils/historyUtils.d.ts +49 -0
- package/dist/utils/historyUtils.js +118 -0
- package/dist/utils/utils.d.ts +104 -0
- package/dist/utils/utils.js +371 -0
- package/docs/API.md +591 -0
- package/docs/ENV_CONFIG_GUIDE.md +444 -0
- package/docs/IMPLEMENTATION_SUMMARY.md +285 -0
- package/docs/PROVIDERS.md +261 -0
- package/docs/QUICKSTART.md +350 -0
- package/docs/README.md +253 -0
- package/docs/TROUBLESHOOTING.md +577 -0
- package/docs/design.md +384 -0
- package/docs/logo.png +0 -0
- package/examples/README.md +326 -0
- package/examples/VALIDATION.md +68 -0
- package/examples/env-configs/.env.anthropic-minimal +40 -0
- package/examples/env-configs/.env.azure-stack +71 -0
- package/examples/env-configs/.env.example +32 -0
- package/examples/env-configs/.env.github-stack +57 -0
- package/examples/env-configs/.env.google-mysql +61 -0
- package/examples/env-configs/.env.ms-auth-examples +56 -0
- package/examples/env-configs/.env.openai-jira +64 -0
- package/examples/package.json +22 -0
- package/package.json +70 -0
- package/playwright-ai-reporter-0.0.4.tgz +0 -0
package/dist/reporter.js
ADDED
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const colors_1 = require("./colors");
|
|
37
|
+
const utils_1 = require("./utils/utils");
|
|
38
|
+
const fileHandlerUtils_1 = require("./utils/fileHandlerUtils");
|
|
39
|
+
const buildInfoUtils_1 = require("./utils/buildInfoUtils");
|
|
40
|
+
const genaiUtils_1 = require("./utils/genaiUtils");
|
|
41
|
+
const ProviderRegistry_1 = require("./providers/ProviderRegistry");
|
|
42
|
+
const IBugTrackerProvider_1 = require("./providers/interfaces/IBugTrackerProvider");
|
|
43
|
+
const INotificationProvider_1 = require("./providers/interfaces/INotificationProvider");
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
/**
|
|
47
|
+
* PlaywrightTestReporter is a modern, maintainable reporter for Playwright tests.
|
|
48
|
+
* It provides detailed, colorized output of test results with comprehensive metrics
|
|
49
|
+
* and configurable options for better visibility into test execution.
|
|
50
|
+
*
|
|
51
|
+
* Features:
|
|
52
|
+
* - Colorized output for different test states (passed, failed, skipped, retried)
|
|
53
|
+
* - Detailed metrics including test duration and slow test identification
|
|
54
|
+
* - Configurable thresholds for slow tests and timeouts
|
|
55
|
+
* - Comprehensive error reporting with stack traces
|
|
56
|
+
* - Support for test retries with clear status indication
|
|
57
|
+
* - Complete monitoring of all error types including setup/teardown errors
|
|
58
|
+
* - JSON output files for CI integration and historical tracking
|
|
59
|
+
*/
|
|
60
|
+
class PlaywrightTestReporter {
|
|
61
|
+
/**
|
|
62
|
+
* Creates a new instance of the PlaywrightTestReporter.
|
|
63
|
+
*
|
|
64
|
+
* @param config - Optional configuration object to customize reporter behavior
|
|
65
|
+
*/
|
|
66
|
+
constructor(config = {}) {
|
|
67
|
+
this._testRecords = new Map();
|
|
68
|
+
this._startTime = 0;
|
|
69
|
+
this._nonTestErrors = [];
|
|
70
|
+
this._hasInterruptedTests = false;
|
|
71
|
+
this._config = {
|
|
72
|
+
slowTestThreshold: config.slowTestThreshold ?? 5,
|
|
73
|
+
maxSlowTestsToShow: config.maxSlowTestsToShow ?? 3,
|
|
74
|
+
timeoutWarningThreshold: config.timeoutWarningThreshold ?? 30,
|
|
75
|
+
showStackTrace: config.showStackTrace ?? true,
|
|
76
|
+
outputDir: config.outputDir ?? './test-results',
|
|
77
|
+
generateFix: config.generateFix ?? false,
|
|
78
|
+
createBug: config.createBug ?? false,
|
|
79
|
+
generatePR: config.generatePR ?? false,
|
|
80
|
+
publishToDB: config.publishToDB ?? false,
|
|
81
|
+
sendEmail: config.sendEmail ?? false,
|
|
82
|
+
};
|
|
83
|
+
this.outputDir = this._config.outputDir;
|
|
84
|
+
this._fileHandler = new fileHandlerUtils_1.FileHandler(this.outputDir);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Called when the test run begins.
|
|
88
|
+
* Initializes the start time and displays a start message.
|
|
89
|
+
*
|
|
90
|
+
* @param config - The full Playwright configuration
|
|
91
|
+
* @param suite - The root test suite
|
|
92
|
+
*/
|
|
93
|
+
onBegin(config, suite) {
|
|
94
|
+
this.suite = suite;
|
|
95
|
+
const totalTestCount = this._countTests(suite);
|
|
96
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
97
|
+
console.log(`${colors_1.colors.fgMagentaBright}🚀 Starting test run: ${totalTestCount} tests using ${config.workers} workers${colors_1.colors.reset}`);
|
|
98
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
99
|
+
console.log(`${colors_1.colors.fgCyan}Test run started at: ${new Date().toLocaleString()}${colors_1.colors.reset}`);
|
|
100
|
+
// Get project name(s) from configuration
|
|
101
|
+
const projectNames = config.projects
|
|
102
|
+
?.map((project) => project.name)
|
|
103
|
+
.filter(Boolean)
|
|
104
|
+
.join(', ') || 'Default Project';
|
|
105
|
+
console.log(`
|
|
106
|
+
Playwright Test Configuration:
|
|
107
|
+
Project Names: ${projectNames}
|
|
108
|
+
Generate Fix: ${this._config.generateFix}
|
|
109
|
+
Create Bug: ${this._config.createBug}
|
|
110
|
+
Generate PR: ${this._config.generatePR}
|
|
111
|
+
Publish to DB: ${this._config.publishToDB}
|
|
112
|
+
Send Email: ${this._config.sendEmail}
|
|
113
|
+
Output Directory: ${this.outputDir}
|
|
114
|
+
Workers: ${config.workers}
|
|
115
|
+
`);
|
|
116
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
117
|
+
console.log('\n');
|
|
118
|
+
this._startTime = Date.now();
|
|
119
|
+
// Use the output directory from reporter config
|
|
120
|
+
// The outputDir is already set in the constructor, so we don't need to reset it here
|
|
121
|
+
// unless there's a specific override in the config
|
|
122
|
+
// If a project-specific output directory is set, use that instead
|
|
123
|
+
if (config.projects && config.projects.length > 0) {
|
|
124
|
+
// Try to find an output directory in any project config
|
|
125
|
+
for (const project of config.projects) {
|
|
126
|
+
if (project.outputDir) {
|
|
127
|
+
this.outputDir = path.resolve(project.outputDir);
|
|
128
|
+
this._fileHandler = new fileHandlerUtils_1.FileHandler(this.outputDir);
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Recursively counts the total number of tests in a suite and its children
|
|
136
|
+
*
|
|
137
|
+
* @param suite - The test suite to count tests from
|
|
138
|
+
* @returns The total number of tests
|
|
139
|
+
* @private
|
|
140
|
+
*/
|
|
141
|
+
_countTests(suite) {
|
|
142
|
+
let count = suite.tests.length;
|
|
143
|
+
for (const childSuite of suite.suites) {
|
|
144
|
+
count += this._countTests(childSuite);
|
|
145
|
+
}
|
|
146
|
+
return count;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Called when an error occurs during test setup or teardown.
|
|
150
|
+
* Logs the error with optional stack trace based on configuration.
|
|
151
|
+
* Now tracks errors to ensure they affect final exit code.
|
|
152
|
+
*
|
|
153
|
+
* @param error - The error that occurred
|
|
154
|
+
*/
|
|
155
|
+
onError(error) {
|
|
156
|
+
console.error(`${colors_1.colors.fgRed}❌ Setup or runtime error: ${error.message}${colors_1.colors.reset}`);
|
|
157
|
+
if (error.stack && this._config.showStackTrace) {
|
|
158
|
+
console.error(`${colors_1.colors.fgRed}${error.stack}${colors_1.colors.reset}`);
|
|
159
|
+
}
|
|
160
|
+
// Track non-test errors to include in final reporting
|
|
161
|
+
this._nonTestErrors.push(error);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Called when a test completes (whether passed, failed, or skipped).
|
|
165
|
+
* Records the test result and logs appropriate output based on the test status.
|
|
166
|
+
* Now tracks all test statuses including interrupted ones.
|
|
167
|
+
*
|
|
168
|
+
* @param test - The test case that completed
|
|
169
|
+
* @param result - The result of the test execution
|
|
170
|
+
*/
|
|
171
|
+
onTestEnd(test, result) {
|
|
172
|
+
const title = test.title;
|
|
173
|
+
const timeTakenSec = result.duration / 1000;
|
|
174
|
+
// Initialize test record if first attempt
|
|
175
|
+
if (!this._testRecords.has(title)) {
|
|
176
|
+
// Create an enhanced test case with required properties
|
|
177
|
+
const testCaseDetails = {
|
|
178
|
+
testId: test.id,
|
|
179
|
+
testTitle: test.title,
|
|
180
|
+
suiteTitle: test.parent?.title || 'Unknown Suite',
|
|
181
|
+
testFile: test.location?.file,
|
|
182
|
+
location: test.location,
|
|
183
|
+
outcome: test.outcome(),
|
|
184
|
+
status: utils_1.TestUtils.outcomeToStatus(test.outcome()),
|
|
185
|
+
owningTeam: utils_1.TestUtils.getOwningTeam(test),
|
|
186
|
+
};
|
|
187
|
+
this._testRecords.set(title, {
|
|
188
|
+
test: testCaseDetails,
|
|
189
|
+
attempts: [],
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
// Update test record with new attempt
|
|
193
|
+
const testRecord = this._testRecords.get(title);
|
|
194
|
+
if (testRecord) {
|
|
195
|
+
// Fix: Added null check instead of non-null assertion
|
|
196
|
+
testRecord.attempts.push({
|
|
197
|
+
status: result.status,
|
|
198
|
+
duration: timeTakenSec,
|
|
199
|
+
errors: result.errors.map((e) => ({
|
|
200
|
+
message: e.message || 'No error message',
|
|
201
|
+
stack: e.stack,
|
|
202
|
+
})),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
// Add failures to the FileHandler
|
|
206
|
+
if (result.status === 'failed' || result.status === 'timedOut') {
|
|
207
|
+
const errorMessage = result.errors[0]?.message || 'Unknown error';
|
|
208
|
+
const errorCategory = utils_1.TestUtils.categorizeError(errorMessage);
|
|
209
|
+
this._fileHandler.addFailure({
|
|
210
|
+
testId: test.id,
|
|
211
|
+
testTitle: test.title,
|
|
212
|
+
suiteTitle: test.parent?.title || 'Unknown Suite',
|
|
213
|
+
errorMessage: errorMessage,
|
|
214
|
+
errorStack: result.errors[0]?.stack || '',
|
|
215
|
+
duration: timeTakenSec,
|
|
216
|
+
owningTeam: utils_1.TestUtils.getOwningTeam(test),
|
|
217
|
+
isTimeout: result.status === 'timedOut',
|
|
218
|
+
errorCategory,
|
|
219
|
+
testFile: test.location?.file,
|
|
220
|
+
location: test.location,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
// Track interrupted tests specifically
|
|
224
|
+
if (result.status === 'interrupted') {
|
|
225
|
+
this._hasInterruptedTests = true;
|
|
226
|
+
}
|
|
227
|
+
// Log test outcome with appropriate formatting
|
|
228
|
+
this._logTestOutcome(test.title, result, timeTakenSec);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Called when all tests have completed.
|
|
232
|
+
* Processes results, displays summary statistics, and sets appropriate exit code.
|
|
233
|
+
* Now properly handles all error conditions including non-test errors.
|
|
234
|
+
*/
|
|
235
|
+
async onEnd() {
|
|
236
|
+
const endTime = Date.now();
|
|
237
|
+
const totalTimeSec = (endTime - this._startTime) / 1000;
|
|
238
|
+
const totalTimeDisplay = utils_1.TestUtils.formatTime(totalTimeSec);
|
|
239
|
+
// Process results
|
|
240
|
+
const { passedCount, testCount, skippedCount, failures, passedDurations } = utils_1.TestUtils.processTestResults(this._testRecords);
|
|
241
|
+
// Handle no tests case
|
|
242
|
+
if (testCount === 0) {
|
|
243
|
+
console.log(`${colors_1.colors.fgRed}❌ No tests found${colors_1.colors.reset}`);
|
|
244
|
+
this._exitWithError();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// Gather build information
|
|
248
|
+
const buildInfo = buildInfoUtils_1.BuildInfoUtils.getBuildInfo();
|
|
249
|
+
// Compute metrics
|
|
250
|
+
const summary = {
|
|
251
|
+
failures,
|
|
252
|
+
testCount,
|
|
253
|
+
passedCount,
|
|
254
|
+
skippedCount,
|
|
255
|
+
failedCount: testCount - passedCount - skippedCount,
|
|
256
|
+
totalTimeDisplay,
|
|
257
|
+
averageTime: utils_1.TestUtils.calculateAverageTime(passedDurations),
|
|
258
|
+
slowestTest: Math.max(...passedDurations, 0),
|
|
259
|
+
slowestTests: utils_1.TestUtils.findSlowestTests(this._testRecords, this._config.maxSlowTestsToShow),
|
|
260
|
+
buildInfo,
|
|
261
|
+
};
|
|
262
|
+
// Generate fix suggestions if enabled
|
|
263
|
+
if (this._config.generateFix && failures.length > 0) {
|
|
264
|
+
await this._generateFixSuggestions(failures);
|
|
265
|
+
}
|
|
266
|
+
// Create bugs if enabled
|
|
267
|
+
if (this._config.createBug && failures.length > 0) {
|
|
268
|
+
await this._createBugsForFailures(failures);
|
|
269
|
+
}
|
|
270
|
+
// Log results
|
|
271
|
+
utils_1.Logger.logSummary(summary);
|
|
272
|
+
// Report non-test errors
|
|
273
|
+
if (this._nonTestErrors.length > 0) {
|
|
274
|
+
console.log(`${colors_1.colors.fgRed}\nSetup or Teardown Errors:${colors_1.colors.reset}`);
|
|
275
|
+
this._nonTestErrors.forEach((error, index) => {
|
|
276
|
+
console.log(`${colors_1.colors.fgRed}Error #${index + 1}: ${error.message}${colors_1.colors.reset}`);
|
|
277
|
+
if (error.stack && this._config.showStackTrace) {
|
|
278
|
+
console.log(`${colors_1.colors.fgRed}${error.stack}${colors_1.colors.reset}`);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
// Report test failures
|
|
283
|
+
if (failures.length > 0) {
|
|
284
|
+
utils_1.Logger.logFailures(failures);
|
|
285
|
+
}
|
|
286
|
+
// Extract all test case details for summary
|
|
287
|
+
const allTestCases = Array.from(this._testRecords.values()).map((record) => record.test);
|
|
288
|
+
// Write summary and test details to JSON
|
|
289
|
+
this._fileHandler.writeSummary(summary, allTestCases);
|
|
290
|
+
// Publish to database if enabled
|
|
291
|
+
if (this._config.publishToDB) {
|
|
292
|
+
await this._publishToDatabase(summary, allTestCases, failures);
|
|
293
|
+
}
|
|
294
|
+
// Send email notification if enabled
|
|
295
|
+
if (this._config.sendEmail) {
|
|
296
|
+
await this._sendEmailNotification(summary, failures);
|
|
297
|
+
}
|
|
298
|
+
// Record last run status in a separate file
|
|
299
|
+
this.saveLastRunStatus(failures.length > 0);
|
|
300
|
+
// Handle interrupted tests
|
|
301
|
+
if (this._hasInterruptedTests) {
|
|
302
|
+
console.log(`${colors_1.colors.fgRed}\n⚠️ Some tests were interrupted. This may indicate a test hang or timeout.${colors_1.colors.reset}`);
|
|
303
|
+
}
|
|
304
|
+
// Determine exit status (any errors should cause a non-zero exit)
|
|
305
|
+
const hasErrors = failures.length > 0 || this._nonTestErrors.length > 0 || this._hasInterruptedTests;
|
|
306
|
+
if (hasErrors) {
|
|
307
|
+
this._exitWithError();
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
this._exitWithSuccess();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Exits the process with a success code.
|
|
315
|
+
* Extracted to a method to make the flow clearer and more maintainable.
|
|
316
|
+
* @private
|
|
317
|
+
*/
|
|
318
|
+
_exitWithSuccess() {
|
|
319
|
+
process.exitCode = 0;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Exits the process with an error code.
|
|
323
|
+
* Extracted to a method to make the flow clearer and more maintainable.
|
|
324
|
+
* @private
|
|
325
|
+
*/
|
|
326
|
+
_exitWithError() {
|
|
327
|
+
process.exitCode = 1;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Formats and logs the outcome of a single test with appropriate coloring.
|
|
331
|
+
* Handles different test states (passed, failed, skipped) and retry attempts.
|
|
332
|
+
* Now includes handling for interrupted tests and other unexpected statuses.
|
|
333
|
+
*
|
|
334
|
+
* @param title - The title of the test
|
|
335
|
+
* @param result - The result of the test execution
|
|
336
|
+
* @param timeTakenSec - The time taken by the test in seconds
|
|
337
|
+
* @private
|
|
338
|
+
*/
|
|
339
|
+
_logTestOutcome(title, result, timeTakenSec) {
|
|
340
|
+
const timeTakenFormatted = timeTakenSec.toFixed(2);
|
|
341
|
+
let passMessage;
|
|
342
|
+
switch (result.status) {
|
|
343
|
+
case 'passed':
|
|
344
|
+
passMessage = result.retry > 0 ? `✅ ${title} passed after retry` : `✅ ${title}`;
|
|
345
|
+
console.log(`${colors_1.colors.fgGreen}${passMessage} in ${timeTakenFormatted}s${colors_1.colors.reset}`);
|
|
346
|
+
break;
|
|
347
|
+
case 'failed':
|
|
348
|
+
case 'timedOut':
|
|
349
|
+
if (result.retry > 0) {
|
|
350
|
+
console.log(`${colors_1.colors.fgYellow}🔄 Retry attempt #${result.retry + 1} for "${title}"${colors_1.colors.reset}`);
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
console.log(`${colors_1.colors.fgRed}❌ ${title} failed in ${timeTakenFormatted}s${colors_1.colors.reset}`);
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
356
|
+
case 'skipped':
|
|
357
|
+
console.log(`${colors_1.colors.fgGray}⚠️ ${title} was skipped${colors_1.colors.reset}`);
|
|
358
|
+
break;
|
|
359
|
+
case 'interrupted':
|
|
360
|
+
console.log(`${colors_1.colors.fgRed}🛑 ${title} was interrupted${colors_1.colors.reset}`);
|
|
361
|
+
break;
|
|
362
|
+
default:
|
|
363
|
+
console.log(`${colors_1.colors.fgRed}⚠️ ${title} ended with unknown status: ${result.status} in ${timeTakenFormatted}s${colors_1.colors.reset}`);
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Records the status of the last test run in a JSON file
|
|
369
|
+
* @param hasFailed - Whether any tests failed
|
|
370
|
+
*/
|
|
371
|
+
saveLastRunStatus(hasFailed) {
|
|
372
|
+
const failedTests = Array.from(this._testRecords.values())
|
|
373
|
+
.filter((record) => record.test.status === 'failed')
|
|
374
|
+
.map((record) => record.test.testId || '');
|
|
375
|
+
const lastRunData = {
|
|
376
|
+
status: hasFailed ? 'failed' : 'passed',
|
|
377
|
+
failedTests,
|
|
378
|
+
};
|
|
379
|
+
try {
|
|
380
|
+
const filePath = path.join(this.outputDir, '.last-run.json');
|
|
381
|
+
fs.writeFileSync(filePath, JSON.stringify(lastRunData, null, 2));
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
console.error('Failed to write last run status:', error);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Generates AI-powered fix suggestions for test failures
|
|
389
|
+
*
|
|
390
|
+
* @param failures - Array of test failures
|
|
391
|
+
* @private
|
|
392
|
+
*/
|
|
393
|
+
async _generateFixSuggestions(failures) {
|
|
394
|
+
console.log('\n');
|
|
395
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
396
|
+
console.log(`${colors_1.colors.fgCyan}🤖 Generating AI-powered fix suggestions...${colors_1.colors.reset}`);
|
|
397
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
398
|
+
const sourceCodeCache = new Map();
|
|
399
|
+
for (const failure of failures) {
|
|
400
|
+
if (!failure.testFile)
|
|
401
|
+
continue;
|
|
402
|
+
try {
|
|
403
|
+
console.log(`${colors_1.colors.fgYellow}Generating fix suggestion for: ${failure.testTitle}${colors_1.colors.reset}`);
|
|
404
|
+
// Read the source file
|
|
405
|
+
if (!sourceCodeCache.has(failure.testFile)) {
|
|
406
|
+
const source = fs.readFileSync(failure.testFile, 'utf8');
|
|
407
|
+
sourceCodeCache.set(failure.testFile, source);
|
|
408
|
+
}
|
|
409
|
+
const result = await genaiUtils_1.GenAIUtils.generateFixSuggestion(failure, sourceCodeCache);
|
|
410
|
+
if (result) {
|
|
411
|
+
console.log(`${colors_1.colors.fgGreen}✅ Fix suggestion generated:${colors_1.colors.reset}`);
|
|
412
|
+
console.log(`${colors_1.colors.fgGreen} - Prompt: ${result.promptPath}${colors_1.colors.reset}`);
|
|
413
|
+
console.log(`${colors_1.colors.fgGreen} - Fix: ${result.fixPath}${colors_1.colors.reset}`);
|
|
414
|
+
// Generate PR only if generatePR flag is enabled
|
|
415
|
+
if (this._config.generatePR) {
|
|
416
|
+
console.log(`${colors_1.colors.fgCyan}🔄 Generating pull request with fix...${colors_1.colors.reset}`);
|
|
417
|
+
await this._generatePullRequest(failure, result);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
console.warn(`${colors_1.colors.fgYellow}⚠️ Could not generate fix suggestion.${colors_1.colors.reset}`);
|
|
422
|
+
console.warn(`${colors_1.colors.fgYellow} Check if you have a .env file with MISTRAL_API_KEY in the project root.${colors_1.colors.reset}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
console.error(`${colors_1.colors.fgRed}❌ Error generating fix suggestion for ${failure.testTitle}: ${error}${colors_1.colors.reset}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
console.log(`${colors_1.colors.fgCyan}AI fix suggestion generation complete${colors_1.colors.reset}`);
|
|
430
|
+
console.log(`${colors_1.colors.fgCyan}Thank you for using the AI fix suggestion tool!${colors_1.colors.reset}`);
|
|
431
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Generates a pull request with the AI-generated fix
|
|
435
|
+
*
|
|
436
|
+
* @param failure - The test failure information
|
|
437
|
+
* @param fixResult - The result from GenAIUtils containing prompt and fix paths
|
|
438
|
+
* @private
|
|
439
|
+
*/
|
|
440
|
+
async _generatePullRequest(failure, fixResult) {
|
|
441
|
+
try {
|
|
442
|
+
// Get the PR provider from registry
|
|
443
|
+
const prProvider = await ProviderRegistry_1.ProviderRegistry.getPRProvider();
|
|
444
|
+
// Read the fix content
|
|
445
|
+
const fixContent = fs.readFileSync(fixResult.fixPath, 'utf8');
|
|
446
|
+
if (!failure.testFile) {
|
|
447
|
+
console.warn(`${colors_1.colors.fgYellow}⚠️ Cannot create PR: test file path not available${colors_1.colors.reset}`);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
// Generate branch name from test info
|
|
451
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
|
|
452
|
+
const sanitizedTestTitle = failure.testTitle.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase();
|
|
453
|
+
const branchName = `autofix/${sanitizedTestTitle}-${timestamp}`;
|
|
454
|
+
// Get base branch from environment or default to main
|
|
455
|
+
const baseBranch = process.env.BASE_BRANCH || process.env.GITHUB_BASE_REF || 'main';
|
|
456
|
+
console.log(`${colors_1.colors.fgCyan} Creating topic branch: ${branchName}${colors_1.colors.reset}`);
|
|
457
|
+
// Step 1: Create the topic branch
|
|
458
|
+
const branchCreated = await prProvider.createBranch(branchName, baseBranch);
|
|
459
|
+
if (!branchCreated) {
|
|
460
|
+
console.error(`${colors_1.colors.fgRed}❌ Failed to create branch: ${branchName}${colors_1.colors.reset}`);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
console.log(`${colors_1.colors.fgGreen} ✅ Branch created successfully${colors_1.colors.reset}`);
|
|
464
|
+
// Step 2: Prepare file changes for commit
|
|
465
|
+
const fileChanges = [
|
|
466
|
+
{
|
|
467
|
+
path: failure.testFile,
|
|
468
|
+
content: fixContent,
|
|
469
|
+
action: 'modify',
|
|
470
|
+
},
|
|
471
|
+
];
|
|
472
|
+
// Step 3: Commit changes to the topic branch
|
|
473
|
+
console.log(`${colors_1.colors.fgCyan} Committing changes to ${branchName}${colors_1.colors.reset}`);
|
|
474
|
+
const commitMessage = `fix: Auto-fix for failing test "${failure.testTitle}"
|
|
475
|
+
|
|
476
|
+
Test: ${failure.testTitle}
|
|
477
|
+
Suite: ${failure.suiteTitle}
|
|
478
|
+
File: ${failure.testFile}
|
|
479
|
+
Error: ${failure.errorMessage.substring(0, 100)}${failure.errorMessage.length > 100 ? '...' : ''}
|
|
480
|
+
|
|
481
|
+
This is an AI-generated fix suggestion.`;
|
|
482
|
+
const commitHash = await prProvider.commitChanges(branchName, fileChanges, commitMessage);
|
|
483
|
+
if (!commitHash) {
|
|
484
|
+
console.error(`${colors_1.colors.fgRed}❌ Failed to commit changes${colors_1.colors.reset}`);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
console.log(`${colors_1.colors.fgGreen} ✅ Changes committed: ${commitHash.substring(0, 7)}${colors_1.colors.reset}`);
|
|
488
|
+
// Step 4: Create pull request from topic branch to base branch
|
|
489
|
+
console.log(`${colors_1.colors.fgCyan} Creating pull request: ${branchName} → ${baseBranch}${colors_1.colors.reset}`);
|
|
490
|
+
const prInfo = await prProvider.createPullRequest({
|
|
491
|
+
sourceBranch: branchName,
|
|
492
|
+
targetBranch: baseBranch,
|
|
493
|
+
title: `🤖 Auto-fix: ${failure.testTitle}`,
|
|
494
|
+
description: `## AI-Generated Fix Suggestion
|
|
495
|
+
|
|
496
|
+
**Test**: ${failure.testTitle}
|
|
497
|
+
**Suite**: ${failure.suiteTitle}
|
|
498
|
+
**File**: \`${failure.testFile}\`
|
|
499
|
+
|
|
500
|
+
### Error
|
|
501
|
+
\`\`\`
|
|
502
|
+
${failure.errorMessage}
|
|
503
|
+
\`\`\`
|
|
504
|
+
|
|
505
|
+
### Error Category
|
|
506
|
+
${failure.errorCategory}
|
|
507
|
+
|
|
508
|
+
### Duration
|
|
509
|
+
${failure.duration.toFixed(2)}s
|
|
510
|
+
|
|
511
|
+
### AI Fix Analysis
|
|
512
|
+
This PR contains an AI-generated fix suggestion. Please review carefully before merging.
|
|
513
|
+
|
|
514
|
+
**Fix Details**: See commit ${commitHash?.substring(0, 7) || 'unknown'}
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
_This PR was automatically generated by playwright-ai-reporter_`,
|
|
518
|
+
labels: ['auto-fix', 'test-failure', 'ai-generated'],
|
|
519
|
+
draft: true, // Create as draft for review
|
|
520
|
+
});
|
|
521
|
+
if (prInfo) {
|
|
522
|
+
console.log(`${colors_1.colors.fgGreen}✅ Pull request created successfully:${colors_1.colors.reset}`);
|
|
523
|
+
console.log(`${colors_1.colors.fgGreen} PR #${prInfo.number}: ${prInfo.url}${colors_1.colors.reset}`);
|
|
524
|
+
console.log(`${colors_1.colors.fgGreen} Branch: ${prInfo.sourceBranch} → ${prInfo.targetBranch}${colors_1.colors.reset}`);
|
|
525
|
+
console.log(`${colors_1.colors.fgGreen} Status: ${prInfo.status} (draft)${colors_1.colors.reset}`);
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
console.warn(`${colors_1.colors.fgYellow}⚠️ Failed to create pull request${colors_1.colors.reset}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
console.error(`${colors_1.colors.fgRed}❌ Error creating pull request: ${error}${colors_1.colors.reset}`);
|
|
533
|
+
if (error instanceof Error) {
|
|
534
|
+
console.error(`${colors_1.colors.fgRed} ${error.message}${colors_1.colors.reset}`);
|
|
535
|
+
if (error.message.includes('PR provider configuration not found')) {
|
|
536
|
+
console.error(`${colors_1.colors.fgRed} Please configure PR provider in your .env file${colors_1.colors.reset}`);
|
|
537
|
+
console.error(`${colors_1.colors.fgRed} See docs/ENV_CONFIG_GUIDE.md for details${colors_1.colors.reset}`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Creates bugs in the configured bug tracker for test failures
|
|
544
|
+
*
|
|
545
|
+
* @param failures - Array of test failures
|
|
546
|
+
* @private
|
|
547
|
+
*/
|
|
548
|
+
async _createBugsForFailures(failures) {
|
|
549
|
+
console.log('\n');
|
|
550
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
551
|
+
console.log(`${colors_1.colors.fgCyan}🐛 Creating bugs for test failures...${colors_1.colors.reset}`);
|
|
552
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
553
|
+
try {
|
|
554
|
+
// Get the bug tracker provider from registry
|
|
555
|
+
const bugTracker = await ProviderRegistry_1.ProviderRegistry.getBugTrackerProvider();
|
|
556
|
+
for (const failure of failures) {
|
|
557
|
+
try {
|
|
558
|
+
console.log(`${colors_1.colors.fgYellow}Creating bug for: ${failure.testTitle}${colors_1.colors.reset}`);
|
|
559
|
+
// Create the bug
|
|
560
|
+
const bugInfo = await bugTracker.createBug({
|
|
561
|
+
title: `[Test Failure] ${failure.testTitle}`,
|
|
562
|
+
description: `## Test Failure Report
|
|
563
|
+
|
|
564
|
+
**Test**: ${failure.testTitle}
|
|
565
|
+
**Suite**: ${failure.suiteTitle}
|
|
566
|
+
**File**: \`${failure.testFile || 'unknown'}\`
|
|
567
|
+
**Owner**: ${failure.owningTeam}
|
|
568
|
+
|
|
569
|
+
### Error
|
|
570
|
+
\`\`\`
|
|
571
|
+
${failure.errorMessage}
|
|
572
|
+
\`\`\`
|
|
573
|
+
|
|
574
|
+
### Error Category
|
|
575
|
+
${failure.errorCategory}
|
|
576
|
+
|
|
577
|
+
### Stack Trace
|
|
578
|
+
\`\`\`
|
|
579
|
+
${failure.errorStack.substring(0, 500)}${failure.errorStack.length > 500 ? '...' : ''}
|
|
580
|
+
\`\`\`
|
|
581
|
+
|
|
582
|
+
### Additional Information
|
|
583
|
+
- **Duration**: ${failure.duration.toFixed(2)}s
|
|
584
|
+
- **Timeout**: ${failure.isTimeout ? 'Yes' : 'No'}
|
|
585
|
+
- **Test ID**: ${failure.testId || 'N/A'}
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
_This bug was automatically created by playwright-ai-reporter_`,
|
|
589
|
+
priority: failure.isTimeout ? IBugTrackerProvider_1.BugPriority.High : IBugTrackerProvider_1.BugPriority.Medium,
|
|
590
|
+
labels: ['test-failure', 'automated', failure.errorCategory.toLowerCase()],
|
|
591
|
+
assignee: failure.owningTeam,
|
|
592
|
+
testFailure: failure,
|
|
593
|
+
});
|
|
594
|
+
if (bugInfo) {
|
|
595
|
+
console.log(`${colors_1.colors.fgGreen} ✅ Bug created: ${bugInfo.url}${colors_1.colors.reset}`);
|
|
596
|
+
console.log(`${colors_1.colors.fgGreen} ID: ${bugInfo.id} | Status: ${bugInfo.status}${colors_1.colors.reset}`);
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
console.warn(`${colors_1.colors.fgYellow} ⚠️ Failed to create bug${colors_1.colors.reset}`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
catch (error) {
|
|
603
|
+
console.error(`${colors_1.colors.fgRed} ❌ Error creating bug for ${failure.testTitle}: ${error}${colors_1.colors.reset}`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
console.log(`${colors_1.colors.fgGreen}✅ Bug creation complete${colors_1.colors.reset}`);
|
|
607
|
+
}
|
|
608
|
+
catch (error) {
|
|
609
|
+
console.error(`${colors_1.colors.fgRed}❌ Error accessing bug tracker: ${error}${colors_1.colors.reset}`);
|
|
610
|
+
if (error instanceof Error) {
|
|
611
|
+
console.error(`${colors_1.colors.fgRed} ${error.message}${colors_1.colors.reset}`);
|
|
612
|
+
if (error.message.includes('Bug tracker provider configuration not found')) {
|
|
613
|
+
console.error(`${colors_1.colors.fgRed} Please configure bug tracker provider in your .env file${colors_1.colors.reset}`);
|
|
614
|
+
console.error(`${colors_1.colors.fgRed} See docs/ENV_CONFIG_GUIDE.md for details${colors_1.colors.reset}`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Publishes test results to database
|
|
622
|
+
*
|
|
623
|
+
* @param summary - Test run summary
|
|
624
|
+
* @param allTestCases - All test cases details
|
|
625
|
+
* @param failures - Test failures
|
|
626
|
+
* @private
|
|
627
|
+
*/
|
|
628
|
+
async _publishToDatabase(summary, allTestCases, failures) {
|
|
629
|
+
console.log('\n');
|
|
630
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
631
|
+
console.log(`${colors_1.colors.fgCyan}📊 Publishing test results to database...${colors_1.colors.reset}`);
|
|
632
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
633
|
+
try {
|
|
634
|
+
// Check if database provider is configured
|
|
635
|
+
const dbConfig = process.env.DATABASE_PROVIDER;
|
|
636
|
+
if (!dbConfig) {
|
|
637
|
+
console.log(`${colors_1.colors.fgYellow}⚠️ Database provider not configured${colors_1.colors.reset}`);
|
|
638
|
+
console.log(`${colors_1.colors.fgYellow} Set DATABASE_PROVIDER in your .env file to enable database logging${colors_1.colors.reset}`);
|
|
639
|
+
console.log(`${colors_1.colors.fgYellow} Example: DATABASE_PROVIDER=sqlite${colors_1.colors.reset}`);
|
|
640
|
+
console.log(`${colors_1.colors.fgYellow} See docs/ENV_CONFIG_GUIDE.md for details${colors_1.colors.reset}`);
|
|
641
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
// Get the database provider from registry
|
|
645
|
+
const dbProvider = await ProviderRegistry_1.ProviderRegistry.getDatabaseProvider();
|
|
646
|
+
// Initialize database (creates DB and tables if they don't exist)
|
|
647
|
+
console.log(`${colors_1.colors.fgCyan} Initializing database connection...${colors_1.colors.reset}`);
|
|
648
|
+
await dbProvider.initialize();
|
|
649
|
+
console.log(`${colors_1.colors.fgGreen} ✅ Database initialized${colors_1.colors.reset}`);
|
|
650
|
+
// Save test run data
|
|
651
|
+
console.log(`${colors_1.colors.fgCyan} Saving test run data...${colors_1.colors.reset}`);
|
|
652
|
+
const testRunId = await dbProvider.saveTestRun({
|
|
653
|
+
name: `Test Run ${new Date().toISOString()}`,
|
|
654
|
+
timestamp: new Date(),
|
|
655
|
+
environment: process.env.NODE_ENV || 'test',
|
|
656
|
+
branch: summary.buildInfo?.buildBranch,
|
|
657
|
+
commitHash: summary.buildInfo?.commitId,
|
|
658
|
+
totalTests: summary.testCount,
|
|
659
|
+
passedTests: summary.passedCount,
|
|
660
|
+
failedTests: summary.failedCount,
|
|
661
|
+
skippedTests: summary.skippedCount,
|
|
662
|
+
duration: parseFloat(summary.totalTimeDisplay),
|
|
663
|
+
metadata: {
|
|
664
|
+
buildInfo: summary.buildInfo,
|
|
665
|
+
},
|
|
666
|
+
});
|
|
667
|
+
console.log(`${colors_1.colors.fgGreen} ✅ Test run saved (ID: ${testRunId})${colors_1.colors.reset}`);
|
|
668
|
+
// Save individual test results
|
|
669
|
+
console.log(`${colors_1.colors.fgCyan} Saving ${allTestCases.length} test results...${colors_1.colors.reset}`);
|
|
670
|
+
for (const testCase of allTestCases) {
|
|
671
|
+
const failure = failures.find((f) => f.testTitle === testCase.testTitle);
|
|
672
|
+
await dbProvider.saveTestResult({
|
|
673
|
+
testRunId,
|
|
674
|
+
testId: testCase.testId || `test-${Date.now()}`,
|
|
675
|
+
testTitle: testCase.testTitle,
|
|
676
|
+
suiteTitle: testCase.suiteTitle,
|
|
677
|
+
status: testCase.status || 'unknown',
|
|
678
|
+
duration: testCase.duration || 0,
|
|
679
|
+
errorMessage: failure?.errorMessage,
|
|
680
|
+
errorStack: failure?.errorStack,
|
|
681
|
+
timestamp: new Date(),
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
console.log(`${colors_1.colors.fgGreen} ✅ All test results saved${colors_1.colors.reset}`);
|
|
685
|
+
console.log(`${colors_1.colors.fgGreen}✅ Test results published to database${colors_1.colors.reset}`);
|
|
686
|
+
console.log(`${colors_1.colors.fgGreen} Summary: ${summary.testCount} tests, ${summary.passedCount} passed, ${summary.failedCount} failed${colors_1.colors.reset}`);
|
|
687
|
+
}
|
|
688
|
+
catch (error) {
|
|
689
|
+
console.error(`${colors_1.colors.fgRed}❌ Error publishing to database: ${error}${colors_1.colors.reset}`);
|
|
690
|
+
if (error instanceof Error) {
|
|
691
|
+
console.error(`${colors_1.colors.fgRed} ${error.message}${colors_1.colors.reset}`);
|
|
692
|
+
if (error.message.includes('Database provider configuration not found') ||
|
|
693
|
+
error.message.includes('Database configuration not found')) {
|
|
694
|
+
console.error(`${colors_1.colors.fgRed} Please configure database provider in your .env file${colors_1.colors.reset}`);
|
|
695
|
+
console.error(`${colors_1.colors.fgRed} See docs/ENV_CONFIG_GUIDE.md for details${colors_1.colors.reset}`);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
console.log(`${colors_1.colors.fgCyan}Database publishing complete${colors_1.colors.reset}`);
|
|
700
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Sends email notification with test results
|
|
704
|
+
*
|
|
705
|
+
* @param summary - Test run summary
|
|
706
|
+
* @param failures - Test failures
|
|
707
|
+
* @private
|
|
708
|
+
*/
|
|
709
|
+
async _sendEmailNotification(summary, failures) {
|
|
710
|
+
console.log('\n');
|
|
711
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
712
|
+
console.log(`${colors_1.colors.fgCyan}📧 Sending email notification...${colors_1.colors.reset}`);
|
|
713
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
714
|
+
try {
|
|
715
|
+
// Check if notification provider is configured
|
|
716
|
+
const emailConfig = process.env.NOTIFICATION_PROVIDER;
|
|
717
|
+
if (!emailConfig) {
|
|
718
|
+
console.log(`${colors_1.colors.fgYellow}⚠️ Email notification provider not configured${colors_1.colors.reset}`);
|
|
719
|
+
console.log(`${colors_1.colors.fgYellow} Set NOTIFICATION_PROVIDER in your .env file to enable email notifications${colors_1.colors.reset}`);
|
|
720
|
+
console.log(`${colors_1.colors.fgYellow} Example: NOTIFICATION_PROVIDER=email${colors_1.colors.reset}`);
|
|
721
|
+
console.log(`${colors_1.colors.fgYellow} See docs/ENV_CONFIG_GUIDE.md for details${colors_1.colors.reset}`);
|
|
722
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
// Get the notification provider from registry
|
|
726
|
+
const notificationProvider = await ProviderRegistry_1.ProviderRegistry.getNotificationProvider();
|
|
727
|
+
// Get email recipients from environment or use default
|
|
728
|
+
const recipients = process.env.EMAIL_RECIPIENTS?.split(',').map((r) => r.trim()) || [];
|
|
729
|
+
if (recipients.length === 0) {
|
|
730
|
+
console.log(`${colors_1.colors.fgYellow}⚠️ No email recipients configured${colors_1.colors.reset}`);
|
|
731
|
+
console.log(`${colors_1.colors.fgYellow} Set EMAIL_RECIPIENTS in your .env file (comma-separated)${colors_1.colors.reset}`);
|
|
732
|
+
console.log(`${colors_1.colors.fgYellow} Example: EMAIL_RECIPIENTS=dev@example.com,qa@example.com${colors_1.colors.reset}`);
|
|
733
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
console.log(`${colors_1.colors.fgCyan} Sending to: ${recipients.join(', ')}${colors_1.colors.reset}`);
|
|
737
|
+
// Determine notification severity
|
|
738
|
+
const severity = summary.failedCount > 0
|
|
739
|
+
? INotificationProvider_1.NotificationSeverity.Error
|
|
740
|
+
: summary.skippedCount > summary.testCount * 0.2
|
|
741
|
+
? INotificationProvider_1.NotificationSeverity.Warning
|
|
742
|
+
: INotificationProvider_1.NotificationSeverity.Info;
|
|
743
|
+
// Send test summary email
|
|
744
|
+
console.log(`${colors_1.colors.fgCyan} Sending test summary...${colors_1.colors.reset}`);
|
|
745
|
+
const summaryResult = await notificationProvider.sendTestSummary(summary, {
|
|
746
|
+
recipients,
|
|
747
|
+
severity,
|
|
748
|
+
subject: `Test Run ${summary.failedCount > 0 ? 'Failed' : 'Completed'}: ${summary.passedCount}/${summary.testCount} passed`,
|
|
749
|
+
});
|
|
750
|
+
if (summaryResult.success) {
|
|
751
|
+
console.log(`${colors_1.colors.fgGreen} ✅ Summary email sent (ID: ${summaryResult.messageId})${colors_1.colors.reset}`);
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
console.warn(`${colors_1.colors.fgYellow} ⚠️ Failed to send summary email: ${summaryResult.error}${colors_1.colors.reset}`);
|
|
755
|
+
}
|
|
756
|
+
// Send failures email if there are failures
|
|
757
|
+
if (failures.length > 0) {
|
|
758
|
+
console.log(`${colors_1.colors.fgCyan} Sending failure details...${colors_1.colors.reset}`);
|
|
759
|
+
const failuresResult = await notificationProvider.sendTestFailures(failures, {
|
|
760
|
+
recipients,
|
|
761
|
+
severity: INotificationProvider_1.NotificationSeverity.Error,
|
|
762
|
+
subject: `Test Failures: ${failures.length} test(s) failed`,
|
|
763
|
+
});
|
|
764
|
+
if (failuresResult.success) {
|
|
765
|
+
console.log(`${colors_1.colors.fgGreen} ✅ Failures email sent (ID: ${failuresResult.messageId})${colors_1.colors.reset}`);
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
console.warn(`${colors_1.colors.fgYellow} ⚠️ Failed to send failures email: ${failuresResult.error}${colors_1.colors.reset}`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
console.log(`${colors_1.colors.fgGreen}✅ Email notification complete${colors_1.colors.reset}`);
|
|
772
|
+
}
|
|
773
|
+
catch (error) {
|
|
774
|
+
console.error(`${colors_1.colors.fgRed}❌ Error sending email notification: ${error}${colors_1.colors.reset}`);
|
|
775
|
+
if (error instanceof Error) {
|
|
776
|
+
console.error(`${colors_1.colors.fgRed} ${error.message}${colors_1.colors.reset}`);
|
|
777
|
+
if (error.message.includes('Notification provider configuration not found')) {
|
|
778
|
+
console.error(`${colors_1.colors.fgRed} Please configure notification provider in your .env file${colors_1.colors.reset}`);
|
|
779
|
+
console.error(`${colors_1.colors.fgRed} See docs/ENV_CONFIG_GUIDE.md for details${colors_1.colors.reset}`);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
console.log(`${colors_1.colors.fgCyan}Email notification complete${colors_1.colors.reset}`);
|
|
784
|
+
console.log(`${colors_1.colors.fgCyan}===============================================${colors_1.colors.reset}`);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
exports.default = PlaywrightTestReporter;
|