artes 1.5.9 → 1.5.11

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
@@ -115,9 +115,14 @@ npx artes [options]
115
115
  | `-rwt, --reportWithTrace` | Add trace to the report | `artes -rwt` or `artes --reportWithTrace` |
116
116
  | `--singleFileReport` | Generate single file allure report | `artes -r --singleFileReport` |
117
117
  | `--zip` | Zip the report folder after generation | `artes -r --zip` |
118
+ | `--uploadReport` | Upload the generated report to Artes Reporting System | `artes --uploadReport --reporterURL "https://example.com"` |
119
+ | `--reporterURL` | URL of the Artes Reporting System to upload the report | `artes --uploadReport --reporterURL "https://example.com"` |
120
+ | `--projectName` | Name of the project in the Artes Reporting System (default: `"Artes Report"`) | `artes --uploadReport --reporterURL "https://example.com" --projectName "My Project"` |
121
+ | `--projectType` | Type of the project for reporting purposes (default: `"Artes"`) | `artes --uploadReport --reporterURL "https://example.com" --projectType "API"` |
122
+ | `--reportPath` | Path to the report zip file to upload (default: `./report.zip`) | `artes --uploadReport --reporterURL "https://example.com" --reportPath "./my_report.zip"` |
118
123
  | 🖼️ `--logo` | Set a custom logo in the report sidebar. Accepts an absolute path, a relative path, or a direct image URL | `artes --logo /abs/path/logo.png`<br>`artes --logo logo.png`<br>`artes --logo 'https://example.com/logo.png'` |
119
124
  | 🏢 `--brandName` | Set the brand name displayed next to the logo in the report sidebar | `artes --brandName 'My Company'` |
120
- | 📄 `--reportName` | Set the report name displayed on the summary widget | `artes --reportName 'Alma UI'`
125
+ | 📄 `--reportName` | Report name displayed on the summary widget and in the Artes Reporting System | `artes --reportName 'Alma UI'`
121
126
  | 📁 `--features` | Specify one or more feature files' relative paths to run (comma-separated) | `artes --features "tests/features/Alma,tests/features/Banan.feature"` |
122
127
  | 📜 `--stepDef` | Specify one or more step definition files' relative paths to use (comma-separated) | `artes --stepDef "tests/steps/login.js,tests/steps/home.js"` |
123
128
  | 🔖 `--tags` | Run tests with specified Cucumber tags | `artes --tags "@smoke or @wip"` |
@@ -615,13 +620,26 @@ Artes supports environment-specific configurations through environment variables
615
620
 
616
621
  | Option | Default Value | Description |
617
622
  | ------------- | ------------------------------ | ------------------------------------------------------ |
618
- | `device` | `""` | [Device List](./docs/emulationDevicesList.md) |
623
+ | `device` | `""` | [Device List](./docs/emulationDevicesList.md) |
619
624
 
620
625
 
621
- ## 📊 Report Generation
626
+ ## 📊 Reporting
622
627
 
623
628
  Artes can generate Allure reports. After running tests with the `-r` flag, the reports will be stored in the `report` folder in HTML format. You can view them in your browser after the tests complete.
624
629
 
630
+ ## 📊 Integration with Artes Reporting System
631
+
632
+ Artes has a built-in integration with the Artes Reporting System. By configuring the options below, you can automatically upload your test reports and keep your pipeline stages clean and organized.
633
+
634
+ | **Option** | **Default Value** | **Description** |
635
+ | ---------------- | ----------------------------- | ---------------------------------------------------------------------- |
636
+ | `uploadReport` | `false` | Automatically upload the report to Artes Reporting System after tests. |
637
+ | `reporterURL` | `""` | URL of the Artes Reporting System instance to upload the report to. |
638
+ | `projectName` | `"Artes Report"` | Name of the project in the Artes Reporting System. |
639
+ | `projectType` | `"Artes"` | Type/category of the project (e.g., UI, API). |
640
+ | `reportName` | `"Artes Report"` | Display name of the report in the Artes Reporting System. |
641
+ | `reportPath` | `"./report.zip"` | Path to the report zip file to be uploaded. |
642
+
625
643
  ---
