cypress: New Cypress 10 global TypeScript type conflicts with Jest `expect`

Current behavior

The following new global TypeScript type for expect in node_modules/cypress/types/cypress-expect.d.ts causes conflicts with Jest expect types, if a project has both Jest and Cypress:

https://github.com/cypress-io/cypress/blob/a0a64cc851c8232b94eb0e907f220e08260bda20/cli/types/cypress-expect.d.ts#L2

Errors:

Property 'toBe' does not exist on type 'Assertion'.
Screen Shot 2022-06-02 at 21 19 56

When looking through the docs, there is a guide here:

Cypress Types Conflict with Jest

However, this guide is based on the fact that Cypress types were not global before (but they are now).

Desired behavior

No response

Test code to reproduce

Jest test code (filename __tests__/colors.test.ts):

import { findWhiteContrastingColor } from '../colors';

test('findWhiteContrastingColor finds colors contrasting with white', () => {
  expect(findWhiteContrastingColor(['#ffffff', '#000000', '#e1e1e1'])).toBe(
    '#000000',
  );
});

Cypress Version

10.0.1

Other

No response

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 83
  • Comments: 57 (16 by maintainers)

Commits related to this issue

Most upvoted comments

@karlhorky thanks for posting, looks like you’re not the only one having issues with Cypress + Jest type collisions.

I was able to reproduce this with the repos linked @karlhorky @mrdrogdrog @wKovacs64. When I checked out the dep update to v10, all of the Jest tests started complaining.

We didn’t change our globals very much for v10. For all the cases I reproduced, it was fixed by adding ./cypress.config.ts to the tsconfig.exclude property. Since the cypress.config.ts is being included in the type checking, it is loading the cypress types which is polluting your jest tests.

./tsconfig.json, include types and exclude properties in config file.

{
  "compilerOptions": {
    ...
    "types": ["jest", "node", "@types/testing-library__jest-dom"]
  },
  "exclude": ["node_modules", "cypress", "./cypress.config.ts"]
}

cypress/tsconfig.json

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "noEmit": true,
    "types": ["cypress"]
  },
  "exclude": ["node_modules"]
}

adding ./cypress.config.ts to the tsconfig.exclude property

This worked for me, thanks @ZachJW34 !

Why was this issue closed?

The docs at https://docs.cypress.io/guides/component-testing/component-test-troubleshooting#Cypress-Types-Conflict-with-Jest don’t address the problem sufficiently. This is especially notable when trying to colocate test files with source files. I really don’t want my test files in other directories for component testing.

  • The guidance to use /// <reference types="cypress" /> won’t be good enough for setups that extend the global Cypress types (like suggested in https://docs.cypress.io/guides/component-testing/mounting-vue#Using-cy-mount-Anywhere).

    Using cy.mount will result in Property 'mount' does not exist on type 'cy & CyEventEmitter'.. This can be avoided with a separate, top-level ambient type declaration (e.g. in a src/shims-cypress.d.ts file) which duplicates the type declaration that by default are in cypress/support/component.ts.

    Additionally, this doesn’t actually seem to allow correct inference of the describe, it, and expect keyword types which in my current setup are inferred as the Jest variant (see next point).

  • There is no guidance on how to restore types for Jest’s describe, it, expect, etc.

    People seem to have some success with excluding Cypress-specific files from the base tsconfig.json file via "exclude": [ "node_modules", "cypress.config.ts", "cypress/**/*.ts" ], but that’s pretty heavy-handed. Cypress shouldn’t just assume that it can override these keywords (sure, it would be lovely if Jest and Cypress required explicitly importing them but neither library chose to do that). This also seems to prevent inference of describe, etc. in a Cypress test file that’s colocated with source files.

    For the time being, people might need to use the @jest/globals package and import global symbols from it (see https://jestjs.io/docs/api). This works reasonably well.

Configuration

tsconfig.json:

{
  "compilerOptions": {...},
  "include": [
    "**/*.ts",
    "**/*.vue"
  ],
  "exclude": [
    "node_modules",
    "cypress.config.ts",
    "cypress/**/*.ts"
  ]
}

.eslintrc.js:

export default {
  // ...
  overrides: [
    // ...
    {
      files: ['*.spec.ts'],
      env: {
        jest: true,
      },
    },
    {
      files: ['*.test.ts'],
      plugins: ['cypress'],
      extends: ['plugin:cypress/recommended'],
      env: {
        'cypress/globals': true,
      },
    },
  ],
}

src/shims-cypress.d.ts:

import { mount } from 'cypress/vue'

declare global {
  namespace Cypress {
    interface Chainable {
      mount: typeof mount
    }
  }
}

@RafaPolit can you create another issue for what you are seeing? tsc --noEmit will use your tsconfig.json, so if you have it structured properly you should be able to exclude any Cypress configuration files and tests. For example, you could have

"exclude": [
    "node_modules",
    "cypress.config.ts",
    "cypress/**/*.ts"
  ]

and it should work. You could add another tsconfig.cypress.json and include your Cypress files if you wanted to typecheck those as well.

@denieler Same advice, though it can be admittedly harder to configure with CT tests since they live alongside components but you can use globs to capture an exclude any *.cy.ts files. If this doesn’t work for you, please open another issue. Types can be hard to configure, so reproductions are always helpful!

For me that worked well. Jest and Cypress now have correct types.

tsconfig.json

  "include": ["**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules", "cypress.config.ts", "cypress"]

cypress/tsconfig.json

  "compilerOptions": {
    ...
    "types": ["cypress"]
  },
  "include": ["../node_modules/cypress", "./**/*.ts"]

./tsconfig.json, include types and exclude properties in config file.

{
  "compilerOptions": {
    ...
    "types": ["jest", "node", "@types/testing-library__jest-dom"]
  },
  "exclude": ["node_modules", "cypress", "./cypress.config.ts"]
}

cypress/tsconfig.json

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "noEmit": true,
    "types": ["cypress"]
  },
  "exclude": ["node_modules"]
}

