@testomatio/reporter 1.1.0-beta-3 → 1.1.0-beta.artifact-count-retry-msg-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.
@@ -1,7 +1,7 @@
1
1
  const debug = require('debug')('@testomatio/reporter:adapter:codeceptjs');
2
2
  const chalk = require('chalk');
3
3
  const TestomatClient = require('../client');
4
- const { STATUS, APP_PREFIX, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
4
+ const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
5
5
  const upload = require('../fileUploader');
6
6
  const { parseTest: getIdFromTestTitle, fileSystem } = require('../utils/utils');
7
7
 
@@ -89,7 +89,7 @@ function CodeceptReporter(config) {
89
89
  await Promise.all(reportTestPromises);
90
90
 
91
91
  if (upload.isArtifactsEnabled()) {
92
- uploadAttachments(client, videos, '🎞️ Uploading', 'video');
92
+ uploadAttachments(client, videos, '🎞️ Uploading', 'video');
93
93
  uploadAttachments(client, traces, '📁 Uploading', 'trace');
94
94
  }
95
95
 
@@ -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' });
@@ -256,7 +253,7 @@ function CodeceptReporter(config) {
256
253
 
257
254
  async function uploadAttachments(client, attachments, messagePrefix, attachmentType) {
258
255
  if (attachments.length > 0) {
259
- console.log(APP_PREFIX, `Attachments: ${messagePrefix} ${attachments.length} ${attachmentType}/-s ...`);
256
+ debug(`Attachments: ${messagePrefix} ${attachments.length} ${attachmentType}/-s ...`);
260
257
 
261
258
  const promises = attachments.map(async (attachment) => {
262
259
  const { testId, title, path, type } = attachment;
@@ -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
  }
@@ -25,8 +26,9 @@ const testomatioReporter = on => {
25
26
  const lastAttemptIndex = test.attempts.length - 1;
26
27
  const latestAttempt = test.attempts[lastAttemptIndex];
27
28
 
28
- const time = latestAttempt.duration;
29
- const error = latestAttempt.error;
29
+ // latestAttempt.duration && latestAttempt.error were available in adapters version up to 13 JFYI
30
+ const time = latestAttempt.duration || latestAttempt.wallClockDuration || test.duration;
31
+ let error = latestAttempt.error;
30
32
 
31
33
  let title = test.title.pop();
32
34
  const examples = title.match(/\(example (#\d+)\)/);
@@ -39,19 +41,30 @@ const testomatioReporter = on => {
39
41
  const testId = parseTest(title);
40
42
  const suiteId = parseSuite(suiteTitle);
41
43
 
42
- if (error) {
44
+ if (!error && test.displayError) {
45
+ error = { message: test.displayError };
43
46
  error.inspect = function() { // eslint-disable-line func-names
44
- if (this && this.codeFrame) {
45
- return this.codeFrame.frame;
46
- }
47
- return '';
48
- }
47
+ return this.message;
48
+ };
49
49
  }
50
50
 
51
- const screenshots = results.screenshots
52
- .filter(screenshot => screenshot.path.includes(title))
53
- .filter(screenshot => screenshot.testAttemptIndex === lastAttemptIndex)
54
- .map(screenshot => screenshot.path);
51
+ const formattedError = error
52
+ ? {
53
+ message: error.message,
54
+ inspect: error.inspect || function() { return this.message; },
55
+ }
56
+ : '';
57
+
58
+ const screenshots = Array.isArray(results.screenshots)
59
+ ? results.screenshots
60
+ .filter(
61
+ screenshot =>
62
+ screenshot?.path &&
63
+ screenshot?.path.includes(title) &&
64
+ screenshot?.takenAt
65
+ )
66
+ .map(screenshot => screenshot.path)
67
+ : [];
55
68
 
56
69
  const files = [...videos, ...screenshots];
57
70
 
@@ -65,8 +78,16 @@ const testomatioReporter = on => {
65
78
  state = STATUS.SKIPPED;
66
79
  }
67
80
 
68
- addSpecTestsPromises.push(client.addTestRun(state, {
69
- title, time, example, error, files, suite_title: suiteTitle, test_id: testId, suite_id: suiteId
81
+ addSpecTestsPromises.push(
82
+ client.addTestRun(state, {
83
+ title,
84
+ time,
85
+ example,
86
+ error: formattedError,
87
+ files,
88
+ suite_title: suiteTitle,
89
+ test_id: testId,
90
+ suite_id: suiteId
70
91
  }));
71
92
  }
72
93
 
@@ -1,9 +1,10 @@
1
+ const debug = require('debug')('@testomatio/reporter:adapter:playwright');
1
2
  const chalk = require('chalk');
2
3
  const crypto = require('crypto');
3
4
  const os = require('os');
4
5
  const path = require('path');
5
6
  const fs = require('fs');
6
- const { APP_PREFIX, STATUS: Status, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
7
+ const { STATUS: Status, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
7
8
  const TestomatioClient = require('../client');
8
9
  const { isArtifactsEnabled } = require('../fileUploader');
9
10
  const { parseTest, fileSystem } = require('../utils/utils');
@@ -83,7 +84,7 @@ class PlaywrightReporter {
83
84
  await Promise.all(reportTestPromises);
84
85
 
85
86
  if (this.uploads.length && isArtifactsEnabled()) {
86
- console.log(APP_PREFIX, `🎞️ Uploading ${this.uploads.length} files...`);
87
+ debug(`Attachments: 📁 Uploading ${this.uploads.length} files...`);
87
88
 
88
89
  const promises = [];
89
90
 
@@ -95,7 +96,11 @@ class PlaywrightReporter {
95
96
  const fileName = tmpFile();
96
97
  fs.writeFileSync(fileName, attachment.body);
97
98
  }
98
- return { path: attachment.path, title, type: attachment.contentType };
99
+ return {
100
+ path: attachment.path,
101
+ title,
102
+ type: attachment.contentType
103
+ };
99
104
  });
100
105
 
101
106
  promises.push(
@@ -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/client.js CHANGED
@@ -169,19 +169,19 @@ class Client {
169
169
 
170
170
  for (const file of files) {
171
171
  uploadedFiles.push(upload.uploadFileByPath(file, this.uuid));
172
+ this.totalUploaded++;
172
173
  }
173
174
 
174
175
  for (const [idx, buffer] of filesBuffers.entries()) {
175
176
  const fileName = `${idx + 1}-${title.replace(/\s+/g, '-')}`;
176
177
  uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.uuid));
178
+ this.totalUploaded++;
177
179
  }
178
180
 
179
181
  const artifacts = await Promise.all(uploadedFiles);
180
182
 
181
183
  // global.testomatioArtifacts = [];
182
184
 
183
- this.totalUploaded += uploadedFiles.filter(n => n).length;
184
-
185
185
  const data = {
186
186
  files,
187
187
  steps,
@@ -204,8 +204,13 @@ class Client {
204
204
  this.pipes.map(async p => {
205
205
  try {
206
206
  const result = await p.addTest(data);
207
- return { pipe: p.toString(), result };
208
- } catch (err) {
207
+
208
+ return {
209
+ pipe: p.toString(),
210
+ result
211
+ };
212
+ }
213
+ catch (err) {
209
214
  console.log(APP_PREFIX, p.toString(), err);
210
215
  }
211
216
  }),
@@ -233,14 +238,27 @@ class Client {
233
238
  .then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
234
239
  .then(() => {
235
240
  debug('TOTAL artifacts', this.totalUploaded);
236
- if (this.totalUploaded && !upload.isArtifactsEnabled())
241
+ if (this.totalUploaded && !upload.isArtifactsEnabled()) {
237
242
  debug(`${this.totalUploaded} artifacts are not uploaded, because artifacts uploading is not enabled`);
243
+ console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
244
+ }
238
245
 
239
246
  if (this.totalUploaded && upload.isArtifactsEnabled()) {
247
+ const isPrivate = process.env.TESTOMATIO_PRIVATE_ARTIFACTS;
248
+
249
+ if (!isPrivate) {
250
+ console.log(APP_PREFIX, `To enable ${chalk.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
251
+ }
252
+ console.log(
253
+ APP_PREFIX,
254
+ `To enable ${chalk.bold('PUBLIC')} uploads remove TESTOMATIO_PRIVATE_ARTIFACTS env variable`,
255
+ );
256
+ console.log(APP_PREFIX, '---------------');
257
+
240
258
  console.log(
241
259
  APP_PREFIX,
242
260
  `🗄️ ${this.totalUploaded} artifacts ${
243
- process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
261
+ isPrivate ? 'privately' : chalk.bold('publicly')
244
262
  } uploaded to S3 bucket`,
245
263
  );
246
264
  }
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,62 +99,63 @@ 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);
109
-
110
- Key = `${runId}/${randomUUID()}-${Key || path.basename(filePath)}`;
111
- const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
107
+ try {
108
+ debug('S3 config', getMaskedConfig());
109
+ debug('Started upload', filePath, 'to ', S3_BUCKET);
112
110
 
113
- const s3 = new S3(_getS3Config());
111
+ // Verification that the file was actually created: 20 attempts of 0.5 second => 10sec
112
+ const isFileExist = await checkFileExists(filePath, 20, 500);
114
113
 
115
- const params = {
116
- Bucket: S3_BUCKET,
117
- Key,
118
- Body: fileData,
119
- ContentType,
120
- ACL,
121
- };
114
+ if (!isFileExist) {
115
+ console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
116
+ return;
117
+ }
122
118
 
123
- try {
124
- const out = new Upload({
125
- client: s3,
126
- params
127
- });
119
+ debug('File: ', filePath, ' exists');
120
+
121
+ const fileData = await readFile(filePath);
128
122
 
129
- await out.done();
130
- debug('Uploaded', out.singleUploadResult.Location)
123
+ Key = `${runId}/${randomUUID()}-${Key || path.basename(filePath)}`;
131
124
 
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());
125
+ const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
136
126
 
137
- console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
138
- if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
139
- console.log(APP_PREFIX, `To enable ${chalk.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
140
- } else {
127
+ if (!S3_BUCKET || !fileData) {
141
128
  console.log(
142
129
  APP_PREFIX,
143
- `To enable ${chalk.bold('PUBLIC')} uploads remove TESTOMATIO_PRIVATE_ARTIFACTS env variable`,
144
- );
130
+ chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), getMaskedConfig());
131
+ return;
145
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
+
144
+ const out = new Upload({
145
+ client: s3,
146
+ params
147
+ });
148
+
149
+ return await getS3LocationLink(out);
150
+ }
151
+ catch (e) {
152
+ debug('S3 file uploading error: ', e);
153
+ console.log(APP_PREFIX, 'S3 file uploading error: ', e);
146
154
  console.log(APP_PREFIX, '---------------');
147
155
  }
148
156
  };
149
157
 
150
158
  const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
151
-
152
159
  const {
153
160
  S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET
154
161
  } = getConfig();
@@ -158,6 +165,18 @@ const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
158
165
  const fileExtension = _getFileExtBase64(buffer.toString('base64'));
159
166
  const Key = `${runId}/${fileName}${fileExtension}`;
160
167
 
168
+ if (!S3_BUCKET || !buffer) {
169
+ console.log(APP_PREFIX, chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), {
170
+ accessKeyId: S3_ACCESS_KEY_ID,
171
+ secretAccessKey: S3_SECRET_ACCESS_KEY ? '**** (hidden) ***' : '(empty)',
172
+ region: S3_REGION,
173
+ bucket: S3_BUCKET,
174
+ acl: ACL,
175
+ endpoint: S3_ENDPOINT,
176
+ });
177
+ return;
178
+ }
179
+
161
180
  const s3 = new S3(_getS3Config());
162
181
 
163
182
  try {
@@ -171,28 +190,13 @@ const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
171
190
  ACL,
172
191
  }
173
192
  });
174
- await out.done();
175
193
 
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
- });
194
+ return await getS3LocationLink(out);
195
+ }
196
+ catch (e) {
197
+ debug('S3 buffer uploading error: ', e);
186
198
 
187
- console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
188
- if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
189
- console.log(APP_PREFIX, `To enable ${chalk.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
190
- } else {
191
- console.log(
192
- APP_PREFIX,
193
- `To enable ${chalk.bold('PUBLIC')} uploads remove TESTOMATIO_PRIVATE_ARTIFACTS env variable`,
194
- );
195
- }
199
+ console.log(APP_PREFIX, 'S3 buffer uploading error: ', e);
196
200
  console.log(APP_PREFIX, '---------------');
197
201
  }
198
202
  };
@@ -203,7 +207,9 @@ const uploadFileByPath = async (filePath, runId) => {
203
207
  return uploadUsingS3(filePath, runId);
204
208
  }
205
209
  } catch (e) {
206
- console.error(chalk.red('Error occurred while uploading artifacts'), e);
210
+ debug(e);
211
+
212
+ console.error(chalk.red('Error occurred while uploading artifacts! '), e);
207
213
  }
208
214
  };
209
215
 
@@ -213,10 +219,61 @@ const uploadFileAsBuffer = async (buffer, fileName, runId) => {
213
219
  return uploadUsingS3AsBuffer(buffer, fileName, runId);
214
220
  }
215
221
  } catch (e) {
216
- console.error(chalk.red('Error occurred while uploading artifacts'), e);
222
+ debug(e);
223
+
224
+ console.error(chalk.red('Error occurred while uploading artifacts! '), e);
217
225
  }
218
226
  };
219
227
 
228
+ const checkFileExists = async (filePath, attempts = 5, intervalMs = 500) => {
229
+ const checkFile = async () => {
230
+ const fileStats = await stat(filePath);
231
+ if (fileStats.isFile()) {
232
+ return true;
233
+ }
234
+
235
+ throw new Error('File not found');
236
+ };
237
+
238
+ try {
239
+ await promiseRetry(
240
+ {
241
+ retries: attempts,
242
+ minTimeout: intervalMs
243
+ },
244
+ checkFile
245
+ );
246
+
247
+ return true;
248
+ } catch (err) {
249
+ console.error(
250
+ chalk.yellow(`File ${filePath} was not found or did not have time to be generated...`)
251
+ );
252
+
253
+ return false;
254
+ }
255
+ };
256
+
257
+ const getS3LocationLink = async (out) => {
258
+ const response = await out.done();
259
+
260
+ let s3Location = response?.Location;
261
+
262
+ debug('Uploaded response.Location', s3Location);
263
+
264
+ if (!s3Location) {
265
+ // TODO: out: a fallback case - remove after deeper testing
266
+ s3Location = out?.singleUploadResult?.Location;
267
+ debug('Uploaded singleUploadResult.Location', s3Location);
268
+
269
+ if (!s3Location) {
270
+ throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
271
+ }
272
+ }
273
+
274
+ return s3Location;
275
+ };
276
+
220
277
  module.exports = {
221
278
  uploadFileByPath: memoize(uploadFileByPath),
222
279
  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(