@testomatio/reporter 1.2.2-beta → 1.2.2-beta-html-pagination-feature-v2

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 (46) hide show
  1. package/README.md +61 -54
  2. package/lib/adapter/codecept.js +140 -61
  3. package/lib/adapter/cucumber/current.js +103 -60
  4. package/lib/adapter/cucumber/legacy.js +27 -12
  5. package/lib/adapter/cucumber.js +2 -2
  6. package/lib/adapter/cypress-plugin/index.js +52 -25
  7. package/lib/adapter/jasmine.js +1 -1
  8. package/lib/adapter/jest.js +49 -11
  9. package/lib/adapter/mocha.js +103 -51
  10. package/lib/adapter/playwright.js +100 -31
  11. package/lib/adapter/webdriver.js +1 -1
  12. package/lib/bin/reportXml.js +14 -13
  13. package/lib/bin/startTest.js +27 -6
  14. package/lib/client.js +193 -69
  15. package/lib/config.js +34 -0
  16. package/lib/constants.js +19 -7
  17. package/lib/data-storage.js +203 -0
  18. package/lib/fileUploader.js +128 -53
  19. package/lib/junit-adapter/adapter.js +0 -2
  20. package/lib/junit-adapter/csharp.js +3 -4
  21. package/lib/junit-adapter/index.js +3 -3
  22. package/lib/junit-adapter/java.js +35 -17
  23. package/lib/junit-adapter/javascript.js +1 -2
  24. package/lib/junit-adapter/python.js +12 -14
  25. package/lib/junit-adapter/ruby.js +1 -2
  26. package/lib/pipe/csv.js +5 -3
  27. package/lib/pipe/github.js +27 -39
  28. package/lib/pipe/gitlab.js +20 -24
  29. package/lib/pipe/html.js +363 -0
  30. package/lib/pipe/index.js +3 -1
  31. package/lib/pipe/testomatio.js +183 -56
  32. package/lib/reporter-functions.js +46 -0
  33. package/lib/reporter.js +11 -9
  34. package/lib/services/artifacts.js +57 -0
  35. package/lib/services/index.js +13 -0
  36. package/lib/services/key-values.js +58 -0
  37. package/lib/services/logger.js +311 -0
  38. package/lib/template/testomatio.hbs +1233 -0
  39. package/lib/utils/pipe_utils.js +128 -0
  40. package/lib/{util.js → utils/utils.js} +144 -12
  41. package/lib/xmlReader.js +211 -122
  42. package/package.json +18 -8
  43. package/lib/_ArtifactStorageOld.js +0 -142
  44. package/lib/artifactStorage.js +0 -25
  45. package/lib/dataStorage.js +0 -180
  46. package/lib/logger.js +0 -278
@@ -1,58 +1,78 @@
1
1
  // eslint-disable-next-line global-require, import/no-extraneous-dependencies
2
2
  const Mocha = require('mocha');
3
- const debug = require('debug')('@testomatio/reporter:adapter:mocha');
4
3
  const chalk = require('chalk');
5
4
  const TestomatClient = require('../client');
6
- const { STATUS } = require('../constants');
7
- const { parseTest, specificTestInfo } = require('../util');
8
- const ArtifactStorage = require('../_ArtifactStorageOld');
9
-
10
- const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING } = Mocha.Runner.constants;
5
+ const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
6
+ const { parseTest, fileSystem } = require('../utils/utils');
7
+ const config = require('../config');
8
+ const { services } = require('../services');
9
+
10
+ const {
11
+ EVENT_RUN_BEGIN,
12
+ EVENT_RUN_END,
13
+ EVENT_TEST_FAIL,
14
+ EVENT_TEST_PASS,
15
+ EVENT_TEST_PENDING,
16
+ EVENT_SUITE_BEGIN,
17
+ EVENT_SUITE_END,
18
+ EVENT_TEST_BEGIN,
19
+ EVENT_TEST_END,
20
+ } = Mocha.Runner.constants;
11
21
 