This works for me! Thanks!

Workarounds

Workaround 1

Use patch-package to patch node_modules/cypress/types/cypress-expect.d.ts with the following content (comment out the global expect type):

// Cypress adds chai expect and assert to global
// declare const expect: Chai.ExpectStatic
declare const assert: Chai.AssertStatic

Then add back the expect variable at the top of all of your test files:

declare const expect: Chai.ExpectStatic;

Workaround 2

Use local-cypress and import expect (more info):

import { expect } from 'local-cypress';

Hello,

We also have this problem with Karma/Jasmine on Angular.

Do you have any informations on if and when you will fix that issue ?

The workarounds aren’t clear enough to be honest, and they are “workaround” anyway which isn’t really good. All Angular projects updating to Cypress 10 will have a clashing problem with describe, it, expect, …

It’s kinda “critical” I think 😄

Have a good day!

For me only this config worked. All my cypress tests are in *.cy.jsx files, so I explicitly excluded them too

  "exclude": [
    "node_modules",
    "cypress",
    "cypress.config.ts",
    "**/*.cy.jsx"
  ],

what about Cypress Component Tests? It doesn’t seem that it can be solved as simple as adding cypress.config.ts to tsconfig.json exclude 🤔

Did anyone find a solution to make jest work with Cypress Component Testing?

Note: I also had to add "files": ["../cypress.config.ts"], to the cypress/tsconfig.json after adding "exclude": ["cypress.config.ts"] to the tsconfig.json.

It’s still not working for nrwl/nx monorepo. If you exlucde jest tests in tsconfig.json and then try to import any other lib defined in tsconfig.base.json paths, it won’t work. You can see details here https://github.com/nrwl/nx/issues/19569

and repo here https://github.com/tar-aldev/nrwl-nx-cypress-component-testing-issues/tree/with-suggested-cypresss-docs-fix

The problem is multiple tools declaring matching globals. This issue could be opened in Jest and be just as relevant.

It looks like Jest has a module API that exposes these: https://jestjs.io/docs/api

import { expect } from '@globals/jest'

There is also something similar via local-cypress: https://www.npmjs.com/package/local-cypress

Surely this combination could let you both

import { expect } from '@jest/globals'
import { expect } from 'local-cypress'

Or you can use

/// <reference types= "cypress" />

