@testomatio/reporter 1.2.3-beta → 1.2.3-beta-batch-upload

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 (48) hide show
  1. package/README.md +61 -54
  2. package/lib/adapter/codecept.js +96 -35
  3. package/lib/adapter/cucumber/current.js +23 -16
  4. package/lib/adapter/cucumber/legacy.js +14 -13
  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 -16
  9. package/lib/adapter/mocha.js +103 -51
  10. package/lib/adapter/playwright.js +80 -27
  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 +192 -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 +220 -62
  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} +144 -12
  42. package/lib/xmlReader.js +215 -122
  43. package/package.json +18 -7
  44. package/lib/_ArtifactStorageOld.js +0 -142
  45. package/lib/artifactStorage.js +0 -25
  46. package/lib/dataStorage.js +0 -241
  47. package/lib/helpers.js +0 -34
  48. package/lib/logger.js +0 -293
@@ -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,22 +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
- try {
17
- // @ts-expect-error "expect" could only be defined inside Jest environement (forbidden to import it outside)
18
- // eslint-disable-next-line no-undef
19
- if (expect && expect?.getState()?.currentTestName) return parseTest(expect?.getState()?.currentTestName);
20
- } catch (e) {
21
- return null;
22
- }
23
- }
24
-
25
18
  onRunStart() {
26
19
  // clear tmp dir
27
- 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);
27
+ }
28
+
29
+ // start of the test (including beforeEach)
30
+ onTestCaseStart(test, testCase) {
31
+ debug('Start running test:', testCase.fullName);
32
+ services.setContext(testCase.fullName);
28
33
  }
29
34
 
35
+ // end of test file! (there is also onTestCaseResult listener)
30
36
  onTestResult(test, testResult) {
31
37
  if (!this.client) return;
32
38
 
@@ -42,16 +48,30 @@ class JestReporter {
42
48
  steps = failureMessages[0];
43
49
  }
44
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
+
45
61
  const deducedStatus = status === 'pending' ? 'skipped' : status;
46
62
  // In jest if test is not matched with test name pattern it is considered as skipped.
47
63
  // So adding a check if it is skipped for real or because of test pattern
48
64
  if (!this._globalConfig.testNamePattern || deducedStatus !== 'skipped') {
49
65
  this.client.addTestRun(deducedStatus, {
50
66
  test_id: testId,
67
+ suite_title: fullSuiteTitle,
51
68
  error,
52
69
  steps,
53
70
  title,
54
71
  time: duration,
72
+ logs,
73
+ manuallyAttachedArtifacts: artifacts,
74
+ meta: keyValues,
55
75
  });
56
76
  }
57
77
  }
@@ -63,10 +83,23 @@ class JestReporter {
63
83
  const { numFailedTests } = results;
64
84
  const status = numFailedTests === 0 ? STATUS.PASSED : STATUS.FAILED;
65
85
  this.client.updateRunStatus(status);
86
+ }
87
+ }
66
88
 
67
- // clear tmp dir
68
- 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}`;
69
101
  }
102
+ return logs;
70
103
  }
71
104
 
72
105
  module.exports = JestReporter;
@@ -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,54 +3,61 @@ 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, TESTOMAT_TMP_STORAGE } = 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, fileSystem } = require('../util');
10
- const { setCurrentlyRunningTestId } = require('../helpers');
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');
11
13
 
12
14
  const reportTestPromises = [];
13
15
 
14
- class TestomatioReporter {
16
+ class PlaywrightReporter {
15
17
  constructor(config = {}) {
16
18
  this.client = new TestomatioClient({ apiKey: config?.apiKey });
17
19
 
18
20
  this.uploads = [];
19
21
  }
20
22
 
21
- onBegin(_config, suite) {
23
+ onBegin(config, suite) {
24
+ // clean data storage
25
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
22
26
  if (!this.client) return;
23
27
  this.suite = suite;
28
+ this.config = config;
24
29
  this.client.createRun();
25
-
26
- // for data storage
27
- // fileSystem.createDir(TESTOMAT_TMP_STORAGE.mainDir);
28
30
  }
29
31
 
30
- onTestBegin(test, result) {
31
- const testId = parseTest(test.title);
32
- setCurrentlyRunningTestId(testId);
33
- // global.testomatioDataStore.currentlyRunningTestId = testId;
34
- // TestomatioReporter.currentlyRunningTestId = testId;
32
+ onTestBegin(testInfo) {
33
+ const fullTestTitle = getTestContextName(testInfo);
34
+ dataStorage.setContext(fullTestTitle);
35
35
  }
36
36
 
37
37
  onTestEnd(test, result) {
38
- // reset currently running test id
39
- // setCurrentlyRunningTestId('');
40
38
  if (!this.client) return;
41
39
 
42
- let testId = parseTest(test.title);
43
-
44
40
  const { title } = test;
41
+
42
+ let testId = parseTest(title);
43
+
45
44
  const { error, duration } = result;
46
45
 
47
- const suite_title = test.parent ? test.parent.title : null;
46
+ const suite_title = test.parent ? test.parent?.title : path.basename(test?.location?.file);
48
47
 
49
48
  const steps = [];
50
49
  for (const step of result.steps) {
51
50
  appendStep(step, steps);
52
51
  }
53
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
+
54
61
  const reportTestPromise = this.client
55
62
  .addTestRun(checkStatus(result.status), {
56
63
  error,
@@ -59,21 +66,44 @@ class TestomatioReporter {
59
66
  title,
60
67
  steps: steps.join('\n'),
61
68
  time: duration,
69
+ logs,
70
+ manuallyAttachedArtifacts,
71
+ meta: keyValues,
72
+ file: test.location?.file,
62
73
  })
63
74
  .then(pipes => {
64
- testId = pipes.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
75
+ testId = pipes?.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
65
76
 
66
77
  this.uploads.push({
67
78
  testId,
68
79
  title,
69
80
  suite_title,
70
81
  files: result.attachments.filter(a => a.body || a.path),
82
+ file: test.location?.file,
71
83
  });
84
+ // remove empty uploads
85
+ this.uploads = this.uploads.filter(upload => upload.files.length);
72
86
  });
73
87
 
74
88
  reportTestPromises.push(reportTestPromise);
75
89
  }
76
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
+
77
107
  async onEnd(result) {
78
108
  if (!this.client) return;
79
109
 
@@ -87,13 +117,11 @@ class TestomatioReporter {
87
117
  for (const upload of this.uploads) {
88
118
  const { title, testId, suite_title } = upload;
89
119
 
90
- const files = upload.files.map(attachment => {
91
- if (attachment.body) {
92
- const fileName = tmpFile();
93
- fs.writeFileSync(fileName, attachment.body);
94
- }
95
- return { path: attachment.path, title, type: attachment.contentType };
96
- });
120
+ const files = upload.files.map(attachment => ({
121
+ path: this.#getArtifactPath(attachment),
122
+ title,
123
+ type: attachment.contentType,
124
+ }));
97
125
 
98
126
  promises.push(
99
127
  this.client.addTestRun(undefined, {
@@ -101,6 +129,7 @@ class TestomatioReporter {
101
129
  title,
102
130
  suite_title,
103
131
  files,
132
+ file: upload.file,
104
133
  }),
105
134
  );
106
135
  }
@@ -140,4 +169,28 @@ function tmpFile(prefix = 'tmp.') {
140
169
  return path.join(tmpdir, prefix + crypto.randomBytes(16).toString('hex'));
141
170
  }
142
171
 
143
- 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) {