aboutsummaryrefslogtreecommitdiff
path: root/node_modules/selenium-webdriver/test
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2016-11-03 01:33:53 +0100
committerFlorian Dold <florian.dold@gmail.com>2016-11-03 01:33:53 +0100
commitd1291f67551c58168af43698a359cb5ddfd266b0 (patch)
tree55a13ed29fe1915e3f42f1b1b7038dafa2e975a7 /node_modules/selenium-webdriver/test
parentd0a0695fb5d34996850723f7d4b1b59c3df909c2 (diff)
node_modules
Diffstat (limited to 'node_modules/selenium-webdriver/test')
-rw-r--r--node_modules/selenium-webdriver/test/actions_test.js54
-rw-r--r--node_modules/selenium-webdriver/test/chrome/options_test.js227
-rw-r--r--node_modules/selenium-webdriver/test/chrome/service_test.js45
-rw-r--r--node_modules/selenium-webdriver/test/cookie_test.js214
-rw-r--r--node_modules/selenium-webdriver/test/element_finding_test.js410
-rw-r--r--node_modules/selenium-webdriver/test/execute_script_test.js352
-rw-r--r--node_modules/selenium-webdriver/test/fingerprint_test.js57
-rw-r--r--node_modules/selenium-webdriver/test/firefox/extension_test.js96
-rw-r--r--node_modules/selenium-webdriver/test/firefox/firefox_test.js188
-rw-r--r--node_modules/selenium-webdriver/test/firefox/profile_test.js186
-rw-r--r--node_modules/selenium-webdriver/test/http/http_test.js205
-rw-r--r--node_modules/selenium-webdriver/test/http/util_test.js184
-rw-r--r--node_modules/selenium-webdriver/test/io_test.js321
-rw-r--r--node_modules/selenium-webdriver/test/lib/by_test.js139
-rw-r--r--node_modules/selenium-webdriver/test/lib/capabilities_test.js111
-rw-r--r--node_modules/selenium-webdriver/test/lib/error_test.js286
-rw-r--r--node_modules/selenium-webdriver/test/lib/events_test.js177
-rw-r--r--node_modules/selenium-webdriver/test/lib/http_test.js655
-rw-r--r--node_modules/selenium-webdriver/test/lib/logging_test.js272
-rw-r--r--node_modules/selenium-webdriver/test/lib/promise_aplus_test.js74
-rw-r--r--node_modules/selenium-webdriver/test/lib/promise_error_test.js880
-rw-r--r--node_modules/selenium-webdriver/test/lib/promise_flow_test.js2284
-rw-r--r--node_modules/selenium-webdriver/test/lib/promise_generator_test.js308
-rw-r--r--node_modules/selenium-webdriver/test/lib/promise_test.js1067
-rw-r--r--node_modules/selenium-webdriver/test/lib/testutil.js90
-rw-r--r--node_modules/selenium-webdriver/test/lib/until_test.js463
-rw-r--r--node_modules/selenium-webdriver/test/lib/webdriver_test.js2177
-rw-r--r--node_modules/selenium-webdriver/test/logging_test.js169
-rw-r--r--node_modules/selenium-webdriver/test/net/index_test.js60
-rw-r--r--node_modules/selenium-webdriver/test/net/portprober_test.js129
-rw-r--r--node_modules/selenium-webdriver/test/page_loading_test.js166
-rw-r--r--node_modules/selenium-webdriver/test/phantomjs/execute_phantomjs_test.js73
-rw-r--r--node_modules/selenium-webdriver/test/proxy_test.js180
-rw-r--r--node_modules/selenium-webdriver/test/remote_test.js117
-rw-r--r--node_modules/selenium-webdriver/test/safari_test.js112
-rw-r--r--node_modules/selenium-webdriver/test/session_test.js53
-rw-r--r--node_modules/selenium-webdriver/test/stale_element_test.js61
-rw-r--r--node_modules/selenium-webdriver/test/tag_name_test.js34
-rw-r--r--node_modules/selenium-webdriver/test/testing/assert_test.js374
-rw-r--r--node_modules/selenium-webdriver/test/testing/index_test.js178
-rw-r--r--node_modules/selenium-webdriver/test/upload_test.js86
-rw-r--r--node_modules/selenium-webdriver/test/window_test.js175
42 files changed, 13489 insertions, 0 deletions
diff --git a/node_modules/selenium-webdriver/test/actions_test.js b/node_modules/selenium-webdriver/test/actions_test.js
new file mode 100644
index 000000000..7ea0047ad
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/actions_test.js
@@ -0,0 +1,54 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var Browser = require('..').Browser,
+ By = require('..').By,
+ until = require('..').until,
+ test = require('../lib/test'),
+ fileServer = require('../lib/test/fileserver');
+
+
+test.suite(function(env) {
+ var driver;
+ test.beforeEach(function() { driver = env.builder().build(); });
+ test.afterEach(function() { driver.quit(); });
+
+ test.ignore(
+ env.browsers(Browser.FIREFOX, Browser.PHANTOM_JS, Browser.SAFARI)).
+ describe('WebDriver.actions()', function() {
+
+ test.it('can move to and click element in an iframe', function() {
+ driver.get(fileServer.whereIs('click_tests/click_in_iframe.html'));
+
+ driver.wait(until.elementLocated(By.id('ifr')), 5000)
+ .then(function(frame) {
+ driver.switchTo().frame(frame);
+ });
+
+ var link = driver.findElement(By.id('link'));
+ driver.actions()
+ .mouseMove(link)
+ .click()
+ .perform();
+
+ driver.wait(until.titleIs('Submitted Successfully!'), 5000);
+ });
+
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/chrome/options_test.js b/node_modules/selenium-webdriver/test/chrome/options_test.js
new file mode 100644
index 000000000..28c4faa91
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/chrome/options_test.js
@@ -0,0 +1,227 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var fs = require('fs');
+
+var webdriver = require('../..'),
+ chrome = require('../../chrome'),
+ symbols = require('../../lib/symbols'),
+ proxy = require('../../proxy'),
+ assert = require('../../testing/assert');
+
+var test = require('../../lib/test');
+
+
+describe('chrome.Options', function() {
+ describe('fromCapabilities', function() {
+
+ it('should return a new Options instance if none were defined',
+ function() {
+ var options = chrome.Options.fromCapabilities(
+ new webdriver.Capabilities());
+ assert(options).instanceOf(chrome.Options);
+ });
+
+ it('should return options instance if present', function() {
+ var options = new chrome.Options();
+ var caps = options.toCapabilities();
+ assert(caps).instanceOf(webdriver.Capabilities);
+ assert(chrome.Options.fromCapabilities(caps)).equalTo(options);
+ });
+
+ it('should rebuild options from wire representation', function() {
+ var expectedExtension = fs.readFileSync(__filename, 'base64');
+ var caps = webdriver.Capabilities.chrome().set('chromeOptions', {
+ args: ['a', 'b'],
+ extensions: [__filename],
+ binary: 'binaryPath',
+ logPath: 'logFilePath',
+ detach: true,
+ localState: 'localStateValue',
+ prefs: 'prefsValue'
+ });
+
+ var options = chrome.Options.fromCapabilities(caps);
+ var json = options[symbols.serialize]();
+
+ assert(json.args.length).equalTo(2);
+ assert(json.args[0]).equalTo('a');
+ assert(json.args[1]).equalTo('b');
+ assert(json.extensions.length).equalTo(1);
+ assert(json.extensions[0]).equalTo(expectedExtension);
+ assert(json.binary).equalTo('binaryPath');
+ assert(json.logPath).equalTo('logFilePath');
+ assert(json.detach).equalTo(true);
+ assert(json.localState).equalTo('localStateValue');
+ assert(json.prefs).equalTo('prefsValue');
+ });
+
+ it('should rebuild options from incomplete wire representation',
+ function() {
+ var caps = webdriver.Capabilities.chrome().set('chromeOptions', {
+ logPath: 'logFilePath'
+ });
+
+ var options = chrome.Options.fromCapabilities(caps);
+ var json = options[symbols.serialize]();
+ assert(json.args).isUndefined();
+ assert(json.binary).isUndefined();
+ assert(json.detach).isUndefined();
+ assert(json.excludeSwitches).isUndefined();
+ assert(json.extensions).isUndefined();
+ assert(json.localState).isUndefined();
+ assert(json.logPath).equalTo('logFilePath');
+ assert(json.prefs).isUndefined();
+ assert(json.minidumpPath).isUndefined();
+ assert(json.mobileEmulation).isUndefined();
+ assert(json.perfLoggingPrefs).isUndefined();
+ });
+
+ it('should extract supported WebDriver capabilities', function() {
+ var proxyPrefs = proxy.direct();
+ var logPrefs = {};
+ var caps = webdriver.Capabilities.chrome().
+ set(webdriver.Capability.PROXY, proxyPrefs).
+ set(webdriver.Capability.LOGGING_PREFS, logPrefs);
+
+ var options = chrome.Options.fromCapabilities(caps);
+ assert(options.proxy_).equalTo(proxyPrefs);
+ assert(options.logPrefs_).equalTo(logPrefs);
+ });
+ });
+
+ describe('addArguments', function() {
+ it('takes var_args', function() {
+ var options = new chrome.Options();
+ assert(options[symbols.serialize]().args).isUndefined();
+
+ options.addArguments('a', 'b');
+ var json = options[symbols.serialize]();
+ assert(json.args.length).equalTo(2);
+ assert(json.args[0]).equalTo('a');
+ assert(json.args[1]).equalTo('b');
+ });
+
+ it('flattens input arrays', function() {
+ var options = new chrome.Options();
+ assert(options[symbols.serialize]().args).isUndefined();
+
+ options.addArguments(['a', 'b'], 'c', [1, 2], 3);
+ var json = options[symbols.serialize]();
+ assert(json.args.length).equalTo(6);
+ assert(json.args[0]).equalTo('a');
+ assert(json.args[1]).equalTo('b');
+ assert(json.args[2]).equalTo('c');
+ assert(json.args[3]).equalTo(1);
+ assert(json.args[4]).equalTo(2);
+ assert(json.args[5]).equalTo(3);
+ });
+ });
+
+ describe('addExtensions', function() {
+ it('takes var_args', function() {
+ var options = new chrome.Options();
+ assert(options.extensions_.length).equalTo(0);
+
+ options.addExtensions('a', 'b');
+ assert(options.extensions_.length).equalTo(2);
+ assert(options.extensions_[0]).equalTo('a');
+ assert(options.extensions_[1]).equalTo('b');
+ });
+
+ it('flattens input arrays', function() {
+ var options = new chrome.Options();
+ assert(options.extensions_.length).equalTo(0);
+
+ options.addExtensions(['a', 'b'], 'c', [1, 2], 3);
+ assert(options.extensions_.length).equalTo(6);
+ assert(options.extensions_[0]).equalTo('a');
+ assert(options.extensions_[1]).equalTo('b');
+ assert(options.extensions_[2]).equalTo('c');
+ assert(options.extensions_[3]).equalTo(1);
+ assert(options.extensions_[4]).equalTo(2);
+ assert(options.extensions_[5]).equalTo(3);
+ });
+ });
+
+ describe('serialize', function() {
+ it('base64 encodes extensions', function() {
+ var expected = fs.readFileSync(__filename, 'base64');
+ var wire = new chrome.Options()
+ .addExtensions(__filename)
+ [symbols.serialize]();
+ assert(wire.extensions.length).equalTo(1);
+ assert(wire.extensions[0]).equalTo(expected);
+ });
+ });
+
+ describe('toCapabilities', function() {
+ it('returns a new capabilities object if one is not provided', function() {
+ var options = new chrome.Options();
+ var caps = options.toCapabilities();
+ assert(caps.get('browserName')).equalTo('chrome');
+ assert(caps.get('chromeOptions')).equalTo(options);
+ });
+
+ it('adds to input capabilities object', function() {
+ var caps = webdriver.Capabilities.firefox();
+ var options = new chrome.Options();
+ assert(options.toCapabilities(caps)).equalTo(caps);
+ assert(caps.get('browserName')).equalTo('firefox');
+ assert(caps.get('chromeOptions')).equalTo(options);
+ });
+
+ it('sets generic driver capabilities', function() {
+ var proxyPrefs = {};
+ var loggingPrefs = {};
+ var options = new chrome.Options().
+ setLoggingPrefs(loggingPrefs).
+ setProxy(proxyPrefs);
+
+ var caps = options.toCapabilities();
+ assert(caps.get('proxy')).equalTo(proxyPrefs);
+ assert(caps.get('loggingPrefs')).equalTo(loggingPrefs);
+ });
+ });
+});
+
+test.suite(function(env) {
+ var driver;
+
+ test.afterEach(function() {
+ driver.quit();
+ });
+
+ describe('Chrome options', function() {
+ test.it('can start Chrome with custom args', function() {
+ var options = new chrome.Options().
+ addArguments('user-agent=foo;bar');
+
+ driver = env.builder().
+ setChromeOptions(options).
+ build();
+
+ driver.get(test.Pages.ajaxyPage);
+
+ var userAgent = driver.executeScript(
+ 'return window.navigator.userAgent');
+ assert(userAgent).equalTo('foo;bar');
+ });
+ });
+}, {browsers: ['chrome']});
diff --git a/node_modules/selenium-webdriver/test/chrome/service_test.js b/node_modules/selenium-webdriver/test/chrome/service_test.js
new file mode 100644
index 000000000..0ccc93b5a
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/chrome/service_test.js
@@ -0,0 +1,45 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var webdriver = require('../..'),
+ chrome = require('../../chrome'),
+ assert = require('../../testing/assert');
+
+var test = require('../../lib/test');
+
+
+test.suite(function(env) {
+ describe('chromedriver', function() {
+ var service;
+ test.afterEach(function() {
+ if (service) {
+ return service.kill();
+ }
+ });
+
+ test.it('can be started on a custom path', function() {
+ service = new chrome.ServiceBuilder()
+ .setPath('/foo/bar/baz')
+ .build();
+ return service.start().then(function(url) {
+ assert(url).endsWith('/foo/bar/baz');
+ });
+ });
+ });
+}, {browsers: ['chrome']});
diff --git a/node_modules/selenium-webdriver/test/cookie_test.js b/node_modules/selenium-webdriver/test/cookie_test.js
new file mode 100644
index 000000000..3912fdbee
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/cookie_test.js
@@ -0,0 +1,214 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var assert = require('assert'),
+ url = require('url');
+
+var test = require('../lib/test'),
+ fileserver = require('../lib/test/fileserver'),
+ Browser = require('..').Browser,
+ Pages = test.Pages;
+
+
+test.suite(function(env) {
+ var driver;
+
+ test.before(function() {
+ driver = env.builder().build();
+ });
+
+ test.after(function() {
+ driver.quit();
+ });
+
+ test.ignore(env.browsers(Browser.SAFARI)). // Cookie handling is broken.
+ describe('Cookie Management;', function() {
+
+ test.beforeEach(function() {
+ driver.get(fileserver.Pages.ajaxyPage);
+ driver.manage().deleteAllCookies();
+ assertHasCookies();
+ });
+
+ test.it('can add new cookies', function() {
+ var cookie = createCookieSpec();
+
+ driver.manage().addCookie(cookie);
+ driver.manage().getCookie(cookie.name).then(function(actual) {
+ assert.equal(actual.value, cookie.value);
+ });
+ });
+
+ test.it('can get all cookies', function() {
+ var cookie1 = createCookieSpec();
+ var cookie2 = createCookieSpec();
+
+ driver.manage().addCookie(cookie1);
+ driver.manage().addCookie(cookie2);
+
+ assertHasCookies(cookie1, cookie2);
+ });
+
+ test.ignore(env.browsers(Browser.IE)).
+ it('only returns cookies visible to the current page', function() {
+ var cookie1 = createCookieSpec();
+
+ driver.manage().addCookie(cookie1);
+
+ var pageUrl = fileserver.whereIs('page/1');
+ var cookie2 = createCookieSpec({
+ path: url.parse(pageUrl).pathname
+ });
+ driver.get(pageUrl);
+ driver.manage().addCookie(cookie2);
+ assertHasCookies(cookie1, cookie2);
+
+ driver.get(fileserver.Pages.ajaxyPage);
+ assertHasCookies(cookie1);
+
+ driver.get(pageUrl);
+ assertHasCookies(cookie1, cookie2);
+ });
+
+ test.it('can delete all cookies', function() {
+ var cookie1 = createCookieSpec();
+ var cookie2 = createCookieSpec();
+
+ driver.executeScript(
+ 'document.cookie = arguments[0] + "=" + arguments[1];' +
+ 'document.cookie = arguments[2] + "=" + arguments[3];',
+ cookie1.name, cookie1.value, cookie2.name, cookie2.value);
+ assertHasCookies(cookie1, cookie2);
+
+ driver.manage().deleteAllCookies();
+ assertHasCookies();
+ });
+
+ test.it('can delete cookies by name', function() {
+ var cookie1 = createCookieSpec();
+ var cookie2 = createCookieSpec();
+
+ driver.executeScript(
+ 'document.cookie = arguments[0] + "=" + arguments[1];' +
+ 'document.cookie = arguments[2] + "=" + arguments[3];',
+ cookie1.name, cookie1.value, cookie2.name, cookie2.value);
+ assertHasCookies(cookie1, cookie2);
+
+ driver.manage().deleteCookie(cookie1.name);
+ assertHasCookies(cookie2);
+ });
+
+ test.it('should only delete cookie with exact name', function() {
+ var cookie1 = createCookieSpec();
+ var cookie2 = createCookieSpec();
+ var cookie3 = {name: cookie1.name + 'xx', value: cookie1.value};
+
+ driver.executeScript(
+ 'document.cookie = arguments[0] + "=" + arguments[1];' +
+ 'document.cookie = arguments[2] + "=" + arguments[3];' +
+ 'document.cookie = arguments[4] + "=" + arguments[5];',
+ cookie1.name, cookie1.value, cookie2.name, cookie2.value,
+ cookie3.name, cookie3.value);
+ assertHasCookies(cookie1, cookie2, cookie3);
+
+ driver.manage().deleteCookie(cookie1.name);
+ assertHasCookies(cookie2, cookie3);
+ });
+
+ test.it('can delete cookies set higher in the path', function() {
+ var cookie = createCookieSpec();
+ var childUrl = fileserver.whereIs('child/childPage.html');
+ var grandchildUrl = fileserver.whereIs(
+ 'child/grandchild/grandchildPage.html');
+
+ driver.get(childUrl);
+ driver.manage().addCookie(cookie);
+ assertHasCookies(cookie);
+
+ driver.get(grandchildUrl);
+ assertHasCookies(cookie);
+
+ driver.manage().deleteCookie(cookie.name);
+ assertHasCookies();
+
+ driver.get(childUrl);
+ assertHasCookies();
+ });
+
+ test.ignore(env.browsers(
+ Browser.ANDROID,
+ Browser.FIREFOX,
+ 'legacy-' + Browser.FIREFOX,
+ Browser.IE)).
+ it('should retain cookie expiry', function() {
+ let expirationDelay = 5 * 1000;
+ let expiry = new Date(Date.now() + expirationDelay);
+ let cookie = createCookieSpec({expiry});
+
+ driver.manage().addCookie(cookie);
+ driver.manage().getCookie(cookie.name).then(function(actual) {
+ assert.equal(actual.value, cookie.value);
+ // expiry times are exchanged in seconds since January 1, 1970 UTC.
+ assert.equal(actual.expiry, Math.floor(expiry.getTime() / 1000));
+ });
+
+ driver.sleep(expirationDelay);
+ assertHasCookies();
+ });
+ });
+
+ function createCookieSpec(opt_options) {
+ let spec = {
+ name: getRandomString(),
+ value: getRandomString()
+ };
+ if (opt_options) {
+ spec = Object.assign(spec, opt_options);
+ }
+ return spec;
+ }
+
+ function buildCookieMap(cookies) {
+ var map = {};
+ cookies.forEach(function(cookie) {
+ map[cookie.name] = cookie;
+ });
+ return map;
+ }
+
+ function assertHasCookies(var_args) {
+ var expected = Array.prototype.slice.call(arguments, 0);
+ driver.manage().getCookies().then(function(cookies) {
+ assert.equal(cookies.length, expected.length,
+ 'Wrong # of cookies.' +
+ '\n Expected: ' + JSON.stringify(expected) +
+ '\n Was : ' + JSON.stringify(cookies));
+
+ var map = buildCookieMap(cookies);
+ for (var i = 0; i < expected.length; ++i) {
+ assert.equal(expected[i].value, map[expected[i].name].value);
+ }
+ });
+ }
+
+ function getRandomString() {
+ var x = 1234567890;
+ return Math.floor(Math.random() * x).toString(36);
+ }
+});
diff --git a/node_modules/selenium-webdriver/test/element_finding_test.js b/node_modules/selenium-webdriver/test/element_finding_test.js
new file mode 100644
index 000000000..819e15655
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/element_finding_test.js
@@ -0,0 +1,410 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var fail = require('assert').fail;
+
+var Browser = require('..').Browser,
+ By = require('..').By,
+ error = require('..').error,
+ until = require('..').until,
+ promise = require('../lib/promise'),
+ test = require('../lib/test'),
+ assert = require('../testing/assert'),
+ Pages = test.Pages;
+
+
+test.suite(function(env) {
+ var browsers = env.browsers;
+
+ var driver;
+
+ test.before(function() {
+ driver = env.builder().build();
+ });
+
+ test.after(function() {
+ driver.quit();
+ });
+
+ describe('finding elements', function() {
+ test.it(
+ 'should work after loading multiple pages in a row',
+ function() {
+ driver.get(Pages.formPage);
+ driver.get(Pages.xhtmlTestPage);
+ driver.findElement(By.linkText('click me')).click();
+ driver.wait(until.titleIs('We Arrive Here'), 5000);
+ });
+
+ describe('By.id()', function() {
+ test.it('should work', function() {
+ driver.get(Pages.xhtmlTestPage);
+ driver.findElement(By.id('linkId')).click();
+ driver.wait(until.titleIs('We Arrive Here'), 5000);
+ });
+
+ test.it('should fail if ID not present on page', function() {
+ driver.get(Pages.formPage);
+ driver.findElement(By.id('nonExistantButton')).
+ then(fail, function(e) {
+ assert(e).instanceOf(error.NoSuchElementError);
+ });
+ });
+
+ test.it(
+ 'should find multiple elements by ID even though that is ' +
+ 'malformed HTML',
+ function() {
+ driver.get(Pages.nestedPage);
+ driver.findElements(By.id('2')).then(function(elements) {
+ assert(elements.length).equalTo(8);
+ });
+ });
+ });
+
+ describe('By.linkText()', function() {
+ test.it('should be able to click on link identified by text', function() {
+ driver.get(Pages.xhtmlTestPage);
+ driver.findElement(By.linkText('click me')).click();
+ driver.wait(until.titleIs('We Arrive Here'), 5000);
+ });
+
+ test.it(
+ 'should be able to find elements by partial link text',
+ function() {
+ driver.get(Pages.xhtmlTestPage);
+ driver.findElement(By.partialLinkText('ick me')).click();
+ driver.wait(until.titleIs('We Arrive Here'), 5000);
+ });
+
+ test.it('should work when link text contains equals sign', function() {
+ driver.get(Pages.xhtmlTestPage);
+ var id = driver.findElement(By.linkText('Link=equalssign')).
+ getAttribute('id');
+ assert(id).equalTo('linkWithEqualsSign');
+ });
+
+ test.it('matches by partial text when containing equals sign',
+ function() {
+ driver.get(Pages.xhtmlTestPage);
+ var id = driver.findElement(By.partialLinkText('Link=')).
+ getAttribute('id');
+ assert(id).equalTo('linkWithEqualsSign');
+ });
+
+ test.it('works when searching for multiple and text contains =',
+ function() {
+ driver.get(Pages.xhtmlTestPage);
+ driver.findElements(By.linkText('Link=equalssign')).
+ then(function(elements) {
+ assert(elements.length).equalTo(1);
+ return elements[0].getAttribute('id');
+ }).
+ then(function(id) {
+ assert(id).equalTo('linkWithEqualsSign');
+ });
+ });
+
+ test.it(
+ 'works when searching for multiple with partial text containing =',
+ function() {
+ driver.get(Pages.xhtmlTestPage);
+ driver.findElements(By.partialLinkText('Link=')).
+ then(function(elements) {
+ assert(elements.length).equalTo(1);
+ return elements[0].getAttribute('id');
+ }).
+ then(function(id) {
+ assert(id).equalTo('linkWithEqualsSign');
+ });
+ });
+
+ test.it('should be able to find multiple exact matches',
+ function() {
+ driver.get(Pages.xhtmlTestPage);
+ driver.findElements(By.linkText('click me')).
+ then(function(elements) {
+ assert(elements.length).equalTo(2);
+ });
+ });
+
+ test.it('should be able to find multiple partial matches',
+ function() {
+ driver.get(Pages.xhtmlTestPage);
+ driver.findElements(By.partialLinkText('ick me')).
+ then(function(elements) {
+ assert(elements.length).equalTo(2);
+ });
+ });
+
+ // See https://github.com/mozilla/geckodriver/issues/137
+ test.ignore(browsers(Browser.FIREFOX)).
+ it('works on XHTML pages', function() {
+ driver.get(test.whereIs('actualXhtmlPage.xhtml'));
+
+ var el = driver.findElement(By.linkText('Foo'));
+ assert(el.getText()).equalTo('Foo');
+ });
+ });
+
+ describe('By.name()', function() {
+ test.it('should work', function() {
+ driver.get(Pages.formPage);
+
+ var el = driver.findElement(By.name('checky'));
+ assert(el.getAttribute('value')).equalTo('furrfu');
+ });
+
+ test.it('should find multiple elements with same name', function() {
+ driver.get(Pages.nestedPage);
+ driver.findElements(By.name('checky')).then(function(elements) {
+ assert(elements.length).greaterThan(1);
+ });
+ });
+
+ test.it(
+ 'should be able to find elements that do not support name property',
+ function() {
+ driver.get(Pages.nestedPage);
+ driver.findElement(By.name('div1'));
+ // Pass if this does not return an error.
+ });
+
+ test.it('shoudl be able to find hidden elements by name', function() {
+ driver.get(Pages.formPage);
+ driver.findElement(By.name('hidden'));
+ // Pass if this does not return an error.
+ });
+ });
+
+ describe('By.className()', function() {
+ test.it('should work', function() {
+ driver.get(Pages.xhtmlTestPage);
+
+ var el = driver.findElement(By.className('extraDiv'));
+ assert(el.getText()).startsWith('Another div starts here.');
+ });
+
+ test.it('should work when name is first name among many', function() {
+ driver.get(Pages.xhtmlTestPage);
+
+ var el = driver.findElement(By.className('nameA'));
+ assert(el.getText()).equalTo('An H2 title');
+ });
+
+ test.it('should work when name is last name among many', function() {
+ driver.get(Pages.xhtmlTestPage);
+
+ var el = driver.findElement(By.className('nameC'));
+ assert(el.getText()).equalTo('An H2 title');
+ });
+
+ test.it('should work when name is middle of many', function() {
+ driver.get(Pages.xhtmlTestPage);
+
+ var el = driver.findElement(By.className('nameBnoise'));
+ assert(el.getText()).equalTo('An H2 title');
+ });
+
+ test.it('should work when name surrounded by whitespace', function() {
+ driver.get(Pages.xhtmlTestPage);
+
+ var el = driver.findElement(By.className('spaceAround'));
+ assert(el.getText()).equalTo('Spaced out');
+ });
+
+ test.it('should fail if queried name only partially matches', function() {
+ driver.get(Pages.xhtmlTestPage);
+ driver.findElement(By.className('nameB')).
+ then(fail, function(e) {
+ assert(e).instanceOf(error.NoSuchElementError);
+ });
+ });
+
+ test.it('should implicitly wait', function() {
+ var TIMEOUT_IN_MS = 1000;
+ var EPSILON = TIMEOUT_IN_MS / 2;
+
+ driver.manage().timeouts().implicitlyWait(TIMEOUT_IN_MS);
+ driver.get(Pages.formPage);
+
+ var start = new Date();
+ driver.findElement(By.id('nonExistantButton')).
+ then(fail, function(e) {
+ var end = new Date();
+ assert(e).instanceOf(error.NoSuchElementError);
+ assert(end - start).closeTo(TIMEOUT_IN_MS, EPSILON);
+ });
+ });
+
+ test.it('should be able to find multiple matches', function() {
+ driver.get(Pages.xhtmlTestPage);
+ driver.findElements(By.className('nameC')).then(function(elements) {
+ assert(elements.length).greaterThan(1);
+ });
+ });
+
+ test.it('permits compound class names', function() {
+ return driver.get(Pages.xhtmlTestPage)
+ .then(() => driver.findElement(By.className('nameA nameC')))
+ .then(el => el.getText())
+ .then(text => assert(text).equalTo('An H2 title'));
+ });
+ });
+
+ describe('By.xpath()', function() {
+ test.it('should work with multiple matches', function() {
+ driver.get(Pages.xhtmlTestPage);
+ driver.findElements(By.xpath('//div')).then(function(elements) {
+ assert(elements.length).greaterThan(1);
+ });
+ });
+
+ test.it('should work for selectors using contains keyword', function() {
+ driver.get(Pages.nestedPage);
+ driver.findElement(By.xpath('//a[contains(., "hello world")]'));
+ // Pass if no error.
+ });
+ });
+
+ describe('By.tagName()', function() {
+ test.it('works', function() {
+ driver.get(Pages.formPage);
+
+ var el = driver.findElement(By.tagName('input'));
+ assert(el.getTagName()).equalTo('input');
+ });
+
+ test.it('can find multiple elements', function() {
+ driver.get(Pages.formPage);
+ driver.findElements(By.tagName('input')).then(function(elements) {
+ assert(elements.length).greaterThan(1);
+ });
+ });
+ });
+
+ describe('By.css()', function() {
+ test.it('works', function() {
+ driver.get(Pages.xhtmlTestPage);
+ driver.findElement(By.css('div.content'));
+ // Pass if no error.
+ });
+
+ test.it('can find multiple elements', function() {
+ driver.get(Pages.xhtmlTestPage);
+ driver.findElements(By.css('p')).then(function(elements) {
+ assert(elements.length).greaterThan(1);
+ });
+ // Pass if no error.
+ });
+
+ test.it(
+ 'should find first matching element when searching by ' +
+ 'compound CSS selector',
+ function() {
+ driver.get(Pages.xhtmlTestPage);
+ var el = driver.findElement(By.css('div.extraDiv, div.content'));
+ assert(el.getAttribute('class')).equalTo('content');
+ });
+
+ test.it('should be able to find multiple elements by compound selector',
+ function() {
+ driver.get(Pages.xhtmlTestPage);
+ driver.findElements(By.css('div.extraDiv, div.content')).
+ then(function(elements) {
+ assertClassIs(elements[0], 'content');
+ assertClassIs(elements[1], 'extraDiv');
+
+ function assertClassIs(el, expected) {
+ assert(el.getAttribute('class')).equalTo(expected);
+ }
+ });
+ });
+
+ // IE only supports short version option[selected].
+ test.ignore(browsers(Browser.IE)).
+ it('should be able to find element by boolean attribute', function() {
+ driver.get(test.whereIs(
+ 'locators_tests/boolean_attribute_selected.html'));
+
+ var el = driver.findElement(By.css('option[selected="selected"]'));
+ assert(el.getAttribute('value')).equalTo('two');
+ });
+
+ test.it(
+ 'should be able to find element with short ' +
+ 'boolean attribute selector',
+ function() {
+ driver.get(test.whereIs(
+ 'locators_tests/boolean_attribute_selected.html'));
+
+ var el = driver.findElement(By.css('option[selected]'));
+ assert(el.getAttribute('value')).equalTo('two');
+ });
+
+ test.it(
+ 'should be able to find element with short boolean attribute ' +
+ 'selector on HTML4 page',
+ function() {
+ driver.get(test.whereIs(
+ 'locators_tests/boolean_attribute_selected_html4.html'));
+
+ var el = driver.findElement(By.css('option[selected]'));
+ assert(el.getAttribute('value')).equalTo('two');
+ });
+ });
+
+ describe('by custom locator', function() {
+ test.it('handles single element result', function() {
+ driver.get(Pages.javascriptPage);
+
+ let link = driver.findElement(function(driver) {
+ let links = driver.findElements(By.tagName('a'));
+ return promise.filter(links, function(link) {
+ return link.getAttribute('id').then(id => id === 'updatediv');
+ }).then(links => links[0]);
+ });
+
+ assert(link.getText()).isEqualTo('Update a div');
+ });
+
+ test.it('uses first element if locator resolves to list', function() {
+ driver.get(Pages.javascriptPage);
+
+ let link = driver.findElement(function() {
+ return driver.findElements(By.tagName('a'));
+ });
+
+ assert(link.getText()).isEqualTo('Change the page title!');
+ });
+
+ test.it('fails if locator returns non-webelement value', function() {
+ driver.get(Pages.javascriptPage);
+
+ let link = driver.findElement(function() {
+ return driver.getTitle();
+ });
+
+ return link.then(
+ () => fail('Should have failed'),
+ (e) => assert(e).instanceOf(TypeError));
+ });
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/execute_script_test.js b/node_modules/selenium-webdriver/test/execute_script_test.js
new file mode 100644
index 000000000..2ee150ed8
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/execute_script_test.js
@@ -0,0 +1,352 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var fail = require('assert').fail;
+
+var webdriver = require('..'),
+ Browser = webdriver.Browser,
+ By = webdriver.By,
+ assert = require('../testing/assert'),
+ test = require('../lib/test');
+
+
+test.suite(function(env) {
+ var driver;
+
+ test.before(function() {
+ driver = env.builder().build();
+ });
+
+ test.after(function() {
+ driver.quit();
+ });
+
+ test.beforeEach(function() {
+ driver.get(test.Pages.echoPage);
+ });
+
+ describe('executeScript;', function() {
+ var shouldHaveFailed = new Error('Should have failed');
+
+ test.it('fails if script throws', function() {
+ execute('throw new Error("boom")')
+ .then(function() { throw shouldHaveFailed; })
+ .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).*/);
+ });
+ });
+
+ test.it('fails if script does not parse', function() {
+ execute('throw function\\*')
+ .then(function() { throw shouldHaveFailed; })
+ .catch(function(e) {
+ assert(e).notEqualTo(shouldHaveFailed);
+ });
+ });
+
+ describe('scripts;', function() {
+ test.it('do not pollute the global scope', function() {
+ execute('var x = 1;');
+ assert(execute('return typeof x;')).equalTo('undefined');
+ });
+
+ test.it('can set global variables', function() {
+ execute('window.x = 1234;');
+ assert(execute('return x;')).equalTo(1234);
+ });
+
+ test.it('may be defined as a function expression', function() {
+ assert(execute(function() {
+ return 1234 + 'abc';
+ })).equalTo('1234abc');
+ });
+ });
+
+ describe('return values;', function() {
+
+ test.it('returns undefined as null', function() {
+ assert(execute('var x; return x;')).isNull();
+ });
+
+ test.it('can return null', function() {
+ assert(execute('return null;')).isNull();
+ });
+
+ test.it('can return numbers', function() {
+ assert(execute('return 1234')).equalTo(1234);
+ assert(execute('return 3.1456')).equalTo(3.1456);
+ });
+
+ test.it('can return strings', function() {
+ assert(execute('return "hello"')).equalTo('hello');
+ });
+
+ test.it('can return booleans', function() {
+ assert(execute('return true')).equalTo(true);
+ assert(execute('return false')).equalTo(false);
+ });
+
+ test.it('can return an array of primitives', function() {
+ execute('var x; return [1, false, null, 3.14, x]')
+ .then(verifyJson([1, false, null, 3.14, null]));
+ });
+
+ test.it('can return nested arrays', function() {
+ execute('return [[1, 2, [3]]]')
+ .then(verifyJson([[1, 2, [3]]]));
+ });
+
+ test.ignore(env.browsers(Browser.IE, Browser.SAFARI)).
+ it('can return empty object literal', function() {
+ execute('return {}').then(verifyJson({}));
+ });
+
+ test.it('can return object literals', function() {
+ execute('return {a: 1, b: false, c: null}').then(function(result) {
+ verifyJson(['a', 'b', 'c'])(Object.keys(result).sort());
+ assert(result.a).equalTo(1);
+ assert(result.b).equalTo(false);
+ assert(result.c).isNull();
+ });
+ });
+
+ test.it('can return complex object literals', function() {
+ execute('return {a:{b: "hello"}}').then(verifyJson({a:{b: 'hello'}}));
+ });
+
+ test.it('can return dom elements as web elements', function() {
+ execute('return document.querySelector(".header.host")')
+ .then(function(result) {
+ assert(result).instanceOf(webdriver.WebElement);
+ assert(result.getText()).startsWith('host: ');
+ });
+ });
+
+ test.it('can return array of dom elements', function() {
+ execute('var nodes = document.querySelectorAll(".request,.host");' +
+ 'return [nodes[0], nodes[1]];')
+ .then(function(result) {
+ assert(result.length).equalTo(2);
+
+ assert(result[0]).instanceOf(webdriver.WebElement);
+ assert(result[0].getText()).startsWith('GET ');
+
+ assert(result[1]).instanceOf(webdriver.WebElement);
+ assert(result[1].getText()).startsWith('host: ');
+ });
+ });
+
+ test.it('can return a NodeList as an array of web elements', function() {
+ execute('return document.querySelectorAll(".request,.host");')
+ .then(function(result) {
+ assert(result.length).equalTo(2);
+
+ assert(result[0]).instanceOf(webdriver.WebElement);
+ assert(result[0].getText()).startsWith('GET ');
+
+ assert(result[1]).instanceOf(webdriver.WebElement);
+ assert(result[1].getText()).startsWith('host: ');
+ });
+ });
+
+ test.it('can return object literal with element property', function() {
+ execute('return {a: document.body}').then(function(result) {
+ assert(result.a).instanceOf(webdriver.WebElement);
+ assert(result.a.getTagName()).equalTo('body');
+ });
+ });
+ });
+
+ describe('parameters;', function() {
+ test.it('can pass numeric arguments', function() {
+ assert(execute('return arguments[0]', 12)).equalTo(12);
+ assert(execute('return arguments[0]', 3.14)).equalTo(3.14);
+ });
+
+ test.it('can pass boolean arguments', function() {
+ assert(execute('return arguments[0]', true)).equalTo(true);
+ assert(execute('return arguments[0]', false)).equalTo(false);
+ });
+
+ test.it('can pass string arguments', function() {
+ assert(execute('return arguments[0]', 'hi')).equalTo('hi');
+ });
+
+ test.it('can pass null arguments', function() {
+ assert(execute('return arguments[0] === null', null)).equalTo(true);
+ assert(execute('return arguments[0]', null)).equalTo(null);
+ });
+
+ test.it('passes undefined as a null argument', function() {
+ var x;
+ assert(execute('return arguments[0] === null', x)).equalTo(true);
+ assert(execute('return arguments[0]', x)).equalTo(null);
+ });
+
+ test.it('can pass multiple arguments', function() {
+ assert(execute('return arguments.length')).equalTo(0);
+ assert(execute('return arguments.length', 1, 'a', false)).equalTo(3);
+ });
+
+ test.ignore(env.browsers(Browser.FIREFOX)).
+ it('can return arguments object as array', function() {
+ execute('return arguments', 1, 'a', false).then(function(val) {
+ assert(val.length).equalTo(3);
+ assert(val[0]).equalTo(1);
+ assert(val[1]).equalTo('a');
+ assert(val[2]).equalTo(false);
+ });
+ });
+
+ test.it('can pass object literal', function() {
+ execute(
+ 'return [typeof arguments[0], arguments[0].a]', {a: 'hello'})
+ .then(function(result) {
+ assert(result[0]).equalTo('object');
+ assert(result[1]).equalTo('hello');
+ });
+ });
+
+ test.it('WebElement arguments are passed as DOM elements', function() {
+ var el = driver.findElement(By.tagName('div'));
+ assert(execute('return arguments[0].tagName.toLowerCase();', el))
+ .equalTo('div');
+ });
+
+ test.it('can pass array containing object literals', function() {
+ execute('return arguments[0]', [{color: "red"}]).then(function(result) {
+ assert(result.length).equalTo(1);
+ assert(result[0].color).equalTo('red');
+ });
+ });
+
+ test.it('does not modify object literal parameters', function() {
+ var input = {color: 'red'};
+ execute('return arguments[0];', input).then(verifyJson(input));
+ });
+ });
+
+ // See https://code.google.com/p/selenium/issues/detail?id=8223.
+ describe('issue 8223;', function() {
+ describe('using for..in loops;', function() {
+ test.it('can return array built from for-loop index', function() {
+ execute(function() {
+ var ret = [];
+ for (var i = 0; i < 3; i++) {
+ ret.push(i);
+ }
+ return ret;
+ }).then(verifyJson[0, 1, 2]);
+ });
+
+ test.it('can copy input array contents', function() {
+ execute(function(input) {
+ var ret = [];
+ for (var i in input) {
+ ret.push(input[i]);
+ }
+ return ret;
+ }, ['fa', 'fe', 'fi']).then(verifyJson(['fa', 'fe', 'fi']));
+ });
+
+ test.it('can iterate over input object keys', function() {
+ execute(function(thing) {
+ var ret = [];
+ for (var w in thing.words) {
+ ret.push(thing.words[w].word);
+ }
+ return ret;
+ }, {words: [{word: 'fa'}, {word: 'fe'}, {word: 'fi'}]})
+ .then(verifyJson(['fa', 'fe', 'fi']));
+ });
+
+ describe('recursive functions;', function() {
+ test.it('can build array from input', function() {
+ var input = ['fa', 'fe', 'fi'];
+ execute(function(thearray) {
+ var ret = [];
+ function build_response(thearray, ret) {
+ ret.push(thearray.shift());
+ return (!thearray.length && ret
+ || build_response(thearray, ret));
+ }
+ return build_response(thearray, ret);
+ }, input).then(verifyJson(input));
+ });
+
+ test.it('can build array from elements in object', function() {
+ var input = {words: [{word: 'fa'}, {word: 'fe'}, {word: 'fi'}]};
+ execute(function(thing) {
+ var ret = [];
+ function build_response(thing, ret) {
+ var item = thing.words.shift();
+ ret.push(item.word);
+ return (!thing.words.length && ret
+ || build_response(thing, ret));
+ }
+ return build_response(thing, ret);
+ }, input).then(verifyJson(['fa', 'fe', 'fi']));
+ });
+ });
+ });
+ });
+
+ describe('async timeouts', function() {
+ var TIMEOUT_IN_MS = 200;
+ var ACCEPTABLE_WAIT = TIMEOUT_IN_MS / 10;
+ var TOO_LONG_WAIT = TIMEOUT_IN_MS * 10;
+
+ before(function() {
+ return driver.manage().timeouts().setScriptTimeout(TIMEOUT_IN_MS)
+ });
+
+ test.it('does not fail if script execute in time', function() {
+ return executeTimeOutScript(ACCEPTABLE_WAIT);
+ });
+
+ test.it('fails if script took too long', function() {
+ return executeTimeOutScript(TOO_LONG_WAIT)
+ .then(function() {
+ fail('it should have timed out');
+ }).catch(function(e) {
+ assert(e.name).equalTo('ScriptTimeoutError');
+ });
+ });
+
+ function executeTimeOutScript(sleepTime) {
+ return driver.executeAsyncScript(function(sleepTime) {
+ var callback = arguments[arguments.length - 1];
+ setTimeout(callback, sleepTime)
+ }, sleepTime);
+ }
+ })
+ });
+
+ function verifyJson(expected) {
+ return function(actual) {
+ assert(JSON.stringify(actual)).equalTo(JSON.stringify(expected));
+ };
+ }
+
+ function execute() {
+ return driver.executeScript.apply(driver, arguments);
+ }
+});
diff --git a/node_modules/selenium-webdriver/test/fingerprint_test.js b/node_modules/selenium-webdriver/test/fingerprint_test.js
new file mode 100644
index 000000000..ca36ce142
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/fingerprint_test.js
@@ -0,0 +1,57 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var assert = require('../testing/assert'),
+ test = require('../lib/test'),
+ Pages = test.Pages;
+
+
+test.suite(function(env) {
+ var browsers = env.browsers;
+
+ var driver;
+ test.before(function() {
+ driver = env.builder().build();
+ });
+
+ test.after(function() {
+ driver.quit();
+ });
+
+ describe('fingerprinting', function() {
+ test.it('it should fingerprint the navigator object', function() {
+ driver.get(Pages.simpleTestPage);
+ assert(driver.executeScript('return navigator.webdriver')).equalTo(true);
+ });
+
+ test.it('fingerprint must not be writable', function() {
+ driver.get(Pages.simpleTestPage);
+ assert(driver.executeScript(
+ 'navigator.webdriver = "ohai"; return navigator.webdriver'))
+ .equalTo(true);
+ });
+
+ test.it('leaves fingerprint on svg pages', function() {
+ driver.get(Pages.svgPage);
+ assert(driver.executeScript('return navigator.webdriver')).equalTo(true);
+ });
+ });
+
+// Currently only implemented in legacy firefox.
+}, {browsers: ['legacy-firefox']});
diff --git a/node_modules/selenium-webdriver/test/firefox/extension_test.js b/node_modules/selenium-webdriver/test/firefox/extension_test.js
new file mode 100644
index 000000000..50936f7cd
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/firefox/extension_test.js
@@ -0,0 +1,96 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var AdmZip = require('adm-zip'),
+ assert = require('assert'),
+ crypto = require('crypto'),
+ fs = require('fs'),
+ path = require('path');
+
+var extension = require('../../firefox/extension'),
+ io = require('../../io'),
+ it = require('../../testing').it;
+
+
+var JETPACK_EXTENSION = path.join(__dirname,
+ '../../lib/test/data/firefox/jetpack-sample.xpi');
+var NORMAL_EXTENSION = path.join(__dirname,
+ '../../lib/test/data/firefox/sample.xpi');
+
+var JETPACK_EXTENSION_ID = 'jid1-EaXX7k0wwiZR7w@jetpack';
+var NORMAL_EXTENSION_ID = 'sample@seleniumhq.org';
+
+
+describe('extension', function() {
+ it('can install a jetpack xpi file', function() {
+ return io.tmpDir().then(function(dir) {
+ return extension.install(JETPACK_EXTENSION, dir).then(function(id) {
+ assert.equal(JETPACK_EXTENSION_ID, id);
+ var file = path.join(dir, id + '.xpi');
+ assert.ok(fs.existsSync(file), 'no such file: ' + file);
+ assert.ok(!fs.statSync(file).isDirectory());
+
+ var copiedSha1 = crypto.createHash('sha1')
+ .update(fs.readFileSync(file))
+ .digest('hex');
+
+ var goldenSha1 = crypto.createHash('sha1')
+ .update(fs.readFileSync(JETPACK_EXTENSION))
+ .digest('hex');
+
+ assert.equal(copiedSha1, goldenSha1);
+ });
+ });
+ });
+
+ it('can install a normal xpi file', function() {
+ return io.tmpDir().then(function(dir) {
+ return extension.install(NORMAL_EXTENSION, dir).then(function(id) {
+ assert.equal(NORMAL_EXTENSION_ID, id);
+
+ var file = path.join(dir, NORMAL_EXTENSION_ID);
+ assert.ok(fs.statSync(file).isDirectory());
+
+ assert.ok(fs.existsSync(path.join(file, 'chrome.manifest')));
+ assert.ok(fs.existsSync(path.join(file, 'content/overlay.xul')));
+ assert.ok(fs.existsSync(path.join(file, 'content/overlay.js')));
+ assert.ok(fs.existsSync(path.join(file, 'install.rdf')));
+ });
+ });
+ });
+
+ it('can install an extension from a directory', function() {
+ return io.tmpDir().then(function(srcDir) {
+ var buf = fs.readFileSync(NORMAL_EXTENSION);
+ new AdmZip(buf).extractAllTo(srcDir, true);
+ return io.tmpDir().then(function(dstDir) {
+ return extension.install(srcDir, dstDir).then(function(id) {
+ assert.equal(NORMAL_EXTENSION_ID, id);
+
+ var dir = path.join(dstDir, NORMAL_EXTENSION_ID);
+
+ assert.ok(fs.existsSync(path.join(dir, 'chrome.manifest')));
+ assert.ok(fs.existsSync(path.join(dir, 'content/overlay.xul')));
+ assert.ok(fs.existsSync(path.join(dir, 'content/overlay.js')));
+ assert.ok(fs.existsSync(path.join(dir, 'install.rdf')));
+ });
+ });
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/firefox/firefox_test.js b/node_modules/selenium-webdriver/test/firefox/firefox_test.js
new file mode 100644
index 000000000..6f3f4b3d9
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/firefox/firefox_test.js
@@ -0,0 +1,188 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var path = require('path');
+
+var firefox = require('../../firefox'),
+ io = require('../../io'),
+ test = require('../../lib/test'),
+ assert = require('../../testing/assert'),
+ Context = require('../../firefox').Context,
+ error = require('../..').error;
+
+
+var JETPACK_EXTENSION = path.join(__dirname,
+ '../../lib/test/data/firefox/jetpack-sample.xpi');
+var NORMAL_EXTENSION = path.join(__dirname,
+ '../../lib/test/data/firefox/sample.xpi');
+
+
+test.suite(function(env) {
+ describe('firefox', function() {
+ describe('Options', function() {
+ var driver;
+
+ test.beforeEach(function() {
+ driver = null;
+ });
+
+ test.afterEach(function() {
+ if (driver) {
+ driver.quit();
+ }
+ });
+
+ test.it('can start Firefox with custom preferences', function() {
+ var profile = new firefox.Profile();
+ profile.setPreference('general.useragent.override', 'foo;bar');
+
+ var options = new firefox.Options().setProfile(profile);
+
+ driver = env.builder().
+ setFirefoxOptions(options).
+ build();
+
+ driver.get('data:text/html,<html><div>content</div></html>');
+
+ var userAgent = driver.executeScript(
+ 'return window.navigator.userAgent');
+ assert(userAgent).equalTo('foo;bar');
+ });
+
+ test.it('can start Firefox with a jetpack extension', function() {
+ var profile = new firefox.Profile();
+ profile.addExtension(JETPACK_EXTENSION);
+
+ var options = new firefox.Options().setProfile(profile);
+
+ driver = env.builder().
+ setFirefoxOptions(options).
+ build();
+
+ loadJetpackPage(driver,
+ 'data:text/html;charset=UTF-8,<html><div>content</div></html>');
+ assert(driver.findElement({id: 'jetpack-sample-banner'}).getText())
+ .equalTo('Hello, world!');
+ });
+
+ test.it('can start Firefox with a normal extension', function() {
+ var profile = new firefox.Profile();
+ profile.addExtension(NORMAL_EXTENSION);
+
+ var options = new firefox.Options().setProfile(profile);
+
+ driver = env.builder().
+ setFirefoxOptions(options).
+ build();
+
+ driver.get('data:text/html,<html><div>content</div></html>');
+ assert(driver.findElement({id: 'sample-extension-footer'}).getText())
+ .equalTo('Goodbye');
+ });
+
+ test.it('can start Firefox with multiple extensions', function() {
+ var profile = new firefox.Profile();
+ profile.addExtension(JETPACK_EXTENSION);
+ profile.addExtension(NORMAL_EXTENSION);
+
+ var options = new firefox.Options().setProfile(profile);
+
+ driver = env.builder().
+ setFirefoxOptions(options).
+ build();
+
+ loadJetpackPage(driver,
+ 'data:text/html;charset=UTF-8,<html><div>content</div></html>');
+ assert(driver.findElement({id: 'jetpack-sample-banner'}).getText())
+ .equalTo('Hello, world!');
+ assert(driver.findElement({id: 'sample-extension-footer'}).getText())
+ .equalTo('Goodbye');
+ });
+
+ function loadJetpackPage(driver, url) {
+ // On linux the jetpack extension does not always run the first time
+ // we load a page. If this happens, just reload the page (a simple
+ // refresh doesn't appear to work).
+ driver.wait(function() {
+ driver.get(url);
+ return driver.findElements({id: 'jetpack-sample-banner'})
+ .then(found => found.length > 0);
+ }, 3000);
+ }
+ });
+
+ describe('binary management', function() {
+ var driver1, driver2;
+
+ test.ignore(env.isRemote).
+ it('can start multiple sessions with single binary instance', function() {
+ var options = new firefox.Options().setBinary(new firefox.Binary);
+ env.builder().setFirefoxOptions(options);
+ driver1 = env.builder().build();
+ driver2 = env.builder().build();
+ // Ok if this doesn't fail.
+ });
+
+ test.afterEach(function() {
+ if (driver1) {
+ driver1.quit();
+ }
+
+ if (driver2) {
+ driver2.quit();
+ }
+ });
+ });
+
+ describe('context switching', function() {
+ var driver;
+
+ test.beforeEach(function() {
+ driver = env.builder().build();
+ });
+
+ test.afterEach(function() {
+ if (driver) {
+ driver.quit();
+ }
+ });
+
+ test.ignore(() => !env.isMarionette).
+ it('can get context', function() {
+ assert(driver.getContext()).equalTo(Context.CONTENT);
+ });
+
+ test.ignore(() => !env.isMarionette).
+ it('can set context', function() {
+ driver.setContext(Context.CHROME);
+ assert(driver.getContext()).equalTo(Context.CHROME);
+ driver.setContext(Context.CONTENT);
+ assert(driver.getContext()).equalTo(Context.CONTENT);
+ });
+
+ test.ignore(() => !env.isMarionette).
+ it('throws on unknown context', function() {
+ driver.setContext("foo").then(assert.fail, function(e) {
+ assert(e).instanceOf(error.InvalidArgumentError);
+ });
+ });
+ });
+
+ });
+}, {browsers: ['firefox']});
diff --git a/node_modules/selenium-webdriver/test/firefox/profile_test.js b/node_modules/selenium-webdriver/test/firefox/profile_test.js
new file mode 100644
index 000000000..807c07b72
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/firefox/profile_test.js
@@ -0,0 +1,186 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var AdmZip = require('adm-zip'),
+ assert = require('assert'),
+ fs = require('fs'),
+ path = require('path');
+
+var promise = require('../..').promise,
+ Profile = require('../../firefox/profile').Profile,
+ decode = require('../../firefox/profile').decode,
+ loadUserPrefs = require('../../firefox/profile').loadUserPrefs,
+ io = require('../../io');
+
+
+var JETPACK_EXTENSION = path.join(__dirname,
+ '../../lib/test/data/firefox/jetpack-sample.xpi');
+var NORMAL_EXTENSION = path.join(__dirname,
+ '../../lib/test/data/firefox/sample.xpi');
+
+var JETPACK_EXTENSION_ID = 'jid1-EaXX7k0wwiZR7w@jetpack.xpi';
+var NORMAL_EXTENSION_ID = 'sample@seleniumhq.org';
+var WEBDRIVER_EXTENSION_ID = 'fxdriver@googlecode.com';
+
+
+
+describe('Profile', function() {
+ describe('setPreference', function() {
+ it('allows setting custom properties', function() {
+ var profile = new Profile();
+ assert.equal(undefined, profile.getPreference('foo'));
+
+ profile.setPreference('foo', 'bar');
+ assert.equal('bar', profile.getPreference('foo'));
+ });
+
+ it('allows overriding mutable properties', function() {
+ var profile = new Profile();
+ assert.equal('about:blank', profile.getPreference('browser.newtab.url'));
+
+ profile.setPreference('browser.newtab.url', 'http://www.example.com');
+ assert.equal('http://www.example.com',
+ profile.getPreference('browser.newtab.url'));
+ });
+
+ it('throws if setting a frozen preference', function() {
+ var profile = new Profile();
+ assert.throws(function() {
+ profile.setPreference('app.update.auto', true);
+ });
+ });
+ });
+
+ describe('writeToDisk', function() {
+ it('copies template directory recursively', function() {
+ var templateDir;
+ return io.tmpDir().then(function(td) {
+ templateDir = td;
+ var foo = path.join(templateDir, 'foo');
+ fs.writeFileSync(foo, 'Hello, world');
+
+ var bar = path.join(templateDir, 'subfolder/bar');
+ fs.mkdirSync(path.dirname(bar));
+ fs.writeFileSync(bar, 'Goodbye, world!');
+
+ return new Profile(templateDir).writeToDisk();
+ }).then(function(profileDir) {
+ assert.notEqual(profileDir, templateDir);
+
+ assert.equal('Hello, world',
+ fs.readFileSync(path.join(profileDir, 'foo')));
+ assert.equal('Goodbye, world!',
+ fs.readFileSync(path.join(profileDir, 'subfolder/bar')));
+ });
+ });
+
+ it('does not copy lock files', function() {
+ return io.tmpDir().then(function(dir) {
+ fs.writeFileSync(path.join(dir, 'parent.lock'), 'lock');
+ fs.writeFileSync(path.join(dir, 'lock'), 'lock');
+ fs.writeFileSync(path.join(dir, '.parentlock'), 'lock');
+ return new Profile(dir).writeToDisk();
+ }).then(function(dir) {
+ assert.ok(fs.existsSync(dir));
+ assert.ok(!fs.existsSync(path.join(dir, 'parent.lock')));
+ assert.ok(!fs.existsSync(path.join(dir, 'lock')));
+ assert.ok(!fs.existsSync(path.join(dir, '.parentlock')));
+ });
+ });
+
+ describe('user.js', function() {
+
+ it('writes defaults', function() {
+ return new Profile().writeToDisk().then(function(dir) {
+ return loadUserPrefs(path.join(dir, 'user.js'));
+ }).then(function(prefs) {
+ // Just check a few.
+ assert.equal(false, prefs['app.update.auto']);
+ assert.equal(true, prefs['browser.EULA.override']);
+ assert.equal(false, prefs['extensions.update.enabled']);
+ assert.equal('about:blank', prefs['browser.newtab.url']);
+ assert.equal(30, prefs['dom.max_script_run_time']);
+ });
+ });
+
+ it('merges template user.js into preferences', function() {
+ return io.tmpDir().then(function(dir) {
+ fs.writeFileSync(path.join(dir, 'user.js'), [
+ 'user_pref("browser.newtab.url", "http://www.example.com")',
+ 'user_pref("dom.max_script_run_time", 1234)'
+ ].join('\n'));
+
+ return new Profile(dir).writeToDisk();
+ }).then(function(profile) {
+ return loadUserPrefs(path.join(profile, 'user.js'));
+ }).then(function(prefs) {
+ assert.equal('http://www.example.com', prefs['browser.newtab.url']);
+ assert.equal(1234, prefs['dom.max_script_run_time']);
+ });
+ });
+
+ it('ignores frozen preferences when merging template user.js',
+ function() {
+ return io.tmpDir().then(function(dir) {
+ fs.writeFileSync(path.join(dir, 'user.js'),
+ 'user_pref("app.update.auto", true)');
+ return new Profile(dir).writeToDisk();
+ }).then(function(profile) {
+ return loadUserPrefs(path.join(profile, 'user.js'));
+ }).then(function(prefs) {
+ assert.equal(false, prefs['app.update.auto']);
+ });
+ });
+ });
+
+ describe('extensions', function() {
+ it('are copied into new profile directory', function() {
+ var profile = new Profile();
+ profile.addExtension(JETPACK_EXTENSION);
+ profile.addExtension(NORMAL_EXTENSION);
+
+ return profile.writeToDisk().then(function(dir) {
+ dir = path.join(dir, 'extensions');
+ assert.ok(fs.existsSync(path.join(dir, JETPACK_EXTENSION_ID)));
+ assert.ok(fs.existsSync(path.join(dir, NORMAL_EXTENSION_ID)));
+ assert.ok(fs.existsSync(path.join(dir, WEBDRIVER_EXTENSION_ID)));
+ });
+ });
+ });
+ });
+
+ describe('encode', function() {
+ it('excludes the bundled WebDriver extension', function() {
+ return new Profile().encode().then(function(data) {
+ return decode(data);
+ }).then(function(dir) {
+ assert.ok(fs.existsSync(path.join(dir, 'user.js')));
+ assert.ok(fs.existsSync(path.join(dir, 'extensions')));
+ return loadUserPrefs(path.join(dir, 'user.js'));
+ }).then(function(prefs) {
+ // Just check a few.
+ assert.equal(false, prefs['app.update.auto']);
+ assert.equal(true, prefs['browser.EULA.override']);
+ assert.equal(false, prefs['extensions.update.enabled']);
+ assert.equal('about:blank', prefs['browser.newtab.url']);
+ assert.equal(30, prefs['dom.max_script_run_time']);
+ });
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/http/http_test.js b/node_modules/selenium-webdriver/test/http/http_test.js
new file mode 100644
index 000000000..6056cda8e
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/http/http_test.js
@@ -0,0 +1,205 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var assert = require('assert'),
+ http = require('http'),
+ url = require('url');
+
+var HttpClient = require('../../http').HttpClient,
+ HttpRequest = require('../../lib/http').Request,
+ HttpResponse = require('../../lib/http').Response,
+ Server = require('../../lib/test/httpserver').Server;
+
+describe('HttpClient', function() {
+ this.timeout(4 * 1000);
+
+ var server = new Server(function(req, res) {
+ let parsedUrl = url.parse(req.url);
+
+ if (req.method == 'GET' && req.url == '/echo') {
+ res.writeHead(200, req.headers);
+ res.end();
+
+ } else if (req.method == 'GET' && req.url == '/redirect') {
+ res.writeHead(303, {'Location': server.url('/hello')});
+ res.end();
+
+ } else if (req.method == 'GET' && req.url == '/hello') {
+ res.writeHead(200, {'content-type': 'text/plain'});
+ res.end('hello, world!');
+
+ } else if (req.method == 'GET' && req.url == '/badredirect') {
+ res.writeHead(303, {});
+ res.end();
+
+ } else if (req.method == 'GET' && req.url == '/protected') {
+ var denyAccess = function() {
+ res.writeHead(401, {'WWW-Authenticate': 'Basic realm="test"'});
+ res.end('Access denied');
+ };
+
+ var basicAuthRegExp = /^\s*basic\s+([a-z0-9\-\._~\+\/]+)=*\s*$/i
+ var auth = req.headers.authorization;
+ var match = basicAuthRegExp.exec(auth || '');
+ if (!match) {
+ denyAccess();
+ return;
+ }
+
+ var userNameAndPass = new Buffer(match[1], 'base64').toString();
+ var parts = userNameAndPass.split(':', 2);
+ if (parts[0] !== 'genie' && parts[1] !== 'bottle') {
+ denyAccess();
+ return;
+ }
+
+ res.writeHead(200, {'content-type': 'text/plain'});
+ res.end('Access granted!');
+
+ } else if (req.method == 'GET'
+ && parsedUrl.pathname
+ && parsedUrl.pathname.endsWith('/proxy')) {
+ let headers = Object.assign({}, req.headers);
+ headers['x-proxy-request-uri'] = req.url;
+ res.writeHead(200, headers);
+ res.end();
+
+ } else if (req.method == 'GET'
+ && parsedUrl.pathname
+ && parsedUrl.pathname.endsWith('/proxy/redirect')) {
+ let path = `/proxy${parsedUrl.search || ''}${parsedUrl.hash || ''}`;
+ res.writeHead(303, {'Location': path});
+ res.end();
+
+ } else {
+ res.writeHead(404, {});
+ res.end();
+ }
+ });
+
+ before(function() {
+ return server.start();
+ });
+
+ after(function() {
+ return server.stop();
+ });
+
+ it('can send a basic HTTP request', function() {
+ var request = new HttpRequest('GET', '/echo');
+ request.headers.set('Foo', 'Bar');
+
+ var agent = new http.Agent();
+ agent.maxSockets = 1; // Only making 1 request.
+
+ var client = new HttpClient(server.url(), agent);
+ return client.send(request).then(function(response) {
+ assert.equal(200, response.status);
+ assert.equal(response.headers.get('content-length'), '0');
+ assert.equal(response.headers.get('connection'), 'keep-alive');
+ assert.equal(response.headers.get('host'), server.host());
+
+ assert.equal(request.headers.get('Foo'), 'Bar');
+ assert.equal(
+ request.headers.get('Accept'), 'application/json; charset=utf-8');
+ });
+ });
+
+ it('can use basic auth', function() {
+ var parsed = url.parse(server.url());
+ parsed.auth = 'genie:bottle';
+
+ var client = new HttpClient(url.format(parsed));
+ var request = new HttpRequest('GET', '/protected');
+ return client.send(request).then(function(response) {
+ assert.equal(200, response.status);
+ assert.equal(response.headers.get('content-type'), 'text/plain');
+ assert.equal(response.body, 'Access granted!');
+ });
+ });
+
+ it('fails requests missing required basic auth', function() {
+ var client = new HttpClient(server.url());
+ var request = new HttpRequest('GET', '/protected');
+ return client.send(request).then(function(response) {
+ assert.equal(401, response.status);
+ assert.equal(response.body, 'Access denied');
+ });
+ });
+
+ it('automatically follows redirects', function() {
+ var request = new HttpRequest('GET', '/redirect');
+ var client = new HttpClient(server.url());
+ return client.send(request).then(function(response) {
+ assert.equal(200, response.status);
+ assert.equal(response.headers.get('content-type'), 'text/plain');
+ assert.equal(response.body, 'hello, world!');
+ });
+ });
+
+ it('handles malformed redirect responses', function() {
+ var request = new HttpRequest('GET', '/badredirect');
+ var client = new HttpClient(server.url());
+ return client.send(request).then(assert.fail, function(err) {
+ assert.ok(/Failed to parse "Location"/.test(err.message),
+ 'Not the expected error: ' + err.message);
+ });
+ });
+
+ describe('with proxy', function() {
+ it('sends request to proxy with absolute URI', function() {
+ var request = new HttpRequest('GET', '/proxy');
+ var client = new HttpClient(
+ 'http://another.server.com', undefined, server.url());
+ return client.send(request).then(function(response) {
+ assert.equal(200, response.status);
+ assert.equal(response.headers.get('host'), 'another.server.com');
+ assert.equal(
+ response.headers.get('x-proxy-request-uri'),
+ 'http://another.server.com/proxy');
+ });
+ });
+
+ it('uses proxy when following redirects', function() {
+ var request = new HttpRequest('GET', '/proxy/redirect');
+ var client = new HttpClient(
+ 'http://another.server.com', undefined, server.url());
+ return client.send(request).then(function(response) {
+ assert.equal(200, response.status);
+ assert.equal(response.headers.get('host'), 'another.server.com');
+ assert.equal(
+ response.headers.get('x-proxy-request-uri'),
+ 'http://another.server.com/proxy');
+ });
+ });
+
+ it('includes search and hash in redirect URI', function() {
+ var request = new HttpRequest('GET', '/proxy/redirect?foo#bar');
+ var client = new HttpClient(
+ 'http://another.server.com', undefined, server.url());
+ return client.send(request).then(function(response) {
+ assert.equal(200, response.status);
+ assert.equal(response.headers.get('host'), 'another.server.com');
+ assert.equal(
+ response.headers.get('x-proxy-request-uri'),
+ 'http://another.server.com/proxy?foo#bar');
+ });
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/http/util_test.js b/node_modules/selenium-webdriver/test/http/util_test.js
new file mode 100644
index 000000000..aa7a9158a
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/http/util_test.js
@@ -0,0 +1,184 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var assert = require('assert'),
+ http = require('http');
+
+var error = require('../../lib/error');
+var util = require('../../http/util');
+
+describe('selenium-webdriver/http/util', function() {
+
+ var server, baseUrl;
+
+ var status, value, responseCode;
+
+ function startServer(done) {
+ if (server) return done();
+
+ server = http.createServer(function(req, res) {
+ var data = JSON.stringify({status: status, value: value});
+ res.writeHead(responseCode, {
+ 'Content-Type': 'application/json; charset=utf-8',
+ 'Content-Length': Buffer.byteLength(data, 'utf8')
+ });
+ res.end(data);
+ });
+
+ server.listen(0, '127.0.0.1', function(e) {
+ if (e) return done(e);
+
+ var addr = server.address();
+ baseUrl = 'http://' + addr.address + ':' + addr.port;
+ done();
+ });
+ }
+
+ function killServer(done) {
+ if (!server) return done();
+ server.close(done);
+ server = null;
+ }
+
+ after(killServer);
+
+ beforeEach(function(done) {
+ status = 0;
+ value = 'abc123';
+ responseCode = 200;
+ startServer(done);
+ });
+
+ describe('#getStatus', function() {
+ it('should return value field on success', function() {
+ return util.getStatus(baseUrl).then(function(response) {
+ assert.equal('abc123', response);
+ });
+ });
+
+ it('should fail if response object is not success', function() {
+ status = 1;
+ return util.getStatus(baseUrl).then(function() {
+ throw Error('expected a failure');
+ }, function(err) {
+ assert.ok(err instanceof error.WebDriverError);
+ assert.equal(err.code, error.WebDriverError.code);
+ assert.equal(err.message, value);
+ });
+ });
+
+ it('should fail if the server is not listening', function(done) {
+ killServer(function(e) {
+ if(e) return done(e);
+
+ util.getStatus(baseUrl).then(function() {
+ done(Error('expected a failure'));
+ }, function() {
+ // Expected.
+ done();
+ });
+ });
+ });
+
+ it('should fail if HTTP status is not 200', function() {
+ status = 1;
+ responseCode = 404;
+ return util.getStatus(baseUrl).then(function() {
+ throw Error('expected a failure');
+ }, function(err) {
+ assert.ok(err instanceof error.WebDriverError);
+ assert.equal(err.code, error.WebDriverError.code);
+ assert.equal(err.message, value);
+ });
+ });
+ });
+
+ describe('#waitForServer', function() {
+ it('resolves when server is ready', function() {
+ status = 1;
+ setTimeout(function() { status = 0; }, 50);
+ return util.waitForServer(baseUrl, 100);
+ });
+
+ it('should fail if server does not become ready', function() {
+ status = 1;
+ return util.waitForServer(baseUrl, 50).
+ then(function() {throw Error('Expected to time out')},
+ function() {});
+ });
+
+ it('can cancel wait', function(done) {
+ status = 1;
+ var err = Error('cancelled!');
+ var isReady = util.waitForServer(baseUrl, 200).
+ then(function() { done('Did not expect to succeed'); }).
+ then(null, function(e) {
+ assert.equal('cancelled!', e.message);
+ }).
+ then(function() { done(); }, done);
+
+ setTimeout(function() {
+ isReady.cancel('cancelled!');
+ }, 50);
+ });
+ });
+
+ describe('#waitForUrl', function() {
+ it('succeeds when URL returns 2xx', function() {
+ responseCode = 404;
+ setTimeout(function() { responseCode = 200; }, 50);
+
+ return util.waitForUrl(baseUrl, 200);
+ });
+
+ it('fails if URL always returns 4xx', function() {
+ responseCode = 404;
+
+ return util.waitForUrl(baseUrl, 50)
+ .then(() => assert.fail('Expected to time out'),
+ () => true);
+ });
+
+ it('fails if cannot connect to server', function() {
+ return new Promise((resolve, reject) => {
+ killServer(function(e) {
+ if (e) return reject(e);
+
+ util.waitForUrl(baseUrl, 50).
+ then(function() { reject(Error('Expected to time out')); },
+ function() { resolve(); });
+ });
+ });
+ });
+
+ it('can cancel wait', function(done) {
+ responseCode = 404;
+ var isReady = util.waitForUrl(baseUrl, 200).
+ then(function() { done('Did not expect to succeed'); }).
+ then(null, function(e) {
+ assert.equal('cancelled!', e.message);
+ }).
+ then(function() { done(); }, done);
+
+ setTimeout(function() {
+ isReady.cancel('cancelled!');
+ }, 50);
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/io_test.js b/node_modules/selenium-webdriver/test/io_test.js
new file mode 100644
index 000000000..42ec8d3a3
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/io_test.js
@@ -0,0 +1,321 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var assert = require('assert'),
+ fs = require('fs'),
+ path = require('path'),
+ tmp = require('tmp');
+
+var io = require('../io');
+
+
+describe('io', function() {
+ describe('copy', function() {
+ var tmpDir;
+
+ before(function() {
+ return io.tmpDir().then(function(d) {
+ tmpDir = d;
+
+ fs.writeFileSync(path.join(d, 'foo'), 'Hello, world');
+ });
+ });
+
+ it('can copy one file to another', function() {
+ return io.tmpFile().then(function(f) {
+ return io.copy(path.join(tmpDir, 'foo'), f).then(function(p) {
+ assert.equal(p, f);
+ assert.equal('Hello, world', fs.readFileSync(p));
+ });
+ });
+ });
+
+ it('can copy symlink to destination', function() {
+ if (process.platform === 'win32') {
+ return; // No symlinks on windows.
+ }
+ fs.symlinkSync(
+ path.join(tmpDir, 'foo'),
+ path.join(tmpDir, 'symlinked-foo'));
+ return io.tmpFile().then(function(f) {
+ return io.copy(path.join(tmpDir, 'symlinked-foo'), f).then(function(p) {
+ assert.equal(p, f);
+ assert.equal('Hello, world', fs.readFileSync(p));
+ });
+ });
+ });
+
+ it('fails if given a directory as a source', function() {
+ return io.tmpFile().then(function(f) {
+ return io.copy(tmpDir, f);
+ }).then(function() {
+ throw Error('Should have failed with a type error');
+ }, function() {
+ // Do nothing; expected.
+ });
+ });
+ });
+
+ describe('copyDir', function() {
+ it('copies recursively', function() {
+ return io.tmpDir().then(function(dir) {
+ fs.writeFileSync(path.join(dir, 'file1'), 'hello');
+ fs.mkdirSync(path.join(dir, 'sub'));
+ fs.mkdirSync(path.join(dir, 'sub/folder'));
+ fs.writeFileSync(path.join(dir, 'sub/folder/file2'), 'goodbye');
+
+ return io.tmpDir().then(function(dst) {
+ return io.copyDir(dir, dst).then(function(ret) {
+ assert.equal(dst, ret);
+
+ assert.equal('hello',
+ fs.readFileSync(path.join(dst, 'file1')));
+ assert.equal('goodbye',
+ fs.readFileSync(path.join(dst, 'sub/folder/file2')));
+ });
+ });
+ });
+ });
+
+ it('creates destination dir if necessary', function() {
+ return io.tmpDir().then(function(srcDir) {
+ fs.writeFileSync(path.join(srcDir, 'foo'), 'hi');
+ return io.tmpDir().then(function(dstDir) {
+ return io.copyDir(srcDir, path.join(dstDir, 'sub'));
+ });
+ }).then(function(p) {
+ assert.equal('sub', path.basename(p));
+ assert.equal('hi', fs.readFileSync(path.join(p, 'foo')));
+ });
+ });
+
+ it('supports regex exclusion filter', function() {
+ return io.tmpDir().then(function(src) {
+ fs.writeFileSync(path.join(src, 'foo'), 'a');
+ fs.writeFileSync(path.join(src, 'bar'), 'b');
+ fs.writeFileSync(path.join(src, 'baz'), 'c');
+ fs.mkdirSync(path.join(src, 'sub'));
+ fs.writeFileSync(path.join(src, 'sub/quux'), 'd');
+ fs.writeFileSync(path.join(src, 'sub/quot'), 'e');
+
+ return io.tmpDir().then(function(dst) {
+ return io.copyDir(src, dst, /(bar|quux)/);
+ });
+ }).then(function(dir) {
+ assert.equal('a', fs.readFileSync(path.join(dir, 'foo')));
+ assert.equal('c', fs.readFileSync(path.join(dir, 'baz')));
+ assert.equal('e', fs.readFileSync(path.join(dir, 'sub/quot')));
+
+ assert.ok(!fs.existsSync(path.join(dir, 'bar')));
+ assert.ok(!fs.existsSync(path.join(dir, 'sub/quux')));
+ });
+ });
+
+ it('supports exclusion filter function', function() {
+ return io.tmpDir().then(function(src) {
+ fs.writeFileSync(path.join(src, 'foo'), 'a');
+ fs.writeFileSync(path.join(src, 'bar'), 'b');
+ fs.writeFileSync(path.join(src, 'baz'), 'c');
+ fs.mkdirSync(path.join(src, 'sub'));
+ fs.writeFileSync(path.join(src, 'sub/quux'), 'd');
+ fs.writeFileSync(path.join(src, 'sub/quot'), 'e');
+
+ return io.tmpDir().then(function(dst) {
+ return io.copyDir(src, dst, function(f) {
+ return f !== path.join(src, 'foo')
+ && f !== path.join(src, 'sub/quot');
+ });
+ });
+ }).then(function(dir) {
+ assert.equal('b', fs.readFileSync(path.join(dir, 'bar')));
+ assert.equal('c', fs.readFileSync(path.join(dir, 'baz')));
+ assert.equal('d', fs.readFileSync(path.join(dir, 'sub/quux')));
+
+ assert.ok(!fs.existsSync(path.join(dir, 'foo')));
+ assert.ok(!fs.existsSync(path.join(dir, 'sub/quot')));
+ });
+ });
+ });
+
+ describe('exists', function() {
+ var dir;
+
+ before(function() {
+ return io.tmpDir().then(function(d) {
+ dir = d;
+ });
+ });
+
+ it('returns a rejected promise if input value is invalid', function() {
+ return io.exists(undefined).then(
+ () => assert.fail('should have failed'),
+ e => assert.ok(e instanceof TypeError));
+ });
+
+ it('works for directories', function() {
+ return io.exists(dir).then(assert.ok);
+ });
+
+ it('works for files', function() {
+ var file = path.join(dir, 'foo');
+ fs.writeFileSync(file, '');
+ return io.exists(file).then(assert.ok);
+ });
+
+ it('does not return a rejected promise if file does not exist', function() {
+ return io.exists(path.join(dir, 'not-there')).then(function(exists) {
+ assert.ok(!exists);
+ });
+ });
+ });
+
+ describe('unlink', function() {
+ var dir;
+
+ before(function() {
+ return io.tmpDir().then(function(d) {
+ dir = d;
+ });
+ });
+
+ it('silently succeeds if the path does not exist', function() {
+ return io.unlink(path.join(dir, 'not-there'));
+ });
+
+ it('deletes files', function() {
+ var file = path.join(dir, 'foo');
+ fs.writeFileSync(file, '');
+ return io.exists(file).then(assert.ok).then(function() {
+ return io.unlink(file);
+ }).then(function() {
+ return io.exists(file);
+ }).then(function(exists) {
+ return assert.ok(!exists);
+ });
+ });
+ });
+
+ describe('rmDir', function() {
+ it('succeeds if the designated directory does not exist', function() {
+ return io.tmpDir().then(function(d) {
+ return io.rmDir(path.join(d, 'i/do/not/exist'));
+ });
+ });
+
+ it('deletes recursively', function() {
+ return io.tmpDir().then(function(dir) {
+ fs.writeFileSync(path.join(dir, 'file1'), 'hello');
+ fs.mkdirSync(path.join(dir, 'sub'));
+ fs.mkdirSync(path.join(dir, 'sub/folder'));
+ fs.writeFileSync(path.join(dir, 'sub/folder/file2'), 'goodbye');
+
+ return io.rmDir(dir).then(function() {
+ assert.ok(!fs.existsSync(dir));
+ assert.ok(!fs.existsSync(path.join(dir, 'sub/folder/file2')));
+ });
+ });
+ });
+ });
+
+ describe('findInPath', function() {
+ const savedPathEnv = process.env['PATH'];
+ afterEach(() => process.env['PATH'] = savedPathEnv);
+
+ const cwd = process.cwd;
+ afterEach(() => process.cwd = cwd);
+
+ let dirs;
+ beforeEach(() => {
+ return Promise.all([io.tmpDir(), io.tmpDir(), io.tmpDir()]).then(arr => {
+ dirs = arr;
+ process.env['PATH'] = arr.join(path.delimiter);
+ });
+ });
+
+ it('returns null if file cannot be found', () => {
+ assert.strictEqual(io.findInPath('foo.txt'), null);
+ });
+
+ it('can find file on path', () => {
+ let filePath = path.join(dirs[1], 'foo.txt');
+ fs.writeFileSync(filePath, 'hi');
+
+ assert.strictEqual(io.findInPath('foo.txt'), filePath);
+ });
+
+ it('returns null if file is in a subdir of a directory on the path', () => {
+ let subDir = path.join(dirs[2], 'sub');
+ fs.mkdirSync(subDir);
+
+ let filePath = path.join(subDir, 'foo.txt');
+ fs.writeFileSync(filePath, 'hi');
+
+ assert.strictEqual(io.findInPath('foo.txt'), null);
+ });
+
+ it('does not match on directories', () => {
+ fs.mkdirSync(path.join(dirs[2], 'sub'));
+ assert.strictEqual(io.findInPath('sub'), null);
+ });
+
+ it('will look in cwd first if requested', () => {
+ return io.tmpDir().then(fakeCwd => {
+ process.cwd = () => fakeCwd;
+
+ let theFile = path.join(fakeCwd, 'foo.txt');
+
+ fs.writeFileSync(path.join(dirs[1], 'foo.txt'), 'hi');
+ fs.writeFileSync(theFile, 'bye');
+
+ assert.strictEqual(io.findInPath('foo.txt', true), theFile);
+ });
+ });
+ });
+
+ describe('read', function() {
+ var tmpDir;
+
+ before(function() {
+ return io.tmpDir().then(function(d) {
+ tmpDir = d;
+
+ fs.writeFileSync(path.join(d, 'foo'), 'Hello, world');
+ });
+ });
+
+ it('can read a file', function() {
+ return io.read(path.join(tmpDir, 'foo')).then(buff => {
+ assert.ok(buff instanceof Buffer);
+ assert.equal('Hello, world', buff.toString());
+ });
+ });
+
+ it('catches errors from invalid input', function() {
+ return io.read(1234)
+ .then(() => assert.fail('should have failed'),
+ (e) => assert.ok(e instanceof TypeError));
+ });
+
+ it('rejects returned promise if file does not exist', function() {
+ return io.read(path.join(tmpDir, 'not-there'))
+ .then(() => assert.fail('should have failed'),
+ (e) => assert.equal('ENOENT', e.code));
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/lib/by_test.js b/node_modules/selenium-webdriver/test/lib/by_test.js
new file mode 100644
index 000000000..1669ec917
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/lib/by_test.js
@@ -0,0 +1,139 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var assert = require('assert');
+var by = require('../../lib/by');
+
+describe('by', function() {
+ describe('By', function() {
+ describe('className', function() {
+ it('delegates to By.css', function() {
+ let locator = by.By.className('foo');
+ assert.equal('css selector', locator.using);
+ assert.equal('.foo', locator.value);
+ });
+
+ it('escapes class name', function() {
+ let locator = by.By.className('foo#bar');
+ assert.equal('css selector', locator.using);
+ assert.equal('.foo\\#bar', locator.value);
+ });
+
+ it('translates compound class names', function() {
+ let locator = by.By.className('a b');
+ assert.equal('css selector', locator.using);
+ assert.equal('.a.b', locator.value);
+
+ locator = by.By.className(' x y z-1 "g" ');
+ assert.equal('css selector', locator.using);
+ assert.equal('.x.y.z-1.\\"g\\"', locator.value);
+ });
+ });
+
+ describe('id', function() {
+ it('delegates to By.css', function() {
+ let locator = by.By.id('foo');
+ assert.equal('css selector', locator.using);
+ assert.equal('*[id="foo"]', locator.value);
+ });
+
+ it('escapes the ID', function() {
+ let locator = by.By.id('foo#bar');
+ assert.equal('css selector', locator.using);
+ assert.equal('*[id="foo\\#bar"]', locator.value);
+ });
+ });
+
+ describe('name', function() {
+ it('delegates to By.css', function() {
+ let locator = by.By.name('foo')
+ assert.equal('css selector', locator.using);
+ assert.equal('*[name="foo"]', locator.value);
+ });
+
+ it('escapes the name', function() {
+ let locator = by.By.name('foo"bar"')
+ assert.equal('css selector', locator.using);
+ assert.equal('*[name="foo\\"bar\\""]', locator.value);
+ });
+
+ it('escapes the name when it starts with a number', function() {
+ let locator = by.By.name('123foo"bar"')
+ assert.equal('css selector', locator.using);
+ assert.equal('*[name="\\31 23foo\\"bar\\""]', locator.value);
+ });
+
+ it('escapes the name when it starts with a negative number', function() {
+ let locator = by.By.name('-123foo"bar"')
+ assert.equal('css selector', locator.using);
+ assert.equal('*[name="-\\31 23foo\\"bar\\""]', locator.value);
+ });
+ });
+ });
+
+ describe('checkedLocator', function() {
+ it('accepts class name', function() {
+ let locator = by.checkedLocator({className: 'foo'});
+ assert.equal('css selector', locator.using);
+ assert.equal('.foo', locator.value);
+ });
+
+ it('accepts css', function() {
+ let locator = by.checkedLocator({css: 'a > b'});
+ assert.equal('css selector', locator.using);
+ assert.equal('a > b', locator.value);
+ });
+
+ it('accepts id', function() {
+ let locator = by.checkedLocator({id: 'foobar'});
+ assert.equal('css selector', locator.using);
+ assert.equal('*[id="foobar"]', locator.value);
+ });
+
+ it('accepts linkText', function() {
+ let locator = by.checkedLocator({linkText: 'hello'});
+ assert.equal('link text', locator.using);
+ assert.equal('hello', locator.value);
+ });
+
+ it('accepts name', function() {
+ let locator = by.checkedLocator({name: 'foobar'});
+ assert.equal('css selector', locator.using);
+ assert.equal('*[name="foobar"]', locator.value);
+ });
+
+ it('accepts partialLinkText', function() {
+ let locator = by.checkedLocator({partialLinkText: 'hello'});
+ assert.equal('partial link text', locator.using);
+ assert.equal('hello', locator.value);
+ });
+
+ it('accepts tagName', function() {
+ let locator = by.checkedLocator({tagName: 'div'});
+ assert.equal('css selector', locator.using);
+ assert.equal('div', locator.value);
+ });
+
+ it('accepts xpath', function() {
+ let locator = by.checkedLocator({xpath: '//div[1]'});
+ assert.equal('xpath', locator.using);
+ assert.equal('//div[1]', locator.value);
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/lib/capabilities_test.js b/node_modules/selenium-webdriver/test/lib/capabilities_test.js
new file mode 100644
index 000000000..0d0c67c9a
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/lib/capabilities_test.js
@@ -0,0 +1,111 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+const Capabilities = require('../../lib/capabilities').Capabilities;
+const Symbols = require('../../lib/symbols');
+
+const assert = require('assert');
+
+describe('Capabilities', function() {
+ it('can set and unset a capability', function() {
+ let caps = new Capabilities();
+ assert.equal(undefined, caps.get('foo'));
+ assert.ok(!caps.has('foo'));
+
+ caps.set('foo', 'bar');
+ assert.equal('bar', caps.get('foo'));
+ assert.ok(caps.has('foo'));
+
+ caps.set('foo', null);
+ assert.equal(null, caps.get('foo'));
+ assert.ok(caps.has('foo'));
+ });
+
+ it('requires string capability keys', function() {
+ let caps = new Capabilities();
+ assert.throws(() => caps.set({}, 'hi'));
+ });
+
+ it('can merge capabilities', function() {
+ let caps1 = new Capabilities()
+ .set('foo', 'bar')
+ .set('color', 'red');
+
+ let caps2 = new Capabilities()
+ .set('color', 'green');
+
+ assert.equal('bar', caps1.get('foo'));
+ assert.equal('red', caps1.get('color'));
+ assert.equal('green', caps2.get('color'));
+ assert.equal(undefined, caps2.get('foo'));
+
+ caps2.merge(caps1);
+ assert.equal('bar', caps1.get('foo'));
+ assert.equal('red', caps1.get('color'));
+ assert.equal('red', caps2.get('color'));
+ assert.equal('bar', caps2.get('foo'));
+ });
+
+ it('can be initialized from a hash object', function() {
+ let caps = new Capabilities({'one': 123, 'abc': 'def'});
+ assert.equal(123, caps.get('one'));
+ assert.equal('def', caps.get('abc'));
+ });
+
+ it('can be initialized from a map', function() {
+ let m = new Map([['one', 123], ['abc', 'def']]);
+
+ let caps = new Capabilities(m);
+ assert.equal(123, caps.get('one'));
+ assert.equal('def', caps.get('abc'));
+ });
+
+ describe('serialize', function() {
+ it('works for simple capabilities', function() {
+ let m = new Map([['one', 123], ['abc', 'def']]);
+ let caps = new Capabilities(m);
+ assert.deepEqual({one: 123, abc: 'def'}, caps[Symbols.serialize]());
+ });
+
+ it('does not omit capabilities set to a false-like value', function() {
+ let caps = new Capabilities;
+ caps.set('bool', false);
+ caps.set('number', 0);
+ caps.set('string', '');
+
+ assert.deepEqual(
+ {bool: false, number: 0, string: ''},
+ caps[Symbols.serialize]());
+ });
+
+ it('omits capabilities with a null value', function() {
+ let caps = new Capabilities;
+ caps.set('foo', null);
+ caps.set('bar', 123);
+ assert.deepEqual({bar: 123}, caps[Symbols.serialize]());
+ });
+
+ it('omits capabilities with an undefined value', function() {
+ let caps = new Capabilities;
+ caps.set('foo', undefined);
+ caps.set('bar', 123);
+ assert.deepEqual({bar: 123}, caps[Symbols.serialize]());
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/lib/error_test.js b/node_modules/selenium-webdriver/test/lib/error_test.js
new file mode 100644
index 000000000..a9fc87193
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/lib/error_test.js
@@ -0,0 +1,286 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+describe('error', function() {
+ let assert = require('assert');
+ let error = require('../../lib/error');
+
+ describe('checkResponse', function() {
+ it('defaults to WebDriverError if type is unrecognized', function() {
+ assert.throws(
+ () => error.checkResponse({error: 'foo', message: 'hi there'}),
+ (e) => {
+ assert.equal(e.constructor, error.WebDriverError);
+ return true;
+ });
+ });
+
+ it('does not throw if error property is not a string', function() {
+ let resp = {error: 123, message: 'abc123'};
+ let out = error.checkResponse(resp);
+ assert.strictEqual(out, resp);
+ });
+
+ test('unknown error', error.WebDriverError);
+ test('element not selectable', error.ElementNotSelectableError);
+ test('element not visible', error.ElementNotVisibleError);
+ test('invalid argument', error.InvalidArgumentError);
+ test('invalid cookie domain', error.InvalidCookieDomainError);
+ test('invalid element coordinates', error.InvalidElementCoordinatesError);
+ test('invalid element state', error.InvalidElementStateError);
+ test('invalid selector', error.InvalidSelectorError);
+ test('invalid session id', error.NoSuchSessionError);
+ test('javascript error', error.JavascriptError);
+ test('move target out of bounds', error.MoveTargetOutOfBoundsError);
+ test('no such alert', error.NoSuchAlertError);
+ test('no such element', error.NoSuchElementError);
+ test('no such frame', error.NoSuchFrameError);
+ test('no such window', error.NoSuchWindowError);
+ test('script timeout', error.ScriptTimeoutError);
+ test('session not created', error.SessionNotCreatedError);
+ test('stale element reference', error.StaleElementReferenceError);
+ test('timeout', error.TimeoutError);
+ test('unable to set cookie', error.UnableToSetCookieError);
+ test('unable to capture screen', error.UnableToCaptureScreenError);
+ test('unexpected alert open', error.UnexpectedAlertOpenError);
+ test('unknown command', error.UnknownCommandError);
+ test('unknown method', error.UnknownMethodError);
+ test('unsupported operation', error.UnsupportedOperationError);
+
+ function test(status, expectedType) {
+ it(`"${status}" => ${expectedType.name}`, function() {
+ assert.throws(
+ () => error.checkResponse({error: status, message: 'oops'}),
+ (e) => {
+ assert.equal(expectedType, e.constructor);
+ assert.equal(e.message, 'oops');
+ return true;
+ });
+ });
+ }
+ });
+
+ describe('encodeError', function() {
+ describe('defaults to an unknown error', function() {
+ it('for a generic error value', function() {
+ runTest('hi', 'unknown error', 'hi');
+ runTest(1, 'unknown error', '1');
+ runTest({}, 'unknown error', '[object Object]');
+ });
+
+ it('for a generic Error object', function() {
+ runTest(Error('oops'), 'unknown error', 'oops');
+ runTest(TypeError('bad value'), 'unknown error', 'bad value');
+ });
+ });
+
+ test(error.WebDriverError, 'unknown error');
+ test(error.ElementNotSelectableError, 'element not selectable');
+ test(error.ElementNotVisibleError, 'element not visible');
+ test(error.InvalidArgumentError, 'invalid argument');
+ test(error.InvalidCookieDomainError, 'invalid cookie domain');
+ test(error.InvalidElementStateError, 'invalid element state');
+ test(error.InvalidSelectorError, 'invalid selector');
+ test(error.NoSuchSessionError, 'invalid session id');
+ test(error.JavascriptError, 'javascript error');
+ test(error.MoveTargetOutOfBoundsError, 'move target out of bounds');
+ test(error.NoSuchAlertError, 'no such alert');
+ test(error.NoSuchElementError, 'no such element');
+ test(error.NoSuchFrameError, 'no such frame');
+ test(error.NoSuchWindowError, 'no such window');
+ test(error.ScriptTimeoutError, 'script timeout');
+ test(error.SessionNotCreatedError, 'session not created');
+ test(error.StaleElementReferenceError, 'stale element reference');
+ test(error.TimeoutError, 'timeout');
+ test(error.UnableToSetCookieError, 'unable to set cookie');
+ test(error.UnableToCaptureScreenError, 'unable to capture screen');
+ test(error.UnexpectedAlertOpenError, 'unexpected alert open');
+ test(error.UnknownCommandError, 'unknown command');
+ test(error.UnknownMethodError, 'unknown method');
+ test(error.UnsupportedOperationError, 'unsupported operation');
+
+ function test(ctor, code) {
+ it(`${ctor.name} => "${code}"`, () => {
+ runTest(new ctor('oops'), code, 'oops');
+ });
+ }
+
+ function runTest(err, code, message) {
+ let obj = error.encodeError(err);
+ assert.strictEqual(obj['error'], code);
+ assert.strictEqual(obj['message'], message);
+ }
+ });
+
+ describe('throwDecodedError', function() {
+ it('defaults to WebDriverError if type is unrecognized', function() {
+ assert.throws(
+ () => error.throwDecodedError({error: 'foo', message: 'hi there'}),
+ (e) => {
+ assert.equal(e.constructor, error.WebDriverError);
+ return true;
+ });
+ });
+
+ it('throws generic error if encoded data is not valid', function() {
+ assert.throws(
+ () => error.throwDecodedError({error: 123, message: 'abc123'}),
+ (e) => {
+ assert.strictEqual(e.constructor, error.WebDriverError);
+ return true;
+ });
+
+ assert.throws(
+ () => error.throwDecodedError('null'),
+ (e) => {
+ assert.strictEqual(e.constructor, error.WebDriverError);
+ return true;
+ });
+
+ assert.throws(
+ () => error.throwDecodedError(''),
+ (e) => {
+ assert.strictEqual(e.constructor, error.WebDriverError);
+ return true;
+ });
+ });
+
+ test('unknown error', error.WebDriverError);
+ test('element not selectable', error.ElementNotSelectableError);
+ test('element not visible', error.ElementNotVisibleError);
+ test('invalid argument', error.InvalidArgumentError);
+ test('invalid cookie domain', error.InvalidCookieDomainError);
+ test('invalid element coordinates', error.InvalidElementCoordinatesError);
+ test('invalid element state', error.InvalidElementStateError);
+ test('invalid selector', error.InvalidSelectorError);
+ test('invalid session id', error.NoSuchSessionError);
+ test('javascript error', error.JavascriptError);
+ test('move target out of bounds', error.MoveTargetOutOfBoundsError);
+ test('no such alert', error.NoSuchAlertError);
+ test('no such element', error.NoSuchElementError);
+ test('no such frame', error.NoSuchFrameError);
+ test('no such window', error.NoSuchWindowError);
+ test('script timeout', error.ScriptTimeoutError);
+ test('session not created', error.SessionNotCreatedError);
+ test('stale element reference', error.StaleElementReferenceError);
+ test('timeout', error.TimeoutError);
+ test('unable to set cookie', error.UnableToSetCookieError);
+ test('unable to capture screen', error.UnableToCaptureScreenError);
+ test('unexpected alert open', error.UnexpectedAlertOpenError);
+ test('unknown command', error.UnknownCommandError);
+ test('unknown method', error.UnknownMethodError);
+ test('unsupported operation', error.UnsupportedOperationError);
+
+ function test(status, expectedType) {
+ it(`"${status}" => ${expectedType.name}`, function() {
+ assert.throws(
+ () => error.throwDecodedError({error: status, message: 'oops'}),
+ (e) => {
+ assert.strictEqual(e.constructor, expectedType);
+ assert.strictEqual(e.message, 'oops');
+ return true;
+ });
+ });
+ }
+ });
+
+ describe('checkLegacyResponse', function() {
+ it('does not throw for success', function() {
+ let resp = {status: error.ErrorCode.SUCCESS};
+ assert.strictEqual(resp, error.checkLegacyResponse(resp));
+ });
+
+ test('NO_SUCH_ELEMENT', error.NoSuchElementError);
+ test('NO_SUCH_FRAME', error.NoSuchFrameError);
+ test('UNKNOWN_COMMAND', error.UnsupportedOperationError);
+ test('UNSUPPORTED_OPERATION', error.UnsupportedOperationError);
+ test('STALE_ELEMENT_REFERENCE', error.StaleElementReferenceError);
+ test('ELEMENT_NOT_VISIBLE', error.ElementNotVisibleError);
+ test('INVALID_ELEMENT_STATE', error.InvalidElementStateError);
+ test('UNKNOWN_ERROR', error.WebDriverError);
+ test('ELEMENT_NOT_SELECTABLE', error.ElementNotSelectableError);
+ test('JAVASCRIPT_ERROR', error.JavascriptError);
+ test('XPATH_LOOKUP_ERROR', error.InvalidSelectorError);
+ test('TIMEOUT', error.TimeoutError);
+ test('NO_SUCH_WINDOW', error.NoSuchWindowError);
+ test('INVALID_COOKIE_DOMAIN', error.InvalidCookieDomainError);
+ test('UNABLE_TO_SET_COOKIE', error.UnableToSetCookieError);
+ test('UNEXPECTED_ALERT_OPEN', error.UnexpectedAlertOpenError);
+ test('NO_SUCH_ALERT', error.NoSuchAlertError);
+ test('SCRIPT_TIMEOUT', error.ScriptTimeoutError);
+ test('INVALID_ELEMENT_COORDINATES', error.InvalidElementCoordinatesError);
+ test('INVALID_SELECTOR_ERROR', error.InvalidSelectorError);
+ test('SESSION_NOT_CREATED', error.SessionNotCreatedError);
+ test('MOVE_TARGET_OUT_OF_BOUNDS', error.MoveTargetOutOfBoundsError);
+ test('INVALID_XPATH_SELECTOR', error.InvalidSelectorError);
+ test('INVALID_XPATH_SELECTOR_RETURN_TYPE', error.InvalidSelectorError);
+ test('METHOD_NOT_ALLOWED', error.UnsupportedOperationError);
+
+ describe('UnexpectedAlertOpenError', function() {
+ it('includes alert text from the response object', function() {
+ let response = {
+ status: error.ErrorCode.UNEXPECTED_ALERT_OPEN,
+ value: {
+ message: 'hi',
+ alert: {text: 'alert text here'}
+ }
+ };
+ assert.throws(
+ () => error.checkLegacyResponse(response),
+ (e) => {
+ assert.equal(error.UnexpectedAlertOpenError, e.constructor);
+ assert.equal(e.message, 'hi');
+ assert.equal(e.getAlertText(), 'alert text here');
+ return true;
+ });
+ });
+
+ it('uses an empty string if alert text omitted', function() {
+ let response = {
+ status: error.ErrorCode.UNEXPECTED_ALERT_OPEN,
+ value: {
+ message: 'hi'
+ }
+ };
+ assert.throws(
+ () => error.checkLegacyResponse(response),
+ (e) => {
+ assert.equal(error.UnexpectedAlertOpenError, e.constructor);
+ assert.equal(e.message, 'hi');
+ assert.equal(e.getAlertText(), '');
+ return true;
+ });
+ });
+ });
+
+ function test(codeKey, expectedType) {
+ it(`${codeKey} => ${expectedType.name}`, function() {
+ let code = error.ErrorCode[codeKey];
+ let resp = {status: code, value: {message: 'hi'}};
+ assert.throws(
+ () => error.checkLegacyResponse(resp),
+ (e) => {
+ assert.equal(expectedType, e.constructor);
+ assert.equal(e.message, 'hi');
+ return true;
+ });
+ });
+ }
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/lib/events_test.js b/node_modules/selenium-webdriver/test/lib/events_test.js
new file mode 100644
index 000000000..a02da9747
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/lib/events_test.js
@@ -0,0 +1,177 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+const EventEmitter = require('../../lib/events').EventEmitter;
+
+const assert = require('assert');
+const sinon = require('sinon');
+
+describe('EventEmitter', function() {
+ describe('#emit()', function() {
+ it('can emit events when nothing is registered', function() {
+ let emitter = new EventEmitter;
+ emitter.emit('foo');
+ // Ok if no errors are thrown.
+ });
+
+ it('can pass args to listeners on emit', function() {
+ let emitter = new EventEmitter;
+ let now = Date.now();
+
+ let messages = [];
+ emitter.on('foo', (arg) => messages.push(arg));
+
+ emitter.emit('foo', now);
+ assert.deepEqual([now], messages);
+
+ emitter.emit('foo', now + 15);
+ assert.deepEqual([now, now + 15], messages);
+ });
+ });
+
+ describe('#addListener()', function() {
+ it('can add multiple listeners for one event', function() {
+ let emitter = new EventEmitter;
+ let count = 0;
+ emitter.addListener('foo', () => count++);
+ emitter.addListener('foo', () => count++);
+ emitter.addListener('foo', () => count++);
+ emitter.emit('foo');
+ assert.equal(3, count);
+ });
+
+ it('only registers each listener function once', function() {
+ let emitter = new EventEmitter;
+ let count = 0;
+ let onFoo = () => count++;
+ emitter.addListener('foo', onFoo);
+ emitter.addListener('foo', onFoo);
+ emitter.addListener('foo', onFoo);
+
+ emitter.emit('foo');
+ assert.equal(1, count);
+
+ emitter.emit('foo');
+ assert.equal(2, count);
+ });
+
+ it('allows users to specify a custom scope', function() {
+ let obj = {
+ count: 0,
+ inc: function() {
+ this.count++;
+ }
+ };
+ let emitter = new EventEmitter;
+ emitter.addListener('foo', obj.inc, obj);
+
+ emitter.emit('foo');
+ assert.equal(1, obj.count);
+
+ emitter.emit('foo');
+ assert.equal(2, obj.count);
+ });
+ });
+
+ describe('#once()', function() {
+ it('only calls registered callback once', function() {
+ let emitter = new EventEmitter;
+ let count = 0;
+ emitter.once('foo', () => count++);
+ emitter.once('foo', () => count++);
+ emitter.once('foo', () => count++);
+
+ emitter.emit('foo');
+ assert.equal(3, count);
+
+ emitter.emit('foo');
+ assert.equal(3, count);
+
+ emitter.emit('foo');
+ assert.equal(3, count);
+ });
+ });
+
+ describe('#removeListeners()', function() {
+ it('only removes the given listener function', function() {
+ let emitter = new EventEmitter;
+ let count = 0;
+ emitter.addListener('foo', () => count++);
+ emitter.addListener('foo', () => count++);
+
+ let toRemove = () => count++;
+ emitter.addListener('foo', toRemove);
+
+ emitter.emit('foo');
+ assert.equal(3, count);
+
+ emitter.removeListener('foo', toRemove);
+ emitter.emit('foo');
+ assert.equal(5, count);
+ });
+ });
+
+ describe('#removeAllListeners()', function() {
+ it('only removes listeners for type if specified', function() {
+ let emitter = new EventEmitter;
+ let count = 0;
+ emitter.addListener('foo', () => count++);
+ emitter.addListener('foo', () => count++);
+ emitter.addListener('foo', () => count++);
+ emitter.addListener('bar', () => count++);
+
+ emitter.emit('foo');
+ assert.equal(3, count);
+
+ emitter.removeAllListeners('foo');
+
+ emitter.emit('foo');
+ assert.equal(3, count);
+
+ emitter.emit('bar');
+ assert.equal(4, count);
+ });
+
+ it('removes absolutely all listeners if no type specified', function() {
+ let emitter = new EventEmitter;
+ let count = 0;
+ emitter.addListener('foo', () => count++);
+ emitter.addListener('bar', () => count++);
+ emitter.addListener('baz', () => count++);
+ emitter.addListener('baz', () => count++);
+
+ emitter.emit('foo');
+ assert.equal(1, count);
+
+ emitter.emit('baz');
+ assert.equal(3, count);
+
+ emitter.removeAllListeners();
+
+ emitter.emit('foo');
+ assert.equal(3, count);
+
+ emitter.emit('bar');
+ assert.equal(3, count);
+
+ emitter.emit('baz');
+ assert.equal(3, count);
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/lib/http_test.js b/node_modules/selenium-webdriver/test/lib/http_test.js
new file mode 100644
index 000000000..343a04800
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/lib/http_test.js
@@ -0,0 +1,655 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var assert = require('assert'),
+ sinon = require('sinon');
+
+var Capabilities = require('../../lib/capabilities').Capabilities,
+ Command = require('../../lib/command').Command,
+ CommandName = require('../../lib/command').Name,
+ error = require('../../lib/error'),
+ http = require('../../lib/http'),
+ Session = require('../../lib/session').Session,
+ promise = require('../../lib/promise'),
+ WebElement = require('../../lib/webdriver').WebElement;
+
+describe('http', function() {
+ describe('buildPath', function() {
+ it('properly replaces path segments with command parameters', function() {
+ var parameters = {'sessionId':'foo', 'url':'http://www.google.com'};
+ var finalPath = http.buildPath('/session/:sessionId/url', parameters);
+ assert.equal(finalPath, '/session/foo/url');
+ assert.deepEqual(parameters, {'url':'http://www.google.com'});
+ });
+
+ it('handles web element references', function() {
+ var parameters = {'sessionId':'foo', 'id': WebElement.buildId('bar')};
+
+ var finalPath = http.buildPath(
+ '/session/:sessionId/element/:id/click', parameters);
+ assert.equal(finalPath, '/session/foo/element/bar/click');
+ assert.deepEqual(parameters, {});
+ });
+
+ it('throws if missing a parameter', function() {
+ assert.throws(
+ () => http.buildPath('/session/:sessionId', {}),
+ function(err) {
+ return err instanceof error.InvalidArgumentError
+ && 'Missing required parameter: sessionId' === err.message;
+ });
+
+ assert.throws(
+ () => http.buildPath(
+ '/session/:sessionId/element/:id', {'sessionId': 'foo'}),
+ function(err) {
+ return err instanceof error.InvalidArgumentError
+ && 'Missing required parameter: id' === err.message;
+ });
+ });
+
+ it('does not match on segments that do not start with a colon', function() {
+ assert.equal(
+ http.buildPath('/session/foo:bar/baz', {}),
+ '/session/foo:bar/baz');
+ });
+ });
+
+ describe('Executor', function() {
+ let executor;
+ let client;
+ let send;
+
+ beforeEach(function setUp() {
+ client = new http.Client;
+ send = sinon.stub(client, 'send');
+ executor = new http.Executor(client);
+ });
+
+ describe('command routing', function() {
+ it('rejects unrecognized commands', function() {
+ assert.throws(
+ () => executor.execute(new Command('fake-name')),
+ function (err) {
+ return err instanceof error.UnknownCommandError
+ && 'Unrecognized command: fake-name' === err.message;
+ });
+ });
+
+ it('rejects promise if client fails to send request', function() {
+ let error = new Error('boom');
+ send.returns(Promise.reject(error));
+ return assertFailsToSend(new Command(CommandName.NEW_SESSION))
+ .then(function(e) {
+ assert.strictEqual(error, e);
+ assertSent(
+ 'POST', '/session', {},
+ [['Accept', 'application/json; charset=utf-8']]);
+ });
+ });
+
+ it('can execute commands with no URL parameters', function() {
+ var resp = JSON.stringify({sessionId: 'abc123'});
+ send.returns(Promise.resolve(new http.Response(200, {}, resp)));
+
+ let command = new Command(CommandName.NEW_SESSION);
+ return assertSendsSuccessfully(command).then(function(response) {
+ assertSent(
+ 'POST', '/session', {},
+ [['Accept', 'application/json; charset=utf-8']]);
+ });
+ });
+
+ it('rejects commands missing URL parameters', function() {
+ let command =
+ new Command(CommandName.FIND_CHILD_ELEMENT).
+ setParameter('sessionId', 's123').
+ // Let this be missing: setParameter('id', {'ELEMENT': 'e456'}).
+ setParameter('using', 'id').
+ setParameter('value', 'foo');
+
+ assert.throws(
+ () => executor.execute(command),
+ function(err) {
+ return err instanceof error.InvalidArgumentError
+ && 'Missing required parameter: id' === err.message;
+ });
+ assert.ok(!send.called);
+ });
+
+ it('replaces URL parameters with command parameters', function() {
+ var command = new Command(CommandName.GET).
+ setParameter('sessionId', 's123').
+ setParameter('url', 'http://www.google.com');
+
+ send.returns(Promise.resolve(new http.Response(200, {}, '')));
+
+ return assertSendsSuccessfully(command).then(function(response) {
+ assertSent(
+ 'POST', '/session/s123/url', {'url': 'http://www.google.com'},
+ [['Accept', 'application/json; charset=utf-8']]);
+ });
+ });
+
+ describe('uses correct URL', function() {
+ beforeEach(() => executor = new http.Executor(client));
+
+ describe('in legacy mode', function() {
+ test(CommandName.GET_WINDOW_SIZE, {sessionId:'s123'}, false,
+ 'GET', '/session/s123/window/current/size');
+
+ test(CommandName.SET_WINDOW_SIZE,
+ {sessionId:'s123', width: 1, height: 1}, false,
+ 'POST', '/session/s123/window/current/size',
+ {width: 1, height: 1});
+
+ test(CommandName.MAXIMIZE_WINDOW, {sessionId:'s123'}, false,
+ 'POST', '/session/s123/window/current/maximize');
+
+ // This is consistent b/w legacy and W3C, just making sure.
+ test(CommandName.GET,
+ {sessionId:'s123', url: 'http://www.example.com'}, false,
+ 'POST', '/session/s123/url', {url: 'http://www.example.com'});
+ });
+
+ describe('in W3C mode', function() {
+ test(CommandName.GET_WINDOW_SIZE,
+ {sessionId:'s123'}, true,
+ 'GET', '/session/s123/window/size');
+
+ test(CommandName.SET_WINDOW_SIZE,
+ {sessionId:'s123', width: 1, height: 1}, true,
+ 'POST', '/session/s123/window/size', {width: 1, height: 1});
+
+ test(CommandName.MAXIMIZE_WINDOW, {sessionId:'s123'}, true,
+ 'POST', '/session/s123/window/maximize');
+
+ // This is consistent b/w legacy and W3C, just making sure.
+ test(CommandName.GET,
+ {sessionId:'s123', url: 'http://www.example.com'}, true,
+ 'POST', '/session/s123/url', {url: 'http://www.example.com'});
+ });
+
+ function test(command, parameters, w3c,
+ expectedMethod, expectedUrl, opt_expectedParams) {
+ it(`command=${command}`, function() {
+ var resp = JSON.stringify({sessionId: 'abc123'});
+ send.returns(Promise.resolve(new http.Response(200, {}, resp)));
+
+ let cmd = new Command(command).setParameters(parameters);
+ executor.w3c = w3c;
+ return executor.execute(cmd).then(function() {
+ assertSent(
+ expectedMethod, expectedUrl, opt_expectedParams || {},
+ [['Accept', 'application/json; charset=utf-8']]);
+ });
+ });
+ }
+ });
+ });
+
+ describe('response parsing', function() {
+ it('extracts value from JSON response', function() {
+ var responseObj = {
+ 'status': error.ErrorCode.SUCCESS,
+ 'value': 'http://www.google.com'
+ };
+
+ var command = new Command(CommandName.GET_CURRENT_URL)
+ .setParameter('sessionId', 's123');
+
+ send.returns(Promise.resolve(
+ new http.Response(200, {}, JSON.stringify(responseObj))));
+
+ return executor.execute(command).then(function(response) {
+ assertSent('GET', '/session/s123/url', {},
+ [['Accept', 'application/json; charset=utf-8']]);
+ assert.strictEqual(response, 'http://www.google.com');
+ });
+ });
+
+ describe('extracts Session from NEW_SESSION response', function() {
+ beforeEach(() => executor = new http.Executor(client));
+
+ const command = new Command(CommandName.NEW_SESSION);
+
+ describe('fails if server returns invalid response', function() {
+ describe('(empty response)', function() {
+ test(true);
+ test(false);
+
+ function test(w3c) {
+ it('w3c === ' + w3c, function() {
+ send.returns(Promise.resolve(new http.Response(200, {}, '')));
+ executor.w3c = w3c;
+ return executor.execute(command).then(
+ () => assert.fail('expected to fail'),
+ (e) => {
+ if (!e.message.startsWith('Unable to parse')) {
+ throw e;
+ }
+ });
+ });
+ }
+ });
+
+ describe('(no session ID)', function() {
+ test(true);
+ test(false);
+
+ function test(w3c) {
+ it('w3c === ' + w3c, function() {
+ let resp = {value:{name: 'Bob'}};
+ send.returns(Promise.resolve(
+ new http.Response(200, {}, JSON.stringify(resp))));
+ executor.w3c = w3c;
+ return executor.execute(command).then(
+ () => assert.fail('expected to fail'),
+ (e) => {
+ if (!e.message.startsWith('Unable to parse')) {
+ throw e;
+ }
+ });
+ });
+ }
+ });
+ });
+
+ it('handles legacy response', function() {
+ var rawResponse = {sessionId: 's123', status: 0, value: {name: 'Bob'}};
+
+ send.returns(Promise.resolve(
+ new http.Response(200, {}, JSON.stringify(rawResponse))));
+
+ assert.ok(!executor.w3c);
+ return executor.execute(command).then(function(response) {
+ assert.ok(response instanceof Session);
+ assert.equal(response.getId(), 's123');
+
+ let caps = response.getCapabilities();
+ assert.ok(caps instanceof Capabilities);
+ assert.equal(caps.get('name'), 'Bob');
+
+ assert.ok(!executor.w3c);
+ });
+ });
+
+ it('auto-upgrades on W3C response', function() {
+ var rawResponse = {sessionId: 's123', value: {name: 'Bob'}};
+
+ send.returns(Promise.resolve(
+ new http.Response(200, {}, JSON.stringify(rawResponse))));
+
+ assert.ok(!executor.w3c);
+ return executor.execute(command).then(function(response) {
+ assert.ok(response instanceof Session);
+ assert.equal(response.getId(), 's123');
+
+ let caps = response.getCapabilities();
+ assert.ok(caps instanceof Capabilities);
+ assert.equal(caps.get('name'), 'Bob');
+
+ assert.ok(executor.w3c);
+ });
+ });
+
+ it('if w3c, does not downgrade on legacy response', function() {
+ var rawResponse = {sessionId: 's123', status: 0, value: null};
+
+ send.returns(Promise.resolve(
+ new http.Response(200, {}, JSON.stringify(rawResponse))));
+
+ executor.w3c = true;
+ return executor.execute(command).then(function(response) {
+ assert.ok(response instanceof Session);
+ assert.equal(response.getId(), 's123');
+ assert.equal(response.getCapabilities().size, 0);
+ assert.ok(executor.w3c, 'should never downgrade');
+ });
+ });
+ });
+
+ describe('extracts Session from DESCRIBE_SESSION response', function() {
+ let command;
+
+ beforeEach(function() {
+ executor = new http.Executor(client);
+ command = new Command(CommandName.DESCRIBE_SESSION)
+ .setParameter('sessionId', 'foo');
+ });
+
+ describe('fails if server returns invalid response', function() {
+ describe('(empty response)', function() {
+ test(true);
+ test(false);
+
+ function test(w3c) {
+ it('w3c === ' + w3c, function() {
+ send.returns(Promise.resolve(new http.Response(200, {}, '')));
+ executor.w3c = w3c;
+ return executor.execute(command).then(
+ () => assert.fail('expected to fail'),
+ (e) => {
+ if (!e.message.startsWith('Unable to parse')) {
+ throw e;
+ }
+ });
+ });
+ }
+ });
+
+ describe('(no session ID)', function() {
+ test(true);
+ test(false);
+
+ function test(w3c) {
+ it('w3c === ' + w3c, function() {
+ let resp = {value:{name: 'Bob'}};
+ send.returns(Promise.resolve(
+ new http.Response(200, {}, JSON.stringify(resp))));
+ executor.w3c = w3c;
+ return executor.execute(command).then(
+ () => assert.fail('expected to fail'),
+ (e) => {
+ if (!e.message.startsWith('Unable to parse')) {
+ throw e;
+ }
+ });
+ });
+ }
+ });
+ });
+
+ it('handles legacy response', function() {
+ var rawResponse = {sessionId: 's123', status: 0, value: {name: 'Bob'}};
+
+ send.returns(Promise.resolve(
+ new http.Response(200, {}, JSON.stringify(rawResponse))));
+
+ assert.ok(!executor.w3c);
+ return executor.execute(command).then(function(response) {
+ assert.ok(response instanceof Session);
+ assert.equal(response.getId(), 's123');
+
+ let caps = response.getCapabilities();
+ assert.ok(caps instanceof Capabilities);
+ assert.equal(caps.get('name'), 'Bob');
+
+ assert.ok(!executor.w3c);
+ });
+ });
+
+ it('does not auto-upgrade on W3C response', function() {
+ var rawResponse = {sessionId: 's123', value: {name: 'Bob'}};
+
+ send.returns(Promise.resolve(
+ new http.Response(200, {}, JSON.stringify(rawResponse))));
+
+ assert.ok(!executor.w3c);
+ return executor.execute(command).then(function(response) {
+ assert.ok(response instanceof Session);
+ assert.equal(response.getId(), 's123');
+
+ let caps = response.getCapabilities();
+ assert.ok(caps instanceof Capabilities);
+ assert.equal(caps.get('name'), 'Bob');
+
+ assert.ok(!executor.w3c);
+ });
+ });
+
+ it('if w3c, does not downgrade on legacy response', function() {
+ var rawResponse = {sessionId: 's123', status: 0, value: null};
+
+ send.returns(Promise.resolve(
+ new http.Response(200, {}, JSON.stringify(rawResponse))));
+
+ executor.w3c = true;
+ return executor.execute(command).then(function(response) {
+ assert.ok(response instanceof Session);
+ assert.equal(response.getId(), 's123');
+ assert.equal(response.getCapabilities().size, 0);
+ assert.ok(executor.w3c, 'should never downgrade');
+ });
+ });
+ });
+
+ it('handles JSON null', function() {
+ var command = new Command(CommandName.GET_CURRENT_URL)
+ .setParameter('sessionId', 's123');
+
+ send.returns(Promise.resolve(new http.Response(200, {}, 'null')));
+
+ return executor.execute(command).then(function(response) {
+ assertSent('GET', '/session/s123/url', {},
+ [['Accept', 'application/json; charset=utf-8']]);
+ assert.strictEqual(response, null);
+ });
+ });
+
+ describe('falsy values', function() {
+ test(0);
+ test(false);
+ test('');
+
+ function test(value) {
+ it(`value=${value}`, function() {
+ var command = new Command(CommandName.GET_CURRENT_URL)
+ .setParameter('sessionId', 's123');
+
+ send.returns(Promise.resolve(
+ new http.Response(200, {},
+ JSON.stringify({status: 0, value: value}))));
+
+ return executor.execute(command).then(function(response) {
+ assertSent('GET', '/session/s123/url', {},
+ [['Accept', 'application/json; charset=utf-8']]);
+ assert.strictEqual(response, value);
+ });
+ });
+ }
+ });
+
+ it('handles non-object JSON', function() {
+ var command = new Command(CommandName.GET_CURRENT_URL)
+ .setParameter('sessionId', 's123');
+
+ send.returns(Promise.resolve(new http.Response(200, {}, '123')));
+
+ return executor.execute(command).then(function(response) {
+ assertSent('GET', '/session/s123/url', {},
+ [['Accept', 'application/json; charset=utf-8']]);
+ assert.strictEqual(response, 123);
+ });
+ });
+
+ it('returns body text when 2xx but not JSON', function() {
+ var command = new Command(CommandName.GET_CURRENT_URL)
+ .setParameter('sessionId', 's123');
+
+ send.returns(Promise.resolve(
+ new http.Response(200, {}, 'hello, world\r\ngoodbye, world!')));
+
+ return executor.execute(command).then(function(response) {
+ assertSent('GET', '/session/s123/url', {},
+ [['Accept', 'application/json; charset=utf-8']]);
+ assert.strictEqual(response, 'hello, world\ngoodbye, world!');
+ });
+ });
+
+ it('returns body text when 2xx but invalid JSON', function() {
+ var command = new Command(CommandName.GET_CURRENT_URL)
+ .setParameter('sessionId', 's123');
+
+ send.returns(Promise.resolve(
+ new http.Response(200, {}, '[')));
+
+ return executor.execute(command).then(function(response) {
+ assertSent('GET', '/session/s123/url', {},
+ [['Accept', 'application/json; charset=utf-8']]);
+ assert.strictEqual(response, '[');
+ });
+ });
+
+ it('returns null if no body text and 2xx', function() {
+ var command = new Command(CommandName.GET_CURRENT_URL)
+ .setParameter('sessionId', 's123');
+
+ send.returns(Promise.resolve(new http.Response(200, {}, '')));
+
+ return executor.execute(command).then(function(response) {
+ assertSent('GET', '/session/s123/url', {},
+ [['Accept', 'application/json; charset=utf-8']]);
+ assert.strictEqual(response, null);
+ });
+ });
+
+ it('returns normalized body text when 2xx but not JSON', function() {
+ var command = new Command(CommandName.GET_CURRENT_URL)
+ .setParameter('sessionId', 's123');
+
+ send.returns(Promise.resolve(new http.Response(200, {}, '\r\n\n\n\r\n')));
+
+ return executor.execute(command).then(function(response) {
+ assertSent('GET', '/session/s123/url', {},
+ [['Accept', 'application/json; charset=utf-8']]);
+ assert.strictEqual(response, '\n\n\n\n');
+ });
+ });
+
+ it('throws UnsupportedOperationError for 404 and body not JSON',
+ function() {
+ var command = new Command(CommandName.GET_CURRENT_URL)
+ .setParameter('sessionId', 's123');
+
+ send.returns(Promise.resolve(
+ new http.Response(404, {}, 'hello, world\r\ngoodbye, world!')));
+
+ return executor.execute(command)
+ .then(
+ () => assert.fail('should have failed'),
+ checkError(
+ error.UnsupportedOperationError,
+ 'hello, world\ngoodbye, world!'));
+ });
+
+ it('throws WebDriverError for generic 4xx when body not JSON',
+ function() {
+ var command = new Command(CommandName.GET_CURRENT_URL)
+ .setParameter('sessionId', 's123');
+
+ send.returns(Promise.resolve(
+ new http.Response(500, {}, 'hello, world\r\ngoodbye, world!')));
+
+ return executor.execute(command)
+ .then(
+ () => assert.fail('should have failed'),
+ checkError(
+ error.WebDriverError,
+ 'hello, world\ngoodbye, world!'))
+ .then(function() {
+ assertSent('GET', '/session/s123/url', {},
+ [['Accept', 'application/json; charset=utf-8']]);
+ });
+ });
+ });
+
+ it('canDefineNewCommands', function() {
+ executor.defineCommand('greet', 'GET', '/person/:name');
+
+ var command = new Command('greet').
+ setParameter('name', 'Bob');
+
+ send.returns(Promise.resolve(new http.Response(200, {}, '')));
+
+ return assertSendsSuccessfully(command).then(function(response) {
+ assertSent('GET', '/person/Bob', {},
+ [['Accept', 'application/json; charset=utf-8']]);
+ });
+ });
+
+ it('canRedefineStandardCommands', function() {
+ executor.defineCommand(CommandName.GO_BACK, 'POST', '/custom/back');
+
+ var command = new Command(CommandName.GO_BACK).
+ setParameter('times', 3);
+
+ send.returns(Promise.resolve(new http.Response(200, {}, '')));
+
+ return assertSendsSuccessfully(command).then(function(response) {
+ assertSent('POST', '/custom/back', {'times': 3},
+ [['Accept', 'application/json; charset=utf-8']]);
+ });
+ });
+
+ it('accepts promised http clients', function() {
+ executor = new http.Executor(Promise.resolve(client));
+
+ var resp = JSON.stringify({sessionId: 'abc123'});
+ send.returns(Promise.resolve(new http.Response(200, {}, resp)));
+
+ let command = new Command(CommandName.NEW_SESSION);
+ return executor.execute(command).then(response => {
+ assertSent(
+ 'POST', '/session', {},
+ [['Accept', 'application/json; charset=utf-8']]);
+ });
+ });
+
+ function entries(map) {
+ let entries = [];
+ for (let e of map.entries()) {
+ entries.push(e);
+ }
+ return entries;
+ }
+
+ function checkError(type, message) {
+ return function(e) {
+ if (e instanceof type) {
+ assert.strictEqual(e.message, message);
+ } else {
+ throw e;
+ }
+ };
+ }
+
+ function assertSent(method, path, data, headers) {
+ assert.ok(send.calledWith(sinon.match(function(value) {
+ assert.equal(value.method, method);
+ assert.equal(value.path, path);
+ assert.deepEqual(value.data, data);
+ assert.deepEqual(entries(value.headers), headers);
+ return true;
+ })));
+ }
+
+ function assertSendsSuccessfully(command) {
+ return executor.execute(command).then(function(response) {
+ return response;
+ });
+ }
+
+ function assertFailsToSend(command, opt_onError) {
+ return executor.execute(command).then(
+ () => {throw Error('should have failed')},
+ (e) => {return e});
+ }
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/lib/logging_test.js b/node_modules/selenium-webdriver/test/lib/logging_test.js
new file mode 100644
index 000000000..29d2af40f
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/lib/logging_test.js
@@ -0,0 +1,272 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+const assert = require('assert');
+const sinon = require('sinon');
+const logging = require('../../lib/logging');
+
+describe('logging', function() {
+ let mgr, root, clock;
+
+ beforeEach(function setUp() {
+ mgr = new logging.LogManager;
+ root = mgr.getLogger('');
+
+ clock = sinon.useFakeTimers();
+ });
+
+ afterEach(function tearDown() {
+ clock.restore();
+ });
+
+ describe('LogManager', function() {
+ describe('getLogger()', function() {
+ it('handles falsey input', function() {
+ assert.strictEqual(root, mgr.getLogger());
+ assert.strictEqual(root, mgr.getLogger(''));
+ assert.strictEqual(root, mgr.getLogger(null));
+ assert.strictEqual(root, mgr.getLogger(0));
+ });
+
+ it('creates parent loggers', function() {
+ let logger = mgr.getLogger('foo.bar.baz');
+ assert.strictEqual(logger.parent_, mgr.getLogger('foo.bar'));
+
+ logger = logger.parent_;
+ assert.strictEqual(logger.parent_, mgr.getLogger('foo'));
+
+ logger = logger.parent_;
+ assert.strictEqual(logger.parent_, mgr.getLogger(''));
+
+ assert.strictEqual(logger.parent_.parent_, null);
+ });
+ });
+ });
+
+ describe('Logger', function() {
+ describe('getEffectiveLevel()', function() {
+ it('defaults to OFF', function() {
+ assert.strictEqual(root.getLevel(), logging.Level.OFF);
+ assert.strictEqual(root.getEffectiveLevel(), logging.Level.OFF);
+
+ root.setLevel(null);
+ assert.strictEqual(root.getLevel(), null);
+ assert.strictEqual(root.getEffectiveLevel(), logging.Level.OFF);
+ });
+
+ it('uses own level if set', function() {
+ let logger = mgr.getLogger('foo.bar.baz');
+ assert.strictEqual(logger.getLevel(), null);
+ assert.strictEqual(logger.getEffectiveLevel(), logging.Level.OFF);
+
+ logger.setLevel(logging.Level.INFO);
+ assert.strictEqual(logger.getLevel(), logging.Level.INFO);
+ assert.strictEqual(logger.getEffectiveLevel(), logging.Level.INFO);
+ });
+
+ it('uses level from set on nearest parent', function() {
+ let ancestor = mgr.getLogger('foo');
+ ancestor.setLevel(logging.Level.SEVERE);
+
+ let logger = mgr.getLogger('foo.bar.baz');
+ assert.strictEqual(logger.getLevel(), null);
+ assert.strictEqual(logger.getEffectiveLevel(), logging.Level.SEVERE);
+ });
+ });
+
+ describe('isLoggable()', function() {
+ it('compares level against logger\'s effective level', function() {
+ const log1 = mgr.getLogger('foo');
+ log1.setLevel(logging.Level.WARNING);
+
+ const log2 = mgr.getLogger('foo.bar.baz');
+
+ assert(!log2.isLoggable(logging.Level.FINEST));
+ assert(!log2.isLoggable(logging.Level.INFO));
+ assert(log2.isLoggable(logging.Level.WARNING));
+ assert(log2.isLoggable(logging.Level.SEVERE));
+
+ log2.setLevel(logging.Level.INFO);
+
+ assert(!log2.isLoggable(logging.Level.FINEST));
+ assert(log2.isLoggable(logging.Level.INFO));
+ assert(log2.isLoggable(logging.Level.WARNING));
+ assert(log2.isLoggable(logging.Level.SEVERE));
+
+ log2.setLevel(logging.Level.ALL);
+
+ assert(log2.isLoggable(logging.Level.FINEST));
+ assert(log2.isLoggable(logging.Level.INFO));
+ assert(log2.isLoggable(logging.Level.WARNING));
+ assert(log2.isLoggable(logging.Level.SEVERE));
+ });
+
+ it('Level.OFF is never loggable', function() {
+ function test(level) {
+ root.setLevel(level);
+ assert(!root.isLoggable(logging.Level.OFF),
+ 'OFF should not be loggable at ' + level);
+ }
+
+ test(logging.Level.ALL);
+ test(logging.Level.INFO);
+ test(logging.Level.OFF);
+ });
+ });
+
+ describe('log()', function() {
+ it('does not invoke loggable if message is not loggable', function() {
+ const log = mgr.getLogger('foo');
+ log.setLevel(logging.Level.OFF);
+
+ let callback = sinon.spy();
+ log.addHandler(callback);
+ root.addHandler(callback);
+
+ assert(!callback.called);
+ });
+
+ it('invokes handlers for each parent logger', function() {
+ const cb1 = sinon.spy();
+ const cb2 = sinon.spy();
+ const cb3 = sinon.spy();
+ const cb4 = sinon.spy();
+
+ const log1 = mgr.getLogger('foo');
+ const log2 = mgr.getLogger('foo.bar');
+ const log3 = mgr.getLogger('foo.bar.baz');
+ const log4 = mgr.getLogger('foo.bar.baz.quot');
+
+ log1.addHandler(cb1);
+ log1.setLevel(logging.Level.INFO);
+
+ log2.addHandler(cb2);
+ log2.setLevel(logging.Level.WARNING);
+
+ log3.addHandler(cb3);
+ log3.setLevel(logging.Level.FINER);
+
+ clock.tick(123456);
+
+ log4.finest('this is the finest message');
+ log4.finer('this is a finer message');
+ log4.info('this is an info message');
+ log4.warning('this is a warning message');
+ log4.severe('this is a severe message');
+
+ assert.equal(4, cb1.callCount);
+ assert.equal(4, cb2.callCount);
+ assert.equal(4, cb3.callCount);
+
+ const entry1 = new logging.Entry(
+ logging.Level.FINER,
+ '[foo.bar.baz.quot] this is a finer message',
+ 123456);
+ const entry2 = new logging.Entry(
+ logging.Level.INFO,
+ '[foo.bar.baz.quot] this is an info message',
+ 123456);
+ const entry3 = new logging.Entry(
+ logging.Level.WARNING,
+ '[foo.bar.baz.quot] this is a warning message',
+ 123456);
+ const entry4 = new logging.Entry(
+ logging.Level.SEVERE,
+ '[foo.bar.baz.quot] this is a severe message',
+ 123456);
+
+ check(cb1.getCall(0).args[0], entry1);
+ check(cb1.getCall(1).args[0], entry2);
+ check(cb1.getCall(2).args[0], entry3);
+ check(cb1.getCall(3).args[0], entry4);
+
+ check(cb2.getCall(0).args[0], entry1);
+ check(cb2.getCall(1).args[0], entry2);
+ check(cb2.getCall(2).args[0], entry3);
+ check(cb2.getCall(3).args[0], entry4);
+
+ check(cb3.getCall(0).args[0], entry1);
+ check(cb3.getCall(1).args[0], entry2);
+ check(cb3.getCall(2).args[0], entry3);
+ check(cb3.getCall(3).args[0], entry4);
+
+ function check(entry, expected) {
+ assert.equal(entry.level, expected.level, 'wrong level');
+ assert.equal(entry.message, expected.message, 'wrong message');
+ assert.equal(entry.timestamp, expected.timestamp, 'wrong time');
+ }
+ });
+
+ it('does not invoke removed handler', function() {
+ root.setLevel(logging.Level.INFO);
+ const cb = sinon.spy();
+
+ root.addHandler(cb);
+ root.info('hi');
+ assert.equal(1, cb.callCount);
+
+ assert(root.removeHandler(cb));
+ root.info('bye');
+ assert.equal(1, cb.callCount);
+
+ assert(!root.removeHandler(cb));
+ });
+ });
+ });
+
+ describe('getLevel()', function() {
+ it('converts named levels', function() {
+ assert.strictEqual(logging.Level.DEBUG, logging.getLevel('DEBUG'));
+ assert.strictEqual(logging.Level.ALL, logging.getLevel('FAKE'));
+ });
+
+ it('converts numeric levels', function() {
+ assert.strictEqual(
+ logging.Level.DEBUG,
+ logging.getLevel(logging.Level.DEBUG.value));
+ });
+
+ it('normalizes numeric levels', function() {
+ assert.strictEqual(
+ logging.Level.OFF,
+ logging.getLevel(logging.Level.OFF.value * 2));
+
+ let diff = logging.Level.SEVERE.value - logging.Level.WARNING.value;
+ assert.strictEqual(
+ logging.Level.WARNING,
+ logging.getLevel(logging.Level.WARNING.value + (diff * .5)));
+
+ assert.strictEqual(logging.Level.ALL, logging.getLevel(0));
+ assert.strictEqual(logging.Level.ALL, logging.getLevel(-1));
+ });
+ });
+
+ describe('Preferences', function() {
+ it('can be converted to JSON', function() {
+ let prefs = new logging.Preferences;
+ assert.equal('{}', JSON.stringify(prefs));
+
+ prefs.setLevel('foo', logging.Level.DEBUG);
+ assert.equal('{"foo":"DEBUG"}', JSON.stringify(prefs));
+
+ prefs.setLevel(logging.Type.BROWSER, logging.Level.FINE);
+ assert.equal('{"foo":"DEBUG","browser":"FINE"}', JSON.stringify(prefs));
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/lib/promise_aplus_test.js b/node_modules/selenium-webdriver/test/lib/promise_aplus_test.js
new file mode 100644
index 000000000..a89391590
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/lib/promise_aplus_test.js
@@ -0,0 +1,74 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+const promise = require('../../lib/promise');
+
+describe('Promises/A+ Compliance Tests', function() {
+ // The promise spec does not define behavior for unhandled rejections and
+ // assumes they are effectively swallowed. This is not the case with our
+ // implementation, so we have to disable error propagation to test that the
+ // rest of our behavior is compliant.
+ // We run the tests with a separate instance of the control flow to ensure
+ // disablign error propagation does not impact other tests.
+ var flow = new promise.ControlFlow();
+ flow.setPropagateUnhandledRejections(false);
+
+ // Skip the tests in 2.2.6.1/2. We are not compliant in these scenarios.
+ var realDescribe = global.describe;
+ global.describe = function(name, fn) {
+ realDescribe(name, function() {
+ var prefix = 'Promises/A+ Compliance Tests 2.2.6: '
+ + '`then` may be called multiple times on the same promise.';
+ var suffix = 'even when one handler is added inside another handler';
+ if (this.fullTitle().startsWith(prefix)
+ && this.fullTitle().endsWith(suffix)) {
+ var realSpecify = global.specify;
+ try {
+ global.specify = function(name) {
+ realSpecify(name);
+ };
+ fn();
+ } finally {
+ global.specify = realSpecify;
+ }
+ } else {
+ fn();
+ }
+ });
+ };
+
+ require('promises-aplus-tests').mocha({
+ resolved: function(value) {
+ return new promise.Promise((fulfill) => fulfill(value), flow);
+ },
+ rejected: function(error) {
+ return new promise.Promise((_, reject) => reject(error), flow);
+ },
+ deferred: function() {
+ var d = new promise.Deferred(flow);
+ return {
+ resolve: d.fulfill,
+ reject: d.reject,
+ promise: d.promise
+ };
+ }
+ });
+
+ global.describe = realDescribe;
+});
diff --git a/node_modules/selenium-webdriver/test/lib/promise_error_test.js b/node_modules/selenium-webdriver/test/lib/promise_error_test.js
new file mode 100644
index 000000000..e6de3cb92
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/lib/promise_error_test.js
@@ -0,0 +1,880 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+/**
+ * @fileoverview Contains tests against promise error handling. Many tests use
+ * NativePromise to control test termination independent of promise
+ * (and notably promise.ControlFlow).
+ */
+
+'use strict';
+
+const testutil = require('./testutil');
+
+const assert = require('assert');
+const promise = require('../../lib/promise');
+
+const NativePromise = Promise;
+const StubError = testutil.StubError;
+const throwStubError = testutil.throwStubError;
+const assertIsStubError = testutil.assertIsStubError;
+
+describe('promise error handling', function() {
+ var flow, uncaughtExceptions;
+
+ beforeEach(function setUp() {
+ flow = promise.controlFlow();
+ uncaughtExceptions = [];
+ flow.on('uncaughtException', onUncaughtException);
+ });
+
+ afterEach(function tearDown() {
+ return waitForIdle(flow).then(function() {
+ assert.deepEqual(
+ [], uncaughtExceptions, 'There were uncaught exceptions');
+ flow.reset();
+ });
+ });
+
+ function onUncaughtException(e) {
+ uncaughtExceptions.push(e);
+ }
+
+ function waitForAbort(opt_flow, opt_n) {
+ var n = opt_n || 1;
+ var theFlow = opt_flow || flow;
+ theFlow.removeAllListeners(
+ promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION);
+ return new NativePromise(function(fulfill, reject) {
+ theFlow.once('idle', function() {
+ reject(Error('expected flow to report an unhandled error'));
+ });
+
+ var errors = [];
+ theFlow.on('uncaughtException', onError);
+ function onError(e) {
+ errors.push(e);
+ if (errors.length === n) {
+ theFlow.removeListener('uncaughtException', onError);
+ fulfill(n === 1 ? errors[0] : errors);
+ }
+ }
+ });
+ }
+
+ function waitForIdle(opt_flow) {
+ var theFlow = opt_flow || flow;
+ return new NativePromise(function(fulfill, reject) {
+ if (theFlow.isIdle()) {
+ fulfill();
+ return;
+ }
+ theFlow.once('idle', fulfill);
+ theFlow.once('uncaughtException', reject);
+ });
+ }
+
+ it('testRejectedPromiseTriggersErrorCallback', function() {
+ return promise.rejected(new StubError).
+ then(assert.fail, assertIsStubError);
+ });
+
+ describe('callback throws trigger subsequent error callback', function() {
+ it('fulfilled promise', function() {
+ return promise.fulfilled().
+ then(throwStubError).
+ then(assert.fail, assertIsStubError);
+ });
+
+ it('rejected promise', function() {
+ var e = Error('not the droids you are looking for');
+ return promise.rejected(e).
+ then(assert.fail, throwStubError).
+ then(assert.fail, assertIsStubError);
+ });
+ });
+
+ describe('callback returns rejected promise triggers subsequent errback', function() {
+ it('from fulfilled callback', function() {
+ return promise.fulfilled().then(function() {
+ return promise.rejected(new StubError);
+ }).then(assert.fail, assertIsStubError);
+ });
+
+ it('from rejected callback', function() {
+ var e = Error('not the droids you are looking for');
+ return promise.rejected(e).
+ then(assert.fail, function() {
+ return promise.rejected(new StubError);
+ }).
+ then(assert.fail, assertIsStubError);
+ });
+ });
+
+ it('testReportsUnhandledRejectionsThroughTheControlFlow', function() {
+ promise.rejected(new StubError);
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ describe('multiple unhandled rejections outside a task', function() {
+ it('are reported in order they occurred', function() {
+ var e1 = Error('error 1');
+ var e2 = Error('error 2');
+
+ promise.rejected(e1);
+ promise.rejected(e2);
+
+ return waitForAbort(flow).then(function(error) {
+ assert.ok(
+ error instanceof promise.MultipleUnhandledRejectionError);
+ // TODO: switch to Array.from when we drop node 0.12.x
+ var errors = [];
+ for (var e of error.errors) {
+ errors.push(e);
+ }
+ assert.deepEqual([e1, e2], errors);
+ });
+ });
+ });
+
+ describe('does not report unhandled rejection when', function() {
+ it('handler added before next tick', function() {
+ promise.rejected(new StubError).then(assert.fail, assertIsStubError);
+ return waitForIdle();
+ });
+
+ it('added async but before next tick', function() {
+ var called = false;
+ return new NativePromise(function(fulfill, reject) {
+ var aPromise;
+ NativePromise.resolve().then(function() {
+ aPromise.then(assert.fail, function(e) {
+ called = true;
+ assertIsStubError(e);
+ });
+ waitForIdle().then(fulfill, reject);
+ });
+ aPromise = promise.rejected(new StubError);
+ }).then(function() {
+ assert.ok(called);
+ })
+ });
+ });
+
+ it('testTaskThrows', function() {
+ return flow.execute(throwStubError).then(assert.fail, assertIsStubError);
+ });
+
+ it('testTaskReturnsRejectedPromise', function() {
+ return flow.execute(function() {
+ return promise.rejected(new StubError)
+ }).then(assert.fail, assertIsStubError);
+ });
+
+ it('testTaskHasUnhandledRejection', function() {
+ return flow.execute(function() {
+ promise.rejected(new StubError)
+ }).then(assert.fail, assertIsStubError);
+ });
+
+ it('testTaskfails_returnedPromiseIsUnhandled', function() {
+ flow.execute(throwStubError);
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ it('testTaskHasUnhandledRejection_cancelsRemainingSubTasks', function() {
+ var seen = [];
+ flow.execute(function() {
+ promise.rejected(new StubError);
+
+ flow.execute(() => seen.push('a'))
+ .then(() => seen.push('b'), (e) => seen.push(e));
+ flow.execute(() => seen.push('c'))
+ .then(() => seen.push('b'), (e) => seen.push(e));
+ });
+
+ return waitForAbort()
+ .then(assertIsStubError)
+ .then(() => assert.deepEqual([], seen));
+ });
+
+ describe('nested task failures', function() {
+ it('returns up to paren', function() {
+ return flow.execute(function() {
+ return flow.execute(function() {
+ return flow.execute(throwStubError);
+ });
+ }).then(assert.fail, assertIsStubError);
+ });
+
+ it('task throws; uncaught error bubbles up', function() {
+ flow.execute(function() {
+ flow.execute(function() {
+ flow.execute(throwStubError);
+ });
+ });
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ it('task throws; uncaught error bubbles up; is caught at root', function() {
+ flow.execute(function() {
+ flow.execute(function() {
+ flow.execute(throwStubError);
+ });
+ }).then(assert.fail, assertIsStubError);
+ return waitForIdle();
+ });
+
+ it('unhandled rejection bubbles up', function() {
+ flow.execute(function() {
+ flow.execute(function() {
+ flow.execute(function() {
+ promise.rejected(new StubError);
+ });
+ });
+ });
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ it('unhandled rejection bubbles up; caught at root', function() {
+ flow.execute(function() {
+ flow.execute(function() {
+ promise.rejected(new StubError);
+ });
+ }).then(assert.fail, assertIsStubError);
+ return waitForIdle();
+ });
+
+ it('mixtureof hanging and free subtasks', function() {
+ flow.execute(function() {
+ return flow.execute(function() {
+ flow.execute(throwStubError);
+ });
+ });
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ it('cancels remaining tasks', function() {
+ var seen = [];
+ flow.execute(function() {
+ flow.execute(() => promise.rejected(new StubError));
+ flow.execute(() => seen.push('a'))
+ .then(() => seen.push('b'), (e) => seen.push(e));
+ flow.execute(() => seen.push('c'))
+ .then(() => seen.push('b'), (e) => seen.push(e));
+ });
+
+ return waitForAbort()
+ .then(assertIsStubError)
+ .then(() => assert.deepEqual([], seen));
+ });
+ });
+
+ it('testTaskReturnsPromiseLikeObjectThatInvokesErrback', function() {
+ return flow.execute(function() {
+ return {
+ 'then': function(_, errback) {
+ errback('abc123');
+ }
+ };
+ }).then(assert.fail, function(value) {
+ assert.equal('abc123', value);
+ });
+ });
+
+ describe('ControlFlow#wait();', function() {
+ describe('condition throws;', function() {
+ it('failure is caught', function() {
+ return flow.wait(throwStubError, 50).then(assert.fail, assertIsStubError);
+ });
+
+ it('failure is not caught', function() {
+ flow.wait(throwStubError, 50);
+ return waitForAbort().then(assertIsStubError);
+ });
+ });
+
+ describe('condition returns promise', function() {
+ it('failure is caught', function() {
+ return flow.wait(function() {
+ return promise.rejected(new StubError);
+ }, 50).then(assert.fail, assertIsStubError);
+ });
+
+ it('failure is not caught', function() {
+ flow.wait(function() {
+ return promise.rejected(new StubError);
+ }, 50);
+ return waitForAbort().then(assertIsStubError);
+ });
+ });
+
+ describe('condition has unhandled promise rejection', function() {
+ it('failure is caught', function() {
+ return flow.wait(function() {
+ promise.rejected(new StubError);
+ }, 50).then(assert.fail, assertIsStubError);
+ });
+
+ it('failure is not caught', function() {
+ flow.wait(function() {
+ promise.rejected(new StubError);
+ }, 50);
+ return waitForAbort().then(assertIsStubError);
+ });
+ });
+
+ describe('condition has subtask failure', function() {
+ it('failure is caught', function() {
+ return flow.wait(function() {
+ flow.execute(function() {
+ flow.execute(throwStubError);
+ });
+ }, 50).then(assert.fail, assertIsStubError);
+ });
+
+ it('failure is not caught', function() {
+ flow.wait(function() {
+ flow.execute(function() {
+ flow.execute(throwStubError);
+ });
+ }, 50);
+ return waitForAbort().then(assertIsStubError);
+ });
+ });
+ });
+
+ describe('errback throws a new error', function() {
+ it('start with normal promise', function() {
+ var error = Error('an error');
+ return promise.rejected(error).
+ catch(function(e) {
+ assert.equal(e, error);
+ throw new StubError;
+ }).
+ catch(assertIsStubError);
+ });
+
+ it('start with task result', function() {
+ var error = Error('an error');
+ return flow.execute(function() {
+ throw error;
+ }).
+ catch(function(e) {
+ assert.equal(e, error);
+ throw new StubError;
+ }).
+ catch(assertIsStubError);
+ });
+
+ it('start with normal promise; uncaught error', function() {
+ var error = Error('an error');
+ promise.rejected(error).
+ catch(function(e) {
+ assert.equal(e, error);
+ throw new StubError;
+ });
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ it('start with task result; uncaught error', function() {
+ var error = Error('an error');
+ flow.execute(function() {
+ throw error;
+ }).
+ catch(function(e) {
+ assert.equal(e, error);
+ throw new StubError;
+ });
+ return waitForAbort().then(assertIsStubError);
+ });
+ });
+
+ it('thrownPromiseCausesCallbackRejection', function() {
+ let p = promise.fulfilled(1234);
+ return promise.fulfilled().then(function() {
+ throw p;
+ }).then(assert.fail, function(value) {
+ assert.strictEqual(p, value);
+ });
+ });
+
+ describe('task throws promise', function() {
+ it('promise was fulfilled', function() {
+ var toThrow = promise.fulfilled(1234);
+ flow.execute(function() {
+ throw toThrow;
+ }).then(assert.fail, function(value) {
+ assert.equal(toThrow, value);
+ return toThrow;
+ }).then(function(value) {
+ assert.equal(1234, value);
+ });
+ return waitForIdle();
+ });
+
+ it('promise was rejected', function() {
+ var toThrow = promise.rejected(new StubError);
+ toThrow.catch(function() {}); // For tearDown.
+ flow.execute(function() {
+ throw toThrow;
+ }).then(assert.fail, function(e) {
+ assert.equal(toThrow, e);
+ return e;
+ }).then(assert.fail, assertIsStubError);
+ return waitForIdle();
+ });
+ });
+
+ it('testFailsTaskIfThereIsAnUnhandledErrorWhileWaitingOnTaskResult', function() {
+ var d = promise.defer();
+ flow.execute(function() {
+ promise.rejected(new StubError);
+ return d.promise;
+ }).then(assert.fail, assertIsStubError);
+
+ return waitForIdle().then(function() {
+ return d.promise;
+ }).then(assert.fail, function(e) {
+ assert.equal('CancellationError: StubError', e.toString());
+ });
+ });
+
+ it('testFailsParentTaskIfAsyncScheduledTaskFails', function() {
+ var d = promise.defer();
+ flow.execute(function() {
+ flow.execute(throwStubError);
+ return d.promise;
+ }).then(assert.fail, assertIsStubError);
+
+ return waitForIdle().then(function() {
+ return d.promise;
+ }).then(assert.fail, function(e) {
+ assert.equal('CancellationError: StubError', e.toString());
+ });
+ });
+
+ describe('long stack traces', function() {
+ afterEach(() => promise.LONG_STACK_TRACES = false);
+
+ it('always includes task stacks in failures', function() {
+ promise.LONG_STACK_TRACES = false;
+ flow.execute(function() {
+ flow.execute(function() {
+ flow.execute(throwStubError, 'throw error');
+ }, 'two');
+ }, 'three').
+ then(assert.fail, function(e) {
+ assertIsStubError(e);
+ if (typeof e.stack !== 'string') {
+ return;
+ }
+ var messages = e.stack.split(/\n/).filter(function(line, index) {
+ return /^From: /.test(line);
+ });
+ assert.deepEqual([
+ 'From: Task: throw error',
+ 'From: Task: two',
+ 'From: Task: three'
+ ], messages);
+ });
+ return waitForIdle();
+ });
+
+ it('does not include completed tasks', function () {
+ flow.execute(function() {}, 'succeeds');
+ flow.execute(throwStubError, 'kaboom').then(assert.fail, function(e) {
+ assertIsStubError(e);
+ if (typeof e.stack !== 'string') {
+ return;
+ }
+ var messages = e.stack.split(/\n/).filter(function(line, index) {
+ return /^From: /.test(line);
+ });
+ assert.deepEqual(['From: Task: kaboom'], messages);
+ });
+ return waitForIdle();
+ });
+
+ it('does not include promise chain when disabled', function() {
+ promise.LONG_STACK_TRACES = false;
+ flow.execute(function() {
+ flow.execute(function() {
+ return promise.fulfilled().
+ then(function() {}).
+ then(function() {}).
+ then(throwStubError);
+ }, 'eventually assert.fails');
+ }, 'start').
+ then(assert.fail, function(e) {
+ assertIsStubError(e);
+ if (typeof e.stack !== 'string') {
+ return;
+ }
+ var messages = e.stack.split(/\n/).filter(function(line, index) {
+ return /^From: /.test(line);
+ });
+ assert.deepEqual([
+ 'From: Task: eventually assert.fails',
+ 'From: Task: start'
+ ], messages);
+ });
+ return waitForIdle();
+ });
+
+ it('includes promise chain when enabled', function() {
+ promise.LONG_STACK_TRACES = true;
+ flow.execute(function() {
+ flow.execute(function() {
+ return promise.fulfilled().
+ then(function() {}).
+ then(function() {}).
+ then(throwStubError);
+ }, 'eventually assert.fails');
+ }, 'start').
+ then(assert.fail, function(e) {
+ assertIsStubError(e);
+ if (typeof e.stack !== 'string') {
+ return;
+ }
+ var messages = e.stack.split(/\n/).filter(function(line, index) {
+ return /^From: /.test(line);
+ });
+ assert.deepEqual([
+ 'From: Promise: then',
+ 'From: Task: eventually assert.fails',
+ 'From: Task: start'
+ ], messages);
+ });
+ return waitForIdle();
+ });
+ });
+
+ describe('frame cancels remaining tasks', function() {
+ it('on unhandled task failure', function() {
+ var run = false;
+ return flow.execute(function() {
+ flow.execute(throwStubError);
+ flow.execute(function() { run = true; });
+ }).then(assert.fail, function(e) {
+ assertIsStubError(e);
+ assert.ok(!run);
+ });
+ });
+
+ it('on unhandled promise rejection', function() {
+ var run = false;
+ return flow.execute(function() {
+ promise.rejected(new StubError);
+ flow.execute(function() { run = true; });
+ }).then(assert.fail, function(e) {
+ assertIsStubError(e);
+ assert.ok(!run);
+ });
+ });
+
+ it('if task throws', function() {
+ var run = false;
+ return flow.execute(function() {
+ flow.execute(function() { run = true; });
+ throw new StubError;
+ }).then(assert.fail, function(e) {
+ assertIsStubError(e);
+ assert.ok(!run);
+ });
+ });
+
+ describe('task callbacks scheduled in another frame', function() {
+ flow = promise.controlFlow();
+ function noop() {}
+
+ let subTask;
+
+ before(function() {
+ flow.execute(function() {
+ // This task will be discarded and never run because of the error below.
+ subTask = flow.execute(() => 'abc');
+ throw new StubError('stub');
+ }).catch(noop);
+ });
+
+ function assertCancellation(e) {
+ assert.ok(e instanceof promise.CancellationError);
+ assert.equal(
+ 'Task was discarded due to a previous failure: stub', e.message);
+ }
+
+ it('are rejected with cancellation error', function() {
+ let result;
+ return Promise.resolve().then(function() {
+ return flow.execute(function() {
+ result = subTask.then(assert.fail);
+ });
+ })
+ .then(() => result)
+ .then(assert.fail, assertCancellation);
+ });
+
+ it('cancellation errors propagate through callbacks (1)', function() {
+ let result;
+ return Promise.resolve().then(function() {
+ return flow.execute(function() {
+ result = subTask
+ .then(assert.fail, assertCancellation)
+ .then(() => 'abc123');
+ });
+ })
+ .then(() => result)
+ .then(value => assert.equal('abc123', value));
+ });
+
+ it('cancellation errors propagate through callbacks (2)', function() {
+ let result;
+ return Promise.resolve().then(function() {
+ return flow.execute(function() {
+ result = subTask.then(assert.fail)
+ .then(noop, assertCancellation)
+ .then(() => 'fin');
+ });
+ })
+ // Verify result actually computed successfully all the way through.
+ .then(() => result)
+ .then(value => assert.equal('fin', value));
+ });
+ });
+ });
+
+ it('testRegisteredTaskCallbacksAreDroppedWhenTaskIsCancelled_return', function() {
+ var seen = [];
+ return flow.execute(function() {
+ flow.execute(throwStubError);
+
+ flow.execute(function() {
+ seen.push(1);
+ }).then(function() {
+ seen.push(2);
+ }, function() {
+ seen.push(3);
+ });
+ }).then(assert.fail, function(e) {
+ assertIsStubError(e);
+ assert.deepEqual([], seen);
+ });
+ });
+
+ it('testRegisteredTaskCallbacksAreDroppedWhenTaskIsCancelled_withReturn', function() {
+ var seen = [];
+ return flow.execute(function() {
+ flow.execute(throwStubError);
+
+ return flow.execute(function() {
+ seen.push(1);
+ }).then(function() {
+ seen.push(2);
+ }, function() {
+ seen.push(3);
+ });
+ }).then(assert.fail, function(e) {
+ assertIsStubError(e);
+ assert.deepEqual([], seen);
+ });
+ });
+
+ it('testTasksWithinACallbackAreDroppedIfContainingTaskIsAborted', function() {
+ var seen = [];
+ return flow.execute(function() {
+ flow.execute(throwStubError);
+
+ // None of the callbacks on this promise should execute because the
+ // task assert.failure above is never handled, causing the containing task to
+ // abort.
+ promise.fulfilled().then(function() {
+ seen.push(1);
+ return flow.execute(function() {
+ seen.push(2);
+ });
+ }).finally(function() {
+ seen.push(3);
+ });
+
+ }).then(assert.fail, function(e) {
+ assertIsStubError(e);
+ assert.deepEqual([], seen);
+ });
+ });
+
+ it('testTaskIsCancelledAfterWaitTimeout', function() {
+ var seen = [];
+ return flow.execute(function() {
+ flow.wait(function() {
+ return promise.delayed(50);
+ }, 5);
+
+ return flow.execute(function() {
+ seen.push(1);
+ }).then(function() {
+ seen.push(2);
+ }, function() {
+ seen.push(3);
+ });
+ }).then(assert.fail, function() {
+ assert.deepEqual([], seen);
+ });
+ });
+
+ describe('task callbacks get cancellation error if registered after task was cancelled', function() {
+ it('(a)', function() {
+ var task;
+ flow.execute(function() {
+ flow.execute(throwStubError);
+ task = flow.execute(function() {});
+ }).then(assert.fail, assertIsStubError);
+ return waitForIdle().then(function() {
+ return task.then(assert.fail, function(e) {
+ assert.ok(e instanceof promise.CancellationError);
+ });
+ });
+ });
+
+ it('(b)', function() {
+ var seen = [];
+
+ var task;
+ flow.execute(function() {
+ flow.execute(throwStubError);
+ task = flow.execute(function() {});
+
+ task.then(() => seen.push(1))
+ .then(() => seen.push(2));
+ task.then(() => seen.push(3))
+ .then(() => seen.push(4));
+
+ }).then(assert.fail, assertIsStubError);
+
+ return waitForIdle().then(function() {
+ return task.then(assert.fail, function(e) {
+ seen.push(5);
+ assert.ok(e instanceof promise.CancellationError);
+ });
+ }).then(() => assert.deepEqual([5], seen));
+ });
+ });
+
+ it('unhandledRejectionInParallelTaskQueue', function() {
+ var seen = [];
+ function schedule(name) {
+ return flow.execute(() => seen.push(name), name);
+ }
+
+ flow.async(function() {
+ schedule('a.1');
+ flow.execute(throwStubError, 'a.2 (throws)');
+ });
+
+ var b3;
+ flow.async(function() {
+ schedule('b.1');
+ schedule('b.2');
+ b3 = schedule('b.3');
+ });
+
+ var c3;
+ flow.async(function() {
+ schedule('c.1');
+ schedule('c.2');
+ c3 = schedule('c.3');
+ });
+
+ function assertWasCancelled(p) {
+ return p.then(assert.fail, function(e) {
+ assert.ok(e instanceof promise.CancellationError);
+ });
+ }
+
+ return waitForAbort()
+ .then(function() {
+ assert.deepEqual(['a.1', 'b.1', 'c.1', 'b.2', 'c.2'], seen);
+ assert.ok(!b3.isPending());
+ assert.ok(!c3.isPending());
+ })
+ .then(() => assertWasCancelled(b3))
+ .then(() => assertWasCancelled(c3));
+ });
+
+ it('errorsInAsyncFunctionsAreReportedAsUnhandledRejection', function() {
+ flow.removeAllListeners(); // For tearDown.
+
+ var task;
+ return new Promise(function(fulfill) {
+ flow.once('uncaughtException', fulfill);
+ flow.async(function() {
+ task = flow.execute(function() {});
+ throw Error('boom');
+ });
+ }).then(function(error) {
+ assert.ok(error instanceof promise.CancellationError);
+ assert.ok(!task.isPending());
+ return task.catch(function(error) {
+ assert.ok(error instanceof promise.CancellationError);
+ });
+ });
+ });
+
+ describe('does not wait for values thrown from callbacks to be resolved', function() {
+ it('(a)', function() {
+ var p1 = promise.fulfilled();
+ var reason = promise.fulfilled('should not see me');
+ return p1.then(function() {
+ throw reason;
+ }).then(assert.fail, function(e) {
+ assert.equal(reason, e);
+ });
+ });
+
+ it('(b)', function() {
+ var p1 = promise.fulfilled();
+ var reason = promise.rejected('should not see me');
+ reason.catch(function() {}); // For tearDown.
+ return p1.then(function() {
+ throw reason;
+ }).then(assert.fail, function(e) {
+ assert.equal(reason, e);
+ });
+ });
+
+ it('(c)', function() {
+ var p1 = promise.fulfilled();
+ var reason = promise.defer();
+ setTimeout(() => reason.fulfill('should not see me'), 100);
+ return p1.then(function() {
+ throw reason.promise;
+ }).then(assert.fail, function(e) {
+ assert.equal(reason.promise, e);
+ });
+ });
+
+ it('(d)', function() {
+ var p1 = promise.fulfilled();
+ var reason = {then: function() {}}; // A thenable like object.
+ return p1.then(function() {
+ throw reason;
+ }).then(assert.fail, function(e) {
+ assert.equal(reason, e);
+ });
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/lib/promise_flow_test.js b/node_modules/selenium-webdriver/test/lib/promise_flow_test.js
new file mode 100644
index 000000000..b42ac5209
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/lib/promise_flow_test.js
@@ -0,0 +1,2284 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+const assert = require('assert');
+const fail = assert.fail;
+const sinon = require('sinon');
+
+const testutil = require('./testutil');
+const promise = require('../../lib/promise');
+
+const NativePromise = Promise;
+
+// Aliases for readability.
+const StubError = testutil.StubError;
+const assertIsStubError = testutil.assertIsStubError;
+const callbackPair = testutil.callbackPair;
+const throwStubError = testutil.throwStubError;
+
+describe('promise control flow', function() {
+ let flow, flowHistory, messages, uncaughtExceptions;
+
+ beforeEach(function setUp() {
+ promise.LONG_STACK_TRACES = false;
+ flow = new promise.ControlFlow();
+ promise.setDefaultFlow(flow);
+ messages = [];
+ flowHistory = [];
+
+ uncaughtExceptions = [];
+ flow.on(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION,
+ onUncaughtException);
+ });
+
+ afterEach(function tearDown() {
+ flow.removeAllListeners(
+ promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION);
+ assert.deepEqual([], uncaughtExceptions,
+ 'There were uncaught exceptions');
+ flow.reset();
+ promise.LONG_STACK_TRACES = false;
+ });
+
+ function onUncaughtException(e) {
+ uncaughtExceptions.push(e);
+ }
+
+ function waitForAbort(opt_flow) {
+ var theFlow = opt_flow || flow;
+ theFlow.removeAllListeners(
+ promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION);
+ return new NativePromise(function(fulfill, reject) {
+ theFlow.once(promise.ControlFlow.EventType.IDLE, function() {
+ reject(Error('expected flow to report an unhandled error'));
+ });
+ theFlow.once(
+ promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION,
+ fulfill);
+ });
+ }
+
+ function waitForIdle(opt_flow) {
+ var theFlow = opt_flow || flow;
+ return new NativePromise(function(fulfill, reject) {
+ theFlow.once(promise.ControlFlow.EventType.IDLE, fulfill);
+ theFlow.once(
+ promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, reject);
+ });
+ }
+
+ function timeout(ms) {
+ return new NativePromise(function(fulfill) {
+ setTimeout(fulfill, ms);
+ });
+ }
+
+
+ function schedule(msg, opt_return) {
+ return scheduleAction(msg, function() {
+ return opt_return;
+ });
+ }
+
+ /**
+ * @param {string} value The value to push.
+ * @param {promise.Promise=} opt_taskPromise Promise to return from
+ * the task.
+ * @return {!promise.Promise} The result.
+ */
+ function schedulePush(value, opt_taskPromise) {
+ return scheduleAction(value, function() {
+ messages.push(value);
+ return opt_taskPromise;
+ });
+ }
+
+ /**
+ * @param {string} msg Debug message.
+ * @param {!Function} actionFn The function.
+ * @return {!promise.Promise} The function result.
+ */
+ function scheduleAction(msg, actionFn) {
+ return promise.controlFlow().execute(function() {
+ flowHistory.push(msg);
+ return actionFn();
+ }, msg);
+ }
+
+ /**
+ * @param {!Function} condition The condition function.
+ * @param {number=} opt_timeout The timeout.
+ * @param {string=} opt_message Optional message.
+ * @return {!promise.Promise} The wait result.
+ */
+ function scheduleWait(condition, opt_timeout, opt_message) {
+ var msg = opt_message || '';
+ // It's not possible to hook into when the wait itself is scheduled, so
+ // we record each iteration of the wait loop.
+ var count = 0;
+ return promise.controlFlow().wait(function() {
+ flowHistory.push((count++) + ': ' + msg);
+ return condition();
+ }, opt_timeout, msg);
+ }
+
+ function asyncRun(fn, opt_self) {
+ NativePromise.resolve().then(() => fn.call(opt_self));
+ }
+
+ function assertFlowHistory(var_args) {
+ var expected = Array.prototype.slice.call(arguments, 0);
+ assert.deepEqual(expected, flowHistory);
+ }
+
+ function assertMessages(var_args) {
+ var expected = Array.prototype.slice.call(arguments, 0);
+ assert.deepEqual(expected, messages);
+ }
+
+ function assertingMessages(var_args) {
+ var args = Array.prototype.slice.call(arguments, 0);
+ return () => assertMessages.apply(null, args);
+ }
+
+ function assertFlowIs(flow) {
+ assert.equal(flow, promise.controlFlow());
+ }
+
+ describe('testScheduling', function() {
+ it('aSimpleFunction', function() {
+ schedule('go');
+ return waitForIdle().then(function() {
+ assertFlowHistory('go');
+ });
+ });
+
+ it('aSimpleFunctionWithANonPromiseReturnValue', function() {
+ schedule('go', 123).then(function(value) {
+ assert.equal(123, value);
+ });
+ return waitForIdle().then(function() {
+ assertFlowHistory('go');
+ });
+ });
+
+ it('aSimpleSequence', function() {
+ schedule('a');
+ schedule('b');
+ schedule('c');
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ });
+
+ it('invokesCallbacksWhenTaskIsDone', function() {
+ var d = new promise.Deferred();
+ var called = false;
+ var done = schedule('a', d.promise).then(function(value) {
+ called = true;
+ assert.equal(123, value);
+ });
+ return timeout(5).then(function() {
+ assert.ok(!called);
+ d.fulfill(123);
+ return done;
+ }).
+ then(function() {
+ assertFlowHistory('a');
+ });
+ });
+
+ it('blocksUntilPromiseReturnedByTaskIsResolved', function() {
+ var done = promise.defer();
+ schedulePush('a', done.promise);
+ schedulePush('b');
+ setTimeout(function() {
+ done.fulfill();
+ messages.push('c');
+ }, 25);
+ return waitForIdle().then(assertingMessages('a', 'c', 'b'));
+ });
+
+ it('waitsForReturnedPromisesToResolve', function() {
+ var d1 = new promise.Deferred();
+ var d2 = new promise.Deferred();
+
+ var callback = sinon.spy();
+ schedule('a', d1.promise).then(callback);
+
+ return timeout(5).then(function() {
+ assert(!callback.called);
+ d1.fulfill(d2.promise);
+ return timeout(5);
+ }).then(function() {
+ assert(!callback.called);
+ d2.fulfill('fluffy bunny');
+ return waitForIdle();
+ }).then(function() {
+ assert(callback.called);
+ assert.equal('fluffy bunny', callback.getCall(0).args[0]);
+ assertFlowHistory('a');
+ });
+ });
+
+ it('executesTasksInAFutureTurnAfterTheyAreScheduled', function() {
+ var count = 0;
+ function incr() { count++; }
+
+ scheduleAction('', incr);
+ assert.equal(0, count);
+ return waitForIdle().then(function() {
+ assert.equal(1, count);
+ });
+ });
+
+ it('executesOneTaskPerTurnOfTheEventLoop', function() {
+ var order = [];
+ function go() {
+ order.push(order.length / 2);
+ asyncRun(function() {
+ order.push('-');
+ });
+ }
+
+ scheduleAction('', go);
+ scheduleAction('', go);
+ return waitForIdle().then(function() {
+ assert.deepEqual([0, '-', 1, '-'], order);
+ })
+ });
+
+ it('firstScheduledTaskIsWithinACallback', function() {
+ promise.fulfilled().then(function() {
+ schedule('a');
+ schedule('b');
+ schedule('c');
+ }).then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ return waitForIdle();
+ });
+
+ it('newTasksAddedWhileWaitingOnTaskReturnedPromise1', function() {
+ scheduleAction('a', function() {
+ var d = promise.defer();
+ setTimeout(function() {
+ schedule('c');
+ d.fulfill();
+ }, 10);
+ return d.promise;
+ });
+ schedule('b');
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ });
+
+ it('newTasksAddedWhileWaitingOnTaskReturnedPromise2', function() {
+ scheduleAction('a', function() {
+ var d = promise.defer();
+ setTimeout(function() {
+ schedule('c');
+ asyncRun(d.fulfill);
+ }, 10);
+ return d.promise;
+ });
+ schedule('b');
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'c', 'b');
+ });
+ });
+ });
+
+ describe('testFraming', function() {
+ it('callbacksRunInANewFrame', function() {
+ schedule('a').then(function() {
+ schedule('c');
+ });
+ schedule('b');
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'c', 'b');
+ });
+ });
+
+ it('lotsOfNesting', function() {
+ schedule('a').then(function() {
+ schedule('c').then(function() {
+ schedule('e').then(function() {
+ schedule('g');
+ });
+ schedule('f');
+ });
+ schedule('d');
+ });
+ schedule('b');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'c', 'e', 'g', 'f', 'd', 'b');
+ });
+ });
+
+ it('callbackReturnsPromiseThatDependsOnATask_1', function() {
+ schedule('a').then(function() {
+ schedule('b');
+ return promise.delayed(5).then(function() {
+ return schedule('c');
+ });
+ });
+ schedule('d');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c', 'd');
+ });
+ });
+
+ it('callbackReturnsPromiseThatDependsOnATask_2', function() {
+ schedule('a').then(function() {
+ schedule('b');
+ return promise.delayed(5).
+ then(function() { return promise.delayed(5) }).
+ then(function() { return promise.delayed(5) }).
+ then(function() { return promise.delayed(5) }).
+ then(function() { return schedule('c'); });
+ });
+ schedule('d');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c', 'd');
+ });
+ });
+
+ it('eachCallbackWaitsForAllScheduledTasksToComplete', function() {
+ schedule('a').
+ then(function() {
+ schedule('b');
+ schedule('c');
+ }).
+ then(function() {
+ schedule('d');
+ });
+ schedule('e');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c', 'd', 'e');
+ });
+ });
+
+ it('eachCallbackWaitsForReturnTasksToComplete', function() {
+ schedule('a').
+ then(function() {
+ schedule('b');
+ return schedule('c');
+ }).
+ then(function() {
+ schedule('d');
+ });
+ schedule('e');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c', 'd', 'e');
+ });
+ });
+
+ it('callbacksOnAResolvedPromiseInsertIntoTheCurrentFlow', function() {
+ promise.fulfilled().then(function() {
+ schedule('b');
+ });
+ schedule('a');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('b', 'a');
+ });
+ });
+
+ it('callbacksInterruptTheFlowWhenPromiseIsResolved', function() {
+ schedule('a').then(function() {
+ schedule('c');
+ });
+ schedule('b');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'c', 'b');
+ });
+ });
+
+ it('allCallbacksInAFrameAreScheduledWhenPromiseIsResolved', function() {
+ var a = schedule('a');
+ a.then(function() { schedule('b'); });
+ schedule('c');
+ a.then(function() { schedule('d'); });
+ schedule('e');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c', 'd', 'e');
+ });
+ });
+
+ it('tasksScheduledInInActiveFrameDoNotGetPrecedence', function() {
+ var d = promise.fulfilled();
+ schedule('a');
+ schedule('b');
+ d.then(function() { schedule('c'); });
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ });
+
+ it('tasksScheduledInAFrameGetPrecedence_1', function() {
+ var a = schedule('a');
+ schedule('b').then(function() {
+ a.then(function() {
+ schedule('c');
+ schedule('d');
+ });
+ var e = schedule('e');
+ a.then(function() {
+ schedule('f');
+ e.then(function() {
+ schedule('g');
+ });
+ schedule('h');
+ });
+ schedule('i');
+ });
+ schedule('j');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
+ });
+ });
+ });
+
+ describe('testErrorHandling', function() {
+ it('thrownErrorsArePassedToTaskErrback', function() {
+ scheduleAction('function that throws', throwStubError).
+ then(fail, assertIsStubError);
+ return waitForIdle();
+ });
+
+ it('thrownErrorsPropagateThroughPromiseChain', function() {
+ scheduleAction('function that throws', throwStubError).
+ then(fail).
+ then(fail, assertIsStubError);
+ return waitForIdle();
+ });
+
+ it('catchesErrorsFromFailedTasksInAFrame', function() {
+ schedule('a').then(function() {
+ schedule('b');
+ scheduleAction('function that throws', throwStubError);
+ }).
+ then(fail, assertIsStubError);
+ return waitForIdle();
+ });
+
+ it('abortsIfOnlyTaskReturnsAnUnhandledRejection', function() {
+ scheduleAction('function that returns rejected promise', function() {
+ return promise.rejected(new StubError);
+ });
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ it('abortsIfThereIsAnUnhandledRejection', function() {
+ promise.rejected(new StubError);
+ schedule('this should not run');
+ return waitForAbort().
+ then(assertIsStubError).
+ then(function() {
+ assertFlowHistory(/* none */);
+ });
+ });
+
+ it('abortsSequenceIfATaskFails', function() {
+ schedule('a');
+ schedule('b');
+ scheduleAction('c', throwStubError);
+ schedule('d'); // Should never execute.
+
+ return waitForAbort().
+ then(assertIsStubError).
+ then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ });
+
+ it('abortsFromUnhandledFramedTaskFailures_1', function() {
+ schedule('outer task').then(function() {
+ scheduleAction('inner task', throwStubError);
+ });
+ schedule('this should not run');
+ return waitForAbort().
+ then(assertIsStubError).
+ then(function() {
+ assertFlowHistory('outer task', 'inner task');
+ });
+ });
+
+ it('abortsFromUnhandledFramedTaskFailures_2', function() {
+ schedule('a').then(function() {
+ schedule('b').then(function() {
+ scheduleAction('c', throwStubError);
+ // This should not execute.
+ schedule('d');
+ });
+ });
+
+ return waitForAbort().
+ then(assertIsStubError).
+ then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ });
+
+ it('abortsWhenErrorBubblesUpFromFullyResolvingAnObject', function() {
+ var callback = sinon.spy();
+
+ scheduleAction('', function() {
+ var obj = {'foo': promise.rejected(new StubError)};
+ return promise.fullyResolved(obj).then(callback);
+ });
+
+ return waitForAbort().
+ then(assertIsStubError).
+ then(() => assert(!callback.called));
+ });
+
+ it('abortsWhenErrorBubblesUpFromFullyResolvingAnObject_withCallback', function() {
+ var callback1 = sinon.spy();
+ var callback2 = sinon.spy();
+
+ scheduleAction('', function() {
+ var obj = {'foo': promise.rejected(new StubError)};
+ return promise.fullyResolved(obj).then(callback1);
+ }).then(callback2);
+
+ return waitForAbort().
+ then(assertIsStubError).
+ then(() => assert(!callback1.called)).
+ then(() => assert(!callback2.called));
+ });
+
+ it('canCatchErrorsFromNestedTasks', function() {
+ var errback = sinon.spy();
+ schedule('a').
+ then(function() {
+ return scheduleAction('b', throwStubError);
+ }).
+ catch(errback);
+ return waitForIdle().then(function() {
+ assert(errback.called);
+ assertIsStubError(errback.getCall(0).args[0]);
+ });
+ });
+
+ it('nestedCommandFailuresCanBeCaughtAndSuppressed', function() {
+ var errback = sinon.spy();
+ schedule('a').then(function() {
+ return schedule('b').then(function() {
+ return schedule('c').then(function() {
+ throw new StubError;
+ });
+ });
+ }).catch(errback);
+ schedule('d');
+ return waitForIdle().
+ then(function() {
+ assert(errback.called);
+ assertIsStubError(errback.getCall(0).args[0]);
+ assertFlowHistory('a', 'b', 'c', 'd');
+ });
+ });
+
+ it('aTaskWithAnUnhandledPromiseRejection', function() {
+ schedule('a');
+ scheduleAction('sub-tasks', function() {
+ promise.rejected(new StubError);
+ });
+ schedule('should never run');
+
+ return waitForAbort().
+ then(assertIsStubError).
+ then(function() {
+ assertFlowHistory('a', 'sub-tasks');
+ });
+ });
+
+ it('aTaskThatReutrnsARejectedPromise', function() {
+ schedule('a');
+ scheduleAction('sub-tasks', function() {
+ return promise.rejected(new StubError);
+ });
+ schedule('should never run');
+
+ return waitForAbort().
+ then(assertIsStubError).
+ then(function() {
+ assertFlowHistory('a', 'sub-tasks');
+ });
+ });
+
+ it('discardsSubtasksIfTaskThrows', function() {
+ var pair = callbackPair(null, assertIsStubError);
+ scheduleAction('a', function() {
+ schedule('b');
+ schedule('c');
+ throwStubError();
+ }).then(pair.callback, pair.errback);
+ schedule('d');
+
+ return waitForIdle().
+ then(pair.assertErrback).
+ then(function() {
+ assertFlowHistory('a', 'd');
+ });
+ });
+
+ it('discardsRemainingSubtasksIfASubtaskFails', function() {
+ var pair = callbackPair(null, assertIsStubError);
+ scheduleAction('a', function() {
+ schedule('b');
+ scheduleAction('c', throwStubError);
+ schedule('d');
+ }).then(pair.callback, pair.errback);
+ schedule('e');
+
+ return waitForIdle().
+ then(pair.assertErrback).
+ then(function() {
+ assertFlowHistory('a', 'b', 'c', 'e');
+ });
+ });
+ });
+
+ describe('testTryModelingFinally', function() {
+ it('happyPath', function() {
+ /* Model:
+ try {
+ doFoo();
+ doBar();
+ } finally {
+ doBaz();
+ }
+ */
+ schedulePush('foo').
+ then(() => schedulePush('bar')).
+ finally(() => schedulePush('baz'));
+ return waitForIdle().then(assertingMessages('foo', 'bar', 'baz'));
+ });
+
+ it('firstTryFails', function() {
+ /* Model:
+ try {
+ doFoo();
+ doBar();
+ } finally {
+ doBaz();
+ }
+ */
+
+ scheduleAction('doFoo and throw', function() {
+ messages.push('foo');
+ throw new StubError;
+ }).
+ then(function() { schedulePush('bar'); }).
+ finally(function() { schedulePush('baz'); });
+
+ return waitForAbort().
+ then(assertIsStubError).
+ then(assertingMessages('foo', 'baz'));
+ });
+
+ it('secondTryFails', function() {
+ /* Model:
+ try {
+ doFoo();
+ doBar();
+ } finally {
+ doBaz();
+ }
+ */
+
+ schedulePush('foo').
+ then(function() {
+ return scheduleAction('doBar and throw', function() {
+ messages.push('bar');
+ throw new StubError;
+ });
+ }).
+ finally(function() {
+ return schedulePush('baz');
+ });
+ return waitForAbort().
+ then(assertIsStubError).
+ then(assertingMessages('foo', 'bar', 'baz'));
+ });
+ });
+
+ describe('testTaskCallbacksInterruptFlow', function() {
+ it('(base case)', function() {
+ schedule('a').then(function() {
+ schedule('b');
+ });
+ schedule('c');
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ });
+
+ it('taskDependsOnImmediatelyFulfilledPromise', function() {
+ scheduleAction('a', function() {
+ return promise.fulfilled();
+ }).then(function() {
+ schedule('b');
+ });
+ schedule('c');
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ });
+
+ it('taskDependsOnPreviouslyFulfilledPromise', function() {
+ var aPromise = promise.fulfilled(123);
+ scheduleAction('a', function() {
+ return aPromise;
+ }).then(function(value) {
+ assert.equal(123, value);
+ schedule('b');
+ });
+ schedule('c');
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ });
+
+ it('taskDependsOnAsyncPromise', function() {
+ scheduleAction('a', function() {
+ return promise.delayed(25);
+ }).then(function() {
+ schedule('b');
+ });
+ schedule('c');
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ });
+
+ it('promiseChainedToTaskInterruptFlow', function() {
+ schedule('a').then(function() {
+ return promise.fulfilled();
+ }).then(function() {
+ return promise.fulfilled();
+ }).then(function() {
+ schedule('b');
+ });
+ schedule('c');
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ });
+
+ it('nestedTaskCallbacksInterruptFlowWhenResolved', function() {
+ schedule('a').then(function() {
+ schedule('b').then(function() {
+ schedule('c');
+ });
+ });
+ schedule('d');
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c', 'd');
+ });
+ });
+ });
+
+ describe('testDelayedNesting', function() {
+
+ it('1', function() {
+ var a = schedule('a');
+ schedule('b').then(function() {
+ a.then(function() { schedule('c'); });
+ schedule('d');
+ });
+ schedule('e');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c', 'd', 'e');
+ });
+ });
+
+ it('2', function() {
+ var a = schedule('a');
+ schedule('b').then(function() {
+ a.then(function() { schedule('c'); });
+ schedule('d');
+ a.then(function() { schedule('e'); });
+ });
+ schedule('f');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f');
+ });
+ });
+
+ it('3', function() {
+ var a = schedule('a');
+ schedule('b').then(function() {
+ a.then(function() { schedule('c'); });
+ a.then(function() { schedule('d'); });
+ });
+ schedule('e');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c', 'd', 'e');
+ });
+ });
+
+ it('4', function() {
+ var a = schedule('a');
+ schedule('b').then(function() {
+ a.then(function() { schedule('c'); }).then(function() {
+ schedule('d');
+ });
+ a.then(function() { schedule('e'); });
+ });
+ schedule('f');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f');
+ });
+ });
+
+ it('5', function() {
+ var a = schedule('a');
+ schedule('b').then(function() {
+ var c;
+ a.then(function() { c = schedule('c'); }).then(function() {
+ schedule('d');
+ a.then(function() { schedule('e'); });
+ c.then(function() { schedule('f'); });
+ schedule('g');
+ });
+ a.then(function() { schedule('h'); });
+ });
+ schedule('i');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i');
+ });
+ });
+ });
+
+ describe('testWaiting', function() {
+ it('onAConditionThatIsAlwaysTrue', function() {
+ scheduleWait(function() { return true;}, 0, 'waiting on true');
+ return waitForIdle().then(function() {
+ assertFlowHistory('0: waiting on true');
+ });
+ });
+
+ it('aSimpleCountingCondition', function() {
+ var count = 0;
+ scheduleWait(function() {
+ return ++count == 3;
+ }, 100, 'counting to 3');
+
+ return waitForIdle().then(function() {
+ assert.equal(3, count);
+ });
+ });
+
+ it('aConditionThatReturnsAPromise', function() {
+ var d = new promise.Deferred();
+ var count = 0;
+
+ scheduleWait(function() {
+ count += 1;
+ return d.promise;
+ }, 0, 'waiting for promise');
+
+ return timeout(50).then(function() {
+ assert.equal(1, count);
+ d.fulfill(123);
+ return waitForIdle();
+ });
+ });
+
+ it('aConditionThatReturnsAPromise_2', function() {
+ var count = 0;
+ scheduleWait(function() {
+ return promise.fulfilled(++count == 3);
+ }, 100, 'waiting for promise');
+
+ return waitForIdle().then(function() {
+ assert.equal(3, count);
+ });
+ });
+
+ it('aConditionThatReturnsATaskResult', function() {
+ var count = 0;
+ scheduleWait(function() {
+ return scheduleAction('increment count', function() {
+ return ++count == 3;
+ });
+ }, 100, 'counting to 3');
+ schedule('post wait');
+
+ return waitForIdle().then(function() {
+ assert.equal(3, count);
+ assertFlowHistory(
+ '0: counting to 3', 'increment count',
+ '1: counting to 3', 'increment count',
+ '2: counting to 3', 'increment count',
+ 'post wait');
+ });
+ });
+
+ it('conditionContainsASubtask', function() {
+ var count = 0;
+ scheduleWait(function() {
+ schedule('sub task');
+ return ++count == 3;
+ }, 100, 'counting to 3');
+ schedule('post wait');
+
+ return waitForIdle().then(function() {
+ assert.equal(3, count);
+ assertFlowHistory(
+ '0: counting to 3', 'sub task',
+ '1: counting to 3', 'sub task',
+ '2: counting to 3', 'sub task',
+ 'post wait');
+ });
+ });
+
+ it('cancelsWaitIfScheduledTaskFails', function() {
+ var pair = callbackPair(null, assertIsStubError);
+ scheduleWait(function() {
+ scheduleAction('boom', throwStubError);
+ schedule('this should not run');
+ return true;
+ }, 100, 'waiting to go boom').then(pair.callback, pair.errback);
+ schedule('post wait');
+
+ return waitForIdle().
+ then(pair.assertErrback).
+ then(function() {
+ assertFlowHistory(
+ '0: waiting to go boom', 'boom',
+ 'post wait');
+ });
+ });
+
+ it('failsIfConditionThrows', function() {
+ var callbacks = callbackPair(null, assertIsStubError);
+ scheduleWait(throwStubError, 0, 'goes boom').
+ then(callbacks.callback, callbacks.errback);
+ schedule('post wait');
+
+ return waitForIdle().
+ then(callbacks.assertErrback).
+ then(function() {
+ assertFlowHistory('0: goes boom', 'post wait');
+ });
+ });
+
+ it('failsIfConditionReturnsARejectedPromise', function() {
+ var callbacks = callbackPair(null, assertIsStubError);
+ scheduleWait(function() {
+ return promise.rejected(new StubError);
+ }, 0, 'goes boom').then(callbacks.callback, callbacks.errback);
+ schedule('post wait');
+
+ return waitForIdle().
+ then(callbacks.assertErrback).
+ then(function() {
+ assertFlowHistory('0: goes boom', 'post wait');
+ });
+ });
+
+ it('failsIfConditionHasUnhandledRejection', function() {
+ var callbacks = callbackPair(null, assertIsStubError);
+ scheduleWait(function() {
+ promise.controlFlow().execute(throwStubError);
+ }, 0, 'goes boom').then(callbacks.callback, callbacks.errback);
+ schedule('post wait');
+
+ return waitForIdle().
+ then(callbacks.assertErrback).
+ then(function() {
+ assertFlowHistory('0: goes boom', 'post wait');
+ });
+ });
+
+ it('failsIfConditionHasAFailedSubtask', function() {
+ var callbacks = callbackPair(null, assertIsStubError);
+ var count = 0;
+ scheduleWait(function() {
+ scheduleAction('maybe throw', function() {
+ if (++count == 2) {
+ throw new StubError;
+ }
+ });
+ }, 100, 'waiting').then(callbacks.callback, callbacks.errback);
+ schedule('post wait');
+
+ return waitForIdle().then(function() {
+ assert.equal(2, count);
+ assertFlowHistory(
+ '0: waiting', 'maybe throw',
+ '1: waiting', 'maybe throw',
+ 'post wait');
+ });
+ });
+
+ it('pollingLoopWaitsForAllScheduledTasksInCondition', function() {
+ var count = 0;
+ scheduleWait(function() {
+ scheduleAction('increment count', function() { ++count; });
+ return count >= 3;
+ }, 100, 'counting to 3');
+ schedule('post wait');
+
+ return waitForIdle().then(function() {
+ assert.equal(4, count);
+ assertFlowHistory(
+ '0: counting to 3', 'increment count',
+ '1: counting to 3', 'increment count',
+ '2: counting to 3', 'increment count',
+ '3: counting to 3', 'increment count',
+ 'post wait');
+ });
+ });
+
+ it('waitsForeverOnAZeroTimeout', function() {
+ var done = false;
+ setTimeout(function() {
+ done = true;
+ }, 150);
+ var waitResult = scheduleWait(function() {
+ return done;
+ }, 0);
+
+ return timeout(75).then(function() {
+ assert.ok(!done);
+ return timeout(100);
+ }).then(function() {
+ assert.ok(done);
+ return waitResult;
+ });
+ });
+
+ it('waitsForeverIfTimeoutOmitted', function() {
+ var done = false;
+ setTimeout(function() {
+ done = true;
+ }, 150);
+ var waitResult = scheduleWait(function() {
+ return done;
+ });
+
+ return timeout(75).then(function() {
+ assert.ok(!done);
+ return timeout(100);
+ }).then(function() {
+ assert.ok(done);
+ return waitResult;
+ });
+ });
+
+ it('timesOut_nonZeroTimeout', function() {
+ var count = 0;
+ scheduleWait(function() {
+ count += 1;
+ var ms = count === 2 ? 65 : 5;
+ return promise.delayed(ms).then(function() {
+ return false;
+ });
+ }, 60, 'counting to 3');
+ return waitForAbort().then(function(e) {
+ switch (count) {
+ case 1:
+ assertFlowHistory('0: counting to 3');
+ break;
+ case 2:
+ assertFlowHistory('0: counting to 3', '1: counting to 3');
+ break;
+ default:
+ fail('unexpected polling count: ' + count);
+ }
+ assert.ok(
+ /^counting to 3\nWait timed out after \d+ms$/.test(e.message));
+ });
+ });
+
+ it('shouldFailIfConditionReturnsARejectedPromise', function() {
+ scheduleWait(function() {
+ return promise.rejected(new StubError);
+ }, 100, 'returns rejected promise on first pass');
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ it('scheduleWithIntermittentWaits', function() {
+ schedule('a');
+ scheduleWait(function() { return true; }, 0, 'wait 1');
+ schedule('b');
+ scheduleWait(function() { return true; }, 0, 'wait 2');
+ schedule('c');
+ scheduleWait(function() { return true; }, 0, 'wait 3');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', '0: wait 1', 'b', '0: wait 2', 'c', '0: wait 3');
+ });
+ });
+
+ it('scheduleWithIntermittentAndNestedWaits', function() {
+ schedule('a');
+ scheduleWait(function() { return true; }, 0, 'wait 1').
+ then(function() {
+ schedule('d');
+ scheduleWait(function() { return true; }, 0, 'wait 2');
+ schedule('e');
+ });
+ schedule('b');
+ scheduleWait(function() { return true; }, 0, 'wait 3');
+ schedule('c');
+ scheduleWait(function() { return true; }, 0, 'wait 4');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory(
+ 'a', '0: wait 1', 'd', '0: wait 2', 'e', 'b', '0: wait 3', 'c',
+ '0: wait 4');
+ });
+ });
+
+ it('requiresConditionToBeAPromiseOrFunction', function() {
+ assert.throws(function() {
+ flow.wait(1234, 0);
+ });
+ flow.wait(function() { return true;}, 0);
+ flow.wait(promise.fulfilled(), 0);
+ return waitForIdle();
+ });
+
+ it('promiseThatDoesNotResolveBeforeTimeout', function() {
+ var d = promise.defer();
+ flow.wait(d.promise, 5).then(fail, function(e) {
+ assert.ok(
+ /Timed out waiting for promise to resolve after \d+ms/
+ .test(e.message),
+ 'unexpected error message: ' + e.message);
+ });
+ return waitForIdle().then(function() {
+ assert.ok('Promise should not be cancelled', d.promise.isPending());
+ });
+ });
+
+ it('unboundedWaitOnPromiseResolution', function() {
+ var messages = [];
+ var d = promise.defer();
+ var waitResult = flow.wait(d.promise).then(function(value) {
+ messages.push('b');
+ assert.equal(1234, value);
+ });
+ setTimeout(function() {
+ messages.push('a');
+ }, 5);
+
+ timeout(10).then(function() {
+ assert.deepEqual(['a'], messages);
+ assert.ok(waitResult.isPending());
+ d.fulfill(1234);
+ return waitResult;
+ }).then(function() {
+ assert.deepEqual(['a', 'b'], messages);
+ });
+
+ return waitForIdle();
+ });
+ });
+
+ describe('testSubtasks', function() {
+ it('(base case)', function() {
+ schedule('a');
+ scheduleAction('sub-tasks', function() {
+ schedule('c');
+ schedule('d');
+ });
+ schedule('b');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'sub-tasks', 'c', 'd', 'b');
+ });
+ });
+
+ it('nesting', function() {
+ schedule('a');
+ scheduleAction('sub-tasks', function() {
+ schedule('b');
+ scheduleAction('sub-sub-tasks', function() {
+ schedule('c');
+ schedule('d');
+ });
+ schedule('e');
+ });
+ schedule('f');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory(
+ 'a', 'sub-tasks', 'b', 'sub-sub-tasks', 'c', 'd', 'e', 'f');
+ });
+ });
+
+ it('taskReturnsSubTaskResult_1', function() {
+ schedule('a');
+ scheduleAction('sub-tasks', function() {
+ return schedule('c');
+ });
+ schedule('b');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'sub-tasks', 'c', 'b');
+ });
+ });
+
+ it('taskReturnsSubTaskResult_2', function() {
+ let pair = callbackPair((value) => assert.equal(123, value));
+ schedule('a');
+ schedule('sub-tasks', promise.fulfilled(123)).then(pair.callback);
+ schedule('b');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'sub-tasks','b');
+ pair.assertCallback();
+ });
+ });
+
+ it('taskReturnsPromiseThatDependsOnSubtask_1', function() {
+ scheduleAction('a', function() {
+ return promise.delayed(10).then(function() {
+ schedule('b');
+ });
+ });
+ schedule('c');
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ });
+
+ it('taskReturnsPromiseThatDependsOnSubtask_2', function() {
+ scheduleAction('a', function() {
+ return promise.fulfilled().then(function() {
+ schedule('b');
+ });
+ });
+ schedule('c');
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ });
+
+ it('taskReturnsPromiseThatDependsOnSubtask_3', function() {
+ scheduleAction('a', function() {
+ return promise.delayed(10).then(function() {
+ return schedule('b');
+ });
+ });
+ schedule('c');
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ });
+
+ it('taskReturnsPromiseThatDependsOnSubtask_4', function() {
+ scheduleAction('a', function() {
+ return promise.delayed(5).then(function() {
+ return promise.delayed(5).then(function() {
+ return schedule('b');
+ });
+ });
+ });
+ schedule('c');
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ });
+
+ it('taskReturnsPromiseThatDependsOnSubtask_5', function() {
+ scheduleAction('a', function() {
+ return promise.delayed(5).then(function() {
+ return promise.delayed(5).then(function() {
+ return promise.delayed(5).then(function() {
+ return promise.delayed(5).then(function() {
+ return schedule('b');
+ });
+ });
+ });
+ });
+ });
+ schedule('c');
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ });
+
+ it('taskReturnsPromiseThatDependsOnSubtask_6', function() {
+ scheduleAction('a', function() {
+ return promise.delayed(5).
+ then(function() { return promise.delayed(5) }).
+ then(function() { return promise.delayed(5) }).
+ then(function() { return promise.delayed(5) }).
+ then(function() { return schedule('b'); });
+ });
+ schedule('c');
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'b', 'c');
+ });
+ });
+
+ it('subTaskFails_1', function() {
+ schedule('a');
+ scheduleAction('sub-tasks', function() {
+ scheduleAction('sub-task that fails', throwStubError);
+ });
+ schedule('should never execute');
+
+ return waitForAbort().
+ then(assertIsStubError).
+ then(function() {
+ assertFlowHistory('a', 'sub-tasks', 'sub-task that fails');
+ });
+ });
+
+ it('subTaskFails_2', function() {
+ schedule('a');
+ scheduleAction('sub-tasks', function() {
+ return promise.rejected(new StubError);
+ });
+ schedule('should never execute');
+
+ return waitForAbort().
+ then(assertIsStubError).
+ then(function() {
+ assertFlowHistory('a', 'sub-tasks');
+ });
+ });
+
+ it('subTaskFails_3', function() {
+ var callbacks = callbackPair(null, assertIsStubError);
+
+ schedule('a');
+ scheduleAction('sub-tasks', function() {
+ return promise.rejected(new StubError);
+ }).then(callbacks.callback, callbacks.errback);
+ schedule('b');
+
+ return waitForIdle().
+ then(function() {
+ assertFlowHistory('a', 'sub-tasks', 'b');
+ callbacks.assertErrback();
+ });
+ });
+ });
+
+ describe('testEventLoopWaitsOnPendingPromiseRejections', function() {
+ it('oneRejection', function() {
+ var d = new promise.Deferred;
+ scheduleAction('one', function() {
+ return d.promise;
+ });
+ scheduleAction('two', function() {});
+
+ return timeout(50).then(function() {
+ assertFlowHistory('one');
+ d.reject(new StubError);
+ return waitForAbort();
+ }).
+ then(assertIsStubError).
+ then(function() {
+ assertFlowHistory('one');
+ });
+ });
+
+ it('multipleRejections', function() {
+ var once = Error('once');
+ var twice = Error('twice');
+
+ scheduleAction('one', function() {
+ promise.rejected(once);
+ promise.rejected(twice);
+ });
+ var twoResult = scheduleAction('two', function() {});
+
+ flow.removeAllListeners(
+ promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION);
+ return new NativePromise(function(fulfill, reject) {
+ setTimeout(function() {
+ reject(Error('Should have reported the two errors by now'));
+ }, 50);
+ flow.on(
+ promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION,
+ fulfill);
+ }).then(function(e) {
+ assert.ok(e instanceof promise.MultipleUnhandledRejectionError,
+ 'Not a MultipleUnhandledRejectionError');
+ let errors = Array.from(e.errors);
+ assert.deepEqual([once, twice], errors);
+ assertFlowHistory('one');
+ assert.ok(!twoResult.isPending(), 'Did not cancel the second task');
+ });
+ });
+ });
+
+ describe('testCancelsPromiseReturnedByCallbackIfFrameFails', function() {
+ it('promiseCallback', function() {
+ var chainPair = callbackPair(null, assertIsStubError);
+ var deferredPair = callbackPair(null, function(e) {
+ assert.equal('CancellationError: StubError', e.toString(),
+ 'callback result should be cancelled');
+ });
+
+ var d = new promise.Deferred();
+ d.promise.then(deferredPair.callback, deferredPair.errback);
+
+ promise.fulfilled().
+ then(function() {
+ scheduleAction('boom', throwStubError);
+ schedule('this should not run');
+ return d.promise;
+ }).
+ then(chainPair.callback, chainPair.errback);
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('boom');
+ chainPair.assertErrback('chain errback not invoked');
+ deferredPair.assertErrback('deferred errback not invoked');
+ });
+ });
+
+ it('taskCallback', function() {
+ var chainPair = callbackPair(null, assertIsStubError);
+ var deferredPair = callbackPair(null, function(e) {
+ assert.equal('CancellationError: StubError', e.toString(),
+ 'callback result should be cancelled');
+ });
+
+ var d = new promise.Deferred();
+ d.promise.then(deferredPair.callback, deferredPair.errback);
+
+ schedule('a').
+ then(function() {
+ scheduleAction('boom', throwStubError);
+ schedule('this should not run');
+ return d.promise;
+ }).
+ then(chainPair.callback, chainPair.errback);
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'boom');
+ chainPair.assertErrback('chain errback not invoked');
+ deferredPair.assertErrback('deferred errback not invoked');
+ });
+ });
+ });
+
+ it('testMaintainsOrderInCallbacksWhenATaskReturnsAPromise', function() {
+ schedule('__start__', promise.fulfilled()).
+ then(function() {
+ messages.push('a');
+ schedulePush('b');
+ messages.push('c');
+ }).
+ then(function() {
+ messages.push('d');
+ });
+ schedulePush('e');
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('__start__', 'b', 'e');
+ assertMessages('a', 'c', 'b', 'd', 'e');
+ });
+ });
+
+ it('testOwningFlowIsActivatedForExecutingTasks', function() {
+ var defaultFlow = promise.controlFlow();
+ var order = [];
+
+ promise.createFlow(function(flow) {
+ assertFlowIs(flow);
+ order.push(0);
+
+ defaultFlow.execute(function() {
+ assertFlowIs(defaultFlow);
+ order.push(1);
+ });
+ });
+
+ return waitForIdle().then(function() {
+ assertFlowIs(defaultFlow);
+ assert.deepEqual([0, 1], order);
+ });
+ });
+
+ it('testCreateFlowReturnsPromisePairedWithCreatedFlow', function() {
+ return new NativePromise(function(fulfill, reject) {
+ var newFlow;
+ promise.createFlow(function(flow) {
+ newFlow = flow;
+ assertFlowIs(newFlow);
+ }).then(function() {
+ assertFlowIs(newFlow);
+ waitForIdle(newFlow).then(fulfill, reject);
+ });
+ });
+ });
+
+ it('testDeferredFactoriesCreateForActiveFlow_defaultFlow', function() {
+ var e = Error();
+ var defaultFlow = promise.controlFlow();
+ promise.fulfilled().then(function() {
+ assertFlowIs(defaultFlow);
+ });
+ promise.rejected(e).then(null, function(err) {
+ assert.equal(e, err);
+ assertFlowIs(defaultFlow);
+ });
+ promise.defer().promise.then(function() {
+ assertFlowIs(defaultFlow);
+ });
+
+ return waitForIdle();
+ });
+
+ it('testDeferredFactoriesCreateForActiveFlow_newFlow', function() {
+ var e = Error();
+ var newFlow = new promise.ControlFlow;
+ newFlow.execute(function() {
+ promise.fulfilled().then(function() {
+ assertFlowIs(newFlow);
+ });
+
+ promise.rejected(e).then(null, function(err) {
+ assert.equal(e, err);
+ assertFlowIs(newFlow);
+ });
+
+ let d = promise.defer();
+ d.promise.then(function() {
+ assertFlowIs(newFlow);
+ });
+ d.fulfill();
+ }).then(function() {
+ assertFlowIs(newFlow);
+ });
+
+ return waitForIdle(newFlow);
+ });
+
+ it('testFlowsSynchronizeWithThemselvesNotEachOther', function() {
+ var defaultFlow = promise.controlFlow();
+ schedulePush('a', 'a');
+ promise.controlFlow().timeout(250);
+ schedulePush('b', 'b');
+
+ promise.createFlow(function() {
+ schedulePush('c', 'c');
+ schedulePush('d', 'd');
+ });
+
+ return waitForIdle().then(function() {
+ assertMessages('a', 'c', 'd', 'b');
+ });
+ });
+
+ it('testUnhandledErrorsAreReportedToTheOwningFlow', function() {
+ var error1 = Error('e1');
+ var error2 = Error('e2');
+
+ var defaultFlow = promise.controlFlow();
+ defaultFlow.removeAllListeners('uncaughtException');
+
+ var flow1Error = NativePromise.defer();
+ flow1Error.promise.then(function(value) {
+ assert.equal(error2, value);
+ });
+
+ var flow2Error = NativePromise.defer();
+ flow2Error.promise.then(function(value) {
+ assert.equal(error1, value);
+ });
+
+ promise.createFlow(function(flow) {
+ flow.once('uncaughtException', flow2Error.resolve);
+ promise.rejected(error1);
+
+ defaultFlow.once('uncaughtException', flow1Error.resolve);
+ defaultFlow.execute(function() {
+ promise.rejected(error2);
+ });
+ });
+
+ return NativePromise.all([flow1Error.promise, flow2Error.promise]);
+ });
+
+ it('testCanSynchronizeFlowsByReturningPromiseFromOneToAnother', function() {
+ var flow1 = new promise.ControlFlow;
+ var flow1Done = NativePromise.defer();
+ flow1.once('idle', flow1Done.resolve);
+ flow1.once('uncaughtException', flow1Done.reject);
+
+ var flow2 = new promise.ControlFlow;
+ var flow2Done = NativePromise.defer();
+ flow2.once('idle', flow2Done.resolve);
+ flow2.once('uncaughtException', flow2Done.reject);
+
+ flow1.execute(function() {
+ schedulePush('a', 'a');
+ return promise.delayed(25);
+ }, 'start flow 1');
+
+ flow2.execute(function() {
+ schedulePush('b', 'b');
+ schedulePush('c', 'c');
+ flow2.execute(function() {
+ return flow1.execute(function() {
+ schedulePush('d', 'd');
+ }, 'flow 1 task');
+ }, 'inject flow1 result into flow2');
+ schedulePush('e', 'e');
+ }, 'start flow 2');
+
+ return NativePromise.all([flow1Done.promise, flow2Done.promise]).
+ then(function() {
+ assertMessages('a', 'b', 'c', 'd', 'e');
+ });
+ });
+
+ it('testFramesWaitToCompleteForPendingRejections', function() {
+ return new NativePromise(function(fulfill, reject) {
+
+ promise.controlFlow().execute(function() {
+ promise.rejected(new StubError);
+ }).then(fulfill, reject);
+
+ }).
+ then(() => fail('expected to fail'), assertIsStubError);
+ });
+
+ it('testSynchronizeErrorsPropagateToOuterFlow', function() {
+ var outerFlow = new promise.ControlFlow;
+ var innerFlow = new promise.ControlFlow;
+
+ var block = NativePromise.defer();
+ innerFlow.execute(function() {
+ return block.promise;
+ }, 'block inner flow');
+
+ outerFlow.execute(function() {
+ block.resolve();
+ return innerFlow.execute(function() {
+ promise.rejected(new StubError);
+ }, 'trigger unhandled rejection error');
+ }, 'run test');
+
+ return NativePromise.all([
+ waitForIdle(innerFlow),
+ waitForAbort(outerFlow).then(assertIsStubError)
+ ]);
+ });
+
+ it('testFailsIfErrbackThrows', function() {
+ promise.rejected('').then(null, throwStubError);
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ it('testFailsIfCallbackReturnsRejectedPromise', function() {
+ promise.fulfilled().then(function() {
+ return promise.rejected(new StubError);
+ });
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ it('testAbortsFrameIfTaskFails', function() {
+ promise.fulfilled().then(function() {
+ promise.controlFlow().execute(throwStubError);
+ });
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ it('testAbortsFramePromisedChainedFromTaskIsNotHandled', function() {
+ promise.fulfilled().then(function() {
+ promise.controlFlow().execute(function() {}).
+ then(throwStubError);
+ });
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ it('testTrapsChainedUnhandledRejectionsWithinAFrame', function() {
+ var pair = callbackPair(null, assertIsStubError);
+ promise.fulfilled().then(function() {
+ promise.controlFlow().execute(function() {}).
+ then(throwStubError);
+ }).then(pair.callback, pair.errback);
+
+ return waitForIdle().then(pair.assertErrback);
+ });
+
+ it('testCancelsRemainingTasksIfFrameThrowsDuringScheduling', function() {
+ var task1, task2;
+ var pair = callbackPair(null, assertIsStubError);
+ var flow = promise.controlFlow();
+ flow.execute(function() {
+ task1 = flow.execute(function() {});
+ task2 = flow.execute(function() {});
+ throw new StubError;
+ }).then(pair.callback, pair.errback);
+
+ return waitForIdle().
+ then(pair.assertErrback).
+ then(function() {
+ assert.ok(!task1.isPending());
+ pair = callbackPair();
+ return task1.then(pair.callback, pair.errback);
+ }).
+ then(function() {
+ pair.assertErrback();
+ assert.ok(!task2.isPending());
+ pair = callbackPair();
+ return task2.then(pair.callback, pair.errback);
+ }).
+ then(function() {
+ pair.assertErrback();
+ });
+ });
+
+ it('testCancelsRemainingTasksInFrameIfATaskFails', function() {
+ var task;
+ var pair = callbackPair(null, assertIsStubError);
+ var flow = promise.controlFlow();
+ flow.execute(function() {
+ flow.execute(throwStubError);
+ task = flow.execute(function() {});
+ }).then(pair.callback, pair.errback);
+
+ return waitForIdle().then(pair.assertErrback).then(function() {
+ assert.ok(!task.isPending());
+ pair = callbackPair();
+ task.then(pair.callback, pair.errback);
+ }).then(function() {
+ pair.assertErrback();
+ });
+ });
+
+ it('testDoesNotModifyRejectionErrorIfPromiseNotInsideAFlow', function() {
+ var error = Error('original message');
+ var originalStack = error.stack;
+ var originalStr = error.toString();
+
+ var pair = callbackPair(null, function(e) {
+ assert.equal(error, e);
+ assert.equal('original message', e.message);
+ assert.equal(originalStack, e.stack);
+ assert.equal(originalStr, e.toString());
+ });
+
+ promise.rejected(error).then(pair.callback, pair.errback);
+ return waitForIdle().then(pair.assertErrback);
+ });
+
+ /** See https://github.com/SeleniumHQ/selenium/issues/444 */
+ it('testMaintainsOrderWithPromiseChainsCreatedWithinAForeach_1', function() {
+ var messages = [];
+ flow.execute(function() {
+ return promise.fulfilled(['a', 'b', 'c', 'd']);
+ }, 'start').then(function(steps) {
+ steps.forEach(function(step) {
+ promise.fulfilled(step)
+ .then(function() {
+ messages.push(step + '.1');
+ }).then(function() {
+ messages.push(step + '.2');
+ });
+ })
+ });
+ return waitForIdle().then(function() {
+ assert.deepEqual(
+ ['a.1', 'a.2', 'b.1', 'b.2', 'c.1', 'c.2', 'd.1', 'd.2'],
+ messages);
+ });
+ });
+
+ /** See https://github.com/SeleniumHQ/selenium/issues/444 */
+ it('testMaintainsOrderWithPromiseChainsCreatedWithinAForeach_2', function() {
+ var messages = [];
+ flow.execute(function() {
+ return promise.fulfilled(['a', 'b', 'c', 'd']);
+ }, 'start').then(function(steps) {
+ steps.forEach(function(step) {
+ promise.fulfilled(step)
+ .then(function() {
+ messages.push(step + '.1');
+ }).then(function() {
+ flow.execute(function() {}, step + '.2').then(function() {
+ messages.push(step + '.2');
+ });
+ });
+ })
+ });
+ return waitForIdle().then(function() {
+ assert.deepEqual(
+ ['a.1', 'a.2', 'b.1', 'b.2', 'c.1', 'c.2', 'd.1', 'd.2'],
+ messages);
+ });
+ });
+
+ /** See https://github.com/SeleniumHQ/selenium/issues/444 */
+ it('testMaintainsOrderWithPromiseChainsCreatedWithinAForeach_3', function() {
+ var messages = [];
+ flow.execute(function() {
+ return promise.fulfilled(['a', 'b', 'c', 'd']);
+ }, 'start').then(function(steps) {
+ steps.forEach(function(step) {
+ promise.fulfilled(step)
+ .then(function(){})
+ .then(function() {
+ messages.push(step + '.1');
+ return flow.execute(function() {}, step + '.1');
+ }).then(function() {
+ flow.execute(function() {}, step + '.2').then(function(text) {
+ messages.push(step + '.2');
+ });
+ });
+ })
+ });
+ return waitForIdle().then(function() {
+ assert.deepEqual(
+ ['a.1', 'a.2', 'b.1', 'b.2', 'c.1', 'c.2', 'd.1', 'd.2'],
+ messages);
+ });
+ });
+
+ /** See https://github.com/SeleniumHQ/selenium/issues/363 */
+ it('testTasksScheduledInASeparateTurnOfTheEventLoopGetASeparateTaskQueue_2', function() {
+ scheduleAction('a', () => promise.delayed(10));
+ schedule('b');
+ setTimeout(() => schedule('c'), 0);
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'c', 'b');
+ });
+ });
+
+ /** See https://github.com/SeleniumHQ/selenium/issues/363 */
+ it('testTasksScheduledInASeparateTurnOfTheEventLoopGetASeparateTaskQueue_2', function() {
+ scheduleAction('a', () => promise.delayed(10));
+ schedule('b');
+ schedule('c');
+ setTimeout(function() {
+ schedule('d');
+ scheduleAction('e', () => promise.delayed(10));
+ schedule('f');
+ }, 0);
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'd', 'e', 'b', 'c', 'f');
+ });
+ });
+
+ /** See https://github.com/SeleniumHQ/selenium/issues/363 */
+ it('testCanSynchronizeTasksFromAdjacentTaskQueues', function() {
+ var task1 = scheduleAction('a', () => promise.delayed(10));
+ schedule('b');
+ setTimeout(function() {
+ scheduleAction('c', () => task1);
+ schedule('d');
+ }, 0);
+
+ return waitForIdle().then(function() {
+ assertFlowHistory('a', 'c', 'd', 'b');
+ });
+ });
+
+ describe('testCancellingAScheduledTask', function() {
+ it('1', function() {
+ var called = false;
+ var task1 = scheduleAction('a', () => called = true);
+ task1.cancel('no soup for you');
+
+ return waitForIdle().then(function() {
+ assert.ok(!called);
+ assertFlowHistory();
+ return task1.catch(function(e) {
+ assert.ok(e instanceof promise.CancellationError);
+ assert.equal('no soup for you', e.message);
+ });
+ });
+ });
+
+ it('2', function() {
+ schedule('a');
+ var called = false;
+ var task2 = scheduleAction('b', () => called = true);
+ schedule('c');
+
+ task2.cancel('no soup for you');
+
+ return waitForIdle().then(function() {
+ assert.ok(!called);
+ assertFlowHistory('a', 'c');
+ return task2.catch(function(e) {
+ assert.ok(e instanceof promise.CancellationError);
+ assert.equal('no soup for you', e.message);
+ });
+ });
+ });
+
+ it('3', function() {
+ var called = false;
+ var task = scheduleAction('a', () => called = true);
+ task.cancel(new StubError);
+
+ return waitForIdle().then(function() {
+ assert.ok(!called);
+ assertFlowHistory();
+ return task.catch(function(e) {
+ assert.ok(e instanceof promise.CancellationError);
+ });
+ });
+ });
+
+ it('4', function() {
+ var seen = [];
+ var task = scheduleAction('a', () => seen.push(1))
+ .then(() => seen.push(2))
+ .then(() => seen.push(3))
+ .then(() => seen.push(4))
+ .then(() => seen.push(5));
+ task.cancel(new StubError);
+
+ return waitForIdle().then(function() {
+ assert.deepEqual([], seen);
+ assertFlowHistory();
+ return task.catch(function(e) {
+ assert.ok(e instanceof promise.CancellationError);
+ });
+ });
+ });
+
+ it('fromWithinAnExecutingTask', function() {
+ var called = false;
+ var task;
+ scheduleAction('a', function() {
+ task.cancel('no soup for you');
+ });
+ task = scheduleAction('b', () => called = true);
+ schedule('c');
+
+ return waitForIdle().then(function() {
+ assert.ok(!called);
+ assertFlowHistory('a', 'c');
+ return task.catch(function(e) {
+ assert.ok(e instanceof promise.CancellationError);
+ assert.equal('no soup for you', e.message);
+ });
+ });
+ });
+ });
+
+ it('testCancellingAPendingTask', function() {
+ var order = [];
+ var unresolved = promise.defer();
+
+ var innerTask;
+ var outerTask = scheduleAction('a', function() {
+ order.push(1);
+
+ // Schedule a task that will never finish.
+ innerTask = scheduleAction('a.1', function() {
+ return unresolved.promise;
+ });
+
+ // 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.catch(function(e) {
+ order.push(2);
+ });
+ });
+ schedule('b');
+
+ outerTask.catch(function(e) {
+ order.push(3);
+ assert.ok(e instanceof promise.CancellationError);
+ assert.equal('no soup for you', e.message);
+ });
+
+ unresolved.promise.catch(function(e) {
+ order.push(4);
+ assert.ok(e instanceof promise.CancellationError);
+ });
+
+ return timeout(10).then(function() {
+ assert.deepEqual([1], order);
+ assert.ok(unresolved.promise.isPending());
+
+ outerTask.cancel('no soup for you');
+ return waitForIdle();
+ }).then(function() {
+ assertFlowHistory('a', 'a.1', 'b');
+ assert.deepEqual([1, 3, 4], order);
+ });
+ });
+
+ it('testCancellingAPendingPromiseCallback', function() {
+ var called = false;
+
+ var root = promise.fulfilled();
+ root.then(function() {
+ cb2.cancel('no soup for you');
+ });
+
+ var cb2 = root.then(fail, fail); // These callbacks should never be called.
+ cb2.then(fail, function(e) {
+ called = true;
+ assert.ok(e instanceof promise.CancellationError);
+ assert.equal('no soup for you', e.message);
+ });
+
+ return waitForIdle().then(function() {
+ assert.ok(called);
+ });
+ });
+
+ describe('testResetFlow', function() {
+ it('1', function() {
+ var called = 0;
+ var task = flow.execute(() => called++);
+ task.finally(() => called++);
+
+ return new Promise(function(fulfill) {
+ flow.once('reset', fulfill);
+ flow.reset();
+
+ }).then(function() {
+ assert.equal(0, called);
+ assert.ok(!task.isPending());
+ return task;
+
+ }).then(fail, function(e) {
+ assert.ok(e instanceof promise.CancellationError);
+ assert.equal('ControlFlow was reset', e.message);
+ });
+ });
+
+ it('2', function() {
+ var called = 0;
+ var task1 = flow.execute(() => called++);
+ task1.finally(() => called++);
+
+ var task2 = flow.execute(() => called++);
+ task2.finally(() => called++);
+
+ var task3 = flow.execute(() => called++);
+ task3.finally(() => called++);
+
+ return new Promise(function(fulfill) {
+ flow.once('reset', fulfill);
+ flow.reset();
+
+ }).then(function() {
+ assert.equal(0, called);
+ assert.ok(!task1.isPending());
+ assert.ok(!task2.isPending());
+ assert.ok(!task3.isPending());
+ });
+ });
+ });
+
+ describe('testPromiseFulfilledInsideTask', function() {
+ it('1', function() {
+ var order = [];
+
+ flow.execute(function() {
+ var d = promise.defer();
+
+ d.promise.then(() => order.push('a'));
+ d.promise.then(() => order.push('b'));
+ d.promise.then(() => order.push('c'));
+ d.fulfill();
+
+ flow.execute(() => order.push('d'));
+
+ }).then(() => order.push('fin'));
+
+ return waitForIdle().then(function() {
+ assert.deepEqual(['a', 'b', 'c', 'd', 'fin'], order);
+ });
+ });
+
+ it('2', function() {
+ var order = [];
+
+ flow.execute(function() {
+ flow.execute(() => order.push('a'));
+ flow.execute(() => order.push('b'));
+
+ var d = promise.defer();
+ d.promise.then(() => order.push('c'));
+ d.promise.then(() => order.push('d'));
+ d.fulfill();
+
+ flow.execute(() => order.push('e'));
+
+ }).then(() => order.push('fin'));
+
+ return waitForIdle().then(function() {
+ assert.deepEqual(['a', 'b', 'c', 'd', 'e', 'fin'], order);
+ });
+ });
+
+ it('3', function() {
+ var order = [];
+ var d = promise.defer();
+ d.promise.then(() => order.push('c'));
+ d.promise.then(() => order.push('d'));
+
+ flow.execute(function() {
+ flow.execute(() => order.push('a'));
+ flow.execute(() => order.push('b'));
+
+ d.promise.then(() => order.push('e'));
+ d.fulfill();
+
+ flow.execute(() => order.push('f'));
+
+ }).then(() => order.push('fin'));
+
+ return waitForIdle().then(function() {
+ assert.deepEqual(['c', 'd', 'a', 'b', 'e', 'f', 'fin'], order);
+ });
+ });
+
+ it('4', function() {
+ var order = [];
+ var d = promise.defer();
+ d.promise.then(() => order.push('a'));
+ d.promise.then(() => order.push('b'));
+
+ flow.execute(function() {
+ flow.execute(function() {
+ order.push('c');
+ flow.execute(() => order.push('d'));
+ d.promise.then(() => order.push('e'));
+ });
+ flow.execute(() => order.push('f'));
+
+ d.promise.then(() => order.push('g'));
+ d.fulfill();
+
+ flow.execute(() => order.push('h'));
+
+ }).then(() => order.push('fin'));
+
+ return waitForIdle().then(function() {
+ assert.deepEqual(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'fin'], order);
+ });
+ });
+ });
+
+ describe('testSettledPromiseCallbacksInsideATask', function() {
+ it('1', function() {
+ var order = [];
+ var p = promise.fulfilled();
+
+ flow.execute(function() {
+ flow.execute(() => order.push('a'));
+ p.then(() => order.push('b'));
+ flow.execute(() => order.push('c'));
+ p.then(() => order.push('d'));
+ }).then(() => order.push('fin'));
+
+ return waitForIdle().then(function() {
+ assert.deepEqual(['a', 'b', 'c', 'd', 'fin'], order);
+ });
+ });
+
+ it('2', function() {
+ var order = [];
+
+ flow.execute(function() {
+ flow.execute(() => order.push('a'))
+ .then( () => order.push('c'));
+ flow.execute(() => order.push('b'));
+ }).then(() => order.push('fin'));
+
+ return waitForIdle().then(function() {
+ assert.deepEqual(['a', 'c', 'b', 'fin'], order);
+ });
+ });
+ });
+
+ it('testTasksDoNotWaitForNewlyCreatedPromises', function() {
+ var order = [];
+
+ flow.execute(function() {
+ var d = promise.defer();
+
+ // This is a normal promise, not a task, so the task for this callback is
+ // considered volatile. Volatile tasks should be skipped when they reach
+ // the front of the task queue.
+ d.promise.then(() => order.push('a'));
+
+ flow.execute(() => order.push('b'));
+ flow.execute(function() {
+ flow.execute(() => order.push('c'));
+ d.promise.then(() => order.push('d'));
+ d.fulfill();
+ });
+ flow.execute(() => order.push('e'));
+
+ }).then(() => order.push('fin'));
+
+ return waitForIdle().then(function() {
+ assert.deepEqual(['b', 'a', 'c', 'd', 'e', 'fin'], order);
+ });
+ });
+
+ it('testCallbackDependenciesDoNotDeadlock', function() {
+ var order = [];
+ var root = promise.defer();
+ var dep = promise.fulfilled().then(function() {
+ order.push('a');
+ return root.promise.then(function() {
+ order.push('b');
+ });
+ });
+ // This callback depends on |dep|, which depends on another callback
+ // attached to |root| via a chain.
+ root.promise.then(function() {
+ order.push('c');
+ return dep.then(() => order.push('d'));
+ }).then(() => order.push('fin'));
+
+ setTimeout(() => root.fulfill(), 20);
+
+ return waitForIdle().then(function() {
+ assert.deepEqual(['a', 'b', 'c', 'd', 'fin'], order);
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/lib/promise_generator_test.js b/node_modules/selenium-webdriver/test/lib/promise_generator_test.js
new file mode 100644
index 000000000..5fdff9f80
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/lib/promise_generator_test.js
@@ -0,0 +1,308 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+const assert = require('assert');
+const promise = require('../../lib/promise');
+
+describe('promise.consume()', function() {
+ it('requires inputs to be generator functions', function() {
+ assert.throws(function() {
+ promise.consume(function() {});
+ });
+ });
+
+ it('handles a basic generator with no yielded promises', function() {
+ var values = [];
+ return promise.consume(function* () {
+ var i = 0;
+ while (i < 4) {
+ i = yield i + 1;
+ values.push(i);
+ }
+ }).then(function() {
+ assert.deepEqual([1, 2, 3, 4], values);
+ });
+ });
+
+ it('handles a promise yielding generator', function() {
+ var values = [];
+ return promise.consume(function* () {
+ var i = 0;
+ while (i < 4) {
+ // Test that things are actually async here.
+ setTimeout(function() {
+ values.push(i * 2);
+ }, 10);
+
+ yield promise.delayed(10).then(function() {
+ values.push(i++);
+ });
+ }
+ }).then(function() {
+ assert.deepEqual([0, 0, 2, 1, 4, 2, 6, 3], values);
+ });
+ });
+
+ it('assignemnts to yielded promises get fulfilled value', function() {
+ return promise.consume(function* () {
+ var p = promise.fulfilled(2);
+ var x = yield p;
+ assert.equal(2, x);
+ });
+ });
+
+ it('is possible to cancel promise generators', function() {
+ var values = [];
+ var p = promise.consume(function* () {
+ var i = 0;
+ while (i < 3) {
+ yield promise.delayed(100).then(function() {
+ values.push(i++);
+ });
+ }
+ });
+ return promise.delayed(75).then(function() {
+ p.cancel();
+ return p.catch(function() {
+ return promise.delayed(300);
+ });
+ }).then(function() {
+ assert.deepEqual([0], values);
+ });
+ });
+
+ it('uses final return value as fulfillment value', function() {
+ return promise.consume(function* () {
+ yield 1;
+ yield 2;
+ return 3;
+ }).then(function(value) {
+ assert.equal(3, value);
+ });
+ });
+
+ it('throws rejected promise errors within the generator', function() {
+ var values = [];
+ return promise.consume(function* () {
+ values.push('a');
+ var e = Error('stub error');
+ try {
+ yield promise.rejected(e);
+ values.push('b');
+ } catch (ex) {
+ assert.equal(e, ex);
+ values.push('c');
+ }
+ values.push('d');
+ }).then(function() {
+ assert.deepEqual(['a', 'c', 'd'], values);
+ });
+ });
+
+ it('aborts the generator if there is an unhandled rejection', function() {
+ var values = [];
+ var e = Error('stub error');
+ return promise.consume(function* () {
+ values.push(1);
+ yield promise.rejected(e);
+ values.push(2);
+ }).catch(function() {
+ assert.deepEqual([1], values);
+ });
+ });
+
+ it('yield waits for promises', function() {
+ var values = [];
+ var d = promise.defer();
+
+ setTimeout(function() {
+ assert.deepEqual([1], values);
+ d.fulfill(2);
+ }, 100);
+
+ return promise.consume(function* () {
+ values.push(1);
+ values.push((yield d.promise), 3);
+ }).then(function() {
+ assert.deepEqual([1, 2, 3], values);
+ });
+ });
+
+ it('accepts custom scopes', function() {
+ return promise.consume(function* () {
+ return this.name;
+ }, {name: 'Bob'}).then(function(value) {
+ assert.equal('Bob', value);
+ });
+ });
+
+ it('accepts initial generator arguments', function() {
+ return promise.consume(function* (a, b) {
+ assert.equal('red', a);
+ assert.equal('apples', b);
+ }, null, 'red', 'apples');
+ });
+
+ it('executes generator within the control flow', function() {
+ var promises = [
+ promise.defer(),
+ promise.defer()
+ ];
+ var values = [];
+
+ setTimeout(function() {
+ assert.deepEqual([], values);
+ promises[0].fulfill(1);
+ }, 100);
+
+ setTimeout(function() {
+ assert.deepEqual([1], values);
+ promises[1].fulfill(2);
+ }, 200);
+
+ return promise.controlFlow().execute(function* () {
+ values.push(yield promises[0].promise);
+ values.push(yield promises[1].promise);
+ values.push('fin');
+ }).then(function() {
+ assert.deepEqual([1, 2, 'fin'], values);
+ });
+ });
+
+ it('handles tasks scheduled in generator', function() {
+ var flow = promise.controlFlow();
+ return flow.execute(function* () {
+ var x = yield flow.execute(function() {
+ return promise.delayed(10).then(function() {
+ return 1;
+ });
+ });
+
+ var y = yield flow.execute(function() {
+ return 2;
+ });
+
+ return x + y;
+ }).then(function(value) {
+ assert.equal(3, value);
+ });
+ });
+
+ it('blocks the control flow while processing generator', function() {
+ var values = [];
+ return promise.controlFlow().wait(function* () {
+ yield values.push(1);
+ values.push(yield promise.delayed(10).then(function() {
+ return 2;
+ }));
+ yield values.push(3);
+ return values.length === 6;
+ }, 250).then(function() {
+ assert.deepEqual([1, 2, 3, 1, 2, 3], values);
+ });
+ });
+
+ it('ControlFlow.wait() will timeout on long generator', function() {
+ var values = [];
+ return promise.controlFlow().wait(function* () {
+ var i = 0;
+ while (i < 3) {
+ yield promise.delayed(100).then(function() {
+ values.push(i++);
+ });
+ }
+ }, 75).catch(function() {
+ assert.deepEqual(
+ [0, 1, 2], values, 'Should complete one loop of wait condition');
+ });
+ });
+
+ describe('generators in promise callbacks', function() {
+ it('works with no initial value', function() {
+ var promises = [
+ promise.defer(),
+ promise.defer()
+ ];
+ var values = [];
+
+ setTimeout(function() {
+ promises[0].fulfill(1);
+ }, 50);
+
+ setTimeout(function() {
+ promises[1].fulfill(2);
+ }, 100);
+
+ return promise.fulfilled().then(function*() {
+ values.push(yield promises[0].promise);
+ values.push(yield promises[1].promise);
+ values.push('fin');
+ }).then(function() {
+ assert.deepEqual([1, 2, 'fin'], values);
+ });
+ });
+
+ it('starts the generator with promised value', function() {
+ var promises = [
+ promise.defer(),
+ promise.defer()
+ ];
+ var values = [];
+
+ setTimeout(function() {
+ promises[0].fulfill(1);
+ }, 50);
+
+ setTimeout(function() {
+ promises[1].fulfill(2);
+ }, 100);
+
+ return promise.fulfilled(3).then(function*(value) {
+ var p1 = yield promises[0].promise;
+ var p2 = yield promises[1].promise;
+ values.push(value + p1);
+ values.push(value + p2);
+ values.push('fin');
+ }).then(function() {
+ assert.deepEqual([4, 5, 'fin'], values);
+ });
+ });
+
+ it('throws yielded rejections within the generator callback', function() {
+ var d = promise.defer();
+ var e = Error('stub');
+
+ setTimeout(function() {
+ d.reject(e);
+ }, 50);
+
+ return promise.fulfilled().then(function*() {
+ var threw = false;
+ try {
+ yield d.promise;
+ } catch (ex) {
+ threw = true;
+ assert.equal(e, ex);
+ }
+ assert.ok(threw);
+ });
+ });
+ });
+});
+
diff --git a/node_modules/selenium-webdriver/test/lib/promise_test.js b/node_modules/selenium-webdriver/test/lib/promise_test.js
new file mode 100644
index 000000000..51554ecd9
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/lib/promise_test.js
@@ -0,0 +1,1067 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+const assert = require('assert');
+
+const testutil = require('./testutil');
+const promise = require('../../lib/promise');
+
+// Aliases for readability.
+const NativePromise = Promise;
+const StubError = testutil.StubError;
+const assertIsStubError = testutil.assertIsStubError;
+const callbackHelper = testutil.callbackHelper;
+const callbackPair = testutil.callbackPair;
+const throwStubError = testutil.throwStubError;
+const fail = () => assert.fail();
+
+// Refer to promise_aplus_test for promise compliance with standard behavior.
+describe('promise', function() {
+ var app, uncaughtExceptions;
+
+ beforeEach(function setUp() {
+ promise.LONG_STACK_TRACES = false;
+ uncaughtExceptions = [];
+
+ app = promise.controlFlow();
+ app.on(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION,
+ (e) => uncaughtExceptions.push(e));
+ });
+
+ afterEach(function tearDown() {
+ app.reset();
+ promise.setDefaultFlow(new promise.ControlFlow);
+ assert.deepEqual([], uncaughtExceptions,
+ 'Did not expect any uncaught exceptions');
+ promise.LONG_STACK_TRACES = false;
+ });
+
+ const assertIsPromise = (p) => assert.ok(promise.isPromise(p));
+ const assertNotPromise = (v) => assert.ok(!promise.isPromise(v));
+
+ function createRejectedPromise(reason) {
+ var p = promise.rejected(reason);
+ p.catch(function() {});
+ return p;
+ }
+
+ it('testCanDetectPromiseLikeObjects', function() {
+ assertIsPromise(new promise.Promise(function(fulfill) {
+ fulfill();
+ }));
+ assertIsPromise(new promise.Deferred().promise);
+ assertIsPromise({then:function() {}});
+
+ assertNotPromise(new promise.Deferred());
+ assertNotPromise(undefined);
+ assertNotPromise(null);
+ assertNotPromise('');
+ assertNotPromise(true);
+ assertNotPromise(false);
+ assertNotPromise(1);
+ assertNotPromise({});
+ assertNotPromise({then:1});
+ assertNotPromise({then:true});
+ assertNotPromise({then:''});
+ });
+
+ describe('then', function() {
+ it('returnsOwnPromiseIfNoCallbacksWereGiven', function() {
+ var deferred = new promise.Deferred();
+ assert.equal(deferred.promise, deferred.promise.then());
+ assert.equal(deferred.promise, deferred.promise.catch());
+ assert.equal(deferred.promise, promise.when(deferred.promise));
+ });
+
+ it('stillConsideredUnHandledIfNoCallbacksWereGivenOnCallsToThen', function() {
+ promise.rejected(new StubError).then();
+ var handler = callbackHelper(assertIsStubError);
+
+ // so tearDown() doesn't throw
+ app.removeAllListeners();
+ app.on(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, handler);
+ return NativePromise.resolve()
+ // Macro yield so the uncaught exception has a chance to trigger.
+ .then(() => new NativePromise(resolve => setTimeout(resolve, 0)))
+ .then(() => handler.assertCalled());
+ });
+ });
+
+ describe('finally', function() {
+ it('nonFailingCallbackDoesNotSuppressOriginalError', function() {
+ var done = callbackHelper(assertIsStubError);
+ return promise.rejected(new StubError).
+ finally(function() {}).
+ catch(done).
+ finally(done.assertCalled);
+ });
+
+ it('failingCallbackSuppressesOriginalError', function() {
+ var done = callbackHelper(assertIsStubError);
+ return promise.rejected(new Error('original')).
+ finally(throwStubError).
+ catch(done).
+ finally(done.assertCalled);
+ });
+
+ it('callbackThrowsAfterFulfilledPromise', function() {
+ var done = callbackHelper(assertIsStubError);
+ return promise.fulfilled().
+ finally(throwStubError).
+ catch(done).
+ finally(done.assertCalled);
+ });
+
+ it('callbackReturnsRejectedPromise', function() {
+ var done = callbackHelper(assertIsStubError);
+ return promise.fulfilled().
+ finally(function() {
+ return promise.rejected(new StubError);
+ }).
+ catch(done).
+ finally(done.assertCalled);
+ });
+ });
+
+ describe('cancel', function() {
+ it('passesTheCancellationReasonToReject', function() {
+ var d = new promise.Deferred();
+ var res = d.promise.then(assert.fail, function(e) {
+ assert.ok(e instanceof promise.CancellationError);
+ assert.equal('because i said so', e.message);
+ });
+ d.promise.cancel('because i said so');
+ return res;
+ });
+
+ describe('can cancel original promise from its child;', function() {
+ it('child created by then()', function() {
+ var d = new promise.Deferred();
+ var p = d.promise.then(assert.fail, function(e) {
+ assert.ok(e instanceof promise.CancellationError);
+ assert.equal('because i said so', e.message);
+ return 123;
+ });
+
+ p.cancel('because i said so');
+ return p.then(v => assert.equal(123, v));
+ });
+
+ it('child linked by resolving with parent', function() {
+ let parent = promise.defer();
+ let child = new promise.Promise(resolve => resolve(parent.promise));
+ child.cancel('all done');
+
+ return parent.promise.then(
+ () => assert.fail('expected a cancellation'),
+ e => {
+ assert.ok(e instanceof promise.CancellationError);
+ assert.equal('all done', e.message);
+ });
+ });
+
+ it('grand child through thenable chain', function() {
+ let p = new promise.Promise(function() {/* never resolve*/});
+
+ let noop = function() {};
+ let gc = p.then(noop).then(noop).then(noop);
+ gc.cancel('stop!');
+
+ return p.then(
+ () => assert.fail('expected to be cancelled'),
+ (e) => {
+ assert.ok(e instanceof promise.CancellationError);
+ assert.equal('stop!', e.message);
+ });
+ });
+
+ it('grand child through thenable chain started at resolve', function() {
+ function noop() {}
+
+ let parent = promise.defer();
+ let child = new promise.Promise(resolve => resolve(parent.promise));
+ let grandChild = child.then(noop).then(noop).then(noop);
+ grandChild.cancel('all done');
+
+ return parent.promise.then(
+ () => assert.fail('expected a cancellation'),
+ e => {
+ assert.ok(e instanceof promise.CancellationError);
+ assert.equal('all done', e.message);
+ });
+ });
+
+ it('"parent" is a Thenable', function() {
+ function noop() {}
+
+ class FakeThenable {
+ constructor(p) {
+ this.promise = p;
+ }
+
+ cancel(reason) {
+ this.promise.cancel(reason);
+ }
+
+ then(cb, eb) {
+ let result = this.promise.then(cb, eb);
+ return new FakeThenable(result);
+ }
+ }
+ promise.Thenable.addImplementation(FakeThenable);
+
+ let root = new promise.Promise(noop);
+ let thenable = new FakeThenable(root);
+ assert.ok(promise.Thenable.isImplementation(thenable));
+
+ let child = new promise.Promise(resolve => resolve(thenable));
+ assert.ok(child instanceof promise.Promise);
+ child.cancel('stop!');
+
+ function assertStopped(p) {
+ return p.then(
+ () => assert.fail('not stopped!'),
+ (e) => {
+ assert.ok(e instanceof promise.CancellationError);
+ assert.equal('stop!', e.message);
+ });
+ }
+
+ return assertStopped(child).then(() => assertStopped(root));
+ });
+ });
+
+ it('canCancelATimeout', function() {
+ var p = promise.delayed(25)
+ .then(assert.fail, (e) => e instanceof promise.CancellationError);
+ setTimeout(() => p.cancel(), 20);
+ p.cancel();
+ return p;
+ });
+
+ it('can cancel timeout from grandchild', function() {
+ });
+
+ it('cancelIsANoopOnceAPromiseHasBeenFulfilled', function() {
+ var p = promise.fulfilled(123);
+ p.cancel();
+ return p.then((v) => assert.equal(123, v));
+ });
+
+ it('cancelIsANoopOnceAPromiseHasBeenRejected', function() {
+ var p = promise.rejected(new StubError);
+ p.cancel();
+
+ var pair = callbackPair(null, assertIsStubError);
+ return p.then(assert.fail, assertIsStubError);
+ });
+
+ it('noopCancelTriggeredOnCallbackOfResolvedPromise', function() {
+ var d = promise.defer();
+ var p = d.promise.then();
+
+ d.fulfill();
+ p.cancel(); // This should not throw.
+ return p; // This should not trigger a failure.
+ });
+ });
+
+ describe('when', function() {
+ it('ReturnsAResolvedPromiseIfGivenANonPromiseValue', function() {
+ var ret = promise.when('abc');
+ assertIsPromise(ret);
+ return ret.then((value) => assert.equal('abc', value));
+ });
+
+ it('PassesRawErrorsToCallbacks', function() {
+ var error = new Error('boo!');
+ return promise.when(error, function(value) {
+ assert.equal(error, value);
+ });
+ });
+
+ it('WaitsForValueToBeResolvedBeforeInvokingCallback', function() {
+ var d = new promise.Deferred(), callback;
+ let result = promise.when(d.promise, callback = callbackHelper(function(value) {
+ assert.equal('hi', value);
+ }));
+ callback.assertNotCalled();
+ d.fulfill('hi');
+ return result.then(callback.assertCalled);
+ });
+ });
+
+ it('firesUncaughtExceptionEventIfRejectionNeverHandled', function() {
+ promise.rejected(new StubError);
+ var handler = callbackHelper(assertIsStubError);
+
+ // so tearDown() doesn't throw
+ app.removeAllListeners();
+ app.on(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, handler);
+
+ return NativePromise.resolve()
+ // Macro yield so the uncaught exception has a chance to trigger.
+ .then(() => new NativePromise(resolve => setTimeout(resolve, 0)))
+ .then(handler.assertCalled);
+ });
+
+ it('cannotResolveADeferredWithItself', function() {
+ var deferred = new promise.Deferred();
+ assert.throws(() => deferred.fulfill(deferred));
+ assert.throws(() => deferred.reject(deferred));
+ });
+
+ describe('fullyResolved', function() {
+ it('primitives', function() {
+ function runTest(value) {
+ var callback, errback;
+ return promise.fullyResolved(value)
+ .then((resolved) => assert.equal(value, resolved));
+ }
+ return runTest(true)
+ .then(() => runTest(function() {}))
+ .then(() => runTest(null))
+ .then(() => runTest(123))
+ .then(() => runTest('foo bar'))
+ .then(() => runTest(undefined));
+ });
+
+ it('arrayOfPrimitives', function() {
+ var fn = function() {};
+ var array = [true, fn, null, 123, '', undefined, 1];
+ return promise.fullyResolved(array).then(function(resolved) {
+ assert.equal(array, resolved);
+ assert.deepEqual([true, fn, null, 123, '', undefined, 1],
+ resolved);
+ });
+ });
+
+ it('nestedArrayOfPrimitives', function() {
+ var fn = function() {};
+ var array = [true, [fn, null, 123], '', undefined];
+ return promise.fullyResolved(array)
+ .then(function(resolved) {
+ assert.equal(array, resolved);
+ assert.deepEqual([true, [fn, null, 123], '', undefined], resolved);
+ assert.deepEqual([fn, null, 123], resolved[1]);
+ });
+ });
+
+ it('arrayWithPromisedPrimitive', function() {
+ return promise.fullyResolved([promise.fulfilled(123)])
+ .then(function(resolved) {
+ assert.deepEqual([123], resolved);
+ });
+ });
+
+ it('promiseResolvesToPrimitive', function() {
+ return promise.fullyResolved(promise.fulfilled(123))
+ .then((resolved) => assert.equal(123, resolved));
+ });
+
+ it('promiseResolvesToArray', function() {
+ var fn = function() {};
+ var array = [true, [fn, null, 123], '', undefined];
+ var aPromise = promise.fulfilled(array);
+
+ var result = promise.fullyResolved(aPromise);
+ return result.then(function(resolved) {
+ assert.equal(array, resolved);
+ assert.deepEqual([true, [fn, null, 123], '', undefined],
+ resolved);
+ assert.deepEqual([fn, null, 123], resolved[1]);
+ });
+ });
+
+ it('promiseResolvesToArrayWithPromises', function() {
+ var nestedPromise = promise.fulfilled(123);
+ var aPromise = promise.fulfilled([true, nestedPromise]);
+ return promise.fullyResolved(aPromise)
+ .then(function(resolved) {
+ assert.deepEqual([true, 123], resolved);
+ });
+ });
+
+ it('rejectsIfArrayPromiseRejects', function() {
+ var nestedPromise = createRejectedPromise(new StubError);
+ var aPromise = promise.fulfilled([true, nestedPromise]);
+
+ var pair = callbackPair(null, assertIsStubError);
+ return promise.fullyResolved(aPromise)
+ .then(assert.fail, assertIsStubError);
+ });
+
+ it('rejectsOnFirstArrayRejection', function() {
+ var e1 = new Error('foo');
+ var e2 = new Error('bar');
+ var aPromise = promise.fulfilled([
+ createRejectedPromise(e1),
+ createRejectedPromise(e2)
+ ]);
+
+ return promise.fullyResolved(aPromise)
+ .then(assert.fail, function(error) {
+ assert.strictEqual(e1, error);
+ });
+ });
+
+ it('rejectsIfNestedArrayPromiseRejects', function() {
+ var aPromise = promise.fulfilled([
+ promise.fulfilled([
+ createRejectedPromise(new StubError)
+ ])
+ ]);
+
+ return promise.fullyResolved(aPromise)
+ .then(assert.fail, assertIsStubError);
+ });
+
+ it('simpleHash', function() {
+ var hash = {'a': 123};
+ return promise.fullyResolved(hash)
+ .then(function(resolved) {
+ assert.strictEqual(hash, resolved);
+ assert.deepEqual(hash, {'a': 123});
+ });
+ });
+
+ it('nestedHash', function() {
+ var nestedHash = {'foo':'bar'};
+ var hash = {'a': 123, 'b': nestedHash};
+
+ return promise.fullyResolved(hash)
+ .then(function(resolved) {
+ assert.strictEqual(hash, resolved);
+ assert.deepEqual({'a': 123, 'b': {'foo': 'bar'}}, resolved);
+ assert.strictEqual(nestedHash, resolved['b']);
+ });
+ });
+
+ it('promiseResolvesToSimpleHash', function() {
+ var hash = {'a': 123};
+ var aPromise = promise.fulfilled(hash);
+
+ return promise.fullyResolved(aPromise)
+ .then((resolved) => assert.strictEqual(hash, resolved));
+ });
+
+ it('promiseResolvesToNestedHash', function() {
+ var nestedHash = {'foo':'bar'};
+ var hash = {'a': 123, 'b': nestedHash};
+ var aPromise = promise.fulfilled(hash);
+
+ return promise.fullyResolved(aPromise)
+ .then(function(resolved) {
+ assert.strictEqual(hash, resolved);
+ assert.strictEqual(nestedHash, resolved['b']);
+ assert.deepEqual(hash, {'a': 123, 'b': {'foo': 'bar'}});
+ });
+ });
+
+ it('promiseResolvesToHashWithPromises', function() {
+ var aPromise = promise.fulfilled({
+ 'a': promise.fulfilled(123)
+ });
+
+ return promise.fullyResolved(aPromise)
+ .then(function(resolved) {
+ assert.deepEqual({'a': 123}, resolved);
+ });
+ });
+
+ it('rejectsIfHashPromiseRejects', function() {
+ var aPromise = promise.fulfilled({
+ 'a': createRejectedPromise(new StubError)
+ });
+
+ return promise.fullyResolved(aPromise)
+ .then(assert.fail, assertIsStubError);
+ });
+
+ it('rejectsIfNestedHashPromiseRejects', function() {
+ var aPromise = promise.fulfilled({
+ 'a': {'b': createRejectedPromise(new StubError)}
+ });
+
+ return promise.fullyResolved(aPromise)
+ .then(assert.fail, assertIsStubError);
+ });
+
+ it('instantiatedObject', function() {
+ function Foo() {
+ this.bar = 'baz';
+ }
+ var foo = new Foo;
+
+ return promise.fullyResolved(foo).then(function(resolvedFoo) {
+ assert.equal(foo, resolvedFoo);
+ assert.ok(resolvedFoo instanceof Foo);
+ assert.deepEqual(new Foo, resolvedFoo);
+ });
+ });
+
+ it('withEmptyArray', function() {
+ return promise.fullyResolved([]).then(function(resolved) {
+ assert.deepEqual([], resolved);
+ });
+ });
+
+ it('withEmptyHash', function() {
+ return promise.fullyResolved({}).then(function(resolved) {
+ assert.deepEqual({}, resolved);
+ });
+ });
+
+ it('arrayWithPromisedHash', function() {
+ var obj = {'foo': 'bar'};
+ var array = [promise.fulfilled(obj)];
+
+ return promise.fullyResolved(array).then(function(resolved) {
+ assert.deepEqual(resolved, [obj]);
+ });
+ });
+ });
+
+ describe('checkedNodeCall', function() {
+ it('functionThrows', function() {
+ return promise.checkedNodeCall(throwStubError)
+ .then(assert.fail, assertIsStubError);
+ });
+
+ it('functionReturnsAnError', function() {
+ return promise.checkedNodeCall(function(callback) {
+ callback(new StubError);
+ }).then(assert.fail, assertIsStubError);
+ });
+
+ it('functionReturnsSuccess', function() {
+ var success = 'success!';
+ return promise.checkedNodeCall(function(callback) {
+ callback(null, success);
+ }).then((value) => assert.equal(success, value));
+ });
+
+ it('functionReturnsAndThrows', function() {
+ var error = new Error('boom');
+ var error2 = new Error('boom again');
+ return promise.checkedNodeCall(function(callback) {
+ callback(error);
+ throw error2;
+ }).then(assert.fail, (e) => assert.equal(error, e));
+ });
+
+ it('functionThrowsAndReturns', function() {
+ var error = new Error('boom');
+ var error2 = new Error('boom again');
+ return promise.checkedNodeCall(function(callback) {
+ setTimeout(() => callback(error), 10);
+ throw error2;
+ }).then(assert.fail, (e) => assert.equal(error2, e));
+ });
+ });
+
+ describe('all', function() {
+ it('(base case)', function() {
+ let defer = [promise.defer(), promise.defer()];
+ var a = [
+ 0, 1,
+ defer[0].promise,
+ defer[1].promise,
+ 4, 5, 6
+ ];
+ delete a[5];
+
+ var pair = callbackPair(function(value) {
+ assert.deepEqual([0, 1, 2, 3, 4, undefined, 6], value);
+ });
+
+ var result = promise.all(a).then(pair.callback, pair.errback);
+ pair.assertNeither();
+
+ defer[0].fulfill(2);
+ pair.assertNeither();
+
+ defer[1].fulfill(3);
+ return result.then(() => pair.assertCallback());
+ });
+
+ it('empty array', function() {
+ return promise.all([]).then((a) => assert.deepEqual([], a));
+ });
+
+ it('usesFirstRejection', function() {
+ let defer = [promise.defer(), promise.defer()];
+ let a = [defer[0].promise, defer[1].promise];
+
+ var result = promise.all(a).then(assert.fail, assertIsStubError);
+ defer[1].reject(new StubError);
+ setTimeout(() => defer[0].reject(Error('ignored')), 0);
+ return result;
+ });
+ });
+
+ describe('map', function() {
+ it('(base case)', function() {
+ var a = [1, 2, 3];
+ return promise.map(a, function(value, index, a2) {
+ assert.equal(a, a2);
+ assert.equal('number', typeof index, 'not a number');
+ return value + 1;
+ }).then(function(value) {
+ assert.deepEqual([2, 3, 4], value);
+ });
+ });
+
+ it('omitsDeleted', function() {
+ var a = [0, 1, 2, 3, 4, 5, 6];
+ delete a[1];
+ delete a[3];
+ delete a[4];
+ delete a[6];
+
+ var expected = [0, 1, 4, 9, 16, 25, 36];
+ delete expected[1];
+ delete expected[3];
+ delete expected[4];
+ delete expected[6];
+
+ return promise.map(a, function(value) {
+ return value * value;
+ }).then(function(value) {
+ assert.deepEqual(expected, value);
+ });
+ });
+
+ it('emptyArray', function() {
+ return promise.map([], function(value) {
+ return value + 1;
+ }).then(function(value) {
+ assert.deepEqual([], value);
+ });
+ });
+
+ it('inputIsPromise', function() {
+ var input = promise.defer();
+ var result = promise.map(input.promise, function(value) {
+ return value + 1;
+ });
+
+ var pair = callbackPair(function(value) {
+ assert.deepEqual([2, 3, 4], value);
+ });
+ result = result.then(pair.callback, pair.errback);
+
+ setTimeout(function() {
+ pair.assertNeither();
+ input.fulfill([1, 2, 3]);
+ }, 10);
+
+ return result;
+ });
+
+ it('waitsForFunctionResultToResolve', function() {
+ var innerResults = [
+ promise.defer(),
+ promise.defer()
+ ];
+
+ var result = promise.map([1, 2], function(value, index) {
+ return innerResults[index].promise;
+ });
+
+ var pair = callbackPair(function(value) {
+ assert.deepEqual(['a', 'b'], value);
+ });
+ result = result.then(pair.callback, pair.errback);
+
+ return NativePromise.resolve()
+ .then(function() {
+ pair.assertNeither();
+ innerResults[0].fulfill('a');
+ })
+ .then(function() {
+ pair.assertNeither();
+ innerResults[1].fulfill('b');
+ return result;
+ })
+ .then(pair.assertCallback);
+ });
+
+ it('rejectsPromiseIfFunctionThrows', function() {
+ return promise.map([1], throwStubError)
+ .then(assert.fail, assertIsStubError);
+ });
+
+ it('rejectsPromiseIfFunctionReturnsRejectedPromise', function() {
+ return promise.map([1], function() {
+ return promise.rejected(new StubError);
+ }).then(assert.fail, assertIsStubError);
+ });
+
+ it('stopsCallingFunctionIfPreviousIterationFailed', function() {
+ var count = 0;
+ return promise.map([1, 2, 3, 4], function() {
+ count++;
+ if (count == 3) {
+ throw new StubError;
+ }
+ }).then(assert.fail, function(e) {
+ assertIsStubError(e);
+ assert.equal(3, count);
+ });
+ });
+
+ it('rejectsWithFirstRejectedPromise', function() {
+ var innerResult = [
+ promise.fulfilled(),
+ createRejectedPromise(new StubError),
+ createRejectedPromise(Error('should be ignored'))
+ ];
+ var count = 0;
+ return promise.map([1, 2, 3, 4], function(value, index) {
+ count += 1;
+ return innerResult[index];
+ }).then(assert.fail, function(e) {
+ assertIsStubError(e);
+ assert.equal(2, count);
+ });
+ });
+
+ it('preservesOrderWhenMapReturnsPromise', function() {
+ var deferreds = [
+ promise.defer(),
+ promise.defer(),
+ promise.defer(),
+ promise.defer()
+ ];
+ var result = promise.map(deferreds, function(value) {
+ return value.promise;
+ });
+
+ var pair = callbackPair(function(value) {
+ assert.deepEqual([0, 1, 2, 3], value);
+ });
+ result = result.then(pair.callback, pair.errback);
+
+ return NativePromise.resolve()
+ .then(function() {
+ pair.assertNeither();
+ for (let i = deferreds.length; i > 0; i -= 1) {
+ deferreds[i - 1].fulfill(i - 1);
+ }
+ return result;
+ }).then(pair.assertCallback);
+ });
+ });
+
+ describe('filter', function() {
+ it('basicFiltering', function() {
+ var a = [0, 1, 2, 3];
+ return promise.filter(a, function(val, index, a2) {
+ assert.equal(a, a2);
+ assert.equal('number', typeof index, 'not a number');
+ return val > 1;
+ }).then(function(val) {
+ assert.deepEqual([2, 3], val);
+ });
+ });
+
+ it('omitsDeleted', function() {
+ var a = [0, 1, 2, 3, 4, 5, 6];
+ delete a[3];
+ delete a[4];
+
+ return promise.filter(a, function(value) {
+ return value > 1 && value < 6;
+ }).then(function(val) {
+ assert.deepEqual([2, 5], val);
+ });
+ });
+
+ it('preservesInputs', function() {
+ var a = [0, 1, 2, 3];
+
+ return promise.filter(a, function(value, i, a2) {
+ assert.equal(a, a2);
+ // Even if a function modifies the input array, the original value
+ // should be inserted into the new array.
+ a2[i] = a2[i] - 1;
+ return a2[i] >= 1;
+ }).then(function(val) {
+ assert.deepEqual([2, 3], val);
+ });
+ });
+
+ it('inputIsPromise', function() {
+ var input = promise.defer();
+ var result = promise.filter(input.promise, function(value) {
+ return value > 1 && value < 3;
+ });
+
+ var pair = callbackPair(function(value) {
+ assert.deepEqual([2], value);
+ });
+ result = result.then(pair.callback, pair.errback);
+ return NativePromise.resolve()
+ .then(function() {
+ pair.assertNeither();
+ input.fulfill([1, 2, 3]);
+ return result;
+ })
+ .then(pair.assertCallback);
+ });
+
+ it('waitsForFunctionResultToResolve', function() {
+ var innerResults = [
+ promise.defer(),
+ promise.defer()
+ ];
+
+ var result = promise.filter([1, 2], function(value, index) {
+ return innerResults[index].promise;
+ });
+
+ var pair = callbackPair(function(value) {
+ assert.deepEqual([2], value);
+ });
+ result = result.then(pair.callback, pair.errback);
+ return NativePromise.resolve()
+ .then(function() {
+ pair.assertNeither();
+ innerResults[0].fulfill(false);
+ })
+ .then(function() {
+ pair.assertNeither();
+ innerResults[1].fulfill(true);
+ return result;
+ })
+ .then(pair.assertCallback);
+ });
+
+ it('rejectsPromiseIfFunctionReturnsRejectedPromise', function() {
+ return promise.filter([1], function() {
+ return promise.rejected(new StubError);
+ }).then(assert.fail, assertIsStubError);
+ });
+
+ it('stopsCallingFunctionIfPreviousIterationFailed', function() {
+ var count = 0;
+ return promise.filter([1, 2, 3, 4], function() {
+ count++;
+ if (count == 3) {
+ throw new StubError;
+ }
+ }).then(assert.fail, function(e) {
+ assertIsStubError(e);
+ assert.equal(3, count);
+ });
+ });
+
+ it('rejectsWithFirstRejectedPromise', function() {
+ var innerResult = [
+ promise.fulfilled(),
+ createRejectedPromise(new StubError),
+ createRejectedPromise(Error('should be ignored'))
+ ];
+
+ return promise.filter([1, 2, 3, 4], function(value, index) {
+ assert.ok(index < innerResult.length);
+ return innerResult[index];
+ }).then(assert.fail, assertIsStubError);
+ });
+
+ it('preservesOrderWhenFilterReturnsPromise', function() {
+ var deferreds = [
+ promise.defer(),
+ promise.defer(),
+ promise.defer(),
+ promise.defer()
+ ];
+ var result = promise.filter([0, 1, 2, 3], function(value, index) {
+ return deferreds[index].promise;
+ });
+
+ var pair = callbackPair(function(value) {
+ assert.deepEqual([1, 2], value);
+ });
+ result = result.then(pair.callback, pair.errback);
+
+ return NativePromise.resolve()
+ .then(function() {
+ pair.assertNeither();
+ for (let i = deferreds.length - 1; i >= 0; i -= 1) {
+ deferreds[i].fulfill(i > 0 && i < 3);
+ }
+ return result;
+ }).then(pair.assertCallback);
+ });
+ });
+
+ it('testAddThenableImplementation', function() {
+ function tmp() {}
+ assert.ok(!promise.Thenable.isImplementation(new tmp()));
+ promise.Thenable.addImplementation(tmp);
+ assert.ok(promise.Thenable.isImplementation(new tmp()));
+
+ class tmpClass {}
+ assert.ok(!promise.Thenable.isImplementation(new tmpClass()));
+ promise.Thenable.addImplementation(tmpClass);
+ assert.ok(promise.Thenable.isImplementation(new tmpClass()));
+ });
+
+ describe('testLongStackTraces', function() {
+ beforeEach(() => promise.LONG_STACK_TRACES = false);
+ afterEach(() => promise.LONG_STACK_TRACES = false);
+
+ it('doesNotAppendStackIfFeatureDisabled', function() {
+ promise.LONG_STACK_TRACES = false;
+
+ var error = Error('hello');
+ var originalStack = error.stack;
+ return promise.rejected(error).
+ then(fail).
+ then(fail).
+ then(fail).
+ then(fail, function(e) {
+ assert.equal(error, e);
+ assert.equal(originalStack, e.stack);
+ });
+ });
+
+ function getStackMessages(error) {
+ return error.stack.split(/\n/).filter(function(line) {
+ return /^From: /.test(line);
+ });
+ }
+
+ it('appendsInitialPromiseCreation_resolverThrows', function() {
+ promise.LONG_STACK_TRACES = true;
+
+ var error = Error('hello');
+ var originalStack = '(placeholder; will be overwritten later)';
+
+ return new promise.Promise(function() {
+ try {
+ throw error;
+ } catch (e) {
+ originalStack = e.stack;
+ throw e;
+ }
+ }).then(fail, function(e) {
+ assert.strictEqual(error, e);
+ if (typeof originalStack !== 'string') {
+ return;
+ }
+ assert.notEqual(originalStack, e.stack);
+ assert.equal(e.stack.indexOf(originalStack), 0,
+ 'should start with original stack');
+ assert.deepEqual(['From: ManagedPromise: new'], getStackMessages(e));
+ });
+ });
+
+ it('appendsInitialPromiseCreation_rejectCalled', function() {
+ promise.LONG_STACK_TRACES = true;
+
+ var error = Error('hello');
+ var originalStack = error.stack;
+
+ return new promise.Promise(function(_, reject) {
+ reject(error);
+ }).then(fail, function(e) {
+ assert.equal(error, e);
+ if (typeof originalStack !== 'string') {
+ return;
+ }
+ assert.notEqual(originalStack, e.stack);
+ assert.equal(e.stack.indexOf(originalStack), 0,
+ 'should start with original stack');
+ assert.deepEqual(['From: ManagedPromise: new'], getStackMessages(e));
+ });
+ });
+
+ it('appendsEachStepToRejectionError', function() {
+ promise.LONG_STACK_TRACES = true;
+
+ var error = Error('hello');
+ var originalStack = '(placeholder; will be overwritten later)';
+
+ return new promise.Promise(function() {
+ try {
+ throw error;
+ } catch (e) {
+ originalStack = e.stack;
+ throw e;
+ }
+ }).
+ then(fail).
+ catch(function(e) { throw e; }).
+ then(fail).
+ catch(function(e) { throw e; }).
+ then(fail, function(e) {
+ assert.equal(error, e);
+ if (typeof originalStack !== 'string') {
+ return;
+ }
+ assert.notEqual(originalStack, e.stack);
+ assert.equal(e.stack.indexOf(originalStack), 0,
+ 'should start with original stack');
+ assert.deepEqual([
+ 'From: ManagedPromise: new',
+ 'From: Promise: then',
+ 'From: Promise: catch',
+ 'From: Promise: then',
+ 'From: Promise: catch',
+ ], getStackMessages(e));
+ });
+ });
+
+ it('errorOccursInCallbackChain', function() {
+ promise.LONG_STACK_TRACES = true;
+
+ var error = Error('hello');
+ var originalStack = '(placeholder; will be overwritten later)';
+
+ return promise.fulfilled().
+ then(function() {}).
+ then(function() {}).
+ then(function() {
+ try {
+ throw error;
+ } catch (e) {
+ originalStack = e.stack;
+ throw e;
+ }
+ }).
+ catch(function(e) { throw e; }).
+ then(fail, function(e) {
+ assert.equal(error, e);
+ if (typeof originalStack !== 'string') {
+ return;
+ }
+ assert.notEqual(originalStack, e.stack);
+ assert.equal(e.stack.indexOf(originalStack), 0,
+ 'should start with original stack');
+ assert.deepEqual([
+ 'From: Promise: then',
+ 'From: Promise: catch',
+ ], getStackMessages(e));
+ });
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/lib/testutil.js b/node_modules/selenium-webdriver/test/lib/testutil.js
new file mode 100644
index 000000000..e68ca28ba
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/lib/testutil.js
@@ -0,0 +1,90 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+const assert = require('assert');
+const sinon = require('sinon');
+
+
+class StubError extends Error {
+ constructor(opt_msg) {
+ super(opt_msg);
+ this.name = this.constructor.name;
+ }
+}
+exports.StubError = StubError;
+
+exports.throwStubError = function() {
+ throw new StubError;
+};
+
+exports.assertIsStubError = function(value) {
+ assert.ok(value instanceof StubError, value + ' is not a ' + StubError.name);
+};
+
+exports.assertIsInstance = function(ctor, value) {
+ assert.ok(value instanceof ctor, 'Not a ' + ctor.name + ': ' + value);
+};
+
+function callbackPair(cb, eb) {
+ if (cb && eb) {
+ throw new TypeError('can only specify one of callback or errback');
+ }
+
+ let callback = cb ? sinon.spy(cb) : sinon.spy();
+ let errback = eb ? sinon.spy(eb) : sinon.spy();
+
+ function assertCallback() {
+ assert.ok(callback.called, 'callback not called');
+ assert.ok(!errback.called, 'errback called');
+ if (callback.threw()) {
+ throw callback.exceptions[0];
+ }
+ }
+
+ function assertErrback() {
+ assert.ok(!callback.called, 'callback called');
+ assert.ok(errback.called, 'errback not called');
+ if (errback.threw()) {
+ throw errback.exceptions[0];
+ }
+ }
+
+ function assertNeither() {
+ assert.ok(!callback.called, 'callback called');
+ assert.ok(!errback.called, 'errback called');
+ }
+
+ return {
+ callback,
+ errback,
+ assertCallback,
+ assertErrback,
+ assertNeither
+ };
+}
+exports.callbackPair = callbackPair;
+
+
+exports.callbackHelper = function(cb) {
+ let pair = callbackPair(cb);
+ let wrapped = pair.callback.bind(null);
+ wrapped.assertCalled = () => pair.assertCallback();
+ wrapped.assertNotCalled = () => pair.assertNeither();
+ return wrapped;
+};
diff --git a/node_modules/selenium-webdriver/test/lib/until_test.js b/node_modules/selenium-webdriver/test/lib/until_test.js
new file mode 100644
index 000000000..d3e81ec18
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/lib/until_test.js
@@ -0,0 +1,463 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+const assert = require('assert');
+
+const By = require('../../lib/by').By;
+const CommandName = require('../../lib/command').Name;
+const error = require('../../lib/error');
+const promise = require('../../lib/promise');
+const until = require('../../lib/until');
+const webdriver = require('../../lib/webdriver'),
+ WebElement = webdriver.WebElement;
+
+describe('until', function() {
+ let driver, executor;
+
+ class TestExecutor {
+ constructor() {
+ this.handlers_ = {};
+ }
+
+ on(cmd, handler) {
+ this.handlers_[cmd] = handler;
+ return this;
+ }
+
+ execute(cmd) {
+ let self = this;
+ return Promise.resolve().then(function() {
+ if (!self.handlers_[cmd.getName()]) {
+ throw new error.UnknownCommandError(cmd.getName());
+ }
+ return self.handlers_[cmd.getName()](cmd);
+ });
+ }
+ }
+
+ function fail(opt_msg) {
+ throw new assert.AssertionError({message: opt_msg});
+ }
+
+ beforeEach(function setUp() {
+ executor = new TestExecutor();
+ driver = new webdriver.WebDriver('session-id', executor);
+ });
+
+ describe('ableToSwitchToFrame', function() {
+ it('failsFastForNonSwitchErrors', function() {
+ let e = Error('boom');
+ executor.on(CommandName.SWITCH_TO_FRAME, function() {
+ throw e;
+ });
+ return driver.wait(until.ableToSwitchToFrame(0), 100)
+ .then(fail, (e2) => assert.strictEqual(e2, e));
+ });
+
+ it('byIndex', function() {
+ executor.on(CommandName.SWITCH_TO_FRAME, () => true);
+ return driver.wait(until.ableToSwitchToFrame(0), 100);
+ });
+
+ it('byWebElement', function() {
+ executor.on(CommandName.SWITCH_TO_FRAME, () => true);
+ var el = new webdriver.WebElement(driver, {ELEMENT: 1234});
+ return driver.wait(until.ableToSwitchToFrame(el), 100);
+ });
+
+ it('byWebElementPromise', function() {
+ executor.on(CommandName.SWITCH_TO_FRAME, () => true);
+ var el = new webdriver.WebElementPromise(driver,
+ promise.fulfilled(new webdriver.WebElement(driver, {ELEMENT: 1234})));
+ return driver.wait(until.ableToSwitchToFrame(el), 100);
+ });
+
+ it('byLocator', function() {
+ executor.on(CommandName.FIND_ELEMENTS, () => [WebElement.buildId(1234)]);
+ executor.on(CommandName.SWITCH_TO_FRAME, () => true);
+ return driver.wait(until.ableToSwitchToFrame(By.id('foo')), 100);
+ });
+
+ it('byLocator_elementNotInitiallyFound', function() {
+ var foundResponses = [[], [], [WebElement.buildId(1234)]];
+ executor.on(CommandName.FIND_ELEMENTS, () => foundResponses.shift());
+ executor.on(CommandName.SWITCH_TO_FRAME, () => true);
+
+ return driver.wait(until.ableToSwitchToFrame(By.id('foo')), 2000)
+ .then(function() {
+ assert.equal(foundResponses.length, 0);
+ });
+ });
+
+ it('timesOutIfNeverAbletoSwitchFrames', function() {
+ var count = 0;
+ executor.on(CommandName.SWITCH_TO_FRAME, function() {
+ count += 1;
+ throw new error.NoSuchFrameError;
+ });
+
+ return driver.wait(until.ableToSwitchToFrame(0), 100)
+ .then(fail, function(e) {
+ assert.ok(count > 0);
+ assert.ok(
+ e.message.startsWith('Waiting to be able to switch to frame'),
+ 'Wrong message: ' + e.message);
+ });
+ });
+ });
+
+ describe('alertIsPresent', function() {
+ it('failsFastForNonAlertSwitchErrors', function() {
+ return driver.wait(until.alertIsPresent(), 100).then(fail, function(e) {
+ assert.ok(e instanceof error.UnknownCommandError);
+ assert.equal(e.message, CommandName.GET_ALERT_TEXT);
+ });
+ });
+
+ it('waitsForAlert', function() {
+ var count = 0;
+ executor.on(CommandName.GET_ALERT_TEXT, function() {
+ if (count++ < 3) {
+ throw new error.NoSuchAlertError;
+ } else {
+ return true;
+ }
+ }).on(CommandName.DISMISS_ALERT, () => true);
+
+ return driver.wait(until.alertIsPresent(), 1000).then(function(alert) {
+ assert.equal(count, 4);
+ return alert.dismiss();
+ });
+ });
+
+ // TODO: Remove once GeckoDriver doesn't throw this unwanted error.
+ // See https://github.com/SeleniumHQ/selenium/pull/2137
+ describe('workaround for GeckoDriver', function() {
+ it('doesNotFailWhenCannotConvertNullToObject', function() {
+ var count = 0;
+ executor.on(CommandName.GET_ALERT_TEXT, function() {
+ if (count++ < 3) {
+ throw new error.WebDriverError(`can't convert null to object`);
+ } else {
+ return true;
+ }
+ }).on(CommandName.DISMISS_ALERT, () => true);
+
+ return driver.wait(until.alertIsPresent(), 1000).then(function(alert) {
+ assert.equal(count, 4);
+ return alert.dismiss();
+ });
+ });
+
+ it('keepsRaisingRegularWebdriverError', function() {
+ var webDriverError = new error.WebDriverError;
+
+ executor.on(CommandName.GET_ALERT_TEXT, function() {
+ throw webDriverError;
+ });
+
+ return driver.wait(until.alertIsPresent(), 1000).then(function() {
+ throw new Error('driver did not fail against WebDriverError');
+ }, function(error) {
+ assert.equal(error, webDriverError);
+ });
+ })
+ });
+ });
+
+ it('testUntilTitleIs', function() {
+ var titles = ['foo', 'bar', 'baz'];
+ executor.on(CommandName.GET_TITLE, () => titles.shift());
+
+ return driver.wait(until.titleIs('bar'), 3000).then(function() {
+ assert.deepStrictEqual(titles, ['baz']);
+ });
+ });
+
+ it('testUntilTitleContains', function() {
+ var titles = ['foo', 'froogle', 'google'];
+ executor.on(CommandName.GET_TITLE, () => titles.shift());
+
+ return driver.wait(until.titleContains('oogle'), 3000).then(function() {
+ assert.deepStrictEqual(titles, ['google']);
+ });
+ });
+
+ it('testUntilTitleMatches', function() {
+ var titles = ['foo', 'froogle', 'aaaabc', 'aabbbc', 'google'];
+ executor.on(CommandName.GET_TITLE, () => titles.shift());
+
+ return driver.wait(until.titleMatches(/^a{2,3}b+c$/), 3000)
+ .then(function() {
+ assert.deepStrictEqual(titles, ['google']);
+ });
+ });
+
+ it('testUntilUrlIs', function() {
+ var urls = ['http://www.foo.com', 'https://boo.com', 'http://docs.yes.com'];
+ executor.on(CommandName.GET_CURRENT_URL, () => urls.shift());
+
+ return driver.wait(until.urlIs('https://boo.com'), 3000).then(function() {
+ assert.deepStrictEqual(urls, ['http://docs.yes.com']);
+ });
+ });
+
+ it('testUntilUrlContains', function() {
+ var urls =
+ ['http://foo.com', 'https://groups.froogle.com', 'http://google.com'];
+ executor.on(CommandName.GET_CURRENT_URL, () => urls.shift());
+
+ return driver.wait(until.urlContains('oogle.com'), 3000).then(function() {
+ assert.deepStrictEqual(urls, ['http://google.com']);
+ });
+ });
+
+ it('testUntilUrlMatches', function() {
+ var urls = ['foo', 'froogle', 'aaaabc', 'aabbbc', 'google'];
+ executor.on(CommandName.GET_CURRENT_URL, () => urls.shift());
+
+ return driver.wait(until.urlMatches(/^a{2,3}b+c$/), 3000)
+ .then(function() {
+ assert.deepStrictEqual(urls, ['google']);
+ });
+ });
+
+ it('testUntilElementLocated', function() {
+ var responses = [
+ [],
+ [WebElement.buildId('abc123'), WebElement.buildId('foo')],
+ ['end']
+ ];
+ executor.on(CommandName.FIND_ELEMENTS, () => responses.shift());
+
+ let element = driver.wait(until.elementLocated(By.id('quux')), 2000);
+ assert.ok(element instanceof webdriver.WebElementPromise);
+ return element.getId().then(function(id) {
+ assert.deepStrictEqual(responses, [['end']]);
+ assert.equal(id, 'abc123');
+ });
+ });
+
+ describe('untilElementLocated, elementNeverFound', function() {
+ function runNoElementFoundTest(locator, locatorStr) {
+ executor.on(CommandName.FIND_ELEMENTS, () => []);
+
+ function expectedFailure() {
+ fail('expected condition to timeout');
+ }
+
+ return driver.wait(until.elementLocated(locator), 100)
+ .then(expectedFailure, function(error) {
+ var expected = 'Waiting for element to be located ' + locatorStr;
+ var lines = error.message.split(/\n/, 2);
+ assert.equal(lines[0], expected);
+
+ let regex = /^Wait timed out after \d+ms$/;
+ assert.ok(regex.test(lines[1]),
+ `Lines <${lines[1]}> does not match ${regex}`);
+ });
+ }
+
+ it('byLocator', function() {
+ return runNoElementFoundTest(
+ By.id('quux'), 'By(css selector, *[id="quux"])');
+ });
+
+ it('byHash', function() {
+ return runNoElementFoundTest(
+ {id: 'quux'}, 'By(css selector, *[id="quux"])');
+ });
+
+ it('byFunction', function() {
+ return runNoElementFoundTest(function() {}, 'by function()');
+ });
+ });
+
+ it('testUntilElementsLocated', function() {
+ var responses = [
+ [],
+ [WebElement.buildId('abc123'), WebElement.buildId('foo')],
+ ['end']
+ ];
+ executor.on(CommandName.FIND_ELEMENTS, () => responses.shift());
+
+ return driver.wait(until.elementsLocated(By.id('quux')), 2000)
+ .then(function(els) {
+ return Promise.all(els.map(e => e.getId()));
+ }).then(function(ids) {
+ assert.deepStrictEqual(responses, [['end']]);
+ assert.equal(ids.length, 2);
+ assert.equal(ids[0], 'abc123');
+ assert.equal(ids[1], 'foo');
+ });
+ });
+
+ describe('untilElementsLocated, noElementsFound', function() {
+ function runNoElementsFoundTest(locator, locatorStr) {
+ executor.on(CommandName.FIND_ELEMENTS, () => []);
+
+ function expectedFailure() {
+ fail('expected condition to timeout');
+ }
+
+ return driver.wait(until.elementsLocated(locator), 100)
+ .then(expectedFailure, function(error) {
+ var expected =
+ 'Waiting for at least one element to be located ' + locatorStr;
+ var lines = error.message.split(/\n/, 2);
+ assert.equal(lines[0], expected);
+
+ let regex = /^Wait timed out after \d+ms$/;
+ assert.ok(regex.test(lines[1]),
+ `Lines <${lines[1]}> does not match ${regex}`);
+ });
+ }
+
+ it('byLocator', function() {
+ return runNoElementsFoundTest(
+ By.id('quux'), 'By(css selector, *[id="quux"])');
+ });
+
+ it('byHash', function() {
+ return runNoElementsFoundTest(
+ {id: 'quux'}, 'By(css selector, *[id="quux"])');
+ });
+
+ it('byFunction', function() {
+ return runNoElementsFoundTest(function() {}, 'by function()');
+ });
+ });
+
+ it('testUntilStalenessOf', function() {
+ let count = 0;
+ executor.on(CommandName.GET_ELEMENT_TAG_NAME, function() {
+ while (count < 3) {
+ count += 1;
+ return 'body';
+ }
+ throw new error.StaleElementReferenceError('now stale');
+ });
+
+ var el = new webdriver.WebElement(driver, {ELEMENT: 'foo'});
+ return driver.wait(until.stalenessOf(el), 2000)
+ .then(() => assert.equal(count, 3));
+ });
+
+ describe('element state conditions', function() {
+ function runElementStateTest(predicate, command, responses, var_args) {
+ let original = new webdriver.WebElement(driver, 'foo');
+ let predicateArgs = [original];
+ if (arguments.length > 3) {
+ predicateArgs = predicateArgs.concat(arguments[1]);
+ command = arguments[2];
+ responses = arguments[3];
+ }
+
+ assert.ok(responses.length > 1);
+
+ responses = responses.concat(['end']);
+ executor.on(command, () => responses.shift());
+
+ let result = driver.wait(predicate.apply(null, predicateArgs), 2000);
+ assert.ok(result instanceof webdriver.WebElementPromise);
+ return result.then(function(value) {
+ assert.ok(value instanceof webdriver.WebElement);
+ assert.ok(!(value instanceof webdriver.WebElementPromise));
+ return value.getId();
+ }).then(function(id) {
+ assert.equal('foo', id);
+ assert.deepStrictEqual(responses, ['end']);
+ });
+ }
+
+ it('elementIsVisible', function() {
+ return runElementStateTest(
+ until.elementIsVisible,
+ CommandName.IS_ELEMENT_DISPLAYED, [false, false, true]);
+ });
+
+ it('elementIsNotVisible', function() {
+ return runElementStateTest(
+ until.elementIsNotVisible,
+ CommandName.IS_ELEMENT_DISPLAYED, [true, true, false]);
+ });
+
+ it('elementIsEnabled', function() {
+ return runElementStateTest(
+ until.elementIsEnabled,
+ CommandName.IS_ELEMENT_ENABLED, [false, false, true]);
+ });
+
+ it('elementIsDisabled', function() {
+ return runElementStateTest(
+ until.elementIsDisabled,
+ CommandName.IS_ELEMENT_ENABLED, [true, true, false]);
+ });
+
+ it('elementIsSelected', function() {
+ return runElementStateTest(
+ until.elementIsSelected,
+ CommandName.IS_ELEMENT_SELECTED, [false, false, true]);
+ });
+
+ it('elementIsNotSelected', function() {
+ return runElementStateTest(
+ until.elementIsNotSelected,
+ CommandName.IS_ELEMENT_SELECTED, [true, true, false]);
+ });
+
+ it('elementTextIs', function() {
+ return runElementStateTest(
+ until.elementTextIs, 'foobar',
+ CommandName.GET_ELEMENT_TEXT,
+ ['foo', 'fooba', 'foobar']);
+ });
+
+ it('elementTextContains', function() {
+ return runElementStateTest(
+ until.elementTextContains, 'bar',
+ CommandName.GET_ELEMENT_TEXT,
+ ['foo', 'foobaz', 'foobarbaz']);
+ });
+
+ it('elementTextMatches', function() {
+ return runElementStateTest(
+ until.elementTextMatches, /fo+bar{3}/,
+ CommandName.GET_ELEMENT_TEXT,
+ ['foo', 'foobar', 'fooobarrr']);
+ });
+ });
+
+ describe('WebElementCondition', function() {
+ it('fails if wait completes with a non-WebElement value', function() {
+ let result = driver.wait(
+ new webdriver.WebElementCondition('testing', () => 123), 1000);
+
+ return result.then(
+ () => assert.fail('expected to fail'),
+ function(e) {
+ assert.ok(e instanceof TypeError);
+ assert.equal(
+ 'WebElementCondition did not resolve to a WebElement: '
+ + '[object Number]',
+ e.message);
+ });
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/lib/webdriver_test.js b/node_modules/selenium-webdriver/test/lib/webdriver_test.js
new file mode 100644
index 000000000..d2d87ca3e
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/lib/webdriver_test.js
@@ -0,0 +1,2177 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+const testutil = require('./testutil');
+
+const By = require('../../lib/by').By;
+const Capabilities = require('../../lib/capabilities').Capabilities;
+const Executor = require('../../lib/command').Executor;
+const CName = require('../../lib/command').Name;
+const error = require('../../lib/error');
+const Button = require('../../lib/input').Button;
+const Key = require('../../lib/input').Key;
+const logging = require('../../lib/logging');
+const Session = require('../../lib/session').Session;
+const promise = require('../../lib/promise');
+const Alert = require('../../lib/webdriver').Alert;
+const AlertPromise = require('../../lib/webdriver').AlertPromise;
+const UnhandledAlertError = require('../../lib/webdriver').UnhandledAlertError;
+const WebDriver = require('../../lib/webdriver').WebDriver;
+const WebElement = require('../../lib/webdriver').WebElement;
+const WebElementPromise = require('../../lib/webdriver').WebElementPromise;
+
+const assert = require('assert');
+const sinon = require('sinon');
+
+const SESSION_ID = 'test_session_id';
+
+// Aliases for readability.
+const NativePromise = Promise;
+const StubError = testutil.StubError;
+const assertIsInstance = testutil.assertIsInstance;
+const assertIsStubError = testutil.assertIsStubError;
+const throwStubError = testutil.throwStubError;
+const fail = (msg) => assert.fail(msg);
+
+describe('WebDriver', function() {
+ const LOG = logging.getLogger('webdriver.test');
+
+ // before(function() {
+ // logging.getLogger('webdriver').setLevel(logging.Level.ALL);
+ // logging.installConsoleHandler();
+ // });
+
+ // after(function() {
+ // logging.getLogger('webdriver').setLevel(null);
+ // logging.removeConsoleHandler();
+ // });
+
+ var driver;
+ var flow;
+ var uncaughtExceptions;
+
+ beforeEach(function setUp() {
+ flow = promise.controlFlow();
+ uncaughtExceptions = [];
+ flow.on('uncaughtException', onUncaughtException);
+ });
+
+ afterEach(function tearDown() {
+ return waitForIdle(flow).then(function() {
+ assert.deepEqual([], uncaughtExceptions);
+ flow.reset();
+ });
+ });
+
+ function onUncaughtException(e) {
+ uncaughtExceptions.push(e);
+ }
+
+ function waitForIdle(opt_flow) {
+ var theFlow = opt_flow || flow;
+ return new Promise(function(fulfill, reject) {
+ if (theFlow.isIdle()) {
+ fulfill();
+ return;
+ }
+ theFlow.once('idle', fulfill);
+ theFlow.once('uncaughtException', reject);
+ });
+ }
+
+ function waitForAbort(opt_flow, opt_n) {
+ var n = opt_n || 1;
+ var theFlow = opt_flow || flow;
+ theFlow.removeAllListeners(
+ promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION);
+ return new Promise(function(fulfill, reject) {
+ theFlow.once('idle', function() {
+ reject(Error('expected flow to report an unhandled error'));
+ });
+
+ var errors = [];
+ theFlow.on('uncaughtException', onError);
+ function onError(e) {
+ errors.push(e);
+ if (errors.length === n) {
+ theFlow.removeListener('uncaughtException', onError);
+ fulfill(n === 1 ? errors[0] : errors);
+ }
+ }
+ });
+ }
+
+ function expectedError(ctor, message) {
+ return function(e) {
+ assertIsInstance(ctor, e);
+ assert.equal(message, e.message);
+ };
+ }
+
+ class Expectation {
+ constructor(executor, name, opt_parameters) {
+ this.executor_ = executor;
+ this.name_ = name;
+ this.times_ = 1;
+ this.sessionId_ = SESSION_ID;
+ this.check_ = null;
+ this.toDo_ = null;
+ this.withParameters(opt_parameters || {});
+ }
+
+ anyTimes() {
+ this.times_ = Infinity;
+ return this;
+ }
+
+ times(n) {
+ this.times_ = n;
+ return this;
+ }
+
+ withParameters(parameters) {
+ this.parameters_ = parameters;
+ if (this.name_ !== CName.NEW_SESSION) {
+ this.parameters_['sessionId'] = this.sessionId_;
+ }
+ return this;
+ }
+
+ andReturn(code, opt_value) {
+ this.toDo_ = function(command) {
+ LOG.info('executing ' + command.getName() + '; returning ' + code);
+ return Promise.resolve(opt_value !== void(0) ? opt_value : null);
+ };
+ return this;
+ }
+
+ andReturnSuccess(opt_value) {
+ this.toDo_ = function(command) {
+ LOG.info('executing ' + command.getName() + '; returning success');
+ return Promise.resolve(opt_value !== void(0) ? opt_value : null);
+ };
+ return this;
+ }
+
+ andReturnError(error) {
+ if (typeof error === 'number') {
+ throw Error('need error type');
+ }
+ this.toDo_ = function(command) {
+ LOG.info('executing ' + command.getName() + '; returning failure');
+ return Promise.reject(error);
+ };
+ return this;
+ }
+
+ expect(name, opt_parameters) {
+ this.end();
+ return this.executor_.expect(name, opt_parameters);
+ }
+
+ end() {
+ if (!this.toDo_) {
+ this.andReturnSuccess(null);
+ }
+ return this.executor_;
+ }
+
+ execute(command) {
+ assert.deepEqual(this.parameters_, command.getParameters());
+ return this.toDo_(command);
+ }
+ }
+
+ class FakeExecutor {
+ constructor() {
+ this.commands_ = new Map;
+ }
+
+ execute(command) {
+ let expectations = this.commands_.get(command.getName());
+ if (!expectations || !expectations.length) {
+ assert.fail('unexpected command: ' + command.getName());
+ return;
+ }
+
+ let next = expectations[0];
+ let result = next.execute(command);
+ if (next.times_ != Infinity) {
+ next.times_ -= 1;
+ if (!next.times_) {
+ expectations.shift();
+ }
+ }
+ return result;
+ }
+
+ expect(commandName, opt_parameters) {
+ if (!this.commands_.has(commandName)) {
+ this.commands_.set(commandName, []);
+ }
+ let e = new Expectation(this, commandName, opt_parameters);
+ this.commands_.get(commandName).push(e);
+ return e;
+ }
+
+ createDriver(opt_session) {
+ let session = opt_session || new Session(SESSION_ID, {});
+ return new WebDriver(session, this);
+ }
+ }
+
+
+ /////////////////////////////////////////////////////////////////////////////
+ //
+ // Tests
+ //
+ /////////////////////////////////////////////////////////////////////////////
+
+
+ describe('testAttachToSession', function() {
+ it('sessionIsAvailable', function() {
+ let aSession = new Session(SESSION_ID, {'browserName': 'firefox'});
+ let executor = new FakeExecutor().
+ expect(CName.DESCRIBE_SESSION).
+ withParameters({'sessionId': SESSION_ID}).
+ andReturnSuccess(aSession).
+ end();
+
+ let driver = WebDriver.attachToSession(executor, SESSION_ID);
+ return driver.getSession().then(v => assert.strictEqual(v, aSession));
+ });
+
+ it('failsToGetSessionInfo', function() {
+ let e = new Error('boom');
+ let executor = new FakeExecutor().
+ expect(CName.DESCRIBE_SESSION).
+ withParameters({'sessionId': SESSION_ID}).
+ andReturnError(e).
+ end();
+
+ let driver = WebDriver.attachToSession(executor, SESSION_ID);
+ return driver.getSession()
+ .then(() => assert.fail('should have failed!'),
+ (actual) => assert.strictEqual(actual, e));
+ });
+
+ it('remote end does not recognize DESCRIBE_SESSION command', function() {
+ let e = new error.UnknownCommandError;
+ let executor = new FakeExecutor().
+ expect(CName.DESCRIBE_SESSION).
+ withParameters({'sessionId': SESSION_ID}).
+ andReturnError(e).
+ end();
+
+ let driver = WebDriver.attachToSession(executor, SESSION_ID);
+ return driver.getSession().then(session => {
+ assert.ok(session instanceof Session);
+ assert.strictEqual(session.getId(), SESSION_ID);
+ assert.equal(session.getCapabilities().size, 0);
+ });
+ });
+
+ it('usesActiveFlowByDefault', function() {
+ let executor = new FakeExecutor().
+ expect(CName.DESCRIBE_SESSION).
+ withParameters({'sessionId': SESSION_ID}).
+ andReturnSuccess({}).
+ end();
+
+ var driver = WebDriver.attachToSession(executor, SESSION_ID);
+ assert.equal(driver.controlFlow(), promise.controlFlow());
+
+ return waitForIdle(driver.controlFlow());
+ });
+
+ it('canAttachInCustomFlow', function() {
+ let executor = new FakeExecutor().
+ expect(CName.DESCRIBE_SESSION).
+ withParameters({'sessionId': SESSION_ID}).
+ andReturnSuccess({}).
+ end();
+
+ var otherFlow = new promise.ControlFlow();
+ var driver = WebDriver.attachToSession(executor, SESSION_ID, otherFlow);
+ assert.equal(otherFlow, driver.controlFlow());
+ assert.notEqual(otherFlow, promise.controlFlow());
+
+ return waitForIdle(otherFlow);
+ });
+ });
+
+ describe('testCreateSession', function() {
+ it('happyPathWithCapabilitiesHashObject', function() {
+ let aSession = new Session(SESSION_ID, {'browserName': 'firefox'});
+ let executor = new FakeExecutor().
+ expect(CName.NEW_SESSION).
+ withParameters({
+ 'desiredCapabilities': {'browserName': 'firefox'}
+ }).
+ andReturnSuccess(aSession).
+ end();
+
+ var driver = WebDriver.createSession(executor, {
+ 'browserName': 'firefox'
+ });
+ return driver.getSession().then(v => assert.strictEqual(v, aSession));
+ });
+
+ it('happyPathWithCapabilitiesInstance', function() {
+ let aSession = new Session(SESSION_ID, {'browserName': 'firefox'});
+ let executor = new FakeExecutor().
+ expect(CName.NEW_SESSION).
+ withParameters({'desiredCapabilities': {'browserName': 'firefox'}}).
+ andReturnSuccess(aSession).
+ end();
+
+ var driver = WebDriver.createSession(executor, Capabilities.firefox());
+ return driver.getSession().then(v => assert.strictEqual(v, aSession));
+ });
+
+ it('handles desired and requried capabilities', function() {
+ let aSession = new Session(SESSION_ID, {'browserName': 'firefox'});
+ let executor = new FakeExecutor().
+ expect(CName.NEW_SESSION).
+ withParameters({
+ 'desiredCapabilities': {'foo': 'bar'},
+ 'requiredCapabilities': {'bim': 'baz'}
+ }).
+ andReturnSuccess(aSession).
+ end();
+
+ let desired = new Capabilities().set('foo', 'bar');
+ let required = new Capabilities().set('bim', 'baz');
+ var driver = WebDriver.createSession(executor, {desired, required});
+ return driver.getSession().then(v => assert.strictEqual(v, aSession));
+ });
+
+ it('failsToCreateSession', function() {
+ let executor = new FakeExecutor().
+ expect(CName.NEW_SESSION).
+ withParameters({'desiredCapabilities': {'browserName': 'firefox'}}).
+ andReturnError(new StubError()).
+ end();
+
+ var driver =
+ WebDriver.createSession(executor, {'browserName': 'firefox'});
+ return driver.getSession().then(fail, assertIsStubError);
+ });
+
+ it('usesActiveFlowByDefault', function() {
+ let executor = new FakeExecutor().
+ expect(CName.NEW_SESSION).
+ withParameters({'desiredCapabilities': {}}).
+ andReturnSuccess(new Session(SESSION_ID)).
+ end();
+
+ var driver = WebDriver.createSession(executor, {});
+ assert.equal(promise.controlFlow(), driver.controlFlow());
+
+ return waitForIdle(driver.controlFlow());
+ });
+
+ it('canCreateInCustomFlow', function() {
+ let executor = new FakeExecutor().
+ expect(CName.NEW_SESSION).
+ withParameters({'desiredCapabilities': {}}).
+ andReturnSuccess({}).
+ end();
+
+ var otherFlow = new promise.ControlFlow();
+ var driver = WebDriver.createSession(executor, {}, otherFlow);
+ assert.equal(otherFlow, driver.controlFlow());
+ assert.notEqual(otherFlow, promise.controlFlow());
+
+ return waitForIdle(otherFlow);
+ });
+ });
+
+ it('testDoesNotExecuteCommandIfSessionDoesNotResolve', function() {
+ var session = Promise.reject(new StubError);
+ new FakeExecutor().createDriver(session).getTitle();
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ it('testCommandReturnValuesArePassedToFirstCallback', function() {
+ let executor = new FakeExecutor().
+ expect(CName.GET_TITLE).andReturnSuccess('Google Search').
+ end();
+
+ var driver = executor.createDriver();
+ return driver.getTitle().then(function(title) {
+ assert.equal('Google Search', title);
+ });
+ });
+
+ it('testStopsCommandExecutionWhenAnErrorOccurs', function() {
+ let e = new error.NoSuchWindowError('window not found');
+ let executor = new FakeExecutor().
+ expect(CName.SWITCH_TO_WINDOW).
+ withParameters({
+ 'name': 'foo',
+ 'handle': 'foo'
+ }).
+ andReturnError(e).
+ end();
+
+ var driver = executor.createDriver();
+ driver.switchTo().window('foo');
+ driver.getTitle(); // mock should blow if this gets executed
+
+ return waitForAbort().then(v => assert.strictEqual(v, e));
+ });
+
+ it('testCanSuppressCommandFailures', function() {
+ let e = new error.NoSuchWindowError('window not found');
+ let executor = new FakeExecutor().
+ expect(CName.SWITCH_TO_WINDOW).
+ withParameters({
+ 'name': 'foo',
+ 'handle': 'foo'
+ }).
+ andReturnError(e).
+ expect(CName.GET_TITLE).
+ andReturnSuccess('Google Search').
+ end();
+
+ var driver = executor.createDriver();
+ driver.switchTo().window('foo')
+ .catch(v => assert.strictEqual(v, e));
+ driver.getTitle();
+ return waitForIdle();
+ });
+
+ it('testErrorsPropagateUpToTheRunningApplication', function() {
+ let e = new error.NoSuchWindowError('window not found');
+ let executor = new FakeExecutor().
+ expect(CName.SWITCH_TO_WINDOW).
+ withParameters({
+ 'name': 'foo',
+ 'handle': 'foo'
+ }).
+ andReturnError(e).
+ end();
+
+ executor.createDriver().switchTo().window('foo');
+ return waitForAbort().then(v => assert.strictEqual(v, e));
+ });
+
+ it('testErrbacksThatReturnErrorsStillSwitchToCallbackChain', function() {
+ let executor = new FakeExecutor().
+ expect(CName.SWITCH_TO_WINDOW).
+ withParameters({
+ 'name': 'foo',
+ 'handle': 'foo'
+ }).
+ andReturnError(new error.NoSuchWindowError('window not found')).
+ end();
+
+ var driver = executor.createDriver();
+ return driver.switchTo().window('foo').
+ catch(function() { return new StubError; });
+ then(assertIsStubError);
+ });
+
+ it('testErrbacksThrownCanOverrideOriginalError', function() {
+ let executor = new FakeExecutor().
+ expect(CName.SWITCH_TO_WINDOW, {
+ 'name': 'foo',
+ 'handle': 'foo'
+ }).
+ andReturnError(new error.NoSuchWindowError('window not found')).
+ end();
+
+ var driver = executor.createDriver();
+ driver.switchTo().window('foo').catch(throwStubError);
+
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ it('testCannotScheduleCommandsIfTheSessionIdHasBeenDeleted', function() {
+ var driver = new FakeExecutor().createDriver();
+ delete driver.session_;
+ assert.throws(() => driver.get('http://www.google.com'));
+ });
+
+ it('testDeletesSessionIdAfterQuitting', function() {
+ var driver;
+ let executor = new FakeExecutor().
+ expect(CName.QUIT).
+ end();
+
+ driver = executor.createDriver();
+ return driver.quit().then(function() {
+ assert.equal(void 0, driver.session_);
+ });
+ });
+
+ it('testReportsErrorWhenExecutingCommandsAfterExecutingAQuit', function() {
+ let executor = new FakeExecutor().
+ expect(CName.QUIT).
+ end();
+
+ var driver = executor.createDriver();
+ driver.quit();
+ driver.get('http://www.google.com');
+ return waitForAbort().
+ then(expectedError(
+ error.NoSuchSessionError,
+ 'This driver instance does not have a valid session ID ' +
+ '(did you call WebDriver.quit()?) and may no longer be used.'));
+ });
+
+ it('testCallbackCommandsExecuteBeforeNextCommand', function() {
+ let executor = new FakeExecutor().
+ expect(CName.GET_CURRENT_URL).
+ expect(CName.GET, {'url': 'http://www.google.com'}).
+ expect(CName.CLOSE).
+ expect(CName.GET_TITLE).
+ end();
+
+ var driver = executor.createDriver();
+ driver.getCurrentUrl().then(function() {
+ driver.get('http://www.google.com').then(function() {
+ driver.close();
+ });
+ });
+ driver.getTitle();
+
+ return waitForIdle();
+ });
+
+ it('testEachCallbackFrameRunsToCompletionBeforeTheNext', function() {
+ let executor = new FakeExecutor().
+ expect(CName.GET_TITLE).
+ expect(CName.GET_CURRENT_URL).
+ expect(CName.GET_CURRENT_WINDOW_HANDLE).
+ expect(CName.CLOSE).
+ expect(CName.QUIT).
+ end();
+
+ var driver = executor.createDriver();
+ driver.getTitle().
+ // Everything in this callback...
+ then(function() {
+ driver.getCurrentUrl();
+ driver.getWindowHandle();
+ }).
+ // ...should execute before everything in this callback.
+ then(function() {
+ driver.close();
+ });
+ // This should execute after everything above
+ driver.quit();
+
+ return waitForIdle();
+ });
+
+ describe('nestedCommandFailures', function() {
+ it('bubbleUpToGlobalHandlerIfUnsuppressed', function() {
+ let e = new error.NoSuchWindowError('window not found');
+ let executor = new FakeExecutor().
+ expect(CName.GET_TITLE).
+ expect(CName.SWITCH_TO_WINDOW, {
+ 'name': 'foo',
+ 'handle': 'foo'
+ }).
+ andReturnError(e).
+ end();
+
+ var driver = executor.createDriver();
+ driver.getTitle().then(function() {
+ driver.switchTo().window('foo');
+ });
+
+ return waitForAbort().then(v => assert.strictEqual(v, e));
+ });
+
+ it('canBeSuppressWhenTheyOccur', function() {
+ let executor = new FakeExecutor().
+ expect(CName.GET_TITLE).
+ expect(CName.SWITCH_TO_WINDOW, {
+ 'name': 'foo',
+ 'handle': 'foo'
+ }).
+ andReturnError(new error.NoSuchWindowError('window not found')).
+ expect(CName.CLOSE).
+ end();
+
+ var driver = executor.createDriver();
+ driver.getTitle().then(function() {
+ driver.switchTo().window('foo').catch(function() {});
+ });
+ driver.close();
+
+ return waitForIdle();
+ });
+
+ it('bubbleUpThroughTheFrameStack', function() {
+ let e = new error.NoSuchWindowError('window not found');
+ let executor = new FakeExecutor().
+ expect(CName.GET_TITLE).
+ expect(CName.SWITCH_TO_WINDOW, {
+ 'name': 'foo',
+ 'handle': 'foo'
+ }).
+ andReturnError(e).
+ end();
+
+ var driver = executor.createDriver();
+ driver.getTitle().
+ then(function() {
+ return driver.switchTo().window('foo');
+ }).
+ catch(v => assert.strictEqual(v, e));
+
+ return waitForIdle();
+ });
+
+ it('canBeCaughtAndSuppressed', function() {
+ let executor = new FakeExecutor().
+ expect(CName.GET_TITLE).
+ expect(CName.GET_CURRENT_URL).
+ expect(CName.SWITCH_TO_WINDOW, {
+ 'name': 'foo',
+ 'handle': 'foo'
+ }).
+ andReturnError(new StubError()).
+ expect(CName.CLOSE).
+ end();
+
+ var driver = executor.createDriver();
+ driver.getTitle().then(function() {
+ driver.getCurrentUrl().
+ then(function() {
+ return driver.switchTo().window('foo');
+ }).
+ catch(function() {});
+ driver.close();
+ });
+
+ return waitForIdle();
+ });
+ });
+
+ describe('returningAPromise', function() {
+ it('fromACallback', function() {
+ let executor = new FakeExecutor().
+ expect(CName.GET_TITLE).
+ expect(CName.GET_CURRENT_URL).
+ andReturnSuccess('http://www.google.com').
+ end();
+
+ var driver = executor.createDriver();
+ driver.getTitle().
+ then(function() {
+ return driver.getCurrentUrl();
+ }).
+ then(function(value) {
+ assert.equal('http://www.google.com', value);
+ });
+ return waitForIdle();
+ });
+
+ it('fromAnErrbackSuppressesTheError', function() {
+ var count = 0;
+ let executor = new FakeExecutor().
+ expect(CName.SWITCH_TO_WINDOW, {
+ 'name': 'foo',
+ 'handle': 'foo'
+ }).
+ andReturnError(new StubError()).
+ expect(CName.GET_CURRENT_URL).
+ andReturnSuccess('http://www.google.com').
+ end();
+
+ var driver = executor.createDriver();
+ driver.switchTo().window('foo').
+ catch(function(e) {
+ assertIsStubError(e);
+ count += 1;
+ return driver.getCurrentUrl();
+ }).
+ then(function(url) {
+ count += 1;
+ assert.equal('http://www.google.com', url);
+ });
+ return waitForIdle().then(function() {
+ assert.equal(2, count);
+ });
+ });
+ });
+
+ describe('customFunctions', function() {
+ it('returnsANonPromiseValue', function() {
+ var driver = new FakeExecutor().createDriver();
+ return driver.call(() => 'abc123').then(function(value) {
+ assert.equal('abc123', value);
+ });
+ });
+
+ it('executionOrderWithCustomFunctions', function() {
+ var msg = [];
+ let executor = new FakeExecutor().
+ expect(CName.GET_TITLE).andReturnSuccess('cheese ').
+ expect(CName.GET_CURRENT_URL).andReturnSuccess('tasty').
+ end();
+
+ var driver = executor.createDriver();
+
+ var pushMsg = msg.push.bind(msg);
+ driver.getTitle().then(pushMsg);
+ driver.call(() => 'is ').then(pushMsg);
+ driver.getCurrentUrl().then(pushMsg);
+ driver.call(() => '!').then(pushMsg);
+
+ return waitForIdle().then(function() {
+ assert.equal('cheese is tasty!', msg.join(''));
+ });
+ });
+
+ it('passingArgumentsToACustomFunction', function() {
+ var add = function(a, b) {
+ return a + b;
+ };
+ var driver = new FakeExecutor().createDriver();
+ return driver.call(add, null, 1, 2).then(function(value) {
+ assert.equal(3, value);
+ });
+ });
+
+ it('passingPromisedArgumentsToACustomFunction', function() {
+ var promisedArg = promise.fulfilled(2);
+ var add = function(a, b) {
+ return a + b;
+ };
+ var driver = new FakeExecutor().createDriver();
+ return driver.call(add, null, 1, promisedArg).then(function(value) {
+ assert.equal(3, value);
+ });
+ });
+
+ it('passingArgumentsAndScopeToACustomFunction', function() {
+ function Foo(name) {
+ this.name = name;
+ }
+ Foo.prototype.getName = function() {
+ return this.name;
+ };
+ var foo = new Foo('foo');
+
+ var driver = new FakeExecutor().createDriver();
+ return driver.call(foo.getName, foo).then(function(value) {
+ assert.equal('foo', value);
+ });
+ });
+
+ it('customFunctionThrowsAnError', function() {
+ var driver = new FakeExecutor().createDriver();
+ return driver.call(throwStubError).then(fail, assertIsStubError);
+ });
+
+ it('customFunctionSchedulesCommands', function() {
+ let executor = new FakeExecutor().
+ expect(CName.GET_TITLE).
+ expect(CName.CLOSE).
+ expect(CName.QUIT).
+ end();
+
+ var driver = executor.createDriver();
+ driver.call(function() {
+ driver.getTitle();
+ driver.close();
+ });
+ driver.quit();
+ return waitForIdle();
+ });
+
+ it('returnsATaskResultAfterSchedulingAnother', function() {
+ let executor = new FakeExecutor().
+ expect(CName.GET_TITLE).
+ andReturnSuccess('Google Search').
+ expect(CName.CLOSE).
+ end();
+
+ var driver = executor.createDriver();
+ return driver.call(function() {
+ var title = driver.getTitle();
+ driver.close();
+ return title;
+ }).then(function(title) {
+ assert.equal('Google Search', title);
+ });
+ });
+
+ it('hasANestedCommandThatFails', function() {
+ let executor = new FakeExecutor().
+ expect(CName.SWITCH_TO_WINDOW, {
+ 'name': 'foo',
+ 'handle': 'foo'
+ }).
+ andReturnError(new StubError()).
+ end();
+
+ var driver = executor.createDriver();
+ return driver.call(function() {
+ return driver.switchTo().window('foo');
+ }).then(fail, assertIsStubError);
+ });
+
+ it('doesNotCompleteUntilReturnedPromiseIsResolved', function() {
+ var order = [];
+ var driver = new FakeExecutor().createDriver();
+
+ var d = promise.defer();
+ d.promise.then(function() {
+ order.push('b');
+ });
+
+ driver.call(function() {
+ order.push('a');
+ return d.promise;
+ });
+ driver.call(function() {
+ order.push('c');
+ });
+
+ // timeout to ensure the first function starts its execution before we
+ // trigger d's callbacks.
+ return new Promise(f => setTimeout(f, 0)).then(function() {
+ assert.deepEqual(['a'], order);
+ d.fulfill();
+ return waitForIdle().then(function() {
+ assert.deepEqual(['a', 'b', 'c'], order);
+ });
+ });
+ });
+
+ it('returnsADeferredAction', function() {
+ let executor = new FakeExecutor().
+ expect(CName.GET_TITLE).andReturnSuccess('Google').
+ end();
+
+ var driver = executor.createDriver();
+ driver.call(function() {
+ return driver.getTitle();
+ }).then(function(title) {
+ assert.equal('Google', title);
+ });
+ return waitForIdle();
+ });
+ });
+
+ describe('nestedCommands', function() {
+ it('commandExecutionOrder', function() {
+ var msg = [];
+ var driver = new FakeExecutor().createDriver();
+ driver.call(msg.push, msg, 'a');
+ driver.call(function() {
+ driver.call(msg.push, msg, 'c');
+ driver.call(function() {
+ driver.call(msg.push, msg, 'e');
+ driver.call(msg.push, msg, 'f');
+ });
+ driver.call(msg.push, msg, 'd');
+ });
+ driver.call(msg.push, msg, 'b');
+ return waitForIdle().then(function() {
+ assert.equal('acefdb', msg.join(''));
+ });
+ });
+
+ it('basicUsage', function() {
+ var msg = [];
+ var driver = new FakeExecutor().createDriver();
+ var pushMsg = msg.push.bind(msg);
+ driver.call(() => 'cheese ').then(pushMsg);
+ driver.call(function() {
+ driver.call(() => 'is ').then(pushMsg);
+ driver.call(() => 'tasty').then(pushMsg);
+ });
+ driver.call(() => '!').then(pushMsg);
+ return waitForIdle().then(function() {
+ assert.equal('cheese is tasty!', msg.join(''));
+ });
+ });
+
+ it('canReturnValueFromNestedFunction', function() {
+ var driver = new FakeExecutor().createDriver();
+ return driver.call(function() {
+ return driver.call(function() {
+ return driver.call(() => 'foobar');
+ });
+ }).then(function(value) {
+ assert.equal('foobar', value);
+ });
+ });
+
+ it('normalCommandAfterNestedCommandThatReturnsAnAction', function() {
+ var msg = [];
+ let executor = new FakeExecutor().
+ expect(CName.CLOSE).
+ end();
+ var driver = executor.createDriver();
+ driver.call(function() {
+ return driver.call(function() {
+ msg.push('a');
+ return driver.call(() => 'foobar');
+ });
+ });
+ driver.close().then(function() {
+ msg.push('b');
+ });
+ return waitForIdle().then(function() {
+ assert.equal('ab', msg.join(''));
+ });
+ });
+
+ it('errorsBubbleUp_caught', function() {
+ var driver = new FakeExecutor().createDriver();
+ var result = driver.call(function() {
+ return driver.call(function() {
+ return driver.call(throwStubError);
+ });
+ }).then(fail, assertIsStubError);
+ return Promise.all([waitForIdle(), result]);
+ });
+
+ it('errorsBubbleUp_uncaught', function() {
+ var driver = new FakeExecutor().createDriver();
+ driver.call(function() {
+ return driver.call(function() {
+ return driver.call(throwStubError);
+ });
+ });
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ it('canScheduleCommands', function() {
+ let executor = new FakeExecutor().
+ expect(CName.GET_TITLE).
+ expect(CName.CLOSE).
+ end();
+
+ var driver = executor.createDriver();
+ driver.call(function() {
+ driver.call(function() {
+ driver.getTitle();
+ });
+ driver.close();
+ });
+ return waitForIdle();
+ });
+ });
+
+ describe('WebElementPromise', function() {
+ it('resolvesWhenUnderlyingElementDoes', function() {
+ var el = new WebElement({}, {'ELEMENT': 'foo'});
+ return new WebElementPromise({}, promise.fulfilled(el)).then(function(e) {
+ assert.strictEqual(e, el);
+ });
+ });
+
+ it('resolvesBeforeCallbacksOnWireValueTrigger', function() {
+ var el = new promise.Deferred();
+
+ var element = new WebElementPromise({}, el.promise);
+ var messages = [];
+
+ element.then(function() {
+ messages.push('element resolved');
+ });
+ element.getId().then(function() {
+ messages.push('wire value resolved');
+ });
+
+ assert.deepEqual([], messages);
+ el.fulfill(new WebElement({}, {'ELEMENT': 'foo'}));
+ return waitForIdle().then(function() {
+ assert.deepEqual([
+ 'element resolved',
+ 'wire value resolved'
+ ], messages);
+ });
+ });
+
+ it('isRejectedIfUnderlyingIdIsRejected', function() {
+ var element = new WebElementPromise({}, promise.rejected(new StubError));
+ return element.then(fail, assertIsStubError);
+ });
+ });
+
+ describe('executeScript', function() {
+ it('nullReturnValue', function() {
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT).
+ withParameters({
+ 'script': 'return document.body;',
+ 'args': []
+ }).
+ andReturnSuccess(null).
+ end();
+
+ var driver = executor.createDriver();
+ return driver.executeScript('return document.body;')
+ .then((result) => assert.equal(null, result));
+ });
+
+ it('primitiveReturnValue', function() {
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT).
+ withParameters({
+ 'script': 'return document.body;',
+ 'args': []
+ }).
+ andReturnSuccess(123).
+ end();
+
+ var driver = executor.createDriver();
+ return driver.executeScript('return document.body;')
+ .then((result) => assert.equal(123, result));
+ });
+
+ it('webElementReturnValue', function() {
+ var json = WebElement.buildId('foo');
+
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT).
+ withParameters({
+ 'script': 'return document.body;',
+ 'args': []
+ }).
+ andReturnSuccess(json).
+ end();
+
+ var driver = executor.createDriver();
+ return driver.executeScript('return document.body;')
+ .then((element) => element.getId())
+ .then((id) => assert.equal(id, 'foo'));
+ });
+
+ it('arrayReturnValue', function() {
+ var json = [WebElement.buildId('foo')];
+
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT).
+ withParameters({
+ 'script': 'return document.body;',
+ 'args': []
+ }).
+ andReturnSuccess(json).
+ end();
+
+ var driver = executor.createDriver();
+ return driver.executeScript('return document.body;')
+ .then(function(array) {
+ assert.equal(1, array.length);
+ return array[0].getId();
+ })
+ .then((id) => assert.equal('foo', id));
+ });
+
+ it('objectReturnValue', function() {
+ var json = {'foo': WebElement.buildId('foo')};
+
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT).
+ withParameters({
+ 'script': 'return document.body;',
+ 'args': []
+ }).
+ andReturnSuccess(json).
+ end();
+
+ var driver = executor.createDriver();
+ var callback;
+ return driver.executeScript('return document.body;')
+ .then((obj) => obj['foo'].getId())
+ .then((id) => assert.equal(id, 'foo'));
+ });
+
+ it('scriptAsFunction', function() {
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT).
+ withParameters({
+ 'script': 'return (' + function() {} +
+ ').apply(null, arguments);',
+ 'args': []
+ }).
+ andReturnSuccess(null).
+ end();
+
+ var driver = executor.createDriver();
+ return driver.executeScript(function() {});
+ });
+
+ it('simpleArgumentConversion', function() {
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT).
+ withParameters({
+ 'script': 'return 1;',
+ 'args': ['abc', 123, true, [123, {'foo': 'bar'}]]
+ }).
+ andReturnSuccess(null).
+ end();
+
+ var driver = executor.createDriver();
+ return driver.executeScript(
+ 'return 1;', 'abc', 123, true, [123, {'foo': 'bar'}]);
+ });
+
+ it('webElementArgumentConversion', function() {
+ var elementJson = WebElement.buildId('fefifofum');
+
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT).
+ withParameters({
+ 'script': 'return 1;',
+ 'args': [elementJson]
+ }).
+ andReturnSuccess(null).
+ end();
+
+ var driver = executor.createDriver();
+ return driver.executeScript('return 1;',
+ new WebElement(driver, 'fefifofum'));
+ });
+
+ it('webElementPromiseArgumentConversion', function() {
+ var elementJson = WebElement.buildId('bar');
+
+ let executor = new FakeExecutor().
+ expect(CName.FIND_ELEMENT,
+ {'using': 'css selector', 'value': '*[id="foo"]'}).
+ andReturnSuccess(elementJson).
+ expect(CName.EXECUTE_SCRIPT).
+ withParameters({
+ 'script': 'return 1;',
+ 'args': [elementJson]
+ }).
+ andReturnSuccess(null).
+ end();
+
+ var driver = executor.createDriver();
+ var element = driver.findElement(By.id('foo'));
+ return driver.executeScript('return 1;', element);
+ });
+
+ it('argumentConversion', function() {
+ var elementJson = WebElement.buildId('fefifofum');
+
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT).
+ withParameters({
+ 'script': 'return 1;',
+ 'args': ['abc', 123, true, elementJson, [123, {'foo': 'bar'}]]
+ }).
+ andReturnSuccess(null).
+ end();
+
+ var driver = executor.createDriver();
+ var element = new WebElement(driver, 'fefifofum');
+ return driver.executeScript('return 1;',
+ 'abc', 123, true, element, [123, {'foo': 'bar'}]);
+ });
+
+ it('scriptReturnsAnError', function() {
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT).
+ withParameters({
+ 'script': 'throw Error(arguments[0]);',
+ 'args': ['bam']
+ }).
+ andReturnError(new StubError).
+ end();
+ var driver = executor.createDriver();
+ return driver.executeScript('throw Error(arguments[0]);', 'bam').
+ then(fail, assertIsStubError);
+ });
+
+ it('failsIfArgumentIsARejectedPromise', function() {
+ let executor = new FakeExecutor();
+
+ var arg = promise.rejected(new StubError);
+ arg.catch(function() {}); // Suppress default handler.
+
+ var driver = executor.createDriver();
+ return driver.executeScript(function() {}, arg).
+ then(fail, assertIsStubError);
+ });
+ });
+
+ describe('executeAsyncScript', function() {
+ it('failsIfArgumentIsARejectedPromise', function() {
+ var arg = promise.rejected(new StubError);
+ arg.catch(function() {}); // Suppress default handler.
+
+ var driver = new FakeExecutor().createDriver();
+ return driver.executeAsyncScript(function() {}, arg).
+ then(fail, assertIsStubError);
+ });
+ });
+
+ describe('findElement', function() {
+ it('elementNotFound', function() {
+ let executor = new FakeExecutor().
+ expect(CName.FIND_ELEMENT,
+ {using: 'css selector', value: '*[id="foo"]'}).
+ andReturnError(new StubError).
+ end();
+
+ var driver = executor.createDriver();
+ var element = driver.findElement(By.id('foo'));
+ element.click(); // This should never execute.
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ it('elementNotFoundInACallback', function() {
+ let executor = new FakeExecutor().
+ expect(CName.FIND_ELEMENT,
+ {using: 'css selector', value: '*[id="foo"]'}).
+ andReturnError(new StubError).
+ end();
+
+ var driver = executor.createDriver();
+ promise.fulfilled().then(function() {
+ var element = driver.findElement(By.id('foo'));
+ return element.click(); // Should not execute.
+ });
+ return waitForAbort().then(assertIsStubError);
+ });
+
+ it('elementFound', function() {
+ let executor = new FakeExecutor().
+ expect(CName.FIND_ELEMENT,
+ {using: 'css selector', value: '*[id="foo"]'}).
+ andReturnSuccess(WebElement.buildId('bar')).
+ expect(CName.CLICK_ELEMENT, {'id': 'bar'}).
+ andReturnSuccess().
+ end();
+
+ var driver = executor.createDriver();
+ var element = driver.findElement(By.id('foo'));
+ element.click();
+ return waitForIdle();
+ });
+
+ it('canUseElementInCallback', function() {
+ let executor = new FakeExecutor().
+ expect(CName.FIND_ELEMENT,
+ {using: 'css selector', value: '*[id="foo"]'}).
+ andReturnSuccess(WebElement.buildId('bar')).
+ expect(CName.CLICK_ELEMENT, {'id': 'bar'}).
+ andReturnSuccess().
+ end();
+
+ var driver = executor.createDriver();
+ driver.findElement(By.id('foo')).then(function(element) {
+ element.click();
+ });
+ return waitForIdle();
+ });
+
+ it('byJs', function() {
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT, {
+ 'script': 'return document.body',
+ 'args': []
+ }).
+ andReturnSuccess(WebElement.buildId('bar')).
+ expect(CName.CLICK_ELEMENT, {'id': 'bar'}).
+ end();
+
+ var driver = executor.createDriver();
+ var element = driver.findElement(By.js('return document.body'));
+ element.click(); // just to make sure
+ return waitForIdle();
+ });
+
+ it('byJs_returnsNonWebElementValue', function() {
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT, {'script': 'return 123', 'args': []}).
+ andReturnSuccess(123).
+ end();
+
+ var driver = executor.createDriver();
+ var element = driver.findElement(By.js('return 123'));
+ element.click(); // Should not execute.
+ return waitForAbort().then(function(e) {
+ assertIsInstance(TypeError, e);
+ assert.equal('Custom locator did not return a WebElement', e.message);
+ });
+ });
+
+ it('byJs_canPassArguments', function() {
+ var script = 'return document.getElementsByTagName(arguments[0]);';
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT, {
+ 'script': script,
+ 'args': ['div']
+ }).
+ andReturnSuccess({'ELEMENT':'one'}).
+ end();
+ var driver = executor.createDriver();
+ driver.findElement(By.js(script, 'div'));
+ return waitForIdle();
+ });
+
+ it('customLocator', function() {
+ let executor = new FakeExecutor().
+ expect(CName.FIND_ELEMENTS, {'using': 'css selector', 'value': 'a'}).
+ andReturnSuccess([
+ WebElement.buildId('foo'),
+ WebElement.buildId('bar')]).
+ expect(CName.CLICK_ELEMENT, {'id': 'foo'}).
+ andReturnSuccess().
+ end();
+
+ var driver = executor.createDriver();
+ var element = driver.findElement(function(d) {
+ assert.equal(driver, d);
+ return d.findElements(By.tagName('a'));
+ });
+ element.click();
+ return waitForIdle();
+ });
+
+ it('customLocatorThrowsIfresultIsNotAWebElement', function() {
+ var driver = new FakeExecutor().createDriver();
+ driver.findElement(function() {
+ return 1;
+ });
+ return waitForAbort().then(function(e) {
+ assertIsInstance(TypeError, e);
+ assert.equal('Custom locator did not return a WebElement', e.message);
+ });
+ });
+ });
+
+ describe('findElements', function() {
+ it('returnsMultipleElements', function() {
+ var ids = ['foo', 'bar', 'baz'];
+ let executor = new FakeExecutor().
+ expect(CName.FIND_ELEMENTS, {'using':'css selector', 'value':'a'}).
+ andReturnSuccess(ids.map(WebElement.buildId)).
+ end();
+
+ var driver = executor.createDriver();
+ return driver.findElements(By.tagName('a'))
+ .then(function(elements) {
+ return promise.all(elements.map(function(e) {
+ assert.ok(e instanceof WebElement);
+ return e.getId();
+ }));
+ })
+ .then((actual) => assert.deepEqual(ids, actual));
+ });
+
+ it('byJs', function() {
+ var ids = ['foo', 'bar', 'baz'];
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT, {
+ 'script': 'return document.getElementsByTagName("div");',
+ 'args': []
+ }).
+ andReturnSuccess(ids.map(WebElement.buildId)).
+ end();
+
+ var driver = executor.createDriver();
+
+ return driver.
+ findElements(By.js('return document.getElementsByTagName("div");')).
+ then(function(elements) {
+ return promise.all(elements.map(function(e) {
+ assert.ok(e instanceof WebElement);
+ return e.getId();
+ }));
+ }).
+ then((actual) => assert.deepEqual(ids, actual));
+ });
+
+ it('byJs_filtersOutNonWebElementResponses', function() {
+ var ids = ['foo', 'bar', 'baz'];
+ var json = [
+ WebElement.buildId(ids[0]),
+ 123,
+ 'a',
+ false,
+ WebElement.buildId(ids[1]),
+ {'not a web element': 1},
+ WebElement.buildId(ids[2])
+ ];
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT, {
+ 'script': 'return document.getElementsByTagName("div");',
+ 'args': []
+ }).
+ andReturnSuccess(json).
+ end();
+
+ var driver = executor.createDriver();
+ driver.findElements(By.js('return document.getElementsByTagName("div");')).
+ then(function(elements) {
+ return promise.all(elements.map(function(e) {
+ assert.ok(e instanceof WebElement);
+ return e.getId();
+ }));
+ }).
+ then((actual) => assert.deepEqual(ids, actual));
+ return waitForIdle();
+ });
+
+ it('byJs_convertsSingleWebElementResponseToArray', function() {
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT, {
+ 'script': 'return document.getElementsByTagName("div");',
+ 'args': []
+ }).
+ andReturnSuccess(WebElement.buildId('foo')).
+ end();
+
+ var driver = executor.createDriver();
+ return driver.
+ findElements(By.js('return document.getElementsByTagName("div");')).
+ then(function(elements) {
+ return promise.all(elements.map(function(e) {
+ assert.ok(e instanceof WebElement);
+ return e.getId();
+ }));
+ }).
+ then((actual) => assert.deepEqual(['foo'], actual));
+ });
+
+ it('byJs_canPassScriptArguments', function() {
+ var script = 'return document.getElementsByTagName(arguments[0]);';
+ let executor = new FakeExecutor().
+ expect(CName.EXECUTE_SCRIPT, {
+ 'script': script,
+ 'args': ['div']
+ }).
+ andReturnSuccess([
+ WebElement.buildId('one'),
+ WebElement.buildId('two')
+ ]).
+ end();
+
+ var driver = executor.createDriver();
+ return driver.findElements(By.js(script, 'div'))
+ then(function(elements) {
+ return promise.all(elements.map(function(e) {
+ assert.ok(e instanceof WebElement);
+ return e.getId();
+ }));
+ }).
+ then((actual) => assert.deepEqual(['one', 'two'], actual));
+ });
+ });
+
+ describe('sendKeys', function() {
+ it('convertsVarArgsIntoStrings_simpleArgs', function() {
+ let executor = new FakeExecutor().
+ expect(CName.SEND_KEYS_TO_ELEMENT,
+ {'id': 'one', 'value':'12abc3'.split('')}).
+ andReturnSuccess().
+ end();
+
+ var driver = executor.createDriver();
+ var element = new WebElement(driver, 'one');
+ element.sendKeys(1, 2, 'abc', 3);
+ return waitForIdle();
+ });
+
+ it('convertsVarArgsIntoStrings_promisedArgs', function() {
+ let executor = new FakeExecutor().
+ expect(CName.FIND_ELEMENT,
+ {'using':'css selector', 'value':'*[id="foo"]'}).
+ andReturnSuccess(WebElement.buildId('one')).
+ expect(CName.SEND_KEYS_TO_ELEMENT,
+ {'id':'one', 'value':'abc123def'.split('')}).
+ andReturnSuccess().
+ end();
+
+ var driver = executor.createDriver();
+ var element = driver.findElement(By.id('foo'));
+ element.sendKeys(
+ promise.fulfilled('abc'), 123,
+ promise.fulfilled('def'));
+ return waitForIdle();
+ });
+
+ it('sendKeysWithAFileDetector', function() {
+ let executor = new FakeExecutor().
+ expect(CName.FIND_ELEMENT,
+ {'using':'css selector', 'value':'*[id="foo"]'}).
+ andReturnSuccess(WebElement.buildId('one')).
+ expect(CName.SEND_KEYS_TO_ELEMENT,
+ {'id': 'one', 'value':'modified/path'.split('')}).
+ andReturnSuccess().
+ end();
+
+ let driver = executor.createDriver();
+ let handleFile = function(d, path) {
+ assert.strictEqual(driver, d);
+ assert.equal(path, 'original/path');
+ return promise.fulfilled('modified/path');
+ };
+ driver.setFileDetector({handleFile});
+
+ var element = driver.findElement(By.id('foo'));
+ element.sendKeys('original/', 'path');
+ return waitForIdle();
+ });
+ });
+
+ describe("switchTo()", function() {
+ describe("window", function() {
+ it('should return a resolved promise when the window is found', function() {
+ let executor = new FakeExecutor().
+ expect(CName.SWITCH_TO_WINDOW).
+ withParameters({
+ 'name': 'foo',
+ 'handle': 'foo'
+ }).
+ andReturnSuccess().
+ end();
+
+ executor.createDriver().switchTo().window('foo');
+ return waitForIdle();
+ });
+
+ it('should propogate exceptions', function() {
+ let e = new error.NoSuchWindowError('window not found');
+ let executor = new FakeExecutor().
+ expect(CName.SWITCH_TO_WINDOW).
+ withParameters({
+ 'name': 'foo',
+ 'handle': 'foo'
+ }).
+ andReturnError(e).
+ end();
+
+ executor.createDriver().switchTo().window('foo');
+ return waitForAbort().then(v => assert.strictEqual(v, e));
+ });
+ });
+ });
+
+ describe('elementEquality', function() {
+ it('isReflexive', function() {
+ var a = new WebElement({}, 'foo');
+ return WebElement.equals(a, a).then(assert.ok);
+ });
+
+ it('failsIfAnInputElementCouldNotBeFound', function() {
+ var id = promise.rejected(new StubError);
+ id.catch(function() {}); // Suppress default handler.
+
+ var driver = new FakeExecutor().createDriver();
+ var a = new WebElement(driver, 'foo');
+ var b = new WebElementPromise(driver, id);
+
+ return WebElement.equals(a, b).then(fail, assertIsStubError);
+ });
+ });
+
+ describe('waiting', function() {
+ it('waitSucceeds', function() {
+ let executor = new FakeExecutor().
+ expect(CName.FIND_ELEMENTS,
+ {using: 'css selector', value: '*[id="foo"]'}).
+ andReturnSuccess([]).
+ times(2).
+ expect(CName.FIND_ELEMENTS,
+ {using: 'css selector', value: '*[id="foo"]'}).
+ andReturnSuccess([WebElement.buildId('bar')]).
+ end();
+
+ var driver = executor.createDriver();
+ driver.wait(function() {
+ return driver.findElements(By.id('foo')).then(els => els.length > 0);
+ }, 200);
+ return waitForIdle();
+ });
+
+ it('waitTimesout_timeoutCaught', function() {
+ let executor = new FakeExecutor().
+ expect(CName.FIND_ELEMENTS,
+ {using: 'css selector', value: '*[id="foo"]'}).
+ andReturnSuccess([]).
+ anyTimes().
+ end();
+
+ var driver = executor.createDriver();
+ return driver.wait(function() {
+ return driver.findElements(By.id('foo')).then(els => els.length > 0);
+ }, 25).then(fail, function(e) {
+ assert.equal('Wait timed out after ',
+ e.message.substring(0, 'Wait timed out after '.length));
+ });
+ });
+
+ it('waitTimesout_timeoutNotCaught', function() {
+ let executor = new FakeExecutor().
+ expect(CName.FIND_ELEMENTS,
+ {using: 'css selector', value: '*[id="foo"]'}).
+ andReturnSuccess([]).
+ anyTimes().
+ end();
+
+ var driver = executor.createDriver();
+ driver.wait(function() {
+ return driver.findElements(By.id('foo')).then(els => els.length > 0);
+ }, 25);
+ return waitForAbort().then(function(e) {
+ assert.equal('Wait timed out after ',
+ e.message.substring(0, 'Wait timed out after '.length));
+ });
+ });
+ });
+
+ describe('alert handling', function() {
+ it('alertResolvesWhenPromisedTextResolves', function() {
+ var deferredText = new promise.Deferred();
+
+ var alert = new AlertPromise({}, deferredText.promise);
+ assert.ok(alert.isPending());
+
+ deferredText.fulfill(new Alert({}, 'foo'));
+ return alert.getText().then(function(text) {
+ assert.equal('foo', text);
+ });
+ });
+
+ it('cannotSwitchToAlertThatIsNotPresent', function() {
+ let e = new error.NoSuchAlertError;
+ let executor = new FakeExecutor()
+ .expect(CName.GET_ALERT_TEXT)
+ .andReturnError(e)
+ .end();
+
+ var driver = executor.createDriver();
+ var alert = driver.switchTo().alert();
+ alert.dismiss(); // Should never execute.
+ return waitForAbort().then(v => assert.strictEqual(v, e));
+ });
+
+ it('alertsBelongToSameFlowAsParentDriver', function() {
+ let executor = new FakeExecutor()
+ .expect(CName.GET_ALERT_TEXT).andReturnSuccess('hello')
+ .end();
+
+ var driver = executor.createDriver();
+ var otherFlow = new promise.ControlFlow();
+ otherFlow.execute(function() {
+ driver.switchTo().alert().then(function() {
+ assert.strictEqual(
+ driver.controlFlow(), promise.controlFlow(),
+ 'Alert should belong to the same flow as its parent driver');
+ });
+ });
+
+ assert.notEqual(otherFlow, driver.controlFlow);
+ return Promise.all([
+ waitForIdle(otherFlow),
+ waitForIdle(driver.controlFlow())
+ ]);
+ });
+
+ it('commandsFailIfAlertNotPresent', function() {
+ let e = new error.NoSuchAlertError;
+ let executor = new FakeExecutor()
+ .expect(CName.GET_ALERT_TEXT)
+ .andReturnError(e)
+ .end();
+
+ var driver = executor.createDriver();
+ var alert = driver.switchTo().alert();
+
+ var expectError = (v) => assert.strictEqual(v, e);
+
+ return alert.getText()
+ .then(fail, expectedError)
+ .then(() => alert.accept())
+ .then(fail, expectedError)
+ .then(() => alert.dismiss())
+ .then(fail, expectError)
+ .then(() => alert.sendKeys('hi'))
+ .then(fail, expectError);
+ });
+ });
+
+ it('testWebElementsBelongToSameFlowAsParentDriver', function() {
+ let executor = new FakeExecutor()
+ .expect(CName.FIND_ELEMENT,
+ {using: 'css selector', value: '*[id="foo"]'})
+ .andReturnSuccess(WebElement.buildId('abc123'))
+ .end();
+
+ var driver = executor.createDriver();
+ var otherFlow = new promise.ControlFlow();
+ otherFlow.execute(function() {
+ driver.findElement({id: 'foo'}).then(function() {
+ assert.equal(driver.controlFlow(), promise.controlFlow());
+ });
+ });
+
+ assert.notEqual(otherFlow, driver.controlFlow);
+ return Promise.all([
+ waitForIdle(otherFlow),
+ waitForIdle(driver.controlFlow())
+ ]);
+ });
+
+ it('testFetchingLogs', function() {
+ let executor = new FakeExecutor().
+ expect(CName.GET_LOG, {'type': 'browser'}).
+ andReturnSuccess([
+ {'level': 'INFO', 'message': 'hello', 'timestamp': 1234},
+ {'level': 'DEBUG', 'message': 'abc123', 'timestamp': 5678}
+ ]).
+ end();
+
+ var driver = executor.createDriver();
+ return driver.manage().logs().get('browser').then(function(entries) {
+ assert.equal(2, entries.length);
+
+ assert.ok(entries[0] instanceof logging.Entry);
+ assert.equal(logging.Level.INFO.value, entries[0].level.value);
+ assert.equal('hello', entries[0].message);
+ assert.equal(1234, entries[0].timestamp);
+
+ assert.ok(entries[1] instanceof logging.Entry);
+ assert.equal(logging.Level.DEBUG.value, entries[1].level.value);
+ assert.equal('abc123', entries[1].message);
+ assert.equal(5678, entries[1].timestamp);
+ });
+ });
+
+ it('testCommandsFailIfInitialSessionCreationFailed', function() {
+ var session = promise.rejected(new StubError);
+
+ var driver = new FakeExecutor().createDriver(session);
+ var navigateResult = driver.get('some-url').then(fail, assertIsStubError);
+ var quitResult = driver.quit().then(fail, assertIsStubError);
+
+ return waitForIdle().then(function() {
+ return promise.all(navigateResult, quitResult);
+ });
+ });
+
+ it('testWebElementCommandsFailIfInitialDriverCreationFailed', function() {
+ var session = Promise.reject(new StubError);
+ var driver = new FakeExecutor().createDriver(session);
+ return driver.findElement(By.id('foo')).click().
+ then(fail, assertIsStubError);
+ });
+
+ it('testWebElementCommansFailIfElementCouldNotBeFound', function() {
+ let e = new error.NoSuchElementError('Unable to find element');
+ let executor = new FakeExecutor().
+ expect(CName.FIND_ELEMENT,
+ {using: 'css selector', value: '*[id="foo"]'}).
+ andReturnError(e).
+ end();
+
+ var driver = executor.createDriver();
+ return driver.findElement(By.id('foo')).click()
+ .then(fail, v => assert.strictEqual(v, e));
+ });
+
+ it('testCannotFindChildElementsIfParentCouldNotBeFound', function() {
+ let e = new error.NoSuchElementError('Unable to find element');
+ let executor = new FakeExecutor().
+ expect(CName.FIND_ELEMENT,
+ {using: 'css selector', value: '*[id="foo"]'}).
+ andReturnError(e).
+ end();
+
+ var driver = executor.createDriver();
+ return driver.findElement(By.id('foo'))
+ .findElement(By.id('bar'))
+ .findElement(By.id('baz'))
+ .then(fail, v => assert.strictEqual(v, e));
+ });
+
+ describe('actions()', function() {
+ it('failsIfInitialDriverCreationFailed', function() {
+ let session = promise.rejected(new StubError);
+ session.catch(function() {});
+ return new FakeExecutor().
+ createDriver(session).
+ actions().
+ mouseDown().
+ mouseUp().
+ perform().
+ catch(assertIsStubError);
+ });
+
+ describe('mouseMove', function() {
+ it('noElement', function() {
+ let executor = new FakeExecutor()
+ .expect(CName.MOVE_TO, {'xoffset': 0, 'yoffset': 125})
+ .andReturnSuccess()
+ .end();
+
+ return executor.createDriver().
+ actions().
+ mouseMove({x: 0, y: 125}).
+ perform();
+ });
+
+ it('element', function() {
+ let executor = new FakeExecutor()
+ .expect(CName.FIND_ELEMENT,
+ {using: 'css selector', value: '*[id="foo"]'})
+ .andReturnSuccess(WebElement.buildId('abc123'))
+ .expect(CName.MOVE_TO,
+ {'element': 'abc123', 'xoffset': 0, 'yoffset': 125})
+ .andReturnSuccess()
+ .end();
+
+ var driver = executor.createDriver();
+ var element = driver.findElement(By.id('foo'));
+ return driver.actions()
+ .mouseMove(element, {x: 0, y: 125})
+ .perform();
+ });
+ });
+
+ it('supportsMouseDown', function() {
+ let executor = new FakeExecutor()
+ .expect(CName.MOUSE_DOWN, {'button': Button.LEFT})
+ .andReturnSuccess()
+ .end();
+
+ return executor.createDriver().
+ actions().
+ mouseDown().
+ perform();
+ });
+
+ it('testActionSequence', function() {
+ let executor = new FakeExecutor()
+ .expect(CName.FIND_ELEMENT,
+ {using: 'css selector', value: '*[id="a"]'})
+ .andReturnSuccess(WebElement.buildId('id1'))
+ .expect(CName.FIND_ELEMENT,
+ {using: 'css selector', value: '*[id="b"]'})
+ .andReturnSuccess(WebElement.buildId('id2'))
+ .expect(CName.SEND_KEYS_TO_ACTIVE_ELEMENT,
+ {'value': [Key.SHIFT]})
+ .andReturnSuccess()
+ .expect(CName.MOVE_TO, {'element': 'id1'})
+ .andReturnSuccess()
+ .expect(CName.CLICK, {'button': Button.LEFT})
+ .andReturnSuccess()
+ .expect(CName.MOVE_TO, {'element': 'id2'})
+ .andReturnSuccess()
+ .expect(CName.CLICK, {'button': Button.LEFT})
+ .andReturnSuccess()
+ .end();
+
+ var driver = executor.createDriver();
+ var element1 = driver.findElement(By.id('a'));
+ var element2 = driver.findElement(By.id('b'));
+
+ return driver.actions()
+ .keyDown(Key.SHIFT)
+ .click(element1)
+ .click(element2)
+ .perform();
+ });
+ });
+
+ describe('touchActions()', function() {
+ it('failsIfInitialDriverCreationFailed', function() {
+ let session = promise.rejected(new StubError);
+ session.catch(function() {});
+ return new FakeExecutor().
+ createDriver(session).
+ touchActions().
+ scroll({x: 3, y: 4}).
+ perform().
+ catch(assertIsStubError);
+ });
+
+ it('testTouchActionSequence', function() {
+ let executor = new FakeExecutor()
+ .expect(CName.TOUCH_DOWN, {x: 1, y: 2}).andReturnSuccess()
+ .expect(CName.TOUCH_MOVE, {x: 3, y: 4}).andReturnSuccess()
+ .expect(CName.TOUCH_UP, {x: 5, y: 6}).andReturnSuccess()
+ .end();
+
+ var driver = executor.createDriver();
+ return driver.touchActions()
+ .tapAndHold({x: 1, y: 2})
+ .move({x: 3, y: 4})
+ .release({x: 5, y: 6})
+ .perform();
+ });
+ });
+
+ describe('generator support', function() {
+ var driver;
+
+ beforeEach(function() {
+ driver = new WebDriver(
+ new Session('test-session', {}),
+ new ExplodingExecutor());
+ });
+
+ it('canUseGeneratorsWithWebDriverCall', function() {
+ return driver.call(function* () {
+ var x = yield promise.fulfilled(1);
+ var y = yield promise.fulfilled(2);
+ return x + y;
+ }).then(function(value) {
+ assert.deepEqual(3, value);
+ });
+ });
+
+ it('canDefineScopeOnGeneratorCall', function() {
+ return driver.call(function* () {
+ var x = yield promise.fulfilled(1);
+ return this.name + x;
+ }, {name: 'Bob'}).then(function(value) {
+ assert.deepEqual('Bob1', value);
+ });
+ });
+
+ it('canSpecifyArgsOnGeneratorCall', function() {
+ return driver.call(function* (a, b) {
+ var x = yield promise.fulfilled(1);
+ var y = yield promise.fulfilled(2);
+ return [x + y, a, b];
+ }, null, 'abc', 123).then(function(value) {
+ assert.deepEqual([3, 'abc', 123], value);
+ });
+ });
+
+ it('canUseGeneratorWithWebDriverWait', function() {
+ var values = [];
+ return driver.wait(function* () {
+ yield values.push(1);
+ values.push(yield promise.delayed(10).then(function() {
+ return 2;
+ }));
+ yield values.push(3);
+ return values.length === 6;
+ }, 250).then(function() {
+ assert.deepEqual([1, 2, 3, 1, 2, 3], values);
+ });
+ });
+
+ /**
+ * @constructor
+ * @implements {CommandExecutor}
+ */
+ function ExplodingExecutor() {}
+
+
+ /** @override */
+ ExplodingExecutor.prototype.execute = function(command, cb) {
+ cb(Error('Unsupported operation'));
+ };
+ });
+
+ describe('wire format', function() {
+ describe('can serialize', function() {
+ function runSerializeTest(input, want) {
+ let executor = new FakeExecutor().
+ expect(CName.NEW_SESSION).
+ withParameters({'desiredCapabilities': want}).
+ andReturnSuccess({'browserName': 'firefox'}).
+ end();
+ return WebDriver.createSession(executor, input)
+ .getSession();
+ }
+
+ it('function as a string', function() {
+ function foo() { return 'foo'; }
+ return runSerializeTest(foo, '' + foo);
+ });
+
+ it('object with toJSON()', function() {
+ return runSerializeTest(
+ new Date(605728511546),
+ '1989-03-12T17:55:11.546Z');
+ });
+
+ it('Session', function() {
+ return runSerializeTest(new Session('foo', {}), 'foo');
+ });
+
+ it('Capabilities', function() {
+ var prefs = new logging.Preferences();
+ prefs.setLevel(logging.Type.BROWSER, logging.Level.DEBUG);
+
+ var caps = Capabilities.chrome();
+ caps.setLoggingPrefs(prefs);
+
+ return runSerializeTest(
+ caps,
+ {
+ 'browserName': 'chrome',
+ 'loggingPrefs': {'browser': 'DEBUG'}
+ });
+ });
+
+ it('WebElement', function() {
+ return runSerializeTest(
+ new WebElement({}, 'fefifofum'),
+ WebElement.buildId('fefifofum'));
+ });
+
+ it('WebElementPromise', function() {
+ return runSerializeTest(
+ new WebElementPromise(
+ {}, promise.fulfilled(new WebElement({}, 'fefifofum'))),
+ WebElement.buildId('fefifofum'));
+ });
+
+ describe('an array', function() {
+ it('with Serializable', function() {
+ return runSerializeTest([new Session('foo', {})], ['foo']);
+ });
+
+ it('with WebElement', function() {
+ return runSerializeTest(
+ [new WebElement({}, 'fefifofum')],
+ [WebElement.buildId('fefifofum')]);
+ });
+
+ it('with WebElementPromise', function() {
+ return runSerializeTest(
+ [new WebElementPromise(
+ {}, promise.fulfilled(new WebElement({}, 'fefifofum')))],
+ [WebElement.buildId('fefifofum')]);
+ });
+
+ it('complex array', function() {
+ var expected = [
+ 'abc', 123, true, WebElement.buildId('fefifofum'),
+ [123, {'foo': 'bar'}]
+ ];
+
+ var element = new WebElement({}, 'fefifofum');
+ var input = ['abc', 123, true, element, [123, {'foo': 'bar'}]];
+ return runSerializeTest(input, expected);
+ });
+
+ it('nested promises', function() {
+ return runSerializeTest(
+ ['abc', Promise.resolve([123, Promise.resolve(true)])],
+ ['abc', [123, true]]);
+ });
+ });
+
+ describe('an object', function() {
+ it('literal', function() {
+ var expected = {sessionId: 'foo'};
+ return runSerializeTest({sessionId: 'foo'}, expected);
+ });
+
+ it('with sub-objects', function() {
+ var expected = {sessionId: {value: 'foo'}};
+ return runSerializeTest(
+ {sessionId: {value: 'foo'}}, expected);
+ });
+
+ it('with values that have toJSON', function() {
+ return runSerializeTest(
+ {a: {b: new Date(605728511546)}},
+ {a: {b: '1989-03-12T17:55:11.546Z'}});
+ });
+
+ it('with a Session', function() {
+ return runSerializeTest(
+ {a: new Session('foo', {})},
+ {a: 'foo'});
+ });
+
+ it('nested', function() {
+ var elementJson = WebElement.buildId('fefifofum');
+ var expected = {
+ 'script': 'return 1',
+ 'args': ['abc', 123, true, elementJson, [123, {'foo': 'bar'}]],
+ 'sessionId': 'foo'
+ };
+
+ var element = new WebElement({}, 'fefifofum');
+ var parameters = {
+ 'script': 'return 1',
+ 'args':['abc', 123, true, element, [123, {'foo': 'bar'}]],
+ 'sessionId': new Session('foo', {})
+ };
+
+ return runSerializeTest(parameters, expected);
+ });
+ });
+ });
+
+ describe('can deserialize', function() {
+ function runDeserializeTest(original, want) {
+ let executor = new FakeExecutor()
+ .expect(CName.GET_CURRENT_URL)
+ .andReturnSuccess(original)
+ .end();
+ let driver = executor.createDriver();
+ return driver.getCurrentUrl().then(function(got) {
+ assert.deepEqual(got, want);
+ });
+ }
+
+ it('primitives', function() {
+ return Promise.all([
+ runDeserializeTest(1, 1),
+ runDeserializeTest('', ''),
+ runDeserializeTest(true, true),
+ runDeserializeTest(undefined, undefined),
+ runDeserializeTest(null, null)
+ ]);
+ });
+
+ it('simple object', function() {
+ return runDeserializeTest(
+ {sessionId: 'foo'},
+ {sessionId: 'foo'});
+ });
+
+ it('nested object', function() {
+ return runDeserializeTest(
+ {'foo': {'bar': 123}},
+ {'foo': {'bar': 123}});
+ });
+
+ it('array', function() {
+ return runDeserializeTest(
+ [{'foo': {'bar': 123}}],
+ [{'foo': {'bar': 123}}]);
+ });
+
+ it('passes through function properties', function() {
+ function bar() {}
+ return runDeserializeTest(
+ [{foo: {'bar': 123}, func: bar}],
+ [{foo: {'bar': 123}, func: bar}]);
+ });
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/logging_test.js b/node_modules/selenium-webdriver/test/logging_test.js
new file mode 100644
index 000000000..fd88daa72
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/logging_test.js
@@ -0,0 +1,169 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var Browser = require('..').Browser,
+ By = require('..').By,
+ logging = require('..').logging,
+ assert = require('../testing/assert'),
+ test = require('../lib/test');
+
+test.suite(function(env) {
+ // Logging API has numerous issues with PhantomJS:
+ // - does not support adjusting log levels for type "browser".
+ // - does not return proper log level for "browser" messages.
+ // - does not delete logs after retrieval
+ // Logging API is not supported in IE.
+ // Logging API not supported in Marionette.
+ // Tests depend on opening data URLs, which is broken in Safari (issue 7586)
+ test.ignore(env.browsers(
+ Browser.PHANTOM_JS, Browser.IE, Browser.SAFARI, Browser.FIREFOX)).
+ describe('logging', function() {
+ var driver;
+
+ test.beforeEach(function() {
+ driver = null;
+ });
+
+ test.afterEach(function() {
+ if (driver) {
+ driver.quit();
+ }
+ });
+
+ test.it('can be disabled', function() {
+ var prefs = new logging.Preferences();
+ prefs.setLevel(logging.Type.BROWSER, logging.Level.OFF);
+
+ driver = env.builder()
+ .setLoggingPrefs(prefs)
+ .build();
+
+ driver.get(dataUrl(
+ '<!DOCTYPE html><script>',
+ 'console.info("hello");',
+ 'console.warn("this is a warning");',
+ 'console.error("and this is an error");',
+ '</script>'));
+ driver.manage().logs().get(logging.Type.BROWSER).then(function(entries) {
+ assert(entries.length).equalTo(0);
+ });
+ });
+
+ // Firefox does not capture JS error console log messages.
+ test.ignore(env.browsers(Browser.FIREFOX, 'legacy-firefox')).
+ it('can be turned down', function() {
+ var prefs = new logging.Preferences();
+ prefs.setLevel(logging.Type.BROWSER, logging.Level.SEVERE);
+
+ driver = env.builder()
+ .setLoggingPrefs(prefs)
+ .build();
+
+ driver.get(dataUrl(
+ '<!DOCTYPE html><script>',
+ 'console.info("hello");',
+ 'console.warn("this is a warning");',
+ 'console.error("and this is an error");',
+ '</script>'));
+ driver.manage().logs().get(logging.Type.BROWSER).then(function(entries) {
+ assert(entries.length).equalTo(1);
+ assert(entries[0].level.name).equalTo('SEVERE');
+ assert(entries[0].message).endsWith('and this is an error');
+ });
+ });
+
+ // Firefox does not capture JS error console log messages.
+ test.ignore(env.browsers(Browser.FIREFOX, 'legacy-firefox')).
+ it('can be made verbose', function() {
+ var prefs = new logging.Preferences();
+ prefs.setLevel(logging.Type.BROWSER, logging.Level.DEBUG);
+
+ driver = env.builder()
+ .setLoggingPrefs(prefs)
+ .build();
+
+ driver.get(dataUrl(
+ '<!DOCTYPE html><script>',
+ 'console.debug("hello");',
+ 'console.warn("this is a warning");',
+ 'console.error("and this is an error");',
+ '</script>'));
+ driver.manage().logs().get(logging.Type.BROWSER).then(function(entries) {
+ assert(entries.length).equalTo(3);
+ assert(entries[0].level.name).equalTo('DEBUG');
+ assert(entries[0].message).endsWith('hello');
+
+ assert(entries[1].level.name).equalTo('WARNING');
+ assert(entries[1].message).endsWith('this is a warning');
+
+ assert(entries[2].level.name).equalTo('SEVERE');
+ assert(entries[2].message).endsWith('and this is an error');
+ });
+ });
+
+ // Firefox does not capture JS error console log messages.
+ test.ignore(env.browsers(Browser.FIREFOX, 'legacy-firefox')).
+ it('clears records after retrieval', function() {
+ var prefs = new logging.Preferences();
+ prefs.setLevel(logging.Type.BROWSER, logging.Level.DEBUG);
+
+ driver = env.builder()
+ .setLoggingPrefs(prefs)
+ .build();
+
+ driver.get(dataUrl(
+ '<!DOCTYPE html><script>',
+ 'console.debug("hello");',
+ 'console.warn("this is a warning");',
+ 'console.error("and this is an error");',
+ '</script>'));
+ driver.manage().logs().get(logging.Type.BROWSER).then(function(entries) {
+ assert(entries.length).equalTo(3);
+ });
+ driver.manage().logs().get(logging.Type.BROWSER).then(function(entries) {
+ assert(entries.length).equalTo(0);
+ });
+ });
+
+ test.it('does not mix log types', function() {
+ var prefs = new logging.Preferences();
+ prefs.setLevel(logging.Type.BROWSER, logging.Level.DEBUG);
+ prefs.setLevel(logging.Type.DRIVER, logging.Level.SEVERE);
+
+ driver = env.builder()
+ .setLoggingPrefs(prefs)
+ .build();
+
+ driver.get(dataUrl(
+ '<!DOCTYPE html><script>',
+ 'console.debug("hello");',
+ 'console.warn("this is a warning");',
+ 'console.error("and this is an error");',
+ '</script>'));
+ driver.manage().logs().get(logging.Type.DRIVER).then(function(entries) {
+ assert(entries.length).equalTo(0);
+ });
+ });
+ });
+
+ function dataUrl(var_args) {
+ return 'data:text/html,'
+ + Array.prototype.slice.call(arguments, 0).join('');
+ }
+});
diff --git a/node_modules/selenium-webdriver/test/net/index_test.js b/node_modules/selenium-webdriver/test/net/index_test.js
new file mode 100644
index 000000000..88c52c063
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/net/index_test.js
@@ -0,0 +1,60 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var assert = require('assert');
+
+var net = require('../../net');
+
+describe('net.splitHostAndPort', function() {
+ it('hostname with no port', function() {
+ assert.deepEqual(
+ net.splitHostAndPort('www.example.com'),
+ {host: 'www.example.com', port: null});
+ });
+
+ it('hostname with port', function() {
+ assert.deepEqual(
+ net.splitHostAndPort('www.example.com:80'),
+ {host: 'www.example.com', port: 80});
+ });
+
+ it('IPv4 with no port', function() {
+ assert.deepEqual(
+ net.splitHostAndPort('127.0.0.1'),
+ {host: '127.0.0.1', port: null});
+ });
+
+ it('IPv4 with port', function() {
+ assert.deepEqual(
+ net.splitHostAndPort('127.0.0.1:1234'),
+ {host: '127.0.0.1', port: 1234});
+ });
+
+ it('IPv6 with no port', function() {
+ assert.deepEqual(
+ net.splitHostAndPort('1234:0:1000:5768:1234:5678:90'),
+ {host: '1234:0:1000:5768:1234:5678:90', port: null});
+ });
+
+ it('IPv6 with port', function() {
+ assert.deepEqual(
+ net.splitHostAndPort('[1234:0:1000:5768:1234:5678:90]:1234'),
+ {host: '1234:0:1000:5768:1234:5678:90', port: 1234});
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/net/portprober_test.js b/node_modules/selenium-webdriver/test/net/portprober_test.js
new file mode 100644
index 000000000..03a2f7a10
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/net/portprober_test.js
@@ -0,0 +1,129 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var assert = require('assert'),
+ net = require('net');
+
+var promise = require('../..').promise,
+ portprober = require('../../net/portprober');
+
+describe('isFree', function() {
+
+ var server;
+
+ beforeEach(function() {
+ server = net.createServer(function(){});
+ })
+
+ afterEach(function(done) {
+ if (!server) return done();
+ server.close(function() {
+ done();
+ });
+ });
+
+ it('should work for INADDR_ANY', function(done) {
+ server.listen(0, function() {
+ var port = server.address().port;
+ assertPortNotfree(port).then(function() {
+ var done = promise.defer();
+ server.close(function() {
+ server = null;
+ done.fulfill(assertPortIsFree(port));
+ });
+ return done.promise;
+ }).then(function() { done(); }, done);
+ });
+ });
+
+ it('should work for a specific host', function(done) {
+ var host = '127.0.0.1';
+ server.listen(0, host, function() {
+ var port = server.address().port;
+ assertPortNotfree(port, host).then(function() {
+ var done = promise.defer();
+ server.close(function() {
+ server = null;
+ done.fulfill(assertPortIsFree(port, host));
+ });
+ return done.promise;
+ }).then(function() { done(); }, done);
+ });
+ });
+});
+
+describe('findFreePort', function() {
+ var server;
+
+ beforeEach(function() {
+ server = net.createServer(function(){});
+ })
+
+ afterEach(function(done) {
+ if (!server) return done();
+ server.close(function() {
+ done();
+ });
+ });
+
+ it('should work for INADDR_ANY', function(done) {
+ portprober.findFreePort().then(function(port) {
+ server.listen(port, function() {
+ assertPortNotfree(port).then(function() {
+ var done = promise.defer();
+ server.close(function() {
+ server = null;
+ done.fulfill(assertPortIsFree(port));
+ });
+ return done.promise;
+ }).then(function() { done(); }, done);
+ });
+ });
+ });
+
+ it('should work for a specific host', function(done) {
+ var host = '127.0.0.1';
+ portprober.findFreePort(host).then(function(port) {
+ server.listen(port, host, function() {
+ assertPortNotfree(port, host).then(function() {
+ var done = promise.defer();
+ server.close(function() {
+ server = null;
+ done.fulfill(assertPortIsFree(port, host));
+ });
+ return done.promise;
+ }).then(function() { done(); }, done);
+ });
+ });
+ });
+});
+
+
+function assertPortIsFree(port, opt_host) {
+ return portprober.isFree(port, opt_host).then(function(free) {
+ assert.ok(free, 'port should be free');
+ });
+}
+
+
+function assertPortNotfree(port, opt_host) {
+ return portprober.isFree(port, opt_host).then(function(free) {
+ assert.ok(!free, 'port should is not free');
+ });
+}
diff --git a/node_modules/selenium-webdriver/test/page_loading_test.js b/node_modules/selenium-webdriver/test/page_loading_test.js
new file mode 100644
index 000000000..098460370
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/page_loading_test.js
@@ -0,0 +1,166 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var Browser = require('..').Browser,
+ By = require('..').By,
+ until = require('..').until,
+ assert = require('../testing/assert'),
+ error = require('../lib/error'),
+ test = require('../lib/test'),
+ Pages = test.Pages;
+
+
+test.suite(function(env) {
+ var browsers = env.browsers;
+
+ var driver;
+ test.before(function() {
+ driver = env.builder().build();
+ });
+
+ test.after(function() {
+ driver.quit();
+ });
+
+ test.it('should wait for document to be loaded', function() {
+ driver.get(Pages.simpleTestPage);
+ assert(driver.getTitle()).equalTo('Hello WebDriver');
+ });
+
+ test.it('should follow redirects sent in the http response headers',
+ function() {
+ driver.get(Pages.redirectPage);
+ assert(driver.getTitle()).equalTo('We Arrive Here');
+ });
+
+ test.it('should follow meta redirects', function() {
+ driver.get(Pages.metaRedirectPage);
+ assert(driver.getTitle()).equalTo('We Arrive Here');
+ });
+
+ // Skip Firefox; see https://bugzilla.mozilla.org/show_bug.cgi?id=1280300
+ test.ignore(browsers(Browser.FIREFOX)).
+ it('should be able to get a fragment on the current page', function() {
+ driver.get(Pages.xhtmlTestPage);
+ driver.get(Pages.xhtmlTestPage + '#text');
+ driver.findElement(By.id('id1'));
+ });
+
+ test.ignore(browsers(Browser.IPAD, Browser.IPHONE)).
+ it('should wait for all frames to load in a frameset', function() {
+ driver.get(Pages.framesetPage);
+ driver.switchTo().frame(0);
+
+ driver.findElement(By.css('span#pageNumber')).getText().then(function(txt) {
+ assert(txt.trim()).equalTo('1');
+ });
+
+ driver.switchTo().defaultContent();
+ driver.switchTo().frame(1);
+ driver.findElement(By.css('span#pageNumber')).getText().then(function(txt) {
+ assert(txt.trim()).equalTo('2');
+ });
+ });
+
+ test.ignore(browsers(Browser.SAFARI)).
+ it('should be able to navigate back in browser history', function() {
+ driver.get(Pages.formPage);
+
+ driver.findElement(By.id('imageButton')).click();
+ driver.wait(until.titleIs('We Arrive Here'), 2500);
+
+ driver.navigate().back();
+ driver.wait(until.titleIs('We Leave From Here'), 2500);
+ });
+
+ test.ignore(browsers(Browser.SAFARI)).
+ it('should be able to navigate back in presence of iframes', function() {
+ driver.get(Pages.xhtmlTestPage);
+
+ driver.findElement(By.name('sameWindow')).click();
+ driver.wait(until.titleIs('This page has iframes'), 2500);
+
+ driver.navigate().back();
+ driver.wait(until.titleIs('XHTML Test Page'), 2500);
+ });
+
+ test.ignore(browsers(Browser.SAFARI)).
+ it('should be able to navigate forwards in browser history', function() {
+ driver.get(Pages.formPage);
+
+ driver.findElement(By.id('imageButton')).click();
+ driver.wait(until.titleIs('We Arrive Here'), 5000);
+
+ driver.navigate().back();
+ driver.wait(until.titleIs('We Leave From Here'), 5000);
+
+ driver.navigate().forward();
+ driver.wait(until.titleIs('We Arrive Here'), 5000);
+ });
+
+ // PhantomJS 2.0 does not properly reload pages on refresh.
+ test.ignore(browsers(Browser.PHANTOM_JS)).
+ it('should be able to refresh a page', function() {
+ driver.get(Pages.xhtmlTestPage);
+
+ driver.navigate().refresh();
+
+ assert(driver.getTitle()).equalTo('XHTML Test Page');
+ });
+
+ test.it('should return title of page if set', function() {
+ driver.get(Pages.xhtmlTestPage);
+ assert(driver.getTitle()).equalTo('XHTML Test Page');
+
+ driver.get(Pages.simpleTestPage);
+ assert(driver.getTitle()).equalTo('Hello WebDriver');
+ });
+
+ // Only implemented in Firefox.
+ test.ignore(browsers(
+ Browser.CHROME,
+ Browser.IE,
+ Browser.IPAD,
+ Browser.IPHONE,
+ Browser.OPERA,
+ Browser.PHANTOM_JS,
+ Browser.SAFARI)).
+ it('should timeout if page load timeout is set', function() {
+ driver.call(function() {
+ driver.manage().timeouts().pageLoadTimeout(1);
+ driver.get(Pages.sleepingPage + '?time=3').
+ then(function() {
+ throw Error('Should have timed out on page load');
+ }, function(e) {
+ if (!(e instanceof error.ScriptTimeoutError)
+ && !(e instanceof error.TimeoutError)) {
+ throw Error('Unexpected error response: ' + e);
+ }
+ });
+ }).then(resetPageLoad, function(err) {
+ resetPageLoad().finally(function() {
+ throw err;
+ });
+ });
+
+ function resetPageLoad() {
+ return driver.manage().timeouts().pageLoadTimeout(-1);
+ }
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/phantomjs/execute_phantomjs_test.js b/node_modules/selenium-webdriver/test/phantomjs/execute_phantomjs_test.js
new file mode 100644
index 000000000..82a814a31
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/phantomjs/execute_phantomjs_test.js
@@ -0,0 +1,73 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var assert = require('assert');
+var path = require('path');
+var test = require('../../lib/test');
+
+test.suite(function(env) {
+ var driver;
+
+ test.before(function() {
+ driver = env.builder().build();
+ });
+
+ test.after(function() {
+ driver.quit();
+ });
+
+ var testPageUrl =
+ 'data:text/html,<html><h1>' + path.basename(__filename) + '</h1></html>';
+
+ test.beforeEach(function() {
+ driver.get(testPageUrl);
+ });
+
+ describe('phantomjs.Driver', function() {
+ describe('#executePhantomJS()', function() {
+
+ test.it('can execute scripts using PhantomJS API', function() {
+ return driver.executePhantomJS('return this.url;').then(function(url) {
+ assert.equal(testPageUrl, decodeURIComponent(url));
+ });
+ });
+
+ test.it('can execute scripts as functions', function() {
+ driver.executePhantomJS(function(a, b) {
+ return a + b;
+ }, 1, 2).then(function(result) {
+ assert.equal(3, result);
+ });
+ });
+
+ test.it('can manipulate the current page', function() {
+ driver.manage().addCookie({name: 'foo', value: 'bar'});
+ driver.manage().getCookie('foo').then(function(cookie) {
+ assert.equal('bar', cookie.value);
+ });
+ driver.executePhantomJS(function() {
+ this.clearCookies();
+ });
+ driver.manage().getCookie('foo').then(function(cookie) {
+ assert.equal(null, cookie);
+ });
+ });
+ });
+ });
+}, {browsers: ['phantomjs']});
diff --git a/node_modules/selenium-webdriver/test/proxy_test.js b/node_modules/selenium-webdriver/test/proxy_test.js
new file mode 100644
index 000000000..c25565b48
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/proxy_test.js
@@ -0,0 +1,180 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var http = require('http'),
+ url = require('url');
+
+var Browser = require('..').Browser,
+ promise = require('..').promise,
+ firefox = require('../firefox'),
+ proxy = require('../proxy'),
+ assert = require('../testing/assert'),
+ test = require('../lib/test'),
+ Server = require('../lib/test/httpserver').Server,
+ Pages = test.Pages;
+
+test.suite(function(env) {
+ function writeResponse(res, body, encoding, contentType) {
+ res.writeHead(200, {
+ 'Content-Length': Buffer.byteLength(body, encoding),
+ 'Content-Type': contentType
+ });
+ res.end(body);
+ }
+
+ function writePacFile(res) {
+ writeResponse(res, [
+ 'function FindProxyForURL(url, host) {',
+ ' if (shExpMatch(url, "' + goodbyeServer.url('*') + '")) {',
+ ' return "DIRECT";',
+ ' }',
+ ' return "PROXY ' + proxyServer.host() + '";',
+ '}'
+ ].join('\n'), 'ascii', 'application/x-javascript-config');
+ }
+
+ var proxyServer = new Server(function(req, res) {
+ var pathname = url.parse(req.url).pathname;
+ if (pathname === '/proxy.pac') {
+ return writePacFile(res);
+ }
+
+ writeResponse(res, [
+ '<!DOCTYPE html>',
+ '<title>Proxy page</title>',
+ '<h3>This is the proxy landing page</h3>'
+ ].join(''), 'utf8', 'text/html; charset=UTF-8');
+ });
+
+ var helloServer = new Server(function(req, res) {
+ writeResponse(res, [
+ '<!DOCTYPE html>',
+ '<title>Hello</title>',
+ '<h3>Hello, world!</h3>'
+ ].join(''), 'utf8', 'text/html; charset=UTF-8');
+ });
+
+ var goodbyeServer = new Server(function(req, res) {
+ writeResponse(res, [
+ '<!DOCTYPE html>',
+ '<title>Goodbye</title>',
+ '<h3>Goodbye, world!</h3>'
+ ].join(''), 'utf8', 'text/html; charset=UTF-8');
+ });
+
+ // Cannot pass start directly to mocha's before, as mocha will interpret the optional
+ // port parameter as an async callback parameter.
+ function mkStartFunc(server) {
+ return function() {
+ return server.start();
+ };
+ }
+
+ test.before(mkStartFunc(proxyServer));
+ test.before(mkStartFunc(helloServer));
+ test.before(mkStartFunc(goodbyeServer));
+
+ test.after(proxyServer.stop.bind(proxyServer));
+ test.after(helloServer.stop.bind(helloServer));
+ test.after(goodbyeServer.stop.bind(goodbyeServer));
+
+ var driver;
+ test.beforeEach(function() { driver = null; });
+ test.afterEach(function() { driver && driver.quit(); });
+
+ function createDriver(proxy) {
+ // For Firefox we need to explicitly enable proxies for localhost by
+ // clearing the network.proxy.no_proxies_on preference.
+ let profile = new firefox.Profile();
+ profile.setPreference('network.proxy.no_proxies_on', '');
+
+ driver = env.builder()
+ .setFirefoxOptions(new firefox.Options().setProfile(profile))
+ .setProxy(proxy)
+ .build();
+ }
+
+ // Proxy support not implemented.
+ test.ignore(env.browsers(Browser.IE, Browser.OPERA, Browser.SAFARI)).
+ describe('manual proxy settings', function() {
+ // phantomjs 1.9.1 in webdriver mode does not appear to respect proxy
+ // settings.
+ test.ignore(env.browsers(Browser.PHANTOM_JS)).
+ it('can configure HTTP proxy host', function() {
+ createDriver(proxy.manual({
+ http: proxyServer.host()
+ }));
+
+ driver.get(helloServer.url());
+ assert(driver.getTitle()).equalTo('Proxy page');
+ assert(driver.findElement({tagName: 'h3'}).getText()).
+ equalTo('This is the proxy landing page');
+ });
+
+ // PhantomJS does not support bypassing the proxy for individual hosts.
+ // geckodriver does not support the bypass option, this must be configured
+ // through profile preferences.
+ test.ignore(env.browsers(
+ Browser.FIREFOX,
+ 'legacy-' + Browser.FIREFOX,
+ Browser.PHANTOM_JS)).
+ it('can bypass proxy for specific hosts', function() {
+ createDriver(proxy.manual({
+ http: proxyServer.host(),
+ bypass: helloServer.host()
+ }));
+
+ driver.get(helloServer.url());
+ assert(driver.getTitle()).equalTo('Hello');
+ assert(driver.findElement({tagName: 'h3'}).getText()).
+ equalTo('Hello, world!');
+
+ driver.get(goodbyeServer.url());
+ assert(driver.getTitle()).equalTo('Proxy page');
+ assert(driver.findElement({tagName: 'h3'}).getText()).
+ equalTo('This is the proxy landing page');
+ });
+
+ // TODO: test ftp and https proxies.
+ });
+
+ // PhantomJS does not support PAC file proxy configuration.
+ // Safari does not support proxies.
+ test.ignore(env.browsers(
+ Browser.IE, Browser.OPERA, Browser.PHANTOM_JS, Browser.SAFARI)).
+ describe('pac proxy settings', function() {
+ test.it('can configure proxy through PAC file', function() {
+ createDriver(proxy.pac(proxyServer.url('/proxy.pac')));
+
+ driver.get(helloServer.url());
+ assert(driver.getTitle()).equalTo('Proxy page');
+ assert(driver.findElement({tagName: 'h3'}).getText()).
+ equalTo('This is the proxy landing page');
+
+ driver.get(goodbyeServer.url());
+ assert(driver.getTitle()).equalTo('Goodbye');
+ assert(driver.findElement({tagName: 'h3'}).getText()).
+ equalTo('Goodbye, world!');
+ });
+ });
+
+ // TODO: figure out how to test direct and system proxy settings.
+ describe.skip('direct proxy settings', function() {});
+ describe.skip('system proxy settings', function() {});
+});
diff --git a/node_modules/selenium-webdriver/test/remote_test.js b/node_modules/selenium-webdriver/test/remote_test.js
new file mode 100644
index 000000000..5edc448bb
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/remote_test.js
@@ -0,0 +1,117 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var assert = require('assert'),
+ fs = require('fs'),
+ path = require('path');
+
+var promise = require('../').promise,
+ io = require('../io'),
+ cmd = require('../lib/command'),
+ remote = require('../remote');
+
+describe('DriverService', function() {
+ describe('start()', function() {
+ var service;
+
+ beforeEach(function() {
+ service = new remote.DriverService(process.execPath, {
+ port: 1234,
+ args: ['-e', 'process.exit(1)']
+ });
+ });
+
+ afterEach(function() {
+ return service.kill();
+ });
+
+ it('fails if child-process dies', function(done) {
+ this.timeout(1000);
+ service.start(500)
+ .then(expectFailure.bind(null, done), verifyFailure.bind(null, done));
+ });
+
+ it('failures propagate through control flow if child-process dies',
+ function(done) {
+ this.timeout(1000);
+
+ promise.controlFlow().execute(function() {
+ promise.controlFlow().execute(function() {
+ return service.start(500);
+ });
+ })
+ .then(expectFailure.bind(null, done), verifyFailure.bind(null, done));
+ });
+
+ function verifyFailure(done, e) {
+ try {
+ assert.ok(!(e instanceof promise.CancellationError));
+ assert.equal('Server terminated early with status 1', e.message);
+ done();
+ } catch (ex) {
+ done(ex);
+ }
+ }
+
+ function expectFailure(done) {
+ done(Error('expected to fail'));
+ }
+ });
+});
+
+describe('FileDetector', function() {
+ class ExplodingDriver {
+ schedule() {
+ throw Error('unexpected call');
+ }
+ }
+
+ it('returns the original path if the file does not exist', function() {
+ return io.tmpDir(dir => {
+ let theFile = path.join(dir, 'not-there');
+ return (new remote.FileDetector)
+ .handleFile(new ExplodingDriver, theFile)
+ .then(f => assert.equal(f, theFile));
+ });
+ });
+
+ it('returns the original path if it is a directory', function() {
+ return io.tmpDir(dir => {
+ return (new remote.FileDetector)
+ .handleFile(new ExplodingDriver, dir)
+ .then(f => assert.equal(f, dir));
+ });
+ });
+
+ it('attempts to upload valid files', function() {
+ return io.tmpFile(theFile => {
+ return (new remote.FileDetector)
+ .handleFile(
+ new (class FakeDriver {
+ schedule(command) {
+ assert.equal(command.getName(), cmd.Name.UPLOAD_FILE);
+ assert.equal(typeof command.getParameters()['file'], 'string');
+ return Promise.resolve('success!');
+ }
+ }),
+ theFile)
+ .then(f => assert.equal(f, 'success!'));
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/safari_test.js b/node_modules/selenium-webdriver/test/safari_test.js
new file mode 100644
index 000000000..41525d9cb
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/safari_test.js
@@ -0,0 +1,112 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+const webdriver = require('..'),
+ proxy = require('../proxy'),
+ safari = require('../safari'),
+ assert = require('../testing/assert'),
+ test = require('../lib/test');
+
+
+describe('safari.Options', function() {
+ describe('fromCapabilities', function() {
+ it('returns a new Options instance if none were defined', function() {
+ let options = safari.Options.fromCapabilities(
+ new webdriver.Capabilities());
+ assert(options).instanceOf(safari.Options);
+ });
+
+ it('returns the options instance if present', function() {
+ let options = new safari.Options().setCleanSession(true),
+ caps = options.toCapabilities();
+ assert(safari.Options.fromCapabilities(caps)).equalTo(options);
+ });
+
+ it('extracts supported WebDriver capabilities', function() {
+ let proxyPrefs = proxy.direct(),
+ logPrefs = {},
+ caps = webdriver.Capabilities.chrome()
+ .set(webdriver.Capability.PROXY, proxyPrefs)
+ .set(webdriver.Capability.LOGGING_PREFS, logPrefs)
+ .set('legacyDriver', true);
+
+ let options = safari.Options.fromCapabilities(caps);
+ assert(options.proxy_).equalTo(proxyPrefs);
+ assert(options.logPrefs_).equalTo(logPrefs);
+ assert(options.legacyDriver_).equalTo(true);
+ });
+ });
+
+ describe('toCapabilities', function() {
+ let options;
+
+ before(function() {
+ options = new safari.Options()
+ .setCleanSession(true);
+ });
+
+ it('returns a new capabilities object if one is not provided', function() {
+ let caps = options.toCapabilities();
+ assert(caps).instanceOf(webdriver.Capabilities);
+ assert(caps.get('browserName')).equalTo('safari');
+ assert(caps.get('safari.options')).equalTo(options);
+ });
+
+ it('adds to input capabilities object', function() {
+ let caps = webdriver.Capabilities.safari();
+ assert(options.toCapabilities(caps)).equalTo(caps);
+ assert(caps.get('safari.options')).equalTo(options);
+ });
+
+ it('sets generic driver capabilities', function() {
+ let proxyPrefs = proxy.direct(),
+ loggingPrefs = {};
+
+ options
+ .setLoggingPrefs(loggingPrefs)
+ .setProxy(proxyPrefs)
+ .useLegacyDriver(true);
+
+ let caps = options.toCapabilities();
+ assert(caps.get('proxy')).equalTo(proxyPrefs);
+ assert(caps.get('loggingPrefs')).equalTo(loggingPrefs);
+ assert(caps.get('legacyDriver')).equalTo(true);
+ });
+ });
+});
+
+test.suite(function(env) {
+ describe('safaridriver', function() {
+ let service;
+
+ test.afterEach(function() {
+ if (service) {
+ return service.kill();
+ }
+ });
+
+ test.it('can start safaridriver', function() {
+ service = new safari.ServiceBuilder().build();
+
+ return service.start().then(function(url) {
+ assert(url).matches(/127\.0\.0\.1/);
+ });
+ });
+ });
+}, {browsers: ['safari']});
diff --git a/node_modules/selenium-webdriver/test/session_test.js b/node_modules/selenium-webdriver/test/session_test.js
new file mode 100644
index 000000000..1fb7475b4
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/session_test.js
@@ -0,0 +1,53 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var WebDriver = require('..').WebDriver,
+ assert = require('../testing/assert'),
+ test = require('../lib/test'),
+ Pages = test.Pages;
+
+
+test.suite(function(env) {
+ var browsers = env.browsers;
+
+ var driver;
+ test.before(function() {
+ driver = env.builder().build();
+ });
+
+ test.after(function() {
+ driver.quit();
+ });
+
+ test.it('can connect to an existing session', function() {
+ driver.get(Pages.simpleTestPage);
+ assert(driver.getTitle()).equalTo('Hello WebDriver');
+
+ return driver.getSession().then(session1 => {
+ let driver2 = WebDriver.attachToSession(
+ driver.getExecutor(),
+ session1.getId());
+
+ assert(driver2.getTitle()).equalTo('Hello WebDriver');
+
+ let session2Id = driver2.getSession().then(s => s.getId());
+ assert(session2Id).equalTo(session1.getId());
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/stale_element_test.js b/node_modules/selenium-webdriver/test/stale_element_test.js
new file mode 100644
index 000000000..6ab8de7ec
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/stale_element_test.js
@@ -0,0 +1,61 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var fail = require('assert').fail;
+
+var Browser = require('..').Browser,
+ By = require('..').By,
+ error = require('..').error,
+ until = require('..').until,
+ assert = require('../testing/assert'),
+ test = require('../lib/test'),
+ Pages = test.Pages;
+
+
+test.suite(function(env) {
+ var driver;
+ test.before(function() { driver = env.builder().build(); });
+ test.after(function() { driver.quit(); });
+
+ test.it(
+ 'dynamically removing elements from the DOM trigger a ' +
+ 'StaleElementReferenceError',
+ function() {
+ driver.get(Pages.javascriptPage);
+
+ var toBeDeleted = driver.findElement(By.id('deleted'));
+ assert(toBeDeleted.isDisplayed()).isTrue();
+
+ driver.findElement(By.id('delete')).click();
+ driver.wait(until.stalenessOf(toBeDeleted), 5000);
+ });
+
+ test.it('an element found in a different frame is stale', function() {
+ driver.get(Pages.missedJsReferencePage);
+
+ var frame = driver.findElement(By.css('iframe[name="inner"]'));
+ driver.switchTo().frame(frame);
+
+ var el = driver.findElement(By.id('oneline'));
+ driver.switchTo().defaultContent();
+ el.getText().then(fail, function(e) {
+ assert(e).instanceOf(error.StaleElementReferenceError);
+ });
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/tag_name_test.js b/node_modules/selenium-webdriver/test/tag_name_test.js
new file mode 100644
index 000000000..d5e18a9a2
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/tag_name_test.js
@@ -0,0 +1,34 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var By = require('..').By,
+ assert = require('../testing/assert'),
+ test = require('../lib/test');
+
+
+test.suite(function(env) {
+ var driver;
+ test.after(function() { driver.quit(); });
+
+ test.it('should return lower case tag name', function() {
+ driver = env.builder().build();
+ driver.get(test.Pages.formPage);
+ assert(driver.findElement(By.id('cheese')).getTagName()).equalTo('input');
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/testing/assert_test.js b/node_modules/selenium-webdriver/test/testing/assert_test.js
new file mode 100644
index 000000000..8c8848254
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/testing/assert_test.js
@@ -0,0 +1,374 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+const assert = require('../../testing/assert');
+
+const AssertionError = require('assert').AssertionError;
+const assertTrue = require('assert').ok;
+const assertEqual = require('assert').equal;
+const assertThrows = require('assert').throws;
+const fail = require('assert').fail;
+
+
+describe('assert', function() {
+ describe('atLeast', function() {
+ it('compares subject >= value', function() {
+ assert(1).atLeast(0);
+ assert(1).atLeast(1);
+ assertThrows(() => assert(1).atLeast(2));
+ });
+
+ it('accepts failure message', function() {
+ assertThrows(
+ () => assert(1).atLeast(2, 'hi there!'),
+ (error) => error.message.indexOf('hi there') != -1);
+ });
+
+ it('fails if given a non-numeric subject', function() {
+ assertThrows(() => assert('a').atLeast(1));
+ });
+
+ it('fails if given a non-numeric bound', function() {
+ assertThrows(() => assert(1).atLeast('a'));
+ });
+
+ it('waits for promised subject', function() {
+ return assert(Promise.resolve(123)).atLeast(100);
+ });
+
+ it('waits for promised subject (with failure)', function() {
+ return assert(Promise.resolve(100))
+ .atLeast(123)
+ .then(() => fail('should have failed'), function(e) {
+ assertInstanceOf(AssertionError, e);
+ assertEqual('100 >= 123', e.message);
+ });
+ });
+ });
+
+ describe('atMost', function() {
+ it('compares subject <= value', function() {
+ assertThrows(() => assert(1).atMost(0));
+ assert(1).atMost(1);
+ assert(1).atMost(2);
+ });
+
+ it('accepts failure message', function() {
+ assertThrows(
+ () => assert(1).atMost(0, 'hi there!'),
+ (error) => error.message.indexOf('hi there!') != -1);
+ });
+
+ it('fails if given a non-numeric subject', function() {
+ assertThrows(() => assert(1).atMost('a'));
+ });
+
+ it('fails if given a non-numeric bound', function() {
+ assertThrows(() => assert('a').atMost(1));
+ });
+
+ it('waits for promised subject', function() {
+ return assert(Promise.resolve(100)).atMost(123);
+ });
+
+ it('waits for promised subject (with failure)', function() {
+ return assert(Promise.resolve(123))
+ .atMost(100)
+ .then(() => fail('should have failed'), function(e) {
+ assertInstanceOf(AssertionError, e);
+ assertEqual('123 <= 100', e.message);
+ });
+ });
+ });
+
+ describe('greaterThan', function() {
+ it('compares subject > value', function() {
+ assertThrows(() => assert(1).greaterThan(1));
+ assertThrows(() => assert(1).greaterThan(2));
+ assert(2).greaterThan(1);
+ });
+
+ it('accepts failure message', function() {
+ assertThrows(
+ () => assert(0).greaterThan(1, 'hi there!'),
+ (error) => error.message.indexOf('hi there!') != -1);
+ });
+
+ it('fails if given a non-numeric subject', function() {
+ assertThrows(() => assert('a').atMost(1));
+ });
+
+ it('fails if given a non-numeric bound', function() {
+ assertThrows(() => assert(1).atMost('a'));
+ });
+
+ it('waits for promised subject', function() {
+ return assert(Promise.resolve(123)).greaterThan(100);
+ });
+
+ it('waits for promised subject (with failure)', function() {
+ return assert(Promise.resolve(100))
+ .greaterThan(123)
+ .then(() => fail('should have failed'), function(e) {
+ assertInstanceOf(AssertionError, e);
+ assertEqual('100 > 123', e.message);
+ });
+ });
+ });
+
+ describe('lessThan', function() {
+ it('compares subject < value', function() {
+ assertThrows(() => assert(1).lessThan(0));
+ assertThrows(() => assert(1).lessThan(1));
+ assert(1).lessThan(2);
+ });
+
+ it('accepts failure message', function() {
+ assertThrows(
+ () => assert(1).lessThan(0, 'hi there!'),
+ (error) => error.message.indexOf('hi there!') != -1);
+ });
+
+ it('fails if given a non-numeric subject', function() {
+ assertThrows(() => assert('a').lessThan(1));
+ });
+
+ it('fails if given a non-numeric bound', function() {
+ assertThrows(() => assert(1).lessThan('a'));
+ });
+
+ it('waits for promised subject', function() {
+ return assert(Promise.resolve(100)).lessThan(123);
+ });
+
+ it('waits for promised subject (with failure)', function() {
+ return assert(Promise.resolve(123))
+ .lessThan(100)
+ .then(() => fail('should have failed'), function(e) {
+ assertInstanceOf(AssertionError, e);
+ assertEqual('123 < 100', e.message);
+ });
+ });
+ });
+
+ describe('closeTo', function() {
+ it('accepts values within epislon of target', function() {
+ assert(123).closeTo(123, 0);
+ assert(123).closeTo(124, 1);
+ assert(125).closeTo(124, 1);
+
+ assertThrows(() => assert(123).closeTo(125, .1));
+ assertThrows(() => assert(1./3).closeTo(.8, .01));
+ });
+
+ it('waits for promised values', function() {
+ let d = Promise.defer();
+ setTimeout(() => d.resolve(123), 10);
+ return assert(d.promise).closeTo(124, 1);
+ });
+ });
+
+ describe('instanceOf', function() {
+ it('works with direct instances', function() {
+ assert(Error('foo')).instanceOf(Error);
+ });
+
+ it('works with sub-types', function() {
+ assert(TypeError('foo')).instanceOf(Error);
+ });
+
+ it('parent types are not instances of sub-types', function() {
+ assertThrows(() => assert(Error('foo')).instanceOf(TypeError));
+ });
+ });
+
+ describe('isNull', function() {
+ it('normal case', function() {
+ assert(null).isNull();
+ assertThrows(() => assert(1).isNull());
+ });
+
+ it('handles promised values', function() {
+ let p = new Promise(function(f) {
+ setTimeout(() => f(null), 10);
+ });
+ return assert(p).isNull();
+ });
+
+ it('does not match on undefined', function() {
+ assertThrows(() => assert(void(0)).isNull());
+ })
+ });
+
+ describe('isUndefined', function() {
+ it('normal case', function() {
+ assert(void(0)).isUndefined();
+ assertThrows(() => assert(1).isUndefined());
+ });
+
+ it('handles promised values', function() {
+ let p = new Promise(function(f) {
+ setTimeout(() => f(void(0)), 10);
+ });
+ return assert(p).isUndefined();
+ });
+
+ it('does not match on null', function() {
+ assertThrows(() => assert(null).isUndefined());
+ })
+ });
+
+ describe('contains', function() {
+ it('works with strings', function() {
+ assert('abc').contains('a');
+ assert('abc').contains('ab');
+ assert('abc').contains('abc');
+ assert('abc').contains('bc');
+ assert('abc').contains('c');
+ assertThrows(() => assert('abc').contains('d'));
+ });
+
+ it('works with arrays', function() {
+ assert([1, 2, 3]).contains(1);
+ assert([1, 2, 3]).contains(2);
+ assert([1, 2, 3]).contains(3);
+ assertThrows(() => assert([1, 2]).contains(3));
+ });
+
+ it('works with maps', function() {
+ let m = new Map;
+ m.set(1, 2);
+ assert(m).contains(1);
+ assertThrows(() => assert(m).contains(2));
+ });
+
+ it('works with sets', function() {
+ let s = new Set;
+ s.add(1);
+ assert(s).contains(1);
+ assertThrows(() => assert(s).contains(2));
+ });
+
+ it('requires an array, string, map, or set subject', function() {
+ assertThrows(() => assert(123).contains('a'));
+ });
+ });
+
+ describe('endsWith', function() {
+ it('works', function() {
+ assert('abc').endsWith('abc');
+ assert('abc').endsWith('bc');
+ assert('abc').endsWith('c');
+ assertThrows(() => assert('abc').endsWith('d'));
+ })
+ });
+
+ describe('startsWith', function() {
+ it('works', function() {
+ assert('abc').startsWith('abc');
+ assert('abc').startsWith('ab');
+ assert('abc').startsWith('a');
+ assertThrows(() => assert('abc').startsWith('d'));
+ })
+ });
+
+ describe('matches', function() {
+ it('requires a regex value', function() {
+ assertThrows(() => assert('abc').matches(1234));
+ });
+
+ it('requires a string value', function() {
+ assertThrows(() => assert(1234).matches(/abc/));
+ });
+
+ it('requires a string value (promise case)', function() {
+ return assert(Promise.resolve(1234))
+ .matches(/abc/)
+ .then(fail, function(error) {
+ assertEqual(
+ 'Expected a string matching /abc/, got <1234> (number)',
+ error.message);
+ });
+ });
+
+ it('applies regex', function() {
+ assert('abc').matches(/abc/);
+ assertThrows(() => assert('def').matches(/abc/));
+ });
+ });
+
+ describe('isTrue', function() {
+ it('only accepts booleans', function() {
+ assertThrows(() => assert(123).isTrue());
+ });
+
+ it('accepts true values', function() {
+ assert(true).isTrue();
+ assert(Boolean('abc')).isTrue();
+ return assert(Promise.resolve(true)).isTrue();
+ });
+
+ it('rejects false values', function() {
+ assertThrows(() => assert(false).isTrue());
+ assertThrows(() => assert(Boolean(0)).isTrue());
+ return assert(Promise.resolve(false)).isTrue()
+ .then(fail, function() {/*no-op, ok*/});
+ });
+ });
+
+ describe('isFalse', function() {
+ it('only accepts booleans', function() {
+ assertThrows(() => assert(123).isFalse());
+ })
+
+ it('accepts false values', function() {
+ assert(false).isFalse();
+ assert(Boolean('')).isFalse();
+ return assert(Promise.resolve(false)).isFalse();
+ });
+
+ it('rejects true values', function() {
+ assertThrows(() => assert(true).isFalse());
+ assertThrows(() => assert(Boolean(1)).isFalse());
+ return assert(Promise.resolve(true)).isFalse()
+ .then(fail, function() {/*no-op, ok*/});
+ });
+ });
+
+ describe('isEqualTo', function() {
+ it('is strict equality', function() {
+ assert('abc').isEqualTo('abc');
+ assert('abc').equals('abc');
+ assert('abc').equalTo('abc');
+ assertThrows(() => assert('1').isEqualTo(1));
+ });
+ });
+
+ describe('notEqualTo', function() {
+ it('tests strict equality', function() {
+ assert('1').notEqualTo(1);
+ assert(1).notEqualTo('1');
+ assertThrows(() => assert('abc').notEqualTo('abc'));
+ });
+ });
+
+ function assertInstanceOf(ctor, value) {
+ assertTrue(value instanceof ctor);
+ }
+});
diff --git a/node_modules/selenium-webdriver/test/testing/index_test.js b/node_modules/selenium-webdriver/test/testing/index_test.js
new file mode 100644
index 000000000..31acff238
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/testing/index_test.js
@@ -0,0 +1,178 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var assert = require('assert');
+var promise = require('../..').promise;
+
+var test = require('../../testing');
+
+describe('Mocha Integration', function() {
+
+ describe('beforeEach properly binds "this"', function() {
+ beforeEach(function() { this.x = 1; });
+ test.beforeEach(function() { this.x = 2; });
+ it('', function() { assert.equal(this.x, 2); });
+ });
+
+ describe('afterEach properly binds "this"', function() {
+ it('', function() { this.x = 1; });
+ test.afterEach(function() { this.x = 2; });
+ afterEach(function() { assert.equal(this.x, 2); });
+ });
+
+ describe('it properly binds "this"', function() {
+ beforeEach(function() { this.x = 1; });
+ test.it('', function() { this.x = 2; });
+ afterEach(function() { assert.equal(this.x, 2); });
+ });
+
+ describe('timeout handling', function() {
+ describe('it does not reset the control flow on a non-timeout', function() {
+ var flowReset = false;
+
+ beforeEach(function() {
+ flowReset = false;
+ test.controlFlow().once(promise.ControlFlow.EventType.RESET, onreset);
+ });
+
+ test.it('', function() {
+ this.timeout(100);
+ return promise.delayed(50);
+ });
+
+ afterEach(function() {
+ assert.ok(!flowReset);
+ test.controlFlow().removeListener(
+ promise.ControlFlow.EventType.RESET, onreset);
+ });
+
+ function onreset() {
+ flowReset = true;
+ }
+ });
+
+ describe('it resets the control flow after a timeout' ,function() {
+ var timeoutErr, flowReset;
+
+ beforeEach(function() {
+ flowReset = false;
+ test.controlFlow().once(promise.ControlFlow.EventType.RESET, onreset);
+ });
+
+ test.it('', function() {
+ var callback = this.runnable().callback;
+ var test = this;
+ this.runnable().callback = function(err) {
+ timeoutErr = err;
+ // Reset our timeout to 0 so Mocha does not fail the test.
+ test.timeout(0);
+ // When we invoke the real callback, do not pass along the error so
+ // Mocha does not fail the test.
+ return callback.call(this);
+ };
+
+ test.timeout(50);
+ return promise.defer().promise;
+ });
+
+ afterEach(function() {
+ return Promise.resolve().then(function() {
+ test.controlFlow().removeListener(
+ promise.ControlFlow.EventType.RESET, onreset);
+ assert.ok(flowReset, 'control flow was not reset after a timeout');
+ });
+ });
+
+ function onreset() {
+ flowReset = true;
+ }
+ });
+ });
+});
+
+describe('Mocha async "done" support', function() {
+ this.timeout(2*1000);
+
+ var waited = false;
+ var DELAY = 100; // ms enough to notice
+
+ // Each test asynchronously sets waited to true, so clear/check waited
+ // before/after:
+ beforeEach(function() {
+ waited = false;
+ });
+
+ afterEach(function() {
+ assert.strictEqual(waited, true);
+ });
+
+ // --- First, vanilla mocha "it" should support the "done" callback correctly.
+
+ // This 'it' should block until 'done' is invoked
+ it('vanilla delayed', function(done) {
+ setTimeout(function delayedVanillaTimeout() {
+ waited = true;
+ done();
+ }, DELAY);
+ });
+
+ // --- Now with the webdriver wrappers for 'it' should support the "done" callback:
+
+ test.it('delayed', function(done) {
+ assert(done);
+ assert.strictEqual(typeof done, 'function');
+ setTimeout(function delayedTimeoutCallback() {
+ waited = true;
+ done();
+ }, DELAY);
+ });
+
+ // --- And test that the webdriver wrapper for 'it' works with a returned promise, too:
+
+ test.it('delayed by promise', function() {
+ var defer = promise.defer();
+ setTimeout(function delayedPromiseCallback() {
+ waited = true;
+ defer.fulfill('ignored');
+ });
+ return defer.promise;
+ });
+
+});
+
+describe('ControlFlow and "done" work together', function() {
+ var flow, order;
+ before(function() {
+ order = [];
+ flow = test.controlFlow();
+ flow.execute(function() { order.push(1); });
+ });
+
+ test.it('control flow updates and async done', function(done) {
+ flow.execute(function() { order.push(2); });
+ flow.execute(function() { order.push(3); }).then(function() {
+ order.push(4);
+ });
+ done();
+ })
+
+ after(function() {
+ assert.deepEqual([1, 2, 3, 4], order);
+ })
+});
diff --git a/node_modules/selenium-webdriver/test/upload_test.js b/node_modules/selenium-webdriver/test/upload_test.js
new file mode 100644
index 000000000..3329f7ca7
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/upload_test.js
@@ -0,0 +1,86 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var fs = require('fs');
+
+var Browser = require('..').Browser,
+ By = require('..').By,
+ until = require('..').until,
+ io = require('../io'),
+ remote = require('../remote'),
+ assert = require('../testing/assert'),
+ test = require('../lib/test'),
+ Pages = test.Pages;
+
+test.suite(function(env) {
+ var LOREM_IPSUM_TEXT = 'lorem ipsum dolor sit amet';
+ var FILE_HTML = '<!DOCTYPE html><div>' + LOREM_IPSUM_TEXT + '</div>';
+
+ var fp;
+ test.before(function() {
+ return fp = io.tmpFile().then(function(fp) {
+ fs.writeFileSync(fp, FILE_HTML);
+ return fp;
+ });
+ })
+
+ var driver;
+ test.before(function() {
+ driver = env.builder().build();
+ });
+
+ test.after(function() {
+ if (driver) {
+ driver.quit();
+ }
+ });
+
+ test.ignore(env.browsers(
+ Browser.IPAD,
+ Browser.IPHONE,
+ // Uploads broken in PhantomJS 2.0.
+ // See https://github.com/ariya/phantomjs/issues/12506
+ Browser.PHANTOM_JS,
+ Browser.SAFARI)).
+ it('can upload files', function() {
+ driver.setFileDetector(new remote.FileDetector);
+
+ driver.get(Pages.uploadPage);
+
+ var fp = driver.call(function() {
+ return io.tmpFile().then(function(fp) {
+ fs.writeFileSync(fp, FILE_HTML);
+ return fp;
+ });
+ });
+
+ driver.findElement(By.id('upload')).sendKeys(fp);
+ driver.findElement(By.id('go')).click();
+
+ // Uploading files across a network may take a while, even if they're small.
+ var label = driver.findElement(By.id('upload_label'));
+ driver.wait(until.elementIsNotVisible(label),
+ 10 * 1000, 'File took longer than 10 seconds to upload!');
+
+ var frame = driver.findElement(By.id('upload_target'));
+ driver.switchTo().frame(frame);
+ assert(driver.findElement(By.css('body')).getText())
+ .equalTo(LOREM_IPSUM_TEXT);
+ });
+});
diff --git a/node_modules/selenium-webdriver/test/window_test.js b/node_modules/selenium-webdriver/test/window_test.js
new file mode 100644
index 000000000..6213d19ac
--- /dev/null
+++ b/node_modules/selenium-webdriver/test/window_test.js
@@ -0,0 +1,175 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+'use strict';
+
+var Browser = require('..').Browser,
+ By = require('..').By,
+ assert = require('../testing/assert'),
+ test = require('../lib/test');
+
+
+test.suite(function(env) {
+ var driver;
+
+ test.before(function() { driver = env.builder().build(); });
+ test.after(function() { driver.quit(); });
+
+ test.beforeEach(function() {
+ driver.switchTo().defaultContent();
+ });
+
+ test.it('can set size of the current window', function() {
+ driver.get(test.Pages.echoPage);
+ changeSizeBy(-20, -20);
+ });
+
+ test.it('can set size of the current window from frame', function() {
+ driver.get(test.Pages.framesetPage);
+
+ var frame = driver.findElement({css: 'frame[name="fourth"]'});
+ driver.switchTo().frame(frame);
+ changeSizeBy(-20, -20);
+ });
+
+ test.it('can set size of the current window from iframe', function() {
+ driver.get(test.Pages.iframePage);
+
+ var frame = driver.findElement({css: 'iframe[name="iframe1-name"]'});
+ driver.switchTo().frame(frame);
+ changeSizeBy(-20, -20);
+ });
+
+ test.it('can switch to a new window', function() {
+ driver.get(test.Pages.xhtmlTestPage);
+
+ driver.getWindowHandle().then(function(handle) {
+ driver.getAllWindowHandles().then(function(originalHandles) {
+ driver.findElement(By.linkText("Open new window")).click();
+
+ driver.wait(forNewWindowToBeOpened(originalHandles), 2000);
+
+ assert(driver.getTitle()).equalTo("XHTML Test Page");
+
+ getNewWindowHandle(originalHandles).then(function(newHandle) {
+ driver.switchTo().window(newHandle);
+
+ assert(driver.getTitle()).equalTo("We Arrive Here")
+ });
+ });
+ });
+ });
+
+ // See https://github.com/mozilla/geckodriver/issues/113
+ test.ignore(env.browsers(Browser.FIREFOX)).
+ it('can set the window position of the current window', function() {
+ driver.manage().window().getPosition().then(function(position) {
+ driver.manage().window().setSize(640, 480);
+ driver.manage().window().setPosition(position.x + 10, position.y + 10);
+
+ // For phantomjs, setPosition is a no-op and the "window" stays at (0, 0)
+ if (env.currentBrowser() === Browser.PHANTOM_JS) {
+ driver.manage().window().getPosition().then(function(position) {
+ assert(position.x).equalTo(0);
+ assert(position.y).equalTo(0);
+ });
+ } else {
+ var dx = position.x + 10;
+ var dy = position.y + 10;
+ // On OSX, Safari position's the window relative to below the menubar
+ // at the top of the screen, which is 23 pixels tall.
+ if (env.currentBrowser() === Browser.SAFARI &&
+ process.platform === 'darwin') {
+ dy += 23;
+ }
+ }
+ });
+ });
+
+ // See https://github.com/mozilla/geckodriver/issues/113
+ test.ignore(env.browsers(Browser.FIREFOX)).
+ it('can set the window position from a frame', function() {
+ driver.get(test.Pages.iframePage);
+ driver.switchTo().frame('iframe1-name');
+ driver.manage().window().getPosition().then(function(position) {
+ driver.manage().window().setSize(640, 480);
+ driver.manage().window().setPosition(position.x + 10, position.y + 10);
+
+ // For phantomjs, setPosition is a no-op and the "window" stays at (0, 0)
+ if (env.currentBrowser() === Browser.PHANTOM_JS) {
+ driver.manage().window().getPosition().then(function(position) {
+ assert(position.x).equalTo(0);
+ assert(position.y).equalTo(0);
+ });
+ } else {
+ var dx = position.x + 10;
+ var dy = position.y + 10;
+ // On OSX, Safari position's the window relative to below the menubar
+ // at the top of the screen, which is 23 pixels tall.
+ if (env.currentBrowser() === Browser.SAFARI &&
+ process.platform === 'darwin') {
+ dy += 23;
+ }
+ driver.wait(forPositionToBe(dx, dy), 1000);
+ }
+ });
+ });
+
+ function changeSizeBy(dx, dy) {
+ driver.manage().window().getSize().then(function(size) {
+ driver.manage().window().setSize(size.width + dx, size.height + dy);
+ driver.wait(forSizeToBe(size.width + dx, size.height + dy), 1000);
+ });
+ }
+
+ function forSizeToBe(w, h) {
+ return function() {
+ return driver.manage().window().getSize().then(function(size) {
+ return size.width === w && size.height === h;
+ });
+ };
+ }
+
+ function forPositionToBe(x, y) {
+ return function() {
+ return driver.manage().window().getPosition().then(function(position) {
+ return position.x === x &&
+ // On OSX, the window height may be bumped down 22px for the top
+ // status bar.
+ // On Linux, Opera's window position will be off by 28px.
+ (position.y >= y && position.y <= (y + 28));
+ });
+ };
+ }
+
+ function forNewWindowToBeOpened(originalHandles) {
+ return function() {
+ return driver.getAllWindowHandles().then(function(currentHandles) {
+ return currentHandles.length > originalHandles.length;
+ });
+ };
+ }
+
+ function getNewWindowHandle(originalHandles) {
+ // Note: this assumes there's just one new window.
+ return driver.getAllWindowHandles().then(function(currentHandles) {
+ return currentHandles.filter(function(i) {
+ return originalHandles.indexOf(i) < 0;
+ })[0];
+ });
+ }
+});