12
22
  function MochaReporter(runner, opts) {
13
23
  Mocha.reporters.Base.call(this, runner);
14
- let passes = 0; let failures = 0; let skipped = 0;
15
- let artifactStore;
24
+ let passes = 0;
25
+ let failures = 0;
26
+ let skipped = 0;
27
+ // let artifactStore;
16
28
 
17
- const apiKey = opts?.reporterOptions?.apiKey || process.env.TESTOMATIO;
29
+ const apiKey = opts?.reporterOptions?.apiKey || config.TESTOMATIO;
18
30
 
19
- if (!apiKey) {
20
- debug('TESTOMATIO key is empty, ignoring reports');
21
- return;
22
- }
23
31
  const client = new TestomatClient({ apiKey });
24
32
 
25
33
  runner.on(EVENT_RUN_BEGIN, () => {
26
34
  client.createRun();
27
35
 
28
- const params = {
29
- toFile: true
30
- };
31
-
32
- artifactStore = runner._workerReporter !== undefined
33
- ? new ArtifactStorage(params)
34
- : new ArtifactStorage();
36
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
35
37
  });
36
38
 
37
- runner.on(EVENT_TEST_PASS, async(test) => {
39
+ runner.on(EVENT_SUITE_BEGIN, async suite => {
40
+ services.setContext(suite.fullTitle());
41
+ });
42
+
43
+ runner.on(EVENT_SUITE_END, async () => {
44
+ services.setContext(null);
45
+ });
46
+
47
+ runner.on(EVENT_TEST_BEGIN, async test => {
48
+ services.setContext(test.fullTitle());
49
+ });
50
+
51
+ runner.on(EVENT_TEST_END, async () => {
52
+ services.setContext(null);
53
+ });
54
+
55
+ runner.on(EVENT_TEST_PASS, async test => {
38
56
  passes += 1;
57
+
39
58
  console.log(chalk.bold.green('✔'), test.fullTitle());
40
59
  const testId = parseTest(test.title);
41
60
 
42
- const specificTest = specificTestInfo(test);
43
- const content = await artifactStore.artifactByTestName(specificTest);
61
+ const logs = getTestLogs(test);
62
+ const artifacts = services.artifacts.get(test.fullTitle());
63
+ const keyValues = services.keyValues.get(test.fullTitle());
44
64
 
45
- debug(`test=${specificTest} content = `, content);
46
-
47
- client.addTestRun(
48
- STATUS.PASSED,
49
- {
50
- test_id: testId,
51
- title: test.title,
52
- time: test.duration,
53
- },
54
- content
55
- );
65
+ client.addTestRun(STATUS.PASSED, {
66
+ test_id: testId,
67
+ suite_title: getSuiteTitle(test),
68
+ title: getTestName(test),
69
+ code: test.body.toString(),
70
+ file: getFile(test),
71
+ time: test.duration,
72
+ logs,
73
+ manuallyAttachedArtifacts: artifacts,
74
+ meta: keyValues,
75
+ });
56
76
  });
57
77
 
58
78
  runner.on(EVENT_TEST_PENDING, test => {
@@ -60,43 +80,75 @@ function MochaReporter(runner, opts) {
60
80
  console.log('skip: %s', test.fullTitle());
61
81
  const testId = parseTest(test.title);
62
82
  client.addTestRun(STATUS.SKIPPED, {
63
- title: test.title,
83
+ title: getTestName(test),
84
+ suite_title: getSuiteTitle(test),
85
+ code: test.body.toString(),
86
+ file: getFile(test),
64
87
  test_id: testId,
65
88
  time: test.duration,
66
89
  });
67
90
  });
68
91
 
69
- runner.on(EVENT_TEST_FAIL, async(test, err) => {
92
+ runner.on(EVENT_TEST_FAIL, async (test, err) => {
70
93
  failures += 1;
71
94
  console.log(chalk.bold.red('✖'), test.fullTitle(), chalk.gray(err.message));
72
95
  const testId = parseTest(test.title);
73
96
 
74
- const specificTest = specificTestInfo(test);
75
- const content = await artifactStore.artifactByTestName(specificTest);
97
+ const logs = getTestLogs(test);
76
98
 
77
- debug(`fail test=${specificTest} content = `, content);
78
-
79
- client.addTestRun(
80
- STATUS.FAILED, {
81
- error: err,
82
- test_id: testId,
83
- title: test.title,
84
- time: test.duration,
85
- },
86
- content
87
- );
99
+ client.addTestRun(STATUS.FAILED, {
100
+ error: err,
101
+ suite_title: getSuiteTitle(test),
102
+ file: getFile(test),
103
+ test_id: testId,
104
+ title: getTestName(test),
105
+ code: test.body.toString(),
106
+ time: test.duration,
107
+ logs,
108
+ });
88
109
  });
89
110
 
90
111
  runner.on(EVENT_RUN_END, () => {
91
112
  const status = failures === 0 ? STATUS.PASSED : STATUS.FAILED;
92
113
  console.log(chalk.bold(status), `${passes} passed, ${failures} failed, ${skipped} skipped`);
93
114
  client.updateRunStatus(status);
94
-
95
- artifactStore.cleanup();
96
115
  });
97
116
  }
98
117
 
118
+ function getTestLogs(test) {
119
+ const suiteLogsArr = services.logger.getLogs(test.parent.fullTitle());
120
+ const suiteLogs = suiteLogsArr ? suiteLogsArr.join('\n').trim() : '';
121
+ const testLogsArr = services.logger.getLogs(test.fullTitle());
122
+ const testLogs = testLogsArr ? testLogsArr.join('\n').trim() : '';
123
+
124
+ let logs = '';
125
+ if (suiteLogs) {
126
+ logs += `${chalk.bold('\t--- BeforeSuite ---')}\n${suiteLogs}`;
127
+ }
128
+ if (testLogs) {
129
+ logs += `\n${chalk.bold('\t--- Test ---')}\n${testLogs}`;
130
+ }
131
+ return logs;
132
+ }
133
+
99
134
  // To have this reporter "extend" a built-in reporter uncomment the following line:
100
135
  Mocha.utils.inherits(MochaReporter, Mocha.reporters.Spec);
101
136
 
102
137
  module.exports = MochaReporter;
138
+
139
+ function getSuiteTitle(test, pathArr = []) {
140
+ if (test.parent.parent) getSuiteTitle(test.parent, pathArr);
141
+
142
+ pathArr.push(test.parent.title);
143
+
144
+ return pathArr.filter(t => !!t)[0];
145
+ }
146
+
147
+ function getFile(test) {
148
+ return test.parent.file?.replace(process.cwd(), '');
149
+ }
150
+
151
+ function getTestName(test) {
152
+ if (process.env.TESTOMATIO_CREATE === 'fulltitle') return test.fullTitle();
153
+ return test.title;
154
+ }
@@ -3,59 +3,107 @@ const crypto = require('crypto');
3
3
  const os = require('os');
4
4
  const path = require('path');
5
5
  const fs = require('fs');
6
- const { APP_PREFIX, STATUS: Status } = require('../constants');
6
+ const { APP_PREFIX, STATUS: Status, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
7
7
  const TestomatioClient = require('../client');
8
8
  const { isArtifactsEnabled } = require('../fileUploader');
9
- const { parseTest } = require('../util');
9
+ const { parseTest, fileSystem } = require('../utils/utils');
10
+ // const debug = require('debug')('@testomatio/reporter:adapter:playwright');
11
+ const { services } = require('../services');
12
+ const { dataStorage } = require('../data-storage');
10
13
 
11
14
  const reportTestPromises = [];
12
15
 
13
- class TestomatioReporter {
16
+ class PlaywrightReporter {
14
17
  constructor(config = {}) {
15
18
  this.client = new TestomatioClient({ apiKey: config?.apiKey });
16
19
 
17
20
  this.uploads = [];
18
21
  }
19
22
 
20
- onBegin(_config, suite) {
23
+ onBegin(config, suite) {
24
+ // clean data storage
25
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
21
26
  if (!this.client) return;
22
27
  this.suite = suite;
28
+ this.config = config;
23
29
  this.client.createRun();
24
30
  }
25
31
 
32
+ onTestBegin(testInfo) {
33
+ const fullTestTitle = getTestContextName(testInfo);
34
+ dataStorage.setContext(fullTestTitle);
35
+ }
36
+
26
37
  onTestEnd(test, result) {
27
38
  if (!this.client) return;
28
39
 
29
- let testId = parseTest(test.title);
30
-
31
40
  const { title } = test;
41
+
42
+ let testId = parseTest(title);
43
+
32
44
  const { error, duration } = result;
33
45
 
34
- const suite_title = test.parent ? test.parent.title : null;
46
+ const suite_title = test.parent ? test.parent?.title : path.basename(test?.location?.file);
35
47
 
36
48
  const steps = [];
37
49
  for (const step of result.steps) {
38
50
  appendStep(step, steps);
39
51
  }
40
-
41
- const reportTestPromise = this.client.addTestRun(checkStatus(result.status), {
42
- error,
43
- test_id: testId,
44
- suite_title,
45
- title,
46
- steps: steps.join('\n'),
47
- time: duration,
48
- }).then(pipes => {
49
- testId = pipes.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
50
-
51
- this.uploads.push({
52
- testId, title, suite_title, files: result.attachments.filter((a) => a.body || a.path)
52
+
53
+ const fullTestTitle = getTestContextName(test);
54
+ let logs = '';
55
+ if (result.stderr.length || result.stdout.length) {
56
+ logs = `\n\n${chalk.bold('Logs:')}\n${chalk.red(result.stderr.join(''))}\n${result.stdout.join('')}`;
57
+ }
58
+ const manuallyAttachedArtifacts = services.artifacts.get(fullTestTitle);
59
+ const keyValues = services.keyValues.get(fullTestTitle);
60
+
61
+ const reportTestPromise = this.client
62
+ .addTestRun(checkStatus(result.status), {
63
+ error,
64
+ test_id: testId,
65
+ suite_title,
66
+ title,
67
+ steps: steps.join('\n'),
68
+ time: duration,
69
+ logs,
70
+ manuallyAttachedArtifacts,
71
+ meta: keyValues,
72
+ file: test.location?.file,
53
73
  })
54
- });;
74
+ .then(pipes => {
75
+ testId = pipes?.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
76
+
77
+ this.uploads.push({
78
+ testId,
79
+ title,
80
+ suite_title,
81
+ files: result.attachments.filter(a => a.body || a.path),
82
+ file: test.location?.file,
83
+ });
84
+ // remove empty uploads
85
+ this.uploads = this.uploads.filter(upload => upload.files.length);
86
+ });
55
87
 
56
88
  reportTestPromises.push(reportTestPromise);
57
89
  }
58
90
 
91
+ #getArtifactPath(artifact) {
92
+ if (artifact.path) {
93
+ if (path.isAbsolute(artifact.path)) return artifact.path;
94
+
95
+ return path.join(this.config.outputDir || this.config.projects[0].outputDir, artifact.path);
96
+ }
97
+
98
+ if (artifact.body) {
99
+ const fileName = tmpFile();
100
+ fs.writeFileSync(fileName, artifact.body);
101
+ return fileName;
102
+ }
103
+
104
+ return null;
105
+ }
106
+
59
107
  async onEnd(result) {
60
108
  if (!this.client) return;
61
109
 
@@ -67,17 +115,13 @@ class TestomatioReporter {
67
115
  const promises = [];
68
116
 
69
117
  for (const upload of this.uploads) {
70
-
71
118
  const { title, testId, suite_title } = upload;
72
119
 
73
- const files = upload.files.map((attachment) => {
74
- if (attachment.body) {
75
- const fileName = tmpFile();
76
- fs.writeFileSync(fileName, attachment.body);
77
- }
78
- return { path: attachment.path, title, type: attachment.contentType };
79
- });
80
-
120
+ const files = upload.files.map(attachment => ({
121
+ path: this.#getArtifactPath(attachment),
122
+ title,
123
+ type: attachment.contentType,
124
+ }));
81
125
 
82
126
  promises.push(
83
127
  this.client.addTestRun(undefined, {
@@ -85,6 +129,7 @@ class TestomatioReporter {
85
129
  title,
86
130
  suite_title,
87
131
  files,
132
+ file: upload.file,
88
133
  }),
89
134
  );
90
135
  }
@@ -124,4 +169,28 @@ function tmpFile(prefix = 'tmp.') {
124
169
  return path.join(tmpdir, prefix + crypto.randomBytes(16).toString('hex'));
125
170
  }
126
171
 
127
- module.exports = TestomatioReporter;
172
+ /**
173
+ * Returns filename + test title
174
+ * @param {*} test - testInfo object from Playwright
175
+ * @returns
176
+ */
177
+ function getTestContextName(test) {
178
+ return `${test._requireFile || ''}_${test.title}`;
179
+ }
180
+
181
+ function initPlaywrightForStorage() {
182
+ try {
183
+ // @ts-ignore-next-line
184
+ // eslint-disable-next-line import/no-unresolved
185
+ const { test } = require('@playwright/test');
186
+ // eslint-disable-next-line no-empty-pattern
187
+ test.beforeEach(async ({}, testInfo) => {
188
+ global.testomatioTestTitle = `${testInfo.file || ''}_${testInfo.title}`;
189
+ });
190
+ } catch (e) {
191
+ // ignore
192
+ }
193
+ }
194
+
195
+ module.exports = PlaywrightReporter;
196
+ module.exports.initPlaywrightForStorage = initPlaywrightForStorage;
@@ -1,7 +1,7 @@
1
1
  // eslint-disable-next-line global-require, import/no-extraneous-dependencies
2
2
  const WDIOReporter = require('@wdio/reporter').default;
3
3
  const TestomatClient = require('../client');
4
- const { parseTest } = require('../util');
4
+ const { parseTest } = require('../utils/utils');
5
5
 
6
6
  class WebdriverReporter extends WDIOReporter {
7
7
  constructor(options) {
@@ -1,29 +1,30 @@
1
1
  #!/usr/bin/env node
2
- const program = require("commander");
3
- const chalk = require("chalk");
2
+ const program = require('commander');
3
+ const chalk = require('chalk');
4
4
  const glob = require('glob');
5
5
  const debug = require('debug')('@testomatio/reporter:xml-cli');
6
6
  const { APP_PREFIX } = require('../constants');
7
- const XmlReader = require("../xmlReader");
7
+ const XmlReader = require('../xmlReader');
8
8
 
9
9
  const { version } = require('../../package.json');
10
10
 
11
11
  console.log(chalk.cyan.bold(` 🤩 Testomat.io XML Reporter v${version}`));
12
12
 
13
13
  program
14
- .arguments("<pattern>")
15
- .option("-d, --dir <dir>", "Project directory")
16
- .option("--java-tests [java-path]", "Load Java tests from path, by default: src/test/java")
17
- .option("--lang <lang>", "Language used (python, ruby, java)")
18
- .option("--timelimit <time>", "default time limit in seconds to kill a stuck process")
19
- .option("--env-file <envfile>", "Load environment variables from env file")
14
+ .arguments('<pattern>')
15
+ .option('-d, --dir <dir>', 'Project directory')
16
+ .option('--java-tests [java-path]', 'Load Java tests from path, by default: src/test/java')
17
+ .option('--lang <lang>', 'Language used (python, ruby, java)')
18
+ .option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
19
+ .option('--env-file <envfile>', 'Load environment variables from env file')
20
20
  .action(async (pattern, opts) => {
21
21
  if (!pattern.endsWith('.xml')) {
22
22
  pattern += '.xml';
23
23
  }
24
24
  let { javaTests, lang } = opts;
25
25
  if (opts.envFile) {
26
- debug('Loading env file: %s', opts.envFile)
26
+ console.log(APP_PREFIX, 'Loading env file:', opts.envFile);
27
+ debug('Loading env file: %s', opts.envFile);
27
28
  require('dotenv').config({ path: opts.envFile }); // eslint-disable-line
28
29
  }
29
30
  if (javaTests === true) javaTests = 'src/test/java';
@@ -37,7 +38,7 @@ program
37
38
  }
38
39
 
39
40
  for (const file of files) {
40
- console.log(APP_PREFIX,`Parsed ${file}`);
41
+ console.log(APP_PREFIX, `Parsed ${file}`);
41
42
  runReader.parse(file);
42
43
  }
43
44
 
@@ -46,7 +47,7 @@ program
46
47
  timeoutTimer = setTimeout(() => {
47
48
  console.log(`⚠️ Reached timeout of ${opts.timelimit}s. Exiting... (Exit code is 0 to not fail the pipeline)`);
48
49
  process.exit(0);
49
- }, parseInt(opts.timelimit, 10) * 1000)
50
+ }, parseInt(opts.timelimit, 10) * 1000);
50
51
  }
51
52
 
52
53
  try {
@@ -56,7 +57,7 @@ program
56
57
  console.log(APP_PREFIX, 'Error updating status, skipping...', err);
57
58
  }
58
59
 
59
- if (timeoutTimer) clearTimeout(timeoutTimer)
60
+ if (timeoutTimer) clearTimeout(timeoutTimer);
60
61
  });
61
62
 
62
63
  if (process.argv.length < 3) {
@@ -5,6 +5,7 @@ const chalk = require('chalk');
5
5
  const TestomatClient = require('../client');
6
6
  const { APP_PREFIX, STATUS } = require('../constants');
7
7
  const { version } = require('../../package.json');
8
+ const config = require('../config');
8
9
 
9
10
  console.log(chalk.cyan.bold(` 🤩 Testomat.io Reporter v${version}`));
10
11
 
@@ -12,13 +13,15 @@ program
12
13
  .option('-c, --command <cmd>', 'Test runner command')
13
14
  .option('--launch', 'Start a new run and return its ID')
14
15
  .option('--finish', 'Finish Run by its ID')
15
- .option("--env-file <envfile>", "Load environment variables from env file")
16
- .action(opts => {
16
+ .option('--env-file <envfile>', 'Load environment variables from env file')
17
+ .option('--filter <filter>', 'Additional execution filter')
18
+ .action(async opts => {
19
+ const { launch, finish, filter } = opts;
20
+ let { command } = opts;
17
21
 
18
- const { command, launch, finish } = opts;
19
22
  if (opts.envFile) require('dotenv').config(opts.envFile); // eslint-disable-line
20
23
 
21
- const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || process.env.TESTOMATIO;
24
+ const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
22
25
  const title = process.env.TESTOMATIO_TITLE;
23
26
 
24
27
  if (launch) {
@@ -57,6 +60,26 @@ program
57
60
  return;
58
61
  }
59
62
 
63
+ const client = new TestomatClient({ apiKey, title, parallel: true });
64
+
65
+ if (filter) {
66
+ const [pipe, ...optsArray] = filter.split(':');
67
+ const pipeOptions = optsArray.join(':');
68
+
69
+ try {
70
+ const tests = await client.prepareRun({ pipe, pipeOptions });
71
+
72
+ if (!tests || tests.length === 0) {
73
+ return;
74
+ }
75
+
76
+ const grep = ` --grep (${tests.join('|')})`;
77
+ command += grep;
78
+ } catch (err) {
79
+ console.log(APP_PREFIX, err);
80
+ }
81
+ }
82
+
60
83
  const testCmds = command.split(' ');
61
84
  console.log(APP_PREFIX, `🚀 Running`, chalk.green(command));
62
85
 
@@ -73,8 +96,6 @@ program
73
96
  return;
74
97
  }
75
98
 
76
- const client = new TestomatClient({ apiKey, title, parallel: true });
77
-
78
99
  client.createRun().then(() => {
79
100
  const cmd = spawn(testCmds[0], testCmds.slice(1), { stdio: 'inherit' });
80
101