Resource library

QA How-To

Cypress custom commands: Examples

Build runnable cypress custom commands examples for typed queries, API data, cy.session login, child commands, secret masking, polling, tasks, and tests.

26 min read | 3,027 words

TL;DR

A strong Cypress custom commands example includes registration, TypeScript augmentation, yielded subject, state ownership, failure behavior, and security controls. The recipes here use public APIs for custom queries, API setup, `cy.session`, child commands, overwrites, polling, and `cy.task`.

Key Takeaways

  • Register command modules once from the support entry and keep global names in an owned catalog.
  • Implement DOM lookups as synchronous idempotent custom queries when whole-query retryability is required.
  • Yield created data from API setup commands instead of storing identities in global variables.
  • Build session IDs from role, user, tenant, and every other input that changes authentication state.
  • Use typed child commands only when the operation has a meaningful previous-element boundary.
  • Bound asynchronous polling, stop on terminal failure, and never retry a side effect that creates duplicate work.
  • Validate every Node task input because task code executes with server-side process access.

The most useful cypress custom commands example is a complete contract, not a one-line wrapper around cy.get(). It shows registration, TypeScript declaration, call-site behavior, yielded subject, side effects, failure evidence, and the reason a command is preferable to a plain function.

This guide presents a small command library for a project-management application. The examples cover a retryable data query, API data creation, cached role login, child commands, secret-safe typing, asynchronous job polling, Node tasks, and command-focused tests. Every Cypress method shown is a public API available in current Cypress documentation.

Application endpoints such as /api/test/login are clearly identified as project contracts. Replace them with approved non-production endpoints from your system. Cypress supplies the command APIs, but it does not supply your authentication or test-data service.

TL;DR

Recipe Cypress API Yield
Data attribute lookup Cypress.Commands.addQuery jQuery elements
Create a test project Cypress.Commands.add plus cy.request Project object
Cache role login cy.session null
Find field inside a card Child command with prevSubject Input element
Mask secret typing Cypress.Commands.overwrite Original element
Poll background job Recursive Cypress chain Completed job
Read a server-side fixture cy.task Serializable task result

1. Set Up the Cypress Custom Commands Example Library

Use one support entry point and import command modules from it. This makes registration deterministic and prevents a command from existing only when a particular spec happened to import a file.

cypress/
  e2e/
    project.cy.ts
  support/
    e2e.ts
    commands/
      auth.commands.ts
      project.commands.ts
      query.commands.ts
      types.d.ts
cypress.config.ts

The support entry imports modules for their registration side effects:

// cypress/support/e2e.ts
import './commands/query.commands'
import './commands/auth.commands'
import './commands/project.commands'

If the package declares sideEffects: false, some bundlers can remove imports that appear to exist only for side effects. Cypress documentation recommends exporting registration functions in that situation and calling them explicitly from the support file. The simple import form is appropriate when the project's bundler preserves support side effects.

Use a declaration file that the Cypress TypeScript configuration includes. If it has imports or exports, wrap namespace augmentation in declare global. If it is an external ambient declaration with neither, declare namespace Cypress is sufficient. Do not mix the patterns accidentally.

Keep command names in one catalog. Names are global within the Cypress support context, so loginAs registered twice creates ambiguity. Group by domain responsibility rather than by whichever developer added the command.

Run npx cypress open --e2e to verify registration interactively and npx cypress run --e2e in CI. A TypeScript check should run before browser execution so mismatched signatures fail quickly.

2. Example One: Add a Retryable data-cy Query

A DOM lookup is a query, not a side-effecting action. Cypress custom queries are synchronous, idempotent, and retryable. The outer callback runs once and returns an inner function that Cypress may call repeatedly. Use a normal function callback because the query API relies on its this context for options such as timeouts.

// cypress/support/commands/query.commands.ts
declare global {
  namespace Cypress {
    interface Chainable {
      getByDataCy(value: string): Chainable<JQuery<HTMLElement>>
    }
  }
}

Cypress.Commands.addQuery('getByDataCy', function getByDataCy(value: string) {
  const selector = `[data-cy="${CSS.escape(value)}"]`

  return () => Cypress.$(selector)
})

export {}

The call site looks like a built-in query and can participate in Cypress assertions:

cy.getByDataCy('save-project')
  .should('be.visible')
  .and('be.enabled')
  .click()

