Cypress: Removing aliases which were set in before hooks

Created on 4 Aug 2017  路  9Comments  路  Source: cypress-io/cypress

Current behavior:

If I set an alias in before hook, it will be accessible only for first test in the current context.

Desired behavior:

Alias which is set in before hook should be accessible for all tests in the current context.

Test code:

This code produces error: cy.get() could not find a registered alias for: '@gaStub'. for the second test.

describe('Google Analytics', () => {
  before(() => {
    cy.visit('http://localhost:8080', {
      onBeforeLoad: (contentWindow) => {
        Object.defineProperties(contentWindow, {
          'ga': {
            value: cy.stub().as('gaStub'),
            writable: false
          }
        })
      }
    })
  })

  it('should perform logging on first page load', () => {
    cy.get('@gaStub').should('have.been.called')
  })

  it('should report pageview', () => {
    cy.get('@gaStub').should('have.been.called.with', 'send', 'pageview')
  })
})
duplicate question

Most helpful comment

The behaviour implemented by Cypress of clearing state between tests forces repetition of steps.

Imagine a test composed of 20 ordered steps.

NB: This is the overwhelming majority of use cases in most applications because the user has to have done something, in order for the user to do something else.

With this behaviour, if one wants to verify the state of the application after each step of the test, ALL previous step need to be repeated.

If state was not being cleared after each test, then no repetition would be needed.

The way in which mocha (on which Cypress is based) executes describe, it, before, beforeEach, afterEach and after blocks makes it clear that whatever pre-conditions were setup in the before block should persist for the entirety of the describe block. Whatever post-conditions are expected to be cleaned up after the describe block should be present in the after block. Similarly, beforeEach and afterEach allow pre and post conditions to be set for all it blocks within that particular describe block.

It is also significant that all describe and it blocks run in sequence, and so, the ability to encode an ordered set of tests is trivially available in mocha.

If there are pre/post-conditions to tests that are truly independent, then the user can either use beforeEach/afterEach or use a separate describe block.

The way Cypress maintains state currently, is counter intuitive, and does not conform to what vanilla mocha implements.

Anything done in a before block should remain for the entirety of the describe block.

All 9 comments

Can confirm, this is bug in 0.19.4 and upcoming 0.20.0. Simpler reproducible example in Kitchen Sink App:

describe('Kitchen Sink Tests', function(){
  before(function(){
    cy.visit('/commands/querying')
    cy.get('.nav').as('mainNav')
  })

  context('Aliases', function(){
    it('first test', function(){
      cy.get('@mainNav')
    })

    it('second test', function(){
      cy.get('@mainNav')
    })
  })
})

I looked at this, and this is not a bug, this is the correct and intended behavior of hooks.

Code in a before hook only runs once and once only. When it reaches the 2nd test: the aliases have been reset since Cypress clears state in between test runs. Maintaining state in between tests is an anti-pattern and can lead to unexpected results, as the state can be modified between each test.

I unfortunately do not have a better document to link to that explains this, as we need to improve our documentation concerning these "lifecycle events".

Regardless of our strong opinions on not maintaining state in between tests, there is a proposal within Cypress to expose configuration to allow users to disable some of these "lifecycle events", For example, you would be able to say: clearAliases: false for the lifecycle of your tests.

For now, all I can suggest is moving the cy.visit() command to be within a beforeEach hook. The problem with this is that it often slows down the test suite waiting for the cy.visit() to resolve. There are some ways that you could make this process faster:

  1. Do not load any unnecessary third party scripts when in tests. Since the cy.visit() does not resolve until it's load event is fired. You can conditionally check if your application is running within Cypress and choose what resources to load.

  2. Use cy.request() when possible to check the DOM content/state of your application. Sometimes a simple request of the html file can satisfy what you want to test. For example: cy.request('/admin').its('body').should('include', '<h1>Admin</h1>'). This does not require waiting for all dependencies to load.

_First off, I love Cypress and am grateful for all the work the team has put into it._

The existing documentation seems to contradict what is described in the discussion here as the intentional clearing of state between tests. I agree 100% that tests should not share state. However, what the Cypress documentation currently says to explain shared contexts (in relation to aliases) is:

