Docs. Cypress. IO/guides/core…

describe('Post Resource'.() = > {
  it('Creating a New Post'.() = > {
    cy.visit('/posts/new') / / 1.

    cy.get('input.post-title') / / 2.
      .type('My First Post') / / 3.

    cy.get('input.post-body') / / 4.
      .type('Hello, world! ') / / 5.

    cy.contains('Submit') / / 6.
      .click() / / 7.

    cy.url() / / 8.
      .should('include'.'/posts/my-first-post')

    cy.get('h1') / / 9.
      .should('contain'.'My First Post')})})Copy the code

The cypress code above looks a lot like a natural language.

Get (‘.my-selector ‘), very similar to jQuery: cy.get(‘.my-selector ‘)

In fact, Cypress itself bundles jQuery:

Support for jquery-like chain calls:

cy.get('#main-content').find('.article').children('img[src^="/static"]').first()
Copy the code

Just one thing in particular:

Ct. get does not return elements to be read synchronously, as jQuery does. Cypress element access, done asynchronously.

Because of jQuery’s synchronous access mechanism, we need to manually check whether the element query API is null after calling it:

// $() returns immediately with an empty collection.
const $myElement = $('.element').first()

// Leads to ugly conditional checks
// and worse - flaky tests!
if ($myElement.length) {
  doSomething($myElement)
}
Copy the code

Cypress’s asynchronous operation, on the other hand, makes the element to be read available only when the result is passed as an argument to the callback function:

cy
  // cy.get() looks for '#element', repeating the query until...
  .get('#element')

  / /... it finds the element!
  // You can now work with it by using .then
  .then(($myElement) = > {
    doSomething($myElement)
  })
Copy the code

In Cypress, when you want to interact with a DOM element directly, call .then() with a callback function that receives the element as its first argument.

That said, Cypress internally encapsulates retry and timeout retry mechanisms for us.

When you want to skip the retry-and-timeout functionality entirely and perform traditional synchronous work, use Cypress.$.

If you want to go back to the jQuery style of reading elements synchronously, use cypress.$.

// Find an element in the document containing the text 'New Post'
cy.contains('New Post')

// Find an element within '.main' containing the text 'New Post'
cy.get('.main').contains('New Post')
Copy the code

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 Cypress command does not return the targets it works on directly, but yields them. The Cypress command is executed asynchronously. The command is inserted into a queue and not executed immediately, but waiting for scheduling. When the command is actually executed, the target object is generated by the previous command and passed into the next command. Between commands, a lot of useful Cypress code is executed to ensure that commands are executed in the same order as they are invoked in Cypress test code.

To work around the need to reference elements, Cypress has a feature known as aliasing. Aliasing helps you to store and save element references for future use.

Cypress provides a mechanism called Aliasing to save element references for future use.

Look at an example:

cy.get('.my-selector')
  .as('myElement') // set the alias using the as command to store the elements returned by get in the custom variable myElement.
  .click()

/* many more actions */

cy.get('@myElement') // re-queries the DOM as before (only if necessary), use @ to reference custom variables
  .click()
Copy the code

Use then to operate on the target of the previous yield command

