Testing with Cypress.io 12

What you can do with Cypress

  • End-to-End testing in BDD (expect/should) and TDD (assert) assertion styles
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
it('adds todos', () => {
cy.visit('https://todo.app.com')
cy.get('[data-testid="new-todo"]')
.type('write code{enter}')
.type('write tests{enter}')
// confirm the application is showing two items
cy.get('[data-testid="todos"]').should('have.length', 2)
})
it('adds todos', () => { cy.visit('https://todo.app.com') cy.get('[data-testid="new-todo"]') .type('write code{enter}') .type('write tests{enter}') // confirm the application is showing two items cy.get('[data-testid="todos"]').should('have.length', 2) })
it('adds todos', () => {
  cy.visit('https://todo.app.com')
  cy.get('[data-testid="new-todo"]')
    .type('write code{enter}')
    .type('write tests{enter}')
  // confirm the application is showing two items
  cy.get('[data-testid="todos"]').should('have.length', 2)
})
  • Component testing
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import TodoList from './components/TodoList'
it('contains the correct number of todos', () => {
const todos = [
{ text: 'Buy milk', id: 1 },
{ text: 'Learn Component Testing', id: 2 },
]
cy.mount(<TodoList todos={todos} />)
// the component starts running like a mini web app
cy.get('[data-testid="todos"]').should('have.length', todos.length)
})
import TodoList from './components/TodoList' it('contains the correct number of todos', () => { const todos = [ { text: 'Buy milk', id: 1 }, { text: 'Learn Component Testing', id: 2 }, ] cy.mount(<TodoList todos={todos} />) // the component starts running like a mini web app cy.get('[data-testid="todos"]').should('have.length', todos.length) })
import TodoList from './components/TodoList'

it('contains the correct number of todos', () => {
  const todos = [
    { text: 'Buy milk', id: 1 },
    { text: 'Learn Component Testing', id: 2 },
  ]

  cy.mount(<TodoList todos={todos} />)
  // the component starts running like a mini web app
  cy.get('[data-testid="todos"]').should('have.length', todos.length)
})
  • API testing
    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    it('adds a todo', () => {
    cy.request({
    url: '/todos',
    method: 'POST',
    body: {
    title: 'Write REST API',
    },
    })
    .its('body')
    .should('deep.contain', {
    title: 'Write REST API',
    completed: false,
    })
    })
    it('adds a todo', () => { cy.request({ url: '/todos', method: 'POST', body: { title: 'Write REST API', }, }) .its('body') .should('deep.contain', { title: 'Write REST API', completed: false, }) })
    it('adds a todo', () => {
      cy.request({
        url: '/todos',
        method: 'POST',
        body: {
          title: 'Write REST API',
        },
      })
        .its('body')
        .should('deep.contain', {
          title: 'Write REST API',
          completed: false,
        })
    })

    Cypress features

    • See what happened at each step of testing (time travel)
    • Debug directly using Developer Tools
    • Automatic waiting without using of wait commands or manual async/await calls
    • Verify and control the behavior of functions using Spies, Stubs, and Clocks
    • Easily control, stub, and test edge cases without involving your server
    • Consistent results, because it does not use Selenium or WebDriver
    • View screenshots taken automatically on failure, or videos of your entire test suite
    • Cross browser Testing
    • Parallelize your test suite on Cypress cloud
    • Discover and diagnose unreliable tests using Cypress Cloud’s Flaky test management.

    Important Cypress concepts

    • Cypress is built on top of Mocha, Sinon and Chai.
    • Cypress Test Runner runs all your tests in a real browser installed on your local system
    • Cypress commands don’t do anything at the moment they are invoked, but rather enqueue themselves to be run later in serial order
    • Cypress commands are asynchronous but you should not/can’t use async/await or loops

    Cypress vs. Selenium vs. WebDriver

    Selenium and most other testing tools run outside of the browser and execute remote commands across the network. Cypress does the opposite as it is executed in the same run loop as the application. Behind Cypress is a Node server process which constantly communicates, synchronizes, and perform tasks in accordance with Cypress. This gives us the ability to respond to the application’s events in real time, while at the same time work outside of the browser for tasks that require a higher privilege. Cypress also operates at the network layer by reading and altering web traffic on the fly. This enables Cypress to not only modify everything coming in and out of the browser. Cypress ultimately controls the entire automation process from top to bottom, which puts it in the unique position of being able to understand everything happening in and outside of the browser. Because Cypress is installed locally on your machine, it can additionally tap into the operating system for automation tasks. This makes performing tasks such as taking screenshots, recording videos, general file system operations and network operations possible.

    Your test code can access all the same objects that your application code can.

    Installing Cypress

    Change into your project root folder (package.json must exist) and run

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    yarn add cypress -dev
    yarn add cypress -dev
    yarn add cypress -dev

    Verify that it was correctly installed by running Cypress:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    yarn run cypress open
    yarn run cypress open
    yarn run cypress open

    or in case this makes trouble (it did for me, see below) run that instead:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    node_modules\.bin\cypress open
    node_modules\.bin\cypress open
    node_modules\.bin\cypress open

    For convenience you can add a script to package.json:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    {
    "scripts": {
    "cy:open": "cypress open",
    "cy:run": "cypress run"
    }
    }
    { "scripts": { "cy:open": "cypress open", "cy:run": "cypress run" } }
    {
      "scripts": {
        "cy:open": "cypress open",
        "cy:run": "cypress run"
      }
    }

    Folder structure

    The first time you run cypress in your project it will create a default folder structure in your project root:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    E2E:
    /cypress.config.ts
    /cypress/fixtures/example.json
    /cypress/support/commands.ts
    /cypress/support/e2e.ts
    Component:
    /cypress.config.ts
    /cypress/fixtures/example.json
    /cypress/support/commands.ts
    /cypress/support/component.ts
    /cypress/support/component-index.html
    E2E: /cypress.config.ts /cypress/fixtures/example.json /cypress/support/commands.ts /cypress/support/e2e.ts Component: /cypress.config.ts /cypress/fixtures/example.json /cypress/support/commands.ts /cypress/support/component.ts /cypress/support/component-index.html
    E2E:
    /cypress.config.ts
    /cypress/fixtures/example.json
    /cypress/support/commands.ts
    /cypress/support/e2e.ts
    
    Component:
    /cypress.config.ts
    /cypress/fixtures/example.json
    /cypress/support/commands.ts
    /cypress/support/component.ts
    /cypress/support/component-index.html
    • Fixtures are external pieces of static data that can be used by your tests. You would typically use them with the cy.fixture() command and most often when you’re stubbing Network Requests.
    • Commands allow you to write common functions, e.g. to login()
    • Support file runs before every single spec file. The names of the support files are fix: component.ts and e2e.ts. This is a great place to put global configuration and behavior that modifies Cypress.

    Asset folders

    The following asset folders are created in certain situations:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    /cypress
    - /downloads (when a test downloads a file)
    - /screenshots (via cy.screenshot() or if test fails)
    - /videos
    /cypress - /downloads (when a test downloads a file) - /screenshots (via cy.screenshot() or if test fails) - /videos
    /cypress
      - /downloads (when a test downloads a file)
      - /screenshots (via cy.screenshot() or if test fails)
      - /videos

    You probably want to .gitignore those folders.

    Test isolation vs. caching in session

    When running component tests, Cypress always resets the browser context, that is clearing cookies, localStorage and sessionStorage. Cypress supports enabling or disabling test isolation in end-to-end testing to describe if a suite of tests should run in a clean browser context or not. When enabled, Cypress resets the browser context before each test.

    Cypress allows for browser context to be cached with cy.session(). This means as a user, you only need to perform authentication once for the entirety of your test suite, and restore the saved session between each test. That means you do not have to visit a login page, type in a username and password and wait for the page to load and/or redirect for every test you run. You can accomplish this once with cy.session() and if needed, cy.origin().

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    // Caching session when logging in via page visit
    cy.session(name, () => {
    cy.visit('/login')
    cy.get('[data-test=name]').type(name)
    cy.get('[data-test=password]').type('s3cr3t')
    cy.get('form').contains('Log In').click()
    cy.url().should('contain', '/login-successful')
    })
    // Caching session when logging in via API
    cy.session(username, () => {
    cy.request({
    method: 'POST',
    url: '/login',
    body: { username, password },
    }).then(({ body }) => {
    window.localStorage.setItem('authToken', body.token)
    })
    })
    // Caching session when logging in via page visit cy.session(name, () => { cy.visit('/login') cy.get('[data-test=name]').type(name) cy.get('[data-test=password]').type('s3cr3t') cy.get('form').contains('Log In').click() cy.url().should('contain', '/login-successful') }) // Caching session when logging in via API cy.session(username, () => { cy.request({ method: 'POST', url: '/login', body: { username, password }, }).then(({ body }) => { window.localStorage.setItem('authToken', body.token) }) })
    // Caching session when logging in via page visit
    cy.session(name, () => {
      cy.visit('/login')
      cy.get('[data-test=name]').type(name)
      cy.get('[data-test=password]').type('s3cr3t')
      cy.get('form').contains('Log In').click()
      cy.url().should('contain', '/login-successful')
    })
    
    // Caching session when logging in via API
    cy.session(username, () => {
      cy.request({
        method: 'POST',
        url: '/login',
        body: { username, password },
      }).then(({ body }) => {
        window.localStorage.setItem('authToken', body.token)
      })
    })

    Writing E2E tests

    • Tests can be written in .ts, .tsx, .js, .jsx, .coffee and .cjsx
    • You can import or require both npm packages and local relative modules.

    Create a test file mytest.spec.js in my-app/cypress/e2e.

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    // mytest.spec.js
    describe('My test suite', () => {
    it('should test something', () => {
    })
    })
    // mytest.spec.js describe('My test suite', () => { it('should test something', () => { }) })
    // mytest.spec.js
    
    describe('My test suite', () => {
      it('should test something', () => {
    
      })
    })

    context() is identical to describe() and specify() is identical to it().

    Test configuration

    You can configure things such a baseUrl, browser, requestTimeout, retries and many more.

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    describe(name, config, fn)
    context(name, config, fn)
    it(name, config, fn)
    specify(name, config, fn)
    describe(name, config, fn) context(name, config, fn) it(name, config, fn) specify(name, config, fn)
    describe(name, config, fn)
    context(name, config, fn)
    it(name, config, fn)
    specify(name, config, fn)

    Querying elements by CSS selectors

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.get('.my-selector');
    cy.get('#elementId');
    cy.get(':checkbox')
    // and all other possible CSS selectors
    cy.get('.my-selector'); cy.get('#elementId'); cy.get(':checkbox') // and all other possible CSS selectors
    cy.get('.my-selector');
    cy.get('#elementId');
    cy.get(':checkbox')
    // and all other possible CSS selectors

    Adjusting async retry logic timeout

    Cypress wraps all DOM queries with an asynchronous retry-and-timeout logic. That’s why the following will not work:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    // This will not work! Cypress does not return the element synchronously.
    const $cyElement = cy.get('.element')
    // This will not work! Cypress does not return the element synchronously. const $cyElement = cy.get('.element')
    // This will not work! Cypress does not return the element synchronously.
    const $cyElement = cy.get('.element')

    Default timeouts

    • 4 seconds for selecting elements
    • 6 seconds for cy.visit() and cy.exec()
    • cy.wait() 5 seconds when waiting for routing alias plus 3 seconds for the server response

    You can define your own timeout if an element needs more time to appear:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    // Give this element 10 seconds to appear
    cy.get('.my-slow-selector', { timeout: 10000 })
    // Give this element 10 seconds to appear cy.get('.my-slow-selector', { timeout: 10000 })
    // Give this element 10 seconds to appear
    cy.get('.my-slow-selector', { timeout: 10000 })

    Here is an example with two assertions:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.get('.mobile-nav').should('be.visible').and('contain', 'Home')
    cy.get('.mobile-nav').should('be.visible').and('contain', 'Home')
    cy.get('.mobile-nav').should('be.visible').and('contain', 'Home')

    It is important to understand that the default timeout of 4 seconds applies to the command, not the assertions. In other words: Cypress gives a total of 4 seconds to assert that the element .mobile-nav is visible and contains the text Home, and not a total of 4 + 4 + 4 = 12 seconds.

    You can also set a global timeout.

    Access a selected element

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.get('#some-link')
    .then(($myElement) => {
    // 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
    })
    cy.get('#some-link') .then(($myElement) => { // 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 })
    cy.get('#some-link')
      .then(($myElement) => {
        // 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
      })

    Querying elements by text content

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    // 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')
    // 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')
    // 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')

    Instead of cy.get(selector).should('contain', text) or cy.get(selector).contains(text) chain, it is recommended using cy.contains(selector, text) which is retried automatically as a single command.

    You probably do not want to use this approach if your text changes frequently or you use translations.

    Filter elements

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.get('td').filter('.users')
    cy.get('td').filter('.users')
    cy.get('td').filter('.users')

    Interacting with elements

    Cypress will wait until a selected element becomes “actionable”, e.g. waits until is not hidden, not being covered, not being disabled, not being animated.

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.get('textarea.post-body').type('This is an excellent post.')
    // others:
    .blur()
    .focus()
    .clear()
    .check()
    .uncheck()
    .select()
    .dblclick()
    .rightclick()
    cy.get('textarea.post-body').type('This is an excellent post.') // others: .blur() .focus() .clear() .check() .uncheck() .select() .dblclick() .rightclick()
    cy.get('textarea.post-body').type('This is an excellent post.')
    
    // others:
    .blur()
    .focus()
    .clear()
    .check()
    .uncheck()
    .select()
    .dblclick()
    .rightclick()

    Example of pressing keys (e.g. Enter) into an element:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.get('.new-todo').type('todo A{enter}')
    cy.get('.new-todo').type('todo A{enter}')
    cy.get('.new-todo').type('todo A{enter}')

    An events’ coordinates are fired at the center of the element, but most commands enable you to change the position it’s fired to.

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.get('button').click({ position: 'topLeft' })
    cy.get('button').click({ position: 'topLeft' })
    cy.get('button').click({ position: 'topLeft' })

    Debugging interactions

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.get('button').debug().click()
    cy.get('button').debug().click()
    cy.get('button').debug().click()

    Now open up the dev tools in the test runner and debug from there.

    In the web dev console you have access to subject variable which points to the selected element. You can invoke functions, e.g. subject.text().

    Assertions

    Cypress uses Chai to handle assertions. Here is the full assertion reference.

    Cypress will automatically wait until these assertions pass. This prevents you from having to know or care about the precise moment your elements eventually do reach this state.

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.get(':checkbox').should('be.disabled')
    // others:
    .should('have.class', 'form-horizontal')
    .should('not.have.value', 'US')
    cy.get(':checkbox').should('be.disabled') // others: .should('have.class', 'form-horizontal') .should('not.have.value', 'US')
    cy.get(':checkbox').should('be.disabled')
    
    // others:
    .should('have.class', 'form-horizontal')
    .should('not.have.value', 'US')

    Assert existence of an element

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    .should('exist')
    .should('not.exist')
    .should('be.visible')
    .should('not.be.visible')
    .should('exist') .should('not.exist') .should('be.visible') .should('not.be.visible')
    .should('exist')
    .should('not.exist')
    .should('be.visible')
    .should('not.be.visible')

    Example of asserting that elements disappear after an action:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    // now Cypress will wait until this
    // <button> is not in the DOM after the click
    cy.get('button.close').click().should('not.exist')
    // and now make sure this #modal does not exist in the DOM
    // and automatically wait until it's gone!
    cy.get('#modal').should('not.exist')
    // now Cypress will wait until this // <button> is not in the DOM after the click cy.get('button.close').click().should('not.exist') // and now make sure this #modal does not exist in the DOM // and automatically wait until it's gone! cy.get('#modal').should('not.exist')
    // now Cypress will wait until this
    // <button> is not in the DOM after the click
    cy.get('button.close').click().should('not.exist')
    
    // and now make sure this #modal does not exist in the DOM
    // and automatically wait until it's gone!
    cy.get('#modal').should('not.exist')

    Assert if CSS class or CSS property exists

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    .should('have.class', 'my-list-item')
    .should('have.css', 'background-color', 'blue')
    .should('have.class', 'my-list-item') .should('have.css', 'background-color', 'blue')
    .should('have.class', 'my-list-item')
    .should('have.css', 'background-color', 'blue')

    Example of asserting that an object contains a specific path of properties:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.window()
    .its('app.model.todos') // retried
    .should('have.length', 2)
    cy.window() .its('app.model.todos') // retried .should('have.length', 2)
    cy.window()
      .its('app.model.todos') // retried
      .should('have.length', 2)

    Assert text content

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    .invoke('text')
    .should('contain')
    .should('not.contain')
    .invoke('text') .should('contain') .should('not.contain')
    .invoke('text')
      .should('contain')
      .should('not.contain')

    Default assertions

    With Cypress, you don’t have to assert to have a useful test. This is because many commands have a built in Default Assertion which offer you a high level of guarantee. For example cy.visit(‘http://somesite.org’) expects the page to send text/html content with a 200 status, or that cy.request() expects that a server provides a response etc.

    A full list of assertion can be found in this reference.

    Chaining multiple assertions

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.get('#header a')
    .should('have.class', 'active')
    .and('have.attr', 'href', '/users')
    cy.get('#header a') .should('have.class', 'active') .and('have.attr', 'href', '/users')
    cy.get('#header a')
      .should('have.class', 'active')
      .and('have.attr', 'href', '/users')

    or use the long way – if you want to change the element in some way prior to making the assertion:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.get('tbody tr:first').should(($tr) => {
    expect($tr).to.have.class('active')
    expect($tr).to.have.attr('href', '/users')
    })
    cy.get('tbody tr:first').should(($tr) => { expect($tr).to.have.class('active') expect($tr).to.have.attr('href', '/users') })
    cy.get('tbody tr:first').should(($tr) => {
      expect($tr).to.have.class('active')
      expect($tr).to.have.attr('href', '/users')
    })

    Here is another advanced example in which we assert that all p-elements have a specific text:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.get('p').should(($p) => {
    // massage our subject from a DOM element
    // into an array of texts from all of the p's
    let texts = $p.map((i, el) => {
    return Cypress.$(el).text()
    })
    // jQuery map returns jQuery object
    // and .get() converts this to an array
    texts = texts.get()
    // array should have length of 3
    expect(texts).to.have.length(3)
    // with this specific content
    expect(texts).to.deep.eq([
    'Some text from first p',
    'More text from second p',
    'And even more text from third p',
    ])
    })
    cy.get('p').should(($p) => { // massage our subject from a DOM element // into an array of texts from all of the p's let texts = $p.map((i, el) => { return Cypress.$(el).text() }) // jQuery map returns jQuery object // and .get() converts this to an array texts = texts.get() // array should have length of 3 expect(texts).to.have.length(3) // with this specific content expect(texts).to.deep.eq([ 'Some text from first p', 'More text from second p', 'And even more text from third p', ]) })
    cy.get('p').should(($p) => {
      // massage our subject from a DOM element
      // into an array of texts from all of the p's
      let texts = $p.map((i, el) => {
        return Cypress.$(el).text()
      })
    
      // jQuery map returns jQuery object
      // and .get() converts this to an array
      texts = texts.get()
    
      // array should have length of 3
      expect(texts).to.have.length(3)
    
      // with this specific content
      expect(texts).to.deep.eq([
        'Some text from first p',
        'More text from second p',
        'And even more text from third p',
      ])
    })

    You have to make sure that the entire function can be executed multiple times without side effects.

    Best practice: Alternate commands and assertions

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    it('adds two items', () => {
    cy.visit('/')
    cy.get('.new-todo').type('todo A{enter}')
    cy.get('.todo-list li') // command
    .should('have.length', 1) // assertion
    .find('label') // command
    .should('contain', 'todo A') // assertion
    cy.get('.new-todo').type('todo B{enter}')
    cy.get('.todo-list li') // command
    .should('have.length', 2) // assertion
    .find('label') // command
    .should('contain', 'todo B') // assertion
    })
    it('adds two items', () => { cy.visit('/') cy.get('.new-todo').type('todo A{enter}') cy.get('.todo-list li') // command .should('have.length', 1) // assertion .find('label') // command .should('contain', 'todo A') // assertion cy.get('.new-todo').type('todo B{enter}') cy.get('.todo-list li') // command .should('have.length', 2) // assertion .find('label') // command .should('contain', 'todo B') // assertion })
    it('adds two items', () => {
      cy.visit('/')
    
      cy.get('.new-todo').type('todo A{enter}')
      cy.get('.todo-list li') // command
        .should('have.length', 1) // assertion
        .find('label') // command
        .should('contain', 'todo A') // assertion
    
      cy.get('.new-todo').type('todo B{enter}')
      cy.get('.todo-list li') // command
        .should('have.length', 2) // assertion
        .find('label') // command
        .should('contain', 'todo B') // assertion
    })
    

    Aliases

    Instead of selecting an element by writing selectors such as '.my-selector > p.something' many times you can instead define a short alias for convenience.

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.get('.my-selector')
    .as('myElement') // sets the alias
    .click()
    /* many more actions */
    cy.get('@myElement') // re-queries the DOM as before (only if necessary)
    .click()
    cy.get('.my-selector') .as('myElement') // sets the alias .click() /* many more actions */ cy.get('@myElement') // re-queries the DOM as before (only if necessary) .click()
    cy.get('.my-selector')
      .as('myElement') // sets the alias
      .click()
    
    /* many more actions */
    
    cy.get('@myElement') // re-queries the DOM as before (only if necessary)
      .click()

    It is recommended that you alias elements as soon as possible instead of further down a chain of commands.

    Aliases when using Mocha hooks

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    beforeEach(() => {
    // alias the $btn.text() as 'text'
    cy.get('button').invoke('text').as('text')
    })
    it('has access to text', function () {
    this.text // access is synchronously, but cannot use with arrow function
    cy.get('@text') // access is asynchronously and also we can use arrow functions
    })
    beforeEach(() => { // alias the $btn.text() as 'text' cy.get('button').invoke('text').as('text') }) it('has access to text', function () { this.text // access is synchronously, but cannot use with arrow function cy.get('@text') // access is asynchronously and also we can use arrow functions })
    beforeEach(() => {
      // alias the $btn.text() as 'text'
      cy.get('button').invoke('text').as('text')
    })
    
    it('has access to text', function () {
      this.text // access is synchronously, but cannot use with arrow function
      cy.get('@text') // access is asynchronously and also we can use arrow functions
    })
    

    Triggering events on selected elements / Dynamically generate tests

    You use .trigger():

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    describe('if your app uses jQuery', () => {
    ;['mouseover', 'mouseout', 'mouseenter', 'mouseleave'].forEach((event) => {
    it('triggers event: ' + event, () => {
    // if your app uses jQuery, then we can trigger a jQuery
    // event that causes the event callback to fire
    cy.get('#with-jquery')
    .invoke('trigger', event)
    .get('#messages')
    .should('contain', 'the event ' + event + 'was fired')
    })
    })
    })
    describe('if your app uses jQuery', () => { ;['mouseover', 'mouseout', 'mouseenter', 'mouseleave'].forEach((event) => { it('triggers event: ' + event, () => { // if your app uses jQuery, then we can trigger a jQuery // event that causes the event callback to fire cy.get('#with-jquery') .invoke('trigger', event) .get('#messages') .should('contain', 'the event ' + event + 'was fired') }) }) })
    describe('if your app uses jQuery', () => {
      ;['mouseover', 'mouseout', 'mouseenter', 'mouseleave'].forEach((event) => {
        it('triggers event: ' + event, () => {
          // if your app uses jQuery, then we can trigger a jQuery
          // event that causes the event callback to fire
          cy.get('#with-jquery')
            .invoke('trigger', event)
            .get('#messages')
            .should('contain', 'the event ' + event + 'was fired')
        })
      })
    })
    

    Fixtures

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    beforeEach(() => {
    // alias the users fixtures
    cy.fixture('users.json').as('users')
    })
    it('utilize users in some way', function () {
    // use the special '@' syntax to access aliases
    // which avoids the use of 'this'
    cy.get('@users').then((users) => {
    // access the users argument
    const user = users[0]
    // make sure the header contains the first
    // user's name
    cy.get('header').should('contain', user.name)
    })
    })
    beforeEach(() => { // alias the users fixtures cy.fixture('users.json').as('users') }) it('utilize users in some way', function () { // use the special '@' syntax to access aliases // which avoids the use of 'this' cy.get('@users').then((users) => { // access the users argument const user = users[0] // make sure the header contains the first // user's name cy.get('header').should('contain', user.name) }) })
    beforeEach(() => {
      // alias the users fixtures
      cy.fixture('users.json').as('users')
    })
    
    it('utilize users in some way', function () {
      // use the special '@' syntax to access aliases
      // which avoids the use of 'this'
      cy.get('@users').then((users) => {
        // access the users argument
        const user = users[0]
    
        // make sure the header contains the first
        // user's name
        cy.get('header').should('contain', user.name)
      })
    })
    

    Running Tests

    • Cypress suggests running test files individually by clicking on the spec filename to ensure the best performance.
    • But Cypress also allows you to run all spec files together by clicking the “Run all specs” button. This mode is equivalent to concatenating all spec files together into a single piece of test code.
    • You can also run a subset of all specs by entering a text search filter.

    Watching for changes

    When running in using cypress open, Cypress watches the filesystem for changes to your spec files. Soon after adding or updating a test Cypress will reload it and run all of the tests in that spec file.

    Skipping tests

    Can be used as placeholder, when you have not yet completely written the test. Those tests will have the state pending.

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    describe('TodoMVC', () => {
    it('is not written yet')
    it.skip('adds 2 todos', function () {
    cy.visit('/')
    cy.get('.new-todo').type('learn testing{enter}').type('be cool{enter}')
    cy.get('.todo-list li').should('have.length', 100)
    })
    xit('another test', () => {
    expect(false).to.true
    })
    })
    describe('TodoMVC', () => { it('is not written yet') it.skip('adds 2 todos', function () { cy.visit('/') cy.get('.new-todo').type('learn testing{enter}').type('be cool{enter}') cy.get('.todo-list li').should('have.length', 100) }) xit('another test', () => { expect(false).to.true }) })
    describe('TodoMVC', () => {
      it('is not written yet')
    
      it.skip('adds 2 todos', function () {
        cy.visit('/')
        cy.get('.new-todo').type('learn testing{enter}').type('be cool{enter}')
        cy.get('.todo-list li').should('have.length', 100)
      })
    
      xit('another test', () => {
        expect(false).to.true
      })
    })
    

    Run in debug mode

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    DEBUG=cypress:server:specs npx cypress open
    ## or
    DEBUG=cypress:server:specs npx cypress run
    DEBUG=cypress:server:specs npx cypress open ## or DEBUG=cypress:server:specs npx cypress run
    DEBUG=cypress:server:specs npx cypress open
    ## or
    DEBUG=cypress:server:specs npx cypress run

    Conditional Testing

    The only way to do conditional testing on the DOM is if you are 100% sure that the state has “settled” and there is no possible way for it to change.

    In all other cases you can still achieve conditional testing without relying on the DOM. You have to anchor yourself to another piece of truth that is not mutable.

    To write tests for A/B Testing you could program your server that always returns a fixed state (A or B) depending on a parameter that you send along:

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.visit('https://app.com?campaign=A')
    cy.visit('https://app.com?campaign=B')
    cy.visit('https://app.com?campaign=A') cy.visit('https://app.com?campaign=B')
    cy.visit('https://app.com?campaign=A')
    
    cy.visit('https://app.com?campaign=B')

    Alternatively, if your server saves the campaign with a session, you could ask your server to tell you which campaign you are on.

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    // this sends us the session cookies
    cy.visit('https://app.com')
    // assuming this sends us back
    // the campaign information
    cy.request('https://app.com/me')
    .its('body.campaign')
    .then((campaign) => {
    // runs different cypress test code
    // based on the type of campaign
    return campaigns.test(campaign)
    })
    // this sends us the session cookies cy.visit('https://app.com') // assuming this sends us back // the campaign information cy.request('https://app.com/me') .its('body.campaign') .then((campaign) => { // runs different cypress test code // based on the type of campaign return campaigns.test(campaign) })
    // this sends us the session cookies
    cy.visit('https://app.com')
    
    // assuming this sends us back
    // the campaign information
    cy.request('https://app.com/me')
      .its('body.campaign')
      .then((campaign) => {
        // runs different cypress test code
        // based on the type of campaign
        return campaigns.test(campaign)
      })
    

    Another way to test this is if your server sent the campaign in a session cookie that you could read off.

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.visit('https://app.com')
    cy.getCookie('campaign').then((campaign) => {
    return campaigns.test(campaign)
    })
    cy.visit('https://app.com') cy.getCookie('campaign').then((campaign) => { return campaigns.test(campaign) })
    cy.visit('https://app.com')
    cy.getCookie('campaign').then((campaign) => {
      return campaigns.test(campaign)
    })

    Another valid strategy would be to embed data directly into the DOM – but do so in a way where this data is always present and query-able. It would have to be present 100% of the time, else this would not work.

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cy.get('html')
    .should('have.attr', 'data-campaign')
    .then((campaign) => {
    return campaigns.test(campaign)
    })
    cy.get('html') .should('have.attr', 'data-campaign') .then((campaign) => { return campaigns.test(campaign) })
    cy.get('html')
      .should('have.attr', 'data-campaign')
      .then((campaign) => {
        return campaigns.test(campaign)
      })

    The official doc has more strategies of conditional testing.

    Environment variables

    Option 1: Setting env var via command line

    E.g. export CYPRESS_MY_VARIABLE="hello" on Linux (must start with CYPRESS_).

    Option 2: Passing as argument

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    cypress open --env MY_VARIABLE="hello"
    cypress open --env MY_VARIABLE="hello"
    cypress open --env MY_VARIABLE="hello"

    Option 3: Adding to Cypress.json

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    {
    "env": {
    "MY_ENV_VARIABLE" : "hello"
    }
    }
    { "env": { "MY_ENV_VARIABLE" : "hello" } }
    {
      "env": {
        "MY_ENV_VARIABLE" : "hello"
      }
    }

    Option 4: Create cypress.env.json

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    {
    "MY_ENV_VARIABLE" : "hello"
    }
    { "MY_ENV_VARIABLE" : "hello" }
    {
      "MY_ENV_VARIABLE" : "hello"
    }

    Accessing env var in tests

    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    const my = Cypress.env('MY_VARIABLE')
    const my = Cypress.env('MY_VARIABLE')
    const my = Cypress.env('MY_VARIABLE')

    Remember to not commit secret info to your repository.

    About Author

    Mathias Bothe To my job profile

    I am Mathias from Heidelberg, Germany. I am a passionate IT freelancer with 15+ years experience in programming, especially in developing web based applications for companies that range from small startups to the big players out there. I create Bosycom and initiated several software projects.