codeceptjs 3.7.5-beta.7 → 3.7.5-beta.9
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/lib/mocha/test.js +1 -0
- package/lib/plugin/failedTestsTracker.js +88 -60
- package/package.json +1 -1
package/lib/mocha/test.js
CHANGED
|
@@ -41,72 +41,77 @@ module.exports = function (config) {
|
|
|
41
41
|
|
|
42
42
|
// Track test failures - only when not using workers
|
|
43
43
|
event.dispatcher.on(event.test.failed, test => {
|
|
44
|
-
// Skip
|
|
45
|
-
if (store.hasWorkers) {
|
|
46
|
-
return
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Skip if we're in a worker thread (CodeceptJS uses worker_threads)
|
|
44
|
+
// Skip collection in worker threads to avoid duplicates
|
|
50
45
|
try {
|
|
51
46
|
const { isMainThread } = require('worker_threads')
|
|
52
|
-
if (!isMainThread)
|
|
53
|
-
return
|
|
54
|
-
}
|
|
47
|
+
if (!isMainThread) return
|
|
55
48
|
} catch (e) {
|
|
56
|
-
// worker_threads not available,
|
|
49
|
+
// worker_threads not available, continue
|
|
57
50
|
}
|
|
58
51
|
|
|
59
|
-
|
|
52
|
+
if (store.hasWorkers) return // Skip if running with workers
|
|
60
53
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
54
|
+
// Only collect on final failure (when retries are exhausted or no retries configured)
|
|
55
|
+
const currentRetry = test._currentRetry || 0
|
|
56
|
+
const maxRetries = typeof test.retries === 'function' ? test.retries() : (test.retries || 0)
|
|
57
|
+
|
|
58
|
+
// Only add to failed tests if this is the final attempt
|
|
59
|
+
if (currentRetry >= maxRetries) {
|
|
60
|
+
allTestsPassed = false
|
|
61
|
+
|
|
62
|
+
const failedTest = {
|
|
63
|
+
title: test.title,
|
|
64
|
+
fullTitle: test.fullTitle(),
|
|
65
|
+
file: test.file || (test.parent && test.parent.file),
|
|
66
|
+
uid: test.uid,
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
}
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
// Add parent suite information
|
|
71
|
+
if (test.parent) {
|
|
72
|
+
failedTest.suite = test.parent.title
|
|
73
|
+
failedTest.suiteFile = test.parent.file
|
|
74
|
+
}
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
// Add error information if available
|
|
77
|
+
if (test.err && options.includeStackTrace) {
|
|
78
|
+
failedTest.error = {
|
|
79
|
+
message: test.err.message || 'Test failed',
|
|
80
|
+
stack: test.err.stack || '',
|
|
81
|
+
name: test.err.name || 'Error',
|
|
82
|
+
}
|
|
81
83
|
}
|
|
82
|
-
}
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
// Add metadata if available
|
|
86
|
+
if (options.includeMetadata) {
|
|
87
|
+
failedTest.metadata = {
|
|
88
|
+
tags: test.tags || [],
|
|
89
|
+
meta: test.meta || {},
|
|
90
|
+
opts: test.opts || {},
|
|
91
|
+
duration: test.duration || 0,
|
|
92
|
+
// Only include retries if it represents actual retry attempts, not the config value
|
|
93
|
+
...(test._currentRetry > 0 && { actualRetries: test._currentRetry }),
|
|
94
|
+
...(maxRetries > 0 && maxRetries !== -1 && { maxRetries: maxRetries }),
|
|
95
|
+
}
|
|
92
96
|
}
|
|
93
|
-
}
|
|
94
97
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
// Add BDD/Gherkin information if available
|
|
99
|
+
if (test.parent && test.parent.feature) {
|
|
100
|
+
failedTest.bdd = {
|
|
101
|
+
feature: test.parent.feature.name || test.parent.title,
|
|
102
|
+
scenario: test.title,
|
|
103
|
+
featureFile: test.parent.file,
|
|
104
|
+
}
|
|
101
105
|
}
|
|
102
|
-
}
|
|
103
106
|
|
|
104
|
-
|
|
105
|
-
|
|
107
|
+
failedTests.push(failedTest)
|
|
108
|
+
output.print(`Failed Tests Tracker: Recorded failed test - ${test.title}`)
|
|
109
|
+
}
|
|
106
110
|
})
|
|
107
111
|
|
|
108
112
|
// Handle test completion and save failed tests
|
|
109
113
|
event.dispatcher.on(event.all.result, (result) => {
|
|
114
|
+
|
|
110
115
|
// Respect CodeceptJS output directory like other plugins
|
|
111
116
|
const outputDir = global.output_dir || './output'
|
|
112
117
|
const outputPath = path.isAbsolute(options.outputFile)
|
|
@@ -114,14 +119,27 @@ module.exports = function (config) {
|
|
|
114
119
|
: path.resolve(outputDir, options.outputFile)
|
|
115
120
|
let allFailedTests = [...failedTests]
|
|
116
121
|
|
|
117
|
-
//
|
|
118
|
-
if (
|
|
119
|
-
|
|
122
|
+
// Collect failed tests from result (both worker and single-process modes)
|
|
123
|
+
if (result) {
|
|
124
|
+
let resultFailedTests = []
|
|
125
|
+
|
|
126
|
+
// Worker mode: result.tests
|
|
127
|
+
if (store.hasWorkers && result.tests) {
|
|
128
|
+
resultFailedTests = result.tests.filter(test => test.state === 'failed' || test.err)
|
|
129
|
+
}
|
|
130
|
+
// Single-process mode: result._failures or result._tests
|
|
131
|
+
else if (!store.hasWorkers && (result._failures || result._tests)) {
|
|
132
|
+
if (result._failures && result._failures.length > 0) {
|
|
133
|
+
resultFailedTests = result._failures.map(failure => failure.test || failure)
|
|
134
|
+
} else if (result._tests) {
|
|
135
|
+
resultFailedTests = result._tests.filter(test => test.state === 'failed' || test.err)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
120
138
|
|
|
121
139
|
// Use a Set to track unique test identifiers to prevent duplicates
|
|
122
140
|
const existingTestIds = new Set(allFailedTests.map(test => test.uid || `${test.file}:${test.title}`))
|
|
123
141
|
|
|
124
|
-
|
|
142
|
+
resultFailedTests.forEach(test => {
|
|
125
143
|
// Create unique identifier for deduplication
|
|
126
144
|
const testId = test.uid || `${test.file || 'unknown'}:${test.title}`
|
|
127
145
|
|
|
@@ -177,7 +195,7 @@ module.exports = function (config) {
|
|
|
177
195
|
existingTestIds.add(testId)
|
|
178
196
|
})
|
|
179
197
|
|
|
180
|
-
output.print(`Failed Tests Tracker: Collected ${
|
|
198
|
+
output.print(`Failed Tests Tracker: Collected ${resultFailedTests.length} failed tests from result`)
|
|
181
199
|
}
|
|
182
200
|
|
|
183
201
|
if (allFailedTests.length === 0) {
|
|
@@ -242,12 +260,22 @@ module.exports = function (config) {
|
|
|
242
260
|
|
|
243
261
|
// If still unknown, try to extract from error stack trace
|
|
244
262
|
if (filePath === 'unknown' && test.err && test.err.stack) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
263
|
+
// Try multiple regex patterns for different stack trace formats
|
|
264
|
+
const patterns = [
|
|
265
|
+
/at.*\(([^)]+\.js):\d+:\d+\)/, // Standard format
|
|
266
|
+
/at.*\(.*[\/\\]([^\/\\]+\.js):\d+:\d+\)/, // With path separators
|
|
267
|
+
/\(([^)]*\.js):\d+:\d+\)/, // Simpler format
|
|
268
|
+
/([^\/\\]+\.js):\d+:\d+/, // Just filename with line numbers
|
|
269
|
+
]
|
|
270
|
+
|
|
271
|
+
for (const pattern of patterns) {
|
|
272
|
+
const stackMatch = test.err.stack.match(pattern)
|
|
273
|
+
if (stackMatch && stackMatch[1]) {
|
|
274
|
+
const absolutePath = stackMatch[1]
|
|
275
|
+
const relativePath = absolutePath.replace(process.cwd() + '/', '').replace(/^.*[\/\\]/, '')
|
|
276
|
+
filePath = relativePath
|
|
277
|
+
break
|
|
278
|
+
}
|
|
251
279
|
}
|
|
252
280
|
}
|
|
253
281
|
|
|
@@ -308,7 +336,7 @@ module.exports = function (config) {
|
|
|
308
336
|
allFailedTests.push(failedTest)
|
|
309
337
|
})
|
|
310
338
|
|
|
311
|
-
output.print(`Failed Tests Tracker: Collected ${
|
|
339
|
+
output.print(`Failed Tests Tracker: Collected ${allFailedTests.length - failedTests.length} failed tests from workers`)
|
|
312
340
|
}
|
|
313
341
|
|
|
314
342
|
if (allFailedTests.length === 0) {
|