626
644
 
627
645
  ## 🐳 Docker Image for CI/CD
@@ -14,7 +14,11 @@ try {
14
14
  console.log("Proceeding with default config.");
15
15
  }
16
16
 
17
- const defaultFormats = ["rerun:@rerun.txt", "progress-bar", './src/helper/controller/status-formatter.js:null'];
17
+ const defaultFormats = [
18
+ "rerun:@rerun.txt",
19
+ "progress-bar",
20
+ "./src/helper/controller/status-formatter.js:null",
21
+ ];
18
22
 
19
23
  const userFormatsFromEnv = process.env.REPORT_FORMAT
20
24
  ? JSON.parse(process.env.REPORT_FORMAT)
@@ -52,7 +56,6 @@ function resolveEnv(artesConfig) {
52
56
  const env = resolveEnv(artesConfig);
53
57
 
54
58
  function loadVariables(cliVariables, artesConfigVariables) {
55
-
56
59
  if (cliVariables) {
57
60
  try {
58
61
  cliVariables = JSON.parse(cliVariables);
@@ -72,13 +75,12 @@ function loadVariables(cliVariables, artesConfigVariables) {
72
75
 
73
76
  const resolveFeaturePaths = (basePath, value) => {
74
77
  return value
75
- .split(',')
76
- .map(p => p.trim())
78
+ .split(",")
79
+ .map((p) => p.trim())
77
80
  .filter(Boolean)
78
- .map(p => path.join(basePath, p));
81
+ .map((p) => path.join(basePath, p));
79
82
  };
80
83
 
81
-
82
84
  module.exports = {
83
85
  default: {
84
86
  // File paths and patterns
@@ -88,13 +90,13 @@ module.exports = {
88
90
  timeout: process.env.TIMEOUT
89
91
  ? Number(process.env.TIMEOUT) * 1000
90
92
  : artesConfig.timeout * 1000 || 30 * 1000, // Default timeout in seconds
91
- paths: process.env.RERUN
93
+ paths: process.env.RERUN
92
94
  ? [path.join("../../", process.env.RERUN)]
93
95
  : process.env.FEATURES
94
96
  ? resolveFeaturePaths(moduleConfig.projectPath, process.env.FEATURES)
95
97
  : artesConfig.features
96
98
  ? resolveFeaturePaths(moduleConfig.projectPath, artesConfig.features)
97
- : [moduleConfig.featuresPath], // Paths to feature files
99
+ : [moduleConfig.featuresPath], // Paths to feature files
98
100
  require: [
99
101
  process.env.STEP_DEFINITIONS
100
102
  ? [path.join(moduleConfig.projectPath, process.env.STEP_DEFINITIONS)]
@@ -173,14 +175,14 @@ module.exports = {
173
175
  },
174
176
  report: {
175
177
  logo: process.env.LOGO
176
- ? process.env.LOGO
177
- : artesConfig?.logo || "./logo.png",
178
- brandName: process.env.BRAND_NAME
178
+ ? process.env.LOGO
179
+ : artesConfig?.logo || "./logo.png",
180
+ brandName: process.env.BRAND_NAME
179
181
  ? process.env.BRAND_NAME
180
182
  : artesConfig?.brandName || "ARTES",
181
- reportName: process.env.REPORT_NAME
182
- ? process.env.REPORT_NAME
183
- : artesConfig?.reportName || "ARTES REPORT",
183
+ reportName: process.env.REPORT_NAME
184
+ ? process.env.REPORT_NAME
185
+ : artesConfig?.reportName || "ARTES REPORT",
184
186
  singleFileReport:
185
187
  process.env.SINGLE_FILE_REPORT == "true"
186
188
  ? true
package/executer.js CHANGED
@@ -10,10 +10,15 @@ const {
10
10
  const { logPomWarnings } = require("./src/helper/controller/pomCollector");
11
11
  const fs = require("fs");
12
12
  const path = require("path");
13
- const { testCoverageCalculator } = require("./src/helper/controller/testCoverageCalculator");
13
+ const {
14
+ testCoverageCalculator,
15
+ } = require("./src/helper/controller/testCoverageCalculator");
14
16
  const { getExecutor } = require("./src/helper/controller/getExecutor");
15
- const { findDuplicateTestNames } = require("./src/helper/controller/findDuplicateTestNames");
17
+ const {
18
+ findDuplicateTestNames,
19
+ } = require("./src/helper/controller/findDuplicateTestNames");
16
20
  const { getEnvInfo } = require("artes/src/helper/controller/getEnvInfo");
21
+ const { uploadReport } = require("./src/helper/controller/reportUploader");
17
22
 
18
23
 
19
24
  const artesConfigPath = path.resolve(process.cwd(), "artes.config.js");
@@ -43,9 +48,14 @@ const flags = {
43
48
  reportWithTrace: args.includes("-rwt") || args.includes("--reportWithTrace"),
44
49
  singleFileReport: args.includes("--singleFileReport"),
45
50
  customLogo: args.includes("--logo"),
46
- customBrandName:args.includes("--brandName"),
47
- customReportName:args.includes("--reportName"),
51
+ customBrandName: args.includes("--brandName"),
52
+ customReportName: args.includes("--reportName"),
48
53
  zip: args.includes("--zip"),
54
+ uploadReport: args.includes("--uploadReport"),
55
+ reporterURL: args.includes("--reporterURL"),
56
+ projectName: args.includes("--projectName"),
57
+ projectType: args.includes("--projectType"),
58
+ reportPath: args.includes("--reportPath"),
49
59
  features: args.includes("--features"),
50
60
  stepDef: args.includes("--stepDef"),
51
61
  tags: args.includes("--tags"),
@@ -68,9 +78,12 @@ const flags = {
68
78
  slowMo: args.includes("--slowMo"),
69
79
  };
70
80
 
71
-
72
81
  const env = getArgValue("--env");
73
82
  const vars = getArgValue("--saveVar");
83
+ const reporterURL = getArgValue("--reporterURL");
84
+ const projectType = getArgValue("--projectType");
85
+ const projectName = getArgValue("--projectName");
86
+ const reportPath = getArgValue("--reportPath");
74
87
  const logo = getArgValue("--logo");
75
88
  const brandName = getArgValue("--brandName");
76
89
  const reportName = getArgValue("--reportName");
@@ -90,7 +103,6 @@ const height = getArgValue("--height");
90
103
  const timeout = getArgValue("--timeout");
91
104
  const slowMo = getArgValue("--slowMo");
92
105
 
93
-
94
106
  flags.env ? (process.env.ENV = env) : "";
95
107
 
96
108
  vars ? (process.env.VARS = vars) : "";
@@ -172,9 +184,7 @@ flags.timeout ? (process.env.TIMEOUT = timeout) : "";
172
184
 
173
185
  flags.slowMo ? (process.env.SLOWMO = slowMo) : "";
174
186
 
175
-
176
-
177
- function main() {
187
+ async function main() {
178
188
  if (flags.help) return showHelp();
179
189
  if (flags.version) return showVersion();
180
190
  if (flags.create) return createProject(flags.createYes, flags.noDeps);
@@ -185,57 +195,72 @@ function main() {
185
195
 
186
196
  findDuplicateTestNames();
187
197
 
188
- const testCoverage = testCoverageCalculator()
198
+ const testCoverage = testCoverageCalculator();
189
199
 
190
- const testPercentage = (process.env.PERCENTAGE ? Number(process.env.PERCENTAGE) : artesConfig.testPercentage || 0)
200
+ const testPercentage = process.env.PERCENTAGE
201
+ ? Number(process.env.PERCENTAGE)
202
+ : artesConfig.testPercentage || 0;
191
203
 
192
204
  if (testPercentage > 0) {
193
-
194
- const meetsThreshold = testCoverage.percentage >= testPercentage
205
+ const meetsThreshold = testCoverage.percentage >= testPercentage;
195
206
 
196
207
  if (meetsThreshold) {
197
208
  console.log(
198
- `✅ Tests passed required ${testPercentage}% success rate with ${testCoverage.percentage.toFixed(2)}%!`,
209
+ `\x1b[32mTests passed required ${testPercentage}% success rate with ${testCoverage.percentage.toFixed(2)}%!\x1b[0m`,
199
210
  );
200
211
  process.env.EXIT_CODE = parseInt(0, 10);
201
212
  } else {
202
213
  console.log(
203
- `❌ Tests failed required ${testPercentage}% success rate with ${testCoverage.percentage.toFixed(2)}%!`,
214
+ `\x1b[31mTests failed required ${testPercentage}% success rate with ${testCoverage.percentage.toFixed(2)}%!\x1b[0m`,
204
215
  );
205
216
  process.env.EXIT_CODE = parseInt(1, 10);
206
217
  }
207
218
  }
208
219
 
220
+ const source = path.join(
221
+ process.cwd(),
222
+ "node_modules",
223
+ "artes",
224
+ "@rerun.txt",
225
+ );
226
+ const destination = path.join(process.cwd(), "@rerun.txt");
209
227
 
210
- const source = path.join(process.cwd(), "node_modules", "artes", "@rerun.txt");
211
- const destination = path.join(process.cwd(), "@rerun.txt");
212
-
213
- if (fs.existsSync(source)) {
214
- fs.renameSync(source, destination);
215
- }
228
+ if (fs.existsSync(source)) {
229
+ fs.renameSync(source, destination);
230
+ }
216
231
 
217
232
  if (
218
233
  flags.reportWithTrace ||
219
234
  artesConfig.reportWithTrace ||
220
235
  flags.report ||
221
236
  artesConfig.report
222
- ){
223
-
237
+ ) {
224
238
  getExecutor();
225
- getEnvInfo()
239
+ getEnvInfo();
226
240
  generateReport();
227
-
228
241
  }
229
242
 
230
- if (!( process.env.TRACE === "true"
243
+ if (
244
+ !(process.env.TRACE === "true"
231
245
  ? process.env.TRACE
232
- : artesConfig.trace || false)) {
233
- fs.rmSync(path.join(process.cwd(), "traces"), {
234
- recursive: true,
235
- force: true,
236
- });
237
- }
238
-
246
+ : artesConfig.trace || false)
247
+ ) {
248
+ fs.rmSync(path.join(process.cwd(), "traces"), {
249
+ recursive: true,
250
+ force: true,
251
+ });
252
+ }
253
+
254
+ if (flags.uploadReport || artesConfig.uploadReport) {
255
+ await uploadReport({
256
+ reporterURL: reporterURL || artesConfig.reporterURL,
257
+ projectName: projectName || artesConfig.projectName || "Artes Report",
258
+ projectType: projectType || artesConfig.projectType || "Artes",
259
+ reportName: reportName || artesConfig.reportName || "Artes Report",
260
+ reportPath: reportPath || artesConfig.reportPath || path.join(process.cwd(), "report.zip")
261
+ });
262
+ }
263
+
239
264
  cleanUp();
240
265
  process.exit(process.env.EXIT_CODE);
241
266
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "artes",
3
- "version": "1.5.9",
3
+ "version": "1.5.11",
4
4
  "description": "The simplest way to automate UI and API tests using Cucumber-style steps.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -38,9 +38,8 @@ function selectorSeparator(element) {
38
38
  }
39
39
  }
40
40
 
41
- function getSelector(element) {
42
-
43
- element = resolveVariable(element)
41
+ function getSelector(element) {
42
+ element = resolveVariable(element);
44
43
 
45
44
  const selector =
46
45
  elements?.[element]?.selector || elements?.[element] || element;
@@ -94,9 +93,7 @@ function getElement(element) {
94
93
  return locator;
95
94
  }
96
95
 
97
-
98
96
  function extractVarsFromResponse(responseBody, vars, customVarNames) {
99
-
100
97
  function getValueByPath(obj, path) {
101
98
  const keys = path.split(".");
102
99
  let current = obj;
@@ -124,13 +121,13 @@ function extractVarsFromResponse(responseBody, vars, customVarNames) {
124
121
  .join("");
125
122
  }
126
123
 
127
- const varPaths = vars.split(",").map(v => v.trim());
124
+ const varPaths = vars.split(",").map((v) => v.trim());
128
125
  let customNames = [];
129
126
 
130
127
  if (!customVarNames) {
131
128
  customNames = varPaths.map(pathToCamelCase);
132
129
  } else if (typeof customVarNames === "string") {
133
- customNames = customVarNames.split(",").map(n => n.trim());
130
+ customNames = customVarNames.split(",").map((n) => n.trim());
134
131
  } else if (Array.isArray(customVarNames)) {
135
132
  customNames = customVarNames;
136
133
  } else {
@@ -2,53 +2,67 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
 
4
4
  function findDuplicateTestNames() {
5
- const testStatusFile = path.join(process.cwd(), "node_modules", "artes" , "test-status", 'test-status.txt');
6
-
7
- if (!fs.existsSync(testStatusFile)) {
8
- console.error('test-status.txt not found');
9
- return;
5
+ const testStatusFile = path.join(
6
+ process.cwd(),
7
+ "node_modules",
8
+ "artes",
9
+ "test-status",
10
+ "test-status.txt",
11
+ );
12
+
13
+ if (!fs.existsSync(testStatusFile)) {
14
+ console.error("test-status.txt not found");
15
+ return;
16
+ }
17
+
18
+ const content = fs.readFileSync(testStatusFile, "utf8");
19
+ const lines = content.split("\n").filter((line) => line.trim());
20
+
21
+ const testNameToEntries = {};
22
+
23
+ lines.forEach((line) => {
24
+ const parts = line.split(" | ");
25
+ if (parts.length < 5) return;
26
+
27
+ const testName = parts[2].trim();
28
+ const filePath = parts[4].trim();
29
+ const uuid = parts[3].trim();
30
+
31
+ if (!testNameToEntries[testName]) {
32
+ testNameToEntries[testName] = [];
10
33
  }
11
-
12
- const content = fs.readFileSync(testStatusFile, 'utf8');
13
- const lines = content.split('\n').filter(line => line.trim());
14
-
15
- const testNameToFiles = {};
16
-
17
- lines.forEach(line => {
18
- const parts = line.split(' | ');
19
- if (parts.length < 5) return;
20
-
21
- const testName = parts[2].trim();
22
- const filePath = parts[4].trim();
23
-
24
- if (!testNameToFiles[testName]) {
25
- testNameToFiles[testName] = new Set();
26
- }
27
-
28
- testNameToFiles[testName].add(filePath);
29
- });
30
-
31
- const duplicates = {};
32
-
33
- Object.entries(testNameToFiles).forEach(([testName, files]) => {
34
- if (files.size > 1) {
35
- duplicates[testName] = Array.from(files);
36
- }
37
- });
38
-
39
- if (Object.keys(duplicates).length > 0) {
40
- console.warn('\n\x1b[33m[WARNING] Duplicate scenarios names found: This will effect your reporting');
41
- Object.entries(duplicates).forEach(([testName, files]) => {
42
- console.log(`\x1b[33m"${testName}" exists in:`);
43
- files.forEach(file => {
44
- console.log(` - ${file}`);
45
- });
46
- console.log('');
34
+
35
+ const alreadyExists = testNameToEntries[testName].some(
36
+ (e) => e.uuid === uuid,
37
+ );
38
+ if (!alreadyExists) {
39
+ testNameToEntries[testName].push({ filePath, uuid });
40
+ }
41
+ });
42
+
43
+ const duplicates = {};
44
+
45
+ Object.entries(testNameToEntries).forEach(([testName, entries]) => {
46
+ if (entries.length > 1) {
47
+ duplicates[testName] = entries.map((e) => e.filePath);
48
+ }
49
+ });
50
+
51
+ if (Object.keys(duplicates).length > 0) {
52
+ console.warn(
53
+ "\n\x1b[33m[WARNING] Duplicate scenario names found: This will affect your reporting",
54
+ );
55
+ Object.entries(duplicates).forEach(([testName, files]) => {
56
+ console.log(`\x1b[33m"${testName}" exists in:`);
57
+ files.forEach((file) => {
58
+ console.log(` - ${file}`);
47
59
  });
48
- console.log("\x1b[0m");
49
- }
50
-
51
- return duplicates;
60
+ console.log("");
61
+ });
62
+ console.log("\x1b[0m");
52
63
  }
53
64
 
54
- module.exports = {findDuplicateTestNames}
65
+ return duplicates;
66
+ }
67
+
68
+ module.exports = { findDuplicateTestNames };
@@ -3,71 +3,84 @@ const fs = require("fs");
3
3
  const path = require("path");
4
4
  const { moduleConfig } = require("artes/src/helper/imports/commons");
5
5
 
6
-
7
6
  async function getEnvInfo() {
8
-
9
- delete require.cache[require.resolve("../../../cucumber.config.js")];
10
- const cucumberConfig = require("../../../cucumber.config.js");
11
-
12
- if (fs.existsSync(path.join(moduleConfig.modulePath, "browser-info.json"))) {
13
- browserInfo = JSON.parse(fs.readFileSync(path.join(moduleConfig.modulePath, "browser-info.json"), "utf8"));
14
- }
15
-
16
- const environment = {
17
- // ── System ──────────────────────────────
18
- "OS_Name": os.type(),
19
- "OS_Version": os.release(),
20
- "OS_Platform": process.platform,
21
- "OS_Arch": os.arch(),
22
- "CPU_Cores": os.cpus().length,
23
- "CPU_Model": os.cpus()[0]?.model ?? "N/A",
24
- "RAM_Total": `${(os.totalmem() / 1024 / 1024 / 1024).toFixed(2)} GB`,
25
- "RAM_Free": `${(os.freemem() / 1024 / 1024 / 1024).toFixed(2)} GB`,
26
- "Hostname": os.hostname(),
27
-
28
- // ── Node ────────────────────────────────
29
- "Node_Version": process.version,
30
- "NPM_Version": process.env.npm_config_user_agent ?? "N/A",
31
- "Working_Dir": process.cwd(),
32
-
33
- // ── Browser ─────────────────────────────
34
- "Browser_Name": cucumberConfig.browser.browserType,
35
- "Browser_Version": browserInfo.BROWSER_VERSION,
36
- "Screen_Size": `w: ${browserInfo.BROWSER_WIDTH}px h:${browserInfo.BROWSER_HEIGHT}px`,
37
- "Headless": cucumberConfig.browser.headless ?? "N/A",
38
-
39
- // ── Test Config ─────────────────────────
40
- "Base_URL": cucumberConfig.baseURL || "N/A",
41
- "Environment": cucumberConfig.env || "local",
42
- "Timeout": cucumberConfig.default.timeout ?? "N/A",
43
-
44
- // ── Git ─────────────────────────────────
45
- "Git_Branch": process.env.GIT_BRANCH ?? process.env.BRANCH_NAME ?? "N/A",
46
- "Git_Commit": process.env.GIT_COMMIT ?? process.env.GIT_SHA ?? "N/A",
47
- "Git_Author": process.env.GIT_AUTHOR ?? "N/A",
48
-
49
- // ── Timestamps ──────────────────────────
50
- "Run_Date": new Date().toLocaleDateString(),
51
- "Run_Time": new Date().toLocaleTimeString(),
52
- "Timezone": Intl.DateTimeFormat().resolvedOptions().timeZone,
53
- };
54
-
55
- const allureResultsDir = path.join(moduleConfig.modulePath, "allure-result");
56
-
57
- if (!fs.existsSync(allureResultsDir)) {
58
- fs.mkdirSync(allureResultsDir, { recursive: true });
59
- }
60
-
61
- const propertiesContent = Object.entries(environment)
62
- .map(([key, value]) => `${key}=${value}`)
63
- .join("\n");
64
-
65
- fs.writeFileSync(
66
- path.join(allureResultsDir, "environment.properties"),
67
- propertiesContent
7
+ delete require.cache[require.resolve("../../../cucumber.config.js")];
8
+ const cucumberConfig = require("../../../cucumber.config.js");
9
+
10
+ let baseURL = "";
11
+
12
+ if (typeof cucumberConfig.baseURL === "object") {
13
+ const env = (cucumberConfig.env || "").trim();
14
+ baseURL = cucumberConfig.baseURL[env];
15
+ } else {
16
+ baseURL = cucumberConfig.baseURL;
17
+ }
18
+
19
+ if (fs.existsSync(path.join(moduleConfig.modulePath, "browser-info.json"))) {
20
+ browserInfo = JSON.parse(
21
+ fs.readFileSync(
22
+ path.join(moduleConfig.modulePath, "browser-info.json"),
23
+ "utf8",
24
+ ),
68
25
  );
69
-
70
- return environment;
71
26
  }
72
27
 
73
- module.exports ={getEnvInfo}
28
+ const environment = {
29
+ // ── System ──────────────────────────────
30
+ OS_Name: os.type(),
31
+ OS_Version: os.release(),
32
+ OS_Platform: process.platform,
33
+ OS_Arch: os.arch(),
34
+ CPU_Cores: os.cpus().length,
35
+ CPU_Model: os.cpus()[0]?.model ?? "N/A",
36
+ RAM_Total: `${(os.totalmem() / 1024 / 1024 / 1024).toFixed(2)} GB`,
37
+ RAM_Free: `${(os.freemem() / 1024 / 1024 / 1024).toFixed(2)} GB`,
38
+ Hostname: os.hostname(),
39
+
40
+ // ── Node ────────────────────────────────
41
+ Node_Version: process.version,
42
+ NPM_Version: process.env.npm_config_user_agent ?? "N/A",
43
+ Working_Dir: process.cwd(),
44
+
45
+ // ── Browser ─────────────────────────────
46
+ Browser_Name: cucumberConfig.browser.browserType,
47
+ Browser_Version: browserInfo.BROWSER_VERSION,
48
+ Screen_Size: `w: ${browserInfo.BROWSER_WIDTH}px h:${browserInfo.BROWSER_HEIGHT}px`,
49
+ Headless: cucumberConfig.browser.headless ?? "N/A",
50
+
51
+ // ── Test Config ─────────────────────────
52
+ Base_URL: baseURL || "N/A",
53
+ Environment: cucumberConfig.env || "local",
54
+ Parallel_Runner: cucumberConfig.default.parallel,
55
+ Timeout: cucumberConfig.default.timeout ?? "N/A",
56
+
57
+ // ── Git ─────────────────────────────────
58
+ Git_Branch: process.env.GIT_BRANCH ?? process.env.BRANCH_NAME ?? "N/A",
59
+ Git_Commit: process.env.GIT_COMMIT ?? process.env.GIT_SHA ?? "N/A",
60
+ Git_Author: process.env.GIT_AUTHOR ?? "N/A",
61
+
62
+ // ── Timestamps ──────────────────────────
63
+ Run_Date: new Date().toLocaleDateString(),
64
+ Run_Time: new Date().toLocaleTimeString(),
65
+ Timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
66
+ };
67
+
68
+ const allureResultsDir = path.join(moduleConfig.modulePath, "allure-result");
69
+
70
+ if (!fs.existsSync(allureResultsDir)) {
71
+ fs.mkdirSync(allureResultsDir, { recursive: true });
72
+ }
73
+
74
+ const propertiesContent = Object.entries(environment)
75
+ .map(([key, value]) => `${key}=${value}`)
76
+ .join("\n");
77
+
78
+ fs.writeFileSync(
79
+ path.join(allureResultsDir, "environment.properties"),
80
+ propertiesContent,
81
+ );
82
+
83
+ return environment;
84
+ }
85
+
86
+ module.exports = { getEnvInfo };