@testomatio/reporter 1.2.0-beta-3 → 1.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "1.2.0-beta-3",
3
+ "version": "1.2.0",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "main": "./lib/reporter.js",
6
6
  "typings": "typings/index.d.ts",
@@ -12,7 +12,8 @@
12
12
  "@aws-sdk/lib-storage": "^3.279.0",
13
13
  "@octokit/rest": "^19.0.5",
14
14
  "aws-sdk": "^2.1072.0",
15
- "axios": "^0.25.0",
15
+ "axios": "^1.6.2",
16
+ "axios-retry": "^3.9.1",
16
17
  "callsite-record": "^4.1.4",
17
18
  "chalk": "^4.1.0",
18
19
  "commander": "^4.1.1",
@@ -30,6 +31,7 @@
30
31
  "lodash.memoize": "^4.1.2",
31
32
  "lodash.merge": "^4.6.2",
32
33
  "minimatch": "^9.0.3",
34
+ "promise-retry": "^2.0.1",
33
35
  "uuid": "^9.0.0"
34
36
  },
35
37
  "files": [
@@ -51,7 +53,7 @@
51
53
  "test:adapter:jasmine:example": "./tests/adapter/examples/jasmine/passReporterOpts.sh && jasmine './tests/adapter/examples/jasmine/index.test.js' --reporter=./../../../lib/adapter/jasmine.js",
52
54
  "test:adapter:codecept:example": "codeceptjs run --config='./tests/adapter/examples/codecept/codecept.conf.js'",
53
55
  "test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js",
54
- "test:storage": "npx mocha ./tests-storage/**"
56
+ "test:storage": "npx mocha tests-storage/artifact-storage.test.js && npx mocha tests-storage/data-storage.test.js && TESTOMATIO_INTERCEPT_CONSOLE_LOGS=true npx mocha tests-storage/logger.test.js && npx mocha tests-storage/logger-2.test.js && npx mocha tests-storage/reporter-functions.test.js"
55
57
  },
