Search code examples
javascriptbrowserautomationcypress

Failing on trying to use a function as a command in Cypress


I'm starting to work with Cypress and programming in general. I'm working on a very simple personal project executing TCs in a webpage.

I have this piece of code in 'registrationPage.js'

/// <reference types="cypress" />
import registrationData from '../fixtures/registrationData.json'
import AccountInfoPage from './accountInfoPage'
import commands from '../support/commands'

class RegistrationPage{

    //Fill out Registration form with valid credentials
    fillRegistrationForm(){

        const userData = cy.getRegistrationData()
        
        cy.getRegistrationData()
        cy.get('[title="First Name"]').type(userData.firstname)
        cy.get('#lastname').type(userData)
        cy.get('[autocomplete="email"]').type(userData)
        cy.get('#password').type(userData)
        cy.get('#password-confirmation').type(userData)
        
    }

    //Click on submit button of Registration form
    submitRegistrationForm(){
        cy.contains('button', 'Create an Account').click()
    }

    //Verify the registration was sucesfull and user was created
    verifyRegistration(){

        const accountInfoPage = new AccountInfoPage();
        const validUser = registrationData.validData.user1

        cy.contains('Thank you for registering with Main Website Store.').should('be.visible')
        cy.get('#block-collapsible-nav').find('li').contains('Account Information').click()

        accountInfoPage.getFirstNameInput().invoke('val')
        .should('equal', validUser.firstname)

        accountInfoPage.getLastNameInput().invoke('val')
        .should('equal', validUser.lastname)

    }
}
export default RegistrationPage;

Since this is a logic that I wan to re-use (only the part of getting the data, not typing it) I thought it would be a good option to have it in 'commands.js' file as a Command.

I have a data set located in registrationData.json but I also wanted to use environment variables in case I want to quickly override what the data set has (I would also like to hear if there is better options to handle this)

I put the logic into commands.js:

import registrationData from '../fixtures/registrationData.json'
import loginData from '../fixtures/loginData.json'

Cypress.Commands.add('getRegistrationData', () => {
    const validUser = Cypress.env('validUser') || registrationData.validData.user1

    return {
        firstname: Cypress.env('firstname') || validUser.firstname,
        lastname: Cypress.env('lastname') || validUser.lastname,
        email: Cypress.env('email') || validUser.email,
        password: Cypress.env('password') || validUser.password,
        passConfirm: Cypress.env('passConfirm') || validUser.passConfirm
    }
})

But when I try to make use of it in registration.spec.js in this way:

import RegistrationPage from "../page-objects/registrationPage"
import LoginPage from "../page-objects/loginPage"

describe('Registration page scenario mapping', () => {

    it.only('Executes a sucessfull registration in the form', () => {

        const registrationPage = new RegistrationPage()
        cy.visit('customer/account/create')

        registrationPage.fillRegistrationForm()
        registrationPage.submitRegistrationForm()
        registrationPage.verifyRegistration()
        
    })

    it('login', function(){
        const loginPage = new LoginPage()

        cy.visit('/')
        cy.contains('a', 'Sign In').should('be.visible').click()
        cy.contains('h1', 'Customer Login').should('be.visible')
        loginPage.fillLoginForm()
    })

})

It gives me an error in registrationPage.js saying that the values withing the .type() are undefined. I tried with validUser.firstname instead but same error and if I use just 'validUser' as value it says that is an object and you can't type that. I don't understand why if I put validUser.firstname doesn't recognize the value.

CypressError
cy.type() can only accept a string or number. You passed in: undefinedLearn more
cypress/page-objects/registrationPage.js:14:40
  12 |         
  13 |         cy.getRegistrationData()
> 14 |         cy.get('[title="First Name"]').type(userData.firstname)
     |                                        ^
  15 |         cy.get('#lastname').type(userData)
  16 |         cy.get('[autocomplete="email"]').type(userData)
  17 |         cy.get('#password').type(userData)

Sorry if I made the question in the wrongway or something, I hope someone can help me out. Thanks


Solution

  • The difference between a function and a Custom Command is that a function returns a value which can be assigned to a variable, but a Custom Command passes the return value down the chain.

    Function

    const getRegistrationData = () => {
      ...
      return ...
    })
    
    const registrationData = getRegistrationData()
    

    Custom Command

    Cypress.Commands.add('getRegistrationData', () => {
      ...
      return ...
    })
    
    cy.getRegistrationData().then((registrationData) => {  // access value here
      ...
    })
    

    So to use the custom command in the registration page,

    class RegistrationPage{
    
      fillRegistrationForm() {
    
        cy.getRegistrationData().then((userData) => {
          cy.get('[title="First Name"]').type(userData.firstname)
          cy.get('#lastname').type(userData)
          cy.get('[autocomplete="email"]').type(userData)
          cy.get('#password').type(userData)
          cy.get('#password-confirmation').type(userData)
        })
    

    Also, you don't need import commands from '../support/commands' in the Registration page because custom commands are set globally, in the same way as built-in commands like cy.get() can just be used anywhere in the test or page object code without importing anything.