@testomatio/reporter 2.6.0 → 2.6.2

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 (41) hide show
  1. package/README.md +10 -10
  2. package/lib/adapter/playwright.d.ts +2 -12
  3. package/lib/adapter/playwright.js +15 -72
  4. package/lib/adapter/utils/playwright.d.ts +25 -0
  5. package/lib/adapter/utils/playwright.js +123 -0
  6. package/lib/adapter/vitest.js +2 -1
  7. package/lib/bin/cli.js +1 -1
  8. package/lib/data-storage.d.ts +1 -1
  9. package/lib/data-storage.js +1 -0
  10. package/lib/pipe/debug.js +1 -1
  11. package/lib/pipe/html.d.ts +2 -3
  12. package/lib/pipe/html.js +745 -37
  13. package/lib/pipe/testomatio.js +13 -2
  14. package/lib/reporter-functions.d.ts +36 -11
  15. package/lib/reporter-functions.js +67 -25
  16. package/lib/reporter.d.ts +42 -86
  17. package/lib/services/artifacts.d.ts +1 -1
  18. package/lib/services/key-values.d.ts +1 -1
  19. package/lib/services/links.d.ts +1 -1
  20. package/lib/services/logger.d.ts +1 -1
  21. package/lib/template/testomatio-old.hbs +1421 -0
  22. package/lib/template/testomatio.hbs +3200 -1157
  23. package/lib/utils/log-formatter.d.ts +2 -1
  24. package/lib/utils/log-formatter.js +8 -4
  25. package/package.json +5 -2
  26. package/src/adapter/playwright.js +15 -79
  27. package/src/adapter/utils/playwright.js +121 -0
  28. package/src/adapter/vitest.js +2 -1
  29. package/src/bin/cli.js +1 -1
  30. package/src/data-storage.js +1 -0
  31. package/src/pipe/debug.js +1 -1
  32. package/src/pipe/html.js +844 -38
  33. package/src/pipe/testomatio.js +13 -2
  34. package/src/reporter-functions.js +68 -28
  35. package/src/template/testomatio-old.hbs +1421 -0
  36. package/src/template/testomatio.hbs +3200 -1157
  37. package/src/utils/log-formatter.js +9 -4
  38. package/types/types.d.ts +29 -5
  39. package/lib/services/labels.d.ts +0 -0
  40. package/lib/services/labels.js +0 -0
  41. package/src/services/labels.js +0 -1
@@ -25,4 +25,5 @@ export function formatError(error: Error & {
25
25
  actual?: any;
26
26
  expected?: any;
27
27
  }, message?: string): string;
