@testomatio/reporter 1.0.0-beta.4 → 1.0.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 +88 -540
- package/lib/{ArtifactStorage.js → _ArtifactStorageOld.js} +15 -4
- package/lib/adapter/codecept.js +29 -19
- package/lib/adapter/cucumber/current.js +1 -1
- package/lib/adapter/jest.js +16 -2
- package/lib/adapter/mocha.js +1 -1
- package/lib/adapter/playwright.js +1 -1
- package/lib/artifactStorage.js +25 -0
- package/lib/client.js +25 -16
- package/lib/constants.js +5 -0
- package/lib/dataStorage.js +180 -0
- package/lib/logger.js +278 -0
- package/lib/pipe/github.js +2 -2
- package/lib/pipe/testomatio.js +2 -1
- package/lib/reporter.js +10 -2
- package/lib/util.js +54 -33
- package/package.json +1 -1
- package/Changelog.md +0 -257
|
@@ -7,24 +7,29 @@ const { TESTOMAT_ARTIFACT_SUFFIX } = require('./constants');
|
|
|
7
7
|
const { specificTestInfo } = require('./util');
|
|
8
8
|
|
|
9
9
|
class ArtifactStorage {
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
constructor(params) {
|
|
12
|
-
this._tmpPrefix = "tsmt_reporter";
|
|
13
12
|
this.isFile = false;
|
|
14
13
|
this.isMemory = true;
|
|
15
14
|
this.storage = params?.toFile;
|
|
16
15
|
|
|
17
16
|
if (this.storage) {
|
|
17
|
+
// this is fine to use when you start saving artifacts;
|
|
18
|
+
// but when you need to retrieve them, you will not know if there is "isFile" param,
|
|
19
|
+
// because you need to create a new instance of ArtifactStorage inside client
|
|
20
|
+
// so the solution is make it opposite to isMemory (which will be retrieved from global)
|
|
18
21
|
this.isFile = true;
|
|
19
22
|
this.isMemory = false;
|
|
20
|
-
this.tmpDirFullpath = this.createTestomatTmpDir(
|
|
23
|
+
this.tmpDirFullpath = this.createTestomatTmpDir(ArtifactStorage._tmpPrefix);
|
|
21
24
|
debug('SAVE to tmp folder mode enabled!');
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
static async storeToFile(tmpDirName, artifact, testSuffix = "test") {
|
|
29
|
+
// this assumes to use multiple files for each test. do we really want this?
|
|
26
30
|
const suffix = TESTOMAT_ARTIFACT_SUFFIX + uuid.v4() + testSuffix;
|
|
27
31
|
const dirpath = join(os.tmpdir(), tmpDirName);
|
|
32
|
+
// why json?
|
|
28
33
|
const filepath = resolve(dirpath, `${suffix}.json`);
|
|
29
34
|
|
|
30
35
|
return fs.promises.appendFile(filepath, JSON.stringify(artifact));
|
|
@@ -34,6 +39,7 @@ class ArtifactStorage {
|
|
|
34
39
|
// TODO: mode for different pre-hooks. As variant - "all-tests" || "only_fail"
|
|
35
40
|
// const mode = process.env.TESTOMAT_ARTIFACTS || "only_fail";
|
|
36
41
|
|
|
42
|
+
// you save the artifact without specifying its source - test id
|
|
37
43
|
if (Array.isArray(global.testomatioArtifacts)) {
|
|
38
44
|
debug("Saving artifacts to global storage");
|
|
39
45
|
|
|
@@ -71,6 +77,7 @@ class ArtifactStorage {
|
|
|
71
77
|
return list;
|
|
72
78
|
}
|
|
73
79
|
|
|
80
|
+
// returns all the content from all files; do we really need it?
|
|
74
81
|
async tmpContents() {
|
|
75
82
|
const contents = [];
|
|
76
83
|
|
|
@@ -103,8 +110,9 @@ class ArtifactStorage {
|
|
|
103
110
|
}
|
|
104
111
|
}
|
|
105
112
|
|
|
113
|
+
// no need to use multiple dirs; we can implement it later if required
|
|
106
114
|
static tmpTestomatDirNames() {
|
|
107
|
-
const subname =
|
|
115
|
+
const subname = ArtifactStorage._tmpPrefix || "tsmt_reporter";
|
|
108
116
|
|
|
109
117
|
return fs.readdirSync(os.tmpdir(), { withFileTypes: true })
|
|
110
118
|
.filter((item) => item.isDirectory())
|
|
@@ -113,6 +121,7 @@ class ArtifactStorage {
|
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
cleanup() {
|
|
124
|
+
// when you do a cleanup, I know nothing about isFile param
|
|
116
125
|
if (this.isFile && this.tmpDirFullpath) {
|
|
117
126
|
const tmpDirNames = ArtifactStorage.tmpTestomatDirNames();
|
|
118
127
|
|
|
@@ -128,4 +137,6 @@ class ArtifactStorage {
|
|
|
128
137
|
}
|
|
129
138
|
}
|
|
130
139
|
|
|
140
|
+
ArtifactStorage._tmpPrefix = "tsmt_reporter";
|
|
141
|
+
|
|
131
142
|
module.exports = ArtifactStorage;
|
package/lib/adapter/codecept.js
CHANGED
|
@@ -33,6 +33,7 @@ if (MAJOR_VERSION < 3) {
|
|
|
33
33
|
function CodeceptReporter(config) {
|
|
34
34
|
let failedTests = [];
|
|
35
35
|
let videos = [];
|
|
36
|
+
let traces = [];
|
|
36
37
|
const reportTestPromises = [];
|
|
37
38
|
|
|
38
39
|
const testTimeMap = {};
|
|
@@ -54,6 +55,7 @@ function CodeceptReporter(config) {
|
|
|
54
55
|
event.dispatcher.on(event.all.before, () => {
|
|
55
56
|
recorder.add('Creating new run', () => client.createRun());
|
|
56
57
|
videos = [];
|
|
58
|
+
traces = [];
|
|
57
59
|
});
|
|
58
60
|
|
|
59
61
|
event.dispatcher.on(event.test.before, () => {
|
|
@@ -70,25 +72,13 @@ function CodeceptReporter(config) {
|
|
|
70
72
|
});
|
|
71
73
|
|
|
72
74
|
event.dispatcher.on(event.all.result, async () => {
|
|
75
|
+
debug('waiting for all tests to be reported');
|
|
73
76
|
// all tests were reported and we can upload videos
|
|
74
77
|
await Promise.all(reportTestPromises);
|
|
75
78
|
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const promises = [];
|
|
80
|
-
for (const video of videos) {
|
|
81
|
-
const { testId, title, path, type } = video;
|
|
82
|
-
const file = { path, type, title };
|
|
83
|
-
promises.push(
|
|
84
|
-
client.addTestRun(undefined, {
|
|
85
|
-
...stripExampleFromTitle(title),
|
|
86
|
-
test_id: testId,
|
|
87
|
-
files: [file],
|
|
88
|
-
}),
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
await Promise.all(promises);
|
|
79
|
+
if (upload.isArtifactsEnabled()) {
|
|
80
|
+
uploadAttachments(client, videos, '🎞️ Uploading', 'video');
|
|
81
|
+
uploadAttachments(client, traces, '📁 Uploading', 'trace');
|
|
92
82
|
}
|
|
93
83
|
|
|
94
84
|
const status = failedTests.length === 0 ? STATUS.PASSED : STATUS.FAILED;
|
|
@@ -164,11 +154,13 @@ function CodeceptReporter(config) {
|
|
|
164
154
|
files,
|
|
165
155
|
steps: output.text(),
|
|
166
156
|
}).then(pipes => {
|
|
167
|
-
testId = pipes
|
|
157
|
+
testId = pipes?.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
|
|
168
158
|
|
|
159
|
+
debug("artifacts", artifacts);
|
|
160
|
+
|
|
169
161
|
for (const aid in artifacts) {
|
|
170
|
-
if (aid.startsWith('video')) videos.push({ testId, title, path: artifacts[aid], type: 'video/webm' });
|
|
171
|
-
if (aid.startsWith('trace'))
|
|
162
|
+
if (aid.startsWith('video')) videos.push({ testId, title, path: artifacts[aid], type: 'video/webm' });
|
|
163
|
+
if (aid.startsWith('trace')) traces.push({ testId, title, path: artifacts[aid], type: 'application/zip' });
|
|
172
164
|
}
|
|
173
165
|
});
|
|
174
166
|
|
|
@@ -242,6 +234,24 @@ function CodeceptReporter(config) {
|
|
|
242
234
|
});
|
|
243
235
|
}
|
|
244
236
|
|
|
237
|
+
async function uploadAttachments(client, attachments, messagePrefix, attachmentType) {
|
|
238
|
+
if (attachments.length > 0) {
|
|
239
|
+
console.log(APP_PREFIX, `Attachments: ${messagePrefix} ${attachments.length} ${attachmentType}/-s ...`);
|
|
240
|
+
|
|
241
|
+
const promises = attachments.map(async (attachment) => {
|
|
242
|
+
const { testId, title, path, type } = attachment;
|
|
243
|
+
const file = { path, type, title };
|
|
244
|
+
return client.addTestRun(undefined, {
|
|
245
|
+
...stripExampleFromTitle(title),
|
|
246
|
+
test_id: testId,
|
|
247
|
+
files: [file],
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
await Promise.all(promises);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
245
255
|
function parseTest(tags) {
|
|
246
256
|
if (tags) {
|
|
247
257
|
for (const tag of tags) {
|
package/lib/adapter/jest.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const TestomatClient = require('../client');
|
|
2
|
-
const { STATUS } = require('../constants');
|
|
3
|
-
const { parseTest, ansiRegExp } = require('../util');
|
|
2
|
+
const { STATUS, TESTOMAT_TMP_STORAGE } = require('../constants');
|
|
3
|
+
const { parseTest, ansiRegExp, fileSystem } = require('../util');
|
|
4
4
|
|
|
5
5
|
class JestReporter {
|
|
6
6
|
constructor(globalConfig, options) {
|
|
@@ -11,6 +11,17 @@ class JestReporter {
|
|
|
11
11
|
this.client.createRun();
|
|
12
12
|
}
|
|
13
13
|
|
|
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
|
+
onRunStart() {
|
|
21
|
+
// clear tmp dir
|
|
22
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
|
|
23
|
+
}
|
|
24
|
+
|
|
14
25
|
onTestResult(test, testResult) {
|
|
15
26
|
if (!this.client) return;
|
|
16
27
|
|
|
@@ -47,6 +58,9 @@ class JestReporter {
|
|
|
47
58
|
const { numFailedTests } = results;
|
|
48
59
|
const status = numFailedTests === 0 ? STATUS.PASSED : STATUS.FAILED;
|
|
49
60
|
this.client.updateRunStatus(status);
|
|
61
|
+
|
|
62
|
+
// clear tmp dir
|
|
63
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
|
|
50
64
|
}
|
|
51
65
|
}
|
|
52
66
|
|
package/lib/adapter/mocha.js
CHANGED
|
@@ -5,7 +5,7 @@ const chalk = require('chalk');
|
|
|
5
5
|
const TestomatClient = require('../client');
|
|
6
6
|
const { STATUS } = require('../constants');
|
|
7
7
|
const { parseTest, specificTestInfo } = require('../util');
|
|
8
|
-
const ArtifactStorage = require('../
|
|
8
|
+
const ArtifactStorage = require('../_ArtifactStorageOld');
|
|
9
9
|
|
|
10
10
|
const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING } = Mocha.Runner.constants;
|
|
11
11
|
|
|
@@ -46,7 +46,7 @@ class TestomatioReporter {
|
|
|
46
46
|
steps: steps.join('\n'),
|
|
47
47
|
time: duration,
|
|
48
48
|
}).then(pipes => {
|
|
49
|
-
testId = pipes
|
|
49
|
+
testId = pipes?.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
|
|
50
50
|
|
|
51
51
|
this.uploads.push({
|
|
52
52
|
testId, title, suite_title, files: result.attachments.filter((a) => a.body || a.path)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// const debug = require('debug')('@testomatio/reporter:logger');
|
|
2
|
+
const { DataStorage } = require('./dataStorage');
|
|
3
|
+
|
|
4
|
+
class ArtifactStorage {
|
|
5
|
+
constructor(params = {}) {
|
|
6
|
+
this.dataStorage = new DataStorage('artifact', params);
|
|
7
|
+
|
|
8
|
+
// singleton
|
|
9
|
+
if (!ArtifactStorage.instance) {
|
|
10
|
+
ArtifactStorage.instance = this;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
save(data, context = null) {
|
|
15
|
+
this.dataStorage.putData(data, context);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get(context) {
|
|
19
|
+
this.dataStorage.getData(context);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
ArtifactStorage.instance = null;
|
|
24
|
+
|
|
25
|
+
module.exports = new ArtifactStorage();
|
package/lib/client.js
CHANGED
|
@@ -7,10 +7,12 @@ const { randomUUID } = require('crypto');
|
|
|
7
7
|
const upload = require('./fileUploader');
|
|
8
8
|
const { APP_PREFIX } = require('./constants');
|
|
9
9
|
const pipesFactory = require('./pipe');
|
|
10
|
+
const logger = require('./logger');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* @typedef {import('../types').TestData} TestData
|
|
13
14
|
* @typedef {import('../types').RunStatus} RunStatus
|
|
15
|
+
* @typedef {import('../types').PipeResult} PipeResult
|
|
14
16
|
*/
|
|
15
17
|
|
|
16
18
|
class Client {
|
|
@@ -33,11 +35,11 @@ class Client {
|
|
|
33
35
|
/**
|
|
34
36
|
* Used to create a new Test run
|
|
35
37
|
*
|
|
36
|
-
* @returns {Promise<
|
|
38
|
+
* @returns {Promise<any>} - resolves to Run id which should be used to update / add test
|
|
37
39
|
*/
|
|
38
40
|
createRun() {
|
|
39
41
|
// all pipes disabled, skipping
|
|
40
|
-
if (!this.pipes.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
42
|
+
if (!this.pipes || !this.pipes.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
41
43
|
|
|
42
44
|
const runParams = {
|
|
43
45
|
title: this.title,
|
|
@@ -46,6 +48,7 @@ class Client {
|
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
global.testomatioArtifacts = [];
|
|
51
|
+
// TODO: create global storage
|
|
49
52
|
|
|
50
53
|
this.queue = this.queue
|
|
51
54
|
.then(() => Promise.all(this.pipes.map(p => p.createRun(runParams))))
|
|
@@ -57,20 +60,21 @@ class Client {
|
|
|
57
60
|
|
|
58
61
|
/**
|
|
59
62
|
* Updates test status and its data
|
|
60
|
-
*
|
|
63
|
+
*
|
|
61
64
|
* @param {string|undefined} status
|
|
62
65
|
* @param {TestData} [testData]
|
|
63
66
|
* @param {string[]} [storeArtifacts]
|
|
64
|
-
* @returns {Promise<
|
|
67
|
+
* @returns {Promise<PipeResult[]>}
|
|
65
68
|
*/
|
|
66
69
|
async addTestRun(status, testData, storeArtifacts = []) {
|
|
67
70
|
// all pipes disabled, skipping
|
|
68
|
-
if (!this.pipes
|
|
71
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) return;
|
|
69
72
|
|
|
70
|
-
if (!testData)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
if (!testData)
|
|
74
|
+
testData = {
|
|
75
|
+
title: 'Unknown test',
|
|
76
|
+
suite_title: 'Unknown suite',
|
|
77
|
+
};
|
|
74
78
|
|
|
75
79
|
const {
|
|
76
80
|
error = null,
|
|
@@ -99,13 +103,18 @@ class Client {
|
|
|
99
103
|
stack = this.formatSteps(stack, steps);
|
|
100
104
|
}
|
|
101
105
|
|
|
106
|
+
const testLogs = logger.getLogs(test_id);
|
|
107
|
+
debug(`Test logs for ${test_id}:\n`, testLogs);
|
|
108
|
+
if (stack) stack += '\n\n';
|
|
109
|
+
stack += testLogs;
|
|
110
|
+
|
|
102
111
|
if (Array.isArray(storeArtifacts) && storeArtifacts.length) {
|
|
103
|
-
debug(
|
|
112
|
+
debug('CLIENT storeArtifact', storeArtifacts);
|
|
104
113
|
files.push(...storeArtifacts);
|
|
105
114
|
}
|
|
106
115
|
|
|
107
116
|
if (Array.isArray(global.testomatioArtifacts)) {
|
|
108
|
-
debug(
|
|
117
|
+
debug('CLIENT global[testomatioArtifacts]', global.testomatioArtifacts);
|
|
109
118
|
files.push(...global.testomatioArtifacts);
|
|
110
119
|
global.testomatioArtifacts = [];
|
|
111
120
|
}
|
|
@@ -147,7 +156,7 @@ class Client {
|
|
|
147
156
|
const result = await p.addTest(data);
|
|
148
157
|
return { pipe: p.toString(), result };
|
|
149
158
|
} catch (err) {
|
|
150
|
-
console.log(APP_PREFIX,
|
|
159
|
+
console.log(APP_PREFIX, p.toString(), err);
|
|
151
160
|
}
|
|
152
161
|
}
|
|
153
162
|
)));
|
|
@@ -160,19 +169,19 @@ class Client {
|
|
|
160
169
|
* Updates the status of the current test run and finishes the run.
|
|
161
170
|
* @param {RunStatus} status - The status of the current test run. Must be one of "passed", "failed", or "finished"
|
|
162
171
|
* @param {boolean} [isParallel] - Whether the current test run was executed in parallel with other tests.
|
|
163
|
-
* @returns {Promise<
|
|
172
|
+
* @returns {Promise<any>} - A Promise that resolves when finishes the run.
|
|
164
173
|
*/
|
|
165
174
|
updateRunStatus(status, isParallel = false) {
|
|
166
175
|
// all pipes disabled, skipping
|
|
167
|
-
if (!this.pipes
|
|
176
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
168
177
|
|
|
169
178
|
const runParams = { status, parallel: isParallel };
|
|
170
179
|
|
|
171
180
|
this.queue = this.queue
|
|
172
181
|
.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
|
|
173
182
|
.then(() => {
|
|
174
|
-
debug(
|
|
175
|
-
|
|
183
|
+
debug('TOTAL uploaded files', this.totalUploaded);
|
|
184
|
+
|
|
176
185
|
if (upload.isArtifactsEnabled() && this.totalUploaded > 0) {
|
|
177
186
|
console.log(
|
|
178
187
|
APP_PREFIX,
|
package/lib/constants.js
CHANGED
|
@@ -3,6 +3,10 @@ const chalk = require('chalk');
|
|
|
3
3
|
const APP_PREFIX = chalk.gray('[TESTOMATIO]');
|
|
4
4
|
const TESTOMAT_ARTIFACT_SUFFIX = "testomatio_artifact_";
|
|
5
5
|
|
|
6
|
+
const TESTOMAT_TMP_STORAGE = {
|
|
7
|
+
mainDir: "testomatio_tmp",
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
const CSV_HEADERS = [
|
|
7
11
|
{ id: 'suite_title', title: 'Suite_title' },
|
|
8
12
|
{ id: 'title', title: 'Title' },
|
|
@@ -21,6 +25,7 @@ const STATUS = {
|
|
|
21
25
|
module.exports = {
|
|
22
26
|
APP_PREFIX,
|
|
23
27
|
TESTOMAT_ARTIFACT_SUFFIX,
|
|
28
|
+
TESTOMAT_TMP_STORAGE,
|
|
24
29
|
CSV_HEADERS,
|
|
25
30
|
STATUS,
|
|
26
31
|
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
const debug = require('debug')('@testomatio/reporter:storage');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const { join } = require('path');
|
|
6
|
+
const JestReporter = require('./adapter/jest');
|
|
7
|
+
const { TESTOMAT_TMP_STORAGE } = require('./constants');
|
|
8
|
+
const { fileSystem } = require('./util');
|
|
9
|
+
|
|
10
|
+
class DataStorage {
|
|
11
|
+
/**
|
|
12
|
+
* Creates data storage instance for specific data type.
|
|
13
|
+
* Stores data to global variable or to file depending on what is applicable for current test runner.
|
|
14
|
+
* dataType: 'log' | 'artifact' | ...
|
|
15
|
+
* @param {*} dataType storage type (like 'log', 'artifact'); could be any string, used only to define file path
|
|
16
|
+
* @param {*} params
|
|
17
|
+
*/
|
|
18
|
+
constructor(dataType, params = {}) {
|
|
19
|
+
if (!dataType) throw new Error('Data type is required when creating data storage');
|
|
20
|
+
this.dataType = dataType || 'data';
|
|
21
|
+
this.dataDirName = `${dataType}s`;
|
|
22
|
+
this.isFileStorage = params?.isFileStorage ?? !global.testomatioDataStore;
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
FYI:
|
|
26
|
+
If this storage instance is used within test runner, it works fine.
|
|
27
|
+
But if, for example, we create storage instance inside the testomatio client (to get stored data),
|
|
28
|
+
the environment could be different (not already a test runner env).
|
|
29
|
+
Thus, checking environment is only reasonable when you put data to starage
|
|
30
|
+
and potentially useless when get data from storage
|
|
31
|
+
*/
|
|
32
|
+
this.runningEnvironment = this.getRunningEnviroment();
|
|
33
|
+
if (this.runningEnvironment === 'jest') this.isFileStorage = true;
|
|
34
|
+
|
|
35
|
+
if (this.isFileStorage) {
|
|
36
|
+
this.dataDirPath = path.join(TESTOMAT_TMP_STORAGE.mainDir, this.dataDirName);
|
|
37
|
+
fileSystem.createDir(this.dataDirPath);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
debug(`Data storage mode: ${this.isFileStorage ? 'file' : 'memory'}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// TODO: implement
|
|
44
|
+
getTestIdFromContext(context) { // eslint-disable-line
|
|
45
|
+
// eslint-disable-line
|
|
46
|
+
// return testId;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Try to define the running environment. Not 100% reliable and used as additional check
|
|
51
|
+
* @returns jest | mocha | ...
|
|
52
|
+
*/
|
|
53
|
+
getRunningEnviroment() {
|
|
54
|
+
if (process.env.JEST_WORKER_ID) return 'jest';
|
|
55
|
+
try {
|
|
56
|
+
// @ts-expect-error mocha is defined only in mocha environment
|
|
57
|
+
if (typeof mocha !== 'undefined') return 'mocha';
|
|
58
|
+
} catch (e) {
|
|
59
|
+
// ignore
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Puts any data to storage (file or global variable)
|
|
66
|
+
* @param {*} data anything you want to store
|
|
67
|
+
* @param {*} context could be testId or any context (test, suite, etc) which is used to define testId
|
|
68
|
+
* @returns
|
|
69
|
+
*/
|
|
70
|
+
putData(data, context = null) {
|
|
71
|
+
// this.isFileStorage = !global.testomatioDataStore;
|
|
72
|
+
|
|
73
|
+
let testId = null;
|
|
74
|
+
if (typeof context === 'string') {
|
|
75
|
+
testId = context;
|
|
76
|
+
} else {
|
|
77
|
+
// TODO: derive testId from context
|
|
78
|
+
// testId = context...
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// try to get testId for Jest
|
|
82
|
+
if (this.runningEnvironment === 'jest') testId = testId ?? JestReporter.getIdOfCurrentlyRunningTest();
|
|
83
|
+
|
|
84
|
+
// if testId is not provided, data is be saved to `{dataType}_other` file;
|
|
85
|
+
if (!testId) testId = 'other';
|
|
86
|
+
|
|
87
|
+
if (this.isFileStorage) {
|
|
88
|
+
this._putDataToFile(data, testId);
|
|
89
|
+
} else {
|
|
90
|
+
this._putDataToGlobalVar(data, testId);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns data, stored for specific testId (or data which was stored without test id specified)
|
|
96
|
+
*
|
|
97
|
+
* (Don't try to guess the execution environment (e.g. test runner) inside this method, it could be any)
|
|
98
|
+
* @param {*} context could be testId or any context (test, suite, etc) which is used to define testId
|
|
99
|
+
* @returns
|
|
100
|
+
*/
|
|
101
|
+
getData(context) {
|
|
102
|
+
let testId = null;
|
|
103
|
+
if (typeof context === 'string') {
|
|
104
|
+
testId = context;
|
|
105
|
+
} else {
|
|
106
|
+
// TODO: derive testId from context
|
|
107
|
+
// testId = context...
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!testId) {
|
|
111
|
+
debug(`Cannot get test id from passed context:\n}`, context);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (this.isFileStorage) {
|
|
115
|
+
return this._getDataFromFile(testId);
|
|
116
|
+
}
|
|
117
|
+
if (global?.testomatioDataStore) {
|
|
118
|
+
return this._getDataFromGlobalVar(testId);
|
|
119
|
+
}
|
|
120
|
+
debug(`No ${this.dataType} data for test id ${testId} in both file and global variable`);
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
_getDataFromGlobalVar(testId) {
|
|
125
|
+
try {
|
|
126
|
+
if (global?.testomatioDataStore[this.dataDirName]) {
|
|
127
|
+
const testData = global.testomatioDataStore[this.dataDirName][testId];
|
|
128
|
+
debug(`Data for test id ${testId}:\n${testData}`);
|
|
129
|
+
return testData;
|
|
130
|
+
}
|
|
131
|
+
debug(`No ${this.dataType} data for test id ${testId}`);
|
|
132
|
+
return null;
|
|
133
|
+
} catch (e) {
|
|
134
|
+
// there could be no data, ignore
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
_getDataFromFile(testId) {
|
|
139
|
+
try {
|
|
140
|
+
const filepath = join(TESTOMAT_TMP_STORAGE.mainDir, `${this.dataType}_${testId}`);
|
|
141
|
+
if (fs.existsSync(filepath)) {
|
|
142
|
+
const testData = fs.readFileSync(filepath, 'utf-8');
|
|
143
|
+
debug(`Data for test id ${testId}:\n${testData}`);
|
|
144
|
+
return testData;
|
|
145
|
+
}
|
|
146
|
+
debug(`No ${this.dataType} data for test id ${testId}`);
|
|
147
|
+
return null;
|
|
148
|
+
} catch (e) {
|
|
149
|
+
// there could be no data, ignore
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
_putDataToGlobalVar(data, testId) {
|
|
155
|
+
debug('Saving data to global variable for test', testId, ':\n', data, '\n');
|
|
156
|
+
global.testomatioDataStore[this.dataDirName] = {};
|
|
157
|
+
global.testomatioDataStore[this.dataDirName][testId] = data;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
_putDataToFile(data, testId) {
|
|
161
|
+
if (typeof data !== 'string') data = JSON.stringify(data);
|
|
162
|
+
const filename = `${this.dataType}_${testId}`;
|
|
163
|
+
const filepath = join(TESTOMAT_TMP_STORAGE.mainDir, filename);
|
|
164
|
+
debug('Saving data to file for test', testId, 'to', filepath, ':\n', data, '\n');
|
|
165
|
+
|
|
166
|
+
// TODO: handle multiple invocations of JSON.stringify.
|
|
167
|
+
// UPD: not actual because decided not to use it - it created extra wrapping quotes "" and // (escaped slashes)
|
|
168
|
+
fs.appendFileSync(filepath, data + os.EOL, 'utf-8');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports.DataStorage = DataStorage;
|
|
173
|
+
|
|
174
|
+
// TODO: consider using fs promises instead of writeSync/appendFileSync to
|
|
175
|
+
// prevent blocking and improve performance (probably queue usage will be required)
|
|
176
|
+
|
|
177
|
+
// TODO: rewrite client - everything regarding storing artifacts
|
|
178
|
+
// TODO: try to define adapter inside client
|
|
179
|
+
// TODO: use .env
|
|
180
|
+
// TODO: ability to intercept multiple loggers
|