Cypress Namespaces For Custom Commands

How to group custom commands in a namespace object.

Cypress has a lot of built-in commands. You can add your own custom commands. If there are a lot of custom commands, it might be useful to group them by namespace. Here is one hack I found to do so:

1
2
3
4
// instead of "flat" cy.add commands
cy.add(2, 3).should('equal', 5)
// you can use custom namespace name
cy.math.add(2, 3).should('equal', 5)

VSCode showing help for the custom command "cy.math.add"

Let's do this.

TypeScript project

First, let's create a simple Cypress project. We will use TypeScript to make sure our namespaces and commands agree. We can use index.d.ts file to add the new custom command type definition to the cy object.

cypress/support/e2e.ts
1
2
3
4
5
6
// the support file has the custom command implementation
Cypress.Commands.add('add', (a: number, b: number) => {
expect(a, 'a').to.be.a('number')
expect(b, 'b').to.be.a('number')
return cy.wrap(a + b, { log: false })
})
cypress/support/index.d.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// the index.d.ts file has the "cy.add" definition
declare namespace Cypress {
/**
* Custom command type for adding two numbers
* and yielding the sum
*/
type addFn = (a: number, b: number) => Chainable<number>

interface Chainable {
/**
* Custom command for adding two numbers
*/
add: addFn
}
}

We can use the custom command cy.add from our spec

cypress/e2e/spec.cy.ts
1
2
3
it('uses the custom command add', () => {
cy.add(2, 3).should('equal', 5)
})

Test uses the custom command and passes

🎁 You can find the source code for this blog post in the public repo bahmutov/cypress-namespaces. The "normal" code above is located in the branch normal.

Namespace object

Let's copy our custom command into a namespace object called "math". We need to place the method in cy.math and we need to update the types.

cypress/support/e2e.ts
1
2
3
4
5
6
7
8
9
10
Cypress.Commands.add('add', (a: number, b: number) => {
expect(a, 'a').to.be.a('number')
expect(b, 'b').to.be.a('number')
return cy.wrap(a + b, { log: false })
})

// create the namespace "math" and add the custom command "add"
cy.math = {
add: cy.add,
}

Simple, right? We simply create an object cy.math and put references to the custom commands we want to be there. Let's update the types

cypress/support/index.d.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
declare namespace Cypress {
/**
* Custom command type for adding two numbers
* and yielding the sum
*/
type addFn = (a: number, b: number) => Chainable<number>

interface Chainable {
/**
* Custom commands for doing math operations
*/
math: {
/**
* Add two numbers
* @example cy.math.add(2, 3).should('equal', 5)
*/
add: addFn
}

/**
* Custom command for adding two numbers
* @deprecated Use `cy.math.add` instead
*/
add: addFn
}
}

Instead of removing the cy.add completely, I use @deprecated JSDoc tag.

Warning the user about directly using "cy.add"

In our tests we can safely use cy.math.add

cypress/e2e/spec.cy.ts
1
2
3
it('uses the custom namespace', () => {
cy.math.add(2, 3).should('equal', 5)
})

Works like a charm.