28
- export function stripColors(str: string): string;
28
+ export const stripColors: typeof stripVTControlCharacters;
29
+ import { stripVTControlCharacters } from 'util';
@@ -114,10 +114,14 @@ function formatError(error, message) {
114
114
  * @returns {boolean}
115
115
  */
116
116
  function isNotInternalFrame(frame) {
117
- return (frame.getFileName() &&
118
- frame.getFileName().includes(path_1.sep) &&
119
- !frame.getFileName().includes('node_modules') &&
120
- !frame.getFileName().includes('internal'));
117
+ const fileName = frame.getFileName();
118
+ if (!fileName)
119
+ return false;
120
+ const isFileUrl = fileName.startsWith('file://');
121
+ const hasPathSeparator = fileName.includes(path_1.sep) || fileName.includes('/') || isFileUrl;
122
+ return (hasPathSeparator &&
123
+ !fileName.includes('node_modules') &&
124
+ !fileName.includes('internal'));
121
125
  }
122
126
 
123
127
  module.exports.formatLogs = formatLogs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "2.6.0",
3
+ "version": "2.6.2",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "engines": {
6
6
  "node": ">=18"
@@ -24,7 +24,7 @@
24
24
  "csv-writer": "^1.6.0",
25
25
  "debug": "4.3.4",
26
26
  "dotenv": "^16.0.1",
27
- "fast-xml-parser": "^4.4.1",
27
+ "fast-xml-parser": "^5.3.4",
28
28
  "file-url": "3.0.0",
29
29
  "filesize": "^10.1.6",
30
30
  "gaxios": ">=6.0 || >=7.0.0-rc.4 || <8",
@@ -44,6 +44,9 @@
44
44
  "strip-ansi": "7.1.0",
45
45
  "uuid": "^9.0.0"
46
46
  },
47
+ "overrides": {
48
+ "fast-xml-parser": "^5.3.4"
49
+ },
47
50
  "files": [
48
51
  "bin",
49
52
  "lib",
@@ -1,4 +1,3 @@
1
- import createDebugMessages from 'debug';
2
1
  import crypto from 'crypto';
3
2
  import os from 'os';
4
3
  import path from 'path';
@@ -11,9 +10,9 @@ import { services } from '../services/index.js';
11
10
  import { dataStorage } from '../data-storage.js';
12
11
  import { extensionMap } from '../utils/constants.js';
13
12
  import pc from 'picocolors';
13
+ import { fetchLinksFromLogs } from './utils/playwright.js';
14
14
 
15
15
  const reportTestPromises = [];
16
- const debug = createDebugMessages('@testomatio/reporter:adapter-playwright');
17
16
 
18
17
  class PlaywrightReporter {
19
18
  constructor(config = {}) {
@@ -43,6 +42,16 @@ class PlaywrightReporter {
43
42
 
44
43
  const { title } = test;
45
44
  const { error, duration } = result;
45
+ const pwAttachments = (result.attachments || []).filter(a => a.body || a.path);
46
+
47
+ const files = pwAttachments
48
+ .map(att => ({
49
+ path: this.#getArtifactPath(att),
50
+ title: att.name || title,
51
+ type: att.contentType,
52
+ }))
53
+ .filter(f => f.path);
54
+
46
55
  const suite_title = test.parent ? test.parent?.title : path.basename(test?.location?.file);
47
56
 
48
57
  const steps = [];
@@ -60,7 +69,7 @@ class PlaywrightReporter {
60
69
 
61
70
  let logs = '';
62
71
  // get links along with filtered logs (liks related logs removed)
63
- const { stdout: filteredStdout, links } = fetchLinksFromLogs(result.stdout);
72
+ const { stdout: filteredStdout, links, meta } = fetchLinksFromLogs(result.stdout);
64
73
  if (filteredStdout?.length || result.stderr?.length) {
65
74
  logs = `\n\n${pc.bold('Logs:')}\n${pc.red(result.stderr.join(''))}\n${filteredStdout.join('')}`;
66
75
  }
@@ -117,12 +126,14 @@ class PlaywrightReporter {
117
126
  logs,
118
127
  links,
119
128
  manuallyAttachedArtifacts,
129
+ files: files.length ? files : undefined,
120
130
  meta: {
121
131
  browser: project.browser,
122
132
  isMobile: project.isMobile,
123
133
  project: project.name,
124
134
  projectDependencies: project.dependencies?.length ? project.dependencies : null,
125
135
  ...testMeta,
136
+ ...meta,
126
137
  ...project.metadata, // metadata has any type (in playwright), but we will stringify it in client.js
127
138
  ...test.annotations?.reduce((acc, annotation) => {
128
139
  acc[annotation.type] = annotation.description;
@@ -135,7 +146,7 @@ class PlaywrightReporter {
135
146
  this.uploads.push({
136
147
  rid: `${rid}-${project.name}`,
137
148
  title: test.title,
138
- files: result.attachments.filter(a => a.body || a.path),
149
+ files: pwAttachments,
139
150
  file: test.location?.file,
140
151
  });
141
152
  // remove empty uploads
@@ -303,80 +314,5 @@ function getTestContextName(test) {
303
314
  return `${test._requireFile || ''}_${test.title}`;
304
315
  }
305
316
 
306
- /**
307
- * Fetches links from stdout. Returns links and filtered stdout (without data containing markers)
308
- *
309
- * @param {(string | Buffer)[]} stdout
310
- * @returns {{ links: { [key: 'test' | 'jira']: string }[], stdout: (string | Buffer)[] }}
311
- */
312
- function fetchLinksFromLogs(stdout) {
313
- const links = [];
314
-
315
- const markers = [
316
- { key: '[TESTOMATIO-LINK-TESTS]', type: 'test' },
317
- { key: '[TESTOMATIO-LINK-JIRA]', type: 'jira' },
318
- ];
319
-
320
- const filteredStdout = [];
321
-
322
- stdout.forEach(entry => {
323
- if (typeof entry !== 'string') {
324
- filteredStdout.push(entry);
325
- return;
326
- }
327
-
328
- // check if entry contains any of markers
329
- if (!markers.some(m => entry.includes(m.key))) {
330
- filteredStdout.push(entry);
331
- return;
332
- }
333
-
334
- const newEntryLines = [];
335
- entry.split('\n').forEach(line => {
336
- line = line.trim();
337
- let hasMarker = false;
338
- for (const marker of markers) {
339
- if (line.includes(marker.key)) {
340
- hasMarker = true;
341
- try {
342
- const rawJson = line.split(marker.key)[1]?.trim();
343
- if (!rawJson) continue;
344
-
345
- // smart JSON extraction: take until the last ']', otherwise take the whole string
346
- const lastBracketIndex = rawJson.lastIndexOf(']');
347
- const jsonStr = lastBracketIndex !== -1 ? rawJson.substring(0, lastBracketIndex + 1) : rawJson;
348
-
349
- // test ids or jira ids
350
- const ids = JSON.parse(jsonStr);
351
- links.push(
352
- ...ids
353
- // filter non-truthy ids
354
- .filter(id => !!id)
355
- .map(id => ({
356
- // marker type is either 'test' or 'jira'
357
- [marker.type]: id,
358
- })),
359
- );
360
- } catch (e) {
361
- debug('Error parsing links from string:', line, '\n', e);
362
- }
363
- }
364
- }
365
- if (!hasMarker && line) {
366
- newEntryLines.push(line);
367
- }
368
- });
369
-
370
- if (newEntryLines.length) {
371
- filteredStdout.push(newEntryLines.join('\n'));
372
- }
373
- });
374
-
375
- return {
376
- stdout: filteredStdout,
377
- links,
378
- };
379
- }
380
-
381
317
  export default PlaywrightReporter;
382
318
  export { extractTags, fetchLinksFromLogs };
@@ -0,0 +1,121 @@
1
+ import createDebugMessages from 'debug';
2
+ const debug = createDebugMessages('@testomatio/reporter:adapter-playwright-utils');
3
+
4
+ export const playwrightLogsMarkers = {
5
+ label: '[TESTOMATIO-LABEL]',
6
+ meta: '[TESTOMATIO-META]',
7
+ linkTest: '[TESTOMATIO-LINK-TEST]',
8
+ linkJira: '[TESTOMATIO-LINK-JIRA]',
9
+ };
10
+
11
+ /**
12
+ * Fetches links from stdout. Returns links and filtered stdout (without data containing markers)
13
+ *
14
+ * @param {(string | Buffer)[]} stdout
15
+ * @returns {{
16
+ * links: { [key: 'test' | 'jira' | 'label']: string }[],
17
+ * meta: { [key: string]: any },
18
+ * stdout: (string | Buffer)[]
19
+ * }}
20
+ */
21
+ export function fetchLinksFromLogs(stdout) {
22
+ const links = [];
23
+ const meta = {};
24
+
25
+ const markers = [
26
+ { key: playwrightLogsMarkers.linkTest, type: 'test' },
27
+ { key: playwrightLogsMarkers.linkJira, type: 'jira' },
28
+ { key: playwrightLogsMarkers.label, type: 'label' },
29
+ { key: playwrightLogsMarkers.meta, type: 'meta' },
30
+ ];
31
+
32
+ const filteredStdout = [];
33
+
34
+ stdout.forEach(entry => {
35
+ if (typeof entry !== 'string') {
36
+ filteredStdout.push(entry);
37
+ return;
38
+ }
39
+
40
+ // check if entry contains any of markers
41
+ if (!markers.some(m => entry.includes(m.key))) {
42
+ filteredStdout.push(entry);
43
+ return;
44
+ }
45
+
46
+ const newEntryLines = [];
47
+ entry.split('\n').forEach(line => {
48
+ line = line.trim();
49
+ let hasMarker = false;
50
+ for (const marker of markers) {
51
+ // links (test/jira/label) stored as array of objects
52
+ if (line.includes(marker.key)) {
53
+ hasMarker = true;
54
+ try {
55
+ const rawData = line.split(marker.key)[1]?.trim();
56
+ if (!rawData) continue;
57
+
58
+ let data;
59
+ try {
60
+ data = JSON.parse(rawData);
61
+ } catch (e) {
62
+ // Try to extract JSON from the beginning of the string (to handle trailing text)
63
+ const jsonMatch = rawData.match(/^\s*(\[.*?\]|\{.*?\})/);
64
+ if (jsonMatch) {
65
+ try {
66
+ data = JSON.parse(jsonMatch[1]);
67
+ } catch (jsonError) {
68
+ // If JSON extraction fails, skip this entry
69
+ debug('Error parsing links from string:', line, '\n', jsonError);
70
+ continue;
71
+ }
72
+ } else {
73
+ // No JSON found, skip this entry
74
+ debug('No valid JSON found in:', line);
75
+ continue;
76
+ }
77
+ }
78
+
79
+ if (marker.type === 'meta') {
80
+ // meta stored as an object, thus make it similar to links
81
+ Object.assign(meta, data);
82
+ } else {
83
+ const ids = Array.isArray(data) ? data : [data];
84
+ links.push(
85
+ ...ids
86
+ // filter non-truthy ids
87
+ .filter(id => !!id)
88
+ .map(id => {
89
+ // If id is already an object with the marker type key, return it as is
90
+ if (typeof id === 'object' && id !== null && marker.type in id) {
91
+ return id;
92
+ }
93
+ // Otherwise, wrap it with the marker type key
94
+ return {
95
+ // marker type is either 'test' or 'jira' or 'label'
96
+ [marker.type]: id,
97
+ };
98
+ }),
99
+ );
100
+ }
101
+ } catch (e) {
102
+ debug('Error parsing links from string:', line, '\n', e);
103
+ }
104
+ }
105
+ }
106
+ if (!hasMarker && line) {
107
+ newEntryLines.push(line);
108
+ }
109
+ });
110
+
111
+ if (newEntryLines.length) {
112
+ filteredStdout.push(newEntryLines.join('\n'));
113
+ }
114
+ });
115
+
116
+ return {
117
+ stdout: filteredStdout,
118
+ links,
119
+ meta,
120
+ };
121
+ }
@@ -101,7 +101,7 @@ class VitestReporter {
101
101
  *
102
102
  * @param {VitestTest} test
103
103
  *
104
- * @returns {TestData & {status: string}}
104
+ * @returns {TestData & {status: 'passed' | 'failed' | 'skipped'}}
105
105
  */
106
106
  #getDataFromTest(test) {
107
107
  return {
@@ -109,6 +109,7 @@ class VitestReporter {
109
109
  file: test.file.name,
110
110
  logs: test.logs ? transformLogsToString(test.logs) : '',
111
111
  meta: test.meta,
112
+ // @ts-ignore - STATUS values are string literals but type system sees them as string
112
113
  status: getTestStatus(test),
113
114
  suite_title: test.suite.name || test.file?.name,
114
115
  test_id: getTestomatIdFromTestTitle(test.name),
package/src/bin/cli.js CHANGED
@@ -15,7 +15,7 @@ import { filesize as prettyBytes } from 'filesize';
15
15
  import dotenv from 'dotenv';
16
16
  import Replay from '../replay.js';
17
17
 
18
- const debug = createDebugMessages('@testomatio/reporter:xml-cli');
18
+ const debug = createDebugMessages('@testomatio/reporter:cli');
19
19
  const version = getPackageVersion();
20
20
  console.log(pc.cyan(pc.bold(` 🤩 Testomat.io Reporter v${version}`)));
21
21
  const program = new Command();
@@ -139,6 +139,7 @@ class DataStorage {
139
139
  const testDataAsText = fs.readFileSync(filepath, 'utf-8');
140
140
  if (testDataAsText) debug('<=', dataType, 'file', context, testDataAsText);
141
141
  const testDataArr = testDataAsText?.split(os.EOL) || [];
142
+ debug('<=', dataType, 'file', context, testDataArr);
142
143
  return testDataArr;
143
144
  }
144
145
  // debug(`No ${this.dataType} data for ${context} in <file> storage`);
package/src/pipe/debug.js CHANGED
@@ -15,7 +15,7 @@ export class DebugPipe {
15
15
  this.isEnabled = !!process.env.TESTOMATIO_DEBUG || !!process.env.DEBUG;
16
16
  if (this.isEnabled) {
17
17
  this.batch = {
18
- isEnabled: this.params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
18
+ isEnabled: this.params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD,
19
19
  intervalFunction: null,
20
20
  intervalTime: 5000,
21
21
  tests: [],