codeceptjs 3.7.3 → 3.7.5-beta.1
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 +45 -0
- package/bin/codecept.js +3 -0
- package/bin/test-server.js +53 -0
- package/lib/codecept.js +46 -0
- package/lib/command/init.js +5 -0
- package/lib/command/run-workers.js +16 -1
- package/lib/command/workers/runTests.js +220 -14
- package/lib/container.js +16 -3
- package/lib/element/WebElement.js +327 -0
- package/lib/helper/JSONResponse.js +23 -4
- package/lib/helper/Mochawesome.js +30 -9
- package/lib/helper/Playwright.js +74 -38
- package/lib/helper/Puppeteer.js +107 -28
- package/lib/helper/WebDriver.js +18 -4
- package/lib/listener/retryEnhancer.js +85 -0
- package/lib/listener/steps.js +12 -0
- package/lib/mocha/asyncWrapper.js +13 -3
- package/lib/mocha/cli.js +1 -1
- package/lib/mocha/gherkin.js +1 -1
- package/lib/mocha/test.js +18 -1
- package/lib/mocha/ui.js +13 -0
- package/lib/output.js +8 -10
- package/lib/pause.js +6 -1
- package/lib/plugin/commentStep.js +1 -1
- package/lib/plugin/htmlReporter.js +2955 -0
- package/lib/plugin/screenshotOnFail.js +1 -9
- package/lib/recorder.js +9 -0
- package/lib/test-server.js +323 -0
- package/lib/utils/mask_data.js +53 -0
- package/lib/utils.js +34 -2
- package/lib/workerStorage.js +2 -1
- package/lib/workers.js +135 -9
- package/package.json +41 -37
- package/typings/index.d.ts +17 -4
- package/typings/promiseBasedTypes.d.ts +53 -688
- package/typings/types.d.ts +125 -691
package/README.md
CHANGED
|
@@ -64,6 +64,8 @@ You don't need to worry about asynchronous nature of NodeJS or about various API
|
|
|
64
64
|
- Also plays nice with TypeScript.
|
|
65
65
|
- </> Smart locators: use names, labels, matching text, CSS or XPath to locate elements.
|
|
66
66
|
- 🌐 Interactive debugging shell: pause test at any point and try different commands in a browser.
|
|
67
|
+
- ⚡ **Parallel testing** with dynamic test pooling for optimal load balancing and performance.
|
|
68
|
+
- 📊 **Built-in HTML Reporter** with interactive dashboard, step-by-step execution details, and comprehensive test analytics.
|
|
67
69
|
- Easily create tests, pageobjects, stepobjects with CLI generators.
|
|
68
70
|
|
|
69
71
|
## Installation
|
|
@@ -233,6 +235,49 @@ Scenario('test title', () => {
|
|
|
233
235
|
})
|
|
234
236
|
```
|
|
235
237
|
|
|
238
|
+
## HTML Reporter
|
|
239
|
+
|
|
240
|
+
CodeceptJS includes a powerful built-in HTML Reporter that generates comprehensive, interactive test reports with detailed information about your test runs. The HTML reporter is **enabled by default** for all new projects and provides:
|
|
241
|
+
|
|
242
|
+
### Features
|
|
243
|
+
|
|
244
|
+
- **Interactive Dashboard**: Visual statistics, pie charts, and expandable test details
|
|
245
|
+
- **Step-by-Step Execution**: Shows individual test steps with timing and status indicators
|
|
246
|
+
- **BDD/Gherkin Support**: Full support for feature files with proper scenario formatting
|
|
247
|
+
- **System Information**: Comprehensive environment details including browser versions
|
|
248
|
+
- **Advanced Filtering**: Real-time filtering by status, tags, features, and test types
|
|
249
|
+
- **History Tracking**: Multi-run history with trend visualization
|
|
250
|
+
- **Error Details**: Clean formatting of error messages and stack traces
|
|
251
|
+
- **Artifacts Support**: Display screenshots and other test artifacts
|
|
252
|
+
|
|
253
|
+
### Visual Examples
|
|
254
|
+
|
|
255
|
+
#### Interactive Test Dashboard
|
|
256
|
+
|
|
257
|
+
The main dashboard provides a complete overview with interactive statistics and pie charts:
|
|
258
|
+
|
|
259
|
+

|
|
260
|
+
|
|
261
|
+
#### Detailed Test Results
|
|
262
|
+
|
|
263
|
+
Each test shows comprehensive execution details with expandable step information:
|
|
264
|
+
|
|
265
|
+

|
|
266
|
+
|
|
267
|
+
#### Advanced Filtering Capabilities
|
|
268
|
+
|
|
269
|
+
Real-time filtering allows quick navigation through test results:
|
|
270
|
+
|
|
271
|
+

|
|
272
|
+
|
|
273
|
+
#### BDD/Gherkin Support
|
|
274
|
+
|
|
275
|
+
Full support for Gherkin scenarios with proper feature formatting:
|
|
276
|
+
|
|
277
|
+

|
|
278
|
+
|
|
279
|
+
The HTML reporter generates self-contained reports that can be easily shared with your team. Learn more about configuration and features in the [HTML Reporter documentation](https://codecept.io/plugins/#htmlreporter).
|
|
280
|
+
|
|
236
281
|
## PageObjects
|
|
237
282
|
|
|
238
283
|
CodeceptJS provides the most simple way to create and use page objects in your test.
|
package/bin/codecept.js
CHANGED
|
@@ -164,6 +164,8 @@ program
|
|
|
164
164
|
.option('--tests', 'run only JS test files and skip features')
|
|
165
165
|
.option('--no-timeouts', 'disable all timeouts')
|
|
166
166
|
.option('-p, --plugins <k=v,k2=v2,...>', 'enable plugins, comma-separated')
|
|
167
|
+
.option('--shuffle', 'Shuffle the order in which test files run')
|
|
168
|
+
.option('--shard <index/total>', 'run only a fraction of tests (e.g., --shard 1/4)')
|
|
167
169
|
|
|
168
170
|
// mocha options
|
|
169
171
|
.option('--colors', 'force enabling of colors')
|
|
@@ -195,6 +197,7 @@ program
|
|
|
195
197
|
.option('-i, --invert', 'inverts --grep matches')
|
|
196
198
|
.option('-o, --override [value]', 'override current config options')
|
|
197
199
|
.option('--suites', 'parallel execution of suites not single tests')
|
|
200
|
+
.option('--by <strategy>', 'test distribution strategy: "test" (pre-assign individual tests), "suite" (pre-assign test suites), or "pool" (dynamic distribution for optimal load balancing, recommended)')
|
|
198
201
|
.option(commandFlags.debug.flag, commandFlags.debug.description)
|
|
199
202
|
.option(commandFlags.verbose.flag, commandFlags.verbose.description)
|
|
200
203
|
.option('--features', 'run only *.feature files and skip tests')
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Standalone test server script to replace json-server
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const path = require('path')
|
|
8
|
+
const TestServer = require('../lib/test-server')
|
|
9
|
+
|
|
10
|
+
// Parse command line arguments
|
|
11
|
+
const args = process.argv.slice(2)
|
|
12
|
+
let dbFile = path.join(__dirname, '../test/data/rest/db.json')
|
|
13
|
+
let port = 8010
|
|
14
|
+
let host = '0.0.0.0'
|
|
15
|
+
|
|
16
|
+
// Simple argument parsing
|
|
17
|
+
for (let i = 0; i < args.length; i++) {
|
|
18
|
+
const arg = args[i]
|
|
19
|
+
|
|
20
|
+
if (arg === '-p' || arg === '--port') {
|
|
21
|
+
port = parseInt(args[++i])
|
|
22
|
+
} else if (arg === '--host') {
|
|
23
|
+
host = args[++i]
|
|
24
|
+
} else if (!arg.startsWith('-')) {
|
|
25
|
+
dbFile = path.resolve(arg)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Create and start server
|
|
30
|
+
const server = new TestServer({ port, host, dbFile })
|
|
31
|
+
|
|
32
|
+
console.log(`Starting test server with db file: ${dbFile}`)
|
|
33
|
+
|
|
34
|
+
server
|
|
35
|
+
.start()
|
|
36
|
+
.then(() => {
|
|
37
|
+
console.log(`Test server is ready and listening on http://${host}:${port}`)
|
|
38
|
+
})
|
|
39
|
+
.catch(err => {
|
|
40
|
+
console.error('Failed to start test server:', err)
|
|
41
|
+
process.exit(1)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// Graceful shutdown
|
|
45
|
+
process.on('SIGINT', () => {
|
|
46
|
+
console.log('\nShutting down test server...')
|
|
47
|
+
server.stop().then(() => process.exit(0))
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
process.on('SIGTERM', () => {
|
|
51
|
+
console.log('\nShutting down test server...')
|
|
52
|
+
server.stop().then(() => process.exit(0))
|
|
53
|
+
})
|
package/lib/codecept.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { existsSync, readFileSync } = require('fs')
|
|
2
2
|
const { globSync } = require('glob')
|
|
3
|
+
const shuffle = require('lodash.shuffle')
|
|
3
4
|
const fsPath = require('path')
|
|
4
5
|
const { resolve } = require('path')
|
|
5
6
|
|
|
@@ -110,6 +111,7 @@ class Codecept {
|
|
|
110
111
|
runHook(require('./listener/helpers'))
|
|
111
112
|
runHook(require('./listener/globalTimeout'))
|
|
112
113
|
runHook(require('./listener/globalRetry'))
|
|
114
|
+
runHook(require('./listener/retryEnhancer'))
|
|
113
115
|
runHook(require('./listener/exit'))
|
|
114
116
|
runHook(require('./listener/emptyRun'))
|
|
115
117
|
|
|
@@ -180,6 +182,50 @@ class Codecept {
|
|
|
180
182
|
})
|
|
181
183
|
}
|
|
182
184
|
}
|
|
185
|
+
|
|
186
|
+
if (this.opts.shuffle) {
|
|
187
|
+
this.testFiles = shuffle(this.testFiles)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (this.opts.shard) {
|
|
191
|
+
this.testFiles = this._applySharding(this.testFiles, this.opts.shard)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Apply sharding to test files based on shard configuration
|
|
197
|
+
*
|
|
198
|
+
* @param {Array<string>} testFiles - Array of test file paths
|
|
199
|
+
* @param {string} shardConfig - Shard configuration in format "index/total" (e.g., "1/4")
|
|
200
|
+
* @returns {Array<string>} - Filtered array of test files for this shard
|
|
201
|
+
*/
|
|
202
|
+
_applySharding(testFiles, shardConfig) {
|
|
203
|
+
const shardMatch = shardConfig.match(/^(\d+)\/(\d+)$/)
|
|
204
|
+
if (!shardMatch) {
|
|
205
|
+
throw new Error('Invalid shard format. Expected format: "index/total" (e.g., "1/4")')
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const shardIndex = parseInt(shardMatch[1], 10)
|
|
209
|
+
const shardTotal = parseInt(shardMatch[2], 10)
|
|
210
|
+
|
|
211
|
+
if (shardTotal < 1) {
|
|
212
|
+
throw new Error('Shard total must be at least 1')
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (shardIndex < 1 || shardIndex > shardTotal) {
|
|
216
|
+
throw new Error(`Shard index ${shardIndex} must be between 1 and ${shardTotal}`)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (testFiles.length === 0) {
|
|
220
|
+
return testFiles
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Calculate which tests belong to this shard
|
|
224
|
+
const shardSize = Math.ceil(testFiles.length / shardTotal)
|
|
225
|
+
const startIndex = (shardIndex - 1) * shardSize
|
|
226
|
+
const endIndex = Math.min(startIndex + shardSize, testFiles.length)
|
|
227
|
+
|
|
228
|
+
return testFiles.slice(startIndex, endIndex)
|
|
183
229
|
}
|
|
184
230
|
|
|
185
231
|
/**
|
package/lib/command/init.js
CHANGED
|
@@ -10,7 +10,22 @@ module.exports = async function (workerCount, selectedRuns, options) {
|
|
|
10
10
|
|
|
11
11
|
const { config: testConfig, override = '' } = options
|
|
12
12
|
const overrideConfigs = tryOrDefault(() => JSON.parse(override), {})
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
// Determine test split strategy
|
|
15
|
+
let by = 'test' // default
|
|
16
|
+
if (options.by) {
|
|
17
|
+
// Explicit --by option takes precedence
|
|
18
|
+
by = options.by
|
|
19
|
+
} else if (options.suites) {
|
|
20
|
+
// Legacy --suites option
|
|
21
|
+
by = 'suite'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Validate the by option
|
|
25
|
+
const validStrategies = ['test', 'suite', 'pool']
|
|
26
|
+
if (!validStrategies.includes(by)) {
|
|
27
|
+
throw new Error(`Invalid --by strategy: ${by}. Valid options are: ${validStrategies.join(', ')}`)
|
|
28
|
+
}
|
|
14
29
|
delete options.parent
|
|
15
30
|
const config = {
|
|
16
31
|
by,
|
|
@@ -20,7 +20,7 @@ const stderr = ''
|
|
|
20
20
|
// Requiring of Codecept need to be after tty.getWindowSize is available.
|
|
21
21
|
const Codecept = require(process.env.CODECEPT_CLASS_PATH || '../../codecept')
|
|
22
22
|
|
|
23
|
-
const { options, tests, testRoot, workerIndex } = workerData
|
|
23
|
+
const { options, tests, testRoot, workerIndex, poolMode } = workerData
|
|
24
24
|
|
|
25
25
|
// hide worker output
|
|
26
26
|
if (!options.debug && !options.verbose)
|
|
@@ -39,15 +39,26 @@ const codecept = new Codecept(config, options)
|
|
|
39
39
|
codecept.init(testRoot)
|
|
40
40
|
codecept.loadTests()
|
|
41
41
|
const mocha = container.mocha()
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
if (poolMode) {
|
|
44
|
+
// In pool mode, don't filter tests upfront - wait for assignments
|
|
45
|
+
// We'll reload test files fresh for each test request
|
|
46
|
+
} else {
|
|
47
|
+
// Legacy mode - filter tests upfront
|
|
48
|
+
filterTests()
|
|
49
|
+
}
|
|
43
50
|
|
|
44
51
|
// run tests
|
|
45
52
|
;(async function () {
|
|
46
|
-
if (
|
|
53
|
+
if (poolMode) {
|
|
54
|
+
await runPoolTests()
|
|
55
|
+
} else if (mocha.suite.total()) {
|
|
47
56
|
await runTests()
|
|
48
57
|
}
|
|
49
58
|
})()
|
|
50
59
|
|
|
60
|
+
let globalStats = { passes: 0, failures: 0, tests: 0, pending: 0, failedHooks: 0 }
|
|
61
|
+
|
|
51
62
|
async function runTests() {
|
|
52
63
|
try {
|
|
53
64
|
await codecept.bootstrap()
|
|
@@ -64,6 +75,192 @@ async function runTests() {
|
|
|
64
75
|
}
|
|
65
76
|
}
|
|
66
77
|
|
|
78
|
+
async function runPoolTests() {
|
|
79
|
+
try {
|
|
80
|
+
await codecept.bootstrap()
|
|
81
|
+
} catch (err) {
|
|
82
|
+
throw new Error(`Error while running bootstrap file :${err}`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
initializeListeners()
|
|
86
|
+
disablePause()
|
|
87
|
+
|
|
88
|
+
// Accumulate results across all tests in pool mode
|
|
89
|
+
let consolidatedStats = { passes: 0, failures: 0, tests: 0, pending: 0, failedHooks: 0 }
|
|
90
|
+
let allTests = []
|
|
91
|
+
let allFailures = []
|
|
92
|
+
let previousStats = { passes: 0, failures: 0, tests: 0, pending: 0, failedHooks: 0 }
|
|
93
|
+
|
|
94
|
+
// Keep requesting tests until no more available
|
|
95
|
+
while (true) {
|
|
96
|
+
// Request a test assignment
|
|
97
|
+
sendToParentThread({ type: 'REQUEST_TEST', workerIndex })
|
|
98
|
+
|
|
99
|
+
const testResult = await new Promise((resolve, reject) => {
|
|
100
|
+
// Set up pool mode message handler
|
|
101
|
+
const messageHandler = async eventData => {
|
|
102
|
+
if (eventData.type === 'TEST_ASSIGNED') {
|
|
103
|
+
const testUid = eventData.test
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
// In pool mode, we need to create a fresh Mocha instance for each test
|
|
107
|
+
// because Mocha instances become disposed after running tests
|
|
108
|
+
container.createMocha() // Create fresh Mocha instance
|
|
109
|
+
filterTestById(testUid)
|
|
110
|
+
const mocha = container.mocha()
|
|
111
|
+
|
|
112
|
+
if (mocha.suite.total() > 0) {
|
|
113
|
+
// Run the test and complete
|
|
114
|
+
await codecept.run()
|
|
115
|
+
|
|
116
|
+
// Get the results from this specific test run
|
|
117
|
+
const result = container.result()
|
|
118
|
+
const currentStats = result.stats || {}
|
|
119
|
+
|
|
120
|
+
// Calculate the difference from previous accumulated stats
|
|
121
|
+
const newPasses = Math.max(0, (currentStats.passes || 0) - previousStats.passes)
|
|
122
|
+
const newFailures = Math.max(0, (currentStats.failures || 0) - previousStats.failures)
|
|
123
|
+
const newTests = Math.max(0, (currentStats.tests || 0) - previousStats.tests)
|
|
124
|
+
const newPending = Math.max(0, (currentStats.pending || 0) - previousStats.pending)
|
|
125
|
+
const newFailedHooks = Math.max(0, (currentStats.failedHooks || 0) - previousStats.failedHooks)
|
|
126
|
+
|
|
127
|
+
// Add only the new results
|
|
128
|
+
consolidatedStats.passes += newPasses
|
|
129
|
+
consolidatedStats.failures += newFailures
|
|
130
|
+
consolidatedStats.tests += newTests
|
|
131
|
+
consolidatedStats.pending += newPending
|
|
132
|
+
consolidatedStats.failedHooks += newFailedHooks
|
|
133
|
+
|
|
134
|
+
// Update previous stats for next comparison
|
|
135
|
+
previousStats = { ...currentStats }
|
|
136
|
+
|
|
137
|
+
// Add new failures to consolidated collections
|
|
138
|
+
if (result.failures && result.failures.length > allFailures.length) {
|
|
139
|
+
const newFailures = result.failures.slice(allFailures.length)
|
|
140
|
+
allFailures.push(...newFailures)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Signal test completed and request next
|
|
145
|
+
parentPort?.off('message', messageHandler)
|
|
146
|
+
resolve('TEST_COMPLETED')
|
|
147
|
+
} catch (err) {
|
|
148
|
+
parentPort?.off('message', messageHandler)
|
|
149
|
+
reject(err)
|
|
150
|
+
}
|
|
151
|
+
} else if (eventData.type === 'NO_MORE_TESTS') {
|
|
152
|
+
// No tests available, exit worker
|
|
153
|
+
parentPort?.off('message', messageHandler)
|
|
154
|
+
resolve('NO_MORE_TESTS')
|
|
155
|
+
} else {
|
|
156
|
+
// Handle other message types (support messages, etc.)
|
|
157
|
+
container.append({ support: eventData.data })
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
parentPort?.on('message', messageHandler)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
// Exit if no more tests
|
|
165
|
+
if (testResult === 'NO_MORE_TESTS') {
|
|
166
|
+
break
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
await codecept.teardown()
|
|
172
|
+
} catch (err) {
|
|
173
|
+
// Log teardown errors but don't fail
|
|
174
|
+
console.error('Teardown error:', err)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Send final consolidated results for the entire worker
|
|
178
|
+
const finalResult = {
|
|
179
|
+
hasFailed: consolidatedStats.failures > 0,
|
|
180
|
+
stats: consolidatedStats,
|
|
181
|
+
duration: 0, // Pool mode doesn't track duration per worker
|
|
182
|
+
tests: [], // Keep tests empty to avoid serialization issues - stats are sufficient
|
|
183
|
+
failures: allFailures, // Include all failures for error reporting
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
sendToParentThread({ event: event.all.after, workerIndex, data: finalResult })
|
|
187
|
+
sendToParentThread({ event: event.all.result, workerIndex, data: finalResult })
|
|
188
|
+
|
|
189
|
+
// Add longer delay to ensure messages are delivered before closing
|
|
190
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
191
|
+
|
|
192
|
+
// Close worker thread when pool mode is complete
|
|
193
|
+
parentPort?.close()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function filterTestById(testUid) {
|
|
197
|
+
// Reload test files fresh for each test in pool mode
|
|
198
|
+
const files = codecept.testFiles
|
|
199
|
+
|
|
200
|
+
// Get the existing mocha instance
|
|
201
|
+
const mocha = container.mocha()
|
|
202
|
+
|
|
203
|
+
// Clear suites and tests but preserve other mocha settings
|
|
204
|
+
mocha.suite.suites = []
|
|
205
|
+
mocha.suite.tests = []
|
|
206
|
+
|
|
207
|
+
// Clear require cache for test files to ensure fresh loading
|
|
208
|
+
files.forEach(file => {
|
|
209
|
+
delete require.cache[require.resolve(file)]
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
// Set files and load them
|
|
213
|
+
mocha.files = files
|
|
214
|
+
mocha.loadFiles()
|
|
215
|
+
|
|
216
|
+
// Now filter to only the target test - use a more robust approach
|
|
217
|
+
let foundTest = false
|
|
218
|
+
for (const suite of mocha.suite.suites) {
|
|
219
|
+
const originalTests = [...suite.tests]
|
|
220
|
+
suite.tests = []
|
|
221
|
+
|
|
222
|
+
for (const test of originalTests) {
|
|
223
|
+
if (test.uid === testUid) {
|
|
224
|
+
suite.tests.push(test)
|
|
225
|
+
foundTest = true
|
|
226
|
+
break // Only add one matching test
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// If no tests found in this suite, remove it
|
|
231
|
+
if (suite.tests.length === 0) {
|
|
232
|
+
suite.parent.suites = suite.parent.suites.filter(s => s !== suite)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Filter out empty suites from the root
|
|
237
|
+
mocha.suite.suites = mocha.suite.suites.filter(suite => suite.tests.length > 0)
|
|
238
|
+
|
|
239
|
+
if (!foundTest) {
|
|
240
|
+
// If testUid doesn't match, maybe it's a simple test name - try fallback
|
|
241
|
+
mocha.suite.suites = []
|
|
242
|
+
mocha.suite.tests = []
|
|
243
|
+
mocha.loadFiles()
|
|
244
|
+
|
|
245
|
+
// Try matching by title
|
|
246
|
+
for (const suite of mocha.suite.suites) {
|
|
247
|
+
const originalTests = [...suite.tests]
|
|
248
|
+
suite.tests = []
|
|
249
|
+
|
|
250
|
+
for (const test of originalTests) {
|
|
251
|
+
if (test.title === testUid || test.fullTitle() === testUid || test.uid === testUid) {
|
|
252
|
+
suite.tests.push(test)
|
|
253
|
+
foundTest = true
|
|
254
|
+
break
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Clean up empty suites again
|
|
260
|
+
mocha.suite.suites = mocha.suite.suites.filter(suite => suite.tests.length > 0)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
67
264
|
function filterTests() {
|
|
68
265
|
const files = codecept.testFiles
|
|
69
266
|
mocha.files = files
|
|
@@ -102,14 +299,20 @@ function initializeListeners() {
|
|
|
102
299
|
event.dispatcher.on(event.hook.passed, hook => sendToParentThread({ event: event.hook.passed, workerIndex, data: hook.simplify() }))
|
|
103
300
|
event.dispatcher.on(event.hook.finished, hook => sendToParentThread({ event: event.hook.finished, workerIndex, data: hook.simplify() }))
|
|
104
301
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
302
|
+
if (!poolMode) {
|
|
303
|
+
// In regular mode, close worker after all tests are complete
|
|
304
|
+
event.dispatcher.once(event.all.after, () => {
|
|
305
|
+
sendToParentThread({ event: event.all.after, workerIndex, data: container.result().simplify() })
|
|
306
|
+
})
|
|
307
|
+
// all
|
|
308
|
+
event.dispatcher.once(event.all.result, () => {
|
|
309
|
+
sendToParentThread({ event: event.all.result, workerIndex, data: container.result().simplify() })
|
|
310
|
+
parentPort?.close()
|
|
311
|
+
})
|
|
312
|
+
} else {
|
|
313
|
+
// In pool mode, don't send result events for individual tests
|
|
314
|
+
// Results will be sent once when the worker completes all tests
|
|
315
|
+
}
|
|
113
316
|
}
|
|
114
317
|
|
|
115
318
|
function disablePause() {
|
|
@@ -121,7 +324,10 @@ function sendToParentThread(data) {
|
|
|
121
324
|
}
|
|
122
325
|
|
|
123
326
|
function listenToParentThread() {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
327
|
+
if (!poolMode) {
|
|
328
|
+
parentPort?.on('message', eventData => {
|
|
329
|
+
container.append({ support: eventData.data })
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
// In pool mode, message handling is done in runPoolTests()
|
|
127
333
|
}
|
package/lib/container.js
CHANGED
|
@@ -28,6 +28,7 @@ let container = {
|
|
|
28
28
|
translation: {},
|
|
29
29
|
/** @type {Result | null} */
|
|
30
30
|
result: null,
|
|
31
|
+
sharedKeys: new Set() // Track keys shared via share() function
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
/**
|
|
@@ -174,6 +175,7 @@ class Container {
|
|
|
174
175
|
container.translation = loadTranslation()
|
|
175
176
|
container.proxySupport = createSupportObjects(newSupport)
|
|
176
177
|
container.plugins = newPlugins
|
|
178
|
+
container.sharedKeys = new Set() // Clear shared keys
|
|
177
179
|
asyncHelperPromise = Promise.resolve()
|
|
178
180
|
store.actor = null
|
|
179
181
|
debug('container cleared')
|
|
@@ -197,7 +199,13 @@ class Container {
|
|
|
197
199
|
* @param {Object} options - set {local: true} to not share among workers
|
|
198
200
|
*/
|
|
199
201
|
static share(data, options = {}) {
|
|
200
|
-
|
|
202
|
+
// Instead of using append which replaces the entire container,
|
|
203
|
+
// directly update the support object to maintain proxy references
|
|
204
|
+
Object.assign(container.support, data)
|
|
205
|
+
|
|
206
|
+
// Track which keys were explicitly shared
|
|
207
|
+
Object.keys(data).forEach(key => container.sharedKeys.add(key))
|
|
208
|
+
|
|
201
209
|
if (!options.local) {
|
|
202
210
|
WorkerStorage.share(data)
|
|
203
211
|
}
|
|
@@ -396,10 +404,11 @@ function createSupportObjects(config) {
|
|
|
396
404
|
{},
|
|
397
405
|
{
|
|
398
406
|
has(target, key) {
|
|
399
|
-
return keys.includes(key)
|
|
407
|
+
return keys.includes(key) || container.sharedKeys.has(key)
|
|
400
408
|
},
|
|
401
409
|
ownKeys() {
|
|
402
|
-
|
|
410
|
+
// Return both original config keys and explicitly shared keys
|
|
411
|
+
return [...new Set([...keys, ...container.sharedKeys])]
|
|
403
412
|
},
|
|
404
413
|
getOwnPropertyDescriptor(target, prop) {
|
|
405
414
|
return {
|
|
@@ -409,6 +418,10 @@ function createSupportObjects(config) {
|
|
|
409
418
|
}
|
|
410
419
|
},
|
|
411
420
|
get(target, key) {
|
|
421
|
+
// First check if this is an explicitly shared property
|
|
422
|
+
if (container.sharedKeys.has(key) && key in container.support) {
|
|
423
|
+
return container.support[key]
|
|
424
|
+
}
|
|
412
425
|
return lazyLoad(key)
|
|
413
426
|
},
|
|
414
427
|
},
|