cy
  // Find the el with id 'some-link'
  .get('#some-link')

  .then(($myElement) = > {
    / /... massage the subject with some arbitrary code

    // grab its href property
    const href = $myElement.prop('href')

    // strip out the 'hash' character and everything after it
    return href.replace(/ / #. (*).' ')
  })
  .then((href) = > {
    // href is now the new subject
    // which we can work with now
  })
Copy the code

The asynchronous execution feature of Cypress

Invoked It is very important to understand that 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.

it('changes the URL when "awesome" is clicked'.() = > {
  cy.visit('/my/resource/path') // Nothing happens yet

  cy.get('.awesome-selector') // Still nothing happening
    .click() // Nope, nothing

  cy.url() // Nothing to see, yet
    .should('include'.'/my/resource/path#awesomeness') // Nada.
})

// Ok, the test function has finished executing...
// We've queued all of these commands and now
// Cypress will begin running them in order!
Copy the code

Cypress doesn’t kick off the browser automation magic until the test function exits.

This is what sets Cypress apart from other front-end automated testing frameworks: Cypress does not trigger the browser’s automatic execution logic until the test function exits.

it('does not work as we expect'.() = > {
  cy.visit('/my/resource/path') // Nothing happens yet

  cy.get('.awesome-selector') // Still nothing happening
    .click() // Nope, nothing

  // Cypress.$ is synchronous, so evaluates immediately
  // there is no element to find yet because
  // the cy.visit() was only queued to visit
  // and did not actually visit the application
  let el = Cypress.$('.new-el') // evaluates immediately as []

  if (el.length) {
    // evaluates immediately as 0
    cy.get('.another-selector')}else {
    // this will always run
    // because the 'el.length' is 0
    // when the code executes
    cy.get('.optional-selector')}})// Ok, the test function has finished executing...
// We've queued all of these commands and now
// Cypress will begin running them in order!
Copy the code

Instead, place the HTML element Evaluation code in the THEN callback:

Each Cypress command (and chain of commands) returns immediately

Each Cypress command (including the command chain) returns immediately after being invoked, without blocking for synchronous execution.

Having only been appended to a queue of commands to be executed at a later time.

These commands are simply added to a command queue, waiting for the Cypress framework to schedule their execution later.

You purposefully cannot do anything useful with the return value from a command. Commands are enqueued and managed entirely behind the scenes.

We can’t do anything useful with the result of executing a command that Cypress returns directly, because the call to the command in the code is actually just queued up for execution. As for when to execute, it is uniformly scheduled by Cypress, which is a black box for Cypress test developers.

We’ve designed our API this way because the DOM is a highly mutable object that constantly goes stale. For Cypress to prevent flake, and know when to proceed, we manage commands in a highly controlled deterministic way.

The reason why the Cypress API is designed this way is that DOM is a mutable object, and the state often goes stale as users operate or interact with it. To avoid flake situations, Cypress follows the thread described above and manages command execution in a highly controlled, deterministic manner.

Here’s an example: the web page displays a random number and stops the test when it jumps to the number 7. If the random number is not 7, reload the page and continue testing.

The following is the wrong Cypress code that will crash the browser:

let found7 = false

while(! found7) {// this schedules an infinite number
  // of "cy.get..." commands, eventually crashing
  // before any of them have a chance to run
  // and set found7 to true
  cy.get('#result')
    .should('not.be.empty')
    .invoke('text')
    .then(parseInt)
    .then((number) = > {
      if (number === 7) {
        found7 = true
        cy.log('lucky **7**')}else {
        cy.reload()
      }
    })
}
Copy the code

The reason: a while loop inserts a huge number of get commands quickly into the task queue (or test chain, to be exact) with no chance of execution.

The above test keeps adding more cy. Get (‘ #result ‘) commands to The test chain without executing any!

The effect of the above code is to keep adding cy.get commands to the test chain in the while loop, but none of them will ever be executed.

The chain of commands keeps growing, but never executes – since the test function never finishes running.

The number of elements in the command queue keeps growing, but it never gets executed because the Cypress code itself is stuck in the while loop.

The while loop never allows Cypress to start executing even The very first cy. Get (…) command.

Even the first cy.get statement in the task queue gets no chance of execution because of the while loop.

Correct way to write it:

  1. Using the recursive
  2. Write the logic for the return after finding 7 in callback.
const checkAndReload = () = > {
  // get the element's text, convert into a number
  cy.get('#result')
    .should('not.be.empty')
    .invoke('text')
    .then(parseInt)
    .then((number) = > {
      // if the expected number is found
      // stop adding any more commands
      if (number === 7) {
        cy.log('lucky **7**')

        return
      }

      // otherwise insert more Cypress commands
      // by calling the function after reload
      cy.wait(500, { log: false })
      cy.reload()
      checkAndReload()
    })
}

cy.visit('public/index.html')
checkAndReload()
Copy the code

Command Is what happens behind the scenes during execution

The following code contains five parts of logic:

it('changes the URL when "awesome" is clicked'.() = > {
  cy.visit('/my/resource/path') / / 1.

  cy.get('.awesome-selector') / / 2.
    .click() / / 3.

  cy.url() / / 4.
    .should('include'.'/my/resource/path#awesomeness') / / 5.
})
Copy the code

Examples of 5 steps:

  1. Visit a URL.
  2. Find an element by its selector.
  3. Perform a click action on that element.
  4. Grab the URL.
  5. Assert the URL to include a specific string.

The above 5 steps are executed sequentially, not concurrently. Behind each step, the Cypress framework quietly performs some “magic” :

  1. Visit a URL

Cypress wait for the page load event to fire after all external resources have loaded

When this command is executed, Cypress waits for all external resources on the page to load, and then the page throws a Page Load event.

  1. Find an Element by its selector Magic: If the Find command does not Find a DOM element, retry until the location is found.
  2. Perform a click action on that Element magic: After we wait for the element to reach an actionable state

Wait for an element to become clickable before clicking it.

Each cy command has certain timeout, recorded in the document: docs. Cypress. IO/guides/refe…

Commands are promise

This is the big secret of Cypress: we’ve taken our favorite pattern for composing JavaScript code, Promises, and built them right into the fabric of Cypress. Above, when we say we’re enqueuing actions to be taken later, we could restate that as “adding Promises to a chain of Promises”.

Cypress adds a retry mechanism to the Promise programming model.

The following code:

it('changes the URL when "awesome" is clicked'.() = > {
  cy.visit('/my/resource/path')

  cy.get('.awesome-selector').click()

  cy.url().should('include'.'/my/resource/path#awesomeness')})Copy the code

The JavaScript code translated into promise style is:

it('changes the URL when "awesome" is clicked'.() = > {
  // THIS IS NOT VALID CODE.
  // THIS IS JUST FOR DEMONSTRATION.
  return cy
    .visit('/my/resource/path')
    .then(() = > {
      return cy.get('.awesome-selector')
    })
    .then(($element) = > {
      // not analogous
      return cy.click($element)
    })
    .then(() = > {
      return cy.url()
    })
    .then((url) = > {
      expect(url).to.eq('/my/resource/path#awesomeness')})})Copy the code

Without retry-ability, assertions would randomly fail. This would lead to flaky, inconsistent results. This is also why we cannot use new JS features like async / await.

The lack of a retry mechanism results in flaky and inconsistent test results, which is why Cypress didn’t choose async/await.

You can think of Cypress as “queueing” every command. Eventually they’ll get run and in the exact order they were used, 100% of the time.

The order in which the Cypress commands are executed is the same as the order in which they are inserted into the test chain queue.

How do I create conditional control flow, using if/else? So that if an element does (or doesn’t) exist, I choose what to do?

Some developers may wonder how to write conditional control flows, such as in an IF/ELSE branch, that execute different test logic.

The problem with this question is that this type of conditional control flow ends up being non-deterministic. This means It’s impossible for a script (or robot), to follow it 100% preferably.

In fact, such conditional control logic would make the test non-deterministic. This means that the test script flings around the robot and does not follow the test procedure 100% strictly.

The following line of code:

cy.get('button').click().should('have.class'.'active')
Copy the code

Translated into natural language:

After clicking on this , I expect its class to eventually be active.

Notice the word eventually.

This above test will pass even if the .active class is applied to the button asynchronously – or after a indeterminate period of time.

Cypress will retry the assertion until.active class is added to the button, either asynchronously or after an unknown length of time.

What makes Cypress unique from other testing tools is that commands automatically retry their assertions. In fact, They will look “downstream” at what you’re expressing and modifying their behavior to make your assertions pass.

You should think of assertions as guards.

Use your guards to describe what your application should look like, and Cypress will automatically block, wait, and retry until it reaches that state.

Assertion mechanism by default for the Cypress command

With Cypress, you don’t have to assert to have a useful test. Even without assertions, a few lines of Cypress can ensure thousands of lines of code are working properly across the client and server!

This is because many commands have a built in Default Assertion which offer you a high level of guarantee.

Many CY commands have a default assertion mechanism.

  • cy.visit() expects the page to send text/html content with a 200 status code. Make sure the page receives a status code of 200 after sending text/ HTML content.
  • cy.request() expects the remote server to exist and provide a response. Ensure that the remote system exists and provides a response.
  • cy.contains() expects the element with content to eventually exist in the DOM. Ensure that the specified content ends up in the DOM.
  • cy.get() expects the element to eventually exist in the DOM.

Ensure that the requested element ends up in the DOM.

  • .find() also expects the element to eventually exist in the DOM. – same as cy. Get
  • .type() expects the element to eventually be in a typeable state. Ensure that the element is in an inputable state.
  • .click() expects the element to eventually be in an actionable state. Make sure the element is clickable.
  • .its() expects to eventually find a property on the current subject. Ensure that the corresponding property can be found on the current object

All DOM based commands automatically wait for their elements to exist in the DOM.

All DOM-based commands automatically block until their element exists in the DOM tree.

cy
  // there is a default assertion that this
  // button must exist in the DOM before proceeding
  .get('button')

  // before issuing the click, this button must be "actionable"
  // it cannot be disabled, covered, or hidden from view.
  .click()
Copy the code

Before the click command can be executed, the button must be clickable or the click command will not be executed. A button cannot be in an actionable state, meaning it cannot be in a disabled, covered, or hidden state.

Timeout setting of the Cypress command

cy.get('.mobile-nav').should('be.visible').and('contain'.'Home')
Copy the code
  1. Queries for the element. mobile-nav, then pause for 4 seconds until the element appears in the DOM.
  2. Pause for another 4 seconds and wait for the element to appear on the page.
  3. Wait another 4 seconds for the element to contain the text attribute of home.

All Cypress commands in a test program share the same timeout value.

More of Jerry’s original articles can be found in “Wang Zixi” :