CSS.escape prevents characters in the value from changing selector syntax. Cypress.$ uses Cypress's bundled jQuery to return a jQuery collection. The inner function only reads DOM state, so Cypress can invoke it repeatedly without side effects.

Keep this command only if data-cy is an agreed test contract. Accessible roles, labels, and names may be better selectors for user behavior. If the team uses Cypress Testing Library, install and register the actual package rather than recreating its richer query semantics.

Do not implement this as Cypress.Commands.add('getByDataCy', () => cy.get(...)) and claim that it is a custom query. The inner cy.get() still retries, but the abstraction and extension API have different contracts. Read Cypress retryability and command queues before building custom lookup behavior.

3. Example Two: Create Data Through an API and Yield It

A data-creation command is valuable when many specs need the same safe server contract. It should validate the setup response and yield the created object, not store an ID in a global variable.

// cypress/support/commands/project.commands.ts
export type Project = {
  id: string
  name: string
  ownerId: string
  status: 'active' | 'archived'
}

export type NewProject = {
  name: string
  ownerId: string
}

Cypress.Commands.add('createProject', (input: NewProject) => {
  return cy.request<Project>({
    method: 'POST',
    url: '/api/test/projects',
    body: input,
    failOnStatusCode: false,
  }).then((response) => {
    expect(response.status, 'create project status').to.equal(201)
    expect(response.body.id, 'created project id').to.be.a('string').and.not.be.empty
    return response.body
  })
})
// cypress/support/commands/types.d.ts
import type { NewProject, Project } from './project.commands'

declare global {
  namespace Cypress {
    interface Chainable {
      createProject(input: NewProject): Chainable<Project>
    }
  }
}

export {}

Use the yielded project within the chain:

it('archives an active project', () => {
  const projectName = `archive-${Date.now()}`

  cy.createProject({ name: projectName, ownerId: 'qa-owner' }).then((project) => {
    cy.visit(`/projects/${project.id}`)
  })

  cy.contains('h1', projectName).should('be.visible')
  cy.contains('button', 'Archive').click()
  cy.get('[role="status"]').should('contain.text', 'Project archived')
})

The endpoint is an application-owned test interface. Restrict it to test environments, require authorized synthetic identities, validate input, and record audit events. Return enough information for targeted cleanup. A delete-all command is not acceptable because parallel workers may own other records.

4. Example Three: Cache Role-Based API Login With cy.session

Use cy.session() to cache and restore cookies, local storage, and session storage for a stable identity. The session ID must include every input that changes the resulting browser credentials. This example uses a role plus a synthetic user key.

// cypress/support/commands/auth.commands.ts
type TestRole = 'viewer' | 'editor' | 'admin'

Cypress.Commands.add('loginAs', (role: TestRole, userKey = 'primary') => {
  return cy.session(
    ['loginAs', role, userKey],
    () => {
      cy.request<{ sessionId: string }>({
        method: 'POST',
        url: '/api/test/login',
        body: { role, userKey },
      }).then(({ body }) => {
        cy.setCookie('session_id', body.sessionId, {
          sameSite: 'lax',
          secure: true,
        })
      })
    },
    {
      validate() {
        cy.request('/api/me').its('status').should('eq', 200)
      },
    },
  )
})
declare global {
  namespace Cypress {
    interface Chainable {
      loginAs(
        role: 'viewer' | 'editor' | 'admin',
        userKey?: string,
      ): Chainable<null>
    }
  }
}

Call login before navigation because test isolation and session restoration can clear the page:

beforeEach(() => {
  cy.loginAs('editor')
  cy.visit('/projects')
})

validate runs for a newly created or restored session. A failed restored validation causes Cypress to rerun setup. It should verify real authenticated state without being so expensive that caching loses its value.

The application must provide the login contract and cookie attributes appropriate to its origin. Do not copy secure: true into an HTTP-only local environment where the browser will reject the cookie. Configure test HTTPS or use the correct documented cookie policy. Never expose this endpoint in production.

5. Example Four: Add a Typed Child Command for a Form Card

A child command is useful when an operation is meaningful only within a current element. Cypress passes the previous subject to the callback and can validate it with prevSubject: 'element'.

This command finds the control associated with a label inside one card. It returns the control, so the caller chooses the action and assertion:

