@testomatio/reporter 0.4.0 → 0.4.4

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,3 +1,26 @@
1
+ # 0.4.4
2
+
3
+ * Fixed returning 0 exit code when a process fails when running tests in parallel via `start-test-run`. Previously was using the last exit code returned by a process. Currently prefers the highest exit code that was returned by a process.
4
+
5
+ # 0.4.3
6
+
7
+ * Added `TESTOMATIO_DISABLE_ARTIFACTS` env variable to disable publishing artifacts.
8
+
9
+ # 0.4.2
10
+
11
+ * print version of reporter
12
+ * print number of uploaded artifacts
13
+ * print access mode for uploaded artifacts
14
+
15
+ # 0.4.1
16
+
17
+ Added `global.testomatioArtifacts = []` array which can be used to add arbitrary artifacts to a report.
18
+
19
+ ```js
20
+ // inside a running test:
21
+ global.testomatioArtifacts.push('file/to/upload.png');
22
+ ```
23
+
1
24
  # 0.4.0
2
25
 
3
26
  * Playwright: Introduced playwright/test support with screenshots and video artifacts
package/README.md CHANGED
@@ -169,9 +169,9 @@ Add environments to run by providing `TESTOMATIO_ENV` as comma seperated values:
169
169
  TESTOMATIO={apiKey} TESTOMATIO_ENV="Windows, Chrome" <actual run command>
