In Cypress, when do you reach for .its() and when for .invoke()?
answer
- Property versus method on the subject
- Dot notation walks nested paths
- Extra arguments go to the call
- One insists the value is not null
- The other insists it is callable
basics
~20 sCypress's .its(path) reads a property off the yielded subject and yields its value; .invoke(name, args) calls a method of that name and yields what it returns. A property holding a function comes back from .its() uncalled.
solid answer
~50 sBoth are child commands that dig into whatever the previous command yielded. `.its('borrower.name')` walks a dot-separated path - or a numeric index into an array - and yields the value it finds. `.invoke('renew', 14)` calls a method of that name on the subject, passing the extra arguments straight through, and yields the return value. As of Cypress 16 both are queries, so both re-read the subject while chained assertions retry. Where they differ is in what each insists on: `.its()` carries an implicit existence check and keeps retrying until the property is neither `null` nor `undefined`, while `.invoke()` drops that check but errors when the named property is not callable - *"returned a `number` value instead of a function"*. Reading a property that happens to hold a function gives you the function itself, not its result.
go deeper
Remember the one-line split: .its() reads a property, .invoke() calls a method. Be able to read a row count with .its('length') and an element's text with .invoke('text').
Explain the implicit checks. .its() retries until the value is not null or undefined and refuses a null subject; .invoke() drops that but errors when the property is not callable. Expect to be given an error message and asked which command to use.
An interviewer at this level expects you to say when neither is the right tool - when the value is on the page, query the page, so the assertion retries against what the user can actually see.
The judgement to own is how far a suite may read into application objects with .its() at all, and what that costs the day the application renames a field that no user-facing test would have noticed.
## Two ways into the same subject Once a Cypress command has yielded a subject, you usually want a piece of it rather than the whole thing: the `length` of a set of catalogue rows, the `dueDate` on a loan object, the rendered text of a due-date cell. `.its()` and `.invoke()` are the two commands for that. Both require a previous command - call one straight off `cy` and Cypress answers *"you are trying to call a child command before running a parent command"* - and both yield a new subject the rest of the chain works on. The distinction is simply **property versus method**. `.its()` reads. `.invoke()` calls. ## `.its()` reads a property `.its(path)` takes a string or a number and yields the value found at that path on the subject. - **Dot notation walks nested objects.** `cy.wrap(loan).its('borrower.name')` yields the name; you do not need a chain of `.its()` calls. - **A number is an array index.** `cy.wrap(['Dune', 'Neuromancer']).its(1)` yields `'Neuromancer'`. - **`length` works on anything array-like.** `cy.get('[data-cy=book-row]').its('length')` yields the count of matching rows. - **A function property is yielded, not called.** `cy.wrap({ due: () => '2026-10-01' }).its('due')` yields the function itself - handy when you want to drill into the function's own properties, and a surprise when you expected the date. - **It refuses a null or undefined subject.** `.its()` errors immediately rather than yielding `undefined` down the chain. `.its()` also carries an **implicit existence assertion**: it keeps retrying until the property is neither `null` nor `undefined`, which is what makes `cy.window().its('catalogue')` a workable way to wait for the application to publish something. There is one deliberate exception - it drops that requirement when the very next command is an assertion such as `not.exist`, `be.undefined`, `be.null`, `not.be.ok`, `eq` or `not.eq` - so `cy.window().its('debugTools').should('not.exist')` does what it says instead of timing out. ## `.invoke()` calls a method `.invoke(name, ...args)` looks the property up the same way, then calls it and yields the return value. Everything after the name is passed through as arguments, with no limit on how many. - `cy.get('[data-cy=due-date]').invoke('text')` yields the rendered text via jQuery's `text()`. - `cy.get('[data-cy=book-row]').invoke('attr', 'data-book-id')` yields the attribute. - `cy.wrap(loan).invoke('renew', 14)` yields whatever `renew(14)` returns. `.invoke()` deliberately drops the non-null existence check that `.its()` adds - a method that legitimately returns `undefined` should not fail your test. In exchange it adds a different one: if the named property is not callable, it errors with *"errored because the property: `length` returned a `number` value instead of a function"*, and the error text tells you to switch to `.its()`. ## The checks each one adds | | `.its(path)` | `.invoke(name, ...args)` | |---|---|---| | what it does | reads a property | calls a method | | extra arguments | none allowed | passed to the call | | retries until | the value is not null or undefined | chained assertions pass | | errors when | subject is null, or the property never appears | the property is not a function | | function property | yielded uncalled | called | ## Choosing between them 1. **Is the thing a value or a behaviour?** `dueDate` is a value, `renew()` is a behaviour. That answers it nine times out of ten. 2. **Do you need to pass arguments?** Only `.invoke()` takes them, so `attr`, `css` and `prop` on a jQuery subject are all `.invoke()` calls. 3. **Does calling it change anything?** If it does, prefer reading the resulting page state with a query rather than calling the method through the chain at all. 4. **Would `undefined` be a legitimate answer?** Then `.invoke()`, because `.its()` would keep retrying and eventually time out on a perfectly correct value. ## The errors you will actually meet - *"trying to call a child command before running a parent command"* - you used one of them as a parent command. Start with a query or `cy.wrap()`. - *"errored because the property: `dueDate` does not exist on your subject"* - it never appeared. Usually a typo in the path, or the application publishing it under a name you did not expect. - *"returned a `string` value instead of a function"* - you wanted `.its()`. - A yielded function where you wanted a value - you used `.its()` on a method and forgot to call it.
- How do you assert with Cypress .its() that a property does not exist?Chain the assertion straight on: `cy.window().its('debugTools').should('not.exist')`. `.its()` normally retries until the property is neither null nor undefined, but it skips that implicit check when the very next command is an assertion such as `not.exist`, `be.undefined`, `be.null`, `not.be.ok`, `eq` or `not.eq`.
- What does Cypress's .its() do with an array-like subject?A numeric argument is treated as an index, so `cy.wrap(['Dune', 'Neuromancer']).its(1)` yields `'Neuromancer'`. `.its('length')` yields the count, which is the usual way to work with how many rows a query matched without holding on to the elements themselves.
saying these in an interview costs you the question
- Expects .its() to call a function property
- Uses .invoke() to read a plain data property
- Thinks .its() or .invoke() can start a chain off cy
- Passes a callback to .invoke() instead of a method name
- Assumes .its() yields undefined for a missing property