codeceptjs 4.0.0-rc.21 → 4.0.0-rc.23
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/docs/advanced.md +1 -1
- package/docs/architecture.md +219 -0
- package/docs/configuration.md +82 -127
- package/docs/continuous-integration.md +113 -151
- package/docs/custom-helpers.md +1 -1
- package/docs/hooks.md +76 -277
- package/docs/installation.md +95 -40
- package/docs/parallel.md +98 -496
- package/docs/plugins.md +43 -0
- package/docs/reports.md +102 -401
- package/docs/retry.md +44 -37
- package/docs/typescript.md +54 -269
- package/lib/actor.js +1 -1
- package/lib/command/workers/runTests.js +1 -5
- package/lib/heal.js +2 -2
- package/lib/helper/Playwright.js +10 -4
- package/lib/plugin/aiTrace.js +4 -3
- package/lib/plugin/junitReporter.js +303 -0
- package/lib/plugin/retryFailedStep.js +4 -3
- package/lib/plugin/screencast.js +1 -1
- package/lib/plugin/screenshot.js +2 -2
- package/lib/plugin/stepTimeout.js +2 -1
- package/lib/step/base.js +7 -7
- package/lib/step/comment.js +2 -2
- package/lib/step/helper.js +4 -4
- package/lib/step/meta.js +3 -3
- package/lib/step/record.js +3 -3
- package/package.json +1 -1
- package/docs/internal-api.md +0 -265
package/docs/parallel.md
CHANGED
|
@@ -5,581 +5,183 @@ title: Parallel Execution
|
|
|
5
5
|
|
|
6
6
|
# Parallel Execution
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Two built-in ways to run tests in parallel:
|
|
9
9
|
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- `run-multiple` - which spawns a subprocess with CodeceptJS. Tests are split by files and configured in `codecept.conf.js`.
|
|
10
|
+
- **`run-workers`** — split tests across worker threads on one runner.
|
|
11
|
+
- **`--shard`** — split test files across CI machines in a matrix build.
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
For anything more specific — your own grouping, a config per group, several browsers at once — drive the `Workers` API from a script (see [Custom parallelization](#custom-parallelization)).
|
|
15
14
|
|
|
16
|
-
##
|
|
15
|
+
## Workers
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Use the `--shard` option with the `run` command to execute only a portion of your tests:
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
# Run the first quarter of tests
|
|
24
|
-
npx codeceptjs run --shard 1/4
|
|
25
|
-
|
|
26
|
-
# Run the second quarter of tests
|
|
27
|
-
npx codeceptjs run --shard 2/4
|
|
28
|
-
|
|
29
|
-
# Run the third quarter of tests
|
|
30
|
-
npx codeceptjs run --shard 3/4
|
|
31
|
-
|
|
32
|
-
# Run the fourth quarter of tests
|
|
33
|
-
npx codeceptjs run --shard 4/4
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
### CI Matrix Example
|
|
37
|
-
|
|
38
|
-
Here's how you can use test sharding with GitHub Actions matrix strategy:
|
|
39
|
-
|
|
40
|
-
```yaml
|
|
41
|
-
name: Tests
|
|
42
|
-
on: [push, pull_request]
|
|
43
|
-
|
|
44
|
-
jobs:
|
|
45
|
-
test:
|
|
46
|
-
runs-on: ubuntu-latest
|
|
47
|
-
strategy:
|
|
48
|
-
matrix:
|
|
49
|
-
shard: [1/4, 2/4, 3/4, 4/4]
|
|
50
|
-
|
|
51
|
-
steps:
|
|
52
|
-
- uses: actions/checkout@v2
|
|
53
|
-
- uses: actions/setup-node@v2
|
|
54
|
-
- run: npm install
|
|
55
|
-
- run: npx codeceptjs run --shard ${{ matrix.shard }}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
This approach ensures:
|
|
59
|
-
|
|
60
|
-
- Each CI job runs only its assigned portion of tests
|
|
61
|
-
- Tests are distributed evenly across shards
|
|
62
|
-
- No manual configuration or maintenance of test lists
|
|
63
|
-
- Automatic load balancing as you add or remove tests
|
|
64
|
-
|
|
65
|
-
### Shard Distribution
|
|
66
|
-
|
|
67
|
-
Tests are distributed evenly across shards using a round-robin approach:
|
|
68
|
-
|
|
69
|
-
- If you have 100 tests and 4 shards, each shard runs approximately 25 tests
|
|
70
|
-
- The first shard gets tests 1-25, second gets 26-50, third gets 51-75, fourth gets 76-100
|
|
71
|
-
- If tests don't divide evenly, earlier shards may get one extra test
|
|
72
|
-
|
|
73
|
-
## Parallel Execution by Workers
|
|
74
|
-
|
|
75
|
-
It is easy to run tests in parallel if you have a lots of tests and free CPU cores. Just execute your tests using `run-workers` command specifying the number of workers to spawn:
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
npx codeceptjs run-workers 2
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
> ℹ Workers require NodeJS >= 11.7
|
|
82
|
-
|
|
83
|
-
This command is similar to `run`, however, steps output can't be shown in workers mode, as it is impossible to synchronize steps output from different processes.
|
|
84
|
-
|
|
85
|
-
Each worker spins an instance of CodeceptJS, executes a group of tests, and sends back report to the main process.
|
|
86
|
-
|
|
87
|
-
By default, the tests are assigned one by one to the available workers this may lead to multiple execution of `BeforeSuite()`. Use the option `--suites` to assign the suites one by one to the workers.
|
|
17
|
+
`run-workers <N>` spawns `N` [worker threads](https://nodejs.org/api/worker_threads.html), each an independent CodeceptJS instance running a slice of the suite, and merges the results:
|
|
88
18
|
|
|
89
19
|
```sh
|
|
90
|
-
npx codeceptjs run-workers
|
|
20
|
+
npx codeceptjs run-workers 4
|
|
91
21
|
```
|
|
92
22
|
|
|
93
|
-
|
|
23
|
+
Steps are not streamed to the console in this mode — output from separate threads can't be interleaved cleanly. While workers run, CodeceptJS sets `process.env.RUNS_WITH_WORKERS=true`, so plugins and helpers can branch on it. All `run` options work here too: `--grep "@smoke"`, `-c codecept.conf.js`, `--debug`, and the rest.
|
|
94
24
|
|
|
95
|
-
|
|
25
|
+
### Distribution strategies
|
|
96
26
|
|
|
97
|
-
|
|
27
|
+
`--by` controls how tests spread across workers:
|
|
98
28
|
|
|
99
|
-
|
|
29
|
+
| `--by` | How tests are assigned | Use when |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| `test` (default) | each test pinned to a worker up front | tests take roughly equal time |
|
|
32
|
+
| `suite` | each suite pinned to a worker; its tests stay together | suites share a `BeforeSuite` you don't want repeated |
|
|
33
|
+
| `pool` | workers pull the next test from a shared queue as they free up | test durations vary — best load balancing |
|
|
100
34
|
|
|
101
35
|
```sh
|
|
102
|
-
npx codeceptjs run-workers 3 --by test
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
#### Suite Strategy (`--by suite`)
|
|
106
|
-
|
|
107
|
-
Test suites are pre-assigned to workers, with all tests in a suite running on the same worker. This ensures better test isolation but may lead to uneven load distribution.
|
|
108
|
-
|
|
109
|
-
```sh
|
|
110
|
-
npx codeceptjs run-workers 3 --by suite
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
#### Pool Strategy (`--by pool`) - **Recommended for optimal performance**
|
|
114
|
-
|
|
115
|
-
Tests are maintained in a shared pool and distributed dynamically to workers as they become available. This provides the best load balancing and resource utilization.
|
|
116
|
-
|
|
117
|
-
```sh
|
|
118
|
-
npx codeceptjs run-workers 3 --by pool
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
## Dynamic Test Pooling Mode
|
|
122
|
-
|
|
123
|
-
The pool mode enables dynamic test distribution for improved worker load balancing. Instead of pre-assigning tests to workers at startup, tests are stored in a shared pool and distributed on-demand as workers become available.
|
|
124
|
-
|
|
125
|
-
### Benefits of Pool Mode
|
|
126
|
-
|
|
127
|
-
- **Better load balancing**: Workers never sit idle while others are still running long tests
|
|
128
|
-
- **Improved performance**: Especially beneficial when tests have varying execution times
|
|
129
|
-
- **Optimal resource utilization**: All CPU cores stay busy until the entire test suite is complete
|
|
130
|
-
- **Automatic scaling**: Workers continuously process tests until the pool is empty
|
|
131
|
-
|
|
132
|
-
### When to Use Pool Mode
|
|
133
|
-
|
|
134
|
-
Pool mode is particularly effective in these scenarios:
|
|
135
|
-
|
|
136
|
-
- **Uneven test execution times**: When some tests take significantly longer than others
|
|
137
|
-
- **Large test suites**: With hundreds or thousands of tests where load balancing matters
|
|
138
|
-
- **Mixed test types**: When combining unit tests, integration tests, and end-to-end tests
|
|
139
|
-
- **CI/CD pipelines**: For consistent and predictable test execution times
|
|
140
|
-
|
|
141
|
-
### Usage Examples
|
|
142
|
-
|
|
143
|
-
```bash
|
|
144
|
-
# Basic pool mode with 4 workers
|
|
145
36
|
npx codeceptjs run-workers 4 --by pool
|
|
146
|
-
|
|
147
|
-
# Pool mode with grep filtering
|
|
148
|
-
npx codeceptjs run-workers 3 --by pool --grep "@smoke"
|
|
149
|
-
|
|
150
|
-
# Pool mode in debug mode
|
|
151
|
-
npx codeceptjs run-workers 2 --by pool --debug
|
|
152
|
-
|
|
153
|
-
# Pool mode with specific configuration
|
|
154
|
-
npx codeceptjs run-workers 3 --by pool -c codecept.conf.js
|
|
155
37
|
```
|
|
156
38
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
1. **Pool Creation**: All tests are collected into a shared pool of test identifiers
|
|
160
|
-
2. **Worker Initialization**: The specified number of workers are spawned
|
|
161
|
-
3. **Dynamic Assignment**: Workers request tests from the pool when they're ready
|
|
162
|
-
4. **Continuous Processing**: Each worker runs one test, then immediately requests the next
|
|
163
|
-
5. **Automatic Completion**: Workers exit when the pool is empty and no more tests remain
|
|
164
|
-
|
|
165
|
-
### Performance Comparison
|
|
166
|
-
|
|
167
|
-
```bash
|
|
168
|
-
# Traditional mode - tests pre-assigned, some workers may finish early
|
|
169
|
-
npx codeceptjs run-workers 3 --by test # ✓ Good for uniform test times
|
|
170
|
-
|
|
171
|
-
# Suite mode - entire suites assigned to workers
|
|
172
|
-
npx codeceptjs run-workers 3 --by suite # ✓ Good for test isolation
|
|
39
|
+
`--suites` is shorthand for `--by suite`.
|
|
173
40
|
|
|
174
|
-
|
|
175
|
-
npx codeceptjs run-workers 3 --by pool # ✓ Best for mixed test execution times
|
|
176
|
-
```
|
|
41
|
+
### Multiple browsers
|
|
177
42
|
|
|
178
|
-
|
|
43
|
+
Define browser profiles under `multiple` in `codecept.conf.js`:
|
|
179
44
|
|
|
180
45
|
```js
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
46
|
+
multiple: {
|
|
47
|
+
default: { browsers: ['chrome', 'firefox'] },
|
|
48
|
+
}
|
|
49
|
+
```
|
|
184
50
|
|
|
185
|
-
|
|
51
|
+
Then run a profile across workers — by name, or `all` for every profile:
|
|
186
52
|
|
|
187
|
-
|
|
53
|
+
```sh
|
|
54
|
+
npx codeceptjs run-workers 3 default
|
|
55
|
+
npx codeceptjs run-workers 3 all
|
|
56
|
+
```
|
|
188
57
|
|
|
189
|
-
|
|
190
|
-
}
|
|
58
|
+
(`run-multiple` runs the same profiles in separate subprocesses instead of threads — see `npx codeceptjs run-multiple --help`.)
|
|
191
59
|
|
|
192
|
-
|
|
193
|
-
FAIL | 7 passed, 1 failed, 1 skipped // 2s
|
|
194
|
-
{
|
|
195
|
-
"tests": {
|
|
196
|
-
"passed": [
|
|
197
|
-
{
|
|
198
|
-
"type": "test",
|
|
199
|
-
"title": "Assert @C3",
|
|
200
|
-
"body": "() => { }",
|
|
201
|
-
"async": 0,
|
|
202
|
-
"sync": true,
|
|
203
|
-
"_timeout": 2000,
|
|
204
|
-
"_slow": 75,
|
|
205
|
-
"_retries": -1,
|
|
206
|
-
"timedOut": false,
|
|
207
|
-
"_currentRetry": 0,
|
|
208
|
-
"pending": false,
|
|
209
|
-
"opts": {},
|
|
210
|
-
"tags": [
|
|
211
|
-
"@C3"
|
|
212
|
-
],
|
|
213
|
-
"uid": "xe4q1HdqpRrZG5dPe0JG+A",
|
|
214
|
-
"workerIndex": 3,
|
|
215
|
-
"retries": -1,
|
|
216
|
-
"duration": 493,
|
|
217
|
-
"err": null,
|
|
218
|
-
"parent": {
|
|
219
|
-
"title": "My",
|
|
220
|
-
"ctx": {},
|
|
221
|
-
"suites": [],
|
|
222
|
-
"tests": [],
|
|
223
|
-
"root": false,
|
|
224
|
-
"pending": false,
|
|
225
|
-
"_retries": -1,
|
|
226
|
-
"_beforeEach": [],
|
|
227
|
-
"_beforeAll": [],
|
|
228
|
-
"_afterEach": [],
|
|
229
|
-
"_afterAll": [],
|
|
230
|
-
"_timeout": 2000,
|
|
231
|
-
"_slow": 75,
|
|
232
|
-
"_bail": false,
|
|
233
|
-
"_onlyTests": [],
|
|
234
|
-
"_onlySuites": [],
|
|
235
|
-
"delayed": false
|
|
236
|
-
},
|
|
237
|
-
"steps": [
|
|
238
|
-
{
|
|
239
|
-
"actor": "I",
|
|
240
|
-
"name": "amOnPage",
|
|
241
|
-
"status": "success",
|
|
242
|
-
"args": [
|
|
243
|
-
"https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST"
|
|
244
|
-
],
|
|
245
|
-
"startedAt": 1698760652610,
|
|
246
|
-
"startTime": 1698760652611,
|
|
247
|
-
"endTime": 1698760653098,
|
|
248
|
-
"finishedAt": 1698760653098,
|
|
249
|
-
"duration": 488
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
"actor": "I",
|
|
253
|
-
"name": "grabCurrentUrl",
|
|
254
|
-
"status": "success",
|
|
255
|
-
"args": [],
|
|
256
|
-
"startedAt": 1698760653098,
|
|
257
|
-
"startTime": 1698760653098,
|
|
258
|
-
"endTime": 1698760653099,
|
|
259
|
-
"finishedAt": 1698760653099,
|
|
260
|
-
"duration": 1
|
|
261
|
-
}
|
|
262
|
-
]
|
|
263
|
-
}
|
|
264
|
-
],
|
|
265
|
-
"failed": [],
|
|
266
|
-
"skipped": []
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
```
|
|
60
|
+
### Reading worker results
|
|
270
61
|
|
|
271
|
-
|
|
62
|
+
When all workers finish, the run fires `event.workers.result` with the merged result:
|
|
272
63
|
|
|
273
64
|
```js
|
|
274
65
|
import { event } from 'codeceptjs'
|
|
275
66
|
|
|
276
67
|
export default function () {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
// this event would not trigger the `_publishResultsToTestrail` multiple times when running `run-workers` command
|
|
283
|
-
event.dispatcher.on(event.all.result, async () => {
|
|
284
|
-
// when running `run` command, this env var is undefined
|
|
285
|
-
if (!process.env.RUNS_WITH_WORKERS) await _publishResultsToTestrail()
|
|
68
|
+
event.dispatcher.on(event.workers.result, result => {
|
|
69
|
+
console.log(result.hasFailed() ? 'FAILED' : 'PASSED', result.stats)
|
|
70
|
+
for (const test of result.tests) {
|
|
71
|
+
console.log(test.title, test.duration, 'ms', `worker ${test.workerIndex}`)
|
|
72
|
+
}
|
|
286
73
|
})
|
|
287
74
|
}
|
|
288
75
|
```
|
|
289
76
|
|
|
290
|
-
|
|
77
|
+
For end-of-run work like publishing to a test-management tool, listen on `event.workers.result` (fires once) rather than `event.all.result` (fires in every worker).
|
|
291
78
|
|
|
292
|
-
|
|
79
|
+
## Sharding
|
|
293
80
|
|
|
294
|
-
|
|
81
|
+
`--shard <index>/<total>` runs only a slice of your **test files**: the file list is cut into `total` even chunks and this run executes chunk `index`. It is built for CI matrices — one machine per shard, each running `run`:
|
|
295
82
|
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
WebDriver: {
|
|
300
|
-
url: 'http://localhost:3000',
|
|
301
|
-
desiredCapabilties: {}
|
|
302
|
-
}
|
|
303
|
-
},
|
|
304
|
-
multiple: {
|
|
305
|
-
profile1: {
|
|
306
|
-
browsers: [
|
|
307
|
-
{
|
|
308
|
-
browser: "firefox",
|
|
309
|
-
desiredCapabilties: {
|
|
310
|
-
// override capabilties related to firefox
|
|
311
|
-
}
|
|
312
|
-
},
|
|
313
|
-
{
|
|
314
|
-
browser: "chrome",
|
|
315
|
-
desiredCapabilties: {
|
|
316
|
-
// override capabilties related to chrome
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
]
|
|
320
|
-
},
|
|
321
|
-
profile2: {
|
|
322
|
-
browsers: [
|
|
323
|
-
{
|
|
324
|
-
browser: "safari",
|
|
325
|
-
desiredCapabilties: {
|
|
326
|
-
// override capabilties related to safari
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
]
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
};
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
To trigger tests on all the profiles configured, you can use the following command:
|
|
336
|
-
|
|
337
|
-
```
|
|
338
|
-
npx codeceptjs run-workers 3 all -c codecept.conf.js
|
|
83
|
+
```sh
|
|
84
|
+
npx codeceptjs run --shard 1/4
|
|
85
|
+
npx codeceptjs run --shard 2/4
|
|
339
86
|
```
|
|
340
87
|
|
|
341
|
-
|
|
88
|
+
GitHub Actions:
|
|
342
89
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
90
|
+
```yaml
|
|
91
|
+
jobs:
|
|
92
|
+
test:
|
|
93
|
+
runs-on: ubuntu-latest
|
|
94
|
+
strategy:
|
|
95
|
+
matrix:
|
|
96
|
+
shard: ['1/4', '2/4', '3/4', '4/4']
|
|
97
|
+
steps:
|
|
98
|
+
- uses: actions/checkout@v4
|
|
99
|
+
- uses: actions/setup-node@v4
|
|
100
|
+
with:
|
|
101
|
+
node-version: 20
|
|
102
|
+
- run: npm ci
|
|
103
|
+
- run: npx codeceptjs run --shard ${{ matrix.shard }}
|
|
347
104
|
```
|
|
348
105
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
## Custom Parallel Execution
|
|
352
|
-
|
|
353
|
-
To get a full control of parallelization create a custom execution script to match your needs.
|
|
354
|
-
This way you can configure which tests are matched, how the groups are formed, and with which configuration each worker is executed.
|
|
355
|
-
|
|
356
|
-
Start with creating file `bin/parallel.js`.
|
|
357
|
-
|
|
358
|
-
On MacOS/Linux run following commands:
|
|
359
|
-
|
|
360
|
-
```
|
|
361
|
-
mkdir bin
|
|
362
|
-
touch bin/parallel.js
|
|
363
|
-
chmod +x bin/parallel.js
|
|
364
|
-
```
|
|
106
|
+
Add or remove tests freely — shards rebalance automatically.
|
|
365
107
|
|
|
366
|
-
|
|
108
|
+
## Custom parallelization
|
|
367
109
|
|
|
368
|
-
|
|
110
|
+
When the built-in commands aren't enough, build a runner with the `Workers` API: decide which tests go to which group, give each group its own config, and listen for results.
|
|
369
111
|
|
|
370
112
|
```js
|
|
371
113
|
#!/usr/bin/env node
|
|
372
114
|
import { Workers, event } from 'codeceptjs'
|
|
373
|
-
// here will go magic
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
Now let's see how to update this file for different parallelization modes:
|
|
377
|
-
|
|
378
|
-
### Example: Running tests in 2 browsers in 4 threads
|
|
379
115
|
|
|
380
|
-
|
|
381
|
-
const workerConfig = {
|
|
382
|
-
testConfig: './test/data/sandbox/codecept.customworker.js',
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// don't initialize workers in constructor
|
|
386
|
-
const workers = new Workers(null, workerConfig)
|
|
387
|
-
// split tests by suites in 2 groups
|
|
388
|
-
const testGroups = workers.createGroupsOfSuites(2)
|
|
389
|
-
|
|
390
|
-
const browsers = ['firefox', 'chrome']
|
|
391
|
-
|
|
392
|
-
const configs = browsers.map(browser => {
|
|
393
|
-
return {
|
|
394
|
-
helpers: {
|
|
395
|
-
WebDriver: { browser },
|
|
396
|
-
},
|
|
397
|
-
}
|
|
398
|
-
})
|
|
116
|
+
const workers = new Workers(null, { testConfig: './codecept.conf.js' })
|
|
399
117
|
|
|
400
|
-
|
|
401
|
-
|
|
118
|
+
// split the suite into 2 groups, run each group on two browsers
|
|
119
|
+
const groups = workers.createGroupsOfSuites(2)
|
|
120
|
+
for (const browser of ['chromium', 'firefox']) {
|
|
121
|
+
for (const group of groups) {
|
|
402
122
|
const worker = workers.spawn()
|
|
403
123
|
worker.addTests(group)
|
|
404
|
-
worker.addConfig(
|
|
124
|
+
worker.addConfig({ helpers: { Playwright: { browser } } })
|
|
405
125
|
}
|
|
406
126
|
}
|
|
407
127
|
|
|
408
|
-
|
|
409
|
-
workers.on(event.
|
|
410
|
-
console.log('Failed : ', failedTest.title)
|
|
411
|
-
})
|
|
412
|
-
|
|
413
|
-
// Listen events for passed test
|
|
414
|
-
workers.on(event.test.passed, successTest => {
|
|
415
|
-
console.log('Passed : ', successTest.title)
|
|
416
|
-
})
|
|
417
|
-
|
|
418
|
-
// test run status will also be available in event
|
|
419
|
-
workers.on(event.all.result, () => {
|
|
420
|
-
// Use printResults() to display result with standard style
|
|
421
|
-
workers.printResults()
|
|
422
|
-
})
|
|
423
|
-
|
|
424
|
-
// run workers as async function
|
|
425
|
-
runWorkers()
|
|
426
|
-
|
|
427
|
-
async function runWorkers() {
|
|
428
|
-
try {
|
|
429
|
-
// run bootstrapAll
|
|
430
|
-
await workers.bootstrapAll()
|
|
431
|
-
// run tests
|
|
432
|
-
await workers.run()
|
|
433
|
-
} finally {
|
|
434
|
-
// run teardown All
|
|
435
|
-
await workers.teardownAll()
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
```
|
|
128
|
+
workers.on(event.test.failed, t => console.log('FAIL', t.title))
|
|
129
|
+
workers.on(event.all.result, () => workers.printResults())
|
|
439
130
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
console.log('Test status : ', result.hasFailed() ? 'Failed' : 'Passed');
|
|
446
|
-
|
|
447
|
-
// print stats
|
|
448
|
-
console.log(`Total tests : ${result.stats.tests}`);
|
|
449
|
-
console.log(`Passed tests : ${result.stats.passes}`);
|
|
450
|
-
console.log(`Failed test tests : ${result.stats.failures}`);
|
|
451
|
-
|
|
452
|
-
// If you don't want to listen for failed and passed test separately, use `tests` array
|
|
453
|
-
for (const test of result.tests) {
|
|
454
|
-
console.log(`Test status: ${test.err===null}, `, `Test : ${test.title}`);
|
|
455
|
-
}
|
|
131
|
+
await workers.bootstrapAll()
|
|
132
|
+
try {
|
|
133
|
+
await workers.run()
|
|
134
|
+
} finally {
|
|
135
|
+
await workers.teardownAll()
|
|
456
136
|
}
|
|
457
137
|
```
|
|
458
138
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
If you want your tests to split according to your need this method is suited for you. For example: If you have 4 long running test files and 4 normal test files there chance all 4 tests end up in same worker thread. For these cases custom function will be helpful.
|
|
462
|
-
|
|
463
|
-
```js
|
|
464
|
-
/*
|
|
465
|
-
Define a function to split your tests.
|
|
466
|
-
|
|
467
|
-
function should return an array with this format [[file1, file2], [file3], ...]
|
|
468
|
-
|
|
469
|
-
where file1 and file2 will run in a worker thread and file3 will run in a worker thread
|
|
470
|
-
*/
|
|
471
|
-
const splitTests = () => {
|
|
472
|
-
const files = [['./test/data/sandbox/guthub_test.js', './test/data/sandbox/devto_test.js'], ['./test/data/sandbox/longrunnig_test.js']]
|
|
473
|
-
|
|
474
|
-
return files
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
const workerConfig = {
|
|
478
|
-
testConfig: './test/data/sandbox/codecept.customworker.js',
|
|
479
|
-
by: splitTests,
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// don't initialize workers in constructor
|
|
483
|
-
const customWorkers = new Workers(null, workerConfig)
|
|
484
|
-
|
|
485
|
-
customWorkers.run()
|
|
486
|
-
|
|
487
|
-
// You can use event listeners similar to above example.
|
|
488
|
-
customWorkers.on(event.all.result, () => {
|
|
489
|
-
workers.printResults()
|
|
490
|
-
})
|
|
491
|
-
```
|
|
139
|
+
Building blocks:
|
|
492
140
|
|
|
493
|
-
|
|
141
|
+
- `new Workers(N, { testConfig, options })` — `N` workers; pass `null` to spawn them yourself with `spawn()`.
|
|
142
|
+
- `createGroupsOfTests(n)` / `createGroupsOfSuites(n)` — split the suite into `n` groups.
|
|
143
|
+
- `worker.addTests(group)` / `worker.addConfig(partialConfig)` — assign tests and config overrides to a spawned worker.
|
|
144
|
+
- `bootstrapAll()` → `run()` → `teardownAll()` — lifecycle (wrap `run()` in `try/finally` so teardown always runs).
|
|
145
|
+
- Events on the `workers` object: `event.test.passed`, `event.test.failed`, `event.all.result`, plus `'message'` for anything a child worker sends. `printResults()` prints the standard summary; `result.hasFailed()` and `result.stats` give the totals.
|
|
494
146
|
|
|
495
|
-
|
|
147
|
+
To split by your own rule, pass a function as `by` — it receives the worker count and returns an array of file groups:
|
|
496
148
|
|
|
497
149
|
```js
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
})
|
|
503
|
-
|
|
504
|
-
workers.on(event.all.result, result => {
|
|
505
|
-
// logic
|
|
506
|
-
})
|
|
507
|
-
```
|
|
508
|
-
|
|
509
|
-
## Sharing Data Between Workers
|
|
150
|
+
const splitTests = () => [
|
|
151
|
+
['./test/login_test.js', './test/signup_test.js'], // group 1
|
|
152
|
+
['./test/slow_checkout_test.js'], // group 2
|
|
153
|
+
]
|
|
510
154
|
|
|
511
|
-
|
|
155
|
+
const workers = new Workers(2, { testConfig: './codecept.conf.js', by: splitTests })
|
|
156
|
+
workers.on(event.all.result, () => workers.printResults())
|
|
157
|
+
await workers.run()
|
|
158
|
+
```
|
|
512
159
|
|
|
513
|
-
|
|
160
|
+
## Sharing data between workers
|
|
514
161
|
|
|
515
|
-
|
|
162
|
+
Worker threads don't share memory. `share()` publishes a value that any worker reads with `inject()`:
|
|
516
163
|
|
|
517
164
|
```js
|
|
518
|
-
//
|
|
519
|
-
share({
|
|
165
|
+
// in any test or hook
|
|
166
|
+
share({ user: { name: 'jane', password: 's3cret' } })
|
|
520
167
|
|
|
521
|
-
//
|
|
522
|
-
const
|
|
523
|
-
console.log(testData.userData.name) // 'user'
|
|
524
|
-
console.log(testData.userData.password) // '123456'
|
|
168
|
+
// anywhere else, even in another worker
|
|
169
|
+
const { user } = inject()
|
|
525
170
|
```
|
|
526
171
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
For complex scenarios where you need to initialize shared data before tests run, you can use the bootstrap function:
|
|
172
|
+
Seed shared state before tests run from `bootstrap()`:
|
|
530
173
|
|
|
531
174
|
```js
|
|
532
|
-
//
|
|
175
|
+
// codecept.conf.js
|
|
533
176
|
export const config = {
|
|
534
177
|
bootstrap() {
|
|
535
|
-
|
|
536
|
-
share({ userData: null, config: { retries: 3 } })
|
|
178
|
+
share({ user: null })
|
|
537
179
|
},
|
|
538
180
|
}
|
|
539
181
|
```
|
|
540
182
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
```js
|
|
544
|
-
const testData = inject()
|
|
545
|
-
if (!testData.userData) {
|
|
546
|
-
// Update shared data - both approaches work:
|
|
547
|
-
share({ userData: { name: 'user', password: '123456' } })
|
|
548
|
-
// or mutate the injected object:
|
|
549
|
-
testData.userData = { name: 'user', password: '123456' }
|
|
550
|
-
}
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
### Working with Proxy Objects
|
|
554
|
-
|
|
555
|
-
Since CodeceptJS 3.7.0+, shared data uses Proxy objects for synchronization between workers. The proxy system works seamlessly for most use cases:
|
|
556
|
-
|
|
557
|
-
```js
|
|
558
|
-
// ✅ All of these work correctly:
|
|
559
|
-
const data = inject()
|
|
560
|
-
console.log(data.userData.name) // Access nested properties
|
|
561
|
-
console.log(Object.keys(data)) // Enumerate shared keys
|
|
562
|
-
data.newProperty = 'value' // Add new properties
|
|
563
|
-
Object.assign(data, { more: 'data' }) // Merge objects
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
**Important Note:** Avoid reassigning the entire injected object:
|
|
567
|
-
|
|
568
|
-
```js
|
|
569
|
-
// ❌ AVOID: This breaks the proxy reference
|
|
570
|
-
let testData = inject()
|
|
571
|
-
testData = someOtherObject // This will NOT work as expected!
|
|
572
|
-
|
|
573
|
-
// ✅ PREFERRED: Use share() to replace data or mutate properties
|
|
574
|
-
share({ userData: someOtherObject }) // This works!
|
|
575
|
-
// or
|
|
576
|
-
Object.assign(inject(), someOtherObject) // This works!
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
### Local Data (Worker-Specific)
|
|
580
|
-
|
|
581
|
-
If you want to share data only within the same worker (not across all workers), use the `local` option:
|
|
183
|
+
Shared data is a Proxy. Don't reassign the injected object itself (`let d = inject(); d = {…}` breaks the link); mutate it or call `share()` again. Pass `{ local: true }` to keep a value inside one worker:
|
|
582
184
|
|
|
583
185
|
```js
|
|
584
|
-
share({
|
|
186
|
+
share({ tmpFile: '/tmp/run-1' }, { local: true })
|
|
585
187
|
```
|