@testomatio/reporter 1.1.0-beta-3 → 1.1.0-beta.codeceptjs-before-suite-logs

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/Changelog.md CHANGED
@@ -1,4 +1,8 @@
1
1
  <!-- pending release updates -->
2
+ # 1.0.18
3
+
4
+ * Fixed stack traces for CodeceptJS
5
+
2
6
  # 1.0.17
3
7
 
4
8
  Renamed `TESTOMATIO_STACK_FILTER` to `TESTOMATIO_STACK_IGNORE`
@@ -78,8 +78,35 @@ function CodeceptReporter(config) {
78
78
  global.testomatioDataStore.steps = [];
79
79
  });
80
80
 
81
+ let hookSteps = [];
82
+ let suiteHookRunning = false;
83
+
84
+ event.dispatcher.on(event.suite.before, (suite) => {
85
+ suiteHookRunning = true;
86
+ hookSteps = [];
87
+ global.testomatioDataStore.steps = [];
88
+ });
89
+
90
+ event.dispatcher.on(event.test.before, () => {
91
+ suiteHookRunning = false;
92
+ global.testomatioDataStore.steps = []
93
+ });
94
+
95
+ event.dispatcher.on(event.hook.started, (suite) => {
96
+ // global.testomatioDataStore.steps = [];
97
+ });
98
+
99
+ event.dispatcher.on(event.hook.passed, (suite) => {
100
+ if (suiteHookRunning) hookSteps.push(...global.testomatioDataStore.steps);
101
+ });
102
+
103
+ event.dispatcher.on(event.hook.failed, (suite) => {
104
+ if (suiteHookRunning) hookSteps.push(...global.testomatioDataStore.steps);
105
+ });
106
+
81
107
  event.dispatcher.on(event.test.started, test => {
82
108
  testTimeMap[test.id] = Date.now();
109
+
83
110
  if (global.testomatioDataStore) global.testomatioDataStore.currentlyRunningTestId = getIdFromTestTitle(test.title);
84
111
  });
85
112
 
@@ -89,7 +116,7 @@ function CodeceptReporter(config) {
89
116
  await Promise.all(reportTestPromises);
90
117
 
91
118
  if (upload.isArtifactsEnabled()) {
92
- uploadAttachments(client, videos, '🎞️ Uploading', 'video');
119
+ uploadAttachments(client, videos, '🎞️ Uploading', 'video');
93
120
  uploadAttachments(client, traces, '📁 Uploading', 'trace');
94
121
  }
95
122
 
@@ -109,7 +136,7 @@ function CodeceptReporter(config) {
109
136
  suite_title: test.parent && test.parent.title,
110
137
  message: testObj.message,
111
138
  time: getDuration(test),
112
- steps: global.testomatioDataStore.steps.join('\n') || null,
139
+ steps: [...hookSteps, ...global.testomatioDataStore.steps].join('\n') || null,
113
140
  test_id: testId,
114
141
  });
115
142
  // output.stop();
@@ -133,6 +160,7 @@ function CodeceptReporter(config) {
133
160
  ...stripExampleFromTitle(title),
134
161
  suite_title: suite.title,
135
162
  test_id: testId,
163
+ steps: hookSteps.join('\n') || null,
136
164
  error,
137
165
  time: 0,
138
166
  });
@@ -147,9 +175,6 @@ function CodeceptReporter(config) {
147
175
  failedTests.push(id || title);
148
176
  let testId = parseTest(tags);
149
177
  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
178
 
154
179
  const files = [];
155
180
  if (artifacts.screenshot) files.push({ path: artifacts.screenshot, type: 'image/png' });
@@ -164,7 +189,7 @@ function CodeceptReporter(config) {
164
189
  message: testObj.message,
165
190
  time: getDuration(test),
166
191
  files,
167
- steps: global.testomatioDataStore?.steps?.join('\n') || null,
192
+ steps: [...hookSteps, ...global.testomatioDataStore.steps].join('\n') || null,
168
193
  })
169
194
  .then(pipes => {
170
195
  testId = pipes.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
@@ -10,6 +10,7 @@ const testomatioReporter = on => {
10
10
  const client = new TestomatClient({ apiKey: process.env.TESTOMATIO });
11
11
 
12
12
  on('before:run', async (run) => {
13
+ // TODO: looks like client.env does not exist
13
14
  if (!client.env) {
14
15
  client.env = `${run.browser.displayName},${run.system.osName}`
15
16
  }
@@ -48,10 +49,16 @@ const testomatioReporter = on => {
48
49
  }
49
50
  }
50
51
 
51
- const screenshots = results.screenshots
52
- .filter(screenshot => screenshot.path.includes(title))
53
- .filter(screenshot => screenshot.testAttemptIndex === lastAttemptIndex)
54
- .map(screenshot => screenshot.path);
52
+ const screenshots = Array.isArray(results.screenshots)
53
+ ? results.screenshots
54
+ .filter(
55
+ screenshot =>
56
+ screenshot?.path &&
57
+ screenshot?.path.includes(title) &&
58
+ screenshot?.takenAt
59
+ )
60
+ .map(screenshot => screenshot.path)
61
+ : [];
55
62
 
56
63
  const files = [...videos, ...screenshots];
57
64
 
@@ -53,6 +53,12 @@ program
53
53
 
54
54
  let exitCode = 0;
55
55
 
56
+ if (!command.split) {
57
+ process.exitCode = 255;
58
+ console.log(APP_PREFIX, `No command provided. Use -c option to launch a test runner.`);
59
+ return;
60
+ }
61
+
56
62
  const client = new TestomatClient({ apiKey, title, parallel: true });
57
63
 
58
64
  if(filter) {
@@ -74,12 +80,6 @@ program
74
80
  }
75
81
  }
76
82
 
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
83
  const testCmds = command.split(' ');
84
84
  console.log(APP_PREFIX, `🚀 Running`, chalk.green(command));
85
85
 
package/lib/config.js ADDED
@@ -0,0 +1,24 @@
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
+ // select only TESTOMATIO related variables (only to print them in debug)
12
+ const testomatioEnvVars =
13
+ Object.keys(process.env)
14
+ .filter(key => key.startsWith('TESTOMATIO') || key.startsWith('S3_'))
15
+ .reduce((obj, key) => {
16
+ obj[key] = process.env[key];
17
+ return obj;
18
+ }, {}) || {};
19
+ debug('TESTOMATIO variables:', testomatioEnvVars);
20
+
21
+ // includes variables from .env file and process.env
22
+ const config = process.env;
23
+
24
+ module.exports = config;
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(