@testomatio/reporter 1.2.1-beta → 1.2.1-beta.codecept-id

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 (47) hide show
  1. package/README.md +61 -54
  2. package/lib/adapter/codecept.js +136 -57
  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 +317 -0
  30. package/lib/pipe/index.js +3 -1
  31. package/lib/pipe/testomatio.js +182 -55
  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/template-draft.hbs +249 -0
  39. package/lib/template/testomatio.hbs +388 -0
  40. package/lib/utils/pipe_utils.js +128 -0
  41. package/lib/{util.js → utils/utils.js} +145 -12
  42. package/lib/xmlReader.js +211 -122
  43. package/package.json +18 -8
  44. package/lib/_ArtifactStorageOld.js +0 -142
  45. package/lib/artifactStorage.js +0 -25
  46. package/lib/dataStorage.js +0 -180
  47. package/lib/logger.js +0 -278
@@ -1,35 +1,59 @@
1
- // eslint-disable-next-line global-require, import/no-extraneous-dependencies
2
- const { Formatter, formatterHelpers } = require("@cucumber/cucumber");
1
+ // eslint-disable-next-line global-require, import/no-extraneous-dependencies, import/no-unresolved
2
+ const { Formatter, formatterHelpers } = require('@cucumber/cucumber');
3
3
  const chalk = require('chalk');
4
4
  const fs = require('fs');
5
- const { STATUS } = require('../../constants');
5
+ const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../../constants');
6
6
  const TestomatClient = require('../../client');
7
+ const { parseTest, fileSystem } = require('../../utils/utils');
8
+ const config = require('../../config');
9
+ const { services } = require('../../services');
10
+
11
+ const { GherkinDocumentParser, PickleParser } = formatterHelpers;
12
+ const { getGherkinScenarioLocationMap, getGherkinStepMap } = GherkinDocumentParser;
13
+ const { getPickleStepMap } = PickleParser;
14
+
15
+ function getTestId(scenario) {
16
+ if (scenario) {
17
+ for (const tag of scenario.tags) {
18
+ const testId = parseTest(tag.name);
19
+ if (testId) return testId;
20
+ }
21
+ }
7
22
 
8
- const { GherkinDocumentParser, PickleParser } = formatterHelpers
9
- const {
10
- getGherkinScenarioLocationMap,
11
- getGherkinStepMap,
12
- } = GherkinDocumentParser
13
- const { getPickleStepMap } = PickleParser
23
+ return null;
24
+ }
14
25
 
