19

I am implementing a custom Cypress command in TypeScript:

// support/commands.ts
const login = () => {
    console.log('Logging in...');
};

Cypress.Commands.add('login', login);

declare namespace Cypress {
    interface Chainable {
        login: typeof login;
    }
}

I try to call it using:

describe('Login Scenario', () => {
    it('should allow a user to login', () => {
        cy.visit('/');
        cy.login();
    });
});

Yet, it seems the command is not set up:

TypeError: cy.login is not a function

If I write the command in pure JavaScript (removing the namespace declaration and updating the call to (cy as any).login();, it works.

What am I missing?

Audwin Oyong
  • 2,247
  • 3
  • 15
  • 32
Jonathan Petitcolas
  • 4,254
  • 4
  • 31
  • 42

4 Answers4

17

I fixed it by adding index.d.ts file in my commands folder. In this file I added something like this:

import { MyCustomType } from '../Types';

declare global {
  namespace Cypress {
    interface Chainable<Subject = any> {
      login(): Chainable<MyCustomType>;
    }
  }
}

If you don't import or export anything, just omit global namespace declaration:

declare namespace Cypress {
  interface Chainable<Subject = any> {
    login(): Chainable<MyCustomType>;
  }
}

Keep in mind that it won't work with Typesciprt < 2.3, because default generics type has to be supported.

Audwin Oyong
  • 2,247
  • 3
  • 15
  • 32
Krzysztof Grzybek
  • 8,818
  • 2
  • 31
  • 35
  • Is there any additional step to follow? Maybe you could give insights about your tsconfig? – Eric Burel Apr 15 '20 at 08:10
  • It should work right away. Currently, I don't work with Cypress so I can't share my tsconfig. Are you sure that your file is named with `.d.ts` suffix? – Krzysztof Grzybek Apr 16 '20 at 15:47
  • This only works for me if the index.d.ts file is in the same folder as the spec. – Jonathan Tuzman Sep 10 '20 at 16:36
  • Do you know why we need `declare global` when we are importing something? I'm importing cypress-wait-until – Rafael Lebre Nov 16 '20 at 15:32
  • 1
    If you don't have any import/export, a file is considered to be in the global namespace Take a look here: https://github.com/microsoft/TypeScript/issues/38592#issue-619054264, or here, point 7 https://www.techatbloomberg.com/blog/10-insights-adopting-typescript-at-scale/ – Krzysztof Grzybek Nov 16 '20 at 18:17
7

Here is what I use and I do not have to add

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

at the top of every file.

I have my custom typings under <projectroot>/cypress/support/index.d.ts

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

declare namespace Cypress {
  interface Chainable<Subject> {
    getByDataTest(tag: string): Chainable<any>
  }
}

And my <projectroot>/cypress/tsconfig.json looks like

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

And TypeScript is finally happy

describe('when I want to select by data test tag', () => {
  it('should select by data test tag', () => {
    cy.getByDataTest('yolo').should('exist')
  });
});
Audwin Oyong
  • 2,247
  • 3
  • 15
  • 32
GentryRiggen
  • 788
  • 10
  • 10
  • May I know where do you write the implementation of the method `getByDataTest` ? – RajKon Nov 02 '19 at 20:18
  • @RajKon const getDataTag = (tag: string, value: any) => `[data-${tag}="${value}"]`; const getDataTestTag = (value: any) => getDataTag('test', value); Cypress.Commands.add('getByDataTest', tag => cy.get(getDataTestTag(tag))); – GentryRiggen Nov 03 '19 at 21:47
  • 2
    you can use either `types` or `typeRoots`, not both. see http://www.typescriptlang.org/docs/handbook/tsconfig-json.html#types-typeroots-and-types – Uzi Jan 15 '20 at 16:19
  • And what if I use only js files, not typescript with cypress? Then namespace declarations can;t be used – Darksymphony May 12 '21 at 10:34
  • You might wanna restart VS, just in case if you still see the red squiggles from your linter. – Ε Г И І И О Aug 24 '21 at 12:19
7

I had the same issue and all solutions I found did not work for me. Having done everything from the official Cypress documentation and other solutions here I still got the error cy.login is not a function.

The problem was that I renamed every .js file to .ts and cypress/support/index.ts was not loaded any more, because per default Cypress only loads the JavaScript one. To fix it, you need to change it to .ts in cypress.json like that (the same with plugins file):

{
  "supportFile": "cypress/support/index.ts",
  "pluginsFile": "cypress/plugins/index.ts"
}

You can also add the part with declare namespace Cypress {... to commands.ts instead of creating an index.d.ts file, to have the declaration and implementation in the same file

Dimitri L.
  • 4,499
  • 1
  • 15
  • 19
  • Did you also have to change anything in your tsconfig file? I've tried setting the `supportFile` and `pluginsFile` paths to the `.ts` file, but I get the Cypress error: "Your pluginsFile is set to `/tests/e2e/plugins/index.ts`, but either the file is missing, it contains a syntax error, or threw an error when required. The pluginsFile must be a .js or .coffee file." – Colin Oct 01 '20 at 16:25
  • My `cypress/tsconfig.json` looks like that: `{ "compilerOptions": { "strict": true, "baseUrl": "../node_modules", "target": "es6", "module": "commonjs", "lib": ["es5", "dom"], "types": ["cypress"], "esModuleInterop": true, "allowSyntheticDefaultImports": true }, "include": ["**/*.ts"] }`. PS: comments do not allow proper block formatting – Dimitri L. Oct 05 '20 at 10:12
4

Custom commands might not get imported, In cypress version 10 using angular/typescript schematic, there is a e2e.ts rather than index in support folder that imports the commands, if not then it needs the index.ts file with below import command, also it needs to be added to cypress config as supportFile:

// cypress/support/e2e.ts 

// When a command from ./commands is ready to use, import with `import './commands'` syntax

   import './commands';

It might already be commented in the file as well

Khuram Niaz
  • 881
  • 10
  • 16