// cypress/support/commands/form.commands.ts
Cypress.Commands.add(
  'findField',
  { prevSubject: 'element' },
  (subject, labelText: string) => {
    return cy.wrap(subject).within(() => {
      cy.contains('label', labelText)
        .should('have.attr', 'for')
        .then((id) => {
          expect(id, 'label for attribute').to.be.a('string').and.not.be.empty
          cy.get(`#${CSS.escape(String(id))}`)
        })
    })
  },
)

The use of .within() changes scoping and its yielded subject semantics, so this exact implementation is not ideal if the caller must receive the input. A clearer implementation derives the ID and then returns the nested query:

Cypress.Commands.add(
  'findField',
  { prevSubject: 'element' },
  (subject, labelText: string) => {
    return cy.wrap(subject)
      .contains('label', labelText)
      .invoke('attr', 'for')
      .then((id) => {
        expect(id, 'label for attribute').to.be.a('string').and.not.be.empty
        return cy.wrap(subject).find(`#${CSS.escape(String(id))}`)
      })
  },
)
declare global {
  namespace Cypress {
    interface Chainable<Subject = any> {
      findField(labelText: string): Chainable<JQuery<HTMLElement>>
    }
  }
}
cy.contains('[data-cy="settings-card"]', 'Notifications')
  .findField('Daily summary')
  .check()
  .should('be.checked')

The final version keeps the card as the search boundary and yields the actual form control. In production, accessible Testing Library queries may express this more directly. The lesson is to test the yielded subject, not only whether the command appears to run.

6. Example Five: Mask Sensitive Typing With a Documented Overwrite

Overwriting a built-in command changes behavior globally, so use it only for a clear cross-cutting policy. Cypress's TypeScript guidance shows generic parameters for overwriting child commands. This example adds a sensitive option to .type() and masks its command-log message.

// cypress/support/commands/sensitive-type.ts
interface SensitiveTypeOptions extends Cypress.TypeOptions {
  sensitive?: boolean
}

Cypress.Commands.overwrite<'type', 'element'>(
  'type',
  (originalFn, element, text, options?: Partial<SensitiveTypeOptions>) => {
    if (options?.sensitive) {
      options.log = false
      Cypress.log({
        $el: element,
        name: 'type',
        message: '*'.repeat(text.length),
      })
    }

    return originalFn(element, text, options)
  },
)

Type augmentation adds the option to the existing method:

declare global {
  namespace Cypress {
    interface TypeOptions {
      sensitive?: boolean
    }
  }
}

The call site is explicit:

cy.env(['apiToken']).then(({ apiToken }) => {
  expect(apiToken, 'API token').to.be.a('string').and.not.be.empty
  cy.getByDataCy('api-token').type(apiToken, {
    sensitive: true,
  })
})

Command-log masking is only one control. The value can still appear in screenshots, videos, application error messages, browser storage, or network evidence. Use synthetic least-privilege secrets, disable or crop unsafe artifacts where policy requires, and never print an environment or secret map wholesale. The asynchronous cy.env() command is the current API for reading sensitive Cypress environment values.

Do not overwrite .type() merely to add ordinary defaults. Every overwrite increases compatibility and review cost. Add a focused spec that checks masked logging behavior and normal typing, then revisit it during Cypress upgrades.

7. Example Six: Poll a Background Job With a Bounded Chain

An asynchronous workflow should wait for observable state, not sleep for an arbitrary duration. A command can poll an application status endpoint with a maximum attempt count and a delay between requests. It must stop on success and explicit failure.

// cypress/support/commands/job.commands.ts
type Job = {
  id: string
  status: 'queued' | 'running' | 'complete' | 'failed'
  errorCode?: string
}

function readJob(jobId: string): Cypress.Chainable<Job> {
  return cy.request<Job>(`/api/jobs/${jobId}`).its('body')
}

Cypress.Commands.add(
  'waitForJob',
  (jobId: string, attempts = 10): Cypress.Chainable<Job> => {
    function poll(remaining: number): Cypress.Chainable<Job> {
      return readJob(jobId).then((job) => {
        if (job.status === 'complete') return job
        if (job.status === 'failed') {
          throw new Error(`Job ${jobId} failed with ${job.errorCode ?? 'unknown'}`)
        }
        if (remaining <= 1) {
          throw new Error(`Job ${jobId} did not complete after ${attempts} attempts`)
        }

        return cy.wait(500, { log: false }).then(() => poll(remaining - 1))
      })
    }

    return poll(attempts)
  },
)
declare global {
  namespace Cypress {
    interface Chainable {
      waitForJob(jobId: string, attempts?: number): Chainable<Job>
    }
  }
}

