Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How exactly do before and beforeEach work in Cypress?

I think I am missing something about the way the before and beforeEach functions work in Cypress. I have a spec file that loads data from a fixture in the before method. Some of that data is used in the before function, and then again in the beforeEach function, as well as in the actual tests. The spec file contains 2 tests. The first test executes as expected. The second fails because the beforeEach says that one of the values from the fixture is undefined.

My expectation is that if I load data from a fixture in the before method it should be available for all tests in the spec file.

When executing the 'Check the state of the button bar' test the window.console.log(this.user_data) in the beforeEach function outputs the user_data as expected.

When executing the 'Submit form' test the window.console.log(this.user_data) in the beforeEach function outputs undefined and the test stops.

What am I missing here?

describe('Customer Profile', () => {

    before(function () {
        window.console.log('Enter the before function')
        // Load the fixture data. Its asynchronous so if we want to use it right here and now
        // we have to put the things that use inside a callback to be executed after the data
        // loaded. 
        cy.fixture('user').as('user_data').then(function (){
            window.console.log('Fixture has loaded the user data')
            cy.visit('/LaunchPad/login')
            // Fill out the login form
            cy.get('input[name="username"]').type(this.user_data.username)
            cy.get('input[name="password"]').type(this.user_data.password)
            cy.get('button[type="submit"]').click()
        })
    })

    beforeEach(function(){
        window.console.log('Enter the beforeEach function')
        window.console.log(this.user_data)
        // Preserve the cookies for all tests in this suite
        Cypress.Cookies.preserveOnce('SESSION')
        // Open the profile view
        cy.visit('/Manager/'+this.user_data.org+'/config/company-profile')
    })

    it('Check the state of the button bar', function(){
        window.console.log('Running test of button bar')
        cy.get('section.content-header > h1').contains('Company Profile Details')
        // Check the state of the action bar and its buttons
        cy.get('section.action-bar').get('label.btn.btn-sm.btn-primary').contains('Save')
            .should('have.attr', 'for', 'submit-form')
            .should('have.attr', 'tabindex', '0')

        cy.get('section.action-bar').get('#resetButton').contains('Reset')
            .should('have.attr', 'type', 'reset')
            .should('have.attr', 'value', 'Reset')
            .should('have.class', 'btn btn-sm btn-default')

        cy.get('section.action-bar').get('a.btn.btn-sm.btn-default').contains('Cancel')
            .should('have.attr', 'href', '/Manager/'+this.user_data.org+'/')

        cy.get('section.action-bar').get('a').contains('Delete').should('not.exist')

    })

    // This form has no required fields and no validation. So just pick a value or two
    // submit the form and verify the banner is correct
    it('Submit form', function(){
        window.console.log('Running the submit form test')
        cy.fixture('company_profile').as('company_profile')
        cy.get('#companyProfileDto.name').type(this.company_profile.name)
    })


})

UPDATE After doing some more reading based on what Carlos Alfredo responded with I came up this. 1. I still have to visit the login page. We use csrf and OATH and trying to get working example is just taking too much time. 2. I have to use whitelisting for the session cookie because preserveOnce does not work at all.

Here are the files I have now. It visits the login page once and gets the session cookies setup. The two tests proceed as expected.

support/index.js

before(function(){
    cy.login('bob', 'password')
    cy.fixture('user').as('user_data')
    cy.fixture('company_profile').as('company_profile')
})

beforeEach(function(){
    window.console.log('Enter the global beforeEach function')
    // Load the fixture data

})

support/commands.js

/*
    We visit the login form despite what the best practise recommendation is because
    we have OAUTH redirects and a CSRF token to deal with. Since the majority of the
    examples and working use cases provided dont deal with those scenarios this is 
    the best I can do at the moment. 
*/
Cypress.Commands.add('login', (username, password, options = {}) => {
    cy.visit('/LaunchPad/login')
    // Fill out the login form
    cy.get('input[name="username"]').type(username)
    cy.get('input[name="password"]').type(password)
    cy.get('button[type="submit"]').click()
})
/*
    We are white listing the cookie because Cypress.Cookies.preserveOnce('SESSION') 
    does not work. https://github.com/cypress-io/cypress/issues/2952

    Because we are forcing Cypress to not clear the cookies, you will have to close
    the test window after the suite is completed other wise the Vision360 apps will
    think your session is alive.
*/
Cypress.Cookies.defaults({
    whitelist: 'SESSION'
})

