Cypress 🤖

Hero image for Cypress 🤖

We prefer using Cypress for end to end testing for our frontend applications. It is easy to set up and it provides some modern features which are convenient while writing end to end tests.

Configuration

  • We use Typescript with our frontend applications. After installing Cypress and Typescript we need to configure Cypress to use Typescript. A sample tsconfig.json file looks like below

    {
      "compilerOptions": {
        "target": "es5",
        "lib": ["es5", "dom"],
        "types": ["cypress"],
        "moduleResolution": "node",
        "resolveJsonModule": true
      },
      "include": [
        "**/*.ts"
      ],
      "files": [
        "./support/index.d.ts"
      ]
    }
    
  • Some scripts should be added to the package.json file for easier usage of Cypress

    "scripts": {
      "cypress:open": "cypress open",
      "cypress:run": "cypress run"
    }
    
  • Base URL must be specified in the cypress.json to avoid repetitive definition of base URL in each test.

    {
      "baseUrl": "http://localhost:3000",
    }
    

Formatting

  • Use soft-tabs with a two-space indent.
  • Limit each line of code to fewer than 130 characters.
  • End each file with a newline.
  • Do not leave line breaks after context or describe blocks.

    // Bad
    describe('User authentication', () => {
      
      context('given valid user credentials', () => {
      
        it('redirects to the landing page', () => {})
      })
    })
    
    // Good
    describe('User authentication', () => {
      context('given valid user credentials', () => {
        it('redirects to the landing page', () => {})
      })
    })
    
  • Leave one line return around context and it blocks.

    // Bad
    describe('User authentication', () => {
      context('given valid user credentials', () => {
        it('redirects to the landing page', () => {
          ...
        })
        it('shows user menu', () => {
          ...
        })
      })
      context('given INVALID user credentials', () => {
        it('shows login error', () => {
          ...
        })
      })
    })
    
    // Good
    describe('User authentication', () => {
      context('given valid user credentials', () => {
        it('redirects to the landing page', () => {
          ...
        })
      
        it('shows user menu', () => {
          ...
        })
      })
      
      context('given INVALID user credentials', () => {
        it('shows login error', () => {
          ...
        })
      })
    })
    

Naming

  • Use camelCase for file names.
  • Each test file name must have the suffix .spec to distinguish test files from source files.

    integration/
    ├── UserAuthentication/
    │   ├── login.spec.ts
    │   ├── signup.spec.ts
    ├── UserSearch/
    │   ├── searchByEmail.spec.ts
    support/
    ├── commands.ts/
    

    To enforce this rule a configuration should be added in the cypress.json file

    {
       "testFiles": ["**/*.spec.ts"]
    }
    
  • Use describe to group tests using the file name for the description.

    // searchByEmail.spec.json
      
    describe('Search by email', () => {})
    
  • Use context to describe testing preconditions. context block descriptions must always start with when or given, and be in the form of a sentence with proper grammar.

    // Bad
    it('redirects to landing page if user credentials are valid', () => {})
    
    // Good
    context('given valid user credentials', () => {
      it('redirects to the landing page', () => {})
    })
    
  • Do NOT prefix it block descriptions with should. Use the imperative tone instead.

    // Bad
    it('should redirect to landing page', () => {})
    
    // Good
    it('redirects to the landing page', () => {})
    

Element selector

Avoid using highly brittle HTML selectors that are subject to change with target elements. Use data-test-id attributes to provide context to your selectors and insulate them from CSS or JS changes.

<div data-test-id="userProfile"></div>

Functions

Use ES6 arrow function for describe/context/it block

// Bad
describe('User authentication', function() {
  context('given valid user credentials', function() {
    it('redirects to the landing page', function() {})
  })
})

// Good
describe('User authentication', () => {
  context('given valid user credentials', () => {
    it('redirects to the landing page', () => {})
  })
})

Stub HTTP requests/responses

From Cypress v6.0.0 a new command cy.intercept() has been added to stub HTTP requests and responses. This command can be used to specify HTTP fixtures as well.

cy.intercept('http://example.com/widgets', { fixture: 'widgets.json' })

All information about this command can be found in the documentation.

Custom commands

Cypress supports user-defined commands. Custom commands are beneficial for automating a workflow repeated in tests. It can significantly DRY-up tests.

If we have an application that requires login on each page then this can be made into a custom command

Cypress.Commands.add('login', () => {
  cy.visit('/login')
  cy.get('input[name=email]').type('[email protected]')
  cy.get('input[name=password]').type(`'password'{enter}`)
})

Then this command can be easily used in tests as cy.login(). When using typescript adding a custom command requires a TypeScript definitions file. More can be found in the documentation.