For Cypress files. These are the workarounds suggested above, too: https://github.com/cypress-io/cypress/issues/22059#issuecomment-1145234159. Have you tried these, @etsraphael?

@maccurt can you try these? There is no magic way to make it work, you’ll need to make sure each spec file knows which test runner / tool it’s associated with. It doesn’t look like you’ve tried that yet. Give one of these a try, it should fix your issue.

As an aside, I don’t know why any framework still uses global variables. I have run into this too, but the reality is if there are multiple types assigned to the same variable (eg expect) there isn’t a good way for the IDE to “know” which one to use reliability without imports or references.

I’m not sure if there’s any edge cases with local-cypress - if not, I’d love to see this in core, but I suspect there are some issues that would need to be ironed out.

Here is what I did (Angular, Jasmine, Cypress). Hope it also somewhat helps with Jest

In my tsconfig.json I added this:

"include": ["src/**/*.ts"]

I then created a tsconfig.json file under cypress/ with the following content, which I basically took from the cypress webpage:

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "target": "es5",
    "lib": ["es5", "dom"],
    "types": ["cypress", "node"]
  },
  "include": ["**/*.ts"]
}

Then I restarted the TS server in VS code and tried to run both test kinds: ng test and npx cypress open

Both work and all IDEA errors are gone

@karlhorky thanks for posting, looks like you’re not the only one having issues with Cypress + Jest type collisions.

I was able to reproduce this with the repos linked @karlhorky @mrdrogdrog @wKovacs64. When I checked out the dep update to v10, all of the Jest tests started complaining.

We didn’t change our globals very much for v10. For all the cases I reproduced, it was fixed by adding ./cypress.config.ts to the tsconfig.exclude property. Since the cypress.config.ts is being included in the type checking, it is loading the cypress types which is polluting your jest tests.

Thank you for sharing this @ZachJW34 , but this seems does not work for me, maybe you could help me. I still see errors which is listed at very first messge

structure: cypress - cypress tests - tsconfig.json has "exclude": ["../cypress.config.ts"] src - unit test using jest cypress.config.ts

tsconfig.json

    "compilerOptions": {
        "baseUrl": ".",
        "esModuleInterop": true,
        "lib": ["dom", "esnext"],
        "noEmit": true,
        "skipLibCheck": true,
        "strict": true
    },
    "include": ["../../node_modules/cypress", "**/*"],
    "exclude": ["../cypress.config.ts"]
}```

This is very frustrating. I think I don’t want to install cypress into my Angular project, but I want to do Component testing. Very hard to bring this to my team, when it break all the asserts for our unit test. Let us make sure also our custom commands still work with this implementation, etc. I notice I get one thing working, then it breaks something else. WE CAN NOT ACCEPT INSTALLING THIS package if it breaks our unit test asserts. I AM VERY SUPRISED this was allowed to get past QA at cypress. I give a lot of grace, but this is breaking a brand new angular project with YOUR INSTRUCTIONS. I guess I am frustrated because I really want to use component testing, but I think I will separate my MY CYPRESS project from my Angular project so they do not collide AND NOT DO COMPONENT TESTING. All I ask is you follow your own instructions and see it breaks. Just do a simple NG new my-project and then install cypress, RIGHT OUT THE BOX IT IS BROKE…

Please see our docs for recommendations on how to avoid clashing. Docs were updated in this PR: https://github.com/cypress-io/cypress-documentation/pull/5514

We investigated ways to automatically avoid this in our product, but didn’t come to a clear solution unfortunately.

I’d appreciate some pointers. @ZachJW34 's answer works inside the IDE. We are using a NextJS app, and when we run yarn check-types, which is using tsc --noEmit --pretty, all the expect().xxx are still reported as not being of type Assertion. What are we missing?

Thanks in advance.

Was a solution ever found for this? Going absolutely insane trying to get it to work. I have tried excluding cypress in every imaginable way, putting it in every imaginable tsconfig we have and it just won’t work. Trying things near enough at random now as it’s so frustrating.

For me only this config worked. All my cypress tests are in *.cy.jsx files, so I explicitly excluded them too

  "exclude": [
    "node_modules",
    "cypress",
    "cypress.config.ts",
    "**/*.cy.jsx"
  ],

that worked very well for me, the “cypress” which solved the problem with me!

For me only this config worked. All my cypress tests are in *.cy.jsx files, so I explicitly excluded them too

  "exclude": [
    "node_modules",
    "cypress",
    "cypress.config.ts",
    "**/*.cy.jsx"
  ],

Thanks, this worked for me!

@RafaPolit can you create another issue for what you are seeing? tsc --noEmit will use your tsconfig.json, so if you have it structured properly you should be able to exclude any Cypress configuration files and tests. For example, you could have

"exclude": [
    "node_modules",
    "cypress.config.ts",
    "cypress/**/*.ts"
  ]

and it should work. You could add another tsconfig.cypress.json and include your Cypress files if you wanted to typecheck those as well.

@denieler Same advice, though it can be admittedly harder to configure with CT tests since they live alongside components but you can use globs to capture an exclude any *.cy.ts files. If this doesn’t work for you, please open another issue. Types can be hard to configure, so reproductions are always helpful!

@ZachJW34 this would not work, no doubts, at least for IDE it’s impossible right now to apply one tsconfig file for one set of files and another tsconfig for another set of files

what about Cypress Component Tests? It doesn’t seem that it can be solved as simple as adding cypress.config.ts to tsconfig.json exclude 🤔

Other than adding /// reference on a file by file basis, I am unsure if there is any real fix other than test runners exporting their own expect as module. This is one of the many reasons global variables are not good, it’s hard to know who defines them and how to type them.

I think Cypress should have a frontend module bundle that does

import { expect, cy, Cypress, describe, it } from 'cypress'

as suggested above in this post: https://github.com/cypress-io/cypress/issues/22059#issuecomment-1145234159. It looks like there is already a package that partially implements this: https://github.com/bahmutov/local-cypress#readme

Even if these are dynamically injected at runtime, the exports should just be type safety wrappers and no-ops, in the same way `import { defineConfig } from ‘cypress’ is a no-op at runtime - it just adds type safety.

