skip to content

In Cypress, what arguments does a `Cypress.Commands.overwrite()` callback receive?

level: middleimportance: should knowfreq 46%

answer

  1. Something is prepended to the argument list
  2. Order depends on the command being replaced
  3. The subject sits in the middle
  4. Delegating keeps the built-in behaviour
  5. No options object is accepted here

basics

~20 s

First the original implementation as originalFn, then the previous subject if the command being overwritten takes one, then the arguments the test passed. Call originalFn to keep the built-in behaviour; overwrite accepts no options object.

solid answer

~40 s

`Cypress.Commands.overwrite(name, callbackFn)` replaces an existing command everywhere, and Cypress invokes your callback with `originalFn` prepended to the real argument list. Overwriting the parent command `cy.visit()` gives you `(originalFn, url, options)`. Overwriting a child command puts the subject in between, so `.type()` gives `(originalFn, element, text, options)` — the overwrite inherits the `prevSubject` rules of the command it replaces, which is why `overwrite` takes no options object of its own. `originalFn` is the built-in implementation: invoke it, usually as the callback's return value, to run the original behaviour after adjusting the arguments, or deliberately skip it to replace the behaviour outright. Cypress rejects the registration when nothing by that name exists, reporting that no existing command exists by that name, and it rejects names that belong to queries rather than commands.

code

javascript · 18 lines
javascript
// cypress/support/commands.js
Cypress.Commands.overwrite('type', (originalFn, element, text, options) => {
  // originalFn, then the subject (.type() is a child command), then the args
  if (options && options.mask) {
    Cypress.log({
      $el: element,
      name: 'type',
      message: '*'.repeat(String(text).length),
    })

    options = { ...options, log: false }
  }

  return originalFn(element, text, options)
})

// cypress/e2e/reimbursement.cy.js
cy.get('[data-cy=reimbursement-account]').type('4242424242424242', { mask: true })

go deeper

for a junior

Recall that overwriting takes the command name and a callback, and that the callback's first parameter is the original implementation you can still call.

for a middle

Be ready to write the parameter list for both a parent and a child command from memory, and to explain why the subject appears in the middle for one and not the other.

for a senior

Show judgment about when an overwrite is the right tool at all, and how you keep one discoverable — a log entry, a named module, a comment at the call site — for the person debugging it later.

for a principal

An interviewer at this level expects a position on how many built-ins a suite may redefine, who reviews such a change, and how that convention holds up as more teams share the same support file.

## What Cypress hands your callback `Cypress.Commands.overwrite()` has one shape — `overwrite(name, callbackFn)`. When the overwritten command runs, Cypress builds the argument list the original would have received and puts **`originalFn` at the front of it**. Everything else keeps its normal position, which means the shape you must destructure depends entirely on the command you are replacing. | Command overwritten | Its `prevSubject` | Your callback's parameters | |---|---|---| | `cy.visit()` | parent | `(originalFn, url, options)` | | `cy.request()` | parent | `(originalFn, ...requestArgs)` | | `.type()` | `'element'` | `(originalFn, element, text, options)` | | `.screenshot()` | `'optional'` | `(originalFn, subjectOrUndefined, name, options)` | The rule is easier to remember than the table: **`originalFn`, then the subject if there is one, then whatever the test wrote inside the parentheses**. ## `originalFn` is the whole point `originalFn` is the built-in implementation, already bound to the command Cypress is running. You get three useful moves out of it: - **Normalise arguments, then delegate.** Merge a default into `options` and `return originalFn(element, text, options)`. This is the common case, and returning it keeps the original command's yielded subject flowing down the chain. - **Decorate around it.** Write a `Cypress.log()` entry of your own, pass `{ log: false }` down so the built-in stays quiet, then delegate. The Command Log then shows your version of the step instead of the raw one. - **Replace it.** Never call `originalFn` and the built-in behaviour simply does not happen. Nothing calls it for you, and Cypress does not warn you about it — which is exactly why an overwrite that silently swallows a command is so hard for the next reader to spot. ## What overwrite cannot do - **It takes no options object.** `Cypress.Commands.overwrite('type', { prevSubject: 'element' }, fn)` is not a supported call; the subject rules come from the original command, and there is nothing for you to redeclare. - **It cannot invent a name.** If no command exists by that name Cypress fails the registration, saying an existing command does not exist by that name. Creating a new step is `Cypress.Commands.add()`'s job, and `add()` refuses an existing built-in name in the opposite direction, telling you to use `overwrite()` instead. - **It cannot touch a query.** Queries carry a different callback contract, so Cypress guards the name and refuses, pointing you at `Cypress.Commands.overwriteQuery()`. - **It is not scoped.** An overwrite registered in your support file applies to every spec in the project for the rest of the run — there is no per-spec or per-suite form of it. ## A worked expense-report example Suppose reimbursement account numbers are typed into a form and you do not want them legible in the Command Log or in a recorded run. Overwriting `.type()` gives you one place to solve it for every spec: ```javascript Cypress.Commands.overwrite('type', (originalFn, element, text, options) => { if (options && options.mask) { Cypress.log({ $el: element, name: 'type', message: '*'.repeat(String(text).length) }) options = { ...options, log: false } } return originalFn(element, text, options) }) ``` Note the three moving parts: `element` is the subject, present only because `.type()` is a child command; `mask` is an option of your own invention that rides along in the same object; and the `return` hands the original command's result back so `.type()` still yields the element it always did. ## Rules of thumb 1. **Delegate by default.** Unless you mean to remove behaviour, end the callback with `return originalFn(...)` so the built-in still runs and still yields. 2. **Keep the arity honest.** Accept the same parameters the original documents, including the trailing `options`, rather than swallowing arguments you did not expect. 3. **Prefer a new name when the behaviour is new.** Overwriting is for adjusting a built-in everyone already understands; a genuinely different step is clearer as its own `Cypress.Commands.add()` command with a name that says what it does. 4. **Write the type declaration at the same time**, because an overwritten child or dual command needs its subject type spelled out for TypeScript to follow the call.

  • What does Cypress do if you call `Cypress.Commands.overwrite()` with a name no command uses?
    It fails the registration rather than quietly creating the command, reporting that it cannot overwrite that name because no existing command exists by it. That guard catches the common typo of overwriting `visits` or `getcookie`, and it is the reason a mis-spelled overwrite surfaces when the support file loads instead of on the spec that expected the new behaviour.
  • Why can overwriting a Cypress built-in be harder to review than adding a new command?
    An added command shows up as an unfamiliar name at the call site, so a reader knows to go and look it up. An overwrite changes a name everybody already believes they understand, from a file the spec never imports, for every spec in the project. Keeping overwrites few, small and clearly logged is what makes them reviewable.

saying these in an interview costs you the question

  • Thinks the callback receives only the original arguments
  • Expects Cypress to call originalFn automatically afterwards
  • Passes a prevSubject options object to overwrite
  • Assumes an overwrite only affects the spec that declared it