admin管理员组

文章数量:1312852

q library has this neat feature to resolve and spread multiple promises into separate arguments:

If you have a promise for an array, you can use spread as a replacement for then. The spread function “spreads” the values over the arguments of the fulfillment handler.

return getUsername()
    .then(function (username) {
        return [username, getUser(username)];
    })
    .spread(function (username, user) {

    });

In protractor, we are trying to use the built-in protractor.promise ing from WebDriverJS.

The Question:

Is it possible to have the "spread" functionality with protractor.promise?

Example use case:

We've implemented a custom jasmine matcher to check if an element is focused. Here we need to resolve two promises before making an equality parison. Currently, we are using protractor.promise.all() and then():

protractor.promise.all([
    elm.getId(),
    browser.driver.switchTo().activeElement().getId()
]).then(function (values) {
    jasmine.matchersUtil.equals(values[0], values[1]);
});

which ideally we'd like to have in a more readable state:

protractor.promise.all([
    elm.getId(),
    browser.driver.switchTo().activeElement().getId()
]).spread(function (currentElementID, activeElementID) {
    return jasmine.matchersUtil.equals(currentElementID, activeElementID);
})

q library has this neat feature to resolve and spread multiple promises into separate arguments:

If you have a promise for an array, you can use spread as a replacement for then. The spread function “spreads” the values over the arguments of the fulfillment handler.

return getUsername()
    .then(function (username) {
        return [username, getUser(username)];
    })
    .spread(function (username, user) {

    });

In protractor, we are trying to use the built-in protractor.promise ing from WebDriverJS.

The Question:

Is it possible to have the "spread" functionality with protractor.promise?

Example use case:

We've implemented a custom jasmine matcher to check if an element is focused. Here we need to resolve two promises before making an equality parison. Currently, we are using protractor.promise.all() and then():

protractor.promise.all([
    elm.getId(),
    browser.driver.switchTo().activeElement().getId()
]).then(function (values) {
    jasmine.matchersUtil.equals(values[0], values[1]);
});

which ideally we'd like to have in a more readable state:

protractor.promise.all([
    elm.getId(),
    browser.driver.switchTo().activeElement().getId()
]).spread(function (currentElementID, activeElementID) {
    return jasmine.matchersUtil.equals(currentElementID, activeElementID);
})
Share Improve this question edited May 23, 2017 at 12:03 CommunityBot 11 silver badge asked Aug 7, 2015 at 22:58 alecxealecxe 474k127 gold badges1.1k silver badges1.2k bronze badges 10
  • How do you expect this to happen if these are two different libraries? Unless WebDriverJS promise is wrapped in Q or Bluebird you can't get functionality of another library – Kirill Slatin Commented Aug 7, 2015 at 23:39
  • 1 hmm, that might make sense to replace the promise engine from the very beginning. I thought of wrapping a specific promise instance. Something similar to bluebird's promisify – Kirill Slatin Commented Aug 8, 2015 at 0:17
  • 1 However I think this might not work, Protractor makes use of ControlFlow, which is implemented inside WebDriverJS. And Protractor.promise most likely is just a reference, so changing it won't help. What really should be changed to slot in a new promise engine is require('webdriver').promise somehow – Kirill Slatin Commented Aug 8, 2015 at 0:59
  • 1 Thought: Starting with a Protractor promise, if you need some sugar method offered by Q, then you could try coercing to Q, using Q's sugar, then coercing back into Protractor, eg. var newProtratorPromise = protractor.promise.when(Q.when(someProtractorPromise).sugarMethod(...));. Probably not worth the bother for your .spread() case but worth considering for more involved sugar, providing Protractor's "ControlFlow" aspects are not lost in the double-shuffle. – Roamer-1888 Commented Aug 8, 2015 at 10:53
  • 1 @KirillSlatin actually, making webdriverjs and protractor to use q worked for me. I've posted it as an answer and created a github thread. I'm really not sure about the problems it can cause, but so far, my tests work as before. Thanks again for the participation. – alecxe Commented Aug 8, 2015 at 23:14
 |  Show 5 more ments

2 Answers 2

Reset to default 7 +100

It may e a bit ugly to use, but you can define an independent helper function, which can be passed to then() as a parameter and have a callback, which is usually passed to then() to be passed to it. This function will then convert array value to function arguments:

protractor.promise.all([
    elm.getId(),
    browser.driver.switchTo().activeElement().getId()
]).then(spread(function (currentElementID, activeElementID) {
    // ---^^^----- use helper function to spread args
    jasmine.matchersUtil.equals(currentElementID, activeElementID);
}));


// helper function gets a callback
function spread(callback) {
    // and returns a new function which will be used by `then()`
    return function (array) {
        // with a result of calling callback via apply to spread array values
        return callback.apply(null, array);
    };
}

You can still chain it with another then() and provide rejection callbacks; it keeps all the behavior of Protractor promises the same, but just converts array of values to arguments.

Drawbacks are that it is does not have a perfect look like in your example (not .all().spread() but .all().then(spread()) ) and you'll probably have to create a module for this helper or define it globally to be able to use it easily in multiple test files.

Update:

With ES2015 it is possible to use destructuring assignment along with then():

protractor.promise.all([
    elm.getId(),
    browser.driver.switchTo().activeElement().getId()
]).then(function (values) {
    // Destructure values to separate variables
    const [currentElementID, activeElementID] = values; 
    jasmine.matchersUtil.equals(currentElementID, activeElementID);
}));

TL;DR Apparently, it's not entirely safe to replace protractor.promise with q. For instance, I've got a hanging test run once I've decided to extend ElementArrayFinder:

  • Take elements while a condition evaluates to true (extending ElementArrayFinder)

Old answer:

Here is what I've done to solve it.

I've replaced protractor.promise with q on the fly (not sure if it's actually safe to do):

onPrepare: {
    protractor.promise = require("q");
},

But, nothing broke so far and now I'm able to use spread() and other syntactic sugar provided by q through protractor.promise:

toBeActive: function() {
    return {
        pare: function(elm) {
            return {
                pass: protractor.promise.all([
                    elm.getId(),
                    browser.driver.switchTo().activeElement().getId()
                ]).spread(function (currentElementID, activeElementID) {
                    return jasmine.matchersUtil.equals(currentElementID, activeElementID);
                })
            };
        }
    };
}

Relevant github thread: protractor.promise to use q.

本文标签: javascriptSpreading promises in ProtractorStack Overflow