100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Testing

Custom Commands

Learn how to extend Cypress with Cypress.Commands.add() to create reusable custom commands like cy.login(), reducing duplication across tests.

Selectors & CommandsAdvanced9 min readJul 10, 2026
Analogies

Custom Commands

As a test suite grows, the same sequences — logging in, adding an item to a cart, navigating to a specific settings page — tend to repeat across dozens of test files. Cypress.Commands.add() lets you package such a sequence once and reuse it anywhere as a first-class command, like cy.login('user@example.com', 'password123'), keeping tests DRY and making the intent of a test easier to read at a glance.

🏏

Cricket analogy: A team develops a signature fielding drill that gets reused in every practice session instead of re-explaining footwork from scratch each time, similar to how Cypress.Commands.add('login', ...) packages a repeated login sequence into one reusable cy.login() call instead of retyping it in every test.

Defining a Custom Command

A custom command is defined by calling Cypress.Commands.add(name, callbackFn) inside cypress/support/commands.js, which runs before every spec file since commands.js is imported from cypress/support/e2e.js. The callback function contains ordinary Cypress commands — cy.visit(), cy.get(), cy.type(), cy.click() — exactly as you'd write them inline in a test, just wrapped up under a reusable name.

🏏

Cricket analogy: A fielding coach documents the exact drill steps — starting position, first step, dive technique — in a training manual so any coach can run it identically, similar to how Cypress.Commands.add('login', (email, password) => {...}) documents the exact sequence of steps a custom command performs.

javascript
// cypress/support/commands.js
Cypress.Commands.add('login', (email, password) => {
  cy.visit('/login');
  cy.get('#email').type(email);
  cy.get('#password').type(password);
  cy.get('button[type=submit]').click();
  cy.url().should('include', '/dashboard');
});

// usage in a test
cy.login('user@example.com', 'password123');

Parent, Child, and Dual Commands

By default, Cypress.Commands.add() creates a parent command, which always starts a fresh chain regardless of what came before it, like cy.login() does. Passing { prevSubject: true } as an options argument creates a child command that receives the previous command's yielded subject as its first argument, letting you write something like cy.get('.price-list').getTotal(), where getTotal operates on the elements cy.get() already found. Setting prevSubject: 'optional' creates a dual command that can be used either way.

🏏

Cricket analogy: A fresh new-ball bowler starts an over from scratch regardless of what happened before, but a substitute fielder coming on continues exactly from where the previous fielder was standing, mirroring how a parent command starts fresh like cy.login() while a child command with {prevSubject: true} continues operating on the subject the previous command yielded.

Register custom commands in cypress/support/commands.js. If the project uses TypeScript, also add a declaration in cypress/support/index.d.ts extending the global Cypress.Chainable interface, so cy.login() gets full autocomplete and type checking in your editor.

Best Practices for Custom Commands

Custom commands work best for genuinely repeated, cohesive sequences like authentication or seeding test data via an API call. Simple one-off interactions don't need a custom command wrapper — plain cy.get().click() is often clearer than a needlessly abstracted cy.clickTheThing(). Keep each command focused on one responsibility, and avoid burying conditional branching logic (if/else based on page state) inside a command, since that kind of logic makes tests harder to reason about and debug.

🏏

Cricket analogy: A captain who calls for a DRS review on every single delivery dilutes its value and slows the game down, similar to how wrapping every single assertion in its own custom command can obscure what a test is actually checking and make failures harder to diagnose.

Be cautious about wrapping assertions inside custom commands, especially ones with vague names. A command like cy.verifyPage() that silently checks five unrelated things hides exactly which condition failed when a test breaks, and future readers can't tell what's actually being verified without opening commands.js.

  • Cypress.Commands.add(name, callbackFn) packages a repeated command sequence into a reusable named command.
  • Custom commands are typically registered in cypress/support/commands.js, loaded before every spec.
  • Parent commands (the default) always start a fresh chain; child commands, created with { prevSubject: true }, operate on the previous command's yielded subject.
  • Dual commands (prevSubject: 'optional') can be used either as a parent or a child.
  • TypeScript projects should extend Cypress.Chainable in index.d.ts for autocomplete and type safety.
  • Reserve custom commands for genuinely repeated, cohesive sequences rather than trivial one-off interactions.
  • Avoid burying vague, multi-purpose assertions or conditional logic inside a custom command, since it obscures failures.

Practice what you learned

Was this page helpful?

Topics covered

#Testing#CypressStudyNotes#TestingQA#CustomCommands#Custom#Commands#Defining#Command#StudyNotes#SkillVeris