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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1183 -0
  3. package/dist/colors.d.ts +54 -0
  4. package/dist/colors.js +57 -0
  5. package/dist/examples/ReporterWorkflow.d.ts +54 -0
  6. package/dist/examples/ReporterWorkflow.js +307 -0
  7. package/dist/providers/ProviderRegistry.d.ts +79 -0
  8. package/dist/providers/ProviderRegistry.js +195 -0
  9. package/dist/providers/ai/AIProviderFactory.d.ts +33 -0
  10. package/dist/providers/ai/AIProviderFactory.js +82 -0
  11. package/dist/providers/ai/AnthropicProvider.d.ts +15 -0
  12. package/dist/providers/ai/AnthropicProvider.js +128 -0
  13. package/dist/providers/ai/AzureOpenAIProvider.d.ts +20 -0
  14. package/dist/providers/ai/AzureOpenAIProvider.js +158 -0
  15. package/dist/providers/ai/GoogleAIProvider.d.ts +17 -0
  16. package/dist/providers/ai/GoogleAIProvider.js +154 -0
  17. package/dist/providers/ai/MistralProvider.d.ts +16 -0
  18. package/dist/providers/ai/MistralProvider.js +137 -0
  19. package/dist/providers/ai/OpenAIProvider.d.ts +16 -0
  20. package/dist/providers/ai/OpenAIProvider.js +141 -0
  21. package/dist/providers/bugTrackers/AzureDevOpsBugTracker.d.ts +32 -0
  22. package/dist/providers/bugTrackers/AzureDevOpsBugTracker.js +295 -0
  23. package/dist/providers/bugTrackers/GitHubBugTracker.d.ts +28 -0
  24. package/dist/providers/bugTrackers/GitHubBugTracker.js +241 -0
  25. package/dist/providers/bugTrackers/JiraBugTracker.d.ts +29 -0
  26. package/dist/providers/bugTrackers/JiraBugTracker.js +279 -0
  27. package/dist/providers/databases/MySQLProvider.d.ts +32 -0
  28. package/dist/providers/databases/MySQLProvider.js +274 -0
  29. package/dist/providers/databases/SQLiteProvider.d.ts +28 -0
  30. package/dist/providers/databases/SQLiteProvider.js +272 -0
  31. package/dist/providers/factories/BugTrackerFactory.d.ts +20 -0
  32. package/dist/providers/factories/BugTrackerFactory.js +50 -0
  33. package/dist/providers/factories/DatabaseFactory.d.ts +28 -0
  34. package/dist/providers/factories/DatabaseFactory.js +71 -0
  35. package/dist/providers/factories/NotificationFactory.d.ts +24 -0
  36. package/dist/providers/factories/NotificationFactory.js +64 -0
  37. package/dist/providers/factories/PRProviderFactory.d.ts +20 -0
  38. package/dist/providers/factories/PRProviderFactory.js +45 -0
  39. package/dist/providers/index.d.ts +28 -0
  40. package/dist/providers/index.js +55 -0
  41. package/dist/providers/interfaces/IAIProvider.d.ts +59 -0
  42. package/dist/providers/interfaces/IAIProvider.js +5 -0
  43. package/dist/providers/interfaces/IBugTrackerProvider.d.ts +70 -0
  44. package/dist/providers/interfaces/IBugTrackerProvider.js +20 -0
  45. package/dist/providers/interfaces/IDatabaseProvider.d.ts +90 -0
  46. package/dist/providers/interfaces/IDatabaseProvider.js +5 -0
  47. package/dist/providers/interfaces/INotificationProvider.d.ts +59 -0
  48. package/dist/providers/interfaces/INotificationProvider.js +13 -0
  49. package/dist/providers/interfaces/IPRProvider.d.ts +82 -0
  50. package/dist/providers/interfaces/IPRProvider.js +5 -0
  51. package/dist/providers/notifications/EmailNotificationProvider.d.ts +29 -0
  52. package/dist/providers/notifications/EmailNotificationProvider.js +290 -0
  53. package/dist/providers/pr/AzureDevOpsPRProvider.d.ts +30 -0
  54. package/dist/providers/pr/AzureDevOpsPRProvider.js +263 -0
  55. package/dist/providers/pr/GitHubPRProvider.d.ts +29 -0
  56. package/dist/providers/pr/GitHubPRProvider.js +320 -0
  57. package/dist/reporter.d.ts +138 -0
  58. package/dist/reporter.js +787 -0
  59. package/dist/types/index.d.ts +168 -0
  60. package/dist/types/index.js +2 -0
  61. package/dist/utils/buildInfoUtils.d.ts +26 -0
  62. package/dist/utils/buildInfoUtils.js +125 -0
  63. package/dist/utils/configValidator.d.ts +67 -0
  64. package/dist/utils/configValidator.js +454 -0
  65. package/dist/utils/fileHandlerUtils.d.ts +42 -0
  66. package/dist/utils/fileHandlerUtils.js +136 -0
  67. package/dist/utils/genaiUtils.d.ts +38 -0
  68. package/dist/utils/genaiUtils.js +178 -0
  69. package/dist/utils/historyUtils.d.ts +49 -0
  70. package/dist/utils/historyUtils.js +118 -0
  71. package/dist/utils/utils.d.ts +104 -0
  72. package/dist/utils/utils.js +371 -0
  73. package/docs/API.md +591 -0
  74. package/docs/ENV_CONFIG_GUIDE.md +444 -0
  75. package/docs/IMPLEMENTATION_SUMMARY.md +285 -0
  76. package/docs/PROVIDERS.md +261 -0
  77. package/docs/QUICKSTART.md +350 -0
  78. package/docs/README.md +253 -0
  79. package/docs/TROUBLESHOOTING.md +577 -0
  80. package/docs/design.md +384 -0
  81. package/docs/logo.png +0 -0
  82. package/examples/README.md +326 -0
  83. package/examples/VALIDATION.md +68 -0
  84. package/examples/env-configs/.env.anthropic-minimal +40 -0
  85. package/examples/env-configs/.env.azure-stack +71 -0
  86. package/examples/env-configs/.env.example +32 -0
  87. package/examples/env-configs/.env.github-stack +57 -0
  88. package/examples/env-configs/.env.google-mysql +61 -0
  89. package/examples/env-configs/.env.ms-auth-examples +56 -0
  90. package/examples/env-configs/.env.openai-jira +64 -0
  91. package/examples/package.json +22 -0
  92. package/package.json +70 -0
  93. package/playwright-ai-reporter-0.0.4.tgz +0 -0
@@ -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;