diff --git a/lib/config.ts b/lib/config.ts index c04849088..c8c647253 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -1,5 +1,7 @@ import {PluginConfig} from './plugins'; +export type SpecScheduler = () => (specs: Array, capabilities: any) => Array>; + export interface Config { [key: string]: any; @@ -340,9 +342,12 @@ export interface Config { /** * If this is set to be true, specs will be sharded by file (i.e. all * files to be run by this set of capabilities will run in parallel). + * By default each test file will run in separeted browser instance. * Default is false. + * You can also provide custom spec scheduler function + * that can change default behaviour and run many specs in one browser instance. */ - shardTestFiles?: boolean; + shardTestFiles: boolean | SpecScheduler; /** * Maximum number of browser instances that can run in parallel for this diff --git a/lib/taskScheduler.ts b/lib/taskScheduler.ts index 420c51dd0..de9a56aeb 100644 --- a/lib/taskScheduler.ts +++ b/lib/taskScheduler.ts @@ -63,17 +63,19 @@ export class TaskScheduler { }); } - let specLists: Array> = []; - // If we shard, we return an array of one element arrays, each containing - // the spec file. If we don't shard, we return an one element array - // containing an array of all the spec files - if (capabilities.shardTestFiles) { - capabilitiesSpecs.forEach((spec) => { - specLists.push([spec]); - }); - } else { - specLists.push(capabilitiesSpecs); + let shardScheduler = capabilities.shardTestFiles; + if (typeof shardScheduler !== 'function') { + // If we shard, we return an array of one element arrays, each containing + // the spec file. If we don't shard, we return an one element array + // containing an array of all the spec files + shardScheduler = function(specs: Array, capabilities: any): Array> { + if (capabilities.shardTestFiles) { + return specs.map(spec => [spec]); + } + return [specs]; + }; } + const specLists = shardScheduler(capabilitiesSpecs, capabilities); capabilities.count = capabilities.count || 1; diff --git a/spec/unit/taskScheduler_test.js b/spec/unit/taskScheduler_test.js index 8bc19a053..f8e195fa8 100644 --- a/spec/unit/taskScheduler_test.js +++ b/spec/unit/taskScheduler_test.js @@ -258,4 +258,52 @@ describe('the task scheduler', function() { expect(scheduler.numTasksOutstanding()).toEqual(0); }); + it('should use custom shard scheduler when provided', function() { + var toAdd = { + specs: [ + 'spec/unit/data/fakespecA.js', + 'spec/unit/data/fakespecB.js', + 'spec/unit/data/fakespecC.js' + ], + multiCapabilities: [{ + shardTestFiles: shardScheduler, + browserName: 'chrome', + maxInstances: 2, + }] + }; + var config = new ConfigParser().addConfig(toAdd).getConfig(); + var scheduler = new TaskScheduler(config); + + var task1 = scheduler.nextTask(); + expect(task1.capabilities.browserName).toEqual('chrome'); + expect(task1.specs.length).toEqual(2); + + var task2 = scheduler.nextTask(); + expect(task2.capabilities.browserName).toEqual('chrome'); + expect(task2.specs.length).toEqual(1); + + task1.done(); + task2.done(); + expect(scheduler.numTasksOutstanding()).toEqual(0); + }); + + function shardScheduler(specs, capabilities) { + const numberOfShards = capabilities.maxInstances; + if(numberOfShards > 1) { + const bucketSize = Math.ceil(specs.length/numberOfShards); + const shards = []; + let start = 0; + while (start < specs.length) { + let end = start + bucketSize; + if ( end > specs.length) { + end = specs.length; + } + shards.push(specs.slice(start,end)); + start = end; + } + return shards; + } + return [specs]; + } + });