From 47df8707848d00c90589a51ab512f9a250f7b863 Mon Sep 17 00:00:00 2001 From: Sammy Jelin Date: Fri, 1 Jul 2016 13:08:22 -0700 Subject: [PATCH] feat(mobile): add extended wd commands for appium Had to make some minor changes to the website to handle longer inheritance chains --- gulpfile.js | 7 ++-- lib/browser.ts | 32 ++++++++++----- lib/runner.ts | 9 +++++ lib/selenium-webdriver/webdriver.js | 8 ++-- lib/webdriver-js-extender/index.js | 63 +++++++++++++++++++++++++++++ package.json | 3 +- website/docgen/dgeni-config.js | 3 +- website/js/api-controller.js | 20 ++++----- website/partials/api.html | 11 ++++- 9 files changed, 126 insertions(+), 30 deletions(-) create mode 100644 lib/webdriver-js-extender/index.js diff --git a/gulpfile.js b/gulpfile.js index 55054cf41..ab8a763b4 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -70,10 +70,9 @@ 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']); + runSpawn(done, 'node', ['node_modules/jshint/bin/jshint', '-c', '.jshintrc', 'lib', 'spec', + 'scripts', '--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 7c4bc7a58..b6abba05b 100644 --- a/lib/browser.ts +++ b/lib/browser.ts @@ -1,5 +1,6 @@ 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'; @@ -34,7 +35,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, @@ -63,6 +64,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 @@ -115,7 +121,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 @@ -123,7 +129,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} */ @@ -138,9 +144,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; /** * Helper function for finding elements. @@ -279,19 +285,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; this.element = buildElementHelper(this); this.$ = build$(this.element, By); this.$$ = build$$(this.element, By); diff --git a/lib/runner.ts b/lib/runner.ts index 38da964da..d036e1690 100644 --- a/lib/runner.ts +++ b/lib/runner.ts @@ -2,6 +2,8 @@ import {EventEmitter} from 'events'; import * as q from 'q'; import {promise as wdpromise, Session} from 'selenium-webdriver'; import * as util from 'util'; +// TODO(sjelin): patch() will no longer be needed with `selenium-webdriver` 3.x +import {patch} from 'webdriver-js-extender'; import {ProtractorBrowser} from './browser'; import {Config} from './config'; @@ -98,6 +100,13 @@ export class Runner extends EventEmitter { * 5) try to find the seleniumServerJar in protractor/selenium */ loadDriverProvider_(config: Config) { + // `webdriver-js-extender` needs to overwrite `DeferredExecutor` and some + // associated functions so that it can define custom commands. In version + // 3.x of `selenium-webdriver`, this will no longer be necessary and will + // have to be removed. + patch( + require('selenium-webdriver/lib/command'), require('selenium-webdriver/executors'), + require('selenium-webdriver/http')); this.config_ = config; if (this.config_.directConnect) { this.driverprovider_ = new Direct(this.config_); diff --git a/lib/selenium-webdriver/webdriver.js b/lib/selenium-webdriver/webdriver.js index 8fdbf1b48..f5609b868 100644 --- a/lib/selenium-webdriver/webdriver.js +++ b/lib/selenium-webdriver/webdriver.js @@ -7,11 +7,11 @@ goog.provide('webdriver'); -// ////////////////////////////////////////////////////////////////////////////// +// ///////////////////////////////////////////////////////////////////////////// // // // // 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 @@ -318,11 +318,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..f1196abe4 --- /dev/null +++ b/lib/webdriver-js-extender/index.js @@ -0,0 +1,63 @@ +// 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. + * + * 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 cdc22fab7..b053c585a 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "saucelabs": "~1.3.0", "selenium-webdriver": "2.53.3", "source-map-support": "~0.4.0", - "webdriver-manager": "^10.3.0" + "webdriver-manager": "^10.3.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 245634718..3da4825f0 100644 --- a/website/docgen/dgeni-config.js +++ b/website/docgen/dgeni-config.js @@ -76,7 +76,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}})

+ +
+