Skip to content

Commit eaa1ff8

Browse files
committed
feat(SELENIUM_PROMISE_MANAGER=0): Improve support for SELENIUM_PROMISE_MANAGER=0
There are three major ways this was done in this change: * In `callWhenIdle`, if `flow.isIdle` is not defined, we assume we are working with a `SimpleScheduler` instance, and so the flow is effectively idle. * In `wrapInControlFlow`, we use `flow.promise` to create a new promise if possible. Since `new webdriver.promise.Promise()` would have always made a `ManagedPromise`, but `flow.promise` will do the right thing. * In `wrapCompare`, we avoid the webdriver library entirely, and never instance any extra promises. Using `webdriver.promise.when` and `webdriver.promise.all` could have been a problem if our instance of `webdriver` had the control flow turned on, but another instance somewhere did not (or even the same instance, but just at a different point in time). I also added code to extract promises out of deferreds. This is needed for selenium 3.x, and Craig already implemented a solution in the beta branch (70c9f62), but I was worried about what could happen if there were multiple versions of `selenium-webdriver` floating around. This change extracts promises without using `instanceof`. As an added bonus, it now works with `q`'s deferreds too! I also also fixed a minor bug where we weren't correctly checking for promises inside an array of expected results. Before we had ```js expected = Array.prototype.slice.call(arguments, 0) ... webdriver.promise.isPromise(expected) ``` I thought about it for a little while, and there's no way that's correct. `expected` is an `Array<any>`, there's no way it has a `.then` function. Might close #69 once we rebase `beta` against this, but I haven't checked.
1 parent ffae218 commit eaa1ff8

File tree

1 file changed

+89
-24
lines changed

1 file changed

+89
-24
lines changed

index.js