This is bounded and diagnostic. The illustrative limit is five seconds across ten intervals, plus request time. Choose values from the system's documented service objective and CI conditions. Add the last observed status and a correlation ID when the API supplies them.

Polling cy.request() is appropriate for a supported application endpoint. For a DOM state, use Cypress's built-in query retryability instead. Do not recursively click a button or submit a request that creates work, because retries can duplicate side effects.

8. Example Seven: Bridge to Node With cy.task

Browser test code cannot safely perform every operating-system operation. cy.task() sends a serializable argument to a task registered in setupNodeEvents and yields its serializable result. A task must not return undefined.

This example checks whether a known generated file exists under a controlled directory:

// cypress.config.ts
import { existsSync } from 'node:fs'
import { isAbsolute, relative, resolve } from 'node:path'
import { defineConfig } from 'cypress'

export default defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      const downloadsRoot = resolve(config.downloadsFolder)

      on('task', {
        downloadExists(fileName: string) {
          const candidate = resolve(downloadsRoot, fileName)
          const relativePath = relative(downloadsRoot, candidate)
          if (relativePath.startsWith('..') || isAbsolute(relativePath)) {
            throw new Error('File name resolves outside the downloads folder')
          }
          return existsSync(candidate)
        },
      })
    },
  },
})
cy.contains('button', 'Export CSV').click()
cy.task<boolean>('downloadExists', 'projects.csv').should('equal', true)

The path.relative guard prevents ../ traversal from reading arbitrary files and handles platform path separators. Keep task inputs synthetic and never make an unrestricted shell-execution task. If symlinks can exist in the downloads folder, compare real paths as an additional containment control.

Use cy.task() for Node-side work such as controlled database seeding, mailbox adapters, or file checks when a purpose-built API is unavailable. Keep the implementation narrow, validate inputs, return JSON-serializable data, and clean up resources. A task is a trust boundary, not a shortcut around security.

9. Test the Commands as Public Contracts

Add focused specs for each command's yield and failure behavior. Feature tests alone may cover only the happy path and make foundational failures hard to locate.

For createProject, use a safe local test server or dedicated environment. Note that cy.intercept() observes browser traffic and does not intercept cy.request() in the same way. Do not write a fake passing test based on that assumption. Pure request-shape logic can be extracted into a unit-tested function, while a small integration test proves the real setup endpoint.

For getByDataCy, mount or visit a controlled page where the element appears after a short timer. Assert that the query finds it without a manual wait, rejects malformed values safely, and yields a jQuery collection. For findField, test two cards with identical labels so scope errors are visible.

For loginAs, verify that role identity is isolated, validation detects an invalid session, and production execution is blocked server-side. Do not assert implementation details of Cypress's own cache. Assert the resulting application identity.

Review every command for:

  • A name that exposes domain intent.
  • Typed arguments and a documented yielded subject.
  • Deterministic state ownership.
  • Safe logs and artifacts.
  • Clear failure messages.
  • Correct retry semantics.
  • No hidden navigation or unrelated assertion.
  • Compatibility with test isolation and parallel workers.

Run lint, TypeScript checks, focused command specs, and representative feature specs after a change. Debugging flaky Cypress tests can help classify command-library failures.

10. Refactor a Bad Cypress Custom Commands Example

Consider a command named completeCheckout that logs in through the UI, searches a product, adds it, enters a fixed address, submits payment, waits five seconds, and asserts a success message. It is reused everywhere because it appears convenient.

The design hides identity, data, payment mode, important assertions, and navigation. It shares fixed records, contains a sleep, and yields nothing. When it fails, the spec name does not reveal which guarantee was under test. Changing the checkout UI breaks unrelated tests that only needed an order.

Refactor by intent:

  1. Use loginAs to establish a named role.
  2. Use an API command to create a cart or order when the UI setup is not the subject.
  3. Keep the critical browser actions visible in the checkout spec.
  4. Replace the fixed sleep with an aliased request or observable status.
  5. Let the test own assertions for its behavior.
  6. Return created identities for targeted cleanup.

The resulting test may be a few lines longer, but every line carries meaning. A reader sees identity, starting state, route, action, and oracle without opening a global utility.