integration/customer_profile/customer_profile_spec.js

describe('Customer Profile', () => {

    it('Check the state of the button bar', function(){
        window.console.log('Running test of button bar')

        cy.visit('/Manager/'+this.user_data.org+'/config/company-profile')

        cy.get('section.content-header > h1').contains('Company Profile Details')
        // Check the state of the action bar and its buttons
        cy.get('section.action-bar').get('label.btn.btn-sm.btn-primary').contains('Save')
            .should('have.attr', 'for', 'submit-form')
            .should('have.attr', 'tabindex', '0')

        cy.get('section.action-bar').get('#resetButton').contains('Reset')
            .should('have.attr', 'type', 'reset')
            .should('have.attr', 'value', 'Reset')
            .should('have.class', 'btn btn-sm btn-default')

        cy.get('section.action-bar').get('a.btn.btn-sm.btn-default').contains('Cancel')
            .should('have.attr', 'href', '/Manager/'+this.user_data.org+'/')

        cy.get('section.action-bar').get('a').contains('Delete').should('not.exist')

    })

    // This form has no required fields and no validation. So just pick a value or two
    // submit the form and verify the banner is correct
    it('Submit form', function(){
        window.console.log('Running the submit form test')
        cy.visit('/Manager/'+this.user_data.org+'/config/company-profile')

        // Fill and submit the form
        cy.get('input#companyProfileDto\\.name').clear().type(this.company_profile.name)
        cy.get('section.action-bar').get('label.btn.btn-sm.btn-primary').contains('Save').click()

        // Check the response
        cy.get('.callout-success').contains('Your changes are saved.')
        cy.get('input#companyProfileDto\\.name').should('have.value', this.company_profile.name)
    })

})
like image 654
RhythmicDevil Avatar asked Apr 03 '19 18:04

RhythmicDevil


Video Answer


3 Answers

I ran into a problem almost like yours with before.

before() runs before the next block of code.

describe("Some test", function() {
    before(function() {
        //something ...
        cy.request('POST', loginUrl, loginInformation).its('body').as('currentUser')
    })

    // this.currentUser exists here
    it("Should foo", function() {
    })

    // this.currentUser doesn't exist here
    it("Should bar", function() {
    })
})

I fixed my problem moving the before() statement out from the describe scope.

before(function() {
    //something ...
    cy.request('POST', loginUrl, loginInformation).its('body').as('currentUser')
})

describe("Some test", function() {     
    // this.currentUser exists here
    it("Should foo", function() {
    })

    // this.currentUser also exists here
    it("Should bar", function() {
    })
})

I hope this can help you solve your problem.

like image 61
JDTheOne Avatar answered Oct 01 '22 03:10

JDTheOne


It looks like cypress is cleaning your fixtures for each test.

From Cypress guides re: "dangling state"

... In fact, Cypress does not clean up its own internal state when the test ends. We want you to have dangling state at the end of the test! Things like stubs, spies, even routes are not removed at the end of the test. This means your application will behave identically while it is running Cypress commands or when you manually work with it after a test ends.

There's nothing explicit about fixtures in that page but, it looks like its happening and fixtures are cleaned too. Thats the beforeEach() works in the first test (Check the state of the button bar), because it runs exactly after the before(). For the second test all that has been setted in the before hook is gone and now the beforeEach() is trying to get fixtures that were never defined.

Hope it helps.

Some other suggestions:

  • Is a good practice to do your login as a custom command skipping the UI, using just the request.
like image 27
Carlos Alfredo Avatar answered Oct 01 '22 01:10

Carlos Alfredo


  • before() runs once before all your code.
  • beforeEach() runs before each of your code blocks.

As such any code, you put in the before() function will only run once and due to cypress clearing states before each test, any code that is put in the before() function will be cleared.

You need to put the code in the beforeEach() function to be able to use it in subsequent blocks.

If you want to make a fixture accessible to all its blocks you need to put it in the beforeEach() function.

A simpler way to achieve this for JSON fixtures is to use an import statement, for example:

import user from '../../fixtures/users.json'

In your it block you can call it:

it('do something with the fixture',()=>{
  cy.log(users)
})
like image 45
dheveshp Avatar answered Oct 01 '22 03:10

dheveshp