Skip to content

Commit

Permalink
chore(types): Inherit from webdriver.WebDriver types (#4016)
Browse files Browse the repository at this point in the history
I decided to address this comment:

#4000 (comment)

While doing do I decided to take on this TODO:

https://github.com/angular/protractor/blob/ccf02ab5f1070f0d7124318dc0099252f3c747e2/lib/browser.ts#L38

One possible issue here is that `ProtractorBrowser` only copies over methods, so it doesn't actually
implement `WebDriver`'s interface.  This isn't a problem right now, since `WebDriver`'s interface
only has functions on it.  But it could be a problem in the future.
  • Loading branch information
sjelin committed Jan 30, 2017
1 parent a20c7a7 commit 46a1e0c
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 116 deletions.
95 changes: 11 additions & 84 deletions lib/browser.ts
@@ -1,5 +1,5 @@
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 {ActionSequence, By, Capabilities, Command as WdCommand, FileDetector, ICommandName, Options, promise as wdpromise, Session, TargetLocator, TouchSequence, until, WebDriver, WebElement, WebElementPromise} from 'selenium-webdriver';
import * as url from 'url';
import {extend as extendWD, ExtendedWebDriver} from 'webdriver-js-extender';

Expand Down Expand Up @@ -34,87 +34,14 @@ for (let foo in require('selenium-webdriver')) {
exports[foo] = require('selenium-webdriver')[foo];
}

// Explicitly define webdriver.WebDriver
// TODO: extend WebDriver from selenium-webdriver typings
export class AbstractWebDriver {
actions: () => ActionSequence;
call:
(fn: (...var_args: any[]) => any, opt_scope?: any,
...var_args: any[]) => wdpromise.Promise<any>;
close: () => void;
controlFlow: () => wdpromise.ControlFlow;
executeScript: (script: string|Function, ...var_args: any[]) => wdpromise.Promise<any>;
executeAsyncScript: (script: string|Function, ...var_args: any[]) => wdpromise.Promise<any>;
getCapabilities: () => wdpromise.Promise<Capabilities>;
getCurrentUrl: () => wdpromise.Promise<string>;
getPageSource: () => wdpromise.Promise<string>;
getSession: () => wdpromise.Promise<Session>;
getTitle: () => wdpromise.Promise<string>;
getWindowHandle: () => wdpromise.Promise<string>;
getAllWindowHandles: () => wdpromise.Promise<string[]>;
manage: () => Options;
quit: () => void;
schedule: (command: WdCommand, description: string) => wdpromise.Promise<any>;
setFileDetector: (detector: FileDetector) => void;
sleep: (ms: number) => wdpromise.Promise<void>;
switchTo: () => TargetLocator;
takeScreenshot: () => wdpromise.Promise<any>;
touchActions: () => TouchSequence;
wait:
(condition: wdpromise.Promise<any>|until.Condition<any>|Function, opt_timeout?: number,
opt_message?: string) => wdpromise.Promise<any>;
}

export class AbstractExtendedWebDriver extends AbstractWebDriver {
getNetworkConnection: () => wdpromise.Promise<0|1|2|3|4|5|6|7>;
setNetworkConnection:
(typeOrAirplaneMode: 0|1|2|3|4|5|6|7|boolean, wifi?: boolean,
data?: boolean) => wdpromise.Promise<void>;
toggleAirplaneMode: () => wdpromise.Promise<void>;
toggleWiFi: () => wdpromise.Promise<void>;
toggleData: () => wdpromise.Promise<void>;
toggleLocationServices: () => wdpromise.Promise<void>;
getGeolocation: () => wdpromise.Promise<{latitude: number, longitude: number, altitude: number}>;
setGeolocation:
(latitude?: number, longitude?: number, altitude?: number) => wdpromise.Promise<void>;
getCurrentDeviceActivity: () => wdpromise.Promise<string>;
startDeviceActivity:
(appPackage: string, appActivity: string, appWaitPackage?: string,
appWaitActivity?: string) => wdpromise.Promise<void>;
getAppiumSettings: () => wdpromise.Promise<{[name: string]: any}>;
setAppiumSettings: (settings: {[name: string]: any}) => wdpromise.Promise<void>;
getCurrentContext: () => wdpromise.Promise<string>;
selectContext: (name: string) => wdpromise.Promise<void>;
listContexts: () => wdpromise.Promise<string[]>;
getScreenOrientation: () => wdpromise.Promise<'LANDSCAPE'|'PORTRAIT'>;
setScreenOrientation: (orientation: string) => wdpromise.Promise<void>;
isDeviceLocked: () => wdpromise.Promise<boolean>;
lockDevice: (delay?: number) => wdpromise.Promise<void>;
unlockDevice: () => wdpromise.Promise<void>;
installApp: (appPath: string) => wdpromise.Promise<void>;
isAppInstalled: (bundleId: string) => wdpromise.Promise<boolean>;
removeApp: (appId: string) => wdpromise.Promise<void>;
pullFileFromDevice: (path: string) => wdpromise.Promise<string>;
pullFolderFromDevice: (path: string) => wdpromise.Promise<any>;
pushFileToDevice: (path: string, base64Data: string) => wdpromise.Promise<void>;
uploadFile: (base64Data: string) => wdpromise.Promise<void>;
switchToParentFrame: () => wdpromise.Promise<void>;
fullscreen: () => wdpromise.Promise<void>;
sendAppToBackground: (delay?: number) => wdpromise.Promise<void>;
closeApp: () => wdpromise.Promise<void>;
getAppStrings: (language?: string) => wdpromise.Promise<string[]>;
launchSession: () => wdpromise.Promise<void>;
resetApp: () => wdpromise.Promise<void>;
hideSoftKeyboard:
(strategy?: 'default'|'tapOutside'|'tapOut'|'swipeDown'|'pressKey'|'press',
key?: string) => wdpromise.Promise<void>;
getDeviceTime: () => wdpromise.Promise<string>;
openDeviceNotifications: () => wdpromise.Promise<void>;
rotationGesture:
(x?: number, y?: number, duration?: number, rotation?: number,
touchCount?: 1|2|3|4|5) => wdpromise.Promise<void>;
shakeDevice: () => wdpromise.Promise<void>;
}
// Explicitly define types for webdriver.WebDriver and ExtendedWebDriver.
// We do this because we use composition over inheritance to implement polymorphism, and therefore
// we don't want to inherit WebDriver's constructor.
export class AbstractWebDriver {}
export interface AbstractWebDriver extends WebDriver {}
export class AbstractExtendedWebDriver extends AbstractWebDriver {}
export interface AbstractExtendedWebDriver extends ExtendedWebDriver {}

/**
* Mix a function from one object onto another. The function will still be
Expand Down Expand Up @@ -808,10 +735,10 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
/**
* Waits for Angular to finish rendering before searching for elements.
* @see webdriver.WebDriver.findElement
* @returns {!webdriver.promise.Promise} A promise that will be resolved to
* @returns {!webdriver.WebElementPromise} A promise that will be resolved to
* the located {@link webdriver.WebElement}.
*/
findElement(locator: Locator): WebElement {
findElement(locator: Locator): WebElementPromise {
return this.element(locator).getWebElement();
}

Expand All @@ -821,7 +748,7 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
* @returns {!webdriver.promise.Promise} A promise that will be resolved to an
* array of the located {@link webdriver.WebElement}s.
*/
findElements(locator: Locator): wdpromise.Promise<any> {
findElements(locator: Locator): wdpromise.Promise<WebElement[]> {
return this.element.all(locator).getWebElements();
}

Expand Down
8 changes: 4 additions & 4 deletions lib/element.ts
Expand Up @@ -2,7 +2,7 @@ import {By, error as wderror, ILocation, ISize, promise as wdpromise, WebDriver,

import {ElementHelper, ProtractorBrowser} from './browser';
import {IError} from './exitCodes';
import {Locator} from './locators';
import {isProtractorLocator, Locator} from './locators';
import {Logger} from './logger';
import {falseIfMissing} from './util';

Expand Down Expand Up @@ -160,7 +160,7 @@ export class ElementArrayFinder extends WebdriverWebElement {
// This is the first time we are looking for an element
return ptor.waitForAngular('Locator: ' + locator)
.then((): wdpromise.Promise<WebElement[]> => {
if (locator.findElementsOverride) {
if (isProtractorLocator(locator)) {
return locator.findElementsOverride(ptor.driver, null, ptor.rootEl);
} else {
return ptor.driver.findElements(locator);
Expand All @@ -171,7 +171,7 @@ export class ElementArrayFinder extends WebdriverWebElement {
// For each parent web element, find their children and construct
// a list of Promise<List<child_web_element>>
let childrenPromiseList = parentWebElements.map((parentWebElement: WebElement) => {
return locator.findElementsOverride ?
return isProtractorLocator(locator) ?
locator.findElementsOverride(ptor.driver, parentWebElement, ptor.rootEl) :
parentWebElement.findElements(locator);
});
Expand Down Expand Up @@ -921,7 +921,7 @@ export class ElementFinder extends WebdriverWebElement {
* browser.driver.findElement(by.css('.parent'));
* browser.findElement(by.css('.parent'));
*
* @returns {webdriver.WebElement}
* @returns {webdriver.WebElementPromise}
*/
getWebElement(): WebElementPromise {
let id = this.elementArrayFinder_.getWebElements().then((parentWebElements: WebElement[]) => {
Expand Down
63 changes: 35 additions & 28 deletions lib/locators.ts
@@ -1,9 +1,10 @@
import {By, promise as wdpromise, WebDriver, WebElement} from 'selenium-webdriver';
import {By, ByHash, promise as wdpromise, WebDriver, WebElement} from 'selenium-webdriver';

let clientSideScripts = require('./clientsidescripts');


// Explicitly define webdriver.By.
// We do this because we want to inherit the static methods of webdriver.By, as opposed to
// inheriting from the webdriver.By class itself, which is actually analogous to ProtractorLocator.
export class WebdriverBy {
className: (className: string) => By = By.className;
css: (css: string) => By = By.css;
Expand All @@ -15,15 +16,21 @@ export class WebdriverBy {
tagName: (tagName: string) => By = By.tagName;
xpath: (xpath: string) => By = By.xpath;
}
export type WebDriverLocator = By | ByHash | Function;

// Protractor locator strategy
export interface Locator {
findElementsOverride?:
export interface ProtractorLocator {
findElementsOverride:
(driver: WebDriver, using: WebElement,
rootSelector: string) => wdpromise.Promise<WebElement[]>;
row?: (index: number) => Locator;
column?: (index: string) => Locator;
}
export type Locator = ProtractorLocator | WebDriverLocator;

export function isProtractorLocator(x: Locator): x is ProtractorLocator {
return x && (typeof(x as any).findElementsOverride === 'function');
}

/**
* The Protractor Locators. These provide ways of finding elements in
Expand Down Expand Up @@ -70,7 +77,7 @@ export class ProtractorBy extends WebdriverBy {
* element. It should return an array of elements.
*/
addLocator(name: string, script: Function|string) {
this[name] = (...args: any[]): Locator => {
this[name] = (...args: any[]): ProtractorLocator => {
let locatorArguments = args;
return {
findElementsOverride: (driver: WebDriver, using: WebElement, rootSelector: string):
Expand Down Expand Up @@ -119,9 +126,9 @@ export class ProtractorBy extends WebdriverBy {
* var deprecatedSyntax = element(by.binding('{{person.name}}'));
*
* @param {string} bindingDescriptor
* @returns {Locator} location strategy
* @returns {ProtractorLocator} location strategy
*/
binding(bindingDescriptor: string): Locator {
binding(bindingDescriptor: string): ProtractorLocator {
return {
findElementsOverride: (driver: WebDriver, using: WebElement, rootSelector: string):
wdpromise.Promise<WebElement[]> => {
Expand Down Expand Up @@ -151,9 +158,9 @@ export class ProtractorBy extends WebdriverBy {
* expect(element(by.exactBinding('phone')).isPresent()).toBe(false);
*
* @param {string} bindingDescriptor
* @returns {Locator} location strategy
* @returns {ProtractorLocator} location strategy
*/
exactBinding(bindingDescriptor: string): Locator {
exactBinding(bindingDescriptor: string): ProtractorLocator {
return {
findElementsOverride: (driver: WebDriver, using: WebElement, rootSelector: string):
wdpromise.Promise<WebElement[]> => {
Expand All @@ -179,9 +186,9 @@ export class ProtractorBy extends WebdriverBy {
* expect(input.getAttribute('value')).toBe('Foo123');
*
* @param {string} model ng-model expression.
* @returns {Locator} location strategy
* @returns {ProtractorLocator} location strategy
*/
model(model: string): Locator {
model(model: string): ProtractorLocator {
return {
findElementsOverride: (driver: WebDriver, using: WebElement, rootSelector: string):
wdpromise.Promise<WebElement[]> => {
Expand All @@ -204,9 +211,9 @@ export class ProtractorBy extends WebdriverBy {
* element(by.buttonText('Save'));
*
* @param {string} searchText
* @returns {Locator} location strategy
* @returns {ProtractorLocator} location strategy
*/
buttonText(searchText: string): Locator {
buttonText(searchText: string): ProtractorLocator {
return {
findElementsOverride: (driver: WebDriver, using: WebElement, rootSelector: string):
wdpromise.Promise<WebElement[]> => {
Expand All @@ -229,9 +236,9 @@ export class ProtractorBy extends WebdriverBy {
* element(by.partialButtonText('Save'));
*
* @param {string} searchText
* @returns {Locator} location strategy
* @returns {ProtractorLocator} location strategy
*/
partialButtonText(searchText: string): Locator {
partialButtonText(searchText: string): ProtractorLocator {
return {
findElementsOverride: (driver: WebDriver, using: WebElement, rootSelector: string):
wdpromise.Promise<WebElement[]> => {
Expand All @@ -245,7 +252,7 @@ export class ProtractorBy extends WebdriverBy {
};

// Generate either by.repeater or by.exactRepeater
private byRepeaterInner(exact: boolean, repeatDescriptor: string): Locator {
private byRepeaterInner(exact: boolean, repeatDescriptor: string): ProtractorLocator {
let name = 'by.' + (exact ? 'exactR' : 'r') + 'epeater';
return {
findElementsOverride: (driver: WebDriver, using: WebElement, rootSelector: string):
Expand All @@ -256,7 +263,7 @@ export class ProtractorBy extends WebdriverBy {
toString: (): string => {
return name + '("' + repeatDescriptor + '")';
},
row: (index: number): Locator => {
row: (index: number): ProtractorLocator => {
return {
findElementsOverride: (driver: WebDriver, using: WebElement, rootSelector: string):
wdpromise.Promise<WebElement[]> => {
Expand All @@ -267,7 +274,7 @@ export class ProtractorBy extends WebdriverBy {
toString: (): string => {
return name + '(' + repeatDescriptor + '").row("' + index + '")"';
},
column: (binding: string): Locator => {
column: (binding: string): ProtractorLocator => {
return {
findElementsOverride: (driver: WebDriver, using: WebElement, rootSelector: string):
wdpromise.Promise<WebElement[]> => {
Expand All @@ -283,7 +290,7 @@ export class ProtractorBy extends WebdriverBy {
}
};
},
column: (binding: string): Locator => {
column: (binding: string): ProtractorLocator => {
return {
findElementsOverride: (driver: WebDriver, using: WebElement, rootSelector: string):
wdpromise.Promise<WebElement[]> => {
Expand All @@ -294,7 +301,7 @@ export class ProtractorBy extends WebdriverBy {
toString: (): string => {
return name + '("' + repeatDescriptor + '").column("' + binding + '")';
},
row: (index: number): Locator => {
row: (index: number): ProtractorLocator => {
return {
findElementsOverride: (driver: WebDriver, using: WebElement, rootSelector: string):
wdpromise.Promise<WebElement[]> => {
Expand Down Expand Up @@ -365,9 +372,9 @@ export class ProtractorBy extends WebdriverBy {
* var divs = element.all(by.repeater('book in library'));
*
* @param {string} repeatDescriptor
* @returns {Locator} location strategy
* @returns {ProtractorLocator} location strategy
*/
repeater(repeatDescriptor: string): Locator {
repeater(repeatDescriptor: string): ProtractorLocator {
return this.byRepeaterInner(false, repeatDescriptor);
}

Expand All @@ -387,9 +394,9 @@ export class ProtractorBy extends WebdriverBy {
* expect(element(by.exactRepeater('car in cars')).isPresent()).toBe(true);
*
* @param {string} repeatDescriptor
* @returns {Locator} location strategy
* @returns {ProtractorLocator} location strategy
*/
exactRepeater(repeatDescriptor: string): Locator {
exactRepeater(repeatDescriptor: string): ProtractorLocator {
return this.byRepeaterInner(true, repeatDescriptor);
}

Expand All @@ -408,9 +415,9 @@ export class ProtractorBy extends WebdriverBy {
*
* @param {string} cssSelector css selector
* @param {string} searchString text search
* @returns {Locator} location strategy
* @returns {ProtractorLocator} location strategy
*/
cssContainingText(cssSelector: string, searchText: string): Locator {
cssContainingText(cssSelector: string, searchText: string): ProtractorLocator {
return {
findElementsOverride: (driver: WebDriver, using: WebElement, rootSelector: string):
wdpromise.Promise<WebElement[]> => {
Expand Down Expand Up @@ -441,9 +448,9 @@ export class ProtractorBy extends WebdriverBy {
* expect(firstOption.getText()).toEqual('red');
*
* @param {string} optionsDescriptor ng-options expression.
* @returns {Locator} location strategy
* @returns {ProtractorLocator} location strategy
*/
options(optionsDescriptor: string): Locator {
options(optionsDescriptor: string): ProtractorLocator {
return {
findElementsOverride: (driver: WebDriver, using: WebElement, rootSelector: string):
wdpromise.Promise<WebElement[]> => {
Expand Down

0 comments on commit 46a1e0c

Please sign in to comment.