Audit the command catalog quarterly or during major Cypress upgrades. Remove wrappers that only rename built-ins, move pure data builders to TypeScript modules, and deprecate commands whose application boundary no longer exists. For the design principles behind this refactor, read how to use Cypress custom commands.

11. Turn the Recipes Into a Maintainable Team API

Once several commands exist, treat them as an internal API. The users are test authors, reviewers, CI maintainers, and engineers diagnosing failures. A command's public contract includes its name, arguments, yielded subject, browser or server side effects, required environment, logs, and cleanup responsibilities. Document these facts close to the declaration.

Use domain language that survives UI redesign. createProject and loginAs describe intent. postToProjectsEndpoint and clickThirdToolbarButton encode current implementation. Domain names do not mean hiding everything, however. A test should still show the role, important data, route, user action, and expected result.

Assign ownership by command module. Authentication commands should change with identity contracts. Project data commands should change with project APIs. Query extensions should be reviewed by engineers who understand Cypress retryability. One giant support file encourages unrelated changes and makes deprecation difficult.

Version the behavior through source control and normal review rather than adding command names such as loginV2 indefinitely. When a contract must change, add the new signature, migrate one representative spec, compare failure evidence, update documentation, then move the remaining callers. Delete the old form after consumers migrate. If compatibility is necessary for a transition, emit a clear development warning that contains no secret values.

Set a high bar for global registration. A helper used in one feature can remain a local function or feature object. Promote it only after the responsibility and reuse are proven. This keeps autocomplete useful and reduces accidental coupling between teams.

Build a small command contract suite that runs before broad end-to-end tests. It should cover successful yield, input rejection, controlled server error, subject validation, test isolation, and sensitive output. A failure in this suite points directly to the shared library. Broad specs can then focus on business behavior instead of retesting every setup detail.

Observe commands in CI. Classify whether failures arise from the command, application contract, data service, environment, or caller misuse. Track first-attempt reliability and setup duration for high-traffic commands such as login. If a cached session saves time but occasionally restores the wrong tenant, it is a net loss until the identity key and validation are fixed.

Security review is mandatory for commands that create identities, call test endpoints, handle secrets, or invoke Node tasks. Enforce environment restrictions on the server, validate task inputs, use least privilege, and redact all artifact paths. Client-side guards are useful but cannot be the only protection.

During Cypress upgrades, compile types first, run the contract suite, then run representative component and end-to-end specs. Review changes to command, query, session, task, and test-isolation behavior in official release documentation. Avoid copying an old workaround forward when the platform now supplies a supported API.

The result should feel smaller over time. Commands that no longer protect a stable boundary should disappear. A mature library is not the one with the most fluent syntax. It is the one where each abstraction earns its place and a new team member can predict what the chain will do.

Before approving a new recipe, ask the author to show its failure output. A convenient command that produces a generic timeout can cost more than the duplicated lines it replaces. Good evidence names the domain boundary, preserves the relevant Cypress log, and yields enough context for the caller to add a precise assertion.

Interview Questions and Answers

Q1: Why is getByDataCy implemented as a query?

It synchronously reads DOM state and should be retried until the following assertion succeeds or times out. Cypress.Commands.addQuery gives it the same query model: idempotent inner function, repeated by Cypress. It should never mutate the application.

Q2: Why does createProject return the response body?

The created project is the useful subject for navigation, assertions, and cleanup. Yielding it keeps state in the Cypress chain and avoids a global variable. The command also validates the setup boundary before allowing the test to continue.

Q3: What belongs in a cy.session ID?

Every input that can produce different cached browser credentials belongs in the ID. That commonly includes authentication method, user, tenant, and role. Omitting one can restore the wrong authorization state.

Q4: Why call cy.visit() after loginAs?

With test isolation, cy.session() can clear the page while creating or restoring state. The authentication command should establish credentials, then the test visits the feature it owns. This separation also makes login reusable.

Q5: How does prevSubject: 'element' help?

It requires a previous subject and asks Cypress to validate that subject as an element. TypeScript can infer the callback subject accordingly. Misuse fails close to the command boundary.

Q6: Is masked command logging enough to protect a secret?

No. Screenshots, videos, application output, browser storage, and network logs can still expose it. Masking is one layer alongside synthetic least-privilege credentials, artifact policy, and redaction.

Q7: Why use recursive polling instead of a loop?

Each recursive step returns a Cypress chain, allowing requests and timers to execute in queue order. A synchronous loop blocks progress and cannot observe changing application state. The recursion must be bounded and stop on terminal failure.

