cypress: Cypress types conflict with TypeScript application types

  • Operating System: macOS High Sierra 10.13.3
  • Cypress Version: 1.4.2
  • Browser Version: n/a

Is this a Feature or Bug?

Bug

Current behavior:

I upgrade from Cypress 1.0.3 to 1.4.2 and my TypeScript application no longer compiles. This is because the 1.4.2 dependencies include TypeScript types, which are installed by default to my node_modules/@types directory and included in my compilation step due to the following configuration setup (tsconfig.json):

{
  "compilerOptions": {
    "typeRoots": [
      "../node_modules/@types"
    ]
  }
}

Here is a relevant issue on the TypeScript repo.

My short term workaround is to use the compilerOptions.types property and enumerate each desired type instead of compilerOptions.typeRoots, which grabs everything from node_modules/@types. This is not desirable long-term as I have to now update my tsconfig.json every time I add new types.

Desired behavior:

Installing Cypress should not add anything to my node_modules/@types directory or otherwise affect how my TypeScript application compiles.

How to reproduce:

n/a

Test code:

n/a

Additional Info (images, stack traces, etc)

n/a

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 3
  • Comments: 22 (12 by maintainers)

Most upvoted comments

@bahmutov when you have a large monorepo with multiple projects that are using Jest/Mocha/Chai and Cypress, sharing the global namespace is an issue. Having to have double the amount of tsconfigs is not the best solution imo.

Would it be possible to export Cypress globals from a package entrypoint? We could get around this issue if we can import the conflicting modules directly from Cypress.

i.e.

import { cy, it, describe } from "cypress"

cc. @lencioni

Hi @bahmutov, thanks for the fast response!

Why do you need to double the number of tsconfigs - just have a tsconfig in Cypress folder, extending your main tsconfig and just adjusting the types.

I think this makes a lot of sense for smaller projects where they only have a single cypress folder.

At Airbnb, we have a fairly large and quickly growing monorepo with hundreds of projects, which will eventually turn into thousands. Each project can be thought of as a fairly standalone piece of software. Some of these projects are entire apps that are individually built and deployed. While cypress won’t be useful for every single project, we hope to enable it it for a bunch of them and we want to keep the overhead of adopting cypress for maintainers of these projects to a minimum. I hope this helps contextualize @sharmilajesupaul’s question!

We were thinking how to move from globals like “cy”, “it” but this is a big breaking change and we are not ready to do it yet.

Would it be possible to keep the globals around for folks who want to continue using them, and also add some exports for people who want to be more explicit? I believe that would be a semver minor change.

@bahmutov just an update, at Airbnb, we use use nested tsconfigs in our project cypress directories. Which is what I think you originally recommended.

The base that all projects inherit from:

// projects/typescript/tsconfig.base.json
{
 ...
    "isolatedModules": true
  },
  "exclude": [
    "../*/server/**/*",
    "../*/cypress/**/*"
  ]
}

The base config that all cypress directories inherit from:

// projects/cypress/tsconfig.cypress.json
// override any options that can't be used in /cypress
{
  "extends": "../typescript/tsconfig.base.json",
  "compilerOptions": {
    "isolatedModules": false
  },
  "exclude": []
}

then in every project cypress/:

// projects/some-ui-project/cypress/tsconfig.json
{
  "extends": "../../cypress/tsconfig.cypress.json",
  "include": [".", "../../../node_modules/cypress"]
}

@bahmutov Your comment helped me get this working in our monorepo. However, all we had to do was add "../*/cypress/**/*" to the exclude in tsconfig.base.json. We didn’t need to do anything with isolatedModules.

In tsconfig.base.json

{
  "compilerOptions": {
    "module": "esnext",
    "target": "es2018",
    "lib": ["es2018", "es2019.array", "dom"],
    "moduleResolution": "node",
    "baseUrl": "./packages",
    "jsx": "react",
    "esModuleInterop": true,
    "strict": true,
    "allowJs": false,
    "allowUnreachableCode": false,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "importHelpers": true,
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "useDefineForClassFields": true,
    "resolveJsonModule": true,
    "typeRoots": ["./node_modules/@types", "./typings"]
  },
  "exclude": ["node_modules", "../*/cypress/**/*"]
}

In tsconfig.cypress.json

{
  "compilerOptions": {
    "baseUrl": "../node_modules",
    "esModuleInterop": true,
    "lib": ["es5", "dom"],
    "strict": true,
    "target": "es5",
    "types": ["cypress"]
  },
  "include": ["**/*.ts"]
}

In all /cypress directories (inside of individual packages in the monorepo)

{
  "extends": "path/to/tsconfig.cypress.json"
}

@bahmutov just an update, at Airbnb, we use use nested tsconfigs in our project cypress directories. Which is what I think you originally recommended.

The base that all projects inherit from:

// projects/typescript/tsconfig.base.json
{
 ...
    "isolatedModules": true
  },
  "exclude": [
    "../*/server/**/*",
    "../*/cypress/**/*"
  ]
}

The base config that all cypress directories inherit from:

// projects/cypress/tsconfig.cypress.json
// override any options that can't be used in /cypress
{
  "extends": "../typescript/tsconfig.base.json",
  "compilerOptions": {
    "isolatedModules": false
  },
  "exclude": []
}

then in every project cypress/:

// projects/some-ui-project/cypress/tsconfig.json
{
  "extends": "../../cypress/tsconfig.cypress.json",
  "include": [".", "../../../node_modules/cypress"]
}

At first, we essentially did this https://github.com/cypress-io/cypress/issues/1319#issuecomment-517489190. We created our own cypress module that re-exported all the cypress globals – everyone imported from our local module vs the cypress node module. But we were still seeing issues with the typing especially for custom commands and some of the globals.

So we fell back on the recommended route and that has been working well so far.


Also, in any test files where the Cypress - Chai expect conflicts with Jest’s expect, adding this line to redeclare expect and explicitly setting it to jest.Expect will fix the conflict for that test file.

// Cypress brings in Chai types for the global expect, but we want to use jest
// expect here so we need to re-declare it.
declare const expect: jest.Expect;

^ @wachunga this could be a workaround for your colocated test files

In our case, we have Jest tests for a CLI tool that uses the Cypress module API and needed to use this method. The nested tsconfig.json should work for all other cases.

Do we have any update on this issue?

I have created a Jest + Cypress example repo that separates their types from each other https://github.com/cypress-io/cypress-and-jest-typescript-example. In general, it shows how to create tsconfig.json in cypress folder. I believe this is the best repo TS option if there are conflicting types.

Maybe I’m missing something, but with that setup you run into compilation errors if running tsc at the root as there are still conflicting global types, and Cypress’s types aren’t loaded via that tsconfig file.

Like others, I prefer colocation rather than a separate directory; the only workaround I’ve been able to come up with for that is to have the main tsconfig exclude the Cypress test files, then have a separate tsconfig for Cypress that includes them and the global Cypress types. Then it should just be a matter of adding import 'cypress/types'; (or /// <reference types="cypress" />) in each Cypress file so the IDE doesn’t complain. Example here:

https://github.com/jrwebdev/typescript-jest-cypress

I think you’d need to do something similar regardless if you had all Cypress tests in a separate directory with a tsconfig.json file, just probably wouldn’t need the import part.

In general though, I agree with others that testing libraries should just export everything through modules rather than globally, but I think this TS issue could help a lot with problems like this too.

I have created a Jest + Cypress example repo that separates their types from each other https://github.com/cypress-io/cypress-and-jest-typescript-example. In general, it shows how to create tsconfig.json in cypress folder. I believe this is the best repo TS option if there are conflicting types.

We’re running into this issue at Airbnb trying to introduce Cypress into TS projects with Jest tests. This is a minimal repro case repo: https://github.com/mohsen1/cypress-ts-issue

One workaround we’re using for now is deleting the cypress/types directory in an npm prepare script. eg:

// package.json
"scripts" {
  ...
 "patch:bad:types": "rimraf ./node_modules/cypress/types",
 "prepare": "npm run patch:bad:types && npm run build",
  ...
}

Same here. I can either use cypress or jest. Using cypress 3.3.1