All notes
Cypress

Command line

docs.cypress.io: command line.

run

Runs Cypress to completion. By default will run all tests headlessly in the Electron browser.



#----------

# Run all tests headlessly in the Electron browser.
cypress run
# Passing --headed will force Electron to be shown.
cypress run --headed
cypress run --config video=false

# Run in chrome. -b, --browser.
cypress run -b chrome

# If Cypress cannot find the browser you should turn on debugging for additional output.
DEBUG=cypress:launcher cypress run --browser chrome

# More DEBUG options:
DEBUG=cypress:cli cypress run
# or even a 3rd level deep submodule
DEBUG=cypress:server:project cypress run
DEBUG=cypress:server:scaffold cypress run
DEBUG=cypress:server:socket cypress run
DEBUG=cypress:server:bundle cypress run


#----------

# Run tests specifying a single test file to run instead of all tests
cypress run --spec cypress/integration/app.spec.js
# Run tests specifying a glob of where to look for test files
cypress run --spec cypress/integration/login/**/*
# Run tests specifying multiple test files to run
cypress run --spec cypress/integration/filter.spec.js,cypress/integration/users.spec.js

#---------- Else

cypress run --config pageLoadTimeout=100000,watchForFileChanges=false

cypress run --env host=api.dev.local

cypress run --port 8080

# Run tests specifying a mocha reporter
cypress run --reporter json
# Run tests specifying mochas reporter options
cypress run --reporter-options mochaFile=result.xml,toConsole=true

#---------- Record video

cypress run --record --key 
# Or
export CYPRESS_RECORD_KEY=abc-key-123
cypress run --record

open

It opens the Cypress Test Runner in interactive mode.



npx cypress open

Quick start



touch {your_project}/cypress/integration/my_test.js

Exemplary test file:



// "describe" and "it" come from Mocha
// "expect" comes from Chai
describe('My First Test', function() {
  it('Does not do much!', function() {
    expect(true).to.equal(true)
  })
})

// cy.visit. Visit a page.
// cy.contains. Find an element by its contents. It’s automatically waiting and retrying because it expects the content to eventually be found in the DOM. It doesn’t immediately fail!
describe('My First Test', function() {
  it('finds the content "type"', function() {
    cy.visit('https://example.cypress.io')

    cy.contains('type').click()

    // Should be on a new URL which includes '/commands/actions'
    cy.url().should('include', '/commands/actions')

    // Get an input, type into it and verify that the value has been updated
    cy.get('.action-email')
      .type('[email protected]')
      .should('have.value', '[email protected]')
  })
})

Alias



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()

//---------- Sharing Context

beforeEach(function () {
  // alias the $btn.text() as 'text'
  cy.get('button').invoke('text').as('text')
})

it('has access to text', function () {
  this.text // is now available
})

// Under the hood, aliasing basic objects and primitives utilizes Mocha’s shared context object: that is, aliases are available as this.*.
// These aliases and properties are automatically cleaned up after each test.

//---------- Accessing Fixtures

beforeEach(function () {
  // alias the users fixtures
  cy.fixture('users.json').as('users')
})

it('utilize users in some way', function () {
  // access the users property
  const user = this.users[0]

  // make sure the header contains the first users name
  cy.get('header').should('contain', user.name)
})

//---------- this.* vs cy.get("@")

// Instead of using the this.* syntax, there is another way to access aliases.
// The cy.get() command is capable of accessing aliases with a special syntax using the ‘@’ character.
// By using cy.get() we avoid the use of this.
// When using this.users we have access to it synchronously, whereas when using cy.get('@users') it becomes an asynchronous command.
// You can think of the cy.get('@users') as doing the same thing as cy.wrap(this.users).

beforeEach(function () {
  // alias the users fixtures
  cy.fixture('users.json').as('users')
})

it('utilize users in some way', function () {
  // which avoids the use of 'this'
  cy.get('@users').then((users) => {
    // access the users argument
    const user = users[0]
  })
})

//---------- Elements

// alias all of the tr's found in the table as 'rows'
cy.get('table').find('tr').as('rows')
// Because we’ve used the @ character in cy.get(), instead of querying the DOM for elements, cy.get() looks for an existing alias
cy.get('@rows').first().click()

// If the DOM elements are stale, Cypress replays the commands leading up to the alias definition.

//---------- Routes