Q8: What security risk does cy.task() introduce?

A task executes in the Node process and can access files, networks, or databases. Inputs therefore require validation and operations need narrow scope. An unrestricted shell or file task would turn test data into a serious trust-boundary problem.

Common Mistakes

  • Registering command modules only from selected specs.
  • Forgetting that names share one global command namespace.
  • Writing a custom command when a pure function is sufficient.
  • Using an arrow callback for a custom query that relies on Cypress query context.
  • Mutating application state inside a retryable query.
  • Saving created IDs in module-level variables.
  • Omitting role, tenant, or user from a session ID.
  • Using a secure cookie on plain HTTP without understanding browser behavior.
  • Assuming cy.intercept() intercepts cy.request() calls.
  • Returning undefined from a Node task.
  • Allowing task paths or commands without input validation.
  • Polling an action that creates duplicate side effects.
  • Masking the command log while leaking the same secret to screenshots.
  • Keeping a giant workflow command because it shortens test files.

Conclusion

A production-ready cypress custom commands example makes the abstraction testable and honest. The command or query has one job, its TypeScript declaration matches registration, its yielded subject is useful, its side effects are controlled, and its logs reveal enough to diagnose without exposing secrets.

Adopt these recipes one boundary at a time. Start with deterministic authentication or test-data setup, add focused contract specs, and keep feature assertions visible. Then remove global wrappers that hide more than they clarify. The result is a smaller command library and a Cypress suite that engineers can understand from the call site.

Interview Questions and Answers

Why implement getByDataCy as a custom query?

The operation synchronously reads DOM state and should be idempotent. The query API allows Cypress to invoke its inner function repeatedly. An action or network call would not be safe in that model.

Why yield the created project object?

The caller needs its identity for navigation, assertions, and targeted cleanup. Yielding keeps the value in chain order and avoids shared mutable state. The command validates the setup response before continuing.

What is wrong with a module-level created ID?

It can be read before the command assigns it and can leak between tests or workers. The result becomes order-dependent. Returning the object through the Cypress chain preserves ownership and timing.

Why include role and user in the session key?

They produce different browser credentials and authorization state. An incomplete key can restore the wrong identity. Validation catches invalid state, but a correct key prevents the collision.

What does prevSubject element provide?

It requires an existing subject and validates it as an element before the callback runs. The callback receives that element with inferred TypeScript support. This creates a precise fluent boundary for nested interactions.

How do you keep secret typing safe?

I disable the original command log and show only a masked message when the sensitive option is explicit. I also protect screenshots, videos, network evidence, and browser storage. Masking one log is not complete secret management.

Why must polling be bounded?

An unbounded poll can hang CI and conceal a failed terminal state. A bound creates a predictable failure with attempts and last observation. The interval should come from service behavior rather than guesswork.

How do you secure cy.task implementations?

I expose narrow named operations, validate all input, restrict paths and environments, and return only serializable safe output. I never provide unrestricted shell or file access. Task code is treated as a server-side trust boundary.

Frequently Asked Questions

What is a good Cypress custom commands example?

A good example wraps a stable domain boundary, such as creating safe test data or establishing an authenticated session. It has typed inputs, a documented yielded subject, controlled side effects, and focused tests.

How do I create a retryable data-cy query?

Declare its type, register it with `Cypress.Commands.addQuery()`, and return an idempotent synchronous inner function that reads the DOM. Escape selector input and never mutate application state in that function.

How do I return API data from a custom command?

Return the `cy.request()` chain and map the response body from `.then()`. Declare the command as `Chainable<YourType>` so the caller can continue with the created object.

How do I make a role-based login command?

Call an approved login endpoint inside `cy.session()` and include role, user, tenant, and method in the session ID as applicable. Add fast validation that proves restored authenticated state.

How should I poll a background job in Cypress?

Use a bounded recursive Cypress chain that reads a status endpoint, returns on success, throws on terminal failure, and delays only between observations. Include attempts and last state in timeout evidence.

Can cy.intercept mock cy.request?

No, do not assume browser interception controls `cy.request()`, which is issued outside the browser network path. Test setup commands against a safe service or extract pure request-building logic for unit tests.

What can a Cypress task return?

A task should return a serializable value or a promise of one and must not return `undefined`. Validate task inputs and keep file, database, and process access narrowly scoped.

Related Guides