170
170
  ```
171
171
 
172
- ## Attaching Screenshots
172
+ ## Attaching Test Artifacts
173
173
 
174
- To save a screenshot of a failed test use S3 storage.
174
+ To save a test artifacts (screenshots and videos) of a failed test use S3 storage.
175
175
  Please note, that the **storage is not connected to Testomatio**.
176
176
  This allows you to store your artifacts on your own account and not expose S3 credentials.
177
177
 
@@ -183,12 +183,77 @@ To save screenshots provide a configuration for S3 bucket via environment variab
183
183
  - **S3_SECRET_ACCESS_KEY** - Secret.
184
184
  - **S3_ENDPOINT** - for providers other than AWS
185
185
 
186
- > AWS S3 bucket is required to have `public-read` permission to operate. Public links are used by Testomatio to display screenshots in UI.
186
+ By default tests artifacts are uploaded to bucket with `public-read` permission.
187
+ In this case uploaded files will be publicly accessible in Internet.
188
+ These public links will be used by Testomat.io to display images and videos.
189
+
190
+ To upload files with `private` access bucket add `TESTOMATIO_PRIVATE_ARTIFACTS=1` environment value.
191
+ Then update provide the same S3 credentials in "Settings > Artifacts" section of a Testomat.io project,
192
+ so Testomat.io could connect to the same bucket and fetch uploaded artifacts.
193
+ Links to files will be pre-signed and expires automatically in 10 minutes.
194
+
195
+ Example upload configuration in environment variables
196
+
197
+ ##### AWS
198
+
199
+ ```
200
+ TESTOMATIO_PRIVATE_ARTIFACTS=1
201
+ S3_ACCESS_KEY_ID=11111111111111111111
202
+ S3_SECRET_ACCESS_KEY=2222222222222222222222222222222222222222222
203
+ S3_BUCKET=artifacts
204
+ S3_REGION=us-west-1
205
+ ```
206
+
207
+ ##### DigitalOcean
208
+
209
+ ```
210
+ TESTOMATIO_PRIVATE_ARTIFACTS=1
211
+ S3_ENDPOINT=https://ams3.digitaloceanspaces.com
212
+ S3_ACCESS_KEY_ID=11111111111111111111
213
+ S3_SECRET_ACCESS_KEY=2222222222222222222222222222222222222222222
214
+ S3_BUCKET=artifacts
215
+ S3_REGION=ams3
216
+ ```
217
+
218
+ ##### Minio
219
+
220
+ ```
221
+ S3_ENDPOINT=http://company.storage.com
222
+ S3_ACCESS_KEY_ID=minio
223
+ S3_SECRET_ACCESS_KEY=minio123
224
+ S3_BUCKET=testomatio
225
+ S3_FORCE_PATH_STYLE=true
226
+ ```
227
+
228
+ > It is important to add S3_FORCE_PATH_STYLE var for minio setup
229
+
187
230
 
188
231
  For local testing, it is recommended to store this configuration in `.env` file and load it with [dotenv](https://www.npmjs.com/package/dotenv) library.
189
232
 
190
233
  On CI set environment variables in CI config.
191
234
 
235
+ Test artifacts are automatically uploaded for these test runners:
236
+
237
+ * CodeceptJS
238
+ * Playwright
239
+
240
+ To manually attach an artifact and upload it for a test use `global.testomatioArtifacts` array:
241
+
242
+ ```js
243
+ // attach a picture inside a test
244
+ global.testomatioArtifacts.push('img/file.png');
245
+ // attach a picture and add a name to it
246
+ global.testomatioArtifacts.push({ name: 'Screenshot', path: 'img/file.png' });
247
+ ```
248
+
249
+ Artifacts will be uploaded for the current test when it is finished.
250
+
251
+ To disable uploading artifacts add `TESTOMATIO_DISABLE_ARTIFACTS` environment variable:
252
+
253
+ ```
254
+ TESTOMATIO_DISABLE_ARTIFACTS=1
255
+ ```
256
+
192
257
 
193
258
  ## Starting an Empty Run
194
259
 
@@ -88,7 +88,7 @@ module.exports = (config) => {
88
88
 
89
89
  event.dispatcher.on(event.all.result, async () => {
90
90
  if (videos.length) {
91
- console.log(TRConstants.APP_PREFIX, `🎞️ Uploading ${videos.length} videos...`);
91
+ console.log(TRConstants.APP_PREFIX, `🎞️ Uploading ${videos.length} videos...`);
92
92
 
93
93
  let promises = [];
94
94
  for (const video of videos) {
@@ -76,7 +76,7 @@ class TestomatioReporter {
76
76
  if (!this.client) return;
77
77
 
78
78
  if (this.videos.length) {
79
- console.log(Status.APP_PREFIX, `🎞️ Uploading ${this.videos.length} videos...`);
79
+ console.log(Status.APP_PREFIX, `🎞️ Uploading ${this.videos.length} videos...`);
80
80
 
81
81
  let promises = [];
82
82
  for (const video of this.videos) {
@@ -43,6 +43,8 @@ program
43
43
  return;
44
44
  }
45
45
 
46
+ let exitCode = 0;
47
+
46
48
  const testCmds = command.split(" ");
47
49
  console.log(APP_PREFIX, `🚀 Running`, chalk.green(command));
48
50
 
@@ -55,7 +57,9 @@ program
55
57
  "⚠️ ",
56
58
  `Runner exited with ${chalk.bold(code)}, report is ignored`
57
59
  );
58
- process.exitCode = code;
60
+
61
+ if (code > exitCode) exitCode = code;
62
+ process.exitCode = exitCode;
59
63
  });
60
64
 
61
65
  return;
@@ -71,7 +75,9 @@ program
71
75
  console.log(APP_PREFIX, emoji, `Runner exited with ${chalk.bold(code)}`);
72
76
  const status = code === 0 ? "passed" : "failed";
73
77
  client.updateRunStatus(status, true);
74
- process.exitCode = code;
78
+
79
+ if (code > exitCode) exitCode = code;
80
+ process.exitCode = exitCode;
75
81
  });
76
82
  });
77
83
  });
package/lib/client.js CHANGED
@@ -2,14 +2,15 @@ const axios = require("axios");
2
2
  const JsonCycle = require("json-cycle");
3
3
  const { URL } = require("url");
4
4
  const createCallsiteRecord = require("callsite-record");
5
-
6
- const { sep } = require("path");
5
+ const { sep, join } = require("path");
6
+ const fs = require("fs");
7
7
  const chalk = require("chalk");
8
- const { uploadFile } = require("./fileUploader");
8
+ const { isPrivate, uploadFile } = require("./fileUploader");
9
9
  const { PASSED, FAILED, FINISHED, APP_PREFIX } = require("./constants");
10
10
 
11
11
  const TESTOMAT_URL = process.env.TESTOMATIO_URL || "https://app.testomat.io";
12
- const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN } = process.env;
12
+ const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN } =
13
+ process.env;
13
14
 
14
15
  if (TESTOMATIO_RUN) {
15
16
  process.env.runId = TESTOMATIO_RUN;
@@ -28,6 +29,8 @@ class TestomatClient {
28
29
  this.runId = process.env.runId;
29
30
  this.queue = Promise.resolve();
30
31
  this.axios = axios.create();
32
+ this.totalUploaded = 0;
33
+ this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
31
34
  }
32
35
 
33
36
  /**
@@ -37,7 +40,7 @@ class TestomatClient {
37
40
  */
38
41
  createRun() {
39
42
  const { runId } = process.env;
40
- if (!this.apiKey) throw new Error('No API key is set, can\'t create run');
43
+ if (!this.apiKey) throw new Error("No API key is set, can't create run");
41
44
  const runParams = {
42
45
  api_key: this.apiKey.trim(),
43
46
  title: this.title,
@@ -45,10 +48,15 @@ class TestomatClient {
45
48
  group_title: TESTOMATIO_RUNGROUP_TITLE,
46
49
  env: TESTOMATIO_ENV,
47
50
  };
51
+
52
+ global.testomatioArtifacts = [];
53
+
48
54
  if (!isValidUrl(TESTOMAT_URL.trim())) {
49
55
  console.log(
50
56
  APP_PREFIX,
51
- chalk.red(`Error creating report on Testomat.io, report url '${TESTOMAT_URL}' is invalid`)
57
+ chalk.red(
58
+ `Error creating report on Testomat.io, report url '${TESTOMAT_URL}' is invalid`
59
+ )
52
60
  );
53
61
  return;
54
62
  }
@@ -56,7 +64,8 @@ class TestomatClient {
56
64
  if (runId) {
57
65
  this.runId = runId;
58
66
  this.queue = this.queue.then(() =>
59
- axios.put(`${TESTOMAT_URL.trim()}/api/reporter/${runId}`, runParams));
67
+ axios.put(`${TESTOMAT_URL.trim()}/api/reporter/${runId}`, runParams)
68
+ );
60
69
  return Promise.resolve(runId);
61
70
  }
62
71
 
@@ -73,10 +82,12 @@ class TestomatClient {
73
82
  console.log(
74
83
  APP_PREFIX,
75
84
  "📊 Report created. Report ID:",
76
- this.runId
85
+ this.runId,
86
+ chalk.gray(`v${this.version}`)
77
87
  );
78
88
  process.env.runId = this.runId;
79
- }))
89
+ })
90
+ )
80
91
  .catch(() => {
81
92
  console.log(
82
93
  APP_PREFIX,
@@ -113,50 +124,18 @@ class TestomatClient {
113
124
  let stack = "";
114
125
 
115
126
  if (error) {
116
- if (!message) message = error.message;
117
- if (error.inspect) message = error.inspect();
118
-
119
- stack = `\n${chalk.bold(message)}\n`;
120
-
121
- // diffs for mocha, cypress, codeceptjs style
122
- if (error.actual && error.expected) {
123
- stack += `\n\n${chalk.bold.green("+ expected")} ${chalk.bold.red(
124
- "- actual"
125
- )}`;
126
- stack += `\n${chalk.red(
127
- `- ${error.actual.toString().split("\n").join("\n- ")}`
128
- )}`;
129
- stack += `\n${chalk.green(
130
- `+ ${error.expected.toString().split("\n").join("\n+ ")}`
131
- )}`;
132
- stack += "\n\n";
133
- }
134
-
135
- try {
136
- const record = createCallsiteRecord({
137
- forError: error,
138
- });
139
- if (record) {
140
- stack += record.renderSync({
141
- stackFilter: (frame) =>
142
- frame.getFileName().indexOf(sep) > -1 &&
143
- frame.getFileName().indexOf("node_modules") < 0 &&
144
- frame.getFileName().indexOf("internal") < 0,
145
- });
146
- }
147
- } catch (e) {
148
- console.log(e);
149
- }
127
+ stack = this.formatError(error);
150
128
  }
151
129
  if (steps) {
152
- stack = stack
153
- ? `${steps}\n\n${chalk.bold.red(
154
- "################[ Failure ]################"
155
- )}\n${stack}`
156
- : steps;
130
+ stack = this.formatSteps(stack, steps);
157
131
  }
158
132
 
159
133
  if (this.runId) {
134
+ if (Array.isArray(global.testomatioArtifacts)) {
135
+ files.push(...global.testomatioArtifacts);
136
+ global.testomatioArtifacts = [];
137
+ }
138
+
160
139
  for (const file of files) {
161
140
  uploadedFiles.push(uploadFile(file, this.runId));
162
141
  }
@@ -165,6 +144,11 @@ class TestomatClient {
165
144
  this.queue = this.queue
166
145
  .then(async () => {
167
146
  if (!this.runId) return;
147
+
148
+ global.testomatioArtifacts = [];
149
+
150
+ this.totalUploaded += uploadedFiles.filter((n) => n).length;
151
+
168
152
  const json = JsonCycle.stringify({
169
153
  api_key: this.apiKey,
170
154
  files,
@@ -196,7 +180,7 @@ class TestomatClient {
196
180
  .catch((err) => {
197
181
  if (err.response) {
198
182
  if (err.response.status >= 400) {
199
- const data = err.response.data || { message: '' };
183
+ const data = err.response.data || { message: "" };
200
184
  console.log(
201
185
  APP_PREFIX,
202
186
  chalk.blue(title),
@@ -210,7 +194,12 @@ class TestomatClient {
210
194
  `Report couldn't be processed: ${err.response.data.message}`
