Cypress Part I: Getting Started

In this new series, we look at how we could include Cypress as part of our testing strategy for our single page applications.

15 Nov 2019

•••

In this post, we are going to look at how we could add Cypress tests to an existing Single Page App project.

The instructions will include an optional, but highly recommended, step for people wanting to use TypeScript.

But before we begin, we will need an existing SPA project — here’s one built using Aurelia and Bulma.

Installation

Now that we have our existing SPA project, we can now add Cypress to it.

  1. Using your preferred package manager, do npm install cypress --save-dev or yarn add cypress -D. Either commands will add Cypress as a development dependency of your project. Hang tight, this installation might take a while ☕.

  2. Now run Cypress; npx cypress open -d or yarn cypress open -d. That is not a typo, npxis now bundled with newer version of npm, more information about it here.

    The command should open (after a while) the Cypress test runner UI and would have prompted you that it added some example tests.

    Cypress initial set up

    You can delete the folder cypress/integration/examples, we will add our own tests later on. If you are opting to use JavaScript, proceed to the Configuration section.

Adding TypeScript

Adding TypeScript support is optional, but highly recommended. To do so, you will need to install the following development dependencies: webpack, @cypress/webpack-preprocessor, ts-loader, typescript. For our sample project, we should only need to install @cypress/webpack-preprocessor.

  1. Add cypress/webpack.config.js and paste the code below. This will be a separate webpack.config.js from our existing project config.

    module.exports = {
      mode: 'development',
      // webpack will transpile TS and JS files
      resolve: {
        extensions: ['.ts', '.js'],
      },
      module: {
        rules: [
          {
            // every time webpack sees a TS file (except for node_modules)
            // webpack will use "ts-loader" to transpile it to JavaScript
            test: /\.ts$/,
            exclude: [/node_modules/],
            use: [
              {
                loader: 'ts-loader',
                options: {
                  // skip type checking for speed
                  transpileOnly: true,
                },
              },
            ],
          },
        ],
      },
    }
  2. We will then need to update the contents of cypress/plugins/index.js

    const wp = require('@cypress/webpack-preprocessor')
    
    module.exports = (on) => {
      const options = {
        webpackOptions: require('../webpack.config'),
      }
    
      // this adds a handler for the Cypress event to trigger the transpilation.
      on('file:preprocessor', wp(options))
    }

    This will trigger the pre-processor to kick-in when Cypress encounters a TypeScript file.

  3. For TypeScript to pick up the types provided by Cypress, we will need to amend the existing tsconfig.json with the new values shown below.

    {
      "compilerOptions": {
        "skipLibCheck": true,
        "types": [
          "jest"
          "node",
          "Cypress"
        ]
      },
      "include": [
        "cypress"
      ],
      "exclude": [
        "node_modules"
      ]
    }

    We need these for our demo application since we are using jest for our unit test and it clashes with some built-in types in Cypress.

That should be it, Cypress should now be set up to use TypeScript.

Configuration

Cypress configuration lives in cypress.json. This should have been automatically created for us when we finished the installation steps above. In this post, we will only add the baseUrl option.

{
  "baseUrl": "http://localhost:8080/"
}

With this set, we won’t need to have to type the full URL of our demo application. We will look at the other options in future posts.

Adding Selectors

Instead of using CSS selectors to query the DOM, Cypress suggests using data attributes over more fragile selectors like classes. This would lessen the chances of tests breaking when style classes change.

Cypress has the following default selector priority: data-cy, data-test, data-testid, id, class, tag, attributes and lastly nth-child.

For this series, we will be using data-test as our selector.

Now let’s go ahead and add selectors to the login form elements.

<div class="field">
  <label class="label">Username</label>
  <div class="control">
    <input
      data-test="login-username-input"
      type="text"
      value.bind="username"
      class="input"
      placeholder="john.smith"
    />
  </div>
</div>

<div class="field">
  <label class="label">Password</label>
  <div class="control">
    <input
      data-test="login-password-input"
      type="password"
      value.bind="password"
      class="input"
      placeholder="password"
    />
  </div>
</div>

<div class="field">
  <button
    data-test="login-login-button"
    class="button is-primary is-fullwidth"
    click.delegate="login()"
  >
    Login
  </button>
</div>

As you can see from the snippet above, I have opted to append the element type at the end. This allows me to easily identify what element they represent when writing my tests.

Writing Tests

Let’s add our very first Cypress test. Put your test files inside cypress/integration; let’s add login.spec.ts and put the content in the snippet below.

describe('Login Page', () => {
  before(() => {
    // Since we defined the baseUrl in the config, we only need the remaining path
    cy.visit('/#/login')
  })
})

Now that we have the initial setup of our Cypress tests for the login page, we can now move onto writing the actual tests.

it('navigates to the correct page and logs in successfully', () => {
  cy.url().should('contain', 'login')

  // I tend to always clear the input before I type anything
  cy.get('[data-test=login-username-input]').clear().type('john.smith')

  cy.get('[data-test=login-password-input]').clear().type('pa$$w0rd')

  cy.get('[data-test=login-login-button]').click()

  cy.url().should('contain', 'dashboard')
})

As you might have noticed, that seems to be a lot for a single test. With Cypress, each of those gets could be considered as assertions. If any of those elements don’t exist, the test fails.

Also, Cypress is an end-to-end test so we should be testing a specific flow rather than specific elements on a page.

Running Tests

Before running our tests, make sure the demo application is running.

We have multiple ways of running our Cypress tests, in this post we will look at using the Cypress runner UI. If you have closed the runner from earlier, just reopen it with the command we used previously.

Select login.spec.ts from the list of Integration Test and Cypress will run that.

You should then see the something line the video above 🎉🎉🎉.

Next Steps

Hopefully, you have learnt something through this tutorial. We will dig deeper into what you could do in Cypress in later posts. Thanks for getting this far 😬.

Similar Posts

Cypress Part II: Configuration

Now that we have added Cypress to our existing SPA, let's have a look at overriding the default configurations.

04 Dec 20192-minute read

Typescript 3.7

The new version of TypeScript has been released and with it comes optional chaining as well as nullish coalescing!

05 Nov 20191-minute read

Cypress: A New Way of Testing UI

Have you ever tested a single page app with Selenium? It can be cumbersome and flaky, Cypress aims to fix those issues.

03 Nov 20192-minute read