testlens-playwright-reporter 0.2.7 → 0.2.8

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/index.d.ts CHANGED
@@ -70,6 +70,46 @@ export interface GitInfo {
70
70
  remoteUrl: string;
71
71
  }
72
72
 
73
+ /** Rich error details from Playwright */
74
+ export interface TestError {
75
+ message: string;
76
+ stack?: string;
77
+ location?: {
78
+ file: string;
79
+ line: number;
80
+ column: number;
81
+ };
82
+ snippet?: string;
83
+ expected?: string;
84
+ actual?: string;
85
+ diff?: string;
86
+ matcherName?: string;
87
+ timeout?: number;
88
+ }
89
+
90
+ export interface TestData {
91
+ id: string;
92
+ name: string;
93
+ status: string;
94
+ originalStatus?: string;
95
+ duration: number;
96
+ startTime: string;
97
+ endTime: string;
98
+ errorMessages: string[];
99
+ errors?: TestError[];
100
+ retryAttempts: number;
101
+ currentRetry: number;
102
+ annotations: Array<{ type: string; description?: string }>;
103
+ projectName: string;
104
+ workerIndex?: number;
105
+ parallelIndex?: number;
106
+ location?: {
107
+ file: string;
108
+ line: number;
109
+ column: number;
110
+ };
111
+ }
112
+
73
113
  export default class TestLensReporter implements Reporter {
74
114
  constructor(options: TestLensReporterOptions);
75
115
 
package/index.js CHANGED
@@ -213,6 +213,7 @@ class TestLensReporter {
213
213
  startTime: new Date().toISOString(),
214
214
  endTime: '',
215
215
  errorMessages: [],
216
+ errors: [],
216
217
  retryAttempts: test.retries,
217
218
  currentRetry: result.retry,
218
219
  annotations: test.annotations.map(ann => ({
@@ -221,7 +222,12 @@ class TestLensReporter {
221
222
  })),
222
223
  projectName: test.parent.project()?.name || 'default',
223
224
  workerIndex: result.workerIndex,
224
- parallelIndex: result.parallelIndex
225
+ parallelIndex: result.parallelIndex,
226
+ location: {
227
+ file: path.relative(process.cwd(), test.location.file),
228
+ line: test.location.line,
229
+ column: test.location.column
230
+ }
225
231
  };
226
232
 
227
233
  this.testMap.set(testData.id, testData);
@@ -254,6 +260,96 @@ class TestLensReporter {
254
260
  testData.endTime = new Date().toISOString();
255
261
  testData.errorMessages = result.errors.map(error => error.message || error.toString());
256
262
  testData.currentRetry = result.retry;
263
+
264
+ // Capture test location
265
+ testData.location = {
266
+ file: path.relative(process.cwd(), test.location.file),
267
+ line: test.location.line,
268
+ column: test.location.column
269
+ };
270
+
271
+ // Capture rich error details like Playwright's HTML report
272
+ testData.errors = result.errors.map(error => {
273
+ const testError = {
274
+ message: error.message || error.toString()
275
+ };
276
+
277
+ // Capture stack trace
278
+ if (error.stack) {
279
+ testError.stack = error.stack;
280
+ }
281
+
282
+ // Capture error location
283
+ if (error.location) {
284
+ testError.location = {
285
+ file: path.relative(process.cwd(), error.location.file),
286
+ line: error.location.line,
287
+ column: error.location.column
288
+ };
289
+ }
290
+
291
+ // Capture code snippet around error - from Playwright error object
292
+ if (error.snippet) {
293
+ testError.snippet = error.snippet;
294
+ }
295
+
296
+ // Capture expected/actual values for assertion failures
297
+ // Playwright stores these as specially formatted strings in the message
298
+ const message = error.message || '';
299
+
300
+ // Try to parse expected pattern from toHaveURL and similar assertions
301
+ const expectedPatternMatch = message.match(/Expected pattern:\s*(.+?)(?:\n|$)/);
302
+ if (expectedPatternMatch) {
303
+ testError.expected = expectedPatternMatch[1].trim();
304
+ }
305
+
306
+ // Also try "Expected string:" format
307
+ if (!testError.expected) {
308
+ const expectedStringMatch = message.match(/Expected string:\s*["']?(.+?)["']?(?:\n|$)/);
309
+ if (expectedStringMatch) {
310
+ testError.expected = expectedStringMatch[1].trim();
311
+ }
312
+ }
313
+
314
+ // Try to parse received/actual value
315
+ const receivedMatch = message.match(/Received (?:string|value):\s*["']?(.+?)["']?(?:\n|$)/);
316
+ if (receivedMatch) {
317
+ testError.actual = receivedMatch[1].trim();
318
+ }
319
+
320
+ // Parse call log entries for debugging info (timeouts, retries, etc.)
321
+ const callLogMatch = message.match(/Call log:([\s\S]*?)(?=\n\n|\n\s*\d+\s*\||$)/);
322
+ if (callLogMatch) {
323
+ // Store call log separately for display
324
+ const callLog = callLogMatch[1].trim();
325
+ if (callLog) {
326
+ testError.diff = callLog; // Reuse diff field for call log
327
+ }
328
+ }
329
+
330
+ // Parse timeout information - multiple formats
331
+ const timeoutMatch = message.match(/(?:with timeout|Timeout:?)\s*(\d+)ms/i);
332
+ if (timeoutMatch) {
333
+ testError.timeout = parseInt(timeoutMatch[1], 10);
334
+ }
335
+
336
+ // Parse matcher name (e.g., toHaveURL, toBeVisible)
337
+ const matcherMatch = message.match(/expect\([^)]+\)\.(\w+)/);
338
+ if (matcherMatch) {
339
+ testError.matcherName = matcherMatch[1];
340
+ }
341
+
342
+ // Extract code snippet from message if not already captured
343
+ // Look for lines like " 9 | await page.click..." or "> 11 | await expect..."
344
+ if (!testError.snippet) {
345
+ const codeSnippetMatch = message.match(/((?:\s*>?\s*\d+\s*\|.*\n?)+)/);
346
+ if (codeSnippetMatch) {
347
+ testError.snippet = codeSnippetMatch[1].trim();
348
+ }
349
+ }
350
+
351
+ return testError;
352
+ });
257
353
 
258
354
  // Only send testEnd event after final retry attempt
259
355
  // If test passed or this is the last retry, send the event
package/index.ts CHANGED
@@ -123,6 +123,22 @@ export interface RunMetadata {
123
123
  status?: string;
124
124
  }
125
125
 
126
+ export interface TestError {
127
+ message: string;
128
+ stack?: string;
129
+ location?: {
130
+ file: string;
131
+ line: number;
132
+ column: number;
133
+ };
134
+ snippet?: string;
135
+ expected?: string;
136
+ actual?: string;
137
+ diff?: string;
138
+ matcherName?: string;
139
+ timeout?: number;
140
+ }
141
+
126
142
  export interface TestData {
127
143
  id: string;
128
144
  name: string;
@@ -132,12 +148,18 @@ export interface TestData {
132
148
  startTime: string;
133
149
  endTime: string;
134
150
  errorMessages: string[];
151
+ errors?: TestError[]; // Rich error details
135
152
  retryAttempts: number;
136
153
  currentRetry: number;
137
154
  annotations: Array<{ type: string; description?: string }>;
138
155
  projectName: string;
139
156
  workerIndex?: number;
140
157
  parallelIndex?: number;
158
+ location?: {
159
+ file: string;
160
+ line: number;
161
+ column: number;
162
+ };
141
163
  }
142
164
 
143
165
  export interface SpecData {
@@ -350,6 +372,7 @@ export class TestLensReporter implements Reporter {
350
372
  startTime: new Date().toISOString(),
351
373
  endTime: '',
352
374
  errorMessages: [],
375
+ errors: [],
353
376
  retryAttempts: test.retries,
354
377
  currentRetry: result.retry,
355
378
  annotations: test.annotations.map((ann: any) => ({
@@ -358,7 +381,12 @@ export class TestLensReporter implements Reporter {
358
381
  })),
359
382
  projectName: test.parent.project()?.name || 'default',
360
383
  workerIndex: result.workerIndex,
361
- parallelIndex: result.parallelIndex
384
+ parallelIndex: result.parallelIndex,
385
+ location: {
386
+ file: path.relative(process.cwd(), test.location.file),
387
+ line: test.location.line,
388
+ column: test.location.column
389
+ }
362
390
  };
363
391
 
364
392
  this.testMap.set(testData.id, testData);
@@ -391,6 +419,96 @@ export class TestLensReporter implements Reporter {
391
419
  testData.endTime = new Date().toISOString();
392
420
  testData.errorMessages = result.errors.map((error: any) => error.message || error.toString());
393
421
  testData.currentRetry = result.retry;
422
+
423
+ // Capture test location
424
+ testData.location = {
425
+ file: path.relative(process.cwd(), test.location.file),
426
+ line: test.location.line,
427
+ column: test.location.column
428
+ };
429
+
430
+ // Capture rich error details like Playwright's HTML report
431
+ testData.errors = result.errors.map((error: any) => {
432
+ const testError: TestError = {
433
+ message: error.message || error.toString()
434
+ };
435
+
436
+ // Capture stack trace
437
+ if (error.stack) {
438
+ testError.stack = error.stack;
439
+ }
440
+
441
+ // Capture error location
442
+ if (error.location) {
443
+ testError.location = {
444
+ file: path.relative(process.cwd(), error.location.file),
445
+ line: error.location.line,
446
+ column: error.location.column
447
+ };
448
+ }
449
+
450
+ // Capture code snippet around error - from Playwright error object
451
+ if (error.snippet) {
452
+ testError.snippet = error.snippet;
453
+ }
454
+
455
+ // Capture expected/actual values for assertion failures
456
+ // Playwright stores these as specially formatted strings in the message
457
+ const message = error.message || '';
458
+
459
+ // Try to parse expected pattern from toHaveURL and similar assertions
460
+ const expectedPatternMatch = message.match(/Expected pattern:\s*(.+?)(?:\n|$)/);
461
+ if (expectedPatternMatch) {
462
+ testError.expected = expectedPatternMatch[1].trim();
463
+ }
464
+
465
+ // Also try "Expected string:" format
466
+ if (!testError.expected) {
467
+ const expectedStringMatch = message.match(/Expected string:\s*["']?(.+?)["']?(?:\n|$)/);
468
+ if (expectedStringMatch) {
469
+ testError.expected = expectedStringMatch[1].trim();
470
+ }
471
+ }
472
+
473
+ // Try to parse received/actual value
474
+ const receivedMatch = message.match(/Received (?:string|value):\s*["']?(.+?)["']?(?:\n|$)/);
475
+ if (receivedMatch) {
476
+ testError.actual = receivedMatch[1].trim();
477
+ }
478
+
479
+ // Parse call log entries for debugging info (timeouts, retries, etc.)
480
+ const callLogMatch = message.match(/Call log:([\s\S]*?)(?=\n\n|\n\s*\d+\s*\||$)/);
481
+ if (callLogMatch) {
482
+ // Store call log separately for display
483
+ const callLog = callLogMatch[1].trim();
484
+ if (callLog) {
485
+ testError.diff = callLog; // Reuse diff field for call log
486
+ }
487
+ }
488
+
489
+ // Parse timeout information - multiple formats
490
+ const timeoutMatch = message.match(/(?:with timeout|Timeout:?)\s*(\d+)ms/i);
491
+ if (timeoutMatch) {
492
+ testError.timeout = parseInt(timeoutMatch[1], 10);
493
+ }
494
+
495
+ // Parse matcher name (e.g., toHaveURL, toBeVisible)
496
+ const matcherMatch = message.match(/expect\([^)]+\)\.(\w+)/);
497
+ if (matcherMatch) {
498
+ testError.matcherName = matcherMatch[1];
499
+ }
500
+
501
+ // Extract code snippet from message if not already captured
502
+ // Look for lines like " 9 | await page.click..." or "> 11 | await expect..."
503
+ if (!testError.snippet) {
504
+ const codeSnippetMatch = message.match(/((?:\s*>?\s*\d+\s*\|.*\n?)+)/);
505
+ if (codeSnippetMatch) {
506
+ testError.snippet = codeSnippetMatch[1].trim();
507
+ }
508
+ }
509
+
510
+ return testError;
511
+ });
394
512
 
395
513
  // Only send testEnd event after final retry attempt
396
514
  // If test passed or this is the last retry, send the event
package/lib/index.js CHANGED
@@ -229,6 +229,7 @@ class TestLensReporter {
229
229
  startTime: new Date().toISOString(),
230
230
  endTime: '',
231
231
  errorMessages: [],
232
+ errors: [],
232
233
  retryAttempts: test.retries,
233
234
  currentRetry: result.retry,
234
235
  annotations: test.annotations.map((ann) => ({
@@ -237,7 +238,12 @@ class TestLensReporter {
237
238
  })),
238
239
  projectName: test.parent.project()?.name || 'default',
239
240
  workerIndex: result.workerIndex,
240
- parallelIndex: result.parallelIndex
241
+ parallelIndex: result.parallelIndex,
242
+ location: {
243
+ file: path.relative(process.cwd(), test.location.file),
244
+ line: test.location.line,
245
+ column: test.location.column
246
+ }
241
247
  };
242
248
  this.testMap.set(testData.id, testData);
243
249
  // Send test start event to API
@@ -267,6 +273,82 @@ class TestLensReporter {
267
273
  testData.endTime = new Date().toISOString();
268
274
  testData.errorMessages = result.errors.map((error) => error.message || error.toString());
269
275
  testData.currentRetry = result.retry;
276
+ // Capture test location
277
+ testData.location = {
278
+ file: path.relative(process.cwd(), test.location.file),
279
+ line: test.location.line,
280
+ column: test.location.column
281
+ };
282
+ // Capture rich error details like Playwright's HTML report
283
+ testData.errors = result.errors.map((error) => {
284
+ const testError = {
285
+ message: error.message || error.toString()
286
+ };
287
+ // Capture stack trace
288
+ if (error.stack) {
289
+ testError.stack = error.stack;
290
+ }
291
+ // Capture error location
292
+ if (error.location) {
293
+ testError.location = {
294
+ file: path.relative(process.cwd(), error.location.file),
295
+ line: error.location.line,
296
+ column: error.location.column
297
+ };
298
+ }
299
+ // Capture code snippet around error - from Playwright error object
300
+ if (error.snippet) {
301
+ testError.snippet = error.snippet;
302
+ }
303
+ // Capture expected/actual values for assertion failures
304
+ // Playwright stores these as specially formatted strings in the message
305
+ const message = error.message || '';
306
+ // Try to parse expected pattern from toHaveURL and similar assertions
307
+ const expectedPatternMatch = message.match(/Expected pattern:\s*(.+?)(?:\n|$)/);
308
+ if (expectedPatternMatch) {
309
+ testError.expected = expectedPatternMatch[1].trim();
310
+ }
311
+ // Also try "Expected string:" format
312
+ if (!testError.expected) {
313
+ const expectedStringMatch = message.match(/Expected string:\s*["']?(.+?)["']?(?:\n|$)/);
314
+ if (expectedStringMatch) {
315
+ testError.expected = expectedStringMatch[1].trim();
316
+ }
317
+ }
318
+ // Try to parse received/actual value
319
+ const receivedMatch = message.match(/Received (?:string|value):\s*["']?(.+?)["']?(?:\n|$)/);
320
+ if (receivedMatch) {
321
+ testError.actual = receivedMatch[1].trim();
322
+ }
323
+ // Parse call log entries for debugging info (timeouts, retries, etc.)
324
+ const callLogMatch = message.match(/Call log:([\s\S]*?)(?=\n\n|\n\s*\d+\s*\||$)/);
325
+ if (callLogMatch) {
326
+ // Store call log separately for display
327
+ const callLog = callLogMatch[1].trim();
328
+ if (callLog) {
329
+ testError.diff = callLog; // Reuse diff field for call log
330
+ }
331
+ }
332
+ // Parse timeout information - multiple formats
333
+ const timeoutMatch = message.match(/(?:with timeout|Timeout:?)\s*(\d+)ms/i);
334
+ if (timeoutMatch) {
335
+ testError.timeout = parseInt(timeoutMatch[1], 10);
336
+ }
337
+ // Parse matcher name (e.g., toHaveURL, toBeVisible)
338
+ const matcherMatch = message.match(/expect\([^)]+\)\.(\w+)/);
339
+ if (matcherMatch) {
340
+ testError.matcherName = matcherMatch[1];
341
+ }
342
+ // Extract code snippet from message if not already captured
343
+ // Look for lines like " 9 | await page.click..." or "> 11 | await expect..."
344
+ if (!testError.snippet) {
345
+ const codeSnippetMatch = message.match(/((?:\s*>?\s*\d+\s*\|.*\n?)+)/);
346
+ if (codeSnippetMatch) {
347
+ testError.snippet = codeSnippetMatch[1].trim();
348
+ }
349
+ }
350
+ return testError;
351
+ });
270
352
  // Only send testEnd event after final retry attempt
271
353
  // If test passed or this is the last retry, send the event
272
354
  const isFinalAttempt = result.status === 'passed' || result.status === 'skipped' || result.retry >= test.retries;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testlens-playwright-reporter",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Universal Playwright reporter for TestLens - works with both TypeScript and JavaScript projects",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",