Skip to content

Commit

Permalink
deps(selenium): upgrade to selenium 4 (#5095)
Browse files Browse the repository at this point in the history
- elements workaround for WebElement.equals
- added a better unhandled rejection warning message in the launcher
- remove global function wrappers for mocha (these wrappers went away with
control flow)
- fix the attach to session driver provider

Typing exported from Protractor:

- removed ActionSequence and EventEmitter (actions is currently missing)
- removed promise.Promise
- removed Promise, defer, delayed, createFlow, controlFlow, all,
fulfilled, filter, when

Typings exported from WebDriver:

- removed attachToSession
- removed WebDriver instance methods: touchActions, call
- removed WebElement getSize and getLocation for getRect
- removed redefined global vars for testing
- In the typings, we are missing Options.setScriptTimeout method. This should not impact users unless they are using the driver.manage() method.

Tests:

- fix element equals test
- add missing 'await' in colorList test that is causing unhandled promise rejections.
- remove control flow related tests
- disable the install test. Installing from "file:../../" is not working.
- fix the attach to session driver provider test to exit with a 1 if errors are encountered
  • Loading branch information
cnishina committed Mar 23, 2019
1 parent 4672265 commit d213aa9
Show file tree
Hide file tree
Showing 25 changed files with 116 additions and 183 deletions.
24 changes: 12 additions & 12 deletions lib/browser.ts
Expand Up @@ -21,7 +21,7 @@ const DEFER_LABEL = 'NG_DEFER_BOOTSTRAP!';
const DEFAULT_RESET_URL = 'data:text/html,<html></html>';
const DEFAULT_GET_PAGE_TIMEOUT = 10000;

let logger = new Logger('protractor');
let logger = new Logger('browser');

// TODO(cnishina): either remove for loop entirely since this does not export anything
// the user might need since everything is composed (with caveat that this could be a
Expand Down Expand Up @@ -489,12 +489,11 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
script = 'return (' + script + ').apply(null, arguments);';
}

// TODO(selenium4): fix promise cast.
return this.driver.schedule(
new Command(CommandName.EXECUTE_SCRIPT)
.setParameter('script', script)
.setParameter('args', scriptArgs),
description) as Promise<any>;
// TODO(selenium4): schedule does not exist on driver. Should use execute instead.
return (this.driver as any)
.execute(new Command(CommandName.EXECUTE_SCRIPT)
.setParameter('script', script)
.setParameter('args', scriptArgs));
}

/**
Expand All @@ -512,14 +511,15 @@ export class ProtractorBrowser extends AbstractExtendedWebDriver {
*/
private executeAsyncScript_(script: string|Function, description: string, ...scriptArgs: any[]):
Promise<any> {
// TODO(selenium4): decide what to do with description.
if (typeof script === 'function') {
script = 'return (' + script + ').apply(null, arguments);';
}
return this.driver.schedule(
new Command(CommandName.EXECUTE_ASYNC_SCRIPT)
.setParameter('script', script)
.setParameter('args', scriptArgs),
description) as Promise<any>;
// TODO(selenium4): fix typings. driver.execute should exist
return (this.driver as any)
.execute(new Command(CommandName.EXECUTE_ASYNC_SCRIPT)
.setParameter('script', script)
.setParameter('args', scriptArgs));
}

/**
Expand Down
7 changes: 4 additions & 3 deletions lib/driverProviders/attachSession.ts
Expand Up @@ -3,7 +3,7 @@
* It is responsible for setting up the account object, tearing
* it down, and setting up the driver correctly.
*/
import {WebDriver} from 'selenium-webdriver';
import {Session, WebDriver} from 'selenium-webdriver';

