Skip to content

Commit f561d29

Browse files
Copilotkobenguyent
andcommitted
Changes before error encountered
Co-authored-by: kobenguyent <[email protected]>
1 parent cbd5e81 commit f561d29

File tree

6 files changed

+487
-75
lines changed

6 files changed

+487
-75
lines changed

bin/codecept.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ program
165165
.option('--no-timeouts', 'disable all timeouts')
166166
.option('-p, --plugins <k=v,k2=v2,...>', 'enable plugins, comma-separated')
167167
.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)')
168169

169170
// mocha options
170171
.option('--colors', 'force enabling of colors')

docs/parallel.md

Lines changed: 134 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,71 @@ title: Parallel Execution
55

66
# Parallel Execution
77

8-
CodeceptJS has two engines for running tests in parallel:
8+
CodeceptJS has multiple approaches for running tests in parallel:
99

10-
* `run-workers` - which spawns [NodeJS Worker](https://nodejs.org/api/worker_threads.html) in a thread. Tests are split by scenarios, scenarios are mixed between groups, each worker runs tests from its own group.
11-
* `run-multiple` - which spawns a subprocess with CodeceptJS. Tests are split by files and configured in `codecept.conf.js`.
10+
- **Test Sharding** - distributes tests across multiple machines for CI matrix execution
11+
- `run-workers` - which spawns [NodeJS Worker](https://nodejs.org/api/worker_threads.html) in a thread. Tests are split by scenarios, scenarios are mixed between groups, each worker runs tests from its own group.
12+
- `run-multiple` - which spawns a subprocess with CodeceptJS. Tests are split by files and configured in `codecept.conf.js`.
1213

1314
Workers are faster and simpler to start, while `run-multiple` requires additional configuration and can be used to run tests in different browsers at once.
1415

16+
## Test Sharding for CI Matrix
17+
18+
Test sharding allows you to split your test suite across multiple machines or CI workers without manual configuration. This is particularly useful for CI/CD pipelines where you want to run tests in parallel across different machines.
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+
1573
## Parallel Execution by Workers
1674
1775
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:
@@ -128,27 +186,27 @@ FAIL | 7 passed, 1 failed, 1 skipped // 2s
128186
CodeceptJS also exposes the env var `process.env.RUNS_WITH_WORKERS` when running tests with `run-workers` command so that you could handle the events better in your plugins/helpers
129187

130188
```js
131-
const { event } = require('codeceptjs');
189+
const { event } = require('codeceptjs')
132190

133-
module.exports = function() {
134-
// this event would trigger the `_publishResultsToTestrail` when running `run-workers` command
191+
module.exports = function () {
192+
// this event would trigger the `_publishResultsToTestrail` when running `run-workers` command
135193
event.dispatcher.on(event.workers.result, async () => {
136-
await _publishResultsToTestrail();
137-
});
138-
194+
await _publishResultsToTestrail()
195+
})
196+
139197
// this event would not trigger the `_publishResultsToTestrail` multiple times when running `run-workers` command
140198
event.dispatcher.on(event.all.result, async () => {
141-
// when running `run` command, this env var is undefined
142-
if (!process.env.RUNS_WITH_WORKERS) await _publishResultsToTestrail();
143-
});
199+
// when running `run` command, this env var is undefined
200+
if (!process.env.RUNS_WITH_WORKERS) await _publishResultsToTestrail()
201+
})
144202
}
145203
```
146204

147205
## Parallel Execution by Workers on Multiple Browsers
148206

149-
To run tests in parallel across multiple browsers, modify your `codecept.conf.js` file to configure multiple browsers on which you want to run your tests and your tests will run across multiple browsers.
207+
To run tests in parallel across multiple browsers, modify your `codecept.conf.js` file to configure multiple browsers on which you want to run your tests and your tests will run across multiple browsers.
150208

151-
Start with modifying the `codecept.conf.js` file. Add multiple key inside the config which will be used to configure multiple profiles.
209+
Start with modifying the `codecept.conf.js` file. Add multiple key inside the config which will be used to configure multiple profiles.
152210

153211
```
154212
exports.config = {
@@ -174,7 +232,7 @@ exports.config = {
174232
}
175233
}
176234
]
177-
},
235+
},
178236
profile2: {
179237
browsers: [
180238
{
@@ -188,16 +246,21 @@ exports.config = {
188246
}
189247
};
190248
```
191-
To trigger tests on all the profiles configured, you can use the following command:
249+
250+
To trigger tests on all the profiles configured, you can use the following command:
251+
192252
```
193253
npx codeceptjs run-workers 3 all -c codecept.conf.js
194254
```
255+
195256
This will run your tests across all browsers configured from profile1 & profile2 on 3 workers.
196257

197-
To trigger tests on specific profile, you can use the following command:
258+
To trigger tests on specific profile, you can use the following command:
259+
198260
```
199261
npx codeceptjs run-workers 2 profile1 -c codecept.conf.js
200262
```
263+
201264
This will run your tests across 2 browsers from profile1 on 2 workers.
202265

203266
## Custom Parallel Execution
@@ -221,7 +284,7 @@ Create a placeholder in file:
221284

222285
```js
223286
#!/usr/bin/env node
224-
const { Workers, event } = require('codeceptjs');
287+
const { Workers, event } = require('codeceptjs')
225288
// here will go magic
226289
```
227290

@@ -232,59 +295,59 @@ Now let's see how to update this file for different parallelization modes:
232295
```js
233296
const workerConfig = {
234297
testConfig: './test/data/sandbox/codecept.customworker.js',
235-
};
298+
}
236299

237300
// don't initialize workers in constructor
238-
const workers = new Workers(null, workerConfig);
301+
const workers = new Workers(null, workerConfig)
239302
// split tests by suites in 2 groups
240-
const testGroups = workers.createGroupsOfSuites(2);
303+
const testGroups = workers.createGroupsOfSuites(2)
241304

242-
const browsers = ['firefox', 'chrome'];
305+
const browsers = ['firefox', 'chrome']
243306

244307
const configs = browsers.map(browser => {
245308
return {
246309
helpers: {
247-
WebDriver: { browser }
248-
}
249-
};
250-
});
310+
WebDriver: { browser },
311+
},
312+
}
313+
})
251314

252315
for (const config of configs) {
253316
for (group of testGroups) {
254-
const worker = workers.spawn();
255-
worker.addTests(group);
256-
worker.addConfig(config);
317+
const worker = workers.spawn()
318+
worker.addTests(group)
319+
worker.addConfig(config)
257320
}
258321
}
259322

260323
// Listen events for failed test
261-
workers.on(event.test.failed, (failedTest) => {
262-
console.log('Failed : ', failedTest.title);
263-
});
324+
workers.on(event.test.failed, failedTest => {
325+
console.log('Failed : ', failedTest.title)
326+
})
264327

265328
// Listen events for passed test
266-
workers.on(event.test.passed, (successTest) => {
267-
console.log('Passed : ', successTest.title);
268-
});
329+
workers.on(event.test.passed, successTest => {
330+
console.log('Passed : ', successTest.title)
331+
})
269332

270333
// test run status will also be available in event
271334
workers.on(event.all.result, () => {
272335
// Use printResults() to display result with standard style
273-
workers.printResults();
274-
});
336+
workers.printResults()
337+
})
275338

276339
// run workers as async function
277-
runWorkers();
340+
runWorkers()
278341

279342
async function runWorkers() {
280343
try {
281344
// run bootstrapAll
282-
await workers.bootstrapAll();
345+
await workers.bootstrapAll()
283346
// run tests
284-
await workers.run();
347+
await workers.run()
285348
} finally {
286349
// run teardown All
287-
await workers.teardownAll();
350+
await workers.teardownAll()
288351
}
289352
}
290353
```
@@ -313,7 +376,6 @@ workers.on(event.all.result, (status, completedTests, workerStats) => {
313376
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.
314377
315378
```js
316-
317379
/*
318380
Define a function to split your tests.
319381
@@ -322,28 +384,25 @@ If you want your tests to split according to your need this method is suited for
322384
where file1 and file2 will run in a worker thread and file3 will run in a worker thread
323385
*/
324386
const splitTests = () => {
325-
const files = [
326-
['./test/data/sandbox/guthub_test.js', './test/data/sandbox/devto_test.js'],
327-
['./test/data/sandbox/longrunnig_test.js']
328-
];
387+
const files = [['./test/data/sandbox/guthub_test.js', './test/data/sandbox/devto_test.js'], ['./test/data/sandbox/longrunnig_test.js']]
329388

330-
return files;
389+
return files
331390
}
332391

333392
const workerConfig = {
334393
testConfig: './test/data/sandbox/codecept.customworker.js',
335-
by: splitTests
336-
};
394+
by: splitTests,
395+
}
337396

338397
// don't initialize workers in constructor
339-
const customWorkers = new Workers(null, workerConfig);
398+
const customWorkers = new Workers(null, workerConfig)
340399

341-
customWorkers.run();
400+
customWorkers.run()
342401

343402
// You can use event listeners similar to above example.
344403
customWorkers.on(event.all.result, () => {
345-
workers.printResults();
346-
});
404+
workers.printResults()
405+
})
347406
```
348407
349408
### Emitting messages to the parent worker
@@ -353,13 +412,13 @@ Child workers can send non-test events to the main process. This is useful if yo
353412
```js
354413
// inside main process
355414
// listen for any non test related events
356-
workers.on('message', (data) => {
415+
workers.on('message', data => {
357416
console.log(data)
358-
});
417+
})
359418

360419
workers.on(event.all.result, (status, completedTests, workerStats) => {
361420
// logic
362-
});
421+
})
363422
```
364423
365424
## Sharing Data Between Workers
@@ -372,12 +431,12 @@ You can share data directly using the `share()` function and access it using `in
372431
373432
```js
374433
// In one test or worker
375-
share({ userData: { name: 'user', password: '123456' } });
434+
share({ userData: { name: 'user', password: '123456' } })
376435

377436
// In another test or worker
378-
const testData = inject();
379-
console.log(testData.userData.name); // 'user'
380-
console.log(testData.userData.password); // '123456'
437+
const testData = inject()
438+
console.log(testData.userData.name) // 'user'
439+
console.log(testData.userData.password) // '123456'
381440
```
382441
383442
### Initializing Data in Bootstrap
@@ -389,20 +448,20 @@ For complex scenarios where you need to initialize shared data before tests run,
389448
exports.config = {
390449
bootstrap() {
391450
// Initialize shared data container
392-
share({ userData: null, config: { retries: 3 } });
393-
}
451+
share({ userData: null, config: { retries: 3 } })
452+
},
394453
}
395454
```
396455
397456
Then in your tests, you can check and update the shared data:
398457
399458
```js
400-
const testData = inject();
459+
const testData = inject()
401460
if (!testData.userData) {
402461
// Update shared data - both approaches work:
403-
share({ userData: { name: 'user', password: '123456' } });
462+
share({ userData: { name: 'user', password: '123456' } })
404463
// or mutate the injected object:
405-
testData.userData = { name: 'user', password: '123456' };
464+
testData.userData = { name: 'user', password: '123456' }
406465
}
407466
```
408467
@@ -412,30 +471,30 @@ Since CodeceptJS 3.7.0+, shared data uses Proxy objects for synchronization betw
412471
413472
```js
414473
// ✅ All of these work correctly:
415-
const data = inject();
416-
console.log(data.userData.name); // Access nested properties
417-
console.log(Object.keys(data)); // Enumerate shared keys
418-
data.newProperty = 'value'; // Add new properties
419-
Object.assign(data, { more: 'data' }); // Merge objects
474+
const data = inject()
475+
console.log(data.userData.name) // Access nested properties
476+
console.log(Object.keys(data)) // Enumerate shared keys
477+
data.newProperty = 'value' // Add new properties
478+
Object.assign(data, { more: 'data' }) // Merge objects
420479
```
421480
422481
**Important Note:** Avoid reassigning the entire injected object:
423482
424483
```js
425484
// ❌ AVOID: This breaks the proxy reference
426-
let testData = inject();
427-
testData = someOtherObject; // This will NOT work as expected!
485+
let testData = inject()
486+
testData = someOtherObject // This will NOT work as expected!
428487

429488
// ✅ PREFERRED: Use share() to replace data or mutate properties
430-
share({ userData: someOtherObject }); // This works!
489+
share({ userData: someOtherObject }) // This works!
431490
// or
432-
Object.assign(inject(), someOtherObject); // This works!
491+
Object.assign(inject(), someOtherObject) // This works!
433492
```
434493
435494
### Local Data (Worker-Specific)
436495
437496
If you want to share data only within the same worker (not across all workers), use the `local` option:
438497
439498
```js
440-
share({ localData: 'worker-specific' }, { local: true });
499+
share({ localData: 'worker-specific' }, { local: true })
441500
```

0 commit comments

Comments
 (0)