diff --git a/bin/codecept.js b/bin/codecept.js index 2d1555c78..9d8ddfc3c 100755 --- a/bin/codecept.js +++ b/bin/codecept.js @@ -130,7 +130,7 @@ program.command('run [test]') .option('--child ', 'option for child processes') .action(errorHandler(require('../lib/command/run'))); -program.command('run-workers ') +program.command('run-workers [selectedRuns...]') .description('Executes tests in workers') .option('-c, --config [file]', 'configuration file to be used') .option('-g, --grep ', 'only run tests matching ') diff --git a/docs/parallel.md b/docs/parallel.md index 47ccfedd6..148de0557 100644 --- a/docs/parallel.md +++ b/docs/parallel.md @@ -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. diff --git a/lib/command/run-workers.js b/lib/command/run-workers.js index 5ffcc3722..378361c8c 100644 --- a/lib/command/run-workers.js +++ b/lib/command/run-workers.js @@ -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; @@ -15,6 +15,7 @@ module.exports = async function (workerCount, options) { by, testConfig, options, + selectedRuns, }; const numberOfWorkers = parseInt(workerCount, 10); diff --git a/lib/workers.js b/lib/workers.js index 3d3d1e713..52e785da6 100644 --- a/lib/workers.js +++ b/lib/workers.js @@ -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'); @@ -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) => { @@ -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 @@ -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; } diff --git a/test/unit/worker_test.js b/test/unit/worker_test.js index 792f4dea0..15bbf3726 100644 --- a/test/unit/worker_test.js +++ b/test/unit/worker_test.js @@ -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(); + }); + }); });