import {Config} from '../config';
import {Logger} from '../logger';
Expand Down Expand Up @@ -38,8 +38,9 @@ export class AttachSession extends DriverProvider {
async getNewDriver(): Promise<WebDriver> {
const httpClient = new http.HttpClient(this.config_.seleniumAddress);
const executor = new http.Executor(httpClient);
const newDriver =
await WebDriver.attachToSession(executor, this.config_.seleniumSessionId, null);
const session = new Session(this.config_.seleniumSessionId, null);

const newDriver = new WebDriver(session, executor);
this.drivers_.push(newDriver);
return newDriver;
}
Expand Down
12 changes: 7 additions & 5 deletions lib/element.ts
Expand Up @@ -1124,11 +1124,13 @@ export class ElementFinder extends WebdriverWebElement {
* @returns {!Promise<boolean>} A promise that will be
* resolved to whether the two WebElements are equal.
*/
equals(element: ElementFinder|WebElement): Promise<any> {
return WebElement.equals(
this.getWebElement(),
(element as any).getWebElement ? (element as ElementFinder).getWebElement() :
element as WebElement) as Promise<any>;
async equals(element: ElementFinder|WebElement): Promise<boolean> {
const a = await this.getWebElement();
const b = (element as any).getWebElement ? await(element as ElementFinder).getWebElement() :
element as WebElement;
// TODO(selenium4): Use `return WebElement.equals(a, b);` when
// https://github.com/SeleniumHQ/selenium/pull/6749 is fixed.
return a.getDriver().executeScript('return arguments[0] === arguments[1]', a, b);
}
}

Expand Down
59 changes: 0 additions & 59 deletions lib/frameworks/mocha.js
Expand Up @@ -13,65 +13,6 @@ exports.run = (runner, specs) => {
require('./setupAfterEach').setup(runner, specs);

return new Promise(async (resolve, reject) => {
// Mocha doesn't set up the ui until the pre-require event, so
// wait until then to load mocha-webdriver adapters as well.
mocha.suite.on('pre-require', () => {
try {
// We need to re-wrap all of the global functions, which `selenium-webdriver/testing` only
// does when it is required. So first we must remove it from the cache.
delete require.cache[require.resolve('selenium-webdriver/testing')];
const seleniumAdapter = require('selenium-webdriver/testing');

// Save unwrapped version
let unwrappedFns = {};
['after', 'afterEach', 'before', 'beforeEach', 'it', 'xit', 'iit'].forEach((fnName) => {
unwrappedFns[fnName] = global[fnName] || Mocha[fnName];
});

const wrapFn = (seleniumWrappedFn, opt_fnName) => {
// This does not work on functions that can be nested (e.g. `describe`)
return function() {
// Set globals to unwrapped version to avoid circular reference
let wrappedFns = {};
for (let fnName in unwrappedFns) {
wrappedFns[fnName] = global[fnName];
global[fnName] = unwrappedFns[fnName];
}

let args = arguments;
// Allow before/after hooks to use names
if (opt_fnName && (arguments.length > 1) && (seleniumWrappedFn.length < 2)) {
global[opt_fnName] = global[opt_fnName].bind(this, args[0]);
args = Array.prototype.slice.call(arguments, 1);
}

try {
seleniumWrappedFn.apply(this, args);
} finally {
// Restore wrapped version
for (fnName in wrappedFns) {
global[fnName] = wrappedFns[fnName];
}
}
};
};

// Wrap functions
global.after = wrapFn(seleniumAdapter.after, 'after');
global.afterEach = wrapFn(seleniumAdapter.afterEach, 'afterEach');
global.before = wrapFn(seleniumAdapter.before, 'before');
global.beforeEach = wrapFn(seleniumAdapter.beforeEach, 'beforeEach');

global.it = wrapFn(seleniumAdapter.it);
global.iit = wrapFn(seleniumAdapter.it.only);
global.xit = wrapFn(seleniumAdapter.xit);
global.it.only = wrapFn(seleniumAdapter.it.only);
global.it.skip = wrapFn(seleniumAdapter.it.skip);
} catch (err) {
reject(err);
}
});

mocha.loadFiles();

try {
Expand Down
3 changes: 2 additions & 1 deletion lib/index.ts
Expand Up @@ -6,7 +6,8 @@ import {PluginConfig, ProtractorPlugin} from './plugins';
import {Ptor} from './ptor';

// Re-export selenium-webdriver types.
export {ActionSequence, Browser, Builder, Button, Capabilities, Capability, error, EventEmitter, FileDetector, Key, logging, promise, Session, until, WebDriver, WebElement, WebElementPromise} from 'selenium-webdriver';
// TODO(selenium4): Actions class typings missing. ActionSequence is deprecated.
export {/*Actions,*/ Browser, Builder, Button, Capabilities, Capability, error, EventEmitter, FileDetector, Key, logging, promise, Session, until, WebDriver, WebElement, WebElementPromise} from 'selenium-webdriver';
// Re-export public types.
export {ElementHelper, ProtractorBrowser} from './browser';
export {Config} from './config';
Expand Down
9 changes: 8 additions & 1 deletion lib/launcher.ts
Expand Up @@ -171,7 +171,14 @@ let initFn = async function(configFile: string, additionalConfig: Config) {
});

process.on('unhandledRejection', (reason: Error | any, p: Promise<any>) => {
logger.warn('Unhandled rejection at:', p, 'reason:', reason);
if (reason.stack.match('angular testability are undefined') ||
reason.stack.match('angular is not defined')) {
logger.warn(
'Unhandled promise rejection error: This is usually occurs ' +
'when a browser.get call is made and a previous async call was ' +
'not awaited');
}
logger.warn(p);
});

process.on('exit', (code: number) => {
Expand Down
4 changes: 3 additions & 1 deletion lib/runner.ts
Expand Up @@ -256,7 +256,9 @@ export class Runner extends EventEmitter {
}

await browser_.waitForAngularEnabled(initProperties.waitForAngularEnabled);
await driver.manage().timeouts().setScriptTimeout(initProperties.allScriptsTimeout || 0);
// TODO(selenium4): Options does not have a setScriptTimeout method.
await(driver.manage() as any).setTimeouts({script: initProperties.allScriptsTimeout || 0});


browser_.getProcessedConfig = () => {
return Promise.resolve(config);
Expand Down
20 changes: 10 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -19,7 +19,7 @@
"jasmine": "^2.8.0",
"optimist": "~0.6.0",
"saucelabs": "^1.5.0",
"selenium-webdriver": "3.6.0",
"selenium-webdriver": "^4.0.0-alpha.1",
"source-map-support": "~0.4.0",
"webdriver-js-extender": "2.1.0",
"webdriver-manager-replacement": "^1.1.1"
Expand Down
11 changes: 10 additions & 1 deletion scripts/driverProviderAttachSession.js
Expand Up @@ -83,6 +83,10 @@ const run = async () => {
throw new Error('The selenium session was not created.');
}
});
res.on('error', (err) => {
console.log(err);
process.exit(1);
});
});
req.end();
});
Expand All @@ -94,7 +98,8 @@ const run = async () => {
console.log(runProtractor.stdout.toString());
if (runProtractor.status !== 0) {
const e = new Error('Protractor did not run properly.');
deleteSession(sessionId, e);
await deleteSession(sessionId, e);
process.exit(1);
}

// 4. After the protractor test completes, check to see that the session still
Expand All @@ -120,6 +125,10 @@ const run = async () => {
deleteSession(sessionId, e);
}
});
res.on('error', (err) => {
console.log(err);
process.exit(1);
});
});
req.end();
});
Expand Down
1 change: 1 addition & 0 deletions scripts/test.js
Expand Up @@ -50,6 +50,7 @@ const passingTests = [
// Dependency tests
'node node_modules/jasmine/bin/jasmine.js JASMINE_CONFIG_PATH=scripts/dependency_test.json',
// Typings tests
// TODO(selenium4): consider rewriting this test.
// 'node spec/install/test.js'
];

