Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(run-workers) : Add support of multiple browsers for run-workers #3606

2 changes: 1 addition & 1 deletion bin/codecept.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ program.command('run [test]')
.option('--child <string>', 'option for child processes')
.action(errorHandler(require('../lib/command/run')));

program.command('run-workers <workers>')
program.command('run-workers <workers> [selectedRuns...]')
.description('Executes tests in workers')
.option('-c, --config [file]', 'configuration file to be used')
.option('-g, --grep <pattern>', 'only run tests matching <pattern>')
Expand Down
56 changes: 56 additions & 0 deletions docs/parallel.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,62 @@ By default the tests are assigned one by one to the available workers this may l
npx codeceptjs run-workers --suites 2
```

## Parallel Execution by Workers on Multiple Browsers

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.

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

```
exports.config = {
helpers: {
WebDriver: {
url: 'http://localhost:3000',
desiredCapabilties: {}
}
},
multiple: {
profile1: {
browsers: [
{
browser: "firefox",
desiredCapabilties: {
// override capabilties related to firefox
}
},
{
browser: "chrome",
desiredCapabilties: {
// override capabilties related to chrome
}
}
]
},
profile2: {
browsers: [
{
browser: "safari",
desiredCapabilties: {
// override capabilties related to safari
}
}
]
}
}
};
```
To trigger tests on all the profiles configured, you can use the following command:
```
npx codeceptjs run-workers 3 all -c codecept.conf.js
```
This will run your tests across all browsers configured from profile1 & profile2 on 3 workers.

To trigger tests on specific profile, you can use the following command:
```
npx codeceptjs run-workers 2 profile1 -c codecept.conf.js
```
This will run your tests across 2 browsers from profile1 on 2 workers.

## Custom Parallel Execution

To get a full control of parallelization create a custom execution script to match your needs.
Expand Down
3 changes: 2 additions & 1 deletion lib/command/run-workers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const output = require('../output');
const event = require('../event');
const Workers = require('../workers');

module.exports = async function (workerCount, options) {
module.exports = async function (workerCount, selectedRuns, options) {
process.env.profile = options.profile;

const { config: testConfig, override = '' } = options;
Expand All @@ -15,6 +15,7 @@ module.exports = async function (workerCount, options) {
by,
testConfig,
options,
selectedRuns,
};

const numberOfWorkers = parseInt(workerCount, 10);
Expand Down
66 changes: 57 additions & 9 deletions lib/workers.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ const MochaFactory = require('./mochaFactory');
const Container = require('./container');
const { getTestRoot } = require('./command/utils');
const { isFunction, fileExists } = require('./utils');
const { replaceValueDeep, deepClone } = require('./utils');
const mainConfig = require('./config');
const output = require('./output');
const event = require('./event');
const recorder = require('./recorder');
const runHook = require('./hooks');
const WorkerStorage = require('./workerStorage');
const collection = require('./command/run-multiple/collection');

const pathToWorker = path.join(__dirname, 'command', 'workers', 'runTests.js');

Expand Down Expand Up @@ -79,15 +81,39 @@ const repackTest = (test) => {
return test;
};

const createWorkerObjects = (testGroups, config, testRoot, options) => {
return testGroups.map((tests, index) => {
const workerObj = new WorkerObject(index);
workerObj.addConfig(config);
workerObj.addTests(tests);
workerObj.setTestRoot(testRoot);
workerObj.addOptions(options);
return workerObj;
const createWorkerObjects = (testGroups, config, testRoot, options, selectedRuns) => {
selectedRuns = options && options.all && config.multiple ? Object.keys(config.multiple) : selectedRuns;
if (selectedRuns === undefined || !selectedRuns.length || config.multiple === undefined) {
return testGroups.map((tests, index) => {
const workerObj = new WorkerObject(index);
workerObj.addConfig(config);
workerObj.addTests(tests);
workerObj.setTestRoot(testRoot);
workerObj.addOptions(options);
return workerObj;
});
}
const workersToExecute = [];
collection.createRuns(selectedRuns, config).forEach((worker) => {
const workerName = worker.getOriginalName() || worker.getName();
const workerConfig = worker.getConfig();
workersToExecute.push(getOverridenConfig(workerName, workerConfig, config));
});
const workers = [];
let index = 0;
testGroups.forEach((tests) => {
const testWorkerArray = [];
workersToExecute.forEach((finalConfig) => {
const workerObj = new WorkerObject(index++);
workerObj.addConfig(finalConfig);
workerObj.addTests(tests);
workerObj.setTestRoot(testRoot);
workerObj.addOptions(options);
testWorkerArray.push(workerObj);
});
workers.push(...testWorkerArray);
});
return workers;
};

const indexOfSmallestElement = (groups) => {
Expand Down Expand Up @@ -115,6 +141,28 @@ const convertToMochaTests = (testGroup) => {
return group;
};

const getOverridenConfig = (workerName, workerConfig, config) => {
// clone config
const overriddenConfig = deepClone(config);

// get configuration
const browserConfig = workerConfig.browser;

for (const key in browserConfig) {
overriddenConfig.helpers = replaceValueDeep(overriddenConfig.helpers, key, browserConfig[key]);
}

// override tests configuration
if (overriddenConfig.tests) {
overriddenConfig.tests = workerConfig.tests;
}

if (overriddenConfig.gherkin && workerConfig.gherkin && workerConfig.gherkin.features) {
overriddenConfig.gherkin.features = workerConfig.gherkin.features;
}
return overriddenConfig;
};

class WorkerObject {
/**
* @param {Number} workerIndex - Unique ID for worker
Expand Down Expand Up @@ -183,7 +231,7 @@ class Workers extends EventEmitter {

_initWorkers(numberOfWorkers, config) {
this.splitTestsByGroups(numberOfWorkers, config);
this.workers = createWorkerObjects(this.testGroups, this.codecept.config, config.testConfig, config.options);
this.workers = createWorkerObjects(this.testGroups, this.codecept.config, config.testConfig, config.options, config.selectedRuns);
this.numberOfWorkers = this.workers.length;
}

Expand Down
30 changes: 30 additions & 0 deletions test/unit/worker_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,34 @@ describe('Workers', () => {
done();
});
});

it('should run worker with multiple config', (done) => {
const workerConfig = {
by: 'test',
testConfig: './test/data/sandbox/codecept.multiple.js',
options: {},
selectedRuns: ['mobile'],
};

const workers = new Workers(2, workerConfig);

for (const worker of workers.getWorkers()) {
worker.addConfig({
helpers: {
FileSystem: {},
Workers: {
require: './custom_worker_helper',
},
},
});
}

workers.run();

workers.on(event.all.result, (status) => {
expect(workers.getWorkers().length).equal(8);
expect(status).equal(true);
done();
});
});
});