cy.server()
cy.route('POST', '/users', { id: 123 }).as('postUser')

cy.get('form').submit()

// Waiting on the route alias to complete.
cy.wait('@postUser').its('requestBody').should('have.property', 'name', 'Brian')

cy.contains('Successfully created user: Brian')

Timeout

Cypress waited 4 seconds before timing out finding a DOM element.

When there is Page Transitions, Cypress detects a "page transition event" it automatically increases the timeout to 60 seconds for the single PAGE LOAD event.

Chains of Commands. It’s very important to understand the mechanism Cypress uses to chain commands together. It manages a Promise chain on your behalf.



// Give this element 10 seconds to appear
cy.get('.my-slow-selector', { timeout: 10000 })

Debugging

Time Travel: hover over the command.
Snapshots: click on the command. We have now pinned this snapshot, and hovering over other commands will not revert to them.


cy.pause()
// Cypress provides us a UI (similar to debugger) to step forward through each command.

cy.debug()
// set a debugger in your code.

Hooks



describe('Hooks', function() {
  before(function() {
    // runs once before all tests in the block
  })

  after(function() {
    // runs once after all tests in the block
  })

  beforeEach(function() {
    // runs before each test in the block
  })

  afterEach(function() {
    // runs after each test in the block
  })
})

Testing Strategies

Seeding data

Generally the server is responsible for sending responses that reflect some kind of state it holds - generally in a database.

To test various page states - like an empty view, or a pagination view, you’d need to seed the server so that this state can be tested.

Three ways to facilitate this with Cypress:



describe('The Home Page', function () {
  beforeEach(function () {
    // reset and seed the database prior to every test
    cy.exec('npm run db:reset && npm run db:seed')

    // seed a post in the DB that we control from our tests
    cy.request('POST', '/test/seed/post', {
      title: 'First Post',
      authorId: 1,
      body: '...'
    })

    // seed a user in the DB that we can control from our tests
    cy.request('POST', '/test/seed/user', { name: 'Jane' }).its('body').as('currentUser')
  })

  it('successfully loads', function() {
    // this.currentUser will now point to the response
    // body of the cy.request() that we could use
    // to log in or work with in some way

    cy.visit('/')
  })
})

Stubbing the Server

You can instead stub the JSON responses coming from it.

While stubbing is great, it means that you don’t have the guarantees that these response payloads actually match what the server will send. However, there are still many valid ways to get around this:

Core concepts

Querying Elements

Cypress wraps all DOM queries with robust retry-and-timeout logic that better suits how real web apps work.

Cypress enforces commands to run only serially. We enqueue all commands onto a global singleton.



//---------- Accessing the DOM elements returned from the query works differently

// This is fine, jQuery returns the element synchronously.
const $jqElement = $('.element')
// This will not work! Cypress does not return the element synchronously.
const $cyElement = cy.get('.element')

//---------- Querying by Text Content

// Find an element in the document containing the text 'New Post'
// They just know they want to click the button labeled “New Post”
cy.contains('New Post').click()

Configuration

cypress.json



//---------- baseUrl

// In cypress.json
{
  "baseUrl": "http://localhost:8080"
}

// This will automatically prefix cy.visit() and cy.request() commands with this baseUrl.
describe('The Home Page', function() {
  it('successfully loads', function() {
    cy.visit('/')
  })
})

Assertions

docs.cypress.io.

FAQ

Cross-origin

Set in "cypress.json":


{
  "chromeWebSecurity": false
}

Proxy

github.com.


HTTP_PROXY=http://my-proxy-address cypress run

Refused to display 'XXX' in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN'

github.com.

In cypress built-in browser, open chrome://extensions and install the extension "Ignore X-Frame headers".

You may or may not need the following in "plugins/index.js":


module.exports = (on, config) => {
  on("before:browser:launch", (browser = {}, args) => {
    if (browser.name === "chrome") {
      args.push("--disable-features=CrossSiteDocumentBlockingIfIsolating,CrossSiteDocumentBlockingAlways,IsolateOrigins,site-per-process");
      args.push("--load-extension=cypress/extensions/Ignore-X-Frame-headers_v1.1");
      return args;
    }
  });
};

Ignore Uncaught exception

github.com.

Sometimes your application has JS exceptions but you just want to ignore now. You can do this in "support/index.js":


Cypress.on('uncaught:exception', (err, runnable) => {
  return false;
});