15
26
  class CucumberReporter extends Formatter {
16
27
  constructor(options) {
17
28
  super(options);
18
- options.eventBroadcaster.on('envelope', this.parseEnvelope.bind(this))
29
+ options.eventBroadcaster.on('envelope', this.parseEnvelope.bind(this));
19
30
  this.failures = [];
20
31
  this.cases = [];
21
32
 
22
- this.client = new TestomatClient();
33
+ this.client = new TestomatClient({ apiKey: options.apiKey || config.TESTOMATIO });
23
34
  this.status = STATUS.PASSED;
24
-
25
35
  }
26
36
 
27
37
  parseEnvelope(envelope) {
28
- if (envelope.testCaseStarted && this.client) this.client.createRun()
38
+ if (envelope.testRunStarted) {
39
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
40
+ }
41
+ if (envelope.testCaseStarted && this.client) {
42
+ this.client.createRun();
43
+ this.onTestCaseStarted(envelope.testCaseStarted);
44
+ }
29
45
  if (envelope.testCaseFinished) this.onTestCaseFinished(envelope.testCaseFinished);
30
46
  if (envelope.testRunFinished) this.onTestRunFinished(envelope);
31
47
  }
32
48
 
49
+ onTestCaseStarted(testCaseStarted) {
50
+ const testCaseAttempt = this.eventDataCollector.getTestCaseAttempt(testCaseStarted.id);
51
+ if (!global.testomatioDataStore) global.testomatioDataStore = {};
52
+
53
+ const testTitle = testCaseAttempt.pickle.name + testCaseAttempt.pickle.uri;
54
+ services.setContext(testTitle);
55
+ }
56
+
33
57
  onTestCaseFinished(testCaseFinished) {
34
58
  const testCaseAttempt = this.eventDataCollector.getTestCaseAttempt(testCaseFinished.testCaseStartedId);
35
59
 
@@ -45,10 +69,10 @@ class CucumberReporter extends Formatter {
45
69
  if (testCaseAttempt.worstTestStepResult.status === 'SKIPPED') {
46
70
  status = STATUS.SKIPPED;
47
71
  }
48
- if (testCaseAttempt.worstTestStepResult.status === 'UNDEFINED') {
49
- status = STATUS.SKIPPED;
50
- message = 'Undefined steps. Implement missing steps and rerun this scenario';
51
- }
72
+ // if (testCaseAttempt.worstTestStepResult.status === 'UNDEFINED') {
73
+ // status = STATUS.SKIPPED;
74
+ // message = 'Undefined steps. Implement missing steps and rerun this scenario';
75
+ // }
52
76
  if (testCaseAttempt.worstTestStepResult.status === 'FAILED') {
53
77
  message = testCaseAttempt?.worstTestStepResult?.message;
54
78
  status = STATUS.FAILED;
@@ -64,10 +88,12 @@ class CucumberReporter extends Formatter {
64
88
  }
65
89
 
66
90
  const scenario = testCaseAttempt.pickle.name;
67
- const testId = testCaseAttempt.pickle.id;
91
+ // this may broke something (it is supposed to work, but I am sure it did not)
92
+ // const testId = testCaseAttempt.pickle.id;
93
+ const testId = getTestId(testCaseAttempt.pickle);
68
94
 
69
95
  let exampleString = '';
70
- if (example) exampleString = ` ${example.join(' | ')}`
96
+ if (example) exampleString = ` ${example.join(' | ')}`;
71
97
  let cliMessage = `${chalk.bold(scenario)}${exampleString}: ${chalk[color].bold(status.toUpperCase())} `;
72
98
 
73
99
  if (message) cliMessage += chalk.gray(message.split('\n')[0]);
@@ -79,19 +105,32 @@ class CucumberReporter extends Formatter {
79
105
 
80
106
  const time = Object.values(testCaseAttempt.stepResults)
81
107
  .map(t => t.duration)
82
- .reduce((sum, duration) => sum + (duration.seconds * 1000) + (duration.nanos / 1000000), 0)
108
+ .reduce((sum, duration) => sum + duration.seconds * 1000 + duration.nanos / 1000000, 0);
83
109
 
84
110
  if (!this.client) return;
85
111
 
112
+ const testTitle = testCaseAttempt.pickle.name + testCaseAttempt.pickle.uri;
113
+ const logs = services.logger.getLogs(testTitle).join('\n');
114
+ const artifacts = services.artifacts.get(testTitle);
115
+ const keyValues = services.keyValues.get(testTitle);
116
+
86
117
  this.client.addTestRun(status, {
87
118
  // error: testCaseAttempt.worstTestStepResult.message,
88
119
  message,
89
- steps: getSteps(testCaseAttempt).map(s => s.toString()).join('\n').trim(),
90
- example: { ...example},
120
+ steps: getSteps(testCaseAttempt)
121
+ .map(s => s.toString())
122
+ .join('\n')
123
+ .trim(),
124
+ example: { ...example },
125
+ logs,
126
+ manuallyAttachedArtifacts: artifacts,
127
+ meta: keyValues,
91
128
  title: scenario,
92
129
  test_id: testId,
93
130
  time,
94
131
  });
132
+
133
+ services.setContext(null);
95
134
  }
96
135
 
97
136
  onTestRunFinished(envelope) {
@@ -99,21 +138,20 @@ class CucumberReporter extends Formatter {
99
138
  console.log(chalk.bold('\nSUMMARY:\n\n'));
100
139
 
101
140
  this.failures.forEach((tc, i) => {
102
- let message = ` ${i+1}) ${tc.pickle.name}\n`;
141
+ let message = ` ${i + 1}) ${tc.pickle.name}\n`;
103
142
 
104
143
  const steps = getSteps(tc);
105
144
 
106
145
  steps.forEach(s => {
107
- message += ` ${s.toString()}\n`
108
- })
146
+ message += ` ${s.toString()}\n`;
147
+ });
109
148
 
110
149
  console.log(message);
111
150
  if (tc?.worstTestStepResult?.message) {
112
151
  console.log(chalk.red(tc?.worstTestStepResult?.message));
113
152
  }
114
153
  console.log();
115
-
116
- })
154
+ });
117
155
  }
118
156
 
119
157
  const { testRunFinished } = envelope;
@@ -125,46 +163,48 @@ class CucumberReporter extends Formatter {
125
163
 
126
164
  if (!this.client) return;
127
165
 
128
- this.client.updateRunStatus(testRunFinished.success ? STATUS.PASSED : STATUS.FAILED)
166
+ this.client.updateRunStatus(testRunFinished.success ? STATUS.PASSED : STATUS.FAILED);
129
167
  }
130
168
  }
