UNPKG

siesta-lite

Version:

Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers

1,277 lines (1,020 loc) 87.1 kB
/* Siesta 5.6.1 Copyright(c) 2009-2022 Bryntum AB https://bryntum.com/contact https://bryntum.com/products/siesta/license */ /** @class Siesta.Project `Siesta.Project` is an abstract base project class in Siesta hierarchy. This class provides no UI, you should use one of it subclasses, for example {@link Siesta.Project.Browser} or {@link Siesta.Project.Browser.ExtJS} This file is a reference only, for a getting start guide and manual, please refer to <a href="#!/guide/getting_started_browser">Siesta getting started in browser environment</a> guide. Synopsys ======== var project = new Siesta.Project.Browser.ExtJS(); project.configure({ title : 'Awesome Test Suite', transparentEx : true, autoCheckGlobals : true, expectedGlobals : [ 'Ext', 'Sch' ], preload : [ "http://cdn.sencha.io/ext-4.0.2a/ext-all-debug.js", "../awesome-project-all.js", { text : "console.log('preload completed')" } ] }) project.plan( // simple string - url relative to project file 'sanity.t.js', // test file descriptor with own configuration options { url : 'basic.t.js', // replace `preload` option of project preload : [ "http://cdn.sencha.io/ext-4.0.6/ext-all-debug.js", "../awesome-project-all.js" ] }, // groups ("folders") of test files (possibly with own options) { group : 'Sanity', autoCheckGlobals : false, items : [ 'data/crud.t.js', ... ] }, ... ) */ Class('Siesta.Project', { does : [ JooseX.Observable, Siesta.Util.Role.CanGetType, Siesta.Util.Role.CanDetectES6 ], has : { /** * @cfg {String} title The title of the test suite. Can contain HTML. When provided in the test file descriptor - will change the name of test in the project UI. */ title : null, /** * @cfg {String} desc The description of the test. Can contain HTML. When provided, will be shown as the tooltip in the tests grid. */ desc : null, /** * @cfg {Class} testClass The test class which will be used for creating test instances, defaults to {@link Siesta.Test}. * You can subclass {@link Siesta.Test} and provide a new class. * * This option can be also specified in the test file descriptor. */ testClass : Siesta.Test, contentManagerClass : Siesta.Content.Manager, // fields of test descriptor: // - id - either `url` or wbs + group - computed // - url // - isMissing - true if test file is missing // - testCode - a test code source (can be provided by user) // - testConfig - config object provided to the StartTest // - index - (in the group) computed // - scopeProvider // - scopeProviderConfig // - preload // - alsoPreload // - parent - parent descriptor (or project for top-most ones) - computed // - preset - computed by project - instance of Siesta.Content.Preset // - forceDOMVisible - true to show the <iframe> on top of all others when running this test // (required for IE when using "document.getElementFromPoint()") // OR - object // - group - group name // - items - array of test descriptors // - expanded - initial state of the group (true by default) descriptors : Joose.I.Array, descriptorsById : Joose.I.Object, launchCounter : 0, launches : Joose.I.Object, scopesByURL : Joose.I.Object, testsByURL : Joose.I.Object, /** * @cfg {Boolean} transparentEx When set to `true` project will not try to catch any exception, thrown from the test code. * This is very useful for debugging - you can for example use the "break on error" option in Firebug. * But, using this option may naturally lead to unhandled exceptions, which may leave the project in incosistent state - * refresh the browser page in such case. * * Defaults to `false` - project will do its best to detect any exception thrown from the test code. * * This option can be also specified in the test file descriptor. */ transparentEx : false, scopeProviderConfig : null, scopeProvider : null, /** * @cfg {String} runCore Either `parallel` or `sequential`. Indicates how the individual tests should be run - several at once or one-by-one. * Default value is "parallel". You do not need to change this option usually. */ runCore : 'parallel', /** * @cfg {Number} maxThreads The maximum number of tests running at the same time. Only applicable for `parallel` run-core. */ maxThreads : 4, /** * @cfg {Boolean} autoCheckGlobals When set to `true`, project will automatically issue an {@link Siesta.Test#verifyGlobals} assertion at the end of each test, * so you won't have to manually specify it each time. The assertion will be triggered only if test completed successfully. Default value is `false`. * See also {@link #expectedGlobals} configuration option and {@link Siesta.Test#expectGlobals} method. * * This option will be always disabled in Opera, since every DOM element with `id` is being added as a global symbol in it. * * This option can be also specified in the test file descriptor. */ autoCheckGlobals : false, disableGlobalsCheck : false, /** * @cfg {Array} expectedGlobals An array of strings or regular expressions which are likely to present in the scope of each test. There is no need to provide the name * of built-in globals - project will automatically scan them from the empty context. Only provide the names of global properties which will be created * by your preload code. * * For example * project.configure({ title : 'Ext Scheduler Test Suite', autoCheckGlobals : true, expectedGlobals : [ 'Ext', 'MyProject', /jQuery\d+/, // Can use RegExp too! ], ... }) * This option can be also specified in the test file descriptor. */ expectedGlobals : Joose.I.Array, // will be populated by `populateCleanScopeGlobals` cleanScopeGlobals : Joose.I.Array, /** * @cfg {Array} preload * * The array which contains the *preload descriptors* describing which files/code should be preloaded into the scope of each test. * * Preload descriptor can be: * * - a string, containing an url to load (cross-domain urls are ok, if url ends with ".css" it will be loaded as CSS) * - an object `{ type : 'css/js', url : '...' }` allowing to specify the CSS files with different extension * - an object `{ type : 'css/js', content : '...' }` allowing to specify the inline content for script / style. The content should only be the tag content - not the tag itself, it'll be created by Siesta. * - an object `{ text : '...' }` which is a shortcut for `{ type : 'js', content : '...' }` * * `preload` array can contain other nested arrays which will be flattened recursively. Any "empty" values * (like `null`, empty string, false etc) will be ignored. * * For example: * project.configure({ title : 'Ext Scheduler Test Suite', preload : [ 'http://cdn.sencha.io/ext-4.0.2a/resources/css/ext-all.css', 'http://cdn.sencha.io/ext-4.0.2a/ext-all-debug.js', { text : 'MySpecialGlobalFunc = function () { if (typeof console != "undefined") ... }' }, // simple conditional preload someCondition ? [ 'http://mydomain.com/file.css', 'http://mydomain.com/file.js' ] : null ], ... }) * This option can be also specified in the test file descriptor. **Note**, that if test descriptor has non-empty * {@link Siesta.Project.Browser#pageUrl pageUrl} option, then *it will not inherit* the `preload` option * from parent descriptors or project, **unless** it has the `preload` config set to string `inherit`. * If both `pageUrl` and `preload` are set on the project level, `preload` value still will be inherited. For example: * project.configure({ pageUrl : 'general-page.html', preload : [ 'my-file.js' ], ... }) project.plan( // this test will inherit both `pageUrl` and `preload` 'test1.js', { // no preloads inherited pageUrl : 'host-page.html', url : 'test2.js' }, { // inherit `preload` value from the upper level - [ 'my-file.js' ] pageUrl : 'host-page.html', preload : 'inherit', url : 'test3.js' }, { group : 'Some group', pageUrl : 'host-page2.html', preload : 'inherit', items : [ { // inherit `pageUrl` value from the group // inherit `preload` value from the upper level - [ 'my-file.js' ] url : 'test3.js' } ] } ) * When loading ES6 modules, one need to indicate this using the `isEcmaModule` property of the preload descriptor. * In this case, the module `<script>` tag will be created with the `type` attribute set to `module`, instead of `text/javascript`. * project.configure({ preload : [ { type : 'js', url : 'some_file.js', isEcmaModule : true }, { type : 'js', content : 'import {something} from "another/module.js"', isEcmaModule : true } ], ... }) * * */ preload : Joose.I.Array, /** * @cfg {Array} alsoPreload The array with preload descriptors describing which files/code should be preloaded **additionally**. * * This option can be also specified in the test file descriptor. */ /** * @cfg {Object} listeners The object which keys corresponds to event names and values - to event handlers. If provided, the special key "scope" will be treated as the * scope for all event handlers, otherwise the project itself will be used as scope. * * Note, that the events from individual {@link Siesta.Test test cases} instances will bubble up to the project - you can listen to all of them in one place: * project.configure({ title : 'Awesome Test Suite', preload : [ 'http://cdn.sencha.io/ext-4.1.0-gpl/resources/css/ext-all.css', 'http://cdn.sencha.io/ext-4.1.0-gpl/ext-all-debug.js', 'preload.js' ], listeners : { testsuitestart : function (event, project) { log('Test suite is starting: ' + project.title) }, testsuiteend : function (event, project) { log('Test suite is finishing: ' + project.title) }, teststart : function (event, test) { log('Test case is starting: ' + test.url) }, testupdate : function (event, test, result) { log('Test case [' + test.url + '] has been updated: ' + result.description + (result.annotation ? ', ' + result.annotation : '')) }, testfailedwithexception : function (event, test) { log('Test case [' + test.url + '] has failed with exception: ' + test.failedException) }, testfinalize : function (event, test) { log('Test case [' + test.url + '] has completed') } } }) */ /** * @cfg {Boolean} cachePreload When set to `true`, project will cache the content of the preload files and provide it for each test, instead of loading it * from network each time. This option may give a slight speedup in tests execution (especially when running the suite from the remote server), but see the * caveats below. Default value is `false`. * * Caveats: this option doesn't work very well for CSS (due to broken relative urls for images). Also its not "debugging-friendly" - as you will not be able * to setup breakpoints for cached code. */ cachePreload : false, mainPreset : null, emptyPreset : null, /** * @cfg {Number} keepNLastResults * * Indicates the number of the test results which still should be kept, for user examination. * Results are cleared when their total number exceed this value, based on FIFO order. */ keepNLastResults : 2, lastResultsURLs : Joose.I.Array, lastResultsByURL : Joose.I.Object, /** * @cfg {Boolean} breakOnFail When set to `true`, the project will not start launching any further tests after * detecting a failed assertion. When running in automation mode, test suite will be finalized immediately, * ignoring the --rerun-failed option. * * Default value is `false`. */ breakOnFail : false, /** * @cfg {Boolean} breakTestOnFail When set to `true`, the whole test file will be finalized after the 1st * failed assertion is generated in any of its sub-tests (`it/describe` sections). The test finalization * is performed by throwing an exception, if {@link #transparentEx} is not enabled you will see it * in the console/debugger. * * To break the currently running sub-test only (`it/describe` section) see {@link #breakSubTestOnFail}. * * This option can be also specified in the test file descriptor. * * Default value is `false`. */ breakTestOnFail : false, /** * @cfg {Boolean} breakSubTestOnFail When set to `true`, the currently running sub test (`it/describe` section) * will be finalized after the 1st failed assertion is generated. The test finalization * is performed by throwing an exception, if {@link #transparentEx} is not enabled you will see it * in the console/debugger. * * To break the whole currently test file see {@link #breakTestOnFail}. * * This option can be also specified in the test file descriptor. * * Default value is `false`. */ breakSubTestOnFail : false, /** * @cfg {Boolean} overrideSetTimeout When set to `true`, the tests will override the native "setTimeout" from the context of each test * for asynchronous code tracking. If setting it to `false`, you will need to use `beginAsync/endAsync` calls to indicate that test is still running. * * Note, that this option may not work reliably, when used for several sub tests launched simultaneously (for example * for several sibling {@link Siesta.Test#todo} sections. * * This option can be also specified in the test file descriptor. Defaults to `false`. */ overrideSetTimeout : false, /** * @cfg {Boolean} needDone When set to `true`, the tests will must indicate that that they have reached the correct * exit point with `t.done()` call, after which, adding any assertions is not allowed. * Using this option will ensure that test did not exit prematurely with some exception silently caught. * * This option can be also specified in the test file descriptor. */ needDone : false, // the default timeout for tests will be increased when launching more than this number of files increaseTimeoutThreshold : 8, // the start and end dates for the most recent `launch` method startDate : null, endDate : null, /** * @cfg {Number} waitForTimeout Default timeout for `waitFor` (in milliseconds). Default value is 10000. * * This option can be also specified in the test file descriptor. */ waitForTimeout : 10000, /** * @cfg {Number} defaultTimeout Default timeout for `beginAsync` operation (in milliseconds). Default value is 15000. * * This option can be also specified in the test file descriptor. */ defaultTimeout : 15000, /** * @cfg {Number} subTestTimeout Default timeout for sub tests. Default value is twice bigger than {@link #defaultTimeout}. * * This option can be also specified in the test file descriptor. */ subTestTimeout : null, /** * @cfg {Number} isReadyTimeout Default timeout for test start (in milliseconds). Default value is 15000. See {@link Siesta.Test#isReady} for details. * * This option can be also specified in the test file descriptor. */ isReadyTimeout : 10000, /** * @cfg {Number} pauseBetweenTests Default timeout between tests (in milliseconds). Increase this settings for big test suites, to give browser time for memory cleanup. */ pauseBetweenTests : 10, /** * @cfg {Boolean} failOnExclusiveSpecsWhenAutomated When this option is enabled and Siesta is running in automation mode * (using WebDriver or Puppeteer launcher) any exclusive BDD specs found (like {@link Siesta.Test#iit t.iit} or {@link Siesta.Test#ddescribe t.ddescribe} * will cause a failing assertion. The idea behind this setting is that such "exclusive" specs should only be used during debugging * and are often mistakenly committed in the codebase, leaving other specs not executed. * * This option can be also specified in the test file descriptor. */ failOnExclusiveSpecsWhenAutomated : false, /** * @cfg {Date/String} snooze * * Either a `Date` instance or a string, recognized by the [Date constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse). * * If test is running prior the specified date, the whole test will be made a "todo". See the {@link Siesta.Test#snooze} method. * * Example: * project.plan( { group : 'Some group', snooze : '2016-10-11', items : [ ... ] } ) * * This option can be also specified in the test file descriptor. */ snooze : null, /** * @cfg {String} referenceUrl * * The url, containing additional information about the test. This option is inherited from the group configs, * as other options. In the Siesta user interface, `CTRL+click` on the * test row will open a new browser window, pointing to this url. Can be used to link the test with some external * resource like ticket, screenshot, etc. project.plan( { url : 'my_test.t.js', referenceUrl : 'http://jira.com/jira_issue' } ) * This option can be also specified in the test file descriptor. */ referenceUrl : null, /** * @cfg {Boolean} suppressPassedWaitForAssertion * * When enabled, the passed "waitFor" assertions won't be included in the tests. * * This option can be also specified in the test file descriptor. */ suppressPassedWaitForAssertion : false, /** * @cfg {Boolean} isEcmaModule * * This option can be specified in the test file descriptor and/or as the global project config. In the latter case it will affect all tests. * * When enabled, the test script file (the one containing the `StartTest()` function) will be loaded using * `<script type="module">` instead of `<script type="text/javascript">` * * See also a note in the {@link Siesta.Project#preload preload} config. */ isEcmaModule : null, setupDone : false, sourceLineForAllAssertions : false, currentLaunchId : null, isAutomated : false, autoLaunchTests : true, configSynonyms : function () { return this.processConfigSynonyms(this.buildConfigSynonyms()) }, uniqueCounter : 0, valueToHashIndicies : Joose.I.Object, // lazy attribute, should be accessed with "getSandboxHashStructure" method sandboxHashStructure : { lazy : 'this.buildSandboxHashStructure' }, /** * @cfg {Boolean} sandbox * * This option controls whether the individual tests should be run in isolation from each other. By default it is enabled, * and every test file will be run inside of the newly created iframe (or in the separate Node.js process), so that it can not interfere with * any other test. Such setup gives you predictable starting state for every test, removes the need for any kind of * cleanup at the end of the test and is more robust in general. * * However, the setup of the new sandbox creates some overhead. If you are sure that your tests * do not modify any global state (like global variable that can affect the other test) you may want to run * all of them in the same context, saving the setup time. In this case, you may want to disable this option. * * Siesta collects all tests with this option disabled and split them into chunks. Every chunk will have exactly * the same values for the configs that influence the initial setup of the page: {@link #preload}, {@link #alsoPreload}, * {@link #pageUrl}, {@link Siesta.Test.ExtJS#requires} and some others. The tests inside of every * chunk will be run sequentially, in the same sandbox. * * **Important**: The 1st test in every chunk will be run normally. Starting from the 2nd one, tests * will skip the {@link Siesta.Test#isReady} check and {@link Siesta.Test#setup} methods. This is because all the * setup is supposed to be already done by the 1st test. This behavior may change (or made configurable) in the future. * * This option can be specified in the test file descriptor. * * See also {@link #sandboxBoundaryByGroup}, {@link #sandboxCleanup} */ sandbox : true, /** * @cfg {Boolean} sandboxBoundaryByGroup * * Only applicable for tests with the {@link #sandbox} option *disabled*. * * when this option is enabled, the tests to be run in the same context will be guaranteed to reside in the same group. * If a new test group starts (even with the same "preload" config) - a fresh context for that group will be created * by Siesta. * * For example, in the following setup, both "Group 1" and "Group 2" have sandboxing disabled and the * same "preload" config. If `sandboxBoundaryByGroup` will be disabled all 4 individual tests will be run * in the same context. If `sandboxBoundaryByGroup` will be enabled, separate fresh context will be created * for the tests from each group. * project.configure({ preload : [ ... ] }); project.plan( { group : 'Group 1', sandbox : false, items : [ '010-basics/010_sanity.t.js', '010-basics/020_jshint.t.js' ] }, { group : 'Group 2', sandbox : false, items : [ '020-basics/010_sanity.t.js', '020-basics/030_bdd.t.js' ] }, ... ) * */ sandboxBoundaryByGroup : true, /** * @cfg {Boolean} sandboxCleanup * * Only applicable for tests with the {@link #sandbox} option *disabled*. When enabled, test that runs * in shared sandbox (the sandbox in which another test just has been run) will perform a cleanup. * * By default it will remove any "unexpected" globals (see {@link #expectedGlobals}) and clear the DOM. * * If you will disable this option, every new test in the "groups" will start from the state previous test * has finished the execution. This will allow you split one big test scenario into several files * * This option can be specified in the test file descriptor. */ sandboxCleanup : true, /** * @cfg {Boolean} debuggerOnFail When set to `true`, the project will issue a `debugger` statement after detecting a failed assertion, allowing you * to inspect the internal state of the test in the browser's debugger. Default value is `false`. */ debuggerOnFail : false, /** * @cfg {Boolean} debuggerOnStart When set to `true`, the project will issue a `debugger` statement before launching any test. Default value is `false`. */ debuggerOnStart : false, /** * @cfg {RegExp/String} ignoreException If this option provided, Siesta will ignore any exceptions, stringified value of which matches this option. * If this option is provided as string, it is passed to the `RegExp` constructor. * * This option is supposed to be used only for some "strange" exceptions, originated from the browser itself. For the exceptions * from the test code behavior of this option is undefined. * * Can also be specified in the test file descriptor. */ ignoreException : null }, methods : { initialize : function () { var me = this me.on('testupdate', function (event, test, result, parentResult) { me.onTestUpdate(test, result, parentResult); }) me.on('testfailedwithexception', function (event, test, exception, stack) { me.onTestFail(test, exception, stack); }) me.on('teststart', function (event, test) { me.onTestStart(test); }) me.on('testfinalize', function (event, test) { me.onTestEnd(test); }) }, buildConfigSynonyms : function () { return {} }, // creates a reference from every synonym to a full list of synonyms, including the main name itself // { 'main' : [ 'main', 'syn1', 'syn2' ], 'syn1' : [ 'main', 'syn1', 'syn2' ], 'syn2' : [ 'main', 'syn1', 'syn2' ] } processConfigSynonyms : function (synonyms) { var result = {} Joose.O.each(synonyms, function (synonymsList, mainName) { if (synonymsList instanceof Array) synonymsList.unshift(mainName) else synonymsList = [ mainName, synonymsList ] Joose.A.each(synonymsList, function (synonym) { result[ synonym ] = synonymsList }) }) return result }, onTestUpdate : function (test, result, parentResult) { }, onTestFail : function (test, exception, stack) { }, onTestStart : function (test) { }, onTestEnd : function (test) { }, onTestSuiteStart : function (descriptors, contentManager, launchState) { this.startDate = new Date() /** * This event is fired when the test suite starts. Note, that when running the test suite in the browser, this event can be fired several times * (for each group of tests you've launched). * * You can subscribe to it, using regular ExtJS syntax: * * project.on('testsuitestart', function (event, project) {}, scope, { single : true }) * * See also the "/examples/events" * * @event testsuitestart * @member Siesta.Project * @param {JooseX.Observable.Event} event The event instance * @param {Siesta.Project} project The project that just has started */ this.fireEvent('testsuitestart', this, launchState) }, onTestSuiteEnd : function (descriptors, contentManager, launchState) { this.endDate = new Date() /** * This event is fired when the test suite ends. Note, that when running the test suite in the browser, this event can be fired several times * (for each group of tests you've launched). * * @event testsuiteend * @member Siesta.Project * @param {JooseX.Observable.Event} event The event instance * @param {Siesta.Project} project The project that just has ended */ this.fireEvent('testsuiteend', this, launchState) }, onBeforeScopePreload : function (scopeProvider, url) { this.fireEvent('beforescopepreload', scopeProvider, url) }, onAfterScopePreload : function (scopeProvider, url) { this.fireEvent('afterscopepreload', scopeProvider, url) }, onCachingError : function (descriptors, contentManager) { }, /** * This method configures the project instance. It just copies the passed configuration option into project instance. * * @param {Object} config - configuration options (values of attributes for this class) */ configure : function (config) { Joose.O.copy(config, this) var me = this if (config.listeners) Joose.O.each(config.listeners, function (value, name) { if (name == 'scope') return me.on(name, value, config.scope || me) }) }, // backward compat processPreloadArray : function (preload) { var me = this preload = this.flattenArray(preload, true) Joose.A.each(preload, function (obj, index) { // do not process { text : "" } preload descriptors if (Object(obj) === obj) { if (obj.url) obj.url = me.normalizeURL(obj.url) } else preload[ index ] = me.normalizeURL(obj) }) return preload }, populateCleanScopeGlobals : function (scopeProvider, callback) { var scopeProviderClass = eval(scopeProvider) var cleanScope = new scopeProviderClass() var cleanScopeGlobals = this.cleanScopeGlobals // we can also use "create" and not "setup" here // create will only create the iframe (in browsers) and will not try to update its content // the latter crashes IE8 cleanScope.setup(function () { for (var name in cleanScope.scope) cleanScopeGlobals.push(name) callback() // this setTimeout seems to stop the spinning loading indicator in FF // accorting to https://github.com/3rd-Eden/Socket.IO/commit/bad600fb1fb70238f42767c56f01256470fa3c15 // it only works *after* onload (this callback will be called *in* onload) setTimeout(function () { // will remove the iframe (in case of browser project) from DOM and stop loading indicator cleanScope.cleanup() }, 0) }) }, startSingle : function (desc, callback) { var me = this this.__counter__ = this.__counter__ || 0 var startSingle = function () { me.launch([ me.normalizeDescriptor(desc, me, me.__counter__++) ], callback) } me.setupDone ? startSingle() : this.setup(startSingle) }, setup : function (callback) { var me = this this.mainPreset = new Siesta.Content.Preset({ preload : this.processPreloadArray(this.preload) }) this.emptyPreset = new Siesta.Content.Preset() // A system level descriptor used by the recorder me.descriptors.push({ isSystemDescriptor : true, url : '/' }); me.normalizeDescriptors(me.descriptors) this.populateCleanScopeGlobals(this.scopeProvider, callback) }, /** * This method adds *test file descriptors* (test files), to the project. It can be called several times. * * A test file descritor is one of the following: * * - a string, containing a test file url. The url should be unique among all tests. If you need to re-use the same test * file, you can add an arbitrary query string to it: `my_test.t.js?copy=1` * - an object containing the `url` property `{ url : '...', option1 : 'value1', option2 : 'value2' }`. The `url` property should point to the test file. * Other properties can contain values of some configuration options of the project (marked accordingly). In this case, they will **override** the corresponding values, * provided to project or parent descriptor. * - an object `{ group : 'groupName', items : [], expanded : true, option1 : 'value1' }` specifying the folder of test files. The `expanded` property * sets the initial state of the folder - "collapsed/expanded". The `items` property can contain an array of test file descriptors. * Other properties will override the applicable project options **for all child descriptors**. * * If test descriptor is `null` or other "falsy" value it is ignored. * * Groups (folder) may contain nested groups. Number of nesting levels is not limited. * * For example, one may easily have a special group of test files, having its own `preload` configuration (for example for testing on-demand loading). In the same * time some test in that group may have its own preload, overriding others. project.configure({ title : 'Ext Scheduler Test Suite', preload : [ 'http://cdn.sencha.io/ext-4.0.2a/resources/css/ext-all.css', 'http://cdn.sencha.io/ext-4.0.2a/ext-all-debug.js', '../awesome-app-all-debug.js' ], ... }) project.plan( // regular file 'data/crud.t.js', // a group with own "preload" config for its items { group : 'On-demand loading', preload : [ 'http://cdn.sencha.io/ext-4.0.2a/resources/css/ext-all.css', 'http://cdn.sencha.io/ext-4.0.2a/ext-all-debug.js', ], items : [ 'ondemand/sanity.t.js', 'ondemand/special-test.t.js', // a test descriptor with its own "preload" config (have the most priority) { url : 'ondemand/4-0-6-compat.t.js', preload : [ 'http://cdn.sencha.io/ext-4.0.6/resources/css/ext-all.css', 'http://cdn.sencha.io/ext-4.0.6/ext-all-debug.js', ] }, // sub-group { group : 'Sub-group', items : [ ... ] } ] }, ... ) * Additionally, you can provide a test descriptor in the test file itself, adding it as the 1st or 2nd argument for `StartTest` call: * StartTest({ autoCheckGlobals : false, alsoPreload : [ 'some_additional_preload.js' ] }, function (t) { ... }) * * Values from this object takes the highest priority and will override any other configuration. * * Test descriptor may contain special property - `config` which will be applied to the test instance created. Be careful not to overwrite * standard properties and methods! * project.plan( { url : 'ondemand/4-0-6-compat.t.js', config : { myProperty1 : 'value1', myProperty2 : 'value2' } }, ... ) StartTest(function (t) { if (t.myProperty1 == 'value1') { // do this } ... }) * * @param {Array/Mixed} descriptor1 or an array of descriptors * @param {Mixed} descriptor2 * @param {Mixed} descriptorN */ plan : function () { var descriptors = this.flattenArray(arguments) // A system level descriptor used by the recorder this.descriptors.push.apply(this.descriptors, descriptors) }, /** * This method will launch a test suite. * * For backward compatibility, it also calls {@link #plan} with its arguments. */ start : function () { var me = Siesta.my.activeHarness = this me.plan(this.flattenArray(arguments)) this.setup(function () { me.setupDone = true me.fireEvent('setupdone') if (me.autoLaunchTests) me.launch(me.descriptors) }) }, /** * This method will read the content of the provided `url` then will try to parse it as JSON * and pass to the regular {@link #start} method. The file on the `url` should contain * a valid JSON array object with test descriptors. * * You can use this method in conjunction with the `bin/discover` utility, which can * auto-discover the test files and generate a starter file for you. In such setup, it is convenient * to specify the test configs in the test file itself (see {@link #start} method for details). * However, in such setup, you can not use conditional processing of the descriptors set, so * you decide what fits best to your needs. * * @param {String} url */ startFromUrl : function (url) { var contentManager = new this.contentManagerClass({ project : this, presets : [ new Siesta.Content.Preset({ preload : [ url ] }) ] }) var me = this contentManager.cache(function () { var content = contentManager.getContentOf(url) try { var descriptors = JSON.parse(content) } catch (e) { alert("The content of: " + url + " is not a valid JSON") return } if (me.typeOf(descriptors) == 'Array') me.start(descriptors) else { alert("The content of: " + url + " is not an array") } }, function () { alert("Can not load the content of: " + url) }) }, // good to have this as a separate method for testing normalizeDescriptors : function (descArray) { var me = this var descriptors = [] Joose.A.each(descArray, function (desc, index) { if (desc) descriptors.push(me.normalizeDescriptor(desc, me, index)) }) me.descriptors = descriptors }, launch : function (descriptors, callback, errback) { var launchId = this.currentLaunchId = ++this.launchCounter var me = this //console.time('launch') //console.time('launch-till-preload') //console.time('launch-after-preload') // no folders, only leafs var flattenDescriptors = this.flattenDescriptors(descriptors) // the preset for the test scripts files var testScriptsPreset = new Siesta.Content.Preset() var presets = [ testScriptsPreset, this.mainPreset ] var notLaunchedByAutomationId = {} Joose.A.each(flattenDescriptors, function (desc) { if (desc.preset != me.mainPreset && desc.preset != me.emptyPreset) presets.push(desc.preset) if (!desc.testCode) testScriptsPreset.addResource(desc.url) me.deleteTestByURL(desc.url) // only used in automation, where the `desc.automationElementId` is populated notLaunchedByAutomationId[ desc.automationElementId ] = 1 }) // cache either everything (this.cachePreload) or only the test files (to be able to show missing files / show content) var contentManager = new this.contentManagerClass({ project : this, presets : [ testScriptsPreset ].concat(this.cachePreload ? presets : []) }) var launchState = this.launches[ launchId ] = { launchId : launchId, increaseTimeout : this.runCore == 'parallel' && flattenDescriptors.length > this.increaseTimeoutThreshold, descriptors : flattenDescriptors, contentManager : contentManager, needToStop : false, notLaunchedByAutomationId : notLaunchedByAutomationId } //console.time('caching') me.onTestSuiteStart(descriptors, contentManager, launchState) contentManager.cache(function () { //console.timeEnd('caching') Joose.A.each(flattenDescriptors, function (desc) { var url = desc.url if (contentManager.hasContentOf(url)) { // the test descriptor defined in the test file itself, takes the highest precendence var testConfig = desc.testConfig = Siesta.getConfigForTestScript(contentManager.getContentOf(url)) // if testConfig contains the "preload" or "alsoPreload" key - then we need to update the preset of the descriptor if (testConfig && (testConfig.preload || testConfig.alsoPreload)) desc.preset = me.getDescriptorPreset(desc) } else // if test code is provided, then test is considered not missing // allow subclasses to define there own logic when found missing test file if (!desc.testCode) me.markMissingFile(desc) me.normalizeScopeProvider(desc) }) me.fireEvent('testsuitelaunch', descriptors, contentManager, launchState) me.runCoreGeneral(flattenDescriptors, contentManager, launchState, launchState.callback = function () { me.onTestSuiteEnd(descriptors, contentManager, launchState) callback && callback(descriptors) launchState.needToStop = true delete me.launches[ launchId ] }) }, function () {}, true) }, markMissingFile : function (desc) { desc.isMissing = true }, flattenDescriptors : function (descriptors, includeFolders) { var flatten = [] var me = this Joose.A.each(descriptors, function (descriptor) { if (descriptor.group) { if (includeFolders) flatten.push(descriptor) flatten.push.apply(flatten, me.flattenDescriptors(descriptor.items, includeFolders)) } else if (!descriptor.isSystemDescriptor) flatten.push(descriptor) }) return flatten }, forEachDescriptor : function (descriptors, includeFolders, func, scope) { var me = this return Joose.A.each(descriptors, function (descriptor) { if (descriptor.group) { if (includeFolders) if (func.call(scope || me, descriptor) === false) return false return me.forEachDescriptor(descriptor.items, includeFolders, func, scope) } else if (!descriptor.isSystemDescriptor) if (func.call(scope || me, descriptor) === false) return false }) }, lookUpValueInDescriptorTree : function (descriptor, configName, doNotLookAtRoot) { if (this.descriptorHasOwnValueFor(descriptor, configName)) return this.getConfigValueFromDescriptor(descriptor, configName) var parent = descriptor.parent if (parent) { if (parent == this) if (doNotLookAtRoot) return undefined else // using project instance itself as a descriptor - a bit hackish because of the "testConfig" property, // which is being checked first return this.getConfigValueFromDescriptor(this, configName) return this.lookUpValueInDescriptorTree(parent, configName, doNotLookAtRoot) } return undefined }, descriptorHasOwnValueFor : function (descriptor, configName) { // the test descriptor, defined in the test file (as the 1st arg to StartTest) // takes the highest priority var testConfig = descriptor.testConfig // "fast" branch if (testConfig && testConfig.hasOwnProperty(configName) || descriptor.hasOwnProperty(configName)) return true var synonymList = this.configSynonyms[ configName ] var result = false // now checking synonims if (synonymList) Joose.A.each(synonymList, function (synonym) { // already checked above if (synonym != configName) { if (testConfig && testConfig.hasOwnProperty(synonym) || descriptor.hasOwnProperty(synonym)) { result = true return false } } }) return result }, getConfigValueFromDescriptor : function (descriptor, configName, allowInherited) { if (descriptor == this) { var synonymList = this.configSynonyms[ configName ] || [ configName ] var result, foundOwnValue Joose.A.each(synonymList, function (synonym) { if (descriptor.hasOwnProperty(synonym)) { result = descriptor[ synonym ] foundOwnValue = true return false } }) return foundOwnValue ? result : this[ configName ] } else { var testConfig = descriptor.testConfig // "fast" branch if (testConfig && testConfig.hasOwnProperty(configName)) return testConfig[ configName ] if (descriptor.h