skip to Main Content

I’m getting the following error attempting to automate my login procedure into a page object.

this.signIn is not a function

Here is my page object:

export class OktaLoginPage {
  oktaServer = Cypress.env('oktaServer'); 

  get oktaServer() {
    return this.oktaServer;
  }

  set oktaServer(server) {
    this.oktaServer = server;
  }

  login(username, password) {
    const loginArgs = {username: username, password: password};
    if (this.domainsMatch(this.oktaServer)) {
      this.signIn(username, password);
    } else {
      cy.origin(this.oktaServer, {args: loginArgs}, ({username, password}) => {
        this.signIn(username, password);
      })
    }
  }

  signIn(username, password) {
    cy.contains('h2', 'Sign In');
    cy.get('input[name="username"]').clear();
    cy.get('input[name="username"]').type(username);
    cy.get('input[type="submit"]').invoke('attr', 'value').then(btnValue => {
      if (btnValue === 'Next') {
        cy.get('input[type="submit"]').click();
      }
    });
    cy.get('input[type="password"]').clear().type(password);
    cy.get('input[type="submit"]').click();
  }

    // domainsMatch not shown
}

If I copy the code from the signIn method into the cy.orgin block it works fine. There is some issue with making a class method call inside an origin block. The cypress docs indicate that I must define the class/object within the origin block. I’ve tried a few different ways to get this to work including:

const OktaLoginPage = Cypress.require('../../support/page-objects/oktaLogin.po');
const login = new OktaLoginPage();
login.signIn(username, password);

Need some assistance with this. thanks.

2

Answers


  1. Chosen as BEST ANSWER

    The following is my solution. Creating a cypress command would also work but this it what I was looking for keeping login in one place:

    export class OktaLoginPage {
      oktaServer = Cypress.env('oktaServer'); 
    
      get oktaServer() {
        return this.oktaServer;
      }
    
      set oktaServer(server) {
        this.oktaServer = server;
      }
    
      login(username, password) {
        const loginArgs = {username: username, password: password};
        if (this.domainsMatch(this.oktaServer)) {
          this.oktaSignIn(username, password);
        } else {
          cy.origin(this.oktaServer, {args: loginArgs}, ({username, password}) => {
            // https://docs.cypress.io/api/commands/origin
            const Login = Cypress.require('./oktaLogin.po'); // since orgin does not have access to JavaScript context
            const login = new Login.OktaLoginPage(); // this technique is documented in Cypress docs
            login.oktaSignIn(username, password);
          })
        }
      }
    
      oktaSignIn(username, password) {
        cy.contains('h2', 'Sign In');
        cy.get('input[name="username"]').clear();
        cy.get('input[name="username"]').type(username);
        cy.get('input[type="submit"]').invoke('attr', 'value').then(btnValue => {
          if (btnValue === 'Next') {
            cy.get('input[type="submit"]').click();
          }
        });
        cy.get('input[type="password"]').clear().type(password);
        cy.get('input[type="submit"]').click();
      }
    
        // domainsMatch not shown
    }
    

    As mentioned, this is a callback so I do not have the context I need to use "this".


  2. The error is coming from this part of the code

    cy.origin(this.oktaServer, {args: loginArgs}, ({username, password}) => {
    
      // cannot access closure variables here, including "this"
      this.signIn(username, password);
    })
    

    It’s a restriction of the cy.origin() command. See the documentation at origin – Arguments

    The args object is the only mechanism via which data may be injected into the callback, the callback is not a closure and does not retain access to the JavaScript context in which it was declared. Values passed into args must be serializable.


    Sharing code in cy.origin()

    It doesn’t gel very well with the page object format, but you can use Cypress.require() to share code.

    The example Sharing Code – Custom commands shows how to do it.

    cypress/support/commands.js

    Cypress.Commands.add('signIn', (username, password) => {
        cy.contains('h2', 'Sign In');
        cy.get('input[name="username"]').clear();
        cy.get('input[name="username"]').type(username);
        cy.get('input[type="submit"]').invoke('attr', 'value').then(btnValue => {
          if (btnValue === 'Next') {
            cy.get('input[type="submit"]').click();
          }
        });
        cy.get('input[type="password"]').clear().type(password);
        cy.get('input[type="submit"]').click();
    })
    

    Test

    before(() => {
      const oktaDomain =  Cypress.env('oktaServer')
      cy.origin(oktaDomain, () => {
        Cypress.require('../support/commands')  // commands to be used inside okta domain
      })
    })
    
    it('testing...', () => {
    
    

    Page object

      login(username, password) {
        const loginArgs = {username: username, password: password};
        if (this.domainsMatch(this.oktaServer)) {
          cy.signIn(username, password);
        } else {
          cy.origin(this.oktaServer, {args: loginArgs}, ({username, password}) => {
            cy.signIn(username, password);
          })
        }
      }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search