Q26 of 38 · TypeScript

How do you add type definitions for a custom Cypress command in TypeScript?

TypeScriptMidtypescriptcypresscustom-commandsdeclaration-mergingchainable

Short answer

Short answer: Implement the command in a `commands.ts` file, then extend the `Cypress.Chainable` interface in a `.d.ts` declaration file (or in the same file with a `declare global` block) to register the new method with its typed parameters and return type.

Detail

Cypress uses declaration merging — you extend the Cypress.Chainable<Subject> interface to add your command's type signature to every cy. call.

Step 1 — Implement the command:

Cypress.Commands.add("login", (email: string, password: string) => {
  cy.request("POST", "/api/login", { email, password })
    .its("body.token").as("authToken");
});

Step 2 — Declare the type: In a .d.ts file or with declare global:

declare global {
  namespace Cypress {
    interface Chainable {
      login(email: string, password: string): Chainable<void>;
    }
  }
}

Step 3 — Include the declaration: Ensure the .d.ts file is included in your tsconfig.json's include array, or place it in a folder TypeScript already scans.

Overloads: Custom commands can have multiple overload signatures — declare them as multiple Chainable method signatures with different parameter types.

Subject typing: The generic Chainable<Subject> parameter represents the yielded subject. If your command yields an element, return Chainable<JQuery<HTMLElement>>.

// EXAMPLE

// support/commands.ts
Cypress.Commands.add("login", (email: string, password: string) => {
  cy.request({
    method: "POST",
    url: "/api/auth/login",
    body: { email, password },
  }).then(({ body }) => {
    window.localStorage.setItem("token", body.token);
  });
});

Cypress.Commands.add("dataCy", (selector: string) => {
  return cy.get(`[data-cy="${selector}"]`);
});

// support/commands.d.ts (or index.d.ts)
export {};  // make it a module
declare global {
  namespace Cypress {
    interface Chainable {
      /** Log in via API and set token in localStorage */
      login(email: string, password: string): Chainable<void>;
      /** Select element by data-cy attribute */
      dataCy(selector: string): Chainable<JQuery<HTMLElement>>;
    }
  }
}

// tsconfig.json — include the declaration
{
  "include": ["cypress/**/*.ts", "cypress/**/*.d.ts"]
}

// WHAT INTERVIEWERS LOOK FOR

Declaration merging via `Cypress.Chainable` extension. The three-step pattern (implement, declare, include). `Chainable<Subject>` return type. Placing the `declare global` in a module file (needs `export {}` or an import). This question tests both TypeScript declaration merging and Cypress-specific knowledge.

// COMMON PITFALL

Forgetting `export {}` in a `.d.ts` file that has no imports — without it, the file is an ambient module file and `declare global` behaves differently than expected.