211
195
  );
212
196
  } else {
213
- console.log(APP_PREFIX, chalk.blue(title), "Report couldn't be processed", err);
197
+ console.log(
198
+ APP_PREFIX,
199
+ chalk.blue(title),
200
+ "Report couldn't be processed",
201
+ err
202
+ );
214
203
  }
215
204
  });
216
205
 
@@ -243,6 +232,15 @@ class TestomatClient {
243
232
  chalk.magenta(this.runUrl)
244
233
  );
245
234
  }
235
+
236
+ if (this.totalUploaded > 0) {
237
+ console.log(
238
+ APP_PREFIX,
239
+ `🗄️ Total ${this.totalUploaded} artifacts ${
240
+ isPrivate ? "privately" : chalk.bold("publicly")
241
+ } uploaded to S3 bucket `
242
+ );
243
+ }
246
244
  }
247
245
  })
248
246
  .catch((err) => {
@@ -250,6 +248,50 @@ class TestomatClient {
250
248
  });
251
249
  return this.queue;
252
250
  }
251
+
252
+ formatSteps(stack, steps) {
253
+ return stack
254
+ ? `${steps}\n\n${chalk.bold.red("################[ Failure ]################")}\n${stack}`
255
+ : steps;
256
+ }
257
+
258
+ formatError(error, message) {
259
+ if (!message) message = error.message;
260
+ if (error.inspect) message = error.inspect();
261
+
262
+ let stack = `\n${chalk.bold(message)}\n`;
263
+
264
+ // diffs for mocha, cypress, codeceptjs style
265
+ if (error.actual && error.expected) {
266
+ stack += `\n\n${chalk.bold.green("+ expected")} ${chalk.bold.red(
267
+ "- actual"
268
+ )}`;
269
+ stack += `\n${chalk.red(
270
+ `- ${error.actual.toString().split("\n").join("\n- ")}`
271
+ )}`;
272
+ stack += `\n${chalk.green(
273
+ `+ ${error.expected.toString().split("\n").join("\n+ ")}`
274
+ )}`;
275
+ stack += "\n\n";
276
+ }
277
+
278
+ try {
279
+ const record = createCallsiteRecord({
280
+ forError: error,
281
+ });
282
+ if (record) {
283
+ stack += record.renderSync({
284
+ stackFilter: (frame) =>
285
+ frame.getFileName().indexOf(sep) > -1 &&
286
+ frame.getFileName().indexOf("node_modules") < 0 &&
287
+ frame.getFileName().indexOf("internal") < 0,
288
+ });
289
+ }
290
+ return stack;
291
+ } catch (e) {
292
+ console.log(e);
293
+ }
294
+ }
253
295
  }
