node.jstypescriptcypressinvokepageobjects

Cypress.io - How do I wait for the result of a method that returns an invoked attribute?


I have a method that gets the href-attribute of an element hrefAppTheme and checks if its one of the values of the string-array appThemes:

describe('...',() => {
    it('should ...', () => {
      (...)
      let defaultAppTheme = '';
      onMenuPage.hrefAppTheme.invoke('attr', 'href')
        .then(hrefVal => {
          if (typeof hrefVal !== 'undefined') {
            expect(hrefVal).to.be.oneOf(onMenuPage.appThemes);
            defaultAppTheme = hrefVal;

      //referencing the variable 'defaultAppTheme' further below...
    }
  });

It is safe to assume that this.hrefAppTheme.invoke('attr', 'href') always returns one of the user-themes (a string), because the code above works reliably.

Since it turned out that I need to use that logic in several different places, I want to extract it into a method and put it into the page-object-class. This is what I've got:

export default class MenuPage {
  (...)
  getUserTheme(): string {
    let userTheme = '';
    cy.then(() => {
      this.hrefAppTheme.invoke('attr', 'href')
        .then(resVal => {
          if (typeof resVal !== 'undefined') {
            userTheme = resVal;
          }
        });
    });
    return userTheme;
  }

I figured that I need to use the cy.wrap()-method to assign the string that is being returned:

describe('...',() => {
    it('should ...', () => {
      (...)
      let defaultAppTheme = '';
      cy.wrap(onMenuPage.getUserTheme()).then(resVal => {
        defaultAppTheme = resVal;
        expect(defaultAppTheme).to.be.oneOf(onMenuPage.appThemes);
      });

      //referencing the variable 'defaultAppTheme' further below...

However, there is something wrong with this, because the value being returned is always an empty string, not the resolved-value:

AssertionError: expected '' to be one of [ 'theme-light.css', 'theme-dark.css' ]
      + expected - actual

      +[ 'theme-light.css', 'theme-dark.css' ]

waiting for the result of an invoked attribute causes an error

How do you return the resolve-value of myWebelement.invoke('attr', 'href').then(hrefVal => {(...), and how can you assign it to a variable by calling this method?

Or is there a better approach to extract the working code into a method?


Solution

  • The this.hrefAppTheme is already a Chainer, since it can use .invoke('attr', 'href') after it. You can simplify by just returning it.

    The .then(resVal...) that follows is all about modifying the resVal. Move your userTheme var inside there and return it. The return value modifies the result of the previous chainers.

    export default class MenuPage {
      (...)
      getUserTheme(): Chainable<string> {
        
        return this.hrefAppTheme.invoke('attr','href') // return the whole chain
          .then(resVal => {
            let userTheme = '';
            if (typeof resVal !== 'undefined') {
              userTheme = resVal;
            }
            return userTheme;   // return here to modify the chainer result
          });
      }
    

    Then in the test, just use it like any Custom Command

    describe('...',() => {
        it('should ...', () => {
    
          onMenuPage.getUserTheme().then(defaultAppTheme => {
            expect(defaultAppTheme).to.be.oneOf(onMenuPage.appThemes);
          });
    

    AssertionError: expected '' to be one of [ 'theme-light.css', 'theme-dark.css' ]

    You can probably now see why that error occurs, since the return value is userTheme = '' when the resVal is undefined.

    That can be fixed by returning one on the expcted strings as default:

    export default class MenuPage {
      (...)
      getUserTheme(): Chainable<string> {
        
        return this.hrefAppTheme.invoke('attr','href') 
          .then(resVal => {
            let userTheme = 'theme-light.css';        // change default value
            if (typeof resVal !== 'undefined') {
              userTheme = resVal;
            }
            return userTheme;  
          });
      }