UNPKG

siesta-lite

Version:

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

1,278 lines (975 loc) 94.2 kB
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>The source code</title> <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" /> <script type="text/javascript" src="../resources/prettify/prettify.js"></script> <style type="text/css"> .highlight { display: block; background-color: #ddd; } </style> <script type="text/javascript"> function highlight() { document.getElementById(location.hash.replace(/#/, "")).className = "highlight"; } </script> </head> <body onload="prettyPrint(); highlight();"> <pre class="prettyprint lang-js">/* Siesta 5.6.1 Copyright(c) 2009-2022 Bryntum AB https://bryntum.com/contact https://bryntum.com/products/siesta/license */ <span id='Siesta-Test'>/** </span>@class Siesta.Test @mixin Siesta.Test.More @mixin Siesta.Test.Date @mixin Siesta.Test.Function @mixin Siesta.Test.BDD @mixin Siesta.Util.Role.CanCompareObjects `Siesta.Test` is a base testing class in Siesta hierarchy. It&#39;s not supposed to be created manually, instead the project will create it for you. This file is a reference only, for a getting start guide and manual please refer to the &lt;a href=&quot;#!/guide/getting_started_browser&quot;&gt;Siesta getting started in browser environment&lt;/a&gt; guide. SYNOPSIS ======== StartTest(function(t) { t.diag(&quot;Sanity&quot;) t.ok($, &#39;jQuery is here&#39;) t.ok(Your.Project, &#39;My project is here&#39;) t.ok(Your.Project.Util, &#39;.. indeed&#39;) setTimeout(function () { t.ok(true, &quot;True is ok&quot;) }, 500) }) */ Class(&#39;Siesta.Test&#39;, { does : [ Siesta.Util.Role.CanFormatStrings, Siesta.Util.Role.CanGetType, Siesta.Util.Role.CanCompareObjects, Siesta.Util.Role.CanEscapeRegExp, Siesta.Test.More, Siesta.Test.Date, Siesta.Test.Function, Siesta.Test.BDD, JooseX.Observable, // quick &quot;id&quot; attribute, perhaps should be changed later Siesta.Util.Role.HasUniqueGeneratedId ], has : { name : null, uniqueId : function () { var holder = Siesta.Test holder.__UNIQUE_ID_GEN__ = holder.__UNIQUE_ID_GEN__ || 0 return ++holder.__UNIQUE_ID_GEN__ }, <span id='Siesta-Test-property-url'> /** </span> * @property url The url of this test, as given to the {@link Siesta.Project#start start} method. All subtests of some top-level test shares the same url. */ url : { required : true }, urlExtractRegex : { is : &#39;rwc&#39;, lazy : function () { return new RegExp(this.url.replace(/([.*+?^${}()|[\]\/\\])/g, &quot;\\$1&quot;) + &#39;:(\\d+)&#39;) } }, referenceUrl : null, assertPlanned : null, assertCount : 0, // whether this test contains only &quot;todo&quot; assertions isTodo : false, results : { lazy : function () { return new Siesta.Result.SubTest({ description : this.name || &#39;Root&#39;, test : this }) } }, run : null, startTestAnchor : null, exceptionCatcher : null, testErrorClass : null, // same number for the whole subtests tree generation : function () { return Math.random() }, launchId : null, parent : null, project : null, // backward compat - alias for `project` harness : null, <span id='Siesta-Test-cfg-isReadyTimeout'> /** </span> * @cfg {Number} isReadyTimeout * * Timeout in milliseconds to wait for test start. Default value is 10000. See also {@link #isReady} */ isReadyTimeout : 10000, // indicates that a test has thrown an exception (not related to failed assertions) failed : false, failedException : null, // stringified exception failedExceptionType : null, // type of exception // start and end date are stored as numbers (new Date() - 0) // this is to allow sharing date instances between different contexts startDate : null, endDate : null, lastActivityDate : null, contentManager : null, // the scope provider for the context of the test page scopeProvider : null, // the context of the test page global : null, reusingSandbox : false, sandboxCleanup : true, sharedSandboxState : null, // the scope provider for the context of the test script // usually the same as the `scopeProvider`, but may be different in case of using `enablePageRedirect` option scriptScopeProvider : null, transparentEx : false, needDone : false, isDone : false, defaultTimeout : 15000, // a default timeout for sub tests subTestTimeout : null, // a timeout of this particular test timeout : null, timeoutsCount : function () { return { counter : 1 } }, timeoutIds : Joose.I.Object, idsToIndex : Joose.I.Object, waitTitles : Joose.I.Object, // indicates that test function has completed the execution (test may be still running due to async) processed : false, // indicates that test has started finalization process (&quot;tearDown&quot; method). At this point, test is considered // finished, but the failing assertion (if &quot;tearDown&quot; fails) may still be added finalizationStarted : false, callback : null, // Nbr of exceptions detected while running the test nbrExceptions : 0, testEndReported : false, // only used for testing itself, otherwise should be always `true` needToCleanup : true, overrideSetTimeout : false, overrideForSetTimeout : null, overrideForClearTimeout : null, originalSetTimeout : null, originalClearTimeout : null, sourceLineForAllAssertions : false, $passCount : null, $failCount : null, actionableMethods : { lazy : &#39;buildActionableMethods&#39; }, jUnitClass : null, groups : null, automationElementId : null, // enableCodeCoverage : false, snoozeUntil : null, breakTestOnFail : false, breakSubTestOnFail : false, // user-provided config values config : null }, methods : { initialize : function () { // backward compat - both `project` and `harness` attributes should be supported this.harness = this.project = this.project || this.harness // suppress bubblings of some events (JooseX.Observable does not provide better mechanism for that, yet) this.on(&#39;teststart&#39;, function (event) { if (this.parent) event.stopPropagation() }) this.on(&#39;testfinalize&#39;, function (event) { if (this.parent) event.stopPropagation() }) this.on(&#39;teststop&#39;, function (event) { if (this.parent) event.stopPropagation() }) this.on(&#39;beforetestfinalize&#39;, function (event) { if (this.parent) event.stopPropagation() }) this.on(&#39;beforetestfinalizeearly&#39;, function (event) { if (this.parent) event.stopPropagation() }) this.subTestTimeout = this.subTestTimeout || 2 * this.defaultTimeout if (this.snoozeUntil) { this.snoozeUntil = new Date(this.snoozeUntil) if (isNaN(this.snoozeUntil - 0 )) this.snoozeUntil = null } if (this.snoozeUntil &amp;&amp; new Date() &lt; this.snoozeUntil) this.isTodo = true // Potentially may overwrite default properties and break test instance, should be used with care if (this.config) Joose.O.extend(this, this.config) }, <span id='Siesta-Test-method-isReady'> /** </span> * This method allows you to delay the start of the test, for example for performing some asynchronous setup code (like login into an application). * Note, that you may want to use the {@link #setup} method instead, as it is a bit simpler to implement. * * It is supposed to be overridden in a subclass of the Siesta.Test class and should return an object with two properties: &quot;ready&quot; and &quot;reason&quot; * (&quot;reason&quot; is only meaningful for the case where &quot;ready : false&quot;). The Test instance will poll this method and will only launch * the test after this method returns &quot;ready : true&quot;. If waiting for this condition takes longer than {@link #isReadyTimeout}, the test * will be launched anyway, but a failing assertion will be added to it. * * **Important** This method should always check the value returned by a `this.SUPER` call. * * A typical example of using this method can be seen below: * Class(&#39;My.Test.Class&#39;, { isa : Siesta.Test.Browser, has : { isCustomSetupDone : false }, override : { isReady : function () { var result = this.SUPERARG(arguments); if (!result.ready) return result; if (!this.isCustomSetupDone) return { ready : false, reason : &quot;Waiting for `isCustomSetupDone` took too long - something wrong?&quot; } return { ready : true } }, start : function () { var me = this; Ext.Ajax.request({ url : &#39;do_login.php&#39;, params : { ... }, success : function () { me.isCustomSetupDone = true } }) this.SUPERARG(arguments) } }, .... }) * * @return {Object} Object with properties `{ ready : true/false, reason : &#39;description&#39; }` */ isReady: function() { var R = Siesta.Resource(&#39;Siesta.Test&#39;); // this should allow us to wait until the presense of &quot;run&quot; function // it will become available after call to StartTest method // which some users may call asynchronously, after some delay // see https://www.assembla.com/spaces/bryntum/tickets/379 // in this case test can not be configured using object as 1st argument for StartTest this.run = this.run || this.getStartTestAnchor().args &amp;&amp; this.getStartTestAnchor().args[ 0 ] return { ready : this.typeOf(this.run) == &#39;Function&#39; || this.typeOf(this.run) == &#39;AsyncFunction&#39;, reason : R.get(&#39;noCodeProvidedToTest&#39;) } }, // indicates that the tests are identical or from the same tree (one is parent for another) isFromTheSameGeneration : function (test2) { return this.generation == test2.generation }, toString : function() { return this.url }, // deprecated plan : function (value) { if (this.assertPlanned != null) throw new Error(&quot;Test plan can&#39;t be changed&quot;) this.assertPlanned = value }, addResult : function (result) { var isAssertion = result instanceof Siesta.Result.Assertion if (isAssertion) result.isTodo = this.isTodo // only allow to add diagnostic results and todo results after the end of test // and only if &quot;needDone&quot; is enabled if (isAssertion &amp;&amp; (this.isDone || this.isFinished()) &amp;&amp; !result.isTodo) { if (!this.testEndReported) { this.testEndReported = true var R = Siesta.Resource(&#39;Siesta.Test&#39;); this.fail(R.get(&#39;addingAssertionsAfterDone&#39;)) } } if (isAssertion &amp;&amp; !result.index) { result.index = ++this.assertCount } this.getResults().push(result) // clear the cache this.$passCount = this.$failCount = null <span id='Siesta-Test-event-testupdate'> /** </span> * This event is fired when an individual test case receives a new result (assertion or diagnostic message). * * This event bubbles up to the {@link Siesta.Project project}, so you can observe it on the project as well. * * @event testupdate * @member Siesta.Test * @param {JooseX.Observable.Event} event The event instance * @param {Siesta.Test} test The test instance that just has started * @param {Siesta.Result} result The new result. Instance of Siesta.Result.Assertion or Siesta.Result.Diagnostic classes */ this.fireEvent(&#39;testupdate&#39;, this, result, this.getResults()) this.lastActivityDate = new Date(); return result }, <span id='Siesta-Test-method-diag'> /** </span> * This method output the diagnostic message. * @param {String} desc The text of diagnostic message */ diag : function (desc, callback) { this.addResult(new Siesta.Result.Diagnostic({ // protection from user passing some arbitrary JSON object instead of string // (which can be circular and then test report will fail with &quot;Converting circular structure to JSON&quot; description : String(desc || &#39;&#39;) })) callback &amp;&amp; callback(); }, <span id='Siesta-Test-method-pass'> /** </span> * This method add the passed assertion to this test. * * @param {String} desc The description of the assertion * @param {String/Object} [annotation] The string with additional description how exactly this assertion passes. Will be shown with monospace font. * Can be also an object with the following properties: * @param {String} annotation.annotation The actual annotation text * @param {String} annotation.descTpl The template for the default description text. Will be used if user did not provide any description for * assertion. Template can contain variables in braces. The values for variables are taken as properties of `annotation` parameters with the same name: * this.pass(desc, { descTpl : &#39;{value1} sounds like {value2}&#39;, value1 : &#39;1&#39;, value2 : &#39;one }) * */ pass : function (desc, annotation, result) { if (annotation &amp;&amp; this.typeOf(annotation) != &#39;String&#39;) { // create a default assertion description if (!desc &amp;&amp; annotation.descTpl) desc = this.formatString(annotation.descTpl, annotation) // actual annotation annotation = annotation.annotation } if (result) { result.passed = true result.description = String(desc || &#39;&#39;) result.annotation = annotation } this.addResult(result || new Siesta.Result.Assertion({ passed : true, // protection from user passing some arbitrary JSON object instead of string // (which can be circular and then test report will fail with &quot;Converting circular structure to JSON&quot; annotation : String(annotation || &#39;&#39;), description : String(desc || &#39;&#39;), sourceLine : (result &amp;&amp; result.sourceLine) || (annotation &amp;&amp; annotation.sourceLine) || this.sourceLineForAllAssertions &amp;&amp; this.getSourceLine() || null })) }, <span id='Siesta-Test-method-fail'> /** </span> * This method add the failed assertion to this test. * * @param {String} desc The description of the assertion * @param {String/Object} annotation The additional description how exactly this assertion fails. Will be shown with monospace font. * * Can be either string or an object with the following properties. In the latter case a string will be constructed from the properties of the object. * * - `assertionName` - the name of assertion, will be shown in the 1st line, along with originating source line (in FF and Chrome only) * - `got` - an arbitrary JavaScript object, when provided will be shown on the next line * - `need` - an arbitrary JavaScript object, when provided will be shown on the next line * - `gotDesc` - a prompt for &quot;got&quot;, default value is &quot;Got&quot;, but can be for example: &quot;We have&quot; * - `needDesc` - a prompt for &quot;need&quot;, default value is &quot;Need&quot;, but can be for example: &quot;We need&quot; * - `annotation` - A text to append on the last line, can contain some additional explanations * * The &quot;got&quot; and &quot;need&quot; values will be stringified to the &quot;not quite JSON&quot; notation. Notably the points of circular references will be * marked with `[Circular]` marks and the values at 4th (and following) level of depth will be marked with triple points: `[ [ [ ... ] ] ]` */ fail : function (desc, annotation, result) { var sourceLine = (result &amp;&amp; result.sourceLine) || (annotation &amp;&amp; annotation.sourceLine) || this.getSourceLine() var assertionName = &#39;&#39;; if (annotation &amp;&amp; this.typeOf(annotation) != &#39;String&#39;) { if (!desc &amp;&amp; annotation.descTpl) desc = this.formatString(annotation.descTpl, annotation) var strings = [] var params = annotation var hasGot = params.hasOwnProperty(&#39;got&#39;) var hasNeed = params.hasOwnProperty(&#39;need&#39;) var gotDesc = params.gotDesc || &#39;Got&#39; var needDesc = params.needDesc || &#39;Need&#39; assertionName = params.assertionName annotation = params.annotation if (!params.ownTextOnly &amp;&amp; (assertionName || sourceLine)) strings.push( &#39;Failed assertion &#39; + (assertionName ? &#39;`&#39; + assertionName + &#39;` &#39; : &#39;&#39;) + this.formatSourceLine(sourceLine) ) if (hasGot &amp;&amp; hasNeed) { var max = Math.max(gotDesc.length, needDesc.length) gotDesc = this.appendSpaces(gotDesc, max - gotDesc.length + 1) needDesc = this.appendSpaces(needDesc, max - needDesc.length + 1) } if (hasGot) strings.push(gotDesc + &#39;: &#39; + Siesta.Util.Serializer.stringify(params.got)) if (hasNeed) strings.push(needDesc + &#39;: &#39; + Siesta.Util.Serializer.stringify(params.need)) if (annotation) strings.push(annotation) annotation = strings.join(&#39;\n&#39;) } if (result) { // Failing a pending waitFor operation result.name = assertionName; result.passed = false; result.annotation = annotation; result.description = desc; } this.addResult(result || new Siesta.Result.Assertion({ name : assertionName, passed : false, sourceLine : sourceLine, // protection from user passing some arbitrary JSON object instead of string // (which can be circular and then test report will fail with &quot;Converting circular structure to JSON&quot; annotation : String(annotation || &#39;&#39;), description : String(desc || &#39;&#39;) })) this.onFailedAssertion() }, onFailedAssertion : function (noNeedToExit) { if (!this.isTodo) { if (this.project.debuggerOnFail) eval(&quot;debugger&quot;) if (this.project.breakOnFail &amp;&amp; !this.__STOPPED__) { this.__STOPPED__ = true this.project.stopCurrentLaunch(this) if (!noNeedToExit) this.exit() } if ((this.breakTestOnFail || this.breakSubTestOnFail) &amp;&amp; !this.__STOPPED__) { this.__STOPPED__ = true if (!noNeedToExit) { if (this.breakTestOnFail) { this.getRootTest().exit() } else { this.exit() } } } } }, <span id='Siesta-Test-method-exit'> /** </span> * This method interrupts the test execution. You can use it if, for example, you already know the status of * test (failed) and further actions involves long waitings etc. * * This method accepts the same arguments as the {@link #fail} method. If at least the one argument is given, * a failed assertion will be added to the test before the exit. * * The interruption is performed by throwing an exception from the test. If you have the * {@link Siesta.Project#transparentEx transparentEx} option enabled you will observe it in the debugger/console. * * See also {@link Siesta.Project#breakTestOnFail breakTestOnFail}, {@link Siesta.Project#breakSubTestOnFail breakSubTestOnFail} * * For example: * t.chain( function (next) { // do something next() }, function (next) { if (someCondition) t.exit(&quot;Failure description&quot;) else next() }, { waitFor : function () { ... } } ) * * @param {String} [desc] The description of the assertion * @param {String/Object} [annotation] The additional description how exactly this assertion fails. Will be shown with monospace font. */ exit : function (desc, annotation) { if (arguments.length &gt; 0) this.fail(desc, annotation) this.finalize(true) throw &#39;__SIESTA_TEST_EXIT_EXCEPTION__&#39; }, getSource : function () { return this.contentManager.getContentOf(this.url) }, getSourceLine : function () { var stack = new Error().stack if (!stack) { try { throw new Error() } catch (e) { stack = e.stack } } if (stack) { var match = stack.match(this.urlExtractRegex()) if (match) return match[ 1 ] } return null }, getStartTestAnchor : function () { return this.startTestAnchor }, getExceptionCatcher : function () { return this.exceptionCatcher }, getTestErrorClass : function () { return this.testErrorClass }, processCallbackFromTest : function (callback, args, scope) { var me = this if (!callback) return true; if (this.transparentEx) { callback.apply(scope || this.global, args || []) } else { var e = this.getExceptionCatcher()(function () { callback.apply(scope || me.global, args || []) }) if (e) { this.failWithException(e) // flow should be interrupted - exception detected return false } } // flow can be continued return true }, getStackTrace : function (e) { if (Object(e) !== e) return null if (!e.stack) return null var stackLines = (e.stack + &#39;&#39;).split(&#39;\n&#39;) var message = e + &#39;&#39; var R = Siesta.Resource(&#39;Siesta.Test&#39;); var result = [] var match for (var i = 0; i &lt; stackLines.length; i++) { var line = stackLines[ i ] if (!line) continue // first line should contain exception message if (!i) { if (line != message) result.push(message) else { result.push(line) continue; } } result.push(line) } if (!result.length) return null return result }, formatSourceLine : function (sourceLine) { var R = Siesta.Resource(&#39;Siesta.Test&#39;); return sourceLine ? (R.get(&#39;atLine&#39;) + &#39; &#39; + sourceLine + &#39; &#39; + R.get(&#39;of&#39;) + &#39; &#39; + this.url) : &#39;&#39; }, appendSpaces : function (str, num) { var spaces = &#39;&#39; while (num--) spaces += &#39; &#39; return str + spaces }, eachAssertion : function (func, scope) { scope = scope || this this.getResults().each(function (result) { if (result instanceof Siesta.Result.Assertion) func.call(scope, result) }) }, eachSubTest : function (func, scope) { scope = scope || this this.getResults().each(function (result) { if (result instanceof Siesta.Result.SubTest) if (func.call(scope, result.test) === false) return false }) }, eachChildTest : function (func, scope) { scope = scope || this this.getResults().eachChild(function (result) { if (result instanceof Siesta.Result.SubTest) if (func.call(scope, result.test) === false) return false }) }, <span id='Siesta-Test-method-ok'> /** </span> * This assertion passes when the supplied `value` evalutes to `true` and fails otherwise. * * @param {Mixed} value The value, indicating wheter assertions passes or fails * @param {String} [desc] The description of the assertion */ ok : function (value, desc) { var R = Siesta.Resource(&#39;Siesta.Test&#39;); if (value) this.pass(desc, { descTpl : R.get(&#39;isTruthy&#39;), value : value }) else this.fail(desc, { assertionName : &#39;ok&#39;, got : value, annotation : R.get(&#39;needTruthy&#39;) }) }, notok : function () { this.notOk.apply(this, arguments) }, <span id='Siesta-Test-method-notOk'> /** </span> * This assertion passes when the supplied `value` evalutes to `false` and fails otherwise. * * It has a synonym - `notok`. * * @param {Mixed} value The value, indicating wheter assertions passes or fails * @param {String} [desc] The description of the assertion */ notOk : function (value, desc) { var R = Siesta.Resource(&#39;Siesta.Test&#39;); if (!value) this.pass(desc, { descTpl : R.get(&#39;isFalsy&#39;), value : value }) else this.fail(desc, { assertionName : &#39;notOk&#39;, got : value, annotation : R.get(&#39;needFalsy&#39;) }) }, <span id='Siesta-Test-method-is'> /** </span> * This assertion passes when the comparison of 1st and 2nd arguments with `==` operator returns true and fails otherwise. * * As a special case, one or both arguments can be *placeholders*, generated with method {@link #any}. * * @param {Mixed} got The value &quot;we have&quot; - will be shown as &quot;Got:&quot; in case of failure * @param {Mixed} expected The value &quot;we expect&quot; - will be shown as &quot;Need:&quot; in case of failure * @param {String} [desc] The description of the assertion */ is : function (got, expected, desc) { var R = Siesta.Resource(&#39;Siesta.Test&#39;); if (expected &amp;&amp; got instanceof this.global.Date) { this.isDateEqual(got, expected, desc); } else if (this.compareObjects(got, expected, false, true)) this.pass(desc, { descTpl : R.get(&#39;isEqualTo&#39;), got : got, expected : expected }) else this.fail(desc, { assertionName : &#39;is&#39;, got : got, need : expected }) }, isnot : function () { this.isNot.apply(this, arguments) }, isnt : function () { this.isNot.apply(this, arguments) }, <span id='Siesta-Test-method-isNot'> /** </span> * This assertion passes when the comparison of 1st and 2nd arguments with `!=` operator returns true and fails otherwise. * It has synonyms - `isnot` and `isnt`. * * As a special case, one or both arguments can be instance of {@link Siesta.Test.BDD.Placeholder} class, generated with method {@link #any}. * * @param {Mixed} got The value &quot;we have&quot; - will be shown as &quot;Got:&quot; in case of failure * @param {Mixed} expected The value &quot;we expect&quot; - will be shown as &quot;Need:&quot; in case of failure * @param {String} [desc] The description of the assertion */ isNot : function (got, expected, desc) { var R = Siesta.Resource(&#39;Siesta.Test&#39;); if (!this.compareObjects(got, expected, false, true)) this.pass(desc, { descTpl : R.get(&#39;isNotEqualTo&#39;), got : got, expected : expected }) else this.fail(desc, { assertionName : &#39;isnt&#39;, got : got, need : expected, needDesc : R.get(&#39;needNot&#39;) }) }, <span id='Siesta-Test-method-exact'> /** </span> * This assertion passes when the comparison of 1st and 2nd arguments with `===` operator returns true and fails otherwise. * * As a special case, one or both arguments can be instance of {@link Siesta.Test.BDD.Placeholder} class, generated with method {@link #any}. * * @param {Mixed} got The value &quot;we have&quot; - will be shown as &quot;Got:&quot; in case of failure * @param {Mixed} expected The value &quot;we expect&quot; - will be shown as &quot;Need:&quot; in case of failure * @param {String} [desc] The description of the assertion */ exact : function (got, expected, desc) { var R = Siesta.Resource(&#39;Siesta.Test&#39;); if (this.compareObjects(got, expected, true, true)) this.pass(desc, { descTpl : R.get(&#39;isStrictlyEqual&#39;), got : got, expected : expected }) else this.fail(desc, { assertionName : &#39;isStrict&#39;, got : got, need : expected, needDesc : R.get(&#39;needStrictly&#39;) }) }, <span id='Siesta-Test-method-isStrict'> /** </span> * This assertion passes when the comparison of 1st and 2nd arguments with `===` operator returns true and fails otherwise. * * As a special case, one or both arguments can be instance of {@link Siesta.Test.BDD.Placeholder} class, generated with method {@link #any}. * * @param {Mixed} got The value &quot;we have&quot; - will be shown as &quot;Got:&quot; in case of failure * @param {Mixed} expected The value &quot;we expect&quot; - will be shown as &quot;Need:&quot; in case of failure * @param {String} [desc] The description of the assertion */ isStrict : function (got, expected, desc) { var R = Siesta.Resource(&#39;Siesta.Test&#39;); if (this.compareObjects(got, expected, true, true)) this.pass(desc, { descTpl : R.get(&#39;isStrictlyEqual&#39;), got : got, expected : expected }) else this.fail(desc, { assertionName : &#39;isStrict&#39;, got : got, need : expected, needDesc : R.get(&#39;needStrictly&#39;) }) }, isntStrict : function () { this.isNotStrict.apply(this, arguments) }, <span id='Siesta-Test-method-isNotStrict'> /** </span> * This assertion passes when the comparison of 1st and 2nd arguments with `!==` operator returns true and fails otherwise. * It has synonyms - `isntStrict`. * * As a special case, one or both arguments can be instance of {@link Siesta.Test.BDD.Placeholder} class, generated with method {@link #any}. * * @param {Mixed} got The value &quot;we have&quot; - will be shown as &quot;Got:&quot; in case of failure * @param {Mixed} expected The value &quot;we expect&quot; - will be shown as &quot;Need:&quot; in case of failure * @param {String} [desc] The description of the assertion */ isNotStrict : function (got, expected, desc) { var R = Siesta.Resource(&#39;Siesta.Test&#39;); if (!this.compareObjects(got, expected, true, true)) this.pass(desc, { descTpl : R.get(&#39;isStrictlyNotEqual&#39;), got : got, expected : expected }) else this.fail(desc, { assertionName : &#39;isntStrict&#39;, got : got, need : expected, needDesc : R.get(&#39;needStrictlyNot&#39;) }) }, // DEPRECATED in 5.0 wait : function (title, howLong) { var R = Siesta.Resource(&#39;Siesta.Test&#39;); if (this.waitTitles.hasOwnProperty(title)) throw new Error(R.get(&#39;alreadyWaiting&#39;)+ &quot; [&quot; + title + &quot;]&quot;) return this.waitTitles[ title ] = this.beginAsync(howLong) }, // DEPRECATED in 5.0 endWait : function (title) { var R = Siesta.Resource(&#39;Siesta.Test&#39;); if (!this.waitTitles.hasOwnProperty(title)) throw new Error(R.get(&#39;noOngoingWait&#39;) + &quot; [&quot; + title + &quot;]&quot;) this.endAsync(this.waitTitles[ title ]) delete this.waitTitles[ title ] }, <span id='Siesta-Test-method-beginAsync'> /** </span> * This method starts the &quot;asynchronous frame&quot;. The test will wait for all asynchronous frames to complete before it will finalize. * The frame should be finished with the {@link #endAsync} call within the provided `time`, otherwise a failure will be reported. * * For example: * * var async = t.beginAsync() * * Ext.require(&#39;Some.Class&#39;, function () { * * t.ok(Some.Class, &#39;Some class was loaded&#39;) * * t.endAsync(async) * }) * * * Additionally, if you return a `Promise` instance from the test function itself, Siesta will wait until that promise is resolved before finalizing the test. * In modern browsers, this allows us to use `async/await` functions: StartTest(t =&gt; { let someAsyncOperation = () =&gt; new Promise((resolve, reject) =&gt; { setTimeout(() =&gt; resolve(true), 1000) }) t.it(&#39;Doing async stuff&#39;, async t =&gt; { let res = await someAsyncOperation() t.ok(res, &quot;Async stuff finished correctly&quot;) }) }) * @param {Number} time The maximum time (in ms) to wait until force the finalization of this async frame. Optional. Default time is 15000 ms. * @param {Function} errback Optional. The function to call in case the call to {@link #endAsync} was not detected withing `time`. If function * will return any &quot;truthy&quot; value, the failure will not be reported (you can report own failure with this errback). * * @return {Object} The frame object, which can be used in {@link #endAsync} call */ beginAsync : function (time, errback) { time = time || this.defaultTimeout if (time &gt; this.getMaximalTimeout()) this.fireEvent(&#39;maxtimeoutchanged&#39;, time) var R = Siesta.Resource(&#39;Siesta.Test&#39;); var me = this var originalSetTimeout = this.originalSetTimeout var index = this.timeoutsCount.counter++ // in NodeJS `setTimeout` returns an object and not a simple ID, so we try hard to store that object under unique index // also using `setTimeout` from the scope of test - as timeouts in different scopes in browsers are mis-synchronized // can&#39;t just use `this.originalSetTimeout` because of scoping issues var timeoutId = originalSetTimeout(function () { if (me.hasAsyncFrame(index)) { if (!errback || !errback.call(me, me)) me.fail(R.get(&#39;noMatchingEndAsync&#39;, { time : time })) me.endAsync(index) } }, time) this.timeoutIds[ index ] = timeoutId return index }, timeoutIdToIndex : function (id) { var index if (typeof id == &#39;object&#39;) { index = id.__index } else { index = this.idsToIndex[ id ] } return index }, hasAsyncFrame : function (index) { return this.timeoutIds.hasOwnProperty(index) }, hasAsyncFrameByTimeoutId : function (id) { return this.timeoutIds.hasOwnProperty(this.timeoutIdToIndex(id)) }, <span id='Siesta-Test-method-endAsync'> /** </span> * This method finalize the &quot;asynchronous frame&quot; started with {@link #beginAsync}. * * @param {Object} frame The frame to finalize (returned by {@link #beginAsync} method */ endAsync : function (index) { var originalSetTimeout = this.originalSetTimeout var originalClearTimeout = this.originalClearTimeout || this.global.clearTimeout var counter = 0 var R = Siesta.Resource(&#39;Siesta.Test&#39;); if (index == null) Joose.O.each(this.timeoutIds, function (timeoutId, indx) { index = indx if (counter++) throw new Error(R.get(&#39;endAsyncMisuse&#39;)) }) var timeoutId = this.timeoutIds[ index ] // need to call in this way for IE &lt; 9 originalClearTimeout(timeoutId) delete this.timeoutIds[ index ] var me = this if (this.processed &amp;&amp; !this.isFinished()) // to allow potential call to `done` after `endAsync` originalSetTimeout(function () { me.finalize() }, 1) }, clearTimeouts : function () { var originalClearTimeout = this.originalClearTimeout Joose.O.each(this.timeoutIds, function (value, id) { originalClearTimeout(value) }) this.timeoutIds = {} }, processSubTestConfig : function (config) { var cfg = Joose.O.extend({ parent : this, isTodo : this.isTodo, transparentEx : this.transparentEx, waitForTimeout : this.waitForTimeout, waitForPollInterval : this.waitForPollInterval, defaultTimeout : this.defaultTimeout, timeout : this.subTestTimeout, subTestTimeout : this.subTestTimeout, global : this.global, url : this.url, scopeProvider : this.scopeProvider, project : this.project, generation : this.generation, launchId : this.launchId, overrideSetTimeout : this.overrideSetTimeout, originalSetTimeout : this.originalSetTimeout, originalClearTimeout : this.originalClearTimeout, // share the same counter for the whole subtests tree timeoutsCount : this.timeoutsCount, autoCheckGlobals : false, needToCleanup : false, breakTestOnFail : this.breakTestOnFail, breakSubTestOnFail : this.breakSubTestOnFail }, config) return cfg }, <span id='Siesta-Test-method-getSubTest'> /** </span> * Returns a new instance of the test class, configured as being a &quot;sub test&quot; of the current test. * * The number of nesting levels is not limited - ie sub-tests may have own sub-tests. * * Note, that this method does not starts the sub test, but only instatiate it. To start the sub test, * use the {@link #launchSubTest} method or the {@link #subTest} helper method. * * @param {String} name The name of the test. Will be used in the UI, as the parent node name in the assertions tree * @param {Function} code A function with test code. Will receive a test instance as the 1st argument. * @param {Number} [timeout] A maximum duration (in ms) for this sub test. If test will not complete within this time, * it will be considered failed. If not provided, the {@link Siesta.Project#subTestTimeout} value is used. * * @return {Siesta.Test} A sub test instance */ getSubTest : function (arg1, arg2, arg3) { var config var R = Siesta.Resource(&#39;Siesta.Test&#39;); if (arguments.length == 2 || arguments.length == 3) config = { name : arg1, run : arg2, timeout : arg3 } else if (arguments.length == 1 &amp;&amp; this.typeOf(arg1) == &#39;Function&#39;) config = { name : &#39;Sub test&#39;, run : arg1 } config = config || arg1 || {} // pass-through only valid timeout values if (config.timeout == null) delete config.timeout var name = config.name if (!config.run) { this.failWithException(R.get(&#39;codeBodyMissingForSubTest&#39;, { name : name })) throw new Error(R.get(&#39;codeBodyMissingForSubTest&#39;, { name : name })) } if (!config.run.length) { this.failWithException(R.get(&#39;codeBodyMissingTestArg&#39;, { name : name })) throw new Error(R.get(&#39;codeBodyMissingTestArg&#39;, { name : name })) } // in the following code, we cache a specialized version of the current test class, // that version has &quot;Siesta.Test.Sub&quot; role consumed, as a trait (on the instance level) // this is done for efficiency (to not create a separate class per every sub test) // and also because Chrome was throwing this exception randomly: // Required attribute [parent] is missed during initialization of undefined // caching seems to have that fixed var constructor = config.meta || this.meta.c var cls = constructor var cfg = this.processSubTestConfig(config) if (constructor.__WITHSUB__) cls = constructor.__WITHSUB__ else // only need trait for the top level test if (!this.parent) cfg.trait = Siesta.Test.Sub var subTest = new cls(cfg) if (!this.parent &amp;&amp; !constructor.__WITHSUB__) constructor.__WITHSUB__ = subTest.meta.c return subTest }, <span id='Siesta-Test-method-launchSubTest'> /** </span> * This method launch the provided sub test instance. * * @param {Siesta.Test} subTest A test instance to launch * @param {Function} callback A function to call, after the test is completed. This function is called regardless from the test execution result. */ launchSubTest : function (subTest, callback) { if (this.parent &amp;&amp; this.parent.finalizationStarted) return var me = this var R = Siesta.Resource(&#39;Siesta.Test&#39;); var timeout = subTest.timeout || this.subTestTimeout var async = this.beginAsync(timeout, function () { me.fail(R.get(&#39;failedToFinishWithin&#39;, { name : subTest.name ? &#39;[&#39; + subTest.name + &#39;]&#39; : &#39;&#39;, timeout : timeout })) me.restoreTimeoutOverrides() testEndListener.remove() subTest.finalize(true) callback &amp;&amp; callback(subTest) return true }) var testEndListener = subTest.on(&#39;testfinalize&#39;, function () { me.endAsync(async) me.restoreTimeoutOverrides() callback &amp;&amp; callback(subT