Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.3k views
in Technique[技术] by (71.8m points)

javascript - Chaining functions in Cypress

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 command. (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?
question from:https://stackoverflow.com/questions/65859785/chaining-functions-in-cypress

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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 commands do not return their subjects, they yield them. Remember: Cypress commands are asynchronous and get queued for execution at a later time. During execution, subjects are yielded from one command to the next, and a lot of helpful Cypress code runs between each command 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 commands to be chained onto them (assuming they expect a DOM subject) like .click() or even cy.contains() again.

Each command yields something different. You can check this in the documentation for each command. 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 command, since all of them return cy making every command chainable. Usually you only need to use .cy at the start of a command 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 commands themselves are not going to be executed, they are just going to be added to a queue by Cypress. Cypress will track your commands and run them in order. Each command has a timeout and will wait for the command condition to be satisfied in that time frame.

As the Commands Are Asynchronous section states:

Cypress commands 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 commands 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 recommend reading it.

Summary

In short, with cy. calls you queue up commands 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 command you would like to run that code. You could write all your consecutive commands 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 command 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. commands 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.com')
  // 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 commands to run (queue up)
      if (username) {
        // "queue up" other commands
        cy.contains(username).click()
        cy.get('ul>li').eq(2)
      } else {
        cy.get('My Profile').click()
      }
    })
  // command that doesn't depend on data inside the `then` block
  cy.get('.status').contains('Done')
})

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...