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,371 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Logger = exports.TestUtils = void 0;
4
+ const colors_1 = require("../colors");
5
+ // Adding required enum or interface definitions for Team
6
+ var Team;
7
+ (function (Team) {
8
+ Team["Frontend"] = "Frontend";
9
+ Team["Backend"] = "Backend";
10
+ Team["QA"] = "QA";
11
+ Team["DevOps"] = "DevOps";
12
+ Team["Performance"] = "Performance";
13
+ Team["Security"] = "Security";
14
+ Team["Accessibility"] = "Accessibility";
15
+ Team["Mobile"] = "Mobile";
16
+ Team["API"] = "API";
17
+ Team["Database"] = "Database";
18
+ Team["Analytics"] = "Analytics";
19
+ Team["Infrastructure"] = "Infrastructure";
20
+ Team["Monitoring"] = "Monitoring";
21
+ Team["Documentation"] = "Documentation";
22
+ Team["Support"] = "Support";
23
+ Team["Marketing"] = "Marketing";
24
+ Team["Sales"] = "Sales";
25
+ Team["Training"] = "Training";
26
+ Team["Research"] = "Research";
27
+ Team["Compliance"] = "Compliance";
28
+ Team["Legal"] = "Legal";
29
+ Team["Product"] = "Product";
30
+ Team["Design"] = "Design";
31
+ Team["UserExperience"] = "User Experience";
32
+ Team["UserInterface"] = "User Interface";
33
+ Team["BusinessIntelligence"] = "Business Intelligence";
34
+ Team["DataScience"] = "Data Science";
35
+ Team["DataEngineering"] = "Data Engineering";
36
+ Team["DataAnalytics"] = "Data Analytics";
37
+ Team["DataVisualization"] = "Data Visualization";
38
+ Team["DataGovernance"] = "Data Governance";
39
+ Team["DataQuality"] = "Data Quality";
40
+ // Add other teams as needed
41
+ })(Team || (Team = {}));
42
+ const FallbackTeam = 'Unknown'; // Default fallback when no team is identified
43
+ /**
44
+ * Utility class containing static methods for test result processing and calculations.
45
+ * Provides functionality for formatting time, calculating statistics, and processing test results.
46
+ */
47
+ class TestUtils {
48
+ /**
49
+ * Formats a time duration from seconds into a human-readable string.
50
+ * Converts to minutes if the duration is longer than 60 seconds.
51
+ *
52
+ * @param timeInSeconds - The time duration to format in seconds
53
+ * @returns A formatted string with appropriate units (e.g., "1.23s" or "2.50min")
54
+ */
55
+ static formatTime(timeInSeconds) {
56
+ return timeInSeconds < 60 ? `${timeInSeconds.toFixed(2)}s` : `${(timeInSeconds / 60).toFixed(2)}min`;
57
+ }
58
+ /**
59
+ * Calculates the average duration from an array of time measurements.
60
+ *
61
+ * @param durations - Array of durations in seconds
62
+ * @returns The average duration in seconds, or 0 if the array is empty
63
+ */
64
+ static calculateAverageTime(durations) {
65
+ return durations.length === 0 ? 0 : durations.reduce((sum, duration) => sum + duration, 0) / durations.length;
66
+ }
67
+ /**
68
+ * Finds the slowest tests from all test records.
69
+ * Only considers passed tests when calculating the slowest ones.
70
+ *
71
+ * @param testRecords - Map of all test records
72
+ * @param limit - Maximum number of slow tests to return
73
+ * @returns Array of the slowest tests, sorted by duration
74
+ */
75
+ static findSlowestTests(testRecords, limit) {
76
+ return Array.from(testRecords.values())
77
+ .flatMap(({ test, attempts }) => attempts
78
+ .filter((a) => a.status === 'passed')
79
+ .map((a) => ({
80
+ testTitle: test.testTitle,
81
+ duration: a.duration,
82
+ })))
83
+ .sort((a, b) => b.duration - a.duration)
84
+ .slice(0, limit);
85
+ }
86
+ /**
87
+ * Categorizes the error message into predefined types
88
+ * @param message - Error message
89
+ * @returns Error category
90
+ */
91
+ static categorizeError(message) {
92
+ if (message.includes('No node found') || message.includes('not visible')) {
93
+ return 'ElementNotFound';
94
+ }
95
+ else if (message.includes('Timeout') || message.includes('timed out')) {
96
+ return 'Timeout/DelayedElement';
97
+ }
98
+ else if (message.includes('selector') || message.includes('locator')) {
99
+ return 'SelectorChanged';
100
+ }
101
+ else if (message.includes('expect(') || message.includes('assertion failed')) {
102
+ return 'AssertionFailure';
103
+ }
104
+ else if (message.includes('Network') || message.includes('fetch failed') || message.includes('status=')) {
105
+ return 'NetworkError';
106
+ }
107
+ else if (message.includes('javascript error') ||
108
+ message.includes('undefined is not') ||
109
+ message.includes('null')) {
110
+ return 'JavaScriptError';
111
+ }
112
+ else if (message.includes('navigation') || message.includes('page.goto')) {
113
+ return 'NavigationError';
114
+ }
115
+ else if (message.includes('element is not clickable') || message.includes('intercepted')) {
116
+ return 'ElementInteractionError';
117
+ }
118
+ else if (message.includes('permission') || message.includes('access denied')) {
119
+ return 'PermissionError';
120
+ }
121
+ else {
122
+ return 'Unknown';
123
+ }
124
+ }
125
+ /**
126
+ * Convert test outcome to status
127
+ * @param outcome - Test outcome
128
+ * @returns Standardized status
129
+ */
130
+ static outcomeToStatus(outcome) {
131
+ switch (outcome) {
132
+ case 'skipped':
133
+ return 'skipped';
134
+ case 'expected':
135
+ return 'passed';
136
+ case 'unexpected':
137
+ return 'failed';
138
+ case 'flaky':
139
+ return 'failed';
140
+ default:
141
+ return outcome;
142
+ }
143
+ }
144
+ /**
145
+ * Determines the team that owns a test based on test name and annotations
146
+ * @param test - Test case to analyze
147
+ * @returns Name of the team that owns the test
148
+ */
149
+ static getOwningTeam(test) {
150
+ const testName = test.title || '';
151
+ for (const team in Team) {
152
+ if (testName.includes(this.wrap(team))) {
153
+ return team;
154
+ }
155
+ }
156
+ const teamName = test.annotations.find((a) => a.type === 'team')?.description;
157
+ if (teamName && teamName in Team) {
158
+ return teamName;
159
+ }
160
+ const ownerName = test.annotations.find((a) => a.type === 'owner')?.description;
161
+ if (ownerName && ownerName in Team) {
162
+ return ownerName;
163
+ }
164
+ if (process.env.FALLBACK_TEAM && process.env.FALLBACK_TEAM !== '' && process.env.FALLBACK_TEAM in Team) {
165
+ return process.env.FALLBACK_TEAM;
166
+ }
167
+ return FallbackTeam;
168
+ }
169
+ /**
170
+ * Helper method to wrap a string for team name matching
171
+ * @param text - Text to wrap
172
+ * @returns Wrapped text
173
+ */
174
+ static wrap(text) {
175
+ return `[${text}]`;
176
+ }
177
+ /**
178
+ * Processes all test records to generate comprehensive test results.
179
+ * Calculates passed, failed, and skipped test counts, and collects timing information.
180
+ *
181
+ * @param testRecords - Map of all test records
182
+ * @returns Object containing test counts, failures, and timing information
183
+ */
184
+ static processTestResults(testRecords) {
185
+ let passedCount = 0;
186
+ let testCount = 0;
187
+ let skippedCount = 0;
188
+ let failedCount = 0;
189
+ const failures = [];
190
+ const passedDurations = [];
191
+ for (const { test, attempts } of testRecords.values()) {
192
+ testCount++;
193
+ const finalOutcome = test.outcome;
194
+ const finalAttempt = attempts[attempts.length - 1];
195
+ if (finalOutcome === 'expected' || finalOutcome === 'flaky') {
196
+ passedCount++;
197
+ passedDurations.push(...attempts.filter((a) => a.status === 'passed').map((a) => a.duration));
198
+ }
199
+ else if (finalOutcome === 'unexpected') {
200
+ const isTimeout = finalAttempt.errors.some((e) => e.message.includes('timeout'));
201
+ const combinedStack = finalAttempt.errors.map((e) => e.stack || '').join('\n');
202
+ const errorMessage = finalAttempt.errors[0]?.message || '';
203
+ const errorCategory = this.categorizeError(errorMessage);
204
+ failures.push({
205
+ testId: test.testId,
206
+ testTitle: test.testTitle,
207
+ suiteTitle: test.suiteTitle || 'Unknown Suite',
208
+ errorMessage: errorMessage,
209
+ errorStack: combinedStack,
210
+ duration: finalAttempt.duration,
211
+ owningTeam: test.owningTeam || 'Unknown Team',
212
+ isTimeout,
213
+ errorCategory,
214
+ testFile: test.testFile,
215
+ location: test.location,
216
+ });
217
+ }
218
+ else if (finalOutcome === 'skipped') {
219
+ skippedCount++;
220
+ }
221
+ else {
222
+ const errorMessage = finalAttempt.status === 'interrupted' ? 'Test was interrupted' : `Unknown outcome: ${finalOutcome}`;
223
+ const errorCategory = this.categorizeError(errorMessage);
224
+ failures.push({
225
+ testId: test.testId,
226
+ testTitle: test.testTitle,
227
+ suiteTitle: test.suiteTitle || 'Unknown Suite',
228
+ errorMessage: errorMessage,
229
+ errorStack: '',
230
+ duration: finalAttempt.duration,
231
+ owningTeam: test.owningTeam || 'Unknown Team',
232
+ isTimeout: false,
233
+ errorCategory,
234
+ testFile: test.testFile,
235
+ location: test.location,
236
+ });
237
+ }
238
+ }
239
+ failedCount = failures.length;
240
+ return {
241
+ passedCount,
242
+ testCount,
243
+ skippedCount,
244
+ failedCount,
245
+ failures,
246
+ passedDurations,
247
+ };
248
+ }
249
+ }
250
+ exports.TestUtils = TestUtils;
251
+ /**
252
+ * Logger class responsible for formatting and outputting test results.
253
+ * Provides methods for logging test summaries, metrics, and failures with appropriate formatting.
254
+ */
255
+ class Logger {
256
+ /**
257
+ * Logs the overall test run summary with appropriate coloring.
258
+ * Includes total tests, passed tests, skipped tests, and total time.
259
+ *
260
+ * @param summary - The test summary to log
261
+ */
262
+ static logSummary(summary) {
263
+ console.log('\n');
264
+ console.log(`${colors_1.colors.fgBlue}===============================================${colors_1.colors.reset}`);
265
+ console.log(`${colors_1.colors.fgCyan}Test Summary:${colors_1.colors.reset}`);
266
+ console.log(`${colors_1.colors.fgBlue}===============================================${colors_1.colors.reset}`);
267
+ if (summary.failures.length > 0) {
268
+ console.log(`${colors_1.colors.fgRed}❌ ${summary.failures.length} of ${summary.testCount} tests failed | ` +
269
+ `${summary.passedCount} passed | ${summary.skippedCount} skipped | ⏱ Total: ${summary.totalTimeDisplay}${colors_1.colors.reset}`);
270
+ }
271
+ else {
272
+ console.log(`${colors_1.colors.fgGreen}✅ All ${summary.testCount} tests passed | ` +
273
+ `${summary.skippedCount} skipped | ⏱ Total: ${summary.totalTimeDisplay}${colors_1.colors.reset}`);
274
+ }
275
+ // Display build information if available
276
+ if (summary.buildInfo) {
277
+ this.logBuildInfo(summary.buildInfo);
278
+ }
279
+ this.logMetrics(summary);
280
+ // Add warning for skipped tests
281
+ if (summary.skippedCount > 0) {
282
+ console.log(`\n${colors_1.colors.fgYellow}⚠️ Warning: ${summary.skippedCount} test${summary.skippedCount === 1 ? ' was' : 's were'} skipped.${colors_1.colors.reset}`);
283
+ console.log(`${colors_1.colors.fgYellow} Please ensure to test the skipped scenarios manually before deployment.${colors_1.colors.reset}`);
284
+ }
285
+ console.log(`${colors_1.colors.fgBlue}===============================================${colors_1.colors.reset}`);
286
+ }
287
+ /**
288
+ * Logs build information if available
289
+ *
290
+ * @param buildInfo - The build information to log
291
+ */
292
+ static logBuildInfo(buildInfo) {
293
+ if (!buildInfo.isPipeline) {
294
+ console.log(`${colors_1.colors.fgMagenta}🖥️ Running locally${colors_1.colors.reset}`);
295
+ return;
296
+ }
297
+ console.log(`${colors_1.colors.fgMagenta}\nBuild Information:${colors_1.colors.reset}`);
298
+ console.log(`${colors_1.colors.fgMagenta}- CI System: ${buildInfo.executionSystem || 'Unknown CI'}${colors_1.colors.reset}`);
299
+ if (buildInfo.buildNumber) {
300
+ console.log(`${colors_1.colors.fgMagenta}- Build: ${buildInfo.buildNumber}${colors_1.colors.reset}`);
301
+ }
302
+ if (buildInfo.buildBranch) {
303
+ console.log(`${colors_1.colors.fgMagenta}- Branch: ${buildInfo.buildBranch}${colors_1.colors.reset}`);
304
+ }
305
+ if (buildInfo.commitId) {
306
+ console.log(`${colors_1.colors.fgMagenta}- Commit: ${buildInfo.commitId.substring(0, 8)}${colors_1.colors.reset}`);
307
+ }
308
+ if (buildInfo.commitId) {
309
+ console.log(`${colors_1.colors.fgMagenta}- Commit: ${buildInfo.commitId.substring(0, 8)}${colors_1.colors.reset}`);
310
+ }
311
+ if (buildInfo.buildLink) {
312
+ console.log(`${colors_1.colors.fgMagenta}- Build link: ${buildInfo.buildLink}${colors_1.colors.reset}`);
313
+ }
314
+ if (buildInfo.artifactsLink) {
315
+ console.log(`${colors_1.colors.fgMagenta}- Artifacts: ${buildInfo.artifactsLink}${colors_1.colors.reset}`);
316
+ }
317
+ // Only show test link for Azure Pipelines, as this is specific to that system
318
+ if (buildInfo.testLink && buildInfo.executionSystem === 'Azure Pipelines') {
319
+ console.log(`${colors_1.colors.fgMagenta}- Test Results: ${buildInfo.testLink}${colors_1.colors.reset}`);
320
+ }
321
+ if (buildInfo.commitLink && buildInfo.executionSystem === 'GitHub Actions') {
322
+ console.log(`${colors_1.colors.fgMagenta}- Commit: ${buildInfo.commitLink}${colors_1.colors.reset}`);
323
+ }
324
+ }
325
+ /**
326
+ * Logs detailed metrics about the test run.
327
+ * Includes average test time and information about slow tests.
328
+ *
329
+ * @param summary - The test summary containing metrics to log
330
+ */
331
+ static logMetrics(summary) {
332
+ console.log(`${colors_1.colors.fgMagenta}\nAdditional Metrics:${colors_1.colors.reset}`);
333
+ console.log(`${colors_1.colors.fgMagenta}- Average passed test time: ${summary.averageTime.toFixed(2)}s${colors_1.colors.reset}`);
334
+ if (summary.slowestTest > 0) {
335
+ console.log(`${colors_1.colors.fgMagenta}- Slowest test took: ${summary.slowestTest.toFixed(2)}s${colors_1.colors.reset}`);
336
+ console.log(`${colors_1.colors.fgMagenta}- Top 3 slowest tests:${colors_1.colors.reset}`);
337
+ summary.slowestTests.forEach((test, index) => {
338
+ console.log(` ${index + 1}. ${test.testTitle}: ` +
339
+ `${colors_1.colors.fgYellow}${test.duration.toFixed(2)}s${colors_1.colors.reset}`);
340
+ });
341
+ }
342
+ }
343
+ /**
344
+ * Logs detailed information about test failures.
345
+ * Includes test title, stack trace, and timeout information.
346
+ *
347
+ * @param failures - Array of test failures to log
348
+ */
349
+ static logFailures(failures) {
350
+ console.log('\n');
351
+ console.log(`${colors_1.colors.fgRed}===============================================${colors_1.colors.reset}`);
352
+ console.log(`${colors_1.colors.fgRed}Test Failures:${colors_1.colors.reset}`);
353
+ console.log(`${colors_1.colors.fgRed}===============================================${colors_1.colors.reset}`);
354
+ failures.forEach((failure, index) => {
355
+ console.log('\n');
356
+ console.group(`--- Failure #${index + 1} ---`);
357
+ console.log(` Test: ${failure.testTitle}`);
358
+ console.log(` ${colors_1.colors.fgGreen}Category: ${failure.errorCategory}${colors_1.colors.reset}`);
359
+ if (failure.errorStack) {
360
+ console.log(` Stack Trace:\n${failure.errorStack}`);
361
+ }
362
+ if (failure.isTimeout) {
363
+ console.log(`${colors_1.colors.fgYellow} (This failure involved a timeout.)${colors_1.colors.reset}`);
364
+ }
365
+ console.groupEnd();
366
+ });
367
+ console.log(`${colors_1.colors.fgRed}\n❌ Tests failed with exit code 1${colors_1.colors.reset}`);
368
+ console.log(`${colors_1.colors.fgRed}===============================================${colors_1.colors.reset}`);
369
+ }
370
+ }
371
+ exports.Logger = Logger;