254
296
 
255
297
  module.exports = TestomatClient;
@@ -3,11 +3,16 @@ const fs = require("fs");
3
3
  const path = require("path");
4
4
  const chalk = require("chalk");
5
5
 
6
+ const isPrivate = process.env.TESTOMATIO_PRIVATE_ARTIFACTS;
7
+
6
8
  const uploadUsingS3 = async (filePath, runId) => {
7
9
  const region = process.env.S3_REGION;
8
10
  const bucket = process.env.S3_BUCKET;
9
11
  const accessKeyId = process.env.S3_ACCESS_KEY_ID;
10
12
  const secretAccessKey = process.env.S3_SECRET_ACCESS_KEY;
13
+ const isDisabled = process.env.TESTOMATIO_DISABLE_ARTIFACTS;
14
+
15
+ if (isDisabled) return;
11
16
 
12
17
  let contentType;
13
18
  let fileName;
@@ -34,13 +39,12 @@ const uploadUsingS3 = async (filePath, runId) => {
34
39
  Key: fileName,
35
40
  Body: file,
36
41
  ContentType: contentType,
37
- ACL: "public-read",
42
+ ACL: isPrivate ? "private" : "public-read",
38
43
  })
39
44
  .promise();
40
45
  return out.Location;
41
46
  };
42
47
 
43
-
44
48
  const uploadFile = async (filePath, runId) => {
45
49
  try {
46
50
  if (process.env.S3_BUCKET) {
@@ -54,4 +58,5 @@ const uploadFile = async (filePath, runId) => {
54
58
 
55
59
  module.exports = {
56
60
  uploadFile,
61
+ isPrivate,
57
62
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "0.4.0",
3
+ "version": "0.4.4",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "main": "./lib/reporter.js",
6
6
  "repository": "git@github.com:testomatio/reporter.git",