runParallelAsyncHarness.js (5349B)
1 (function(root){ 2 'use strict'; 3 // testharness doesn't know about async test queues, 4 // so this wrapper takes care of that 5 6 /* USAGE: 7 runParallelAsyncHarness({ 8 // list of data to test, must be array of objects. 9 // each object must contain a "name" property to describe the test 10 // besides name, the object can contain whatever data you need 11 tests: [ 12 {name: "name of test 1", custom: "data"}, 13 {name: "name of test 2", custom: "data"}, 14 // ... 15 ], 16 17 // number of tests (tests, not test-cases!) to run concurrently 18 testsPerSlice: 100, 19 20 // time in milliseconds a test-run takes 21 duration: 1000, 22 23 // test-cases to run for for the test - there must be at least one 24 // each case creates its separate async_test() instance 25 cases: { 26 // test case named "test1" 27 test1: { 28 // run as a async_test.step() this callback contains your primary assertions 29 start: function(testCaseKey, data, options){}, 30 // run as a async_test.step() this callback contains assertions to be run 31 // when the test ended, immediately before teardown 32 done: function(testCaseKey, data, options){} 33 }, 34 // ... 35 } 36 37 // all callbacks are optional: 38 39 // invoked for individual test before it starts so you can setup the environment 40 // like DOM, CSS, adding event listeners and such 41 setup: function(data, options){}, 42 43 // invoked after a test ended, so you can clean up the environment 44 // like DOM, CSS, removing event listeners and such 45 teardown: function(data, options){}, 46 47 // invoked before a batch of tests ("slice") are run concurrently 48 // tests is an array of test data objects 49 sliceStart: function(options, tests) 50 51 // invoked after a batch of tests ("slice") were run concurrently 52 // tests is an array of test data objects 53 sliceDone: function(options, tests) 54 55 // invoked once all tests are done 56 done: function(options){} 57 }) 58 */ 59 60 function DomContentLoadedPromise() { 61 return new Promise(resolve => { 62 document.addEventListener("DOMContentLoaded", (event) => { 63 resolve(); 64 }); 65 }); 66 } 67 68 root.runParallelAsyncHarness = async function(options) { 69 const ready = DomContentLoadedPromise(); 70 71 if (!options.cases) { 72 throw new Error("Options don't contain test cases!"); 73 } 74 75 var noop = function(){}; 76 77 // names of individual tests 78 var cases = Object.keys(options.cases); 79 80 // run tests in a batch of slices 81 // primarily not to overload weak devices (tablets, phones, …) 82 // with too many tests running simultaneously 83 var iteration = -1; 84 var testPerSlice = options.testsPerSlice || 100; 85 var slices = Math.ceil(options.tests.length / testPerSlice); 86 87 // initialize all async test cases 88 // Note: satisfying testharness.js needs to know all async tests before load-event 89 options.tests.forEach(function(data, index) { 90 data.cases = {}; 91 cases.forEach(function(name) { 92 data.cases[name] = async_test(data.name + " / " + name); 93 }); 94 }); 95 96 function runLoop() { 97 iteration++; 98 if (iteration >= slices) { 99 // no more slice, we're done 100 (options.done || noop)(options); 101 return; 102 } 103 104 // grab a slice of testss and initialize them 105 var offset = iteration * testPerSlice; 106 var tests = options.tests.slice(offset, offset + testPerSlice); 107 tests.forEach(function(data) { 108 (options.setup || noop)(data, options); 109 }); 110 111 // kick off the current slice of tests 112 (options.sliceStart || noop)(options, tests); 113 114 // perform individual "start" test-case 115 tests.forEach(function(data) { 116 cases.forEach(function(name) { 117 data.cases[name].step(function() { 118 (options.cases[name].start || noop)(data.cases[name], data, options); 119 }); 120 }); 121 }); 122 123 // Start sampling. 124 (options.transitionsStarted || noop)(options, tests); 125 126 // conclude slice (possibly abort) 127 var concludeSlice = function() { 128 tests.forEach(function(data) { 129 // perform individual "done" test-case 130 cases.forEach(function(name) { 131 data.cases[name].step(function() { 132 (options.cases[name].done || noop)(data.cases[name], data, options); 133 }); 134 }); 135 // clean up after individual test 136 (options.teardown || noop)(data, options); 137 // tell harness we're done with individual test-cases 138 cases.forEach(function(name) { 139 data.cases[name].done(); 140 }); 141 }); 142 143 // finish the test for current slice of tests 144 (options.sliceDone || noop)(options, tests); 145 146 requestAnimationFrame(runLoop); 147 } 148 149 options.allTransitionsCompleted = concludeSlice; 150 } 151 152 // allow DOMContentLoaded before actually doing something 153 ready.then(runLoop); 154 }; 155 156 })(window);