Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(SELENIUM_PROMISE_MANAGER): Support SELENIUM_PROMISE_MANAGER=0 #72

Merged
merged 1 commit into from
Jan 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
*.log
node_modules
spec/asyncAwaitSpec.js
built_spec
2 changes: 1 addition & 1 deletion .jshintignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
./spec/asyncAwaitSpec.js
./built_spec/*
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
node_modules/
spec/
built_spec/
scripts/
tsconfig.json
tslint.json
.gitignore
.jshintignore
.npmignore
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@ available via several compilers. At the moment, they often break the WebDriver
control flow.
([GitHub issue](https://github.com/SeleniumHQ/selenium/issues/3037)). You can
still use them, but if you do then you will have to use `await`/Promises for
almost all your synchronization. See `spec/asyncAwaitSpec.ts` for details.
almost all your synchronization. See `spec/asyncAwaitAdapterSpec.ts` and
`spec/asyncAwaitErrorSpec.ts` for examples.
69 changes: 36 additions & 33 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

var webdriver = require('selenium-webdriver');
var maybePromise = require('./maybePromise');

/**
* Validates that the parameter is a function.
Expand Down Expand Up @@ -54,7 +55,7 @@ function validateString(stringtoValidate) {
* @param {!Function} fn The function to call
*/
function callWhenIdle(flow, fn) {
if (flow.isIdle()) {
if (!flow.isIdle || flow.isIdle()) {
fn();
} else {
flow.once(webdriver.promise.ControlFlow.EventType.IDLE, function() {
Expand Down Expand Up @@ -84,7 +85,14 @@ function wrapInControlFlow(flow, globalFn, fnName) {
var testFn = fn.bind(this);

flow.execute(function controlFlowExecute() {
return new webdriver.promise.Promise(function(fulfill, reject) {
function newPromise(resolver) {
if (typeof flow.promise == 'function') {
return flow.promise(resolver);
} else {
return new webdriver.promise.Promise(resolver, flow);
}
}
return newPromise(function(fulfill, reject) {
function wrappedReject(err) {
var wrappedErr = new Error(err);
reject(wrappedErr);
Expand All @@ -106,7 +114,7 @@ function wrapInControlFlow(flow, globalFn, fnName) {
fulfill(ret);
}
}
}, flow);
});
}, 'Run ' + fnName + description + ' in control flow').then(
callWhenIdle.bind(null, flow, done), function(err) {
if (!err) {
Expand Down Expand Up @@ -173,15 +181,17 @@ function initJasmineWd(flow) {
global.beforeAll = wrapInControlFlow(flow, global.beforeAll, 'beforeAll');
global.afterAll = wrapInControlFlow(flow, global.afterAll, 'afterAll');

// On timeout, the flow should be reset. This will prevent webdriver tasks
// from overflowing into the next test and causing it to fail or timeout
// as well. This is done in the reporter instead of an afterEach block
// to ensure that it runs after any afterEach() blocks with webdriver tasks
// get to complete first.
jasmine.getEnv().addReporter(new OnTimeoutReporter(function() {
console.warn('A Jasmine spec timed out. Resetting the WebDriver Control Flow.');
flow.reset();
}));
if (flow.reset) {
// On timeout, the flow should be reset. This will prevent webdriver tasks
// from overflowing into the next test and causing it to fail or timeout
// as well. This is done in the reporter instead of an afterEach block
// to ensure that it runs after any afterEach() blocks with webdriver tasks
// get to complete first.
jasmine.getEnv().addReporter(new OnTimeoutReporter(function() {
console.warn('A Jasmine spec timed out. Resetting the WebDriver Control Flow.');
flow.reset();
}));
}
}

var originalExpect = global.expect;
Expand All @@ -196,6 +206,10 @@ global.expect = function(actual) {
/**
* Creates a matcher wrapper that resolves any promises given for actual and
* expected values, as well as the `pass` property of the result.
*
* Wrapped matchers will return either `undefined` or a promise which resolves
* when the matcher is complete, depending on if the matcher had to resolve any
* promises.
*/
jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) {
return function() {
Expand All @@ -205,16 +219,12 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) {

matchError.stack = matchError.stack.replace(/ +at.+jasminewd.+\n/, '');

if (!webdriver.promise.isPromise(expectation.actual) &&
!webdriver.promise.isPromise(expected)) {
compare(expectation.actual, expected);
} else {
webdriver.promise.when(expectation.actual).then(function(actual) {
return webdriver.promise.all(expected).then(function(expected) {
return compare(actual, expected);
});
// Return either undefined or a promise of undefined
return maybePromise(expectation.actual, function(actual) {
return maybePromise.all(expected, function(expected) {
return compare(actual, expected);
});
}
});

function compare(actual, expected) {
var args = expected.slice(0);
Expand All @@ -229,12 +239,9 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) {

var result = matcherCompare.apply(null, args);

if (webdriver.promise.isPromise(result.pass)) {
return webdriver.promise.when(result.pass).then(compareDone);
} else {
return compareDone(result.pass);
}
return maybePromise(result.pass, compareDone);

// compareDone always returns undefined
function compareDone(pass) {
var message = '';

Expand Down Expand Up @@ -268,13 +275,9 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) {

function defaultNegativeCompare() {
var result = matcher.compare.apply(null, args);
if (webdriver.promise.isPromise(result.pass)) {
result.pass = result.pass.then(function(pass) {
return !pass;
});
} else {
result.pass = !result.pass;
}
result.pass = maybePromise(result.pass, function(pass) {
return !pass;
});
return result;
}
}
Expand Down
58 changes: 58 additions & 0 deletions maybePromise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* This file implements jasminewd's peculiar alternatives to Promise.resolve()
* and Promise.all(). Do not use the code from this file as polyfill for
* Promise.resolve() or Promise.all(). There are a number of reasons why this
* implementation will cause unexpected errors in most codebases.
*
* Called "maybePromise" because both the parameters and the return values may
* or may not be promises, and code execution may or may not be synchronous.
*/

/**
* Runs a callback synchronously against non-promise values and asynchronously
* against promises. Similar to ES6's `Promise.resolve` except that it is
* synchronous when possible and won't wrap the return value.
*
* This is not what you normally want. Normally you want the code to be
* consistently asynchronous, and you want the result wrapped into a promise.
* But because of webdriver's control flow, we're better off not introducing any
* extra layers of promises or asynchronous activity.
*
* @param {*} val The value to call the callback with.
* @param {!Function} callback The callback function
* @return {*} If val isn't a promise, the return value of the callback is
* directly returned. If val is a promise, a promise (generated by val.then)
* resolving to the callback's return value is returned.
*/
var maybePromise = module.exports = function maybePromise(val, callback) {
if (val && (typeof val.then == 'function')) {
return val.then(callback);
} else {
return callback(val);
}
}

/**
* Like maybePromise() but for an array of values. Analogous to `Promise.all`.
*
* @param {!Array<*>} vals An array of values to call the callback with
* @param {!Function} callback the callback function
* @return {*} If nothing in vals is a promise, the return value of the callback
* is directly returned. Otherwise, a promise (generated by the .then
* functions in vals) resolving to the callback's return value is returned.
*/
maybePromise.all = function all(vals, callback) {
var resolved = new Array(vals.length);
function resolveAt(i) {
if (i >= vals.length) {
return callback(resolved);
} else {
return maybePromise(vals[i], function(val) {
resolved[i] = val;
return resolveAt(i+1);
});
}
}
return resolveAt(0);
}

15 changes: 12 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@
"selenium-webdriver": "3.0.1"
},
"devDependencies": {
"@types/node": "^6.0.56",
"@types/selenium-webdriver": "^2.53.38",
"jasmine": "2.4.1",
"jshint": "^2.9.4",
"typescript": "^2.0.10"
"selenium-webdriver": "2.53.3",
"tslint": "^4.2.0",
"tslint-eslint-rules": "^3.2.3",
"typescript": "^2.0.10",
"vrsource-tslint-rules": "^4.0.0"
},
"repository": {
"type": "git",
Expand All @@ -26,8 +33,10 @@
"main": "index.js",
"scripts": {
"jshint": "jshint index.js spec",
"tsc": "tsc -t ES2015 spec/asyncAwaitSpec.ts",
"pretest": "npm run jshint && npm run tsc",
"tslint": "tslint spec/*.ts",
"lint": "npm run jshint && npm run tslint",
"tsc": "tsc; cp spec/*.js built_spec",
"pretest": "npm run lint && npm run tsc",
"test": "scripts/test.sh"
},
"license": "MIT",
Expand Down
44 changes: 41 additions & 3 deletions scripts/test.sh
Original file line number Diff line number Diff line change
@@ -1,21 +1,59 @@
LIB_SPECS="spec/support/lib_specs.json"
PASSING_SPECS="spec/support/passing_specs.json"
FAILING_SPECS="spec/support/failing_specs.json"
NO_CF_PASSING_SPECS="spec/support/no_cf_passing_specs.json"
NO_CF_FAILING_SPECS="spec/support/no_cf_failing_specs.json"
CMD_BASE="node node_modules/.bin/jasmine JASMINE_CONFIG_PATH="

echo "### running passing specs"
# Run unit tests

echo "### running all unit tests"
CMD=$CMD_BASE$LIB_SPECS
echo "### $CMD"
$CMD
[ "$?" -eq 0 ] || exit 1
echo


# Run all tests when the control flow is enabled

export SELENIUM_PROMISE_MANAGER=1

echo "### running all passing specs"
CMD=$CMD_BASE$PASSING_SPECS
echo "### $CMD"
$CMD
[ "$?" -eq 0 ] || exit 1
echo

EXPECTED_RESULTS="18 specs, 16 failures"
echo "### running failing specs (expecting $EXPECTED_RESULTS)"
EXPECTED_RESULTS="38 specs, 34 failures"
echo "### running all failing specs (expecting $EXPECTED_RESULTS)"
CMD=$CMD_BASE$FAILING_SPECS
echo "### $CMD"
res=`$CMD 2>/dev/null`
results_line=`echo "$res" | tail -2 | head -1`
echo "result: $results_line"
[ "$results_line" = "$EXPECTED_RESULTS" ] || exit 1

# Run only the async/await tests when the control flow is disabled

export SELENIUM_PROMISE_MANAGER=0

echo "### running async/await passing specs"
CMD=$CMD_BASE$NO_CF_PASSING_SPECS
echo "### $CMD"
$CMD
[ "$?" -eq 0 ] || exit 1
echo

EXPECTED_RESULTS="19 specs, 17 failures"
echo "### running async/await failing specs (expecting $EXPECTED_RESULTS)"
CMD=$CMD_BASE$NO_CF_FAILING_SPECS
echo "### $CMD"
res=`$CMD 2>/dev/null`
results_line=`echo "$res" | tail -2 | head -1`
echo "result: $results_line"
[ "$results_line" = "$EXPECTED_RESULTS" ] || exit 1


echo "all pass"
8 changes: 7 additions & 1 deletion spec/adapterSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ describe('webdriverJS Jasmine adapter', function() {

fakeDriver.getValueList().then(function(list) {
var result = list.map(function(webElem) {
var webElemsPromise = webdriver.promise.fulfilled(webElem).then(function(webElem) {
var webElemsPromise = webdriver.promise.when(webElem).then(function(webElem) {
return [webElem];
});
return webdriver.promise.fullyResolved(checkTexts(webElemsPromise));
Expand Down Expand Up @@ -244,6 +244,12 @@ describe('webdriverJS Jasmine adapter', function() {
});

describe('native promises', function() {
it('should have done argument override return returned promise', function(done) {
var ret = new Promise(function() {});
done();
return ret;
});

var currentTest = null;

it('should wait for webdriver events sent from native promise', function() {
Expand Down
Loading