Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / DevOps / testing

How to Write Fully Type Safe E2E Tests with Cypress using TypeScript

0.00/5 (No votes)
16 Jan 2020CPOL2 min read 13.6K   50  
This article describes how to create a test project that allows you to both write and fully debug in TypeScript source files.

Introduction

Automated end-to-end web application testing is one of the pillars in developing production applications. There are various test frameworks to increase the efficiency of QA. Recently, I had the opportunity to try out Cypress framework on a newly developed product. Cypress is a JS testing framework that runs in a browser and thus very well simulates the behavior of a real client. It is possible to write both GUI and API tests with it and it offers many interesting features.

As a fullstack developer, I am used to type-safe code from backend languages, so I use the TypeScript type extension for frontend development. I also consider it necessary to have the type-safe code for the E2E tests. On the official Cypress site, there are links to possible TypeScript settings, but at the time I wrote this article, these tutorials were not fully functional. They did not allow debugging in the source maps of the tests themselves.

This article describes how to create a test project that allows you to both write and fully debug in TypeScript source files.

Create a Project

First, initialize the npm project with the command:

npm init

Now install:

  1. cypress - testing framework
  2. typescript - TypeScript compiler
  3. ts-loader - Loader for TypeScript source codes
  4. webpack - build tool
  5. @cypress/webpack-preprocessor - plugin file preprocessing using webpack
npm i cypress typescript ts-loader webpack @cypress/webpack-preprocessor --save

These are all the packages that are needed.

Configure TypeScript

To configure typescript, create tsconfig.json in the root of project:

JavaScript
{
  "compilerOptions": {
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noEmitHelpers": true,
    "noImplicitAny": true,
    "strictPropertyInitialization": true,
    "preserveConstEnums": false,
    "target": "es5",
    "lib": [
      "es5",
      "dom",
      "es2015.core",
      "es2015.promise",
      "es2015.iterable",
      "es2015.collection"
    ],
    "types": ["cypress"],
    "sourceMap": true,
    "reactNamespace": "b",
    "removeComments": false,
    "skipLibCheck": true,
    "skipDefaultLibCheck": true
  },
  "include": ["**/*.ts"]
}

This example contains the most strict restrictions for type safety.

Configure webpack

The build tool for your testing project is webpack. To configure it, create webpack.config.js containing:

JavaScript
module.exports = {
  resolve: {
    extensions: [".js", ".ts", ".d.ts"]
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: [
          {
            loader: "ts-loader",
            options: { allowTsInNodeModules: true }
          }
        ]
      }
    ]
  },
  mode: "development"
};

Configure Cypress

Cypress Plugin for Webpack Preprocessing

To use @cypress-webpack-preprocessor, change cypress/plugins/index.js to look like the following:

JavaScript
// plugins file
const webpack = require("@cypress/webpack-preprocessor");

module.exports = (on, config) => {
  const options = {
    webpackOptions: require("../../webpack.config"),
    watchOptions: {}
  };
  on("file:preprocessor", getWepPackWithFileChange(options));
};

function getWepPackWithFileChange(options) {
  const webPackPreProcessor = webpack(options);
  return function(file) {
    file.outputPath = file.outputPath.replace(".ts", ".js");
    return webPackPreProcessor(file);
  };
}

This is the most important part because this allows you to compile and get source maps for debugging for both cypress commands and spec files.

Transform Cypress Support Files to TS

Rename support/commands.js to cypress/support/commands.ts and add the following custom cy command:

JavaScript
declare namespace Cypress {
  interface Chainable<Subject> {
    customCommand(): Cypress.Chainable<null>;
  }
}

Cypress.Commands.add(
  "customCommand",
  (): Cypress.Chainable<null> => {
    return cy.log("TEST LOG");
  }
);

Now rename support/index.js to cypress/support/index.ts and ensure it contains the following:

JavaScript
import "./commands";

Cypress

Finally, change the main cypress config in cypress.json to use TypeScript spec and support files:

JavaScript
{
  "testFiles": "**/*.spec.ts",
  "pluginsFile": "cypress/plugins/index.js",
  "supportFile": "cypress/support/index.ts"
}

Testing

Now you can define one simple test in cypress/integration folder, e.g. cypress/integration/examples/test.spec.ts containing:

JavaScript
describe("Example", () => {
  it("test", () => {
    const testString = "test-string";

    debugger; // Just to break running tests for debugging - this should be removed 
              // in final code

    cy.wrap(testString)
      .should("exist", testString)
      .customCommand();
  });
});

Now you can run cypress tests as usual:

PowerShell
npx cypress open

You can see that there is test.spec.ts in the list of tests.

Image 1

If you now run tests with open developer tools, the debugger will stop at the debugger line. This is necessary to stop execution. After that, the debugger will also stop at your breakpoints. Note that debugging is directly in test.spec.ts, so in the original TypeScript code. You can also debug commands.

Image 2

Conclusion

Cypress is a great tool for testing and I believe this article has helped you to improve its use.

History

  • 16th January, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)