131
169
 
132
170
  function getSteps(tc) {
133
171
  const stepIds = Object.keys(tc.stepResults);
134
- const pickleSteps = getPickleStepMap(tc.pickle)
135
- return stepIds.map(stepId => {
136
- const ts = tc.testCase.testSteps.find(t => t.id === stepId);
137
- if (!ts) return;
138
- if (!ts.pickleStepId) return;
139
- const result = tc.stepResults[stepId];
140
- const pickleStep = pickleSteps[ts.pickleStepId];
141
- const sourceStepId = pickleStep.astNodeIds[0];
142
- const step = {
143
- text: pickleStep.text,
144
- duration: result.duration,
145
- status: result.status,
146
- }
147
- const color = getStatusColor(result.status);
148
- if (sourceStepId && getGherkinStepMap(tc.gherkinDocument)[sourceStepId]) {
149
- step.keyword = getGherkinStepMap(tc.gherkinDocument)[sourceStepId].keyword;
150
- }
151
- step.toString = function toString() {
152
- const duration = step.duration.seconds * 1000 + (step.duration.nanos / 1000000)
153
- const durationString = ` ${ chalk.gray(`(${Number(duration).toFixed(2)}ms)`)}`;
154
- const stepString = `${chalk.bold(this.keyword)}${this.text}`.trim();
155
- if (color === 'red') return chalk.red(stepString) + durationString;
156
- if (color === 'yellow') return chalk.yellow(stepString) + durationString;
157
- return stepString + durationString;
158
- }
159
- return step;
160
- }).filter(s => !!s)
172
+ const pickleSteps = getPickleStepMap(tc.pickle);
173
+ return stepIds
174
+ .map(stepId => {
175
+ const ts = tc.testCase.testSteps.find(t => t.id === stepId);
176
+ if (!ts) return;
177
+ if (!ts.pickleStepId) return;
178
+ const result = tc.stepResults[stepId];
179
+ const pickleStep = pickleSteps[ts.pickleStepId];
180
+ const sourceStepId = pickleStep.astNodeIds[0];
181
+ const step = {
182
+ text: pickleStep.text,
183
+ duration: result.duration,
184
+ status: result.status,
185
+ };
186
+ const color = getStatusColor(result.status);
187
+ if (sourceStepId && getGherkinStepMap(tc.gherkinDocument)[sourceStepId]) {
188
+ step.keyword = getGherkinStepMap(tc.gherkinDocument)[sourceStepId].keyword;
189
+ }
190
+ step.toString = function toString() {
191
+ const duration = step.duration.seconds * 1000 + step.duration.nanos / 1000000;
192
+ const durationString = ` ${chalk.gray(`(${Number(duration).toFixed(2)}ms)`)}`;
193
+ const stepString = `${chalk.bold(this.keyword)}${this.text}`.trim();
194
+ if (color === 'red') return chalk.red(stepString) + durationString;
195
+ if (color === 'yellow') return chalk.yellow(stepString) + durationString;
196
+ return stepString + durationString;
197
+ };
198
+ return step;
199
+ })
200
+ .filter(s => !!s);
161
201
  }
162
202
 
163
203
  function getStatusColor(status) {
164
204
  if (status === 'UNDEFINED') return 'yellow';
165
205
  if (status === 'SKIPPED') return 'yellow';
166
206
  if (status === 'FAILED') return 'red';
167
- return 'green'
207
+ return 'green';
168
208
  }
169
209
 
170
210
  function getExample(testCaseAttempt) {
@@ -173,11 +213,14 @@ function getExample(testCaseAttempt) {
173
213
  if (!nodesMap[exampleNodeId]) return;
174
214
  const featureDoc = fs.readFileSync(testCaseAttempt.gherkinDocument.uri).toString();
175
215
  const { line } = nodesMap[exampleNodeId];
176
- const example = featureDoc.split('\n')[line-1];
216
+ const example = featureDoc.split('\n')[line - 1];
177
217
  if (example) {
178
- return example.trim().split('|').filter(r => !!r).map(r => r.trim());
179
- };
180
-
218
+ return example
219
+ .trim()
220
+ .split('|')
221
+ .filter(r => !!r)
222
+ .map(r => r.trim());
223
+ }
181
224
  }
182
225
 
183
226
  module.exports = CucumberReporter;
