@testomatio/reporter 1.1.0-beta-3 → 1.1.0-beta-codecept-logger

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.
@@ -147,9 +147,6 @@ function CodeceptReporter(config) {
147
147
  failedTests.push(id || title);
148
148
  let testId = parseTest(tags);
149
149
  const testObj = getTestAndMessage(title);
150
- if (error && error.stack && test.steps && test.steps.length) {
151
- error.stack = test.steps[test.steps.length - 1].line();
152
- }
153
150
 
154
151
  const files = [];
155
152
  if (artifacts.screenshot) files.push({ path: artifacts.screenshot, type: 'image/png' });
@@ -6,6 +6,7 @@ const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../../constants');
6
6
  const TestomatClient = require('../../client');
7
7
  const logger = require('../../storages/logger');
8
8
  const { parseTest, fileSystem } = require('../../utils/utils');
9
+ const { TESTOMATIO } = require('../../config');
9
10
 
10
11
  const { GherkinDocumentParser, PickleParser } = formatterHelpers;
11
12
  const { getGherkinScenarioLocationMap, getGherkinStepMap } = GherkinDocumentParser;
@@ -29,7 +30,7 @@ class CucumberReporter extends Formatter {
29
30
  this.failures = [];
30
31
  this.cases = [];
31
32
 
32
- this.client = new TestomatClient({ apiKey: options.apiKey || process.env.TESTOMATIO });
33
+ this.client = new TestomatClient({ apiKey: options.apiKey || TESTOMATIO });
33
34
  this.status = STATUS.PASSED;
34
35
 
35
36
  global.testomatioRunningEnvironment = 'cucumber:current';
@@ -4,6 +4,7 @@ const chalk = require('chalk');
4
4
  const { parseTest, fileSystem } = require('../../utils/utils');
5
5
  const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../../constants');
6
6
  const TestomatClient = require('../../client');
7
+ const { TESTOMATIO } = require('../../config');
7
8
 
8
9
  const createTestomatFormatter = apiKey => {
9
10
  if (!apiKey || apiKey === '') {
@@ -151,4 +152,4 @@ const createTestomatFormatter = apiKey => {
151
152
  };
152
153
  };
153
154
 
154
- module.exports = createTestomatFormatter(process.env.TESTOMATIO);
155
+ module.exports = createTestomatFormatter(TESTOMATIO);
@@ -1,17 +1,19 @@
1
1
  const { STATUS } = require('../../constants');
2
2
  const { parseTest, parseSuite } = require('../../utils/utils');
3
3
  const TestomatClient = require('../../client');
4
+ const { TESTOMATIO } = require('../../config');
4
5
 
5
6
  const testomatioReporter = on => {
6
- if (!process.env.TESTOMATIO) {
7
+ if (!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: 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);
@@ -4,6 +4,7 @@ const chalk = require('chalk');
4
4
  const TestomatClient = require('../client');
5
5
  const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
6
6
  const { parseTest, fileSystem } = require('../utils/utils');
7
+ const { TESTOMATIO } = require('../config');
7
8
 
8
9
  const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING } =
9
10
  Mocha.Runner.constants;
@@ -15,7 +16,7 @@ function MochaReporter(runner, opts) {
15
16
  let skipped = 0;
16
17
  // let artifactStore;
17
18
 
18
- const apiKey = opts?.reporterOptions?.apiKey || process.env.TESTOMATIO;
19
+ const apiKey = opts?.reporterOptions?.apiKey || TESTOMATIO;
19
20
 
20
21
  const client = new TestomatClient({ apiKey });
21
22
 
@@ -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 { TESTOMATIO } = require('../config');
8
9
 
9
10
  console.log(chalk.cyan.bold(` 🤩 Testomat.io Reporter v${version}`));
10
11
 
@@ -20,7 +21,7 @@ program
20
21
 
21
22
  if (opts.envFile) require('dotenv').config(opts.envFile); // eslint-disable-line
22
23
 
23
- const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || process.env.TESTOMATIO;
24
+ const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || TESTOMATIO;
24
25
  const title = process.env.TESTOMATIO_TITLE;
25
26
 
26
27
  if (launch) {
@@ -53,6 +54,12 @@ program
53
54
 
54
55
  let exitCode = 0;
55
56
 
57
+ if (!command.split) {
58
+ process.exitCode = 255;
59
+ console.log(APP_PREFIX, `No command provided. Use -c option to launch a test runner.`);
60
+ return;
61
+ }
62
+
56
63
  const client = new TestomatClient({ apiKey, title, parallel: true });
57
64
 
58
65
  if(filter) {
@@ -74,12 +81,6 @@ program
74
81
  }
75
82
  }
76
83
 
77
- if (!command.split) {
78
- process.exitCode = 255;
79
- console.log(APP_PREFIX, `No command provided. Use -c option to launch a test runner.`);
80
- return;
81
- }
82
-
83
84
  const testCmds = command.split(' ');
84
85
  console.log(APP_PREFIX, `🚀 Running`, chalk.green(command));
85
86
 
package/lib/config.js ADDED
@@ -0,0 +1,29 @@
1
+ // This file is used to read environment variables from .env file and process.env
2
+
3
+ // ! uncommenting next line leads ro reading vars from .env file
4
+ // require('dotenv').config();
5
+ const debug = require('debug')('@testomatio/reporter:config');
6
+
7
+ /* for possibility to use multiple env files (reading different paths)
8
+ const dotenv = require('dotenv');
9
+ const envFileVars = dotenv.config({ path: '.env' }).parsed; */
10
+
11
+ const TESTOMATIO = process.env.TESTOMATIO || process.env.TESTOMATIO_API_KEY || process.env.TESTOMATIO_TOKEN || '';
12
+ if (TESTOMATIO === 'undefined') console.error('TESTOMATIO is "undefined". Something went wrong. Contact dev team.');
13
+ process.env.TESTOMATIO = TESTOMATIO;
14
+
15
+ // select only TESTOMATIO related variables (only to print them in debug)
16
+ const testomatioEnvVars =
17
+ Object.keys(process.env)
18
+ .filter(key => key.startsWith('TESTOMATIO') || key.startsWith('S3_'))
19
+ .reduce((obj, key) => {
20
+ obj[key] = process.env[key];
21
+ return obj;
22
+ }, {}) || {};
23
+ debug('TESTOMATIO variables:', testomatioEnvVars);
24
+
25
+ // includes variables from .env file and process.env
26
+ const config = process.env;
27
+
28
+ module.exports = config;
29
+ module.exports.TESTOMATIO = TESTOMATIO;
package/lib/constants.js CHANGED
@@ -20,10 +20,17 @@ const STATUS = {
20
20
  SKIPPED: 'skipped',
21
21
  FINISHED: 'finished',
22
22
  };
23
+ // html pipe var
24
+ const HTML_REPORT = {
25
+ FOLDER: "html-report",
26
+ REPORT_DEFAULT_NAME: "testomatio-report.html",
27
+ TEMPLATE_NAME: 'testomatio.hbs'
28
+ };
23
29
 
24
30
  module.exports = {
25
31
  APP_PREFIX,
26
32
  TESTOMAT_TMP_STORAGE_DIR,
27
33
  CSV_HEADERS,
28
34
  STATUS,
35
+ HTML_REPORT
29
36
  }
@@ -3,10 +3,16 @@ const { S3 } = require('@aws-sdk/client-s3');
3
3
  const { Upload } = require('@aws-sdk/lib-storage');
4
4
 
5
5
  const fs = require('fs');
6
+ const util = require('util');
6
7
  const path = require('path');
8
+ const promiseRetry = require('promise-retry');
9
+
10
+ const readFile = util.promisify(fs.readFile);
11
+ const stat = util.promisify(fs.stat);
7
12
  const chalk = require('chalk');
8
13
  const { randomUUID } = require('crypto');
9
14
  const memoize = require('lodash.memoize');
15
+
10
16
  const { APP_PREFIX } = require('./constants');
11
17
 
12
18
  const keys = [
@@ -93,51 +99,64 @@ const uploadUsingS3 = async (filePath, runId) => {
93
99
  Key = filePath.name;
94
100
  }
95
101
 
96
- if (!fs.existsSync(filePath)) {
97
- console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
98
- return;
99
- }
100
-
101
102
  const {
102
- TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET
103
+ TESTOMATIO_PRIVATE_ARTIFACTS,
104
+ S3_BUCKET
103
105
  } = getConfig();
104
106
 
105
- debug('S3 config', getMaskedConfig());
106
- debug('Uploading', filePath, 'to', S3_BUCKET);
107
-
108
- const fileData = fs.readFileSync(filePath);
107
+ try {
108
+ debug('S3 config', getMaskedConfig());
109
+ debug('Started upload', filePath, 'to ', S3_BUCKET);
109
110
 
110
- Key = `${runId}/${randomUUID()}-${Key || path.basename(filePath)}`;
111
- const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
111
+ // Verification that the file was actually created: 20 attempts of 0.5 second => 10sec
112
+ const isFileExist = await checkFileExists(filePath, 20, 500);
112
113
 
113
- const s3 = new S3(_getS3Config());
114
+ if (!isFileExist) {
115
+ console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
116
+ return;
117
+ }
114
118
 
115
- const params = {
116
- Bucket: S3_BUCKET,
117
- Key,
118
- Body: fileData,
119
- ContentType,
120
- ACL,
121
- };
119
+ debug('File: ', filePath, ' exists');
120
+
121
+ const fileData = await readFile(filePath);
122
122
 
123
- try {
123
+ Key = `${runId}/${randomUUID()}-${Key || path.basename(filePath)}`;
124
+
125
+ const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
126
+
127
+ if (!S3_BUCKET || !fileData) {
128
+ console.log(
129
+ APP_PREFIX,
130
+ chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), getMaskedConfig());
131
+ return;
132
+ }
133
+
134
+ const s3 = new S3(_getS3Config());
135
+
136
+ const params = {
137
+ Bucket: S3_BUCKET,
138
+ Key,
139
+ Body: fileData,
140
+ ContentType,
141
+ ACL,
142
+ };
143
+
124
144
  const out = new Upload({
125
145
  client: s3,
126
146
  params
127
147
  });
128
148
 
129
- await out.done();
130
- debug('Uploaded', out.singleUploadResult.Location)
131
-
132
- return out.singleUploadResult.Location;
133
- } catch (e) {
134
- console.log(e);
135
- console.log(APP_PREFIX, chalk.red(`Failed uploading '${Key}'. Please check S3 credentials`), getMaskedConfig());
136
-
149
+ return await getS3LocationLink(out);
150
+ }
151
+ catch (e) {
152
+ debug('S3 file uploading error: ', e);
153
+
137
154
  console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
155
+
138
156
  if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
139
157
  console.log(APP_PREFIX, `To enable ${chalk.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
140
- } else {
158
+ }
159
+ else {
141
160
  console.log(
142
161
  APP_PREFIX,
143
162
  `To enable ${chalk.bold('PUBLIC')} uploads remove TESTOMATIO_PRIVATE_ARTIFACTS env variable`,
@@ -148,7 +167,6 @@ const fileData = fs.readFileSync(filePath);
148
167
  };
149
168
 
150
169
  const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
151
-
152
170
  const {
153
171
  S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET
154
172
  } = getConfig();
@@ -158,6 +176,18 @@ const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
158
176
  const fileExtension = _getFileExtBase64(buffer.toString('base64'));
159
177
  const Key = `${runId}/${fileName}${fileExtension}`;
160
178
 
179
+ if (!S3_BUCKET || !buffer) {
180
+ console.log(APP_PREFIX, chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), {
181
+ accessKeyId: S3_ACCESS_KEY_ID,
182
+ secretAccessKey: S3_SECRET_ACCESS_KEY ? '**** (hidden) ***' : '(empty)',
183
+ region: S3_REGION,
184
+ bucket: S3_BUCKET,
185
+ acl: ACL,
186
+ endpoint: S3_ENDPOINT,
187
+ });
188
+ return;
189
+ }
190
+
161
191
  const s3 = new S3(_getS3Config());
162
192
 
163
193
  try {
@@ -171,20 +201,14 @@ const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
171
201
  ACL,
172
202
  }
173
203
  });
174
- await out.done();
175
204
 
176
- return out.singleUploadResult.Location;
177
- } catch (e) {
178
- console.log(APP_PREFIX, chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), {
179
- accessKeyId: S3_ACCESS_KEY_ID,
180
- secretAccessKey: S3_SECRET_ACCESS_KEY ? '**** (hidden) ***' : '(empty)',
181
- region: S3_REGION,
182
- bucket: S3_BUCKET,
183
- acl: ACL,
184
- endpoint: S3_ENDPOINT,
185
- });
205
+ return await getS3LocationLink(out);
206
+ }
207
+ catch (e) {
208
+ debug('S3 buffer uploading error: ', e);
186
209
 
187
210
  console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
211
+
188
212
  if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
189
213
  console.log(APP_PREFIX, `To enable ${chalk.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
190
214
  } else {
@@ -203,7 +227,9 @@ const uploadFileByPath = async (filePath, runId) => {
203
227
  return uploadUsingS3(filePath, runId);
204
228
  }
205
229
  } catch (e) {
206
- console.error(chalk.red('Error occurred while uploading artifacts'), e);
230
+ debug(e);
231
+
232
+ console.error(chalk.red('Error occurred while uploading artifacts! '), e);
207
233
  }
208
234
  };
209
235
 
@@ -213,10 +239,61 @@ const uploadFileAsBuffer = async (buffer, fileName, runId) => {
213
239
  return uploadUsingS3AsBuffer(buffer, fileName, runId);
214
240
  }
215
241
  } catch (e) {
216
- console.error(chalk.red('Error occurred while uploading artifacts'), e);
242
+ debug(e);
243
+
244
+ console.error(chalk.red('Error occurred while uploading artifacts! '), e);
217
245
  }
218
246
  };
219
247
 
248
+ const checkFileExists = async (filePath, attempts = 5, intervalMs = 500) => {
249
+ const checkFile = async () => {
250
+ const fileStats = await stat(filePath);
251
+ if (fileStats.isFile()) {
252
+ return true;
253
+ }
254
+
255
+ throw new Error('File not found');
256
+ };
257
+
258
+ try {
259
+ await promiseRetry(
260
+ {
261
+ retries: attempts,
262
+ minTimeout: intervalMs
263
+ },
264
+ checkFile
265
+ );
266
+
267
+ return true;
268
+ } catch (err) {
269
+ console.error(
270
+ chalk.yellow(`File ${filePath} was not found or did not have time to be generated...`)
271
+ );
272
+
273
+ return false;
274
+ }
275
+ };
276
+
277
+ const getS3LocationLink = async (out) => {
278
+ const response = await out.done();
279
+
280
+ let s3Location = response?.Location;
281
+
282
+ debug('Uploaded response.Location', s3Location);
283
+
284
+ if (!s3Location) {
285
+ // TODO: out: a fallback case - remove after deeper testing
286
+ s3Location = out?.singleUploadResult?.Location;
287
+ debug('Uploaded singleUploadResult.Location', s3Location);
288
+
289
+ if (!s3Location) {
290
+ throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
291
+ }
292
+ }
293
+
294
+ return s3Location;
295
+ };
296
+
220
297
  module.exports = {
221
298
  uploadFileByPath: memoize(uploadFileByPath),
222
299
  uploadFileAsBuffer: memoize(uploadFileAsBuffer),
@@ -23,7 +23,7 @@ class GitLabPipe {
23
23
  this.store = store;
24
24
  this.tests = [];
25
25
  // GitLab PAT looks like glpat-nKGdja3jsG4850sGksh7
26
- this.token = params.GITLAB_PAT || this.ENV.GITLAB_PAT;
26
+ this.token = params.GITLAB_PAT || process.env.GITLAB_PAT || this.ENV.GITLAB_PAT;
27
27
  this.hiddenCommentData = `<!--- testomat.io report ${process.env.CI_JOB_NAME || ''} -->`;
28
28
 
29
29
  debug(