In Cypress, what does the `prevSubject` option in `Cypress.Commands.add()` control?
answer
- The second argument to the add call
- Parent, child or dual
- Decides who receives the previous subject
- false, true, or the string optional
- element, document and window also validate
basics
~20 sIt decides how your command treats the subject the previous command yielded: false makes a parent that ignores it, true a child that receives it, optional a dual command that works either way. element, document and window also validate it.
solid answer
~40 s`Cypress.Commands.add(name, options, callbackFn)` reads `prevSubject` from its options object, and that one key does two jobs. It sets the command's **type**: `false`, the default, makes a **parent** command that starts a fresh chain and discards anything before it; `true` makes a **child** command that must be chained and receives the previous subject as the callback's first parameter, shifting your own arguments one place right; `'optional'` makes a **dual** command that works both ways, so the subject may be `undefined` and the body has to branch. It also declares **subject validation**: `'element'`, `'document'` and `'window'` make Cypress check the incoming subject and fail the command before your callback runs. The values combine in an array and are OR'd, which is how the built-in `.scrollTo()` is declared as `{ prevSubject: ['optional', 'element', 'window'] }`.
code
javascript · 17 lines// cypress/support/commands.js
Cypress.Commands.add(
'approve',
{ prevSubject: 'element' },
(subject, approverNote) => {
// subject arrives first; approverNote is shifted one place right
cy.wrap(subject).find('[data-cy=approver-note]').type(approverNote)
cy.wrap(subject).find('[data-cy=approve-report]').click()
return cy.wrap(subject)
}
)
// cypress/e2e/expense-reports.cy.js
cy.get('[data-cy=report-R-91]')
.approve('Within travel policy')
.should('contain', 'Approved')go deeper
Be ready to recite the three values and what each one makes: false a parent command, true a child, optional a dual. Also remember that a child command's callback receives the subject before your own arguments.
Explain that prevSubject sets the command type and declares subject validation at the same time, and that element, document and window are OR'd when given as an array.
Show that you pick the narrowest validation a helper can carry so a misuse fails on the calling line with a readable error rather than deep inside a shared support file.
Own the convention: which shared steps are allowed to be child commands at all, and how the team keeps subject rules and the TypeScript declarations for them from drifting apart.
## The option and where it lives `Cypress.Commands.add()` accepts either `(name, callbackFn)` or `(name, options, callbackFn)`, and the documented key in that options object is **`prevSubject`**, whose default is `false`. `Cypress.Commands.addAll()` takes the same options object and applies it to every command in the object you hand it, so a whole family of expense-report steps can share one subject rule in a single call. `Cypress.Commands.overwrite()`, by contrast, accepts **no** options object — an overwrite inherits the subject rules of the command it replaces. `prevSubject` answers exactly one question: *what should Cypress do with the subject the previous command yielded, before it calls my callback?* The answer settles two separate things — the command's **type**, and whether Cypress **validates** the subject's shape first. ## Parent, child and dual | `prevSubject` | Command type | How it chains | Callback signature | |---|---|---|---| | `false` (default) | parent | always begins a new chain | `(...yourArgs)` | | `true` | child | must follow another command | `(subject, ...yourArgs)` | | `'optional'` | dual | works with or without a subject | `(subjectOrUndefined, ...yourArgs)` | - A **parent** command behaves like `cy.visit()` or `cy.request()`. If you write `cy.get('[data-cy=report-R-91]').seedReceipts()` and `seedReceipts` is a parent command, the report row is silently thrown away and the command starts over on its own. - A **child** command behaves like `.click()`. Cypress passes the previous subject in as the callback's first parameter and **shifts your own arguments one place right** — the detail candidates most often get wrong when reading a support file. Invoking it with nothing in front of it fails immediately with an error saying a child command must be chained after a parent, because it operates on a previous subject. - A **dual** command behaves like `cy.scrollTo()` or `cy.screenshot()`. `subject` may be `undefined`, so the body branches on it. The Cypress documentation calls dual commands rare and says only a handful of built-ins use the form; reach for one only when the step genuinely reads well both ways. ## Validation is a second axis `'element'`, `'document'` and `'window'` are **validations**, not types. Passing one of them implies the command takes a subject, and additionally tells Cypress what that subject must be: - `'element'` requires one or more DOM elements, and also checks that the element is still attached to the document. A mismatch fails with a message of the form *"cy.markPaid() failed because it requires a DOM element"*, raised before your callback body executes. - `'document'` and `'window'` require the document or the window object respectively — the subjects that `cy.document()` and `cy.window()` yield. - Values combine in an array and are evaluated as **or**, never **and**: `{ prevSubject: ['element', 'document', 'window'] }` is how the built-in `.trigger()` is declared, and `{ prevSubject: ['optional', 'element', 'window'] }` is how `.scrollTo()` is declared. - `{ prevSubject: true }` on its own requires that *a* subject exists but checks nothing about its type, so your callback still has to cope with whatever arrived. Getting this right is worth the two extra words: a validated command fails on the line that misused it, with a message naming the command, instead of throwing something unhelpful three lines deeper inside a shared helper. ## What the command yields Whatever your callback returns becomes the subject for whatever is chained next. Two patterns cover almost every expense-report helper you will write: 1. **Return the subject unchanged** when the command is a step in a longer chain — the documented `.console()` example ends with `return subject` precisely so the chain carries on untouched. 2. **Return a `cy` chain** when the command should hand on something new: `return cy.wrap(subject).find('[data-cy=approver-note]')` yields that element, so a caller can go straight into `.should('contain', 'Within policy')`. Inside a child command it is usually worth passing the raw subject through `cy.wrap()` before doing anything with it, because that turns a plain jQuery object back into something the rest of the Cypress API can act on. ## Choosing a value 1. If the step is setup that stands on its own — seed a report, open the reimbursement queue — leave `prevSubject` off entirely and get a parent command. 2. If the step only makes sense against something already found on the page — approve *this* row, attach a receipt to *this* claim — use `{ prevSubject: 'element' }` rather than bare `true`, so a wrong subject is rejected with a readable error. 3. Reach for `'optional'` only when both readings are genuinely useful, and branch on `subject` in the first line of the callback so the two paths are obvious to the next reader. 4. Declare the same shape in your TypeScript `Chainable` entry, because Cypress infers the callback's parameter types from that declaration rather than the other way round.
- What happens if you call a Cypress command registered with `{ prevSubject: true }` as `cy.approve(...)`, with nothing before it?Cypress fails the test before your callback runs, reporting that you are trying to call a child command before a parent command and explaining that a child command must be chained because it operates on a previous subject. The fix is to chain it — `cy.get('[data-cy=report-R-91]').approve(...)` — or to re-register the step without `prevSubject` if it never needed a subject.
- How does `Cypress.Commands.addAll()` treat the `prevSubject` option?`Cypress.Commands.addAll(options, callbackObj)` applies the one options object to every command in `callbackObj`, so all of them become parent, child or dual together. It is convenient for a set of row-level expense helpers that share a subject rule, and wrong for a mixed bag — register those separately with `Cypress.Commands.add()` so each gets its own rule.
saying these in an interview costs you the question
- Says every custom command automatically receives the previous subject
- Thinks prevSubject can also be passed to Cypress.Commands.overwrite
- Forgets that a child command shifts its own arguments right
- Believes element and optional cannot appear in the same array