56
58
  "devDependencies": {
57
59
  "@cucumber/cucumber": "^9.3.0",
@@ -66,6 +68,7 @@
66
68
  "eslint-plugin-import": "^2.25.4",
67
69
  "jasmine": "^3.10.0",
68
70
  "jest": "^27.4.7",
71
+ "jsdom": "^22.1.0",
69
72
  "mocha": "^9.2.0",
70
73
  "mock-http-server": "^1.4.5",
71
74
  "pino": "^8.15.0",
@@ -1,142 +0,0 @@
1
- const debug = require('debug')('@testomatio/reporter:storage');
2
- const { join, resolve } = require('path');
3
- const fs = require('fs');
4
- const os = require('os');
5
- const uuid = require('uuid');
6
- const { TESTOMAT_ARTIFACT_SUFFIX } = require('./constants');
7
- const { specificTestInfo } = require('./utils/utils');
8
-
9
- class ArtifactStorage {
10
-
11
- constructor(params) {
12
- this.isFile = false;
13
- this.isMemory = true;
14
- this.storage = params?.toFile;
15
-
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)
21
- this.isFile = true;
22
- this.isMemory = false;
23
- this.tmpDirFullpath = this.createTestomatTmpDir(ArtifactStorage._tmpPrefix);
24
- debug('SAVE to tmp folder mode enabled!');
25
- }
26
- }
27
-
28
- static async storeToFile(tmpDirName, artifact, testSuffix = "test") {
29
- // this assumes to use multiple files for each test. do we really want this?
30
- const suffix = TESTOMAT_ARTIFACT_SUFFIX + uuid.v4() + testSuffix;
31
- const dirpath = join(os.tmpdir(), tmpDirName);
32
- // why json?
33
- const filepath = resolve(dirpath, `${suffix}.json`);
34
-
35
- return fs.promises.appendFile(filepath, JSON.stringify(artifact));
36
- }
37
-
38
- static async artifact(artifact, context) {
39
- // TODO: mode for different pre-hooks. As variant - "all-tests" || "only_fail"
40
- // const mode = process.env.TESTOMAT_ARTIFACTS || "only_fail";
41
-
42
- // you save the artifact without specifying its source - test id
43
- if (Array.isArray(global.testomatioArtifacts)) {
44
- debug("Saving artifacts to global storage");
45
-
46
- global.testomatioArtifacts.push(artifact);
47
- }
48
-
49
- if (global?.testomatioArtifacts === undefined) {
50
- const tmpDirNames = ArtifactStorage.tmpTestomatDirNames();
51
-
52
- const testSuffix = specificTestInfo(context.test);
53
-
54
- if (Array.isArray(tmpDirNames) && tmpDirNames.length) {
55
- debug("Saving artifacts to memory tmp folder");
56
-
57
- return ArtifactStorage.storeToFile(tmpDirNames[tmpDirNames.length - 1], artifact, testSuffix);
58
- }
59
- }
60
- }
61
-
62
- async artifactByTestName(test) {
63
- const list = [];
64
-
65
- if (this.isFile && this.tmpDirFullpath) {
66
- const files = fs.readdirSync(this.tmpDirFullpath);
67
-
68
- for (const file of files) {
69
- if (file.includes(test)) {
70
- const buff = await fs.promises.readFile(`${this.tmpDirFullpath }/${ file}`);
71
-
72
- list.push(JSON.parse(buff.toString()));
73
- }
74
- }
75
- }
76
-
77
- return list;
78
- }
79
-
80
- // returns all the content from all files; do we really need it?
81
- async tmpContents() {
82
- const contents = [];
83
-
84
- if (this.isFile && this.tmpDirFullpath) {
85
- const files = fs.readdirSync(this.tmpDirFullpath);
86
-
87
- for (const file of files) {
88
- const buff = await fs.promises.readFile(`${this.tmpDirFullpath }/${ file}`);
89
- const content = buff.toString();
90
-
91
- contents.push(content);
92
- }
93
- }
94
-
95
- return contents;
96
- }
97
-
98
- createTestomatTmpDir(clientPrefix) {
99
- return fs.mkdtempSync(join(os.tmpdir(), clientPrefix));
100
- }
101
-
102
- clearTmpDirByName(name) {
103
- const tmpDirPath = join(os.tmpdir(), name);
104
-
105
- if (fs.existsSync(tmpDirPath)) {
106
- fs.rmSync(tmpDirPath, { recursive: true });
107
- debug(` Testomat tmpDir = ${tmpDirPath} was deleted successfully!`);
108
-
109
-
110
- }
111
- }
112
-
113
- // no need to use multiple dirs; we can implement it later if required
114
- static tmpTestomatDirNames() {
115
- const subname = ArtifactStorage._tmpPrefix || "tsmt_reporter";
116
-
117
- return fs.readdirSync(os.tmpdir(), { withFileTypes: true })
118
- .filter((item) => item.isDirectory())
119
- .map((item) => item.name)
120
- .filter((name) => name.includes(subname));
121
- }
122
-
123
- cleanup() {
124
- // when you do a cleanup, I know nothing about isFile param
125
- if (this.isFile && this.tmpDirFullpath) {
126
- const tmpDirNames = ArtifactStorage.tmpTestomatDirNames();
127
-
128
- if (Array.isArray(tmpDirNames) && tmpDirNames.length) {
129
- for (const name of tmpDirNames) {
130
- this.clearTmpDirByName(name);
131
- }
132
- }
133
- }
134
- else {
135
- debug("The tmp folder has not been created! Nothing to delete");
136
- }
137
- }
138
- }
139
-
140
- ArtifactStorage._tmpPrefix = "tsmt_reporter";
141
-
142
- module.exports = ArtifactStorage;
@@ -1,25 +0,0 @@
1
- // const debug = require('debug')('@testomatio/reporter:logger');
2
- const { DataStorage } = require('./dataStorage');
3
-
4
- class ArtifactStorage {
5
- constructor() {
6
- this.dataStorage = new DataStorage('artifact');
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();
@@ -1,232 +0,0 @@
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('./utils/utils');
9
- const getTestIdFromTestTitle = require('./utils/utils').parseTest;
10
-
11
- class DataStorage {
12
- /**
13
- * Creates data storage instance for specific data type.
14
- * Stores data to global variable or to file depending on what is applicable for current test runner
15
- * (running environment).
16
- * @param {*} dataType storage type (like 'log', 'artifact'); could be any string, used only to define file path
17
- */
18
- constructor(dataType) {
19
- this.dataType = dataType || 'data';
20
- this.isFileStorage = true;
21
- this.#refreshStorageType();
22
-
23
- this.dataDirPath = path.join(TESTOMAT_TMP_STORAGE.mainDir, this.dataType);
24
- fileSystem.createDir(this.dataDirPath);
25
- }
26
-
27
- /**
28
- * Try to define the running environment. Not 100% reliable and used as additional check
29
- * @returns jest | mocha | ...
30
- */
31
- getRunningEnviroment() {
32
- // jest
33
- if (process.env.JEST_WORKER_ID) return 'jest';
34
-
35
- if (global.codeceptjs) return 'codeceptjs';
36
-
37
- // 'cucumber:current', 'cucumber:legacy'
38
- if (global.testomatioRunningEnvironment) return global.testomatioRunningEnvironment;
39
-
40
- if (process.env.PLAYWRIGHT_TEST_BASE_URL || process.env.TEST_WORKER_INDEX) return 'playwright';
41
-
42
- // mocha - can't detect
43
- return null;
44
- }
45
-
46
- /**
47
- * Puts any data to storage (file or global variable)
48
- * @param {*} data anything you want to store
49
- * @param {*} context could be testId or any context (test, suite, etc) which is used to define testId
50
- * @returns
51
- */
52
- putData(data, context = null) {
53
- this.#refreshStorageType();
54
-
55
- let testId = this.#tryToRetrieveTestId(context) || null;
56
-
57
- if (this.runningEnvironment === 'codeceptjs') {
58
- this.isFileStorage = false;
59
- testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
60
- }
61
-
62
- if (this.runningEnvironment === 'jest') {
63
- testId = testId ?? JestReporter.getIdOfCurrentlyRunningTest();
64
- }
65
-
66
- // logs in playwright are gathered by pw framework itself
67
- if (this.runningEnvironment === 'playwright' && this.dataType === 'log') return;
68
-
69
- // get id from global store
70
- if (global.testomatioDataStore && global.testomatioDataStore.currentlyRunningTestId)
71
- testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
72
-
73
- if (!testId && global?.currentlyRunningTestTitle)
74
- testId = this.#tryToRetrieveTestId(global.currentlyRunningTestTitle);
75
-
76
- if (!testId) {
77
- debug(`No test id provided for ${this.dataType} data: ${data}`);
78
- return;
79
- }
80
-
81
- if (this.isFileStorage) {
82
- this.#putDataToFile(data, testId);
83
- } else {
84
- this.#putDataToGlobalVar(data, testId);
85
- }
86
- }
87
-
88
- /**
89
- * Returns data, stored for specific testId (or data which was stored without test id specified).
90
- * This method will get data from global variable. But if it is not available, it will try to get data from file.
91
- * Thus, good approach is to remove file storage folder before each test run (and after, for sure).
92
- *
93
- * Defining the execution environment is not guaranteed! Is used only as additional check.
94
- * @param {*} context could be testId or any context (test, suite, etc) which is used to define testId
95
- * @returns
96
- */
97
- getData(context) {
98
- this.#refreshStorageType();
99
-
100
- let testId = null;
101
- if (typeof context === 'string') {
102
- testId = this.#tryToRetrieveTestId(context) || context;
103
- } else {
104
- // TODO: derive testId from context
105
- // testId = context...
106
- }
107
-
108
- if (!testId) {
109
- debug(`Cannot get test id from passed context:`, context);
110
- return null;
111
- }
112
-
113
- let testData = '';
114
-
115
- if (global?.testomatioDataStore) {
116
- testData = this.#getDataFromGlobalVar(testId);
117
- // these frameworks use global variable storage
118
- if (testData && ['cucumber', 'codeceptjs'].includes(this.runningEnvironment)) return testData;
119
- }
120
-
121
- /* condition is removed for mocha
122
- mocha has created a global storage, but just in some cases, generally it is not available
123
- */
124
- // if (this.isFileStorage || !testData) {
125
- // testData = this.#getDataFromFile(testId);
126
- // if (testData) return testData;
127
- // }
128
-
129
- const testDataFromFile = this.#getDataFromFile(testId);
130
- testData += testDataFromFile || '';
131
-
132
- debug(`No ${this.dataType} data for test id ${testId} in both file and global variable`);
133
- return testData || '';
134
- }
135
-
136
- /**
137
- * This method is named as "try" because it does not guarantee that test id could be retrieved.
138
- * Context could be anything (test, suite, string, etc) which is used to define testId.
139
- * Or it could represent any other entity (which does not contain test id).
140
- * @param {*} context
141
- */
142
- #tryToRetrieveTestId(context) {
143
- if (!context) return null;
144
- this.#refreshStorageType();
145
-
146
- if (this.runningEnvironment === 'playwright' || context?.title) {
147
- // context is testInfo
148
- const testId = getTestIdFromTestTitle(context.title);
149
- if (testId) return testId;
150
- }
151
-
152
- if (typeof context === 'string') {
153
- const testId = getTestIdFromTestTitle(context);
154
- if (testId) return testId;
155
- }
156
-
157
- return null;
158
- }
159
-
160
- /**
161
- * Refreshes storage type (file or global variable) depending on current environment
162
- * This method should be run on each attempt to put or get data from/to storage
163
- * Because storage instance is created before the test runner is started. And storage type could be changed
164
- */
165
- #refreshStorageType() {
166
- this.runningEnvironment = this.getRunningEnviroment();
167
-
168
- // some test frameworks do not persist global variables, thus file storage is used for them (by default)
169
- if (['playwright', 'codeceptjs'].includes(this.runningEnvironment)) this.isFileStorage = false;
170
- }
171
-
172
- #getDataFromGlobalVar(testId) {
173
- try {
174
- if (global?.testomatioDataStore[this.dataType]) {
175
- const testData = global.testomatioDataStore[this.dataType][testId];
176
- debug(`Data for test id ${testId}:\n${testData}`);
177
- return testData || '';
178
- }
179
- debug(`No ${this.dataType} data for test id ${testId} in <global> storage`);
180
- return '';
181
- } catch (e) {
182
- // there could be no data, ignore
183
- }
184
- }
185
-
186
- #getDataFromFile(testId) {
187
- try {
188
- const filepath = join(this.dataDirPath, `${this.dataType}_${testId}`);
189
- if (fs.existsSync(filepath)) {
190
- const testData = fs.readFileSync(filepath, 'utf-8');
191
- debug(`Data for test id ${testId}:\n${testData}`);
192
- return testData || '';
193
- }
194
- debug(`No ${this.dataType} data for test id ${testId} in <file> storage`);
195
- return '';
196
- } catch (e) {
197
- // there could be no data, ignore
198
- }
199
- return '';
200
- }
201
-
202
- #putDataToGlobalVar(data, testId) {
203
- debug('Saving data to global variable for test', testId, ':\n', data, '\n');
204
- if (!global.testomatioDataStore) global.testomatioDataStore = {};
205
- if (!global.testomatioDataStore?.[this.dataType]) global.testomatioDataStore[this.dataType] = {};
206
- global.testomatioDataStore?.[this.dataType][testId] // eslint-disable-line no-unused-expressions
207
- ? (global.testomatioDataStore[this.dataType][testId] += `\n${data}`)
208
- : (global.testomatioDataStore[this.dataType][testId] = data);
209
- }
210
-
211
- #putDataToFile(data, testId) {
212
- if (typeof data !== 'string') data = JSON.stringify(data);
213
- const filename = `${this.dataType}_${testId}`;
214
- const filepath = join(this.dataDirPath, filename);
215
- if (!fs.existsSync(this.dataDirPath)) fileSystem.createDir(this.dataDirPath);
216
- debug('Saving data to file for test', testId, 'to', filepath, ':\n', data, '\n');
217
-
218
- // TODO: handle multiple invocations of JSON.stringify.
219
- // UPD: not actual because decided not to use it - it created extra wrapping quotes "" and // (escaped slashes)
220
- fs.appendFileSync(filepath, data + os.EOL, 'utf-8');
221
- }
222
- }
223
-
224
- module.exports.DataStorage = DataStorage;
225
-
226
- // TODO: consider using fs promises instead of writeSync/appendFileSync to
227
- // prevent blocking and improve performance (probably queue usage will be required)
228
-
229
- // TODO: rewrite client - everything regarding storing artifacts
230
- // TODO: try to define adapter inside client
231
- // TODO: use .env
232
- // TODO: ability to intercept multiple loggers (upd: no need)
@@ -1,47 +0,0 @@
1
- // TODO: use as example for the unit-tests
2
-
3
- const result = {
4
- "runId": "51eb2798",
5
- "status": "failed",
6
- "runUrl": "https://beta.testomat.io/projects/codecept-new-mode-exmple/runs/51eb2798/report",
7
- "executionTime": "0 seconds",
8
- "executionDate": "(30/07/2023 11:11:11)",
9
- "tests": [{
10
- "files": [],
11
- "steps": "\u001b[32m\u001b[1mOn TodosPage: goto \u001b[22m\u001b[39m\n",
12
- "status": "passed",
13
- "stack": "I execute script () => sessionStorage.clear()",
14
- "example": null,
15
- "code": null,
16
- "title": "Create a new todo item @T50e82737",
17
- "suite_title": "Create Tasks @step:06 @smoke @story:12 @S2f5c1942",
18
- "test_id": "50e82737",
19
- "message": "",
20
- "run_time": 121,
21
- "artifacts": [],
22
- "api_key": "tstmt_gRqrhBUaVxTpezGpZjRmlahOeqcRBBbDMA1692050199",
23
- "create": false
24
- }, {
25
- "files": [{
26
- "path": "/home/codeceptjs-testomat-example/codeceptJS/output/Create_2_@T5b8d1186.failed.png",
27
- "type": "image/png"
28
- }],
29
- "steps": "I am on page \"http://todomvc.com/examples/angularjs/#/\"",
30
- "status": "failed",
31
- "stack": "I am on page \"http://todomvc.com/examples/angularjs/#/\"",
32
- "example": null,
33
- "code": null,
34
- "title": "Create a new todo item part 2 @T5b8d1186",
35
- "suite_title": "@second Create Tasks @step:07 @smoke @story:13 @S2f5c1942",
36
- "test_id": "5b8d1186",
37
- "message": "expected expected number of visible is 2, but found 1 \"1\" to equal \"2\"",
38
- "run_time": 176,
39
- "artifacts": [null],
40
- "api_key": "tstmt_gRqrhBUaVxTpezGpZjRmlahOeqcRBBbDMA1692050199",
41
- "create": false
42
- }]
43
- }
44
-
45
- module.exports = {
46
- result
47
- }