Mocha automatically shares contexts for us across all applicable hooks for each test. Additionally these aliases and properties are automatically cleaned up after each test.

Emphasis on across all applicable hooks for each test. A user could read that documentation and reasonably expect that within _any_ specific test she or he should have access to the "context" from the beforeEach and the before hooks associated with that test (and any parent blocks), even if the test is not the first in the block.

I understand why visiting a page and inspecting the DOM in a before hook, aliasing DOM elements, and then expecting those aliases for be good in multiple tests would be ill advised... but what about selecting specific data from fixtures in a before hook that sets the stage for all tests in the block?

@jennifer-shehane
I think, aliases from before hook should be available through all tests.
An example which I have: usually you are preparing your db in before hook as you want to add only one element used for the testing, then you want to create an alias of this element to use it in each test...but this is not possible right now.
However there is a workaround here: using backflips! Which is against your documentation: https://docs.cypress.io/guides/core-concepts/variables-and-aliases.html#Return-Values
Using backflips works; so seems you are not clearing the state of let variable between tests which is differenet than what you wrote here:

Cypress clears state in between test runs

Regarding below:

Regardless of our strong opinions on not maintaining state in between tests, there is a proposal within Cypress to expose configuration to allow users to disable some of these "lifecycle events", For example, you would be able to say: clearAliases: false for the lifecycle of your tests.

Is this already in some "in progress" state?

OK seems I have found a solution for that; you need to put your tests in separate contexts like so:

describe('Describe', function () { 
  before(function () {
    addDbElementAndReturnItsKey().then(elementKey => {
      cy.wrap(elementKey).as('elementKey')
    })
  })
  context('Context1', function () {
    it('Test1Context1', function () {
      // You have access to elementKey by this.elementKey
    })
  })
  context('Context2', function () {
    it('Test1Context2', function () {
      // You have access to elementKey by this.elementKey
    })
  })
})

I am not sure if this is Mocha and Cypress specific or a bug:) but it works!

@kapalkat You can follow the LifeCycle Events issue here: https://github.com/cypress-io/cypress/issues/686

I can see that this issue is closed, but I still would like to comment.

I am a QA engineer writing test automation for 7 years now and I am new to Cypress. Behaviour, such as sharing context from before hooks with all subsequent tests within a block, is almost standard for so many other tools that the current behaviour is really confusing. I see that Cypress team is taking their own way, but there are many people who would still need to use other tools beside it. Always keeping in mind that Cypress is "different" is really exhausting and I just wish it would work the way people would expect more often than not. It is a tool and it should help, it shouldn't protect me from using anti-patterns or whatever.

The behaviour implemented by Cypress of clearing state between tests forces repetition of steps.

Imagine a test composed of 20 ordered steps.

NB: This is the overwhelming majority of use cases in most applications because the user has to have done something, in order for the user to do something else.

With this behaviour, if one wants to verify the state of the application after each step of the test, ALL previous step need to be repeated.

If state was not being cleared after each test, then no repetition would be needed.

The way in which mocha (on which Cypress is based) executes describe, it, before, beforeEach, afterEach and after blocks makes it clear that whatever pre-conditions were setup in the before block should persist for the entirety of the describe block. Whatever post-conditions are expected to be cleaned up after the describe block should be present in the after block. Similarly, beforeEach and afterEach allow pre and post conditions to be set for all it blocks within that particular describe block.

It is also significant that all describe and it blocks run in sequence, and so, the ability to encode an ordered set of tests is trivially available in mocha.

If there are pre/post-conditions to tests that are truly independent, then the user can either use beforeEach/afterEach or use a separate describe block.

The way Cypress maintains state currently, is counter intuitive, and does not conform to what vanilla mocha implements.

Anything done in a before block should remain for the entirety of the describe block.

Please follow the LifeCycle Events issue and 馃憤 support for that issue there. That proposes more control of when and how state is cleared in between tests. https://github.com/cypress-io/cypress/issues/686

We'll be locking conversation on this issue as it is closed and not being followed. Please comment in #686

Was this page helpful?
0 / 5 - 0 ratings