@@ -1,9 +1,10 @@
1
1
  // eslint-disable-next-line global-require, import/no-extraneous-dependencies, import/no-unresolved
2
2
  const { Formatter } = require('cucumber');
3
3
  const chalk = require('chalk');
4
- const { parseTest } = require('../../util');
5
- const { STATUS } = require('../../constants');
4
+ const { parseTest, fileSystem } = require('../../utils/utils');
5
+ const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../../constants');
6
6
  const TestomatClient = require('../../client');
7
+ const config = require('../../config');
7
8
 
8
9
  const createTestomatFormatter = apiKey => {
9
10
  if (!apiKey || apiKey === '') {
@@ -96,9 +97,23 @@ const createTestomatFormatter = apiKey => {
96
97
  this.status = STATUS.PASSED.toString();
97
98
 
98
99
  options.eventBroadcaster.on('gherkin-document', addDocument);
99
- options.eventBroadcaster.on('test-run-started', () => this?.client?.createRun());
100
+ options.eventBroadcaster.on('test-run-started', () => {
101
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
102
+ this?.client?.createRun();
103
+ });
100
104
  options.eventBroadcaster.on('test-case-finished', this.onTestCaseFinished.bind(this));
105
+ options.eventBroadcaster.on('test-case-started', this.onTestCaseStarted.bind(this));
101
106
  options.eventBroadcaster.on('test-run-finished', () => this?.client?.updateRunStatus(this.status));
107
+
108
+ global.testomatioRunningEnvironment = 'cucumber:legacy';
109
+ }
110
+
111
+ onTestCaseStarted(event) {
112
+ const scenario = getScenario(event.sourceLocation);
113
+ const testId = getTestId(scenario);
114
+
115
+ if (!global.testomatioDataStore) global.testomatioDataStore = {};
116
+ global.testomatioDataStore.currentlyRunningTestId = testId;
102
117
  }
103
118
 
104
119
  onTestCaseFinished(event) {
@@ -111,15 +126,15 @@ const createTestomatFormatter = apiKey => {
111
126
 
112
127
  if (!scenario.name) return;
113
128
 
114
- let message = '';
115
- let cliMessage = `- ${scenario.name}: ${chalk.bold(status.toUpperCase())}`;
129
+ const message = '';
130
+ const cliMessage = `- ${scenario.name}: ${chalk.bold(status.toUpperCase())}`;
116
131
 
117
- if (event.result.status === 'undefined') {
118
- cliMessage += chalk.yellow(
119
- ' (undefined steps. Run Cucumber without this formatter and implement missing steps)',
120
- );
121
- message = 'Undefined steps. Implement missing steps and rerun this scenario';
122
- }
132
+ // if (event.result.status === 'undefined') {
133
+ // cliMessage += chalk.yellow(
134
+ // ' (undefined steps. Run Cucumber without this formatter and implement missing steps)',
135
+ // );
136
+ // message = 'Undefined steps. Implement missing steps and rerun this scenario';
137
+ // }
123
138
  console.log(cliMessage);
124
139
  if (status !== STATUS.PASSED && status !== STATUS.SKIPPED) {
125
140
  this.status = STATUS.FAILED;
@@ -137,4 +152,4 @@ const createTestomatFormatter = apiKey => {
137
152
  };
138
153
  };
139
154
 
140
- module.exports = createTestomatFormatter(process.env.TESTOMATIO);
155
+ module.exports = createTestomatFormatter(config.TESTOMATIO);
@@ -1,6 +1,6 @@
1
1
  try {
2
2
  // eslint-disable-next-line import/no-unresolved
3
- require.resolve("@cucumber/cucumber");
3
+ require.resolve('@cucumber/cucumber');
4
4
  // eslint-disable-next-line global-require, import/no-extraneous-dependencies
5
5
  module.exports = require('./cucumber/current');
6
6
  } catch (_e) {
@@ -11,6 +11,6 @@ try {
11
11
  module.exports = require('./cucumber/legacy');
12
12
  } catch (_err) {
13
13
  console.error('Cucumber packages: "@cucumber/cucumber" or "cucumber" were not detected. Report won\'t be sent');
14
- module.exports = {}
14
+ module.exports = {};
15
15
  }
16
16
  }
@@ -1,17 +1,19 @@
1
1
  const { STATUS } = require('../../constants');
2
- const { parseTest, parseSuite } = require('../../util');
2
+ const { parseTest, parseSuite } = require('../../utils/utils');
3
3
  const TestomatClient = require('../../client');
4
+ const config = require('../../config');
4
5
 
5
6
  const testomatioReporter = on => {
6
- if (!process.env.TESTOMATIO) {
7
+ if (!config.TESTOMATIO) {
7
8
  console.log('TESTOMATIO key is empty, ignoring reports');
8
- return
9
+ return;
9
10
  }
10
- const client = new TestomatClient({ apiKey: process.env.TESTOMATIO });
11
+ const client = new TestomatClient({ apiKey: config.TESTOMATIO });
11
12
 
12
- on('before:run', async (run) => {
13
+ on('before:run', async run => {
14
+ // TODO: looks like client.env does not exist
13
15
  if (!client.env) {
14
- client.env = `${run.browser.displayName},${run.system.osName}`
16
+ client.env = `${run.browser.displayName},${run.system.osName}`;
15
17
  }
16
18
  await client.createRun();
17
19
  });
@@ -25,8 +27,9 @@ const testomatioReporter = on => {
25
27
  const lastAttemptIndex = test.attempts.length - 1;
26
28
  const latestAttempt = test.attempts[lastAttemptIndex];
27
29
 
28
- const time = latestAttempt.duration;
29
- const error = latestAttempt.error;
30
+ // latestAttempt.duration && latestAttempt.error were available in adapters version up to 13 JFYI
31
+ const time = latestAttempt.duration || latestAttempt.wallClockDuration || test.duration;
32
+ let error = latestAttempt.error;
30
33
 
31
34
  let title = test.title.pop();
32
35
  const examples = title.match(/\(example (#\d+)\)/);
@@ -39,35 +42,59 @@ const testomatioReporter = on => {
39
42
  const testId = parseTest(title);
40
43
  const suiteId = parseSuite(suiteTitle);
41
44
 
42
- if (error) {
43
- error.inspect = function() { // eslint-disable-line func-names
44
- if (this && this.codeFrame) {
45
- return this.codeFrame.frame;
46
- }
47
- return '';
48
- }
45
+ if (!error && test.displayError) {
46
+ error = { message: test.displayError };
47
+ error.inspect = function () {
48
+ // eslint-disable-line func-names
49
+ return this.message;
50
+ };
49
51
  }
50
52
 
51
- const screenshots = results.screenshots
52
- .filter(screenshot => screenshot.path.includes(title))
53
- .filter(screenshot => screenshot.testAttemptIndex === lastAttemptIndex)
54
- .map(screenshot => screenshot.path);
53
+ const formattedError = error
54
+ ? {
55
+ message: error.message,
56
+ inspect:
57
+ error.inspect ||
58
+ function () {
59
+ return this.message;
60
+ },
61
+ }
62
+ : '';
63
+
64
+ const screenshots = Array.isArray(results.screenshots)
65
+ ? results.screenshots
66
+ .filter(screenshot => screenshot?.path && screenshot?.path.includes(title) && screenshot?.takenAt)
67
+ .map(screenshot => screenshot.path)
68
+ : [];
55
69
 
56
70
  const files = [...videos, ...screenshots];
57
71
 
58
72
  let state;
59
73
  switch (test.state) {
60
- case 'passed': state = STATUS.PASSED; break;
61
- case 'failed': state = STATUS.FAILED; break;
74
+ case 'passed':
75
+ state = STATUS.PASSED;
76
+ break;
77
+ case 'failed':
78
+ state = STATUS.FAILED;
79
+ break;
62
80
  case 'skipped':
63
- case 'pending':
81
+ case 'pending':
64
82
  default:
65
83
  state = STATUS.SKIPPED;
66
84
  }
67
85
 
68
- addSpecTestsPromises.push(client.addTestRun(state, {
69
- title, time, example, error, files, suite_title: suiteTitle, test_id: testId, suite_id: suiteId
70
- }));
86
+ addSpecTestsPromises.push(
87
+ client.addTestRun(state, {
88
+ title,
89
+ time,
90
+ example,
91
+ error: formattedError,
92
+ files,
93
+ suite_title: suiteTitle,
94
+ test_id: testId,
95
+ suite_id: suiteId,
96
+ }),
97
+ );
71
98
  }
72
99
 
73
100
  await Promise.all(addSpecTestsPromises);
@@ -1,5 +1,5 @@
1
1
  const TestomatClient = require('../client');
2
- const { parseTest, ansiRegExp } = require('../util');
2
+ const { parseTest, ansiRegExp } = require('../utils/utils');
3
3
  const { STATUS } = require('../constants');
4
4
 
5
5
  class JasmineReporter {
@@ -1,6 +1,10 @@
1
+ const chalk = require('chalk');
1
2
  const TestomatClient = require('../client');
2
- const { STATUS, TESTOMAT_TMP_STORAGE } = require('../constants');
3
- const { parseTest, ansiRegExp, fileSystem } = require('../util');
3
+ const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
4
+ const { parseTest, ansiRegExp, fileSystem } = require('../utils/utils');
5
+ const { services } = require('../services');
6
+ const debug = require('debug')('@testomatio/reporter:adapter-jest');
7
+ const path = require('path');
4
8
 
5
9
  class JestReporter {
6
10
  constructor(globalConfig, options) {
@@ -11,17 +15,24 @@ class JestReporter {
11
15
  this.client.createRun();
12
16
  }
13
17
 
14
- static getIdOfCurrentlyRunningTest() {
15
- if (!process.env.JEST_WORKER_ID) return null;
16
- // @ts-expect-error "expect" could only be defined inside Jest environement (forbidden to import it outside)
17
- return parseTest(expect?.getState()?.currentTestName) || null; // eslint-disable-line
18
- }
19
-
20
18
  onRunStart() {
21
19
  // clear tmp dir
22
- fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
20
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
21
+ }
22
+
23
+ // start of test file (including beforeAll)
24
+ onTestStart(testFile) {
25
+ debug('Start running test file:', testFile.path);
26
+ services.setContext(testFile.path);
23
27
  }
24
28
 
29
+ // start of the test (including beforeEach)
30
+ onTestCaseStart(test, testCase) {
31
+ debug('Start running test:', testCase.fullName);
32
+ services.setContext(testCase.fullName);
33
+ }
34
+
35
+ // end of test file! (there is also onTestCaseResult listener)
25
36
  onTestResult(test, testResult) {
26
37
  if (!this.client) return;
27
38
 
@@ -37,16 +48,30 @@ class JestReporter {
37
48
  steps = failureMessages[0];
38
49
  }
39
50
  const testId = parseTest(title);
51
+
52
+ // suite titles from most outer to most inner, separated by space
53
+ let fullSuiteTitle = testResult.ancestorTitles?.join(' ');
54
+ // if no suite titles, use file name
55
+ if (!fullSuiteTitle && testResult.testFilePath) fullSuiteTitle = path.basename(testResult.testFilePath);
56
+
57
+ const logs = getTestLogs(result);
58
+ const artifacts = services.artifacts.get(result.fullName);
59
+ const keyValues = services.keyValues.get(result.fullName);
60
+
40
61
  const deducedStatus = status === 'pending' ? 'skipped' : status;
41
62
  // In jest if test is not matched with test name pattern it is considered as skipped.
42
63
  // So adding a check if it is skipped for real or because of test pattern
43
64
  if (!this._globalConfig.testNamePattern || deducedStatus !== 'skipped') {
44
65
  this.client.addTestRun(deducedStatus, {
45
66
  test_id: testId,
67
+ suite_title: fullSuiteTitle,
46
68
  error,
47
69
  steps,
48
70
  title,
49
71
  time: duration,
72
+ logs,
73
+ manuallyAttachedArtifacts: artifacts,
74
+ meta: keyValues,
50
75
  });
51
76
  }
52
77
  }
@@ -58,10 +83,23 @@ class JestReporter {
58
83
  const { numFailedTests } = results;
59
84
  const status = numFailedTests === 0 ? STATUS.PASSED : STATUS.FAILED;
60
85
  this.client.updateRunStatus(status);
86
+ }
87
+ }
61
88
 
62
- // clear tmp dir
63
- fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
89
+ function getTestLogs(testResult) {
90
+ const suiteLogsArr = services.logger.getLogs(testResult.testFilePath);
91
+ const suiteLogs = suiteLogsArr ? suiteLogsArr.join('\n').trim() : '';
92
+ const testLogsArr = services.logger.getLogs(testResult.fullName);
93
+ const testLogs = testLogsArr ? testLogsArr.join('\n').trim() : '';
94
+
95
+ let logs = '';
96
+ if (suiteLogs) {
97
+ logs += `${chalk.bold('\t--- Suite ---')}\n${suiteLogs}`;
98
+ }
99
+ if (testLogs) {
100
+ logs += `\n${chalk.bold('\t--- Test ---')}\n${testLogs}`;
64
101
  }
102
+ return logs;
65
103
  }
66
104
 
67
105
  module.exports = JestReporter;