Use TypeScript With Cypress

Step by step tutorial how to set up TypeScript support in Cypress using WebPack bundler.

How to write Cypress.io end-to-end tests in TypeScript is a question that comes up again and again. Here is what you need to do step by step if you are using WebPack already. If you don't want to follow steps, just use bahmutov/add-typescript-to-cypress module.

You can find the source code for this post in bahmutov/use-typescript-with-cypress repo.

Install Cypress

If you haven't already, install Cypress

1
npm install --save-dev cypress

Open Cypress for the first time - it will scaffold cypress folder with examples. We don't need it and can delete it. I will replace it with a single JavaScript spec file.

cypress/integration/spec.js
1
2
3
4
it('loads examples', () => {
cy.visit('https://example.cypress.io')
cy.contains('Kitchen Sink')
})

Cypress runs the test and it passes

first test passes

There is no IntelliSense yet. The global variable cy has type any according to VSCode.

no IntelliSense support

If we add @ts-check comment, VSCode is complaining about unknown variable cy.

cypress/integration/spec.js
1
2
3
4
5
// @ts-check
it('loads examples', () => {
cy.visit('https://example.cypress.io')
cy.contains('Kitchen Sink')
})

ts-check error

Fix IntelliSense and ts-check

Ok, if you want IntelliSense and ts-check to work - just add a single comment with reference types at the top of the JavaScript spec file, see Cypress docs.

cypress/integration/spec.js
1
2
3
4
5
6
/// <reference types="cypress" />
// @ts-check
it('loads examples', () => {
cy.visit('https://example.cypress.io')
cy.contains('Kitchen Sink')
})

That's it. Nothing else is necessary. You now have IntelliSense and ts-check working.

with reference types comment

TypeScript Spec

Let's switch from JavaScript to TypeScript. Rename spec.js to spec.ts and start using types. We don't need the reference comment or @ts-check directive.

cypress/integration/spec.ts
1
2
3
4
5
6
type Url = string
it('loads examples', () => {
const url: Url = 'https://example.cypress.io'
cy.visit(url)
cy.contains('Kitchen Sink')
})

Hmm, Cypress cannot bundle the spec file.

typescript is not working

VSCode cannot find global variable cy anymore.

cannot find cy

Let's fix this. We will need Webpack and Cypress Webpack preprocessor.

Transpile TypeScript using Webpack

To transpile TS code I will use Webpack. I am following the Webpack TypeScript guide exactly as written.

1. install tools

1
npm install --save-dev webpack typescript ts-loader

2. copy tsconfig.json example

I will copy the tsconfig.json exactly as is from the Webpack TypeScript guide and save it locally.

tsconfig.json
1
2
3
4
5
6
7
8
9
10
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "es6",
"target": "es5",
"jsx": "react",
"allowJs": true
}
}

Then I will add a single types entry to load global cypress variables.

tsconfig.json
1
2
3
4
5
6
7
8
9
10
11
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "es6",
"target": "es5",
"jsx": "react",
"allowJs": true,
"types": ["cypress"]
}
}

I have IntelliSense and types working in VSCode again.

fixed types

3. copy webpack.config.js example

We need webpack.config.js too - and again I am copying the example from the Webpack TypeScript guide as is.

webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const path = require('path')

module.exports = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}

For Cypress transpile, the entry and the output fields do not matter, since each spec file will have its own. But the ts-loader and resolve rules are important. Now we need to configure Cypress to transpile spec files using Webpack, and not its built-in Browserify.

Cypress Webpack preprocessor

We need to install cypress-webpack-preprocessor and point it at webpack.config.js. I am copying everything exactly as its README shows.

1
npm install --save-dev @cypress/webpack-preprocessor

Because we have already configured Webpack and ts-loader, no other dependencies are necessary.

Now copy the options snippet from the README to cypress/plugins/index.js to configure Webpack as a preprocessor

cypress/plugins/index.js
1
2
3
4
5
6
7
8
9
10
11
const webpack = require('@cypress/webpack-preprocessor')
module.exports = on => {
const options = {
// send in the options from your webpack.config.js, so it works the same
// as your app's code
webpackOptions: require('../../webpack.config'),
watchOptions: {}
}

on('file:preprocessor', webpack(options))
}

Start Cypress again and the TypeScript specs will be transpiled.

transpiled TypeScript spec

Additional features

If we try to use ES6 features in our specs, VSCode will show an error, and Cypress test will not be transpiled.

cypress/integration/spec.ts
1
2
3
4
5
6
7
it('handles ES6 features', () => {
const o = {
// @ts-ignore
name: cy.stub().returns('Joe')
}
const proxy = new Proxy()
})

proxy error

Go to tsconfig.json and change target property from "es5" to "es6".

tsconfig.json
1
2
3
4
5
6
7
8
9
10
11
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "es6",
"target": "es6",
"jsx": "react",
"allowJs": true,
"types": ["cypress"]
}
}

Now everything will work again and we can test how an ES6 proxy can intercept and redirect calls.

cypress/integration/spec.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
it('handles ES6 features', () => {
const o = {
// @ts-ignore
name: cy.stub().returns('Joe')
}
const proxy = new Proxy(o, {
get (target, method) {
return target.name
}
})
// @ts-ignore
expect(proxy.whatever()).to.equal('Joe')
expect(o.name).to.have.been.calledOnce
})

proxy test passes

Related