artes 1.4.4 → 1.4.6
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/README.md +17 -5
- package/cucumber.config.js +21 -1
- package/executer.js +213 -29
- package/package.json +1 -1
- package/src/helper/executers/helper.js +3 -0
- package/src/helper/executers/projectCreator.js +2 -0
- package/src/helper/executers/reportGenerator.js +1 -1
- package/src/helper/imports/commons.js +1 -1
- package/src/hooks/hooks.js +13 -66
- package/status-formatter.js +138 -0
package/README.md
CHANGED
|
@@ -119,6 +119,7 @@ npx artes [options]
|
|
|
119
119
|
| 📜 `--stepDef` | Specify one or more step definition files' relative paths to use (comma-separated) | `artes --stepDef "tests/steps/login.js,tests/steps/home.js"` |
|
|
120
120
|
| 🔖 `--tags` | Run tests with specified Cucumber tags | `artes --tags "@smoke or @wip"` |
|
|
121
121
|
| 🌐 `--env` | Set the environment for the test run | `artes --env "dev"` |
|
|
122
|
+
| `--saveVar` | Set the variables from CLI | `artes --saveVar '{"armud":20,"banana":200}'` |
|
|
122
123
|
| 🕶️ `--headless` | Run browser in headless mode | `artes --headless` |
|
|
123
124
|
| ⚡ `--parallel` | Run tests in parallel mode | `artes --parallel 2` |
|
|
124
125
|
| 🔁 `--retry` | Retry failed tests | `artes --retry 3` |
|
|
@@ -482,23 +483,34 @@ You can configure Artes by editing the `artes.config.js` file. Below are the def
|
|
|
482
483
|
|
|
483
484
|
| **Option** | **Default Value** | **Description** |
|
|
484
485
|
| ----------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------- |
|
|
486
|
+
| `headless` | `false` | Run browser in headless mode. |
|
|
487
|
+
| `env` | `""` | Environment name for tests. |
|
|
488
|
+
| `variables` | `{}` | Variables for tests. |
|
|
489
|
+
| `baseURL` | `""` | Base URL for API or web tests. |
|
|
485
490
|
| `timeout` | `30` | Default timeout in seconds. |
|
|
486
|
-
| `slowMo` | `0` | Default slow motion in seconds
|
|
491
|
+
| `slowMo` | `0` | Default slow motion in seconds. |
|
|
487
492
|
| `paths` | `[moduleConfig.featuresPath]` | Paths to feature files. |
|
|
488
493
|
| `require` | `[moduleConfig.stepsPath, "src/stepDefinitions/*.js", "src/hooks/hooks.js"]` | Support code paths (CommonJS). |
|
|
489
494
|
| `pomPath` | `moduleConfig.pomPath` | Path to Page Object Models. |
|
|
490
495
|
| `import` | `[]` | Support code paths. |
|
|
491
496
|
| `testPercentage` | `0` | Define test coverage percentage |
|
|
492
497
|
| `report` | `false` | Generate report |
|
|
493
|
-
| `
|
|
494
|
-
| `
|
|
495
|
-
| `
|
|
498
|
+
| `zip` | `false` | Generate zip of report |
|
|
499
|
+
| `reportSuccess` | `false` | Add screenshots and video records also for success test cases |
|
|
500
|
+
| `trace` | `false` | Enable tracing |
|
|
501
|
+
| `reportWithTrace` | `false` | Include trace in report |
|
|
496
502
|
| `format` | `["rerun:@rerun.txt", "allure-cucumberjs/reporter"]` | Formatter names/paths. |
|
|
497
503
|
| `formatOptions` | `{ "resultsDir": "allure-result" }` | Formatter options. |
|
|
498
504
|
| `parallel` | `1` | Number of parallel workers. |
|
|
505
|
+
| `browser` | `"chrome"` | Browser to use: "chrome", "firefox", "webkit". |
|
|
506
|
+
| `offline` | `false` | Run browser in offline mode. |
|
|
507
|
+
| `device` | `""` | Emulate specific device (e.g., "iPhone 13"). |
|
|
508
|
+
| `width` | `1280` | Browser width in pixels. |
|
|
509
|
+
| `height` | `720` | Browser height in pixels. |
|
|
510
|
+
| `maximizeScreen` | `true` | Maximize browser window on start. |
|
|
499
511
|
| `dryRun` | `false` | Prepare test run without execution. |
|
|
500
512
|
| `failFast` | `false` | Stop on first test failure. |
|
|
501
|
-
| `forceExit` | `false` | Force `process.exit()` after tests
|
|
513
|
+
| `forceExit` | `false` | Force `process.exit()` after tests |
|
|
502
514
|
| `strict` | `true` | Fail on pending steps. |
|
|
503
515
|
| `backtrace` | `false` | Show full backtrace for errors. |
|
|
504
516
|
| `tags` | `""` | Tag expression to filter scenarios. |
|
package/cucumber.config.js
CHANGED
|
@@ -14,7 +14,7 @@ try {
|
|
|
14
14
|
console.log("Proceeding with default config.");
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const defaultFormats = ["rerun:@rerun.txt",
|
|
17
|
+
const defaultFormats = ["rerun:@rerun.txt", "progress-bar", './status-formatter.js:null'];
|
|
18
18
|
|
|
19
19
|
const userFormatsFromEnv = process.env.REPORT_FORMAT
|
|
20
20
|
? JSON.parse(process.env.REPORT_FORMAT)
|
|
@@ -51,6 +51,25 @@ function resolveEnv(artesConfig) {
|
|
|
51
51
|
}
|
|
52
52
|
const env = resolveEnv(artesConfig);
|
|
53
53
|
|
|
54
|
+
function loadVariables(cliVariables, artesConfigVariables) {
|
|
55
|
+
|
|
56
|
+
if (cliVariables) {
|
|
57
|
+
try {
|
|
58
|
+
cliVariables = JSON.parse(cliVariables);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error("Invalid JSON in process.env.VARS:", process.env.VARS);
|
|
61
|
+
envVars = {};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const mergedVars = {
|
|
66
|
+
...(artesConfigVariables || {}),
|
|
67
|
+
...cliVariables,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return mergedVars;
|
|
71
|
+
}
|
|
72
|
+
|
|
54
73
|
const resolveFeaturePaths = (basePath, value) => {
|
|
55
74
|
return value
|
|
56
75
|
.split(',')
|
|
@@ -162,6 +181,7 @@ module.exports = {
|
|
|
162
181
|
zip: process.env.ZIP == "true" ? true : artesConfig.zip ? true : false,
|
|
163
182
|
},
|
|
164
183
|
env: env,
|
|
184
|
+
variables: loadVariables(process.env.VARS, artesConfig.variables),
|
|
165
185
|
baseURL: process.env.BASE_URL
|
|
166
186
|
? JSON.parse(process.env.BASE_URL)
|
|
167
187
|
: artesConfig?.baseURL
|
package/executer.js
CHANGED
|
@@ -10,17 +10,23 @@ const {
|
|
|
10
10
|
const { logPomWarnings } = require("./src/helper/controller/pomCollector");
|
|
11
11
|
const fs = require("fs");
|
|
12
12
|
const path = require("path");
|
|
13
|
+
const { spawnSync } = require("child_process");
|
|
13
14
|
|
|
14
|
-
const
|
|
15
|
+
const artesConfigPath = path.resolve(process.cwd(), "artes.config.js");
|
|
15
16
|
|
|
16
17
|
let artesConfig = {};
|
|
17
18
|
|
|
18
|
-
if (fs.existsSync(
|
|
19
|
-
artesConfig = require(
|
|
19
|
+
if (fs.existsSync(artesConfigPath)) {
|
|
20
|
+
artesConfig = require(artesConfigPath);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const args = process.argv.slice(2);
|
|
23
24
|
|
|
25
|
+
const getArgValue = (flag) => {
|
|
26
|
+
const index = args.indexOf(flag);
|
|
27
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
28
|
+
};
|
|
29
|
+
|
|
24
30
|
const flags = {
|
|
25
31
|
help: args.includes("-h") || args.includes("--help"),
|
|
26
32
|
version: args.includes("-v") || args.includes("--version"),
|
|
@@ -37,6 +43,7 @@ const flags = {
|
|
|
37
43
|
stepDef: args.includes("--stepDef"),
|
|
38
44
|
tags: args.includes("--tags"),
|
|
39
45
|
env: args.includes("--env"),
|
|
46
|
+
saveVar: args.includes("--saveVar"),
|
|
40
47
|
headless: args.includes("--headless"),
|
|
41
48
|
parallel: args.includes("--parallel"),
|
|
42
49
|
retry: args.includes("--retry"),
|
|
@@ -54,25 +61,30 @@ const flags = {
|
|
|
54
61
|
slowMo: args.includes("--slowMo"),
|
|
55
62
|
};
|
|
56
63
|
|
|
57
|
-
|
|
58
|
-
const
|
|
64
|
+
|
|
65
|
+
const env = getArgValue("--env");
|
|
66
|
+
const vars = getArgValue("--saveVar");
|
|
67
|
+
const featureFiles = getArgValue("--features");
|
|
59
68
|
const features = flags.features && featureFiles;
|
|
60
|
-
const stepDef =
|
|
61
|
-
const tags =
|
|
62
|
-
const parallel =
|
|
63
|
-
const retry =
|
|
64
|
-
const rerun =
|
|
65
|
-
const percentage =
|
|
66
|
-
const browser =
|
|
67
|
-
const device =
|
|
68
|
-
const baseURL =
|
|
69
|
-
const width =
|
|
70
|
-
const height =
|
|
71
|
-
const timeout =
|
|
72
|
-
const slowMo =
|
|
69
|
+
const stepDef = getArgValue("--stepDef");
|
|
70
|
+
const tags = getArgValue("--tags");
|
|
71
|
+
const parallel = getArgValue("--parallel");
|
|
72
|
+
const retry = getArgValue("--retry");
|
|
73
|
+
const rerun = getArgValue("--rerun");
|
|
74
|
+
const percentage = getArgValue("--percentage");
|
|
75
|
+
const browser = getArgValue("--browser");
|
|
76
|
+
const device = getArgValue("--device");
|
|
77
|
+
const baseURL = getArgValue("--baseURL");
|
|
78
|
+
const width = getArgValue("--width");
|
|
79
|
+
const height = getArgValue("--height");
|
|
80
|
+
const timeout = getArgValue("--timeout");
|
|
81
|
+
const slowMo = getArgValue("--slowMo");
|
|
82
|
+
|
|
73
83
|
|
|
74
84
|
flags.env ? (process.env.ENV = env) : "";
|
|
75
85
|
|
|
86
|
+
vars ? (process.env.VARS = vars) : "";
|
|
87
|
+
|
|
76
88
|
flags.reportWithTrace ||
|
|
77
89
|
artesConfig.reportWithTrace ||
|
|
78
90
|
flags.report ||
|
|
@@ -146,6 +158,140 @@ flags.timeout ? (process.env.TIMEOUT = timeout) : "";
|
|
|
146
158
|
|
|
147
159
|
flags.slowMo ? (process.env.SLOWMO = slowMo) : "";
|
|
148
160
|
|
|
161
|
+
|
|
162
|
+
function findDuplicateTestNames() {
|
|
163
|
+
const testStatusFile = path.join(process.cwd(), "node_modules", "artes" , "test-status", 'test-status.txt');
|
|
164
|
+
|
|
165
|
+
if (!fs.existsSync(testStatusFile)) {
|
|
166
|
+
console.error('test-status.txt not found');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const content = fs.readFileSync(testStatusFile, 'utf8');
|
|
171
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
172
|
+
|
|
173
|
+
const testNameToFiles = {};
|
|
174
|
+
|
|
175
|
+
lines.forEach(line => {
|
|
176
|
+
const parts = line.split(' | ');
|
|
177
|
+
if (parts.length < 5) return;
|
|
178
|
+
|
|
179
|
+
const testName = parts[2].trim();
|
|
180
|
+
const filePath = parts[4].trim();
|
|
181
|
+
|
|
182
|
+
if (!testNameToFiles[testName]) {
|
|
183
|
+
testNameToFiles[testName] = new Set();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
testNameToFiles[testName].add(filePath);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const duplicates = {};
|
|
190
|
+
|
|
191
|
+
Object.entries(testNameToFiles).forEach(([testName, files]) => {
|
|
192
|
+
if (files.size > 1) {
|
|
193
|
+
duplicates[testName] = Array.from(files);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (Object.keys(duplicates).length > 0) {
|
|
198
|
+
console.warn('\n\x1b[33m[WARNING] Duplicate scenarios names found: This will effect your reporting');
|
|
199
|
+
Object.entries(duplicates).forEach(([testName, files]) => {
|
|
200
|
+
console.log(`\x1b[33m"${testName}" exists in:`);
|
|
201
|
+
files.forEach(file => {
|
|
202
|
+
console.log(` - ${file}`);
|
|
203
|
+
});
|
|
204
|
+
console.log('');
|
|
205
|
+
});
|
|
206
|
+
console.log("\x1b[0m");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return duplicates;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
function testCoverageCalculation() {
|
|
214
|
+
|
|
215
|
+
const testStatusFile = path.join(process.cwd(), "node_modules", "artes" , "test-status", 'test-status.txt');
|
|
216
|
+
|
|
217
|
+
if (!fs.existsSync(testStatusFile)) {
|
|
218
|
+
console.error('test-status.txt not found');
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const content = fs.readFileSync(testStatusFile, 'utf8');
|
|
223
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
224
|
+
|
|
225
|
+
const map = {};
|
|
226
|
+
const retriedTests = [];
|
|
227
|
+
const uuidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
|
|
228
|
+
|
|
229
|
+
lines.forEach(line => {
|
|
230
|
+
const parts = line.split(' | ');
|
|
231
|
+
if (parts.length < 5) return;
|
|
232
|
+
|
|
233
|
+
const timestamp = parts[0].trim();
|
|
234
|
+
const status = parts[1].trim();
|
|
235
|
+
const scenario = parts[2].trim();
|
|
236
|
+
const id = parts[3].trim();
|
|
237
|
+
const uri = parts[4].trim();
|
|
238
|
+
|
|
239
|
+
if (!uuidRegex.test(id)) return;
|
|
240
|
+
|
|
241
|
+
if (!map[id]) {
|
|
242
|
+
map[id] = {
|
|
243
|
+
count: 1,
|
|
244
|
+
latest: { status, scenario, timestamp, uri }
|
|
245
|
+
};
|
|
246
|
+
} else {
|
|
247
|
+
map[id].count++;
|
|
248
|
+
if (timestamp > map[id].latest.timestamp) {
|
|
249
|
+
map[id].latest = { status, scenario, timestamp, uri };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
let total = 0;
|
|
255
|
+
let notPassed = 0;
|
|
256
|
+
|
|
257
|
+
Object.entries(map).forEach(([id, data]) => {
|
|
258
|
+
total++;
|
|
259
|
+
|
|
260
|
+
if (data.count > 1) {
|
|
261
|
+
retriedTests.push({
|
|
262
|
+
scenario: data.latest.scenario,
|
|
263
|
+
id,
|
|
264
|
+
count: data.count
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (data.latest.status !== 'PASSED') {
|
|
269
|
+
notPassed++;
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (retriedTests.length > 0) {
|
|
274
|
+
console.warn('\n\x1b[33mRetried test cases:');
|
|
275
|
+
retriedTests.forEach(t => {
|
|
276
|
+
console.warn(`- "${t.scenario}" ran ${t.count} times`);
|
|
277
|
+
});
|
|
278
|
+
console.log("\x1b[0m");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
percentage: (total - notPassed) / total * 100,
|
|
283
|
+
totalTests: total,
|
|
284
|
+
notPassed,
|
|
285
|
+
passed: total - notPassed,
|
|
286
|
+
latestStatuses: Object.fromEntries(
|
|
287
|
+
Object.entries(map).map(([id, data]) => [
|
|
288
|
+
id,
|
|
289
|
+
data.latest.status
|
|
290
|
+
])
|
|
291
|
+
)
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
149
295
|
function main() {
|
|
150
296
|
if (flags.help) return showHelp();
|
|
151
297
|
if (flags.version) return showVersion();
|
|
@@ -155,6 +301,37 @@ function main() {
|
|
|
155
301
|
|
|
156
302
|
logPomWarnings();
|
|
157
303
|
|
|
304
|
+
findDuplicateTestNames();
|
|
305
|
+
|
|
306
|
+
const testCoverage = testCoverageCalculation()
|
|
307
|
+
|
|
308
|
+
const testPercentage = (process.env.PERCENTAGE ? Number(process.env.PERCENTAGE) : artesConfig.testPercentage || 0)
|
|
309
|
+
|
|
310
|
+
if (testPercentage > 0) {
|
|
311
|
+
|
|
312
|
+
const meetsThreshold = testCoverage.percentage >= testPercentage
|
|
313
|
+
|
|
314
|
+
if (meetsThreshold) {
|
|
315
|
+
console.log(
|
|
316
|
+
`✅ Tests passed required ${testPercentage}% success rate with ${testCoverage.percentage.toFixed(2)}%!`,
|
|
317
|
+
);
|
|
318
|
+
process.env.EXIT_CODE = parseInt(0, 10);
|
|
319
|
+
} else {
|
|
320
|
+
console.log(
|
|
321
|
+
`❌ Tests failed required ${testPercentage}% success rate with ${testCoverage.percentage.toFixed(2)}%!`,
|
|
322
|
+
);
|
|
323
|
+
process.env.EXIT_CODE = parseInt(1, 10);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (fs.existsSync(path.join(process.cwd(), "node_modules", "artes" , "@rerun.txt"))) {
|
|
328
|
+
spawnSync("mv", ["@rerun.txt", process.cwd()], {
|
|
329
|
+
cwd: path.join(process.cwd(), "node_modules", "artes"),
|
|
330
|
+
stdio: "inherit",
|
|
331
|
+
shell: true,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
158
335
|
if (
|
|
159
336
|
flags.reportWithTrace ||
|
|
160
337
|
artesConfig.reportWithTrace ||
|
|
@@ -163,18 +340,25 @@ function main() {
|
|
|
163
340
|
)
|
|
164
341
|
generateReport();
|
|
165
342
|
|
|
166
|
-
if (
|
|
167
|
-
fs.existsSync(
|
|
168
|
-
path.join(process.cwd(), "node_modules", "artes", "EXIT_CODE.txt"),
|
|
169
|
-
)
|
|
170
|
-
) {
|
|
171
|
-
const data = fs.readFileSync(
|
|
172
|
-
path.join(process.cwd(), "node_modules", "artes", "EXIT_CODE.txt"),
|
|
173
|
-
"utf8",
|
|
174
|
-
);
|
|
175
|
-
process.env.EXIT_CODE = parseInt(data, 10);
|
|
176
|
-
}
|
|
177
343
|
|
|
344
|
+
if (!( process.env.TRACE === "true"
|
|
345
|
+
? process.env.TRACE
|
|
346
|
+
: artesConfig.trace || false)) {
|
|
347
|
+
spawnSync(
|
|
348
|
+
"npx",
|
|
349
|
+
[
|
|
350
|
+
"rimraf",
|
|
351
|
+
"--no-glob",
|
|
352
|
+
path.join(process.cwd(), "traces"),
|
|
353
|
+
],
|
|
354
|
+
{
|
|
355
|
+
cwd: process.cwd(),
|
|
356
|
+
stdio: "inherit",
|
|
357
|
+
shell: false,
|
|
358
|
+
},
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
178
362
|
cleanUp();
|
|
179
363
|
process.exit(process.env.EXIT_CODE);
|
|
180
364
|
}
|
package/package.json
CHANGED
|
@@ -54,6 +54,9 @@ function showHelp() {
|
|
|
54
54
|
|
|
55
55
|
🌐 --env Set environment for the test run
|
|
56
56
|
Usage: artes --env "dev"
|
|
57
|
+
|
|
58
|
+
--saveVar Set variables from cli
|
|
59
|
+
artes --saveVar '{"armud":20,"banana":200}'
|
|
57
60
|
|
|
58
61
|
🕶️ --headless Run browser in headless mode
|
|
59
62
|
Usage: artes --headless
|
|
@@ -47,6 +47,7 @@ function createProject(createYes, noDeps) {
|
|
|
47
47
|
|
|
48
48
|
// Configuration options:
|
|
49
49
|
// env: "", // string - Environment name for tests
|
|
50
|
+
// variables: {} // object - Variables for tests
|
|
50
51
|
// testPercentage: 0, // number - Minimum success rate percentage(Default: 0)
|
|
51
52
|
// baseURL: "", // string - Base URL for API tests
|
|
52
53
|
// paths: [], // string[] - Paths to feature files
|
|
@@ -56,6 +57,7 @@ function createProject(createYes, noDeps) {
|
|
|
56
57
|
// slowMo: 0, // number - Slow down test execution (Default: 0 seconds)
|
|
57
58
|
// parallel: 0, // number - Number of parallel workers
|
|
58
59
|
// report: true // boolean - Generate report
|
|
60
|
+
// zip: false // boolean - Generate zip of report
|
|
59
61
|
// reportSuccess: false, // boolean - Add screenshots and video records to report also for success test cases
|
|
60
62
|
// trace: false, // boolean - Enable tracing
|
|
61
63
|
// reportWithTrace: false, // boolean - Include trace in report
|
|
@@ -30,7 +30,7 @@ function generateReport() {
|
|
|
30
30
|
);
|
|
31
31
|
|
|
32
32
|
if (fs.existsSync(moduleConfig.reportPath) && process.env.ZIP === "true") {
|
|
33
|
-
console.log(`🗜️ Zipping report folder
|
|
33
|
+
console.log(`🗜️ Zipping report folder...`);
|
|
34
34
|
|
|
35
35
|
const zipPath = path.join(
|
|
36
36
|
path.dirname(moduleConfig.reportPath),
|
|
@@ -35,7 +35,7 @@ const moduleConfig = {
|
|
|
35
35
|
stepsPath: path.join(projectPath, "/tests/steps/*.js"),
|
|
36
36
|
pomPath: path.join(projectPath, "/tests/POMs"),
|
|
37
37
|
cleanUpPaths:
|
|
38
|
-
"allure-result allure-results test-results @rerun.txt
|
|
38
|
+
"allure-result allure-results test-results @rerun.txt test-status null pomDuplicateWarnings.json",
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
module.exports = {
|
package/src/hooks/hooks.js
CHANGED
|
@@ -8,18 +8,16 @@ const {
|
|
|
8
8
|
BeforeStep,
|
|
9
9
|
AfterAll,
|
|
10
10
|
} = require("@cucumber/cucumber");
|
|
11
|
-
const { spawnSync } = require("child_process");
|
|
12
11
|
const { invokeBrowser } = require("../helper/contextManager/browserManager");
|
|
13
12
|
const { invokeRequest } = require("../helper/contextManager/requestManager");
|
|
14
13
|
const {
|
|
15
|
-
pomCollector
|
|
16
|
-
logPomWarnings,
|
|
14
|
+
pomCollector
|
|
17
15
|
} = require("../helper/controller/pomCollector");
|
|
18
16
|
const cucumberConfig = require("../../cucumber.config");
|
|
19
17
|
const { context } = require("./context");
|
|
20
18
|
const fs = require("fs");
|
|
21
19
|
const path = require("path");
|
|
22
|
-
const { moduleConfig } = require("artes/src/helper/imports/commons");
|
|
20
|
+
const { moduleConfig, saveVar } = require("artes/src/helper/imports/commons");
|
|
23
21
|
require("allure-cucumberjs");
|
|
24
22
|
const allure = require("allure-js-commons");
|
|
25
23
|
|
|
@@ -43,14 +41,6 @@ async function attachResponse(attachFn) {
|
|
|
43
41
|
}
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
function saveTestStatus(result, pickle) {
|
|
47
|
-
fs.mkdirSync(statusDir, { recursive: true });
|
|
48
|
-
fs.writeFileSync(
|
|
49
|
-
path.join(statusDir, `${result.status}-${pickle.id}.txt`),
|
|
50
|
-
"",
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
44
|
const projectHooksPath = path.resolve(
|
|
55
45
|
moduleConfig.projectPath,
|
|
56
46
|
"tests/steps/hooks.js",
|
|
@@ -81,6 +71,15 @@ BeforeAll(async () => {
|
|
|
81
71
|
Before(async function ({pickle}) {
|
|
82
72
|
context.vars = {};
|
|
83
73
|
|
|
74
|
+
const vars = await cucumberConfig.variables
|
|
75
|
+
|
|
76
|
+
if (vars && typeof vars === "object") {
|
|
77
|
+
for (let [key, value] of Object.entries(vars)) {
|
|
78
|
+
|
|
79
|
+
saveVar(value, key);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
84
83
|
const envFilePath = path.join(
|
|
85
84
|
moduleConfig.projectPath,
|
|
86
85
|
"tests",
|
|
@@ -142,7 +141,7 @@ AfterStep(async function ({ pickleStep }) {
|
|
|
142
141
|
}
|
|
143
142
|
});
|
|
144
143
|
|
|
145
|
-
After(async function ({ pickle
|
|
144
|
+
After(async function ({result, pickle}) {
|
|
146
145
|
if (typeof projectHooks.After === "function") {
|
|
147
146
|
await projectHooks.After();
|
|
148
147
|
}
|
|
@@ -156,12 +155,11 @@ After(async function ({ pickle, result }) {
|
|
|
156
155
|
await allure.attachment("Screenshot", screenshotBuffer, "image/png");
|
|
157
156
|
}
|
|
158
157
|
|
|
159
|
-
saveTestStatus(result, pickle);
|
|
160
158
|
|
|
161
159
|
if (cucumberConfig.default.reportWithTrace || cucumberConfig.default.trace) {
|
|
162
160
|
var tracePath = path.join(
|
|
163
161
|
moduleConfig.projectPath,
|
|
164
|
-
`./traces/${pickle.name.replaceAll(" ", "_")}.zip`,
|
|
162
|
+
`./traces/${pickle.name.replaceAll(" ", "_")}-${pickle.id}.zip`,
|
|
165
163
|
);
|
|
166
164
|
}
|
|
167
165
|
|
|
@@ -212,55 +210,4 @@ AfterAll(async () => {
|
|
|
212
210
|
await projectHooks.AfterAll();
|
|
213
211
|
}
|
|
214
212
|
|
|
215
|
-
logPomWarnings();
|
|
216
|
-
|
|
217
|
-
if (!cucumberConfig.default.trace) {
|
|
218
|
-
spawnSync(
|
|
219
|
-
"npx",
|
|
220
|
-
[
|
|
221
|
-
"rimraf",
|
|
222
|
-
"--no-glob",
|
|
223
|
-
path.join(moduleConfig.projectPath, "./traces"),
|
|
224
|
-
],
|
|
225
|
-
{
|
|
226
|
-
cwd: moduleConfig.projectPath,
|
|
227
|
-
stdio: "inherit",
|
|
228
|
-
shell: false,
|
|
229
|
-
},
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (!fs.existsSync(statusDir)) return;
|
|
234
|
-
|
|
235
|
-
const files = fs.readdirSync(statusDir);
|
|
236
|
-
const passedCount = files.filter((f) => f.split("-")[0] === "PASSED").length;
|
|
237
|
-
const totalTests = files.length;
|
|
238
|
-
const successPercentage = (passedCount / totalTests) * 100;
|
|
239
|
-
|
|
240
|
-
const failed = files.filter((f) => f.split("-")[0] === "FAILED").length;
|
|
241
|
-
|
|
242
|
-
if (failed > 0) {
|
|
243
|
-
spawnSync("mv", ["@rerun.txt", moduleConfig.projectPath], {
|
|
244
|
-
cwd: path.join(moduleConfig.projectPath, "node_modules", "artes"),
|
|
245
|
-
stdio: "ignore",
|
|
246
|
-
shell: true,
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (cucumberConfig.default.testPercentage > 0) {
|
|
251
|
-
const meetsThreshold =
|
|
252
|
-
successPercentage >= cucumberConfig.default.testPercentage;
|
|
253
|
-
|
|
254
|
-
if (meetsThreshold) {
|
|
255
|
-
console.log(
|
|
256
|
-
`✅ Tests passed required ${cucumberConfig.default.testPercentage}% success rate with ${successPercentage.toFixed(2)}%!`,
|
|
257
|
-
);
|
|
258
|
-
fs.writeFileSync(path.join(process.cwd(), "EXIT_CODE.txt"), "0");
|
|
259
|
-
} else {
|
|
260
|
-
console.log(
|
|
261
|
-
`❌ Tests failed required ${cucumberConfig.default.testPercentage}% success rate with ${successPercentage.toFixed(2)}%!`,
|
|
262
|
-
);
|
|
263
|
-
fs.writeFileSync(path.join(process.cwd(), "EXIT_CODE.txt"), "1");
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
213
|
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const { Formatter } = require('@cucumber/cucumber');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
class StatusFormatter extends Formatter {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
super(options);
|
|
8
|
+
|
|
9
|
+
const outputDir = './test-status';
|
|
10
|
+
if (!fs.existsSync(outputDir)) {
|
|
11
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const workerId = process.env.CUCUMBER_WORKER_ID || '0';
|
|
15
|
+
this.workerId = workerId;
|
|
16
|
+
this.outputFile = path.join(outputDir, `test-results-${workerId}.txt`);
|
|
17
|
+
this.outputDir = outputDir;
|
|
18
|
+
|
|
19
|
+
fs.writeFileSync(this.outputFile, '', 'utf8');
|
|
20
|
+
|
|
21
|
+
this.pickles = new Map();
|
|
22
|
+
this.testCases = new Map();
|
|
23
|
+
this.testCaseStartedIdToAttempt = new Map();
|
|
24
|
+
|
|
25
|
+
const { eventBroadcaster } = options;
|
|
26
|
+
|
|
27
|
+
eventBroadcaster.on('envelope', (envelope) => {
|
|
28
|
+
if (envelope.pickle) {
|
|
29
|
+
this.pickles.set(envelope.pickle.id, envelope.pickle);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (envelope.testCase) {
|
|
33
|
+
this.testCases.set(envelope.testCase.id, envelope.testCase);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (envelope.testCaseStarted) {
|
|
37
|
+
this.testCaseStartedIdToAttempt.set(
|
|
38
|
+
envelope.testCaseStarted.id,
|
|
39
|
+
envelope.testCaseStarted.testCaseId
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (envelope.testCaseFinished) {
|
|
44
|
+
this.handleTestCaseFinished(envelope.testCaseFinished);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (envelope.testRunFinished) {
|
|
48
|
+
this.handleTestRunFinished();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getFormattedTimestamp() {
|
|
54
|
+
const now = new Date();
|
|
55
|
+
const YYYY = now.getFullYear();
|
|
56
|
+
const MM = String(now.getMonth() + 1).padStart(2, '0');
|
|
57
|
+
const DD = String(now.getDate()).padStart(2, '0');
|
|
58
|
+
const hh = String(now.getHours()).padStart(2, '0');
|
|
59
|
+
const mm = String(now.getMinutes()).padStart(2, '0');
|
|
60
|
+
const ss = String(now.getSeconds()).padStart(2, '0');
|
|
61
|
+
const ms = String(now.getMilliseconds()).padStart(3, '0');
|
|
62
|
+
|
|
63
|
+
return `${YYYY}${MM}${DD}-${hh}${mm}${ss}-${ms}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
handleTestCaseFinished(testCaseFinished) {
|
|
67
|
+
try {
|
|
68
|
+
const timestamp = this.getFormattedTimestamp();
|
|
69
|
+
|
|
70
|
+
const testCaseId = this.testCaseStartedIdToAttempt.get(
|
|
71
|
+
testCaseFinished.testCaseStartedId
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (!testCaseId) return;
|
|
75
|
+
|
|
76
|
+
const testCase = this.testCases.get(testCaseId);
|
|
77
|
+
if (!testCase) return;
|
|
78
|
+
|
|
79
|
+
const pickle = this.pickles.get(testCase.pickleId);
|
|
80
|
+
if (!pickle) return;
|
|
81
|
+
|
|
82
|
+
const testCaseAttempt = this.eventDataCollector.getTestCaseAttempt(
|
|
83
|
+
testCaseFinished.testCaseStartedId
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const status = testCaseAttempt?.worstTestStepResult?.status || 'UNKNOWN';
|
|
87
|
+
|
|
88
|
+
const info = {
|
|
89
|
+
name: pickle.name,
|
|
90
|
+
id: pickle.id,
|
|
91
|
+
uri: pickle.uri
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const line = `${timestamp} | ${status.toUpperCase()} | ${info.name} | ${info.id} | ${info.uri}\n`;
|
|
95
|
+
|
|
96
|
+
fs.appendFileSync(this.outputFile, line, 'utf8');
|
|
97
|
+
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('Error in handleTestCaseFinished:', error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
handleTestRunFinished() {
|
|
104
|
+
if (this.workerId !== '0') {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
setTimeout(() => {
|
|
109
|
+
this.mergeResults();
|
|
110
|
+
}, 1000);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
mergeResults() {
|
|
114
|
+
try {
|
|
115
|
+
const testStatusFile = path.join(this.outputDir, 'test-status.txt');
|
|
116
|
+
|
|
117
|
+
const files = fs.readdirSync(this.outputDir)
|
|
118
|
+
.filter(f => f.startsWith('test-results-') && f.endsWith('.txt'))
|
|
119
|
+
.sort();
|
|
120
|
+
|
|
121
|
+
if (files.length === 0) {
|
|
122
|
+
console.log('No result files found to merge');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const combined = files
|
|
127
|
+
.map(f => fs.readFileSync(path.join(this.outputDir, f), 'utf8'))
|
|
128
|
+
.join('');
|
|
129
|
+
|
|
130
|
+
fs.writeFileSync(testStatusFile, combined, 'utf8');
|
|
131
|
+
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error('Error merging results:', error);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = StatusFormatter;
|