Bird
Raised Fist0
Cypresstesting~15 mins

Cypress.Commands.add() - Deep Dive

Choose your learning style10 modes available
Overview - Cypress.Commands.add()
What is it?
Cypress.Commands.add() is a way to create your own custom commands in Cypress tests. It lets you bundle repeated steps into a single command with a name you choose. This makes your tests cleaner and easier to read. Instead of writing the same code many times, you write it once and reuse it.
Why it matters
Without custom commands, test code becomes long, repetitive, and hard to maintain. If you change a common step, you must update it everywhere. Custom commands solve this by centralizing repeated actions. This saves time, reduces mistakes, and makes tests more reliable and understandable.
Where it fits
Before learning Cypress.Commands.add(), you should know basic Cypress test writing and how to select elements on a page. After mastering custom commands, you can learn about Cypress plugins and advanced test architecture to build scalable test suites.
Mental Model
Core Idea
Cypress.Commands.add() lets you create named shortcuts for repeated test steps to keep tests simple and DRY (Don't Repeat Yourself).
Think of it like...
It's like creating a recipe card for a dish you cook often. Instead of remembering every step each time, you just follow your saved recipe card.
┌───────────────────────────────┐
│ Cypress Test File             │
│ ┌─────────────────────────┐ │
│ │ Custom Command:          │ │
│ │ 'login'                 │ │
│ │ ──────────────────────  │ │
│ │ Steps:                  │ │
│ │ - Visit login page       │ │
│ │ - Enter username         │ │
│ │ - Enter password         │ │
│ │ - Click submit           │ │
│ └─────────────────────────┘ │
│ Usage: cy.login()            │
└───────────────────────────────┘
Build-Up - 6 Steps
1
FoundationWhat is Cypress.Commands.add()
🤔
Concept: Introduction to the method that creates custom commands in Cypress.
Cypress.Commands.add() is a function where you give a name and a function to define a new command. For example, you can add a command called 'login' that performs login steps. This command can then be used in your tests like cy.login().
Result
You get a new command available in all your tests that runs the steps you defined.
Understanding that Cypress.Commands.add() extends Cypress with your own commands helps keep tests clean and reusable.
2
FoundationBasic Syntax and Usage
🤔
Concept: How to write and call a simple custom command.
Example: Cypress.Commands.add('login', () => { cy.visit('/login') cy.get('#user').type('user1') cy.get('#pass').type('password') cy.get('button[type=submit]').click() }) Then in a test: cy.login()
Result
When cy.login() runs, it performs all the steps inside the function automatically.
Knowing the syntax lets you create commands that hide complex steps behind simple names.
3
IntermediatePassing Arguments to Commands
🤔Before reading on: do you think custom commands can accept inputs like functions? Commit to your answer.
Concept: Custom commands can take parameters to make them flexible for different test data.
You can define commands that accept arguments: Cypress.Commands.add('login', (username, password) => { cy.visit('/login') cy.get('#user').type(username) cy.get('#pass').type(password) cy.get('button[type=submit]').click() }) Usage: cy.login('user1', 'password')
Result
The command runs with the given username and password, allowing reuse with different users.
Allowing parameters makes commands adaptable and avoids hardcoding values.
4
IntermediateOverwriting Existing Commands
🤔Before reading on: can you overwrite built-in Cypress commands with Cypress.Commands.add()? Commit to yes or no.
Concept: You can replace or extend existing Cypress commands to customize behavior.
Use Cypress.Commands.overwrite() to change how a command works: Cypress.Commands.overwrite('visit', (originalFn, url, options) => { // Add logging console.log('Visiting:', url) return originalFn(url, options) }) Now every cy.visit() logs the URL before visiting.
Result
Built-in commands can be customized globally to add features like logging or waiting.
Overwriting commands lets you inject behavior without changing every test.
5
AdvancedChaining and Returning Values
🤔Before reading on: do you think custom commands can return values to chain further commands? Commit to yes or no.
Concept: Custom commands can return Cypress chains to allow chaining and further commands.
Example: Cypress.Commands.add('getByData', (selector) => { return cy.get(`[data-test=${selector}]`) }) Usage: cy.getByData('submit').click()
Result
The custom command returns a Cypress chainable object, so you can continue chaining commands naturally.
Returning Cypress chains from commands preserves the fluent style and test readability.
6
ExpertAvoiding Common Pitfalls with Custom Commands
🤔Before reading on: do you think custom commands run immediately or wait for Cypress commands inside? Commit to your answer.
Concept: Understanding Cypress command queue and asynchronous behavior is key to writing reliable custom commands.
Cypress commands are asynchronous and queued. Custom commands must return Cypress chains to ensure proper timing. Forgetting to return or mixing sync code causes flaky tests. Example mistake: Cypress.Commands.add('badCommand', () => { cy.get('#btn').click() // Missing return causes timing issues }) Correct: Cypress.Commands.add('goodCommand', () => { return cy.get('#btn').click() })
Result
Properly returning Cypress chains ensures commands run in the right order and tests are stable.
Knowing Cypress's command queue prevents subtle bugs and flaky tests when creating custom commands.
Under the Hood
Cypress.Commands.add() registers a new command name and function in Cypress's internal command registry. When you call cy.yourCommand(), Cypress queues the function to run in order with other commands. Cypress commands are asynchronous but appear synchronous because Cypress manages the queue and waits for each command to finish before running the next. Custom commands must return Cypress chainables to integrate into this queue properly.
Why designed this way?
Cypress was designed to make asynchronous browser testing feel synchronous and easy. The command queue and chaining model hide complex timing issues. Custom commands fit into this model by extending the queue with user-defined steps. This design avoids callback hell and makes tests readable and reliable.
┌───────────────────────────────┐
│ Cypress Command Registry       │
│ ┌─────────────────────────┐ │
│ │ Registered Commands      │ │
│ │ - visit                 │ │
│ │ - get                   │ │
│ │ - login (custom)        │ │
│ └─────────────────────────┘ │
│                               │
│ cy.login() called             │
│ ↓                             │
│ Command Queue:                │
│ [visit('/login'), get('#user'), type('user1'), ...] │
│ ↓                             │
│ Cypress runs commands in order│
└───────────────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does Cypress.Commands.add() immediately run the command when defined? Commit yes or no.
Common Belief:People often think adding a command runs it right away.
Tap to reveal reality
Reality:Adding a command only registers it; the command runs only when called in a test.
Why it matters:Thinking commands run on add leads to confusion about test flow and timing.
Quick: Can you use regular JavaScript return values from custom commands to pass data? Commit yes or no.
Common Belief:Some believe custom commands can return values like normal functions to pass data.
Tap to reveal reality
Reality:Custom commands return Cypress chainables, not direct values, because of asynchronous execution.
Why it matters:Expecting direct returns causes errors and misunderstanding of Cypress's async model.
Quick: Can you safely mix synchronous code and Cypress commands inside a custom command? Commit yes or no.
Common Belief:Many think mixing sync code with Cypress commands inside custom commands is fine.
Tap to reveal reality
Reality:Mixing sync code without returning chains can cause timing issues and flaky tests.
Why it matters:Ignoring Cypress's command queue leads to unpredictable test failures.
Quick: Does overwriting a command with Cypress.Commands.add() work? Commit yes or no.
Common Belief:Some believe Cypress.Commands.add() can overwrite existing commands.
Tap to reveal reality
Reality:You must use Cypress.Commands.overwrite() to change existing commands; add() only creates new ones.
Why it matters:Using add() to overwrite silently fails, causing unexpected test behavior.
Expert Zone
1
Custom commands can be chained with .then() to access resolved values, bridging Cypress chains and JavaScript promises.
2
Using TypeScript with custom commands requires declaration merging to get proper autocomplete and type safety.
3
Custom commands can be organized into separate files and loaded via support/index.js for better test suite structure.
When NOT to use
Avoid custom commands for one-off or very simple steps that reduce readability. Instead, write inline commands. For complex test flows, consider Cypress plugins or task commands for backend interactions.
Production Patterns
In real projects, teams create custom commands for login, form filling, and navigation to reduce duplication. Overwriting commands adds logging or error handling globally. Commands are grouped by feature in support files for maintainability.
Connections
Function Abstraction in Programming
Cypress.Commands.add() builds on the idea of creating reusable functions to simplify code.
Understanding function abstraction helps grasp why custom commands reduce repetition and improve test clarity.
Asynchronous Programming
Custom commands rely on Cypress's asynchronous command queue to manage timing.
Knowing async programming concepts clarifies why commands return chainables and how Cypress controls execution order.
Recipe Writing
Both involve writing a set of steps once to reuse multiple times.
Seeing custom commands as recipes helps appreciate their role in making tests easier to write and maintain.
Common Pitfalls
#1Not returning Cypress chainables from a custom command causes timing issues.
Wrong approach:Cypress.Commands.add('clickButton', () => { cy.get('#btn').click() // No return statement })
Correct approach:Cypress.Commands.add('clickButton', () => { return cy.get('#btn').click() })
Root cause:Forgetting to return breaks Cypress's command queue, causing commands to run out of order.
#2Trying to overwrite a command using Cypress.Commands.add() instead of overwrite().
Wrong approach:Cypress.Commands.add('visit', (url) => { cy.log('Visiting ' + url) cy.visit(url) })
Correct approach:Cypress.Commands.overwrite('visit', (originalFn, url) => { cy.log('Visiting ' + url) return originalFn(url) })
Root cause:add() only creates new commands; overwrite() is needed to replace existing ones.
#3Hardcoding test data inside custom commands reduces flexibility.
Wrong approach:Cypress.Commands.add('login', () => { cy.get('#user').type('fixedUser') cy.get('#pass').type('fixedPass') })
Correct approach:Cypress.Commands.add('login', (user, pass) => { cy.get('#user').type(user) cy.get('#pass').type(pass) })
Root cause:Not using parameters limits reuse and forces code changes for different data.
Key Takeaways
Cypress.Commands.add() creates reusable custom commands that simplify and clean up test code.
Custom commands must return Cypress chainables to integrate properly with Cypress's asynchronous command queue.
Passing arguments to commands makes them flexible and reusable with different data.
Overwriting existing commands requires Cypress.Commands.overwrite(), not add().
Understanding Cypress's command queue and async model is essential to avoid flaky tests when writing custom commands.

Practice

(1/5)
1. What is the main purpose of Cypress.Commands.add() in Cypress testing?
easy
A. To configure browser settings
B. To add new test cases automatically
C. To create reusable custom commands for cleaner test code
D. To generate test reports

Solution

  1. Step 1: Understand the function of Cypress.Commands.add()

    This method allows you to define your own commands that you can reuse in tests.
  2. Step 2: Identify the benefit of reusable commands

    Reusable commands help keep test code clean and avoid repetition.
  3. Final Answer:

    To create reusable custom commands for cleaner test code -> Option C
  4. Quick Check:

    Reusable commands = Cleaner code [OK]
Hint: Remember: add() creates custom commands you can reuse [OK]
Common Mistakes:
  • Thinking it adds test cases automatically
  • Confusing it with test report generation
  • Assuming it changes browser settings
2. Which of the following is the correct syntax to add a custom command named login in Cypress?
easy
A. Cypress.Commands.create('login', () => { /* code */ })
B. Cypress.addCommand('login', () => { /* code */ })
C. Commands.add('login', () => { /* code */ })
D. Cypress.Commands.add('login', () => { /* code */ })

Solution

  1. Step 1: Recall the correct method name

    The correct method to add commands is Cypress.Commands.add().
  2. Step 2: Check the syntax format

    The first argument is the command name as a string, the second is a function.
  3. Final Answer:

    Cypress.Commands.add('login', () => { /* code */ }) -> Option D
  4. Quick Check:

    Correct method and syntax = Cypress.Commands.add('login', () => { /* code */ }) [OK]
Hint: Use Cypress.Commands.add('name', function) always [OK]
Common Mistakes:
  • Using Cypress.addCommand instead of Commands.add
  • Omitting Cypress.Commands prefix
  • Using create instead of add
3. Given the code below, what will be the output when cy.login() is called in a test?
 Cypress.Commands.add('login', (username, password) => {
  cy.get('#user').type(username)
  cy.get('#pass').type(password)
  cy.get('#submit').click()
})

// In test
cy.login('user1', 'pass1')
medium
A. Types 'user1' in #user, 'pass1' in #pass, then clicks #submit
B. Throws an error because login needs no parameters
C. Clicks #submit without typing anything
D. Types 'user1' in #pass and 'pass1' in #user

Solution

  1. Step 1: Analyze the custom command parameters

    The command takes username and password and types them into respective fields.
  2. Step 2: Follow the command actions

    It types username into '#user', password into '#pass', then clicks '#submit'.
  3. Final Answer:

    Types 'user1' in #user, 'pass1' in #pass, then clicks #submit -> Option A
  4. Quick Check:

    Parameters map to correct fields = Types 'user1' in #user, 'pass1' in #pass, then clicks #submit [OK]
Hint: Match parameters to fields carefully [OK]
Common Mistakes:
  • Assuming no parameters are needed
  • Mixing up username and password fields
  • Ignoring the click action
4. Identify the error in the following custom command definition:
 Cypress.Commands.add('clickButton', () => {
  cy.get('#button')
  cy.click()
})
medium
A. Missing chaining of click() to cy.get()
B. Wrong command name, should be clickbutton
C. Should use cy.find() instead of cy.get()
D. No error, code is correct

Solution

  1. Step 1: Review the command chaining

    cy.get('#button') returns an element, but cy.click() is called separately, not chained.
  2. Step 2: Understand correct chaining

    Click should be chained to the element: cy.get('#button').click().
  3. Final Answer:

    Missing chaining of click() to cy.get() -> Option A
  4. Quick Check:

    Method chaining required = Missing chaining of click() to cy.get() [OK]
Hint: Always chain commands like cy.get(...).click() [OK]
Common Mistakes:
  • Calling cy.click() separately
  • Changing command name unnecessarily
  • Using cy.find() instead of cy.get() without reason
5. You want to create a custom command fillForm that fills multiple fields and submits a form. Which approach correctly uses Cypress.Commands.add() to accept an object with field values and perform the actions?
hard
A. Cypress.Commands.add('fillForm', () => { cy.get('#name').type(name) cy.get('#email').type(email) cy.get('#submit').click() })
B. Cypress.Commands.add('fillForm', (data) => { cy.get('#name').type(data.name) cy.get('#email').type(data.email) cy.get('#submit').click() })
C. Cypress.Commands.add('fillForm', (name, email) => { cy.get('#name').type(name) cy.get('#email').type(email) cy.get('#submit').click() })
D. Cypress.Commands.add('fillForm', (data) => { cy.get('#name').type(data[0]) cy.get('#email').type(data[1]) cy.get('#submit').click() })

Solution

  1. Step 1: Understand parameter passing

    Using a single object parameter data with named properties is clear and flexible.
  2. Step 2: Check usage of object properties

    Accessing data.name and data.email matches the object keys.
  3. Final Answer:

    Cypress.Commands.add('fillForm', (data) => { cy.get('#name').type(data.name) cy.get('#email').type(data.email) cy.get('#submit').click() }) -> Option B
  4. Quick Check:

    Object parameter with named fields = Cypress.Commands.add('fillForm', (data) => { cy.get('#name').type(data.name) cy.get('#email').type(data.email) cy.get('#submit').click() }) [OK]
Hint: Pass one object with named fields for clarity [OK]
Common Mistakes:
  • Using undefined variables without parameters
  • Passing multiple parameters instead of one object
  • Accessing object fields by index instead of name
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard