@testomatio/reporter 2.6.0 β†’ 2.6.1

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/README.md CHANGED
@@ -13,7 +13,7 @@ Testomat.io Reporter (this npm package) supports:
13
13
  - πŸ”Ž [Stack traces](./docs/stacktrace.md) and error messages
14
14
  - πŸ™ [GitHub](./docs/pipes/github.md), [GitLab](./docs/pipes/gitlab.md) & [Bitbucket](./docs/pipes/bitbucket.md) integration
15
15
  - πŸš… Realtime reports
16
- - πŸ—ƒοΈ Other test frameworks supported via [JUnit XML](./docs/junit.md) with [XML import configuration](./docs/xml-imports.md)
16
+ - πŸ—ƒοΈ Other test frameworks supported via [JUnit XML](./docs/junit.md)
17
17
  - πŸšΆβ€β™€οΈ Steps _(work in progress)_
18
18
  - πŸ“„ [Logger](./docs/logger.md) _(work in progress, supports Jest for now)_
19
19
  - ☁️ Custom properties and metadata _(work in progress)_
@@ -63,11 +63,11 @@ yarn add @testomatio/reporter --dev
63
63
  ### 1️⃣ Attach Reporter to the Test Runner
64
64
 
65
65
  | | | |
66
- | ----------------------------------------------- | --------------------------------------------- | --------------------------------------------------------- |
67
- | [Playwright](./docs/frameworks.md#playwright) | [CodeceptJS](./docs/frameworks.md#CodeceptJS) | [Cypress](./docs/frameworks.md#Cypress) |
68
- | [Jest](./docs/frameworks.md#Jest) | [Mocha](./docs/frameworks.md#Mocha) | [WebDriverIO](./docs/frameworks.md#WebDriverIO) |
69
- | [TestCafe](./docs/frameworks.md#TestCafe) | [Detox](./docs/frameworks.md#Detox) | [Codeception](https://github.com/testomatio/php-reporter) |
70
- | [Newman (Postman)](./docs/frameworks.md#Newman) | [JUnit](./docs/junit.md#junit) | [NUnit](./docs/junit.md#nunit) |
66
+ |-------------------------------------------------|-----------------------------------------------|-----------------------------------------------------------|
67
+ | [Playwright](./docs/frameworks.md#playwright) | [CodeceptJS](./docs/frameworks.md#codeceptjs) | [Cypress](./docs/frameworks.md#cypress) |
68
+ | [Jest](./docs/frameworks.md#jest) | [Mocha](./docs/frameworks.md#mocha) | [WebDriverIO](./docs/frameworks.md#webdriverIO) |
69
+ | [TestCafe](./docs/frameworks.md#testcafe) | [Detox](./docs/frameworks.md#detox) | [Codeception](https://github.com/testomatio/php-reporter) |
70
+ | [Newman (Postman)](./docs/frameworks.md#newman) | [JUnit](./docs/junit.md#junit) | [NUnit](./docs/junit.md#nunit) |
71
71
  | [PyTest](./docs/junit.md#pytest) | [PHPUnit](./docs/junit.md#phpunit) | [Protractor](./docs/frameworks.md#protractor) |
72
72
 
73
73
  or **any [other via JUnit](./docs/junit.md)** report....
@@ -103,7 +103,7 @@ With our reporter, you can:
103
103
  * Visualize data on successful and failed tests, including statistics and error details.
104
104
  * Quickly share reports with your team members or stakeholders.
105
105
 
106
- ![HTML report](./docs/images/html_reporter_example.gif)
106
+ ![HTML report](./docs/images/html-reporter-example.gif)
107
107
 
108
108
  Learn more about generating HTML reports [here](./docs/pipes/html.md)
109
109
 
@@ -18,16 +18,6 @@ declare class PlaywrightReporter {
18
18
  * @returns {string[]} - array of normalized tags with @ prefix
19
19
  */
20
20
  export function extractTags(test: any): string[];
21
- /**
22
- * Fetches links from stdout. Returns links and filtered stdout (without data containing markers)
23
- *
24
- * @param {(string | Buffer)[]} stdout
25
- * @returns {{ links: { [key: 'test' | 'jira']: string }[], stdout: (string | Buffer)[] }}
26
- */
27
- export function fetchLinksFromLogs(stdout: (string | Buffer)[]): {
28
- links: {
29
- [key: "test" | "jira"]: string;
30
- }[];
31
- stdout: (string | Buffer)[];
32
- };
21
+ import { fetchLinksFromLogs } from './utils/playwright.js';
33
22
  import TestomatioClient from '../client.js';
23
+ export { fetchLinksFromLogs };
@@ -3,9 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.fetchLinksFromLogs = void 0;
6
7
  exports.extractTags = extractTags;
7
- exports.fetchLinksFromLogs = fetchLinksFromLogs;
8
- const debug_1 = __importDefault(require("debug"));
9
8
  const crypto_1 = __importDefault(require("crypto"));
10
9
  const os_1 = __importDefault(require("os"));
11
10
  const path_1 = __importDefault(require("path"));
@@ -18,8 +17,9 @@ const index_js_1 = require("../services/index.js");
18
17
  const data_storage_js_1 = require("../data-storage.js");
19
18
  const constants_js_2 = require("../utils/constants.js");
20
19
  const picocolors_1 = __importDefault(require("picocolors"));
20
+ const playwright_js_1 = require("./utils/playwright.js");
21
+ Object.defineProperty(exports, "fetchLinksFromLogs", { enumerable: true, get: function () { return playwright_js_1.fetchLinksFromLogs; } });
21
22
  const reportTestPromises = [];
22
- const debug = (0, debug_1.default)('@testomatio/reporter:adapter-playwright');
23
23
  class PlaywrightReporter {
24
24
  constructor(config = {}) {
25
25
  this.client = new client_js_1.default({ apiKey: config?.apiKey });
@@ -44,6 +44,14 @@ class PlaywrightReporter {
44
44
  return;
45
45
  const { title } = test;
46
46
  const { error, duration } = result;
47
+ const pwAttachments = (result.attachments || []).filter(a => a.body || a.path);
48
+ const files = pwAttachments
49
+ .map(att => ({
50
+ path: this.#getArtifactPath(att),
51
+ title: att.name || title,
52
+ type: att.contentType,
53
+ }))
54
+ .filter(f => f.path);
47
55
  const suite_title = test.parent ? test.parent?.title : path_1.default.basename(test?.location?.file);
48
56
  const steps = [];
49
57
  for (const step of result.steps) {
@@ -57,7 +65,7 @@ class PlaywrightReporter {
57
65
  const fullTestTitle = getTestContextName(test);
58
66
  let logs = '';
59
67
  // get links along with filtered logs (liks related logs removed)
60
- const { stdout: filteredStdout, links } = fetchLinksFromLogs(result.stdout);
68
+ const { stdout: filteredStdout, links, meta } = (0, playwright_js_1.fetchLinksFromLogs)(result.stdout);
61
69
  if (filteredStdout?.length || result.stderr?.length) {
62
70
  logs = `\n\n${picocolors_1.default.bold('Logs:')}\n${picocolors_1.default.red(result.stderr.join(''))}\n${filteredStdout.join('')}`;
63
71
  }
@@ -112,12 +120,14 @@ class PlaywrightReporter {
112
120
  logs,
113
121
  links,
114
122
  manuallyAttachedArtifacts,
123
+ files: files.length ? files : undefined,
115
124
  meta: {
116
125
  browser: project.browser,
117
126
  isMobile: project.isMobile,
118
127
  project: project.name,
119
128
  projectDependencies: project.dependencies?.length ? project.dependencies : null,
120
129
  ...testMeta,
130
+ ...meta,
121
131
  ...project.metadata, // metadata has any type (in playwright), but we will stringify it in client.js
122
132
  ...test.annotations?.reduce((acc, annotation) => {
123
133
  acc[annotation.type] = annotation.description;
@@ -129,7 +139,7 @@ class PlaywrightReporter {
129
139
  this.uploads.push({
130
140
  rid: `${rid}-${project.name}`,
131
141
  title: test.title,
132
- files: result.attachments.filter(a => a.body || a.path),
142
+ files: pwAttachments,
133
143
  file: test.location?.file,
134
144
  });
135
145
  // remove empty uploads
@@ -273,73 +283,6 @@ function extractTags(test) {
273
283
  function getTestContextName(test) {
274
284
  return `${test._requireFile || ''}_${test.title}`;
275
285
  }
276
- /**
277
- * Fetches links from stdout. Returns links and filtered stdout (without data containing markers)
278
- *
279
- * @param {(string | Buffer)[]} stdout
280
- * @returns {{ links: { [key: 'test' | 'jira']: string }[], stdout: (string | Buffer)[] }}
281
- */
282
- function fetchLinksFromLogs(stdout) {
283
- const links = [];
284
- const markers = [
285
- { key: '[TESTOMATIO-LINK-TESTS]', type: 'test' },
286
- { key: '[TESTOMATIO-LINK-JIRA]', type: 'jira' },
287
- ];
288
- const filteredStdout = [];
289
- stdout.forEach(entry => {
290
- if (typeof entry !== 'string') {
291
- filteredStdout.push(entry);
292
- return;
293
- }
294
- // check if entry contains any of markers
295
- if (!markers.some(m => entry.includes(m.key))) {
296
- filteredStdout.push(entry);
297
- return;
298
- }
299
- const newEntryLines = [];
300
- entry.split('\n').forEach(line => {
301
- line = line.trim();
302
- let hasMarker = false;
303
- for (const marker of markers) {
304
- if (line.includes(marker.key)) {
305
- hasMarker = true;
306
- try {
307
- const rawJson = line.split(marker.key)[1]?.trim();
308
- if (!rawJson)
309
- continue;
310
- // smart JSON extraction: take until the last ']', otherwise take the whole string
311
- const lastBracketIndex = rawJson.lastIndexOf(']');
312
- const jsonStr = lastBracketIndex !== -1 ? rawJson.substring(0, lastBracketIndex + 1) : rawJson;
313
- // test ids or jira ids
314
- const ids = JSON.parse(jsonStr);
315
- links.push(...ids
316
- // filter non-truthy ids
317
- .filter(id => !!id)
318
- .map(id => ({
319
- // marker type is either 'test' or 'jira'
320
- [marker.type]: id,
321
- })));
322
- }
323
- catch (e) {
324
- debug('Error parsing links from string:', line, '\n', e);
325
- }
326
- }
327
- }
328
- if (!hasMarker && line) {
329
- newEntryLines.push(line);
330
- }
331
- });
332
- if (newEntryLines.length) {
333
- filteredStdout.push(newEntryLines.join('\n'));
334
- }
335
- });
336
- return {
337
- stdout: filteredStdout,
338
- links,
339
- };
340
- }
341
286
  module.exports = PlaywrightReporter;
342
287
 
343
288
  module.exports.extractTags = extractTags;
344
-
345
- module.exports.fetchLinksFromLogs = fetchLinksFromLogs;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Fetches links from stdout. Returns links and filtered stdout (without data containing markers)
3
+ *
4
+ * @param {(string | Buffer)[]} stdout
5
+ * @returns {{
6
+ * links: { [key: 'test' | 'jira' | 'label']: string }[],
7
+ * meta: { [key: string]: any },
8
+ * stdout: (string | Buffer)[]
9
+ * }}
10
+ */
11
+ export function fetchLinksFromLogs(stdout: (string | Buffer)[]): {
12
+ links: {
13
+ [key: "test" | "jira" | "label"]: string;
14
+ }[];
15
+ meta: {
16
+ [key: string]: any;
17
+ };
18
+ stdout: (string | Buffer)[];
19
+ };
20
+ export namespace playwrightLogsMarkers {
21
+ let label: string;
22
+ let meta: string;
23
+ let linkTest: string;
24
+ let linkJira: string;
25
+ }
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.playwrightLogsMarkers = void 0;
7
+ exports.fetchLinksFromLogs = fetchLinksFromLogs;
8
+ const debug_1 = __importDefault(require("debug"));
9
+ const debug = (0, debug_1.default)('@testomatio/reporter:adapter-playwright-utils');
10
+ exports.playwrightLogsMarkers = {
11
+ label: '[TESTOMATIO-LABEL]',
12
+ meta: '[TESTOMATIO-META]',
13
+ linkTest: '[TESTOMATIO-LINK-TEST]',
14
+ linkJira: '[TESTOMATIO-LINK-JIRA]',
15
+ };
16
+ /**
17
+ * Fetches links from stdout. Returns links and filtered stdout (without data containing markers)
18
+ *
19
+ * @param {(string | Buffer)[]} stdout
20
+ * @returns {{
21
+ * links: { [key: 'test' | 'jira' | 'label']: string }[],
22
+ * meta: { [key: string]: any },
23
+ * stdout: (string | Buffer)[]
24
+ * }}
25
+ */
26
+ function fetchLinksFromLogs(stdout) {
27
+ const links = [];
28
+ const meta = {};
29
+ const markers = [
30
+ { key: exports.playwrightLogsMarkers.linkTest, type: 'test' },
31
+ { key: exports.playwrightLogsMarkers.linkJira, type: 'jira' },
32
+ { key: exports.playwrightLogsMarkers.label, type: 'label' },
33
+ { key: exports.playwrightLogsMarkers.meta, type: 'meta' },
34
+ ];
35
+ const filteredStdout = [];
36
+ stdout.forEach(entry => {
37
+ if (typeof entry !== 'string') {
38
+ filteredStdout.push(entry);
39
+ return;
40
+ }
41
+ // check if entry contains any of markers
42
+ if (!markers.some(m => entry.includes(m.key))) {
43
+ filteredStdout.push(entry);
44
+ return;
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)
57
+ continue;
58
+ let data;
59
+ try {
60
+ data = JSON.parse(rawData);
61
+ }
62
+ catch (e) {
63
+ // Try to extract JSON from the beginning of the string (to handle trailing text)
64
+ const jsonMatch = rawData.match(/^\s*(\[.*?\]|\{.*?\})/);
65
+ if (jsonMatch) {
66
+ try {
67
+ data = JSON.parse(jsonMatch[1]);
68
+ }
69
+ catch (jsonError) {
70
+ // If JSON extraction fails, skip this entry
71
+ debug('Error parsing links from string:', line, '\n', jsonError);
72
+ continue;
73
+ }
74
+ }
75
+ else {
76
+ // No JSON found, skip this entry
77
+ debug('No valid JSON found in:', line);
78
+ continue;
79
+ }
80
+ }
81
+ if (marker.type === 'meta') {
82
+ // meta stored as an object, thus make it similar to links
83
+ Object.assign(meta, data);
84
+ }
85
+ else {
86
+ const ids = Array.isArray(data) ? data : [data];
87
+ links.push(...ids
88
+ // filter non-truthy ids
89
+ .filter(id => !!id)
90
+ .map(id => {
91
+ // If id is already an object with the marker type key, return it as is
92
+ if (typeof id === 'object' && id !== null && marker.type in id) {
93
+ return id;
94
+ }
95
+ // Otherwise, wrap it with the marker type key
96
+ return {
97
+ // marker type is either 'test' or 'jira' or 'label'
98
+ [marker.type]: id,
99
+ };
100
+ }));
101
+ }
102
+ }
103
+ catch (e) {
104
+ debug('Error parsing links from string:', line, '\n', e);
105
+ }
106
+ }
107
+ }
108
+ if (!hasMarker && line) {
109
+ newEntryLines.push(line);
110
+ }
111
+ });
112
+ if (newEntryLines.length) {
113
+ filteredStdout.push(newEntryLines.join('\n'));
114
+ }
115
+ });
116
+ return {
117
+ stdout: filteredStdout,
118
+ links,
119
+ meta,
120
+ };
121
+ }
122
+
123
+ module.exports.fetchLinksFromLogs = fetchLinksFromLogs;
@@ -100,7 +100,7 @@ class VitestReporter {
100
100
  *
101
101
  * @param {VitestTest} test
102
102
  *
103
- * @returns {TestData & {status: string}}
103
+ * @returns {TestData & {status: 'passed' | 'failed' | 'skipped'}}
104
104
  */
105
105
  #getDataFromTest(test) {
106
106
  return {
@@ -108,6 +108,7 @@ class VitestReporter {
108
108
  file: test.file.name,
109
109
  logs: test.logs ? transformLogsToString(test.logs) : '',
110
110
  meta: test.meta,
111
+ // @ts-ignore - STATUS values are string literals but type system sees them as string
111
112
  status: getTestStatus(test),
112
113
  suite_title: test.suite.name || test.file?.name,
113
114
  test_id: (0, utils_js_1.getTestomatIdFromTestTitle)(test.name),
package/lib/bin/cli.js CHANGED
@@ -18,7 +18,7 @@ const picocolors_1 = __importDefault(require("picocolors"));
18
18
  const filesize_1 = require("filesize");
19
19
  const dotenv_1 = __importDefault(require("dotenv"));
20
20
  const replay_js_1 = __importDefault(require("../replay.js"));
21
- const debug = (0, debug_1.default)('@testomatio/reporter:xml-cli');
21
+ const debug = (0, debug_1.default)('@testomatio/reporter:cli');
22
22
  const version = (0, utils_js_1.getPackageVersion)();
23
23
  console.log(picocolors_1.default.cyan(picocolors_1.default.bold(` 🀩 Testomat.io Reporter v${version}`)));
24
24
  const program = new commander_1.Command();
@@ -167,6 +167,7 @@ class DataStorage {
167
167
  if (testDataAsText)
168
168
  debug('<=', dataType, 'file', context, testDataAsText);
169
169
  const testDataArr = testDataAsText?.split(os_1.default.EOL) || [];
170
+ debug('<=', dataType, 'file', context, testDataArr);
170
171
  return testDataArr;
171
172
  }
172
173
  // debug(`No ${this.dataType} data for ${context} in <file> storage`);
@@ -7,7 +7,6 @@ declare class HtmlPipe {
7
7
  isHtml: string;
8
8
  isEnabled: boolean;
9
9
  htmlOutputPath: string;
10
- fullHtmlOutputPath: string;
11
10
  filenameMsg: string;
12
11
  tests: any[];
13
12
  htmlReportDir: string;
@@ -19,9 +18,9 @@ declare class HtmlPipe {
19
18
  updateRun(): void;
20
19
  /**
21
20
  * Add test data to the result array for saving. As a result of this function, we get a result object to save.
22
- * @param {import('../../types/types.js').RunData} test - object which includes each test entry.
21
+ * @param {import('../../types/types.js').HtmlTestData} test - object which includes each test entry.
23
22
  */
24
- addTest(test: import("../../types/types.js").RunData): void;
23
+ addTest(test: import("../../types/types.js").HtmlTestData): void;
25
24
  finishRun(runParams: any): Promise<void>;
26
25
  /**
27
26
  * Generates an HTML report based on provided test data and a template.