From 9465b9f1e667c9590e05d9ddac16fe5143aa93af Mon Sep 17 00:00:00 2001 From: Sammy Jelin Date: Wed, 21 Dec 2016 19:25:43 -0800 Subject: [PATCH] feat(mobile): add extended wd commands for appium (#3860) Also had to make some minor changes to the website to handle longer inheritance chains Closes https://github.com/angular/protractor/issues/1940 --- gulpfile.js | 4 +- lib/browser.ts | 32 +++++++++---- lib/selenium-webdriver/webdriver.js | 6 +-- lib/webdriver-js-extender/index.js | 69 +++++++++++++++++++++++++++++ package.json | 3 +- website/docgen/dgeni-config.js | 3 +- website/js/api-controller.js | 20 +++++---- website/partials/api.html | 11 ++++- 8 files changed, 121 insertions(+), 27 deletions(-) create mode 100644 lib/webdriver-js-extender/index.js diff --git a/gulpfile.js b/gulpfile.js index 55054cf41..b678b8daa 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -72,8 +72,8 @@ gulp.task('webdriver:update', function(done) { gulp.task('jshint', function(done) { runSpawn(done, 'node', ['node_modules/jshint/bin/jshint', '-c', '.jshintrc', 'lib', 'spec', 'scripts', - '--exclude=lib/selenium-webdriver/**/*.js,spec/dependencyTest/*.js,' + - 'spec/install/**/*.js']); + '--exclude=lib/selenium-webdriver/**/*.js,lib/webdriver-js-extender/**/*.js,' + + 'spec/dependencyTest/*.js,spec/install/**/*.js']); }); gulp.task('format:enforce', function() { diff --git a/lib/browser.ts b/lib/browser.ts index d60f85dee..6c646ecb4 100644 --- a/lib/browser.ts +++ b/lib/browser.ts @@ -1,6 +1,7 @@ import {BPClient} from 'blocking-proxy'; import {ActionSequence, By, Capabilities, Command as WdCommand, FileDetector, ICommandName, Options, promise as wdpromise, Session, TargetLocator, TouchSequence, until, WebDriver, WebElement} from 'selenium-webdriver'; import * as url from 'url'; +import {extend as extendWD, ExtendedWebDriver} from 'webdriver-js-extender'; import {DebugHelper} from './debugger'; import {build$, build$$, ElementArrayFinder, ElementFinder} from './element'; @@ -35,7 +36,7 @@ for (let foo in require('selenium-webdriver')) { // Explicitly define webdriver.WebDriver // TODO: extend WebDriver from selenium-webdriver typings -export class Webdriver { +export class AbstractWebDriver { actions: () => ActionSequence; call: (fn: (...var_args: any[]) => any, opt_scope?: any, @@ -64,6 +65,11 @@ export class Webdriver { opt_message?: string) => wdpromise.Promise; } +export class AbstractExtendedWebDriver extends AbstractWebDriver { + getNetworkConnection: () => wdpromise.Promise; + setNetworkConnection: (type: number) => wdpromise.Promise; +} + /** * Mix a function from one object onto another. The function will still be * called in the context of the original object. Any arguments of type @@ -116,7 +122,7 @@ function buildElementHelper(browser: ProtractorBrowser): ElementHelper { /** * @alias browser * @constructor - * @extends {webdriver.WebDriver} + * @extends {webdriver_extensions.ExtendedWebDriver} * @param {webdriver.WebDriver} webdriver * @param {string=} opt_baseUrl A base URL to run get requests against. * @param {string=} opt_rootElement Selector element that has an ng-app in @@ -124,7 +130,7 @@ function buildElementHelper(browser: ProtractorBrowser): ElementHelper { * @param {boolean=} opt_untrackOutstandingTimeouts Whether Protractor should * stop tracking outstanding $timeouts. */ -export class ProtractorBrowser extends Webdriver { +export class ProtractorBrowser extends AbstractExtendedWebDriver { /** * @type {ProtractorBy} */ @@ -139,9 +145,9 @@ export class ProtractorBrowser extends Webdriver { * The wrapped webdriver instance. Use this to interact with pages that do * not contain Angular (such as a log-in screen). * - * @type {webdriver.WebDriver} + * @type {webdriver_extensions.ExtendedWebDriver} */ - driver: WebDriver; + driver: ExtendedWebDriver; /** * The client used to control the BlockingProxy. If unset, BlockingProxy is @@ -303,19 +309,27 @@ export class ProtractorBrowser extends Webdriver { // wait for Angular to sync up before performing the action. This does not // include functions which are overridden by protractor below. let methodsToSync = ['getCurrentUrl', 'getPageSource', 'getTitle']; + let extendWDInstance: ExtendedWebDriver; + try { + extendWDInstance = extendWD(webdriverInstance); + } catch (e) { + // Probably not a driver that can be extended (e.g. gotten using + // `directConnect: true` in the config) + extendWDInstance = webdriverInstance as ExtendedWebDriver; + } // Mix all other driver functionality into Protractor. Object.getOwnPropertyNames(WebDriver.prototype).forEach(method => { - if (!this[method] && typeof(webdriverInstance as any)[method] === 'function') { + if (!this[method] && typeof(extendWDInstance as any)[method] === 'function') { if (methodsToSync.indexOf(method) !== -1) { - ptorMixin(this, webdriverInstance, method, this.waitForAngular.bind(this)); + ptorMixin(this, extendWDInstance, method, this.waitForAngular.bind(this)); } else { - ptorMixin(this, webdriverInstance, method); + ptorMixin(this, extendWDInstance, method); } } }); - this.driver = webdriverInstance; + this.driver = extendWDInstance; if (opt_blockingProxyUrl) { logger.info('Starting BP client for ' + opt_blockingProxyUrl); this.bpClient = new BPClient(opt_blockingProxyUrl); diff --git a/lib/selenium-webdriver/webdriver.js b/lib/selenium-webdriver/webdriver.js index 5954791c2..3c46a3dac 100644 --- a/lib/selenium-webdriver/webdriver.js +++ b/lib/selenium-webdriver/webdriver.js @@ -25,7 +25,7 @@ webdriver.TouchSequence = function() {}; // // // // webdriver.WebDriver // // -// ////////////////////////////////////////////////////////////////////////////// +// ///////////////////////////////////////////////////////////////////////////// /** * Protractor's `browser` object is a wrapper for `selenium-webdriver` WebDriver. * It inherits call of WebDriver's methods, but only the methods most useful to @@ -352,11 +352,11 @@ webdriver.WebDriver.prototype.takeScreenshot = function() {}; */ webdriver.WebDriver.prototype.switchTo = function() {} -// ////////////////////////////////////////////////////////////////////////////// +// ///////////////////////////////////////////////////////////////////////////// // // // // webdriver.WebElement // // -// ////////////////////////////////////////////////////////////////////////////// +// ///////////////////////////////////////////////////////////////////////////// // // // diff --git a/lib/webdriver-js-extender/index.js b/lib/webdriver-js-extender/index.js new file mode 100644 index 000000000..9e836ec54 --- /dev/null +++ b/lib/webdriver-js-extender/index.js @@ -0,0 +1,69 @@ +// Used to provide better protractor documentation for methods given by +// `webdriver-js-extender`. + +/** + * @fileoverview Extra methods provided by webdriver-js-extender. + */ + +goog.provide('webdriver_extensions'); + +// ///////////////////////////////////////////////////////////////////////////// +// // +// // webdriver_extensions.ExtendedWebDriver +// // +// ///////////////////////////////////////////////////////////////////////////// +/** + * Protractor's `browser` object is a wrapper for an instance of + * `ExtendedWebDriver`, provided by `webdriver-js-extender`, which itself is + * just an instance of `selenium-webdriver`'s WebDriver with some extra methods + * added in. The `browser` object inherits all of WebDriver's and + * ExtendedWebDriver's methods, but only the methods most useful to Protractor + * users are documented here. + * + * ***If you are not using an appium server, `browser` may sometimes inherit + * directly from a normal `WebDriver` instance, and thus not inherit any of + * the extra methods defined by `webdriver-js-extender`. Even when `browser` + * does inherit from `ExtendedWebDriver`, these extra methods will only work if + * your server implements the Appium API.*** + * + * More information about `webdriver-js-extender` can be found on the [GitHub + * repo](https://github.com/angular/webdriver-js-extender). + * @alias ExtendedWebDriver + * @constructor + * @extends {webdriver.WebDriver} + */ +webdriver_extensions.ExtendedWebDriver = function() {}; + +/** + * Schedules a command to retrieve the network connection type. + * + * Network connection types are a bitmask with: + * 1 -> airplane mode + * 2 -> wifi + * 4 -> data + * + * @example + * expect(browser.getNetworkConnection()).toBe(6); //Expect wifi and data on + * + * @returns {!webdriver.promise.Promise.} A promise that will be + * resolved with the current network connection type. + */ +webdriver_extensions.ExtendedWebDriver.prototype.getNetworkConnection = function() {}; + +/** + * Schedules a command to set the network connection type. + * + * Network connection types are a bitmask with: + * 1 -> airplane mode + * 2 -> wifi + * 4 -> data + * + * @example + * browser.setNetworkConnection(1); //Turn on airplane mode + * expect(browser.getNetworkConnection()).toBe(1); + * + * @param {number} type The type to set the network connection to. + * @returns {!webdriver.promise.Promise.} A promise that will be + * resolved when the network connection type is set. + */ +webdriver_extensions.ExtendedWebDriver.prototype.setNetworkConnection = function(type) {}; diff --git a/package.json b/package.json index a55494c72..7acb2c16b 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "saucelabs": "~1.3.0", "selenium-webdriver": "3.0.1", "source-map-support": "~0.4.0", - "webdriver-manager": "^11.1.0" + "webdriver-manager": "^11.1.0", + "webdriver-js-extender": "^0.2.2" }, "devDependencies": { "@types/chalk": "^0.4.28", diff --git a/website/docgen/dgeni-config.js b/website/docgen/dgeni-config.js index 7e5d6d37b..3fefd807f 100644 --- a/website/docgen/dgeni-config.js +++ b/website/docgen/dgeni-config.js @@ -77,7 +77,8 @@ myPackage.config(function(readFilesProcessor, templateFinder, writeFilesProcesso {include: 'built/locators.js'}, {include: 'built/expectedConditions.js'}, {include: 'lib/selenium-webdriver/locators.js'}, - {include: 'lib/selenium-webdriver/webdriver.js'} + {include: 'lib/selenium-webdriver/webdriver.js'}, + {include: 'lib/webdriver-js-extender/index.js'} ]; // Add a folder to search for our own templates to use when rendering docs diff --git a/website/js/api-controller.js b/website/js/api-controller.js index c733135f4..b46f33a89 100644 --- a/website/js/api-controller.js +++ b/website/js/api-controller.js @@ -250,16 +250,18 @@ // Remove braces from {type}. var parentName = item.extends.replace(/[{}]/g, ''); var nameExpr = new RegExp(parentName + '\\.prototype'); + var parent = self.itemsByName[parentName]; - // Find all the parent functions. - item.base = { - name: parentName, - items: _.filter(list, function(item) { - return item.name && item.name.match(nameExpr); - }) - }; - if (self.itemsByName[parentName]) { - self.itemsByName[parentName].extension = true; + if (parent) { + item.base = parent; + parent.extension = true; + } else { + item.base = { + name: parentName, + children: _.filter(list, function(item) { + return item.name && item.name.match(nameExpr); + }), + }; } }); }; diff --git a/website/partials/api.html b/website/partials/api.html index a5824cd6a..e55b7c1ab 100644 --- a/website/partials/api.html +++ b/website/partials/api.html @@ -173,9 +173,16 @@

Functions

-

Extends {{currentItem.base.name}}

+

Extends {{currentItem.base.title}}

-
+
+ + +
+

Extends {{currentItem.base.base.title}} (via {{currentItem.base.title}})

+ +
+