Dispensing guarantees with Protractor

q library offers a unique feature that allows resolving and spreading multiple promises into separate arguments:

If you have a promise for an array, you can utilize spread instead of then. The spread function distributes the values as arguments in the fulfillment handler.

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

    });

In our work with protractor, we are exploring the capabilities of the built-in protractor.promise from WebDriverJS.

The Query:

Is it feasible to implement the "spread" functionality using protractor.promise?

A Practical Example:

We have developed a custom jasmine matcher to verify if an element is focused. This requires resolving two promises before conducting an equality comparison. Currently, we rely on 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]);
});

Ideally, we would prefer a more readable approach such as:

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

Answer №1

If you find it a bit messy to use, there is a solution. You can create a separate helper function that can be passed as a parameter to then() and contain a callback which is typically passed to then(). This helper function will then convert the array value into function arguments:

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


// The helper function takes a callback
function spread(callback) {
    // It returns a new function to be used by `then()`
    return function (array) {
        // Call the callback using apply to spread the array values 
        return callback.apply(null, array);
    };
}

You can still chain it with another then() and provide rejection callbacks; this maintains the behavior of Protractor promises while converting an array of values into arguments.

A downside is that it doesn't look exactly like your example (not .all().spread(), but .all().then(spread())) and you may need to create a module for this helper or define it globally for easier use in multiple test files.

Update:

With ES2015, you can now utilize destructuring assignment along with then():

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

Answer №2

TL;DR: It seems that simply replacing protractor.promise with q may not be completely safe. When extending ElementArrayFinder, I encountered a hanging test run.

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

Previous solution:

Here is what I did to address this issue.

I decided to switch from protractor.promise to q on the go, although I wasn't sure about the safety of this approach:

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

Surprisingly, nothing has broken so far and now I can utilize spread() and other helpful features offered by q using protractor.promise:

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

Check out the relevant github thread: protractor.promise to use q.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Creating a stunning art exhibition using React Native

Currently, I am in the process of creating a gallery component that utilizes both the scrollview and image APIs. I'm curious about how the scrollview manages its child components when it scrolls down. Does it unmount the parts that are not currently ...

I seem to be stuck in an endless loop within React and can't find a way to break free

Currently, I am utilizing the useState() function along with an array errors[] as part of the state and a function setError() to pass the useState() function to child elements for calling purposes: const [errors, setErrors] = useState([]); //-------------- ...

Is Grouping Together Installed Private Modules Possible?

Exploring a modular JavaScript approach in my upcoming project is a new concept for me. I would prefer explanations to be simple due to my limited experience. I have uploaded my private package on npm: @name/package-name The private package contains mul ...

Ajax alert: The index 'id' is not defined

I'm currently learning PHP and scripting, but I am encountering some difficulties when it comes to sending information to PHP. I would appreciate any help you can offer. Thank you for your assistance. Here is the issue at hand: Error Message: Noti ...

Load Angular scripts only when needed

I need to develop an application that can dynamically register new Angular Controllers obtained from a script. This application should load the minimum necessary scripts at startup and then fetch additional scripts as needed from other modules. Here' ...

How come running `npm install <folder>` results in installing different dependencies compared to `npm install lib`?

My current project, project1, relies on <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="5221262b3e37367f313d3f223d3c373c262112667c6">[email protected]</a>. When attempting to integrate project2 into project1 as a ...

IONIC App detecting lack of internet connectivity

I've successfully installed the Cordova network plugin with no errors on the code side. When I run ionic serve to test the app in my browser, it detects my WiFi and the plugin recognizes a connection. If I disable WiFi on my computer, it correctly id ...

Delete items within the first 10 minutes of shutting it down

Is there a way to temporarily remove a newsletter element for 10 minutes after closing it on a webpage? The idea is that once the panel is closed, it should stay hidden even if the page is refreshed within that timeframe. I was considering using local stor ...

What is the best way to adjust the width of a textarea based on its content

How can I dynamically set the width of a React Semantic UI textarea based on its content? Setting min-width doesn't seem to be working. Any suggestions? <Textarea key={idx} defaultValue={formattedText} className="customInpu ...

Exploring the dynamic data loading feature in Vue 3 by fetching data from the server and displaying it using a v-for

I am encountering an issue where I want to display data dynamically from a database using a v-for loop. However, when I attempt to push new data into the array, they show correctly in a console.log() but do not reflect any changes in the template. I have ...

Steps to load a script when the document is ready:

What is the best method to include JavaScript using the following code: <script type="text/javascript" src="http://uads.ir/l.php?s=125125&w=5307cd5c027373e1773c9869"></script> only after the page has fully loaded? $(document).ready(funct ...

What steps should be taken to retrieve the contents of a file that has been chosen using the browse

You have successfully implemented a browse button that allows the user to navigate the directory and choose a file. The path and file name are then displayed in the text element of the Browse button complex. Now, the question arises - how can I extract dat ...

Several middlewares using router.params()

Is it possible to include multiple middlewares as parameters in the function router.params() in Node-Express? I currently have the following setup: const checkAuth = (req, res, next) => {console.log("checking auth"); next()} const checkAuth = ...

Creating a Typescript interface for a anonymous function being passed into a React component

I have been exploring the use of Typescript in conjunction with React functional components, particularly when utilizing a Bootstrap modal component. I encountered some confusion regarding how to properly define the Typescript interface for the component w ...

Looking to integrate the date, month, and year selection tags into the inline format

The Edit User Profile page includes a Birthday field, which is currently displayed vertically. I am looking to change the layout so that the Birthday field appears horizontally. Here is the code I am using: Within visitors/edit.html.erb, <%= simple_f ...

Steps for generating a time selection dropdown menu

My issue is with the functionality of my timepicker dropdown. Below is the code I am currently using: $(document).ready(function() { $('.timepicker-input').timepicker({ timeFormat: 'h:mm p', interval: 60, minTime: ' ...

invoke the modal function from a separate React file

I am currently studying react and nextjs. I am experimenting with calling a modal from another file but unfortunately it's not functioning as expected. Here is the code I used: Signin.js import { Modal } from "react-bootstrap"; import { u ...

Troubleshooting Bootstrap 4 Modal in JavaScript and Vue: Uncaught ReferenceError: $ is not defined

I'm struggling to figure out how to trigger a Bootstrap 4 modal from my Vue 3 app using JavaScript. Whenever I try to launch the modal, I keep encountering this error message: $ is not defined at eval When looking for solutions, I noticed that most a ...

Setting the initial state for your ngrx store application is a crucial step in ensuring the

I'm completely new to ngrx and I'm currently exploring how to handle state management with it. In my application, each staff member (agent) is associated with a group of customers. I'm struggling to define the initial state for each agent ob ...

Utilizing React for incorporating HTML5 canvas gradients

I'm currently working on an app that allows users to generate a background gradient with two distinct colors using React. The issue I'm facing is that while the first color appears correctly, the second color ends up looking more like a solid col ...