add-cypress-custom-command-in-typescript
add-cypress-custom-command-in-typescript copied to clipboard
Custom commands that pass prevSubject don't work with TypeScript
The following code produces a compilation error: "Expected 1 arguments, but got 0."
// support/commands.ts
function doSomething(subject) {
console.log(subject);
}
Cypress.Commands.add(
"doSomething",
{
prevSubject: true
},
doSomething
);
declare namespace Cypress {
interface Chainable {
doSomething: typeof doSomething;
}
}
// integration/test.ts
it('works', () => {
cy.get('body').doSomething();
});
When you use typeof, Typescript declares the method using exactly the same interface of itself (in this case, (subject: any) => void).
When you call the method, you pass one less argument and Typescript understands this as a wrong call.
What you need to do is adapt the type of the method in the interface you are declaring, in your case, like this:
declare namespace Cypress {
interface Chainable {
doSomething: () => void;
}
}
Another example:
function doSomethingWithArgs(subject, arg1, arg2) {
console.log(subject, arg1, arg2);
return true;
}
Cypress.Commands.add(
"doSomethingWithArgs",
{
prevSubject: true
},
doSomethingWithArgs
);
declare namespace Cypress {
interface Chainable {
doSomethingWithArgs: (arg1: any, arg2: any) => boolean;
}
That will work!
If you have complex types for your args that you don't want to re-type inside the declaration you can do something like this:
type ChainableCommand<T extends (...args: any) => any>
= T extends (subject: infer I, ...args: infer P) => infer R
? (...args: P) => R
: never;
declare namespace Cypress {
interface Chainable<Subject> {
doSomethingWithArgs: ChainableCommand<typeof doSomethingWithArgs>;
}
}
Cypress.Commands.add('doSomethingWithArgs', { prevSubject: true }, doSomethingWithArgs);
function doSomethingWithArgs(subject: Cypress.Chainable, other: string, args: number) {
console.log(subject, other, args);
return subject;
}
Explanation: ChainableCommand
maps the type of a function A into the type of a function B that takes the same parameters as A excluding the first one and returns the same thing as A.
The solutions proposed here work fine with Cypress up to version 8.x but stopped working in Cypress 9. Any idea on how to make it work with the newest version of Cypress without having to resort to brutally overriding the type system by doing the following?
Cypress.Commands.add('myShinyNewCommand', { prevSubject: true }, (myCmd as unknown) as MyTypeWithoutSubject);
@momesana See https://github.com/cypress-io/cypress/pull/19003