codeceptjs 3.7.5-beta.9 → 3.7.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.
@@ -1,374 +0,0 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
- const event = require('../event')
4
- const output = require('../output')
5
- const store = require('../store')
6
-
7
- const defaultConfig = {
8
- enabled: true,
9
- outputFile: 'failed-tests.json',
10
- clearOnSuccess: true,
11
- includeStackTrace: true,
12
- includeMetadata: true,
13
- }
14
-
15
- /**
16
- * Failed Tests Tracker Plugin for CodeceptJS
17
- *
18
- * Tracks failed tests and saves them to a file for later re-execution.
19
- *
20
- * ## Configuration
21
- *
22
- * ```js
23
- * "plugins": {
24
- * "failedTestsTracker": {
25
- * "enabled": true,
26
- * "outputFile": "failed-tests.json",
27
- * "clearOnSuccess": true,
28
- * "includeStackTrace": true,
29
- * "includeMetadata": true
30
- * }
31
- * }
32
- * ```
33
- *
34
- * @param {object} config plugin configuration
35
- */
36
- module.exports = function (config) {
37
- const options = { ...defaultConfig, ...config }
38
- let failedTests = []
39
- let allTestsPassed = true
40
- let workerFailedTests = new Map() // Track failed tests from workers
41
-
42
- // Track test failures - only when not using workers
43
- event.dispatcher.on(event.test.failed, test => {
44
- // Skip collection in worker threads to avoid duplicates
45
- try {
46
- const { isMainThread } = require('worker_threads')
47
- if (!isMainThread) return
48
- } catch (e) {
49
- // worker_threads not available, continue
50
- }
51
-
52
- if (store.hasWorkers) return // Skip if running with workers
53
-
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
- }
69
-
70
- // Add parent suite information
71
- if (test.parent) {
72
- failedTest.suite = test.parent.title
73
- failedTest.suiteFile = test.parent.file
74
- }
75
-
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
- }
83
- }
84
-
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
- }
96
- }
97
-
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
- }
105
- }
106
-
107
- failedTests.push(failedTest)
108
- output.print(`Failed Tests Tracker: Recorded failed test - ${test.title}`)
109
- }
110
- })
111
-
112
- // Handle test completion and save failed tests
113
- event.dispatcher.on(event.all.result, (result) => {
114
-
115
- // Respect CodeceptJS output directory like other plugins
116
- const outputDir = global.output_dir || './output'
117
- const outputPath = path.isAbsolute(options.outputFile)
118
- ? options.outputFile
119
- : path.resolve(outputDir, options.outputFile)
120
- let allFailedTests = [...failedTests]
121
-
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
- }
138
-
139
- // Use a Set to track unique test identifiers to prevent duplicates
140
- const existingTestIds = new Set(allFailedTests.map(test => test.uid || `${test.file}:${test.title}`))
141
-
142
- resultFailedTests.forEach(test => {
143
- // Create unique identifier for deduplication
144
- const testId = test.uid || `${test.file || 'unknown'}:${test.title}`
145
-
146
- // Skip if we already have this test
147
- if (existingTestIds.has(testId)) {
148
- return
149
- }
150
-
151
- const failedTest = {
152
- title: test.title,
153
- fullTitle: test.fullTitle || test.title,
154
- file: test.file || 'unknown',
155
- uid: test.uid,
156
- timestamp: new Date().toISOString(),
157
- }
158
-
159
- // Add parent suite information
160
- if (test.parent) {
161
- failedTest.suite = test.parent.title
162
- failedTest.suiteFile = test.parent.file
163
- }
164
-
165
- // Add error information if available
166
- if (test.err && options.includeStackTrace) {
167
- failedTest.error = {
168
- message: test.err.message || 'Test failed',
169
- stack: test.err.stack || '',
170
- name: test.err.name || 'Error',
171
- }
172
- }
173
-
174
- // Add metadata if available
175
- if (options.includeMetadata) {
176
- failedTest.metadata = {
177
- tags: test.tags || [],
178
- meta: test.meta || {},
179
- opts: test.opts || {},
180
- duration: test.duration || 0,
181
- retries: test.retries || 0,
182
- }
183
- }
184
-
185
- // Add BDD/Gherkin information if available
186
- if (test.parent && test.parent.feature) {
187
- failedTest.bdd = {
188
- feature: test.parent.feature.name || test.parent.title,
189
- scenario: test.title,
190
- featureFile: test.parent.file,
191
- }
192
- }
193
-
194
- allFailedTests.push(failedTest)
195
- existingTestIds.add(testId)
196
- })
197
-
198
- output.print(`Failed Tests Tracker: Collected ${resultFailedTests.length} failed tests from result`)
199
- }
200
-
201
- if (allFailedTests.length === 0) {
202
- if (options.clearOnSuccess && fs.existsSync(outputPath)) {
203
- try {
204
- fs.unlinkSync(outputPath)
205
- output.print(`Failed Tests Tracker: Cleared previous failed tests file (all tests passed)`)
206
- } catch (error) {
207
- output.print(`Failed Tests Tracker: Could not clear failed tests file: ${error.message}`)
208
- }
209
- } else {
210
- output.print(`Failed Tests Tracker: No failed tests to save`)
211
- }
212
- return
213
- }
214
-
215
- const failedTestsData = {
216
- timestamp: new Date().toISOString(),
217
- totalFailedTests: allFailedTests.length,
218
- codeceptVersion: require('../codecept').version(),
219
- tests: allFailedTests,
220
- }
221
-
222
- try {
223
- // Ensure directory exists
224
- const dir = path.dirname(outputPath)
225
- if (!fs.existsSync(dir)) {
226
- fs.mkdirSync(dir, { recursive: true })
227
- }
228
-
229
- fs.writeFileSync(outputPath, JSON.stringify(failedTestsData, null, 2))
230
- output.print(`Failed Tests Tracker: Saved ${allFailedTests.length} failed tests to ${outputPath}`)
231
- } catch (error) {
232
- output.print(`Failed Tests Tracker: Failed to save failed tests: ${error.message}`)
233
- }
234
- })
235
-
236
- // Reset state for new test runs
237
- event.dispatcher.on(event.all.before, () => {
238
- failedTests = []
239
- allTestsPassed = true
240
- workerFailedTests.clear()
241
- })
242
-
243
- // Handle worker mode - listen to workers.result event for consolidated results
244
- event.dispatcher.on(event.workers.result, (result) => {
245
- // Respect CodeceptJS output directory like other plugins
246
- const outputDir = global.output_dir || './output'
247
- const outputPath = path.isAbsolute(options.outputFile)
248
- ? options.outputFile
249
- : path.resolve(outputDir, options.outputFile)
250
-
251
- let allFailedTests = []
252
-
253
- // In worker mode, collect failed tests from consolidated result
254
- if (result && result.tests) {
255
- const workerFailedTests = result.tests.filter(test => test.state === 'failed' || test.err)
256
-
257
- workerFailedTests.forEach(test => {
258
- // Extract file path from test title or error stack trace as fallback
259
- let filePath = test.file || test.parent?.file || 'unknown'
260
-
261
- // If still unknown, try to extract from error stack trace
262
- if (filePath === 'unknown' && test.err && test.err.stack) {
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
- }
279
- }
280
- }
281
-
282
- // If still unknown, try to extract from test context or use test file pattern
283
- if (filePath === 'unknown') {
284
- // Look for common test file patterns in the test title or fullTitle
285
- const fullTitle = test.fullTitle || test.title
286
- if (fullTitle && fullTitle.includes('checkout')) {
287
- filePath = 'checkout_test.js'
288
- } else if (fullTitle && fullTitle.includes('github')) {
289
- filePath = 'github_test.js'
290
- }
291
- }
292
-
293
- const failedTest = {
294
- title: test.title,
295
- fullTitle: test.fullTitle || test.title,
296
- file: filePath,
297
- uid: test.uid,
298
- timestamp: new Date().toISOString(),
299
- }
300
-
301
- // Add parent suite information
302
- if (test.parent) {
303
- failedTest.suite = test.parent.title
304
- failedTest.suiteFile = test.parent.file
305
- }
306
-
307
- // Add error information if available
308
- if (test.err && options.includeStackTrace) {
309
- failedTest.error = {
310
- message: test.err.message || 'Test failed',
311
- stack: test.err.stack || '',
312
- name: test.err.name || 'Error',
313
- }
314
- }
315
-
316
- // Add metadata if available
317
- if (options.includeMetadata) {
318
- failedTest.metadata = {
319
- tags: test.tags || [],
320
- meta: test.meta || {},
321
- opts: test.opts || {},
322
- duration: test.duration || 0,
323
- retries: test.retries || 0,
324
- }
325
- }
326
-
327
- // Add BDD/Gherkin information if available
328
- if (test.parent && test.parent.feature) {
329
- failedTest.bdd = {
330
- feature: test.parent.feature.name || test.parent.title,
331
- scenario: test.title,
332
- featureFile: test.parent.file,
333
- }
334
- }
335
-
336
- allFailedTests.push(failedTest)
337
- })
338
-
339
- output.print(`Failed Tests Tracker: Collected ${allFailedTests.length - failedTests.length} failed tests from workers`)
340
- }
341
-
342
- if (allFailedTests.length === 0) {
343
- if (options.clearOnSuccess && fs.existsSync(outputPath)) {
344
- try {
345
- fs.unlinkSync(outputPath)
346
- output.print(`Failed Tests Tracker: Cleared previous failed tests file (all tests passed)`)
347
- } catch (error) {
348
- output.print(`Failed Tests Tracker: Could not clear failed tests file: ${error.message}`)
349
- }
350
- }
351
- return
352
- }
353
-
354
- // Save failed tests to file
355
- try {
356
- const failedTestsData = {
357
- timestamp: new Date().toISOString(),
358
- totalFailed: allFailedTests.length,
359
- tests: allFailedTests,
360
- }
361
-
362
- // Ensure output directory exists
363
- const dir = path.dirname(outputPath)
364
- if (!fs.existsSync(dir)) {
365
- fs.mkdirSync(dir, { recursive: true })
366
- }
367
-
368
- fs.writeFileSync(outputPath, JSON.stringify(failedTestsData, null, 2))
369
- output.print(`Failed Tests Tracker: Saved ${allFailedTests.length} failed tests to ${outputPath}`)
370
- } catch (error) {
371
- output.print(`Failed Tests Tracker: Failed to save failed tests: ${error.message}`)
372
- }
373
- })
374
- }