diff --git a/javascript/node/selenium-webdriver/CHANGES.md b/javascript/node/selenium-webdriver/CHANGES.md index 70270d4752dd2..4bbb749a654bc 100644 --- a/javascript/node/selenium-webdriver/CHANGES.md +++ b/javascript/node/selenium-webdriver/CHANGES.md @@ -24,6 +24,8 @@ * Added support for setting the username and password in basic auth pop-up dialogs (currently IE only). * Deprecated `WebElement#getInnerHtml()` and `WebEleemnt#getOuterHtml()` +* Deprecated `Promise#thenCatch()` - use `Promise#catch()` instead +* Deprecated `Promise#thenFinally()` - use `promise.thenFinally()` instead * FIXED: `io.findInPath()` will no longer match against directories that have the same basename as the target file. * FIXED: `phantomjs.Driver` now takes a third argument that defines the path to diff --git a/javascript/node/selenium-webdriver/firefox/index.js b/javascript/node/selenium-webdriver/firefox/index.js index 14ad21bd9776e..96fd942400264 100644 --- a/javascript/node/selenium-webdriver/firefox/index.js +++ b/javascript/node/selenium-webdriver/firefox/index.js @@ -374,10 +374,13 @@ class Driver extends webdriver.WebDriver { }); onQuit = function() { - return command.then(command => { + let finishCommand = command.then(command => { command.kill(); return command.result(); - }).thenFinally(() => preparedProfile.then(io.rmDir)); + }); + return promise.thenFinally( + finishCommand, + () => preparedProfile.then(io.rmDir)); }; } diff --git a/javascript/node/selenium-webdriver/lib/promise.js b/javascript/node/selenium-webdriver/lib/promise.js index 86fa494fc9e60..fb3c0a9d245ba 100644 --- a/javascript/node/selenium-webdriver/lib/promise.js +++ b/javascript/node/selenium-webdriver/lib/promise.js @@ -918,6 +918,7 @@ class Thenable { * expect a single argument: the rejection reason. * @return {!ManagedPromise} A new promise which will be * resolved wdith the result of the invoked callback. + * @deprecated Use {@link #catch()} instead. * @template R */ thenCatch(errback) {} @@ -956,6 +957,10 @@ class Thenable { * to call when this promise is resolved. * @return {!ManagedPromise} A promise that will be fulfilled * with the callback result. + * @deprecated thenFinally has been deprecated to help make WebDriver's + * managed promises API compatible with native promises. The functionality + * provided by this method is now offered for any promise implementation + * using the {@link thenFinally} function in the promise module. * @template R */ thenFinally(callback) {} @@ -1239,12 +1244,21 @@ class ManagedPromise { null, errback, 'catch', ManagedPromise.prototype.catch); } - /** @override */ + /** + * @override + * @deprecated Use {@link #catch()} instead. + */ thenCatch(errback) { return this.catch(errback); } - /** @override */ + /** + * @override + * @deprecated thenFinally has been deprecated to help make WebDriver's + * managed promises API compatible with native promises. The functionality + * provided by this method is now offered for any promise implementation + * using the {@link thenFinally} function in the promise module. + */ thenFinally(callback) { var error; var mustThrow = false; @@ -1438,6 +1452,70 @@ class Deferred { Thenable.addImplementation(Deferred); +/** + * Registers a listener to invoke when a promise is resolved, regardless of + * whether the promise's value was successfully computed. This function is + * synonymous with the `finally` clause in a synchronous API: + * + * // Synchronous API: + * try { + * doSynchronousWork(); + * } finally { + * cleanUp(); + * } + * + * // Asynchronous promise API: + * promise.finally(doAsynchronousWork(), cleanUp); + * + * __Note:__ similar to the `finally` clause, if the registered callback returns + * a rejected promise or throws an error, that error will silently replace the + * rejection (if any) from the initial promise: + * + * try { + * throw Error('one'); + * } finally { + * throw Error('two'); // Hides Error: one + * } + * + * let p = promise.rejected(Error('one')); + * promise.finally(p, function() { + * throw Error('two'); // Hides Error: one + * }); + * + * @param {PROMISE_TYPE} promise The thenable, promise-like object with which + * to register the callback. + * @param {function(): *} callback The function to call when the promise is + * resolved. + * @return {!PROMISE_TYPE} A new promise that is chained to the callback. This + * promise will inherit the value of the original input if the callback + * does not throw or return a rejected promise. + * @template PROMISE_TYPE + */ +function thenFinally(promise, callback) { + if (!isPromise(promise)) { + throw TypeError('first argument must be a promise-like object'); + } + + if (typeof callback !== 'function') { + throw TypeError('second argument must be a function'); + } + + let error; + let mustThrow = false; + return promise + .then(() => callback(), (e) => { + error = e; + mustThrow = true; + return callback(); + }) + .then(() => { + if (mustThrow) { + throw error; + } + }); +} + + /** * Tests if a value is an Error-like object. This is more than an straight * instanceof check since the value may originate from another context. @@ -3091,6 +3169,7 @@ module.exports = { map: map, rejected: rejected, setDefaultFlow: setDefaultFlow, + thenFinally: thenFinally, when: when, get LONG_STACK_TRACES() { return LONG_STACK_TRACES; }, diff --git a/javascript/node/selenium-webdriver/lib/until.js b/javascript/node/selenium-webdriver/lib/until.js index 1b36bf00b84e4..53ef7bdb77119 100644 --- a/javascript/node/selenium-webdriver/lib/until.js +++ b/javascript/node/selenium-webdriver/lib/until.js @@ -114,7 +114,7 @@ exports.ableToSwitchToFrame = function ableToSwitchToFrame(frame) { */ exports.alertIsPresent = function alertIsPresent() { return new Condition('for alert to be present', function(driver) { - return driver.switchTo().alert().thenCatch(function(e) { + return driver.switchTo().alert().catch(function(e) { if (!(e instanceof error.NoSuchAlertError)) { throw e; } diff --git a/javascript/node/selenium-webdriver/lib/webdriver.js b/javascript/node/selenium-webdriver/lib/webdriver.js index 654ee6c9924be..54fca5939194d 100644 --- a/javascript/node/selenium-webdriver/lib/webdriver.js +++ b/javascript/node/selenium-webdriver/lib/webdriver.js @@ -373,13 +373,13 @@ class WebDriver { // actually executes the command. This addresses scenarios like catching // an element not found error in: // - // driver.findElement(By.id('foo')).click().thenCatch(function(e) { + // driver.findElement(By.id('foo')).click().catch(function(e) { // if (e instanceof NoSuchElementError) { // // Do something. // } // }); var prepCommand = toWireValue(command.getParameters()); - prepCommand.thenCatch(function() {}); + prepCommand.catch(function() {}); var flow = this.flow_; var executor = this.executor_; @@ -447,7 +447,7 @@ class WebDriver { 'WebDriver.quit()'); // Delete our session ID when the quit command finishes; this will allow us to // throw an error when attemnpting to use a driver post-quit. - return result.thenFinally(() => delete this.session_); + return promise.thenFinally(result, () => delete this.session_); } /** @@ -926,7 +926,7 @@ class WebDriver { setParameter('using', locator.using). setParameter('value', locator.value); let res = this.schedule(cmd, 'WebDriver.findElements(' + locator + ')'); - return res.thenCatch(function(e) { + return res.catch(function(e) { if (e instanceof error.NoSuchElementError) { return []; } @@ -1930,7 +1930,7 @@ class WebElement { } // Suppress unhandled rejection errors until the flow executes the command. - keys.thenCatch(function() {}); + keys.catch(function() {}); var element = this; return this.driver_.flow_.execute(function() { @@ -2200,10 +2200,10 @@ class WebElementPromise extends WebElement { this.catch = el.catch.bind(el); /** @override */ - this.thenCatch = el.thenCatch.bind(el); + this.thenCatch = el.catch.bind(el); /** @override */ - this.thenFinally = el.thenFinally.bind(el); + this.thenFinally = (cb) => promise.thenFinally(el, cb); /** * Defers returning the element ID until the wrapped WebElement has been @@ -2354,10 +2354,10 @@ class AlertPromise extends Alert { this.catch = alert.catch.bind(alert); /** @override */ - this.thenCatch = alert.thenCatch.bind(alert); + this.thenCatch = alert.catch.bind(alert); /** @override */ - this.thenFinally = alert.thenFinally.bind(alert); + this.thenFinally = (cb) => promise.thenFinally(alert, cb); /** * Defer returning text until the promised alert has been resolved. diff --git a/javascript/node/selenium-webdriver/net/portprober.js b/javascript/node/selenium-webdriver/net/portprober.js index f455ede7b57d3..1606d1f0545c1 100644 --- a/javascript/node/selenium-webdriver/net/portprober.js +++ b/javascript/node/selenium-webdriver/net/portprober.js @@ -53,7 +53,7 @@ function findSystemPortRange() { } var range = process.platform === 'win32' ? findWindowsPortRange() : findUnixPortRange(); - return systemRange = range.thenCatch(function() { + return systemRange = range.catch(function() { return DEFAULT_IANA_RANGE; }); } @@ -154,7 +154,7 @@ function findWindowsPortRange() { function isFree(port, opt_host) { var result = promise.defer(); - result.promise.thenCatch(function(e) { + result.promise.catch(function(e) { if (e instanceof promise.CancellationError) { server.close(); } diff --git a/javascript/node/selenium-webdriver/remote/index.js b/javascript/node/selenium-webdriver/remote/index.js index 820de75c02d15..ff923a39202bd 100644 --- a/javascript/node/selenium-webdriver/remote/index.js +++ b/javascript/node/selenium-webdriver/remote/index.js @@ -237,7 +237,7 @@ class DriverService { return new promise.Promise(function(fulfill, reject) { var ready = httpUtil.waitForServer(serverUrl, timeout) .then(fulfill, reject); - earlyTermination.thenCatch(function(e) { + earlyTermination.catch(function(e) { ready.cancel(/** @type {Error} */(e)); reject(Error(e.message)); }); diff --git a/javascript/node/selenium-webdriver/safari.js b/javascript/node/selenium-webdriver/safari.js index a411c3d01d3a5..992a3ae7f6133 100644 --- a/javascript/node/selenium-webdriver/safari.js +++ b/javascript/node/selenium-webdriver/safari.js @@ -414,7 +414,7 @@ class CommandExecutor { })); } var self = this; - return promise.all(tasks).thenFinally(function() { + return promise.thenFinally(promise.all(tasks), function() { self.server_ = null; self.socket_ = null; self.safari_ = null; diff --git a/javascript/node/selenium-webdriver/test/execute_script_test.js b/javascript/node/selenium-webdriver/test/execute_script_test.js index bff14fe635e8d..9b1f44fb0baf4 100644 --- a/javascript/node/selenium-webdriver/test/execute_script_test.js +++ b/javascript/node/selenium-webdriver/test/execute_script_test.js @@ -47,7 +47,7 @@ test.suite(function(env) { test.it('fails if script throws', function() { execute('throw new Error("boom")') .then(function() { throw shoudlHaveFailed; }) - .thenCatch(function(e) { + .catch(function(e) { // The java WebDriver server adds a bunch of crap to error messages. // Error message will just be "JavaScript error" for IE. assert(e.message).matches(/.*(JavaScript error|boom).*/); @@ -57,7 +57,7 @@ test.suite(function(env) { test.it('fails if script does not parse', function() { execute('throw function\\*') .then(function() { throw shoudlHaveFailed; }) - .thenCatch(function(e) { + .catch(function(e) { assert(e).notEqualTo(shouldHaveFailed); }); }); @@ -323,7 +323,7 @@ test.suite(function(env) { }); }); }); - + function verifyJson(expected) { return function(actual) { assert(JSON.stringify(actual)).equalTo(JSON.stringify(expected)); diff --git a/javascript/node/selenium-webdriver/test/lib/promise_error_test.js b/javascript/node/selenium-webdriver/test/lib/promise_error_test.js index 114189ce2b29e..361657dfdc590 100644 --- a/javascript/node/selenium-webdriver/test/lib/promise_error_test.js +++ b/javascript/node/selenium-webdriver/test/lib/promise_error_test.js @@ -362,11 +362,11 @@ describe('promise error handling', function() { it('start with normal promise', function() { var error = Error('an error'); return promise.rejected(error). - thenCatch(function(e) { + catch(function(e) { assert.equal(e, error); throw new StubError; }). - thenCatch(assertIsStubError); + catch(assertIsStubError); }); it('start with task result', function() { @@ -374,17 +374,17 @@ describe('promise error handling', function() { return flow.execute(function() { throw error; }). - thenCatch(function(e) { + catch(function(e) { assert.equal(e, error); throw new StubError; }). - thenCatch(assertIsStubError); + catch(assertIsStubError); }); it('start with normal promise; uncaught error', function() { var error = Error('an error'); promise.rejected(error). - thenCatch(function(e) { + catch(function(e) { assert.equal(e, error); throw new StubError; }); @@ -396,7 +396,7 @@ describe('promise error handling', function() { flow.execute(function() { throw error; }). - thenCatch(function(e) { + catch(function(e) { assert.equal(e, error); throw new StubError; }); @@ -429,7 +429,7 @@ describe('promise error handling', function() { it('promise was rejected', function() { var toThrow = promise.rejected(new StubError); - toThrow.thenCatch(function() {}); // For tearDown. + toThrow.catch(function() {}); // For tearDown. flow.execute(function() { throw toThrow; }).then(assert.fail, function(e) { @@ -756,7 +756,7 @@ describe('promise error handling', function() { }).then(function(error) { assert.ok(error instanceof promise.CancellationError); assert.ok(!task.isPending()); - return task.thenCatch(function(error) { + return task.catch(function(error) { assert.ok(error instanceof promise.CancellationError); }); }); diff --git a/javascript/node/selenium-webdriver/test/lib/promise_flow_test.js b/javascript/node/selenium-webdriver/test/lib/promise_flow_test.js index fd4469d5e446c..a8f9d91402fb3 100644 --- a/javascript/node/selenium-webdriver/test/lib/promise_flow_test.js +++ b/javascript/node/selenium-webdriver/test/lib/promise_flow_test.js @@ -581,7 +581,7 @@ describe('promise control flow', function() { then(function() { return scheduleAction('b', throwStubError); }). - thenCatch(errback); + catch(errback); return waitForIdle().then(function() { assert(errback.called); assertIsStubError(errback.getCall(0).args[0]); @@ -596,7 +596,7 @@ describe('promise control flow', function() { throw new StubError; }); }); - }).thenCatch(errback); + }).catch(errback); schedule('d'); return waitForIdle(). then(function() { @@ -1921,7 +1921,7 @@ describe('promise control flow', function() { return waitForIdle().then(function() { assert.ok(!called); assertFlowHistory(); - return task1.thenCatch(function(e) { + return task1.catch(function(e) { assert.ok(e instanceof promise.CancellationError); assert.equal('no soup for you', e.message); }); @@ -1939,7 +1939,7 @@ describe('promise control flow', function() { return waitForIdle().then(function() { assert.ok(!called); assertFlowHistory('a', 'c'); - return task2.thenCatch(function(e) { + return task2.catch(function(e) { assert.ok(e instanceof promise.CancellationError); assert.equal('no soup for you', e.message); }); @@ -1954,7 +1954,7 @@ describe('promise control flow', function() { return waitForIdle().then(function() { assert.ok(!called); assertFlowHistory(); - return task.thenCatch(function(e) { + return task.catch(function(e) { assert.ok(e instanceof promise.CancellationError); }); }); @@ -1972,7 +1972,7 @@ describe('promise control flow', function() { return waitForIdle().then(function() { assert.deepEqual([], seen); assertFlowHistory(); - return task.thenCatch(function(e) { + return task.catch(function(e) { assert.ok(e instanceof promise.CancellationError); }); }); @@ -1990,7 +1990,7 @@ describe('promise control flow', function() { return waitForIdle().then(function() { assert.ok(!called); assertFlowHistory('a', 'c'); - return task.thenCatch(function(e) { + return task.catch(function(e) { assert.ok(e instanceof promise.CancellationError); assert.equal('no soup for you', e.message); }); @@ -2014,19 +2014,19 @@ describe('promise control flow', function() { // Since the outerTask is cancelled below, innerTask should be cancelled // with a DiscardedTaskError, which means its callbacks are silently // dropped - so this should never execute. - innerTask.thenCatch(function(e) { + innerTask.catch(function(e) { order.push(2); }); }); schedule('b'); - outerTask.thenCatch(function(e) { + outerTask.catch(function(e) { order.push(3); assert.ok(e instanceof promise.CancellationError); assert.equal('no soup for you', e.message); }); - unresolved.promise.thenCatch(function(e) { + unresolved.promise.catch(function(e) { order.push(4); assert.ok(e instanceof promise.CancellationError); }); diff --git a/javascript/node/selenium-webdriver/test/lib/promise_generator_test.js b/javascript/node/selenium-webdriver/test/lib/promise_generator_test.js index 44568c9802055..5fdff9f80f51d 100644 --- a/javascript/node/selenium-webdriver/test/lib/promise_generator_test.js +++ b/javascript/node/selenium-webdriver/test/lib/promise_generator_test.js @@ -79,7 +79,7 @@ describe('promise.consume()', function() { }); return promise.delayed(75).then(function() { p.cancel(); - return p.thenCatch(function() { + return p.catch(function() { return promise.delayed(300); }); }).then(function() { @@ -122,7 +122,7 @@ describe('promise.consume()', function() { values.push(1); yield promise.rejected(e); values.push(2); - }).thenCatch(function() { + }).catch(function() { assert.deepEqual([1], values); }); }); @@ -227,7 +227,7 @@ describe('promise.consume()', function() { values.push(i++); }); } - }, 75).thenCatch(function() { + }, 75).catch(function() { assert.deepEqual( [0, 1, 2], values, 'Should complete one loop of wait condition'); }); diff --git a/javascript/node/selenium-webdriver/test/lib/promise_test.js b/javascript/node/selenium-webdriver/test/lib/promise_test.js index ed6d965853a76..00a19a132b00e 100644 --- a/javascript/node/selenium-webdriver/test/lib/promise_test.js +++ b/javascript/node/selenium-webdriver/test/lib/promise_test.js @@ -57,7 +57,7 @@ describe('promise', function() { function createRejectedPromise(reason) { var p = promise.rejected(reason); - p.thenCatch(function() {}); + p.catch(function() {}); return p; } @@ -85,7 +85,7 @@ describe('promise', function() { it('returnsOwnPromiseIfNoCallbacksWereGiven', function() { var deferred = new promise.Deferred(); assert.equal(deferred.promise, deferred.promise.then()); - assert.equal(deferred.promise, deferred.promise.thenCatch()); + assert.equal(deferred.promise, deferred.promise.catch()); assert.equal(deferred.promise, promise.when(deferred.promise)); }); @@ -105,7 +105,7 @@ describe('promise', function() { var done = callbackHelper(assertIsStubError); return promise.rejected(new StubError). thenFinally(function() {}). - thenCatch(done). + catch(done). thenFinally(done.assertCalled); }); @@ -113,7 +113,7 @@ describe('promise', function() { var done = callbackHelper(assertIsStubError); return promise.rejected(new Error('original')). thenFinally(throwStubError). - thenCatch(done). + catch(done). thenFinally(done.assertCalled); }); @@ -121,7 +121,7 @@ describe('promise', function() { var done = callbackHelper(assertIsStubError); return promise.fulfilled(). thenFinally(throwStubError). - thenCatch(done). + catch(done). thenFinally(done.assertCalled); }); @@ -131,7 +131,7 @@ describe('promise', function() { thenFinally(function() { return promise.rejected(new StubError); }). - thenCatch(done). + catch(done). thenFinally(done.assertCalled); }); }); @@ -900,7 +900,7 @@ describe('promise', function() { assert.notEqual(originalStack, e.stack); assert.equal(e.stack.indexOf(originalStack), 0, 'should start with original stack'); - assert.deepEqual(['From: Promise: new'], getStackMessages(e)); + assert.deepEqual(['From: ManagedPromise: new'], getStackMessages(e)); }); }); @@ -920,7 +920,7 @@ describe('promise', function() { assert.notEqual(originalStack, e.stack); assert.equal(e.stack.indexOf(originalStack), 0, 'should start with original stack'); - assert.deepEqual(['From: Promise: new'], getStackMessages(e)); + assert.deepEqual(['From: ManagedPromise: new'], getStackMessages(e)); }); }); @@ -939,9 +939,9 @@ describe('promise', function() { } }). then(fail). - thenCatch(function(e) { throw e; }). + catch(function(e) { throw e; }). then(fail). - thenCatch(function(e) { throw e; }). + catch(function(e) { throw e; }). then(fail, function(e) { assert.equal(error, e); if (typeof originalStack !== 'string') { @@ -951,7 +951,7 @@ describe('promise', function() { assert.equal(e.stack.indexOf(originalStack), 0, 'should start with original stack'); assert.deepEqual([ - 'From: Promise: new', + 'From: ManagedPromise: new', 'From: Promise: then', 'From: Promise: catch', 'From: Promise: then', @@ -977,7 +977,7 @@ describe('promise', function() { throw e; } }). - thenCatch(function(e) { throw e; }). + catch(function(e) { throw e; }). then(fail, function(e) { assert.equal(error, e); if (typeof originalStack !== 'string') { diff --git a/javascript/node/selenium-webdriver/test/lib/webdriver_test.js b/javascript/node/selenium-webdriver/test/lib/webdriver_test.js index d39aa0a994993..78775c7000a44 100644 --- a/javascript/node/selenium-webdriver/test/lib/webdriver_test.js +++ b/javascript/node/selenium-webdriver/test/lib/webdriver_test.js @@ -415,7 +415,7 @@ describe('WebDriver', function() { var driver = executor.createDriver(); driver.switchTo().window('foo') - .thenCatch(v => assert.strictEqual(v, e)); + .catch(v => assert.strictEqual(v, e)); driver.getTitle(); return waitForIdle(); }); @@ -441,7 +441,7 @@ describe('WebDriver', function() { var driver = executor.createDriver(); return driver.switchTo().window('foo'). - thenCatch(function() { return new StubError; }); + catch(function() { return new StubError; }); then(assertIsStubError); }); @@ -452,7 +452,7 @@ describe('WebDriver', function() { end(); var driver = executor.createDriver(); - driver.switchTo().window('foo').thenCatch(throwStubError); + driver.switchTo().window('foo').catch(throwStubError); return waitForAbort().then(assertIsStubError); }); @@ -562,7 +562,7 @@ describe('WebDriver', function() { var driver = executor.createDriver(); driver.getTitle().then(function() { - driver.switchTo().window('foo').thenCatch(function() {}); + driver.switchTo().window('foo').catch(function() {}); }); driver.close(); @@ -582,7 +582,7 @@ describe('WebDriver', function() { then(function() { return driver.switchTo().window('foo'); }). - thenCatch(v => assert.strictEqual(v, e)); + catch(v => assert.strictEqual(v, e)); return waitForIdle(); }); @@ -602,7 +602,7 @@ describe('WebDriver', function() { then(function() { return driver.switchTo().window('foo'); }). - thenCatch(function() {}); + catch(function() {}); driver.close(); }); @@ -640,7 +640,7 @@ describe('WebDriver', function() { var driver = executor.createDriver(); driver.switchTo().window('foo'). - thenCatch(function(e) { + catch(function(e) { assertIsStubError(e); count += 1; return driver.getCurrentUrl(); @@ -1143,7 +1143,7 @@ describe('WebDriver', function() { let executor = new FakeExecutor(); var arg = promise.rejected(new StubError); - arg.thenCatch(function() {}); // Suppress default handler. + arg.catch(function() {}); // Suppress default handler. var driver = executor.createDriver(); return driver.executeScript(function() {}, arg). @@ -1154,7 +1154,7 @@ describe('WebDriver', function() { describe('executeAsyncScript', function() { it('failsIfArgumentIsARejectedPromise', function() { var arg = promise.rejected(new StubError); - arg.thenCatch(function() {}); // Suppress default handler. + arg.catch(function() {}); // Suppress default handler. var driver = new FakeExecutor().createDriver(); return driver.executeAsyncScript(function() {}, arg). @@ -1543,7 +1543,7 @@ describe('WebDriver', function() { it('failsIfAnInputElementCouldNotBeFound', function() { var id = promise.rejected(new StubError); - id.thenCatch(function() {}); // Suppress default handler. + id.catch(function() {}); // Suppress default handler. var driver = new FakeExecutor().createDriver(); var a = new WebElement(driver, 'foo'); @@ -1783,7 +1783,7 @@ describe('WebDriver', function() { mouseDown(). mouseUp(). perform(). - thenCatch(assertIsStubError); + catch(assertIsStubError); }); describe('mouseMove', function() { @@ -1871,7 +1871,7 @@ describe('WebDriver', function() { touchActions(). scroll({x: 3, y: 4}). perform(). - thenCatch(assertIsStubError); + catch(assertIsStubError); }); it('testTouchActionSequence', function() {