Expand Down
8 changes: 5 additions & 3 deletions spec/basic/elements_spec.js
@@ -1,3 +1,5 @@
const {WebElement} = require('selenium-webdriver');

describe('ElementFinder', () => {
beforeEach(async() => {
// Clear everything between each test.
Expand Down Expand Up @@ -162,7 +164,7 @@ describe('ElementFinder', () => {

it('should be returned from a helper without infinite loops', async() => {
await browser.get('index.html#/form');
const helperPromise = protractor.promise.when(true).then(() => {
const helperPromise = Promise.resolve(true).then(() => {
return element(by.binding('greeting'));
});

Expand Down Expand Up @@ -385,11 +387,11 @@ describe('ElementArrayFinder', () => {

it('should get an element from an array by promise index', async() => {
const colorList = element.all(by.model('color'));
const index = protractor.promise.fulfilled(1);
const index = Promise.resolve(1);

await browser.get('index.html#/form');

expect(await colorList.get(index).getAttribute('value')).toEqual('green');
expect(await colorList.get(await index).getAttribute('value')).toEqual('green');
});

it('should get an element from an array using negative indices', async() => {
Expand Down
3 changes: 2 additions & 1 deletion spec/basic/lib_spec.js
Expand Up @@ -20,7 +20,8 @@ describe('protractor library', () => {

it('should export other webdriver classes onto the global protractor',
() => {
expect(protractor.ActionSequence).toBeDefined();
// TODO(selenium4): Actions API missing from typings.
// expect(protractor.Actions).toBeDefined();
expect(protractor.Key.RETURN).toEqual('\uE006');
});

Expand Down
14 changes: 5 additions & 9 deletions spec/dependencyTest/protractor_spec.js
Expand Up @@ -13,9 +13,10 @@ describe('require(\'protractor\')', () => {
});

it('should have selenium-webdriver functions defined', () => {
var seleniumFunctions = ['ActionSequence', 'Builder',
'Capabilities', 'Command', 'EventEmitter', 'FileDetector',
'Session', 'WebDriver', 'WebElement', 'WebElementPromise'];
// TODO(selenium4): Update Actions when it is in typings. ActionSequence and EventEmitter is deprecated.
var seleniumFunctions = [/*'Actions', */'Builder',
'Capabilities', 'Command', 'FileDetector', 'Session', 'WebDriver',
'WebElement', 'WebElementPromise'];
for (var pos in seleniumFunctions) {
var propertyObj = seleniumFunctions[pos];
expect(typeof protractor[propertyObj]).toEqual('function');
Expand All @@ -31,10 +32,6 @@ describe('require(\'protractor\')', () => {
});


it('should have selenium-webdriver promise.Promise', function() {
expect(typeof protractor['promise']['Promise']).toEqual('function');
});

describe('browser class', () => {
it('should have static variables defined', () => {
var staticVariables = ['By'];
Expand All @@ -48,8 +45,7 @@ describe('require(\'protractor\')', () => {

describe('promise namespace', () => {
it('should have functions defined (spot check)', () => {
var promiseFunctions = ['Promise', 'defer', 'delayed', 'createFlow',
'controlFlow', 'all', 'fulfilled', 'filter', 'when' ]
var promiseFunctions = ['delayed', 'filter'];
for (var pos in promiseFunctions) {
var property = promiseFunctions[pos];
expect(typeof protractor.promise[property]).toEqual('function');
Expand Down

0 comments on commit d213aa9

Please sign in to comment.