最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Chaining functions in Cypress - Stack Overflow

programmeradmin1浏览0评论

Context

I am having trouble trying to understand when asynchrony in Cypress should be handled by the developer and when not (because it is handled under the hood).

Consider these 2 tests:

A)

it('stackoverflow example',() => {
    cy.get('#soSection').should('contain', 'Stack Overflow').then(() =>{
      cy.get('#soButton').click().then(() => {
        cy.get('#soSection').should('not.contain', 'Stack Overflow');
      });
    });
});

B)

it('stackoverflow example',() => {
  cy.get('#soSection').should('contain', 'Stack Overflow');
  cy.get('#soButton').click();
  cy.get('#soSection').should('not.contain', 'Stack Overflow');
});
  • #soSection will contain 'Stack Overflow' until we click the button. Therefore, both lines should run someway synchronously.
  • (A) uses then which enables you to work with the subject yielded from the previous mand. (B) does not use it.

What I've tried so far

I've run both (A) and (B) tests. Both methods work well and behave the same way.


Doubts

  1. If we needed to use the previous subject then we should only use the method (A) with chainables. Are there any other cases where we MUST use chainables like in (A) example?
  2. Look at the 3 lines of code in (B). We expect those 3 lines run in that order to success. However, Cypress claim to run asynchronously. Therefore, how can we be sure those 3 lines will run in the order expected?

Context

I am having trouble trying to understand when asynchrony in Cypress should be handled by the developer and when not (because it is handled under the hood).

Consider these 2 tests:

A)

it('stackoverflow example',() => {
    cy.get('#soSection').should('contain', 'Stack Overflow').then(() =>{
      cy.get('#soButton').click().then(() => {
        cy.get('#soSection').should('not.contain', 'Stack Overflow');
      });
    });
});

B)

it('stackoverflow example',() => {
  cy.get('#soSection').should('contain', 'Stack Overflow');
  cy.get('#soButton').click();
  cy.get('#soSection').should('not.contain', 'Stack Overflow');
});
  • #soSection will contain 'Stack Overflow' until we click the button. Therefore, both lines should run someway synchronously.
  • (A) uses then which enables you to work with the subject yielded from the previous mand. (B) does not use it.

What I've tried so far

I've run both (A) and (B) tests. Both methods work well and behave the same way.


Doubts

  1. If we needed to use the previous subject then we should only use the method (A) with chainables. Are there any other cases where we MUST use chainables like in (A) example?
  2. Look at the 3 lines of code in (B). We expect those 3 lines run in that order to success. However, Cypress claim to run asynchronously. Therefore, how can we be sure those 3 lines will run in the order expected?
Share Improve this question asked Jan 23, 2021 at 13:32 fjplaurrfjplaurr 1,9503 gold badges21 silver badges39 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 4

You should read the page called Introduction to Cypress from the Official documentation, or more generally everything under the "Core Concepts" menu. Most of these topics and concepts are explained there.

Command yields

Your assumption is correct, if you need the previous subject you need to use then since Cypress is async and doesn't return the subject. Instead it yields it. This is stated as a core concept of subject management:

Cypress mands do not return their subjects, they yield them. Remember: Cypress mands are asynchronous and get queued for execution at a later time. During execution, subjects are yielded from one mand to the next, and a lot of helpful Cypress code runs between each mand to ensure everything is in order.

The subject management section shows yielding examples:

Some methods yield null and thus cannot be chained, such as cy.clearCookies().

Some methods, such as cy.get() or cy.contains(), yield a DOM element, allowing further mands to be chained onto them (assuming they expect a DOM subject) like .click() or even cy.contains() again.

Each mand yields something different. You can check this in the documentation for each mand. For example, cy.children's yield section states that it returns a DOM element.

Technically you doesn't even need to add cy. before each mand, since all of them return cy making every mand chainable. Usually you only need to use .cy at the start of a mand chain (like in the beginning inside a then block). This is more of a code style question.

Command execution

When you run the 3 cy.get lines the mands themselves are not going to be executed, they are just going to be added to a queue by Cypress. Cypress will track your mands and run them in order. Each mand has a timeout and will wait for the mand condition to be satisfied in that time frame.

As the Commands Are Asynchronous section states:

Cypress mands don’t do anything at the moment they are invoked, but rather enqueue themselves to be run later. This is what we mean when we say Cypress mands are asynchronous.

Technically I should cite the whole page of the documentation because it is really well structured, concise and detailed with a lot of example code. I highly remend reading it.

Summary

In short, with cy. calls you queue up mands to run for Cypress. If you need to run your own synchronous code, or need the yielded subject you should add a .then() after the Cypress mand you would like to run that code. You could write all your consecutive mands in a .then() but it is unnecessary, you just recreate the JS then Christmas tree of doom AKA the callback hell. So only use then if you need the yielded subject of the previous mand or want to inject synchronous code. Of course, after some synchronous code (like an if condition) you need to call cy. again and they will be queued up inside that then. Whether you add your remaining cy. mands inside the then or one or more levels above, depends on if you need to work on top of the stuff happening inside the given then block or how you prefer to style your code. Here is an example from the docs modified for demonstration:

it('test', () => {
  cy.visit('https://app.')
  // these lines will be queued up and will be run by Cypress in order
  cy.get('ul>li').eq(4)
  cy.get('.nav').contains('About')
  // we want to use the .user field's value
  cy.get('.user')
    .then(($el) => {
      // this line evaluates after the .then() executes
      let username = $el.text()
      // synchronous code to decide which mands to run (queue up)
      if (username) {
        // "queue up" other mands
        cy.contains(username).click()
        cy.get('ul>li').eq(2)
      } else {
        cy.get('My Profile').click()
      }
    })
  // mand that doesn't depend on data inside the `then` block
  cy.get('.status').contains('Done')
})
发布评论

评论列表(0)

  1. 暂无评论