Cypress : Setting a variable from the result of a task

I need to retrieve an url from an email that was sent during the begining of a cypress scenario.

Waiting for the email and retrieving the url inside it is an asynchronous task, so I created a cypress plugin called “readMail” : it will take the email address as input, wait for the gmail api to actually have an email whose recipient is this address, then read the mail and return a promise resolving an url contained in the body of the email. This process works fine, because logs from the plugin do correctly show the extracted url.

My issue is to make this extractedUrl available for later use as a param e.g. cy.visit(extractedUrl) : outside the body of task(...).then(...), the variable is still not set.

Here is the code :

it('should generate correct email', () => {
    const recipientEmail: string = '[email protected]';
    const extractedUrl:string = '';

    // ....scenario generating a email to '[email protected]'.....

    cy.task('readMail', recipientEmail) // plugin logs "https://some.url.com" after a few seconds
    .then((result:any) => {
        extractedUrl= result;
        console.log(result); // logs "https://some.url.com" in the terminal
        cy.log("1:" + extractedUrl); // logs "1:https://some.url.com" in cypress UI
    });
    cy.log("2:"+ extractedUrl); // logs "2:" in cypress UI
    console.log(extractedUrl); // logs nothing
    cy.visit(extractedUrl);
});

I am probably missing something about how cypress handles async… I tried several different things : cy.wrap and the cypress-wait-until plugin but couldn’t find any way to make those work.

Actually I found one way which was to declare extractedUrl outside the test and begin a new test it('...', () => {...}) : then, extractedUrl actually has the correct value at the beginning of the next test, but that is not ideal and I want to understand why. Thanks for your help !

Answer

TL;DR; put your entire code inside the then callback

it('should generate correct email', () => {
    const recipientEmail: string = '[email protected]';
    const extractedUrl:string = '';

    // ....scenario generating a email to '[email protected]'.....
    // plugin logs "https://some.url.com" after a few seconds
    cy.task('readMail', recipientEmail).then((result:any) => {
        extractedUrl= result;
        console.log(result); // logs "https://some.url.com" in the terminal
        cy.log("1:" + extractedUrl); // logs "1:https://some.url.com" in cypress UI
        cy.log("2:"+ extractedUrl); // logs "2:" in cypress UI
        console.log(extractedUrl); // logs nothing
        cy.visit(extractedUrl);
    });

});

Contrary to what it seems, Cypress commands do not return promises, they return promise-like object and the execution queue is handle by the Cypress framework. That’s the main reason some actions that would be logical when handling promises will not work.

More info here: Await-ing Cypress Chains #1417

You could also try cypress-promise, that is a plugin to change cypress promise-like objects into actual promises. More info here: Cypress.io — Using async and await by Nicholas Boll

UPDATE #1:

You could also use alias in order to access the value:

it('should generate correct email', () => {
    const recipientEmail: string = '[email protected]';
    const extractedUrl:string = '';

    // ....scenario generating a email to '[email protected]'.....
    // plugin logs "https://some.url.com" after a few seconds
    cy.task('readMail', recipientEmail).then((result:any) => {
        extractedUrl= result;
        cy.wrap(result).as('extractedUrl');
        console.log(result); // logs "https://some.url.com" in the terminal
        cy.log("1:" + extractedUrl); // logs "1:https://some.url.com" in cypress UI
    });

    cy.get('@extractedUrl').then((extractedUrl) => {
        cy.log("2:"+ extractedUrl); // logs "2:" in cypress UI
        console.log(extractedUrl); // logs nothing
        cy.visit(extractedUrl);
    })

});

Leave a Reply

Your email address will not be published. Required fields are marked *