cypress-io / cypress-documentation

Cypress Documentation for the Cypress App, API, Cypress Cloud, UI Coverage and Cypress Accessibility.
https://docs.cypress.io
MIT License
945 stars 1.05k forks source link

TypeScript documentation needs clarification #4043

Open TheDutchCoder opened 3 years ago

TheDutchCoder commented 3 years ago

Current behavior

The docs say that you can extend the Chainable interface for your custom commands, however this assumes that people use the outdated namespace declarations and linters will complain about it.

Furthermore, it prevents you from importing (external) types, as TS will throw an error on the Chainable Type 'Chainable' is not generic.ts(2315).

Desired behavior

The docs should mention that instead of using this:

// in cypress/support/index.ts
// load type definitions that come with Cypress module
/// <reference types="cypress" />

import type { MyType } from './types'

declare namespace Cypress {
  interface Chainable {
    /**
     * Custom command to select DOM element by data-cy attribute.
     * @example cy.dataCy('greeting')
     */
    dataCy(value: MyType): Chainable<Element> // This will throw a TS error.
  }
}

People should be using this:

// in cypress/support/index.ts
// load type definitions that come with Cypress module
/// <reference types="cypress" />

import type { MyType } from './types'

declare global {
  namespace Cypress {
    interface Chainable {
      /**
       * Custom command to select DOM element by data-cy attribute.
       * @example cy.dataCy('greeting')
       */
      dataCy(value: MyType): Chainable<Element> // This works
    }
  }
}

Test code to reproduce

Use typescript-eslint with default settings and the documentation example will not work with imported types.

Cypress Version

8.0.0

Other

It might be worth looking into ditching namespaces, as it's no longer recommended to use them by TS. They advise to use import/export.

jennifer-shehane commented 3 years ago

I'll move this issue over to our documentation repo for our team to consider there. Our documentation is open source and contributions are welcome!

devuxer commented 3 years ago

@TheDutchCoder,

No matter which version I use, I still get this linter error:

ES2015 module syntax is preferred over custom TypeScript modules and namespaces. eslint(@typescript-eslint/no-namespace)

You are correct, however, that the version in the documentation also results in a TypeScript error about Chainable not being generic.

ANRCorleone commented 2 years ago

I've just started using Cypress with Typescript and following the tutorial. To get around this I added this in a typescript declaration file index.d.ts inside the cypress > support folder

declare namespace Cypress {
    // add custom commands here
    interface Chainable {
        /**
         * Custom command to select DOM element by data-cy attribute.
         * @example cy.dataCy('greeting')
         */
        dataTest(value: string): Chainable<Element>
    }
}

then included the declaration file in tsconfig.json inside the cypress folder

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "es5",
      "dom"
    ],
    "types": [
      "cypress",
      "./support"
    ]
  },
  "include": [
    "**/*.ts"
  ]
}

then added the implementation in the commands.ts file inside cypress > support folder as usual

Cypress.Commands.add("dataTest", (value: string) => console.log(value));

then use the command inside a a test file in cypress > integration folder (test.ts)

it("works", () => {
    cy.visit("/");
    cy.dataTest("test");
});

Seems to be working okay as the editor will complain if we add a different name in commands.ts that is not in the declaration file - e.g. Cypress.Commands.add("randomName") will complain. I'm using Webstorm btw, guessing this should work as well in VSCode, etc. or will need some config in other editors.

What kinda bugs me is the index.ts in cypress > support folder is now empty 😄. Can add this in the docs if its an okay solution.

bvandenbon commented 1 year ago

What took me quite some time to figure out, was the supportFile setting, which was on false by default. It should point to the file that defines the custom commands.

import {defineConfig} from 'cypress'

export default defineConfig({

    e2e: {
        'baseUrl': 'http://localhost:4200',
        supportFile: 'cypress/support/e2e.ts'
    },

})