artes 1.4.4 → 1.4.5

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 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
- | `reportSuccess` | `false` | Add screenshots and video records for also success test cases |
494
- | `trace` | `false` | Enable trace |
495
- | `reportWithTrace` | `false` | Add trace to the report |
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. |
@@ -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(process.env.VARS);
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,13 +10,14 @@ 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 configPath = path.resolve(process.cwd(), "artes.config.js");
15
+ const artesConfigPath = path.resolve(process.cwd(), "artes.config.js");
15
16
 
16
17
  let artesConfig = {};
17
18
 
18
- if (fs.existsSync(configPath)) {
19
- artesConfig = require(configPath);
19
+ if (fs.existsSync(artesConfigPath)) {
20
+ artesConfig = require(artesConfigPath);
20
21
  }
21
22
 
22
23
  const args = process.argv.slice(2);
@@ -37,6 +38,7 @@ const flags = {
37
38
  stepDef: args.includes("--stepDef"),
38
39
  tags: args.includes("--tags"),
39
40
  env: args.includes("--env"),
41
+ saveVar: args.includes("--saveVar"),
40
42
  headless: args.includes("--headless"),
41
43
  parallel: args.includes("--parallel"),
42
44
  retry: args.includes("--retry"),
@@ -55,6 +57,7 @@ const flags = {
55
57
  };
56
58
 
57
59
  const env = args[args.indexOf("--env") + 1];
60
+ const vars = args[args.indexOf("--saveVar") + 1]
58
61
  const featureFiles = args[args.indexOf("--features") + 1];
59
62
  const features = flags.features && featureFiles;
60
63
  const stepDef = args[args.indexOf("--stepDef") + 1];
@@ -73,6 +76,8 @@ const slowMo = args[args.indexOf("--slowMo") + 1];
73
76
 
74
77
  flags.env ? (process.env.ENV = env) : "";
75
78
 
79
+ vars ? (process.env.VARS = vars) : "";
80
+
76
81
  flags.reportWithTrace ||
77
82
  artesConfig.reportWithTrace ||
78
83
  flags.report ||
@@ -146,6 +151,83 @@ flags.timeout ? (process.env.TIMEOUT = timeout) : "";
146
151
 
147
152
  flags.slowMo ? (process.env.SLOWMO = slowMo) : "";
148
153
 
154
+
155
+ function testCoverageCalculation(testStatusDir){
156
+ if(fs.existsSync(testStatusDir)){
157
+ const files = fs.readdirSync(testStatusDir);
158
+
159
+ const map = {};
160
+ const retriedTests = [];
161
+ const uuidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
162
+
163
+ files.forEach(file => {
164
+ const match = file.match(uuidRegex);
165
+ if (!match) return;
166
+
167
+ const id = match[0];
168
+
169
+ const beforeId = file.substring(0, file.indexOf(id) - 1);
170
+ const afterId = file.substring(file.indexOf(id) + id.length + 1);
171
+
172
+ const status = beforeId.split('-')[0];
173
+ const scenario = beforeId.substring(status.length + 1);
174
+ const timestamp = afterId;
175
+
176
+ if (!map[id]) {
177
+ map[id] = {
178
+ count: 1,
179
+ latest: { status, scenario, timestamp }
180
+ };
181
+ } else {
182
+ map[id].count++;
183
+ if (timestamp > map[id].latest.timestamp) {
184
+ map[id].latest = { status, scenario, timestamp };
185
+ }
186
+ }
187
+ });
188
+
189
+ let total = 0;
190
+ let notPassed = 0;
191
+
192
+ Object.entries(map).forEach(([id, data]) => {
193
+ total++;
194
+
195
+ if (data.count > 1) {
196
+ retriedTests.push({
197
+ scenario: data.latest.scenario,
198
+ id,
199
+ count: data.count
200
+ });
201
+ }
202
+
203
+ if (data.latest.status !== 'PASSED') {
204
+ notPassed++;
205
+ }
206
+ });
207
+
208
+ if (retriedTests.length > 0) {
209
+ console.warn('\n\x1b[33mRetried test cases:');
210
+ retriedTests.forEach(t => {
211
+ console.warn(`- "${t.scenario}" ran ${t.count} times`);
212
+ });
213
+ console.log("\x1b[0m");
214
+ }
215
+
216
+ return {
217
+ percentage : (total - notPassed)/total*100,
218
+ totalTests: total,
219
+ notPassed,
220
+ passed: total - notPassed,
221
+ latestStatuses: Object.fromEntries(
222
+ Object.entries(map).map(([id, data]) => [
223
+ id,
224
+ data.latest.status
225
+ ])
226
+ )
227
+ };
228
+ }
229
+ }
230
+
149
231
  function main() {
150
232
  if (flags.help) return showHelp();
151
233
  if (flags.version) return showVersion();
@@ -155,6 +237,35 @@ function main() {
155
237
 
156
238
  logPomWarnings();
157
239
 
240
+ const testCoverage = testCoverageCalculation(path.join(process.cwd(), "node_modules", "artes" , "testsStatus"))
241
+
242
+ const testPercentage = (process.env.PERCENTAGE ? Number(process.env.PERCENTAGE) : artesConfig.testPercentage || 0)
243
+
244
+ if (testPercentage > 0) {
245
+
246
+ const meetsThreshold = testCoverage.percentage >= testPercentage
247
+
248
+ if (meetsThreshold) {
249
+ console.log(
250
+ `✅ Tests passed required ${testPercentage}% success rate with ${testCoverage.percentage.toFixed(2)}%!`,
251
+ );
252
+ process.env.EXIT_CODE = parseInt(0, 10);
253
+ } else {
254
+ console.log(
255
+ `❌ Tests failed required ${testPercentage}% success rate with ${testCoverage.percentage.toFixed(2)}%!`,
256
+ );
257
+ process.env.EXIT_CODE = parseInt(1, 10);
258
+ }
259
+ }
260
+
261
+ if (fs.existsSync(path.join(process.cwd(), "node_modules", "artes" , "@rerun.txt"))) {
262
+ spawnSync("mv", ["@rerun.txt", process.cwd()], {
263
+ cwd: path.join(process.cwd(), "node_modules", "artes"),
264
+ stdio: "inherit",
265
+ shell: true,
266
+ });
267
+ }
268
+
158
269
  if (
159
270
  flags.reportWithTrace ||
160
271
  artesConfig.reportWithTrace ||
@@ -163,18 +274,25 @@ function main() {
163
274
  )
164
275
  generateReport();
165
276
 
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
277
 
278
+ if (!( process.env.TRACE === "true"
279
+ ? process.env.TRACE
280
+ : artesConfig.trace || false)) {
281
+ spawnSync(
282
+ "npx",
283
+ [
284
+ "rimraf",
285
+ "--no-glob",
286
+ path.join(process.cwd(), "traces"),
287
+ ],
288
+ {
289
+ cwd: process.cwd(),
290
+ stdio: "inherit",
291
+ shell: false,
292
+ },
293
+ );
294
+ }
295
+
178
296
  cleanUp();
179
297
  process.exit(process.env.EXIT_CODE);
180
298
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "artes",
3
- "version": "1.4.4",
3
+ "version": "1.4.5",
4
4
  "description": "The simplest way to automate UI and API tests using Cucumber-style steps.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -30,6 +30,7 @@
30
30
  "dayjs": "1.11.13",
31
31
  "deasync": "^0.1.31",
32
32
  "playwright": "^1.58.2",
33
+ "proper-lockfile": "^4.1.2",
33
34
  "rimraf": "6.0.1"
34
35
  },
35
36
  "repository": {
@@ -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
@@ -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 testsStatus EXIT_CODE.txt pomDuplicateWarnings.json",
38
+ "allure-result allure-results test-results @rerun.txt testsStatus pomDuplicateWarnings.json",
39
39
  };
40
40
 
41
41
  module.exports = {
@@ -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
 
@@ -44,11 +42,26 @@ async function attachResponse(attachFn) {
44
42
  }
45
43
 
46
44
  function saveTestStatus(result, pickle) {
45
+
47
46
  fs.mkdirSync(statusDir, { recursive: true });
48
- fs.writeFileSync(
49
- path.join(statusDir, `${result.status}-${pickle.id}.txt`),
50
- "",
51
- );
47
+
48
+ const now = new Date();
49
+ const YYYY = now.getFullYear();
50
+ const MM = String(now.getMonth() + 1).padStart(2, '0');
51
+ const DD = String(now.getDate()).padStart(2, '0');
52
+ const hh = String(now.getHours()).padStart(2, '0');
53
+ const mm = String(now.getMinutes()).padStart(2, '0');
54
+ const ss = String(now.getSeconds()).padStart(2, '0');
55
+ const ms = String(now.getMilliseconds()).padStart(3, '0');
56
+
57
+ const timestamp = `${YYYY}${MM}${DD}-${hh}${mm}${ss}-${ms}`;
58
+
59
+ const fileName = `${result.status}-${pickle.name}-${pickle.id}-${timestamp}.txt`;
60
+
61
+ const filePath = path.join(statusDir, fileName);
62
+
63
+ fs.writeFileSync(filePath, "");
64
+
52
65
  }
53
66
 
54
67
  const projectHooksPath = path.resolve(
@@ -81,6 +94,15 @@ BeforeAll(async () => {
81
94
  Before(async function ({pickle}) {
82
95
  context.vars = {};
83
96
 
97
+ const vars = await cucumberConfig.variables
98
+
99
+ if (vars && typeof vars === "object") {
100
+ for (let [key, value] of Object.entries(vars)) {
101
+
102
+ saveVar(value, key);
103
+ }
104
+ }
105
+
84
106
  const envFilePath = path.join(
85
107
  moduleConfig.projectPath,
86
108
  "tests",
@@ -142,7 +164,7 @@ AfterStep(async function ({ pickleStep }) {
142
164
  }
143
165
  });
144
166
 
145
- After(async function ({ pickle, result }) {
167
+ After(async function ({result, pickle}) {
146
168
  if (typeof projectHooks.After === "function") {
147
169
  await projectHooks.After();
148
170
  }
@@ -156,12 +178,12 @@ After(async function ({ pickle, result }) {
156
178
  await allure.attachment("Screenshot", screenshotBuffer, "image/png");
157
179
  }
158
180
 
159
- saveTestStatus(result, pickle);
181
+ if(cucumberConfig.default.testPercentage>0) saveTestStatus(result, pickle);
160
182
 
161
183
  if (cucumberConfig.default.reportWithTrace || cucumberConfig.default.trace) {
162
184
  var tracePath = path.join(
163
185
  moduleConfig.projectPath,
164
- `./traces/${pickle.name.replaceAll(" ", "_")}.zip`,
186
+ `./traces/${pickle.name.replaceAll(" ", "_")}-${pickle.id}.zip`,
165
187
  );
166
188
  }
167
189
 
@@ -212,55 +234,4 @@ AfterAll(async () => {
212
234
  await projectHooks.AfterAll();
213
235
  }
214
236
 
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
237
  });