artes 1.4.5 → 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.
@@ -14,7 +14,7 @@ try {
14
14
  console.log("Proceeding with default config.");
15
15
  }
16
16
 
17
- const defaultFormats = ["rerun:@rerun.txt", "progress-bar"];
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)
@@ -55,7 +55,7 @@ function loadVariables(cliVariables, artesConfigVariables) {
55
55
 
56
56
  if (cliVariables) {
57
57
  try {
58
- cliVariables = JSON.parse(process.env.VARS);
58
+ cliVariables = JSON.parse(cliVariables);
59
59
  } catch (err) {
60
60
  console.error("Invalid JSON in process.env.VARS:", process.env.VARS);
61
61
  envVars = {};
package/executer.js CHANGED
@@ -22,6 +22,11 @@ if (fs.existsSync(artesConfigPath)) {
22
22
 
23
23
  const args = process.argv.slice(2);
24
24
 
25
+ const getArgValue = (flag) => {
26
+ const index = args.indexOf(flag);
27
+ return index >= 0 ? args[index + 1] : undefined;
28
+ };
29
+
25
30
  const flags = {
26
31
  help: args.includes("-h") || args.includes("--help"),
27
32
  version: args.includes("-v") || args.includes("--version"),
@@ -56,23 +61,25 @@ const flags = {
56
61
  slowMo: args.includes("--slowMo"),
57
62
  };
58
63
 
59
- const env = args[args.indexOf("--env") + 1];
60
- const vars = args[args.indexOf("--saveVar") + 1]
61
- const featureFiles = args[args.indexOf("--features") + 1];
64
+
65
+ const env = getArgValue("--env");
66
+ const vars = getArgValue("--saveVar");
67
+ const featureFiles = getArgValue("--features");
62
68
  const features = flags.features && featureFiles;
63
- const stepDef = args[args.indexOf("--stepDef") + 1];
64
- const tags = args[args.indexOf("--tags") + 1];
65
- const parallel = args[args.indexOf("--parallel") + 1];
66
- const retry = args[args.indexOf("--retry") + 1];
67
- const rerun = args[args.indexOf("--rerun") + 1];
68
- const percentage = args[args.indexOf("--percentage") + 1];
69
- const browser = args[args.indexOf("--browser") + 1];
70
- const device = args[args.indexOf("--device") + 1];
71
- const baseURL = args[args.indexOf("--baseURL") + 1];
72
- const width = args[args.indexOf("--width") + 1];
73
- const height = args[args.indexOf("--height") + 1];
74
- const timeout = args[args.indexOf("--timeout") + 1];
75
- const slowMo = args[args.indexOf("--slowMo") + 1];
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
+
76
83
 
77
84
  flags.env ? (process.env.ENV = env) : "";
78
85
 
@@ -152,80 +159,137 @@ flags.timeout ? (process.env.TIMEOUT = timeout) : "";
152
159
  flags.slowMo ? (process.env.SLOWMO = slowMo) : "";
153
160
 
154
161
 
155
- function testCoverageCalculation(testStatusDir){
156
- if(fs.existsSync(testStatusDir)){
157
- const files = fs.readdirSync(testStatusDir);
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() {
158
214
 
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;
215
+ const testStatusFile = path.join(process.cwd(), "node_modules", "artes" , "test-status", 'test-status.txt');
162
216
 
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') {
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') {
204
269
  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
- };
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");
228
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
+ };
229
293
  }
230
294
 
231
295
  function main() {
@@ -237,7 +301,9 @@ function main() {
237
301
 
238
302
  logPomWarnings();
239
303
 
240
- const testCoverage = testCoverageCalculation(path.join(process.cwd(), "node_modules", "artes" , "testsStatus"))
304
+ findDuplicateTestNames();
305
+
306
+ const testCoverage = testCoverageCalculation()
241
307
 
242
308
  const testPercentage = (process.env.PERCENTAGE ? Number(process.env.PERCENTAGE) : artesConfig.testPercentage || 0)
243
309
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "artes",
3
- "version": "1.4.5",
3
+ "version": "1.4.6",
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,7 +30,6 @@
30
30
  "dayjs": "1.11.13",
31
31
  "deasync": "^0.1.31",
32
32
  "playwright": "^1.58.2",
33
- "proper-lockfile": "^4.1.2",
34
33
  "rimraf": "6.0.1"
35
34
  },
36
35
  "repository": {
@@ -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 testsStatus pomDuplicateWarnings.json",
38
+ "allure-result allure-results test-results @rerun.txt test-status null pomDuplicateWarnings.json",
39
39
  };
40
40
 
41
41
  module.exports = {
@@ -41,29 +41,6 @@ async function attachResponse(attachFn) {
41
41
  }
42
42
  }
43
43
 
44
- function saveTestStatus(result, pickle) {
45
-
46
- fs.mkdirSync(statusDir, { recursive: true });
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
-
65
- }
66
-
67
44
  const projectHooksPath = path.resolve(
68
45
  moduleConfig.projectPath,
69
46
  "tests/steps/hooks.js",
@@ -178,7 +155,6 @@ After(async function ({result, pickle}) {
178
155
  await allure.attachment("Screenshot", screenshotBuffer, "image/png");
179
156
  }
180
157
 
181
- if(cucumberConfig.default.testPercentage>0) saveTestStatus(result, pickle);
182
158
 
183
159
  if (cucumberConfig.default.reportWithTrace || cucumberConfig.default.trace) {
184
160
  var tracePath = path.join(
@@ -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;