Lines changed: 89 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function validateString(stringtoValidate) {
5454
* @param {!Function} fn The function to call
5555
*/
5656
function callWhenIdle(flow, fn) {
57-
if (flow.isIdle()) {
57+
if (!flow.isIdle || flow.isIdle()) {
5858
fn();
5959
} else {
6060
flow.once(webdriver.promise.ControlFlow.EventType.IDLE, function() {
@@ -84,7 +84,14 @@ function wrapInControlFlow(flow, globalFn, fnName) {
8484
var testFn = fn.bind(this);
8585

8686
flow.execute(function controlFlowExecute() {
87-
return new webdriver.promise.Promise(function(fulfill, reject) {
87+
function newPromise(resolver) {
88+
if (typeof flow.promise == 'function') {
89+
return flow.promise(resolver);
90+
} else {
91+
return new webdriver.promise.Promise(resolver, flow);
92+
}
93+
}
94+
return newPromise(function(fulfill, reject) {
8895
function wrappedReject(err) {
8996
var wrappedErr = new Error(err);
9097
reject(wrappedErr);
@@ -106,7 +113,7 @@ function wrapInControlFlow(flow, globalFn, fnName) {
106113
fulfill(ret);
107114
}
108115
}
109-
}, flow);
116+
});
110117
}, 'Run ' + fnName + description + ' in control flow').then(
111118
callWhenIdle.bind(null, flow, done), function(err) {
112119
if (!err) {
@@ -193,6 +200,74 @@ global.expect = function(actual) {
193200
return originalExpect(actual);
194201
};
195202

203+
/**
204+
* Gets the underlying promise out of a deferred, does nothing to non-deferreds.
205+
*
206+
* @param {*} val The deferred (or not)
207+
* @return {*} The underlying promise of val if it is a deferred, or val itself
208+
* if it wasn't a deferred or was already a promise
209+
*/
210+
function extractPromiseFromDeferred(val) {
211+
// This check isn't perfect, but it's much better than `instanceof Deferred`,
212+
// which will fail if there's a version mismatch or if the deferred is from a
213+
// different library.
214+
if (val && !val.then && val.promise && (typeof val.fulfill == 'function') &&
215+
(typeof val.reject == 'function') && val.constructor &&
216+
val.constructor.name && (val.constructor.name.indexOf('defer') != -1)) {
217+
return val.promise;
218+
} else {
219+
return val;
220+
}
221+
}
222+
223+
/**
224+
* Runs a callback synchronously against non-promise values and asynchronously
225+
* against promises. Similar to ES6's `Promise.resolve` except that it is
226+
* synchronous when possible and won't wrap the return value.
227+
*
228+
* This is not what you normally want. Normally you want the code to be
229+
* consistently asynchronous, and you want the result wrapped into a promise.
230+
* But because of webdriver's control flow, we're better off not introducing any
231+
* extra layers of promises or asynchronous activity.
232+
*
233+
* @param {*} val The value to call the callback with.
234+
* @param {!Function} callback The callback function
235+
* @return {*} If val isn't a promise, the return value of the callback is
236+
* directly returned. If val is a promise, a promise (generated by val.then)
237+
* resolving to the callback's return value is returned.
238+
*/
239+
function maybePromise(val, callback) {
240+
if (val && (typeof val.then == 'function')) {
241+
return val.then(callback);
242+
} else {
243+
return callback(val);
244+
}
245+
}
246+
247+
/**
248+
* Like maybePromise() but for an array of values
249+
*
250+
* @param {!Array<*>} vals An array of values to call the callback with
251+
* @param {!Function} callback the callback function
252+
* @return {*} If nothing in vals is a promise, the return value of the callback
253+
* is directly returned. Otherwise, a promise (generated by the .then
254+
* functions in vals) resolving to the callback's return value is returned.
255+
*/
256+
function maybePromises(vals, callback) {
257+
var resolved = new Array(vals.length);
258+
function resolveAt(i) {
259+
if (i >= vals.length) {
260+
return callback(resolved);
261+
} else {
262+
return maybePromise(vals[i], function(val) {
263+
resolved[i] = val;
264+
return resolveAt(i+1);
265+
});
266+
}
267+
}
268+
return resolveAt(0);
269+
}
270+
196271
/**
197272
* Creates a matcher wrapper that resolves any promises given for actual and
198273
* expected values, as well as the `pass` property of the result.
@@ -205,16 +280,14 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) {
205280

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

208-
if (!webdriver.promise.isPromise(expectation.actual) &&
209-
!webdriver.promise.isPromise(expected)) {
210-
compare(expectation.actual, expected);
211-
} else {
212-
webdriver.promise.when(expectation.actual).then(function(actual) {
213-
return webdriver.promise.all(expected).then(function(expected) {
214-
return compare(actual, expected);
215-
});
283+
expectation.actual = extractPromiseFromDeferred(expectation.actual);
284+
expected = expected.map(extractPromiseFromDeferred);
285+
286+
maybePromise(expectation.actual, function(actual) {
287+
return maybePromises(expected, function(expected) {
288+
return compare(actual, expected);
216289
});
217-
}
290+
});
218291

219292
function compare(actual, expected) {
220293
var args = expected.slice(0);
@@ -229,11 +302,7 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) {
229302

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

232-
if (webdriver.promise.isPromise(result.pass)) {
233-
return webdriver.promise.when(result.pass).then(compareDone);
234-
} else {
235-
return compareDone(result.pass);
236-
}
305+
return maybePromise(result.pass, compareDone);
237306

238307
function compareDone(pass) {
239308
var message = '';
@@ -268,13 +337,9 @@ jasmine.Expectation.prototype.wrapCompare = function(name, matcherFactory) {
268337

269338
function defaultNegativeCompare() {
270339
var result = matcher.compare.apply(null, args);
271-
if (webdriver.promise.isPromise(result.pass)) {
272-
result.pass = result.pass.then(function(pass) {
273-
return !pass;
274-
});
275-
} else {
276-
result.pass = !result.pass;
277-
}
340+
result.pass = maybePromise(result.pass, function(pass) {
341+
return !pass;
342+
});
278343
return result;
279344
}
280345
}

0 commit comments

Comments
 (0)