I am unsure on the complexity of this or if we are looking to implement this soon. Is someone interested on working on this? If so, I can propose this internally to our product team to get requirements (if we decide to prioritize it).

This is still an issue with a brand new install of Angular and cypress as of 7/12/2023… Is there an official fix/documentation

“dependencies”: { “@angular/animations”: “^16.1.0”, “@angular/common”: “^16.1.0”, “@angular/compiler”: “^16.1.0”, “@angular/core”: “^16.1.0”, “@angular/forms”: “^16.1.0”, “@angular/platform-browser”: “^16.1.0”, “@angular/platform-browser-dynamic”: “^16.1.0”, “@angular/router”: “^16.1.0”, “rxjs”: “~7.8.0”, “tslib”: “^2.3.0”, “zone.js”: “~0.13.0” }, “devDependencies”: { “@angular-devkit/build-angular”: “^16.1.1”, “@angular/cli”: “~16.1.1”, “@angular/compiler-cli”: “^16.1.0”, “@types/jasmine”: “~4.3.0”, “cypress”: “^12.17.1”, “jasmine-core”: “~4.6.0”, “karma”: “~6.4.0”, “karma-chrome-launcher”: “~3.2.0”, “karma-coverage”: “~2.2.0”, “karma-jasmine”: “~5.1.0”, “karma-jasmine-html-reporter”: “~2.1.0”, “typescript”: “~5.1.3” }

@mjhenkes why the issue is closed? I’ve installed Cypress 12. cypress.config.ts is still installed to the root folder and tsconfig.json doesn’t have ‘exclude cypress.config.ts’ instruction. https://github.com/cypress-io/cypress/issues/22059#issuecomment-1148921141 Has too many upvotes Can this be done automatically by configuration process?

I thought I’d post an example utilizing the Solution Style tsconfig pattern. You have to set up the includes/excludes properly, but once you do the IDE should support Cypress + Jest globals in the same repo.

This is an example utilizing React + Vitest + Cypress: https://github.com/ZachJW34/cypress-react-vitest

This won’t work for all projects such as create-react-app as they don’t support this format

I’ve created https://github.com/cypress-io/cypress-documentation/issues/4559 against the docs repo so that we can document the solution there. Thanks everybody for reporting and providing examples, and @ZachJW34 for pointing out the right step to take.

Good point @arelra about maybe mentioning this in the Migration Guide and as part of migration itself, we will consider both.

Going to keep this issue open a little longer since it does seem like we somebody for whom exclude is not working yet. @kuantayevs am I reading your structure correctly, in that your tsconfig.json is nested inside your cypress folder?

adding ./cypress.config.ts to the tsconfig.exclude property Thank you @ZachJW34 it helps for me too

@lmiller1990 Regarding your last comment, maybe https://www.npmjs.com/package/@jest/globals can be a point for inspiration. I’m using this package to avoid globally typing “Jest globals” and it works very well.

If you are using eslint as well, don’t forget to add an override to redefine the parserOptions.project, otherwise eslint will complain that no project file is associated with the cypress specs:

{
	...
	"overrides": [
		{
			"files": ["cypress/**/*.ts", "cypress.config.ts"],
			"parserOptions": {
				"project": "./cypress/tsconfig.json"
			},
			...
		},
		...
	],
	"parser": "@typescript-eslint/parser",
	"parserOptions": {
		"project": "./tsconfig.json",
		...
	},
}

Hello!

Like other people, I have component tests (Cypress) and unit tests (Jest) in the same folder, along with the original code.

I read all the previous comments, and maybe I missed this, but I managed to solve the problem in a very simple way, in my opinion.

It’s not the best thing in the world, but honestly, it’s pretty simple.

You just need to add /// <reference types="jest" /> at the top of your Jest unit test.

My tsconfig.json is configured 100% for Cypress.

{
  "compilerOptons": {
    ...
    "types": ["cypress"]
  },
  "include": ["src", "cypress.d.ts"],
}

Downside

For some reason unknown to me, due to my lack of experience with TypeScript, component tests with Cypress are now com two types, the main being Jest and the secondary being Cypress.

So far this hasn’t caused any problems, but it looks like the cause is indeed /// <reference types="jest"> in the *.test.ts files.

image

Since I added "./cypress.config.ts" to the "exclude" list in tsconfig.json, the following error started showing up only on CI (Github Actions), although it doesn’t happen locally (MacBook Pro w/ M1) 🤔

Type error: Cannot find name ‘cy’.

  1 | describe("...", () => {
  2 |   it("...", () => {
> 3 |     cy.visit("http://localhost:3000/");
    |     ^

the github action “cypress-io/github-action” still generates a cypress.json

Which version are you on? Try upgrading to v4 - this made this problem disappear for us

Note: I also had to add "files": ["../cypress.config.ts"], to the cypress/tsconfig.json after adding "exclude": ["cypress.config.ts"] to the tsconfig.json.

@SchroederSteffen why did you need to do this? (eg. what were the details of what was happening without this change?)

I didn’t need to do this, but maybe I and others would want to for some reason…

Yes, sorry for not clarifying! 🤦‍♂️ It was needed, because otherwise the typescript-eslint plugin would raise the following error:

C:\<project-directory>\cypress.config.ts
  0:0  error  Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
The file does not match your project config: cypress.config.ts.
The file must be included in at least one of the projects provided

Note: Our ESLint config selects files as follows:

  overrides: [
    {
      files: ['*.ts'],
      parserOptions: {
        project: ['tsconfig.json'],
      },
      extends: [
        'plugin:@typescript-eslint/recommended',
        'plugin:@typescript-eslint/recommended-requiring-type-checking',
        [...],
      ],
      [...],
      overrides: [
        {
          files: ['cypress/**/*.ts'],
          parserOptions: {
            project: ['cypress/tsconfig.json'],
          },
          extends: ['plugin:cypress/recommended'],
        },
      ]
    }
  ]

@karlhorky @ZachJW34 Thanks for all your input here

We had the same issue blocking our upgrade to v10.

Adding cypress.config.[js|ts] to tsconfig.json’s exclude property worked for us too.

I would also like to echo that this issue be elevated to the v10 migration guide or made much clearer in the documentation please. It is very difficult to diagnose and remedy without any pointers.

Thanks