Resource library

QA How-To

How to Skip and group tests in Cypress (2026)

Learn cypress how to skip and group tests with describe, it.skip, conditional skips, tags, hooks behavior, CI policy, and anti-patterns for 2026.

22 min read | 2,541 words

TL;DR

Group Cypress tests with nested describe/context. Skip with it.skip or describe.skip (plus a reason). Use it.only only locally. For cross-file groups, use folders and tags. Conditional skips belong behind documented Cypress.env flags.

Key Takeaways

  • Group related examples with nested describe or context and shared hooks at the right level.
  • Skip with it.skip or describe.skip and include a ticket or reason in the title.
  • Treat it.only as temporary local focus, never as a committed smoke strategy.
  • Use this.skip() or describe versus describe.skip for environment-conditional execution.
  • Scale cross-file grouping with folders and tags filtered in CI.
  • Track skips as visible quality debt with owners instead of commenting out tests.
  • Keep hook placement intentional so skips do not create surprise setup cost or side effects.

The short answer to cypress how to skip and group tests is to use Mocha's suite API: nest describe blocks to group, use context as an alias for readable grouping, and use it.skip / describe.skip (or xit / xdescribe) to exclude examples intentionally. Use it.only / describe.only only for temporary local focus. For cross-file grouping in CI, add tags and grep-style filters rather than commenting out code.

Skipping and grouping are how professional suites stay honest under partial outages, incomplete features, and large domain boundaries. This guide covers structure, skip vs pending, exclusive tests, conditional skips, hooks interaction, tagging strategies, anti-patterns, and interview answers with practical Cypress examples for 2026.

TL;DR

Intent API / technique Commit to main?
Group related examples Nested describe / context Yes
Skip one test it.skip or xit Yes, with ticket
Skip a suite describe.skip or xdescribe Yes, with ticket
Run only one locally it.only / describe.only No
Pending placeholder it('title') without body (where supported) Rarely
Cross-file group Tags + grep plugin Yes
Env-based skip Conditional describe / runtime skip helpers Yes, documented

Group for readability and shared hooks. Skip with a reason. Never confuse skip (exclude) with only (focus).

1. Cypress How to Skip and Group Tests: Mental Model

Cypress inherits Mocha's BDD interface for organizing tests. A spec file loads one or more top-level suites. A suite (describe / context) groups examples and can nest. An example (it / specify) is the runnable unit reporters count.

Grouping answers: "What feature or risk area is this?" Skipping answers: "Should this example run in this pipeline right now?" Focusing answers: "Temporarily run only this while I debug." Those three verbs must stay distinct in code review.

Bad suites look like flat lists of fifty it blocks with copy-pasted setup. Good suites look like a table of contents: auth, validation, permissions, error paths. Bad skipping looks like large commented regions. Good skipping looks like it.skip('...', () => { ... }) with a ticket link in the title or a comment above.

When people search cypress how to skip and group tests, they often paste it.skip into a file and stop. This guide goes further: hooks, CI policy, tags, and how grouping interacts with isolation commands from how to run a single test in Cypress.

2. Group With describe, context, and Nested Suites

// cypress/e2e/billing/invoices.cy.ts
describe('Billing invoices', () => {
  beforeEach(() => {
    cy.loginAs('finance@example.com')
    cy.visit('/billing/invoices')
  })

  context('list view', () => {
    it('shows invoices for the current workspace', () => {
      cy.get('[data-cy=invoice-row]').should('have.length.greaterThan', 0)
    })

    it('filters by status', () => {
      cy.get('[data-cy=status-filter]').select('Open')
      cy.get('[data-cy=invoice-row]').each(($row) => {
        cy.wrap($row).should('contain', 'Open')
      })
    })
  })

  context('detail view', () => {
    it('opens an invoice PDF download affordance', () => {
      cy.get('[data-cy=invoice-row]').first().click()
      cy.get('[data-cy=download-pdf]').should('be.visible')
    })
  })
})

context is an alias of describe. Use whichever reads better. Many teams use describe for the feature and context for state variations (logged out, admin, empty state).

Nesting benefits:

  1. Shared beforeEach at the right level reduces duplication.
  2. Reports and open-mode UI become navigable.
  3. describe.skip can park an entire branch of coverage intentionally.
  4. Ownership is clearer in code review ("you changed the permissions context").

Do not nest endlessly. Three levels is usually enough. Deeper trees often mean the file should split.

3. Skip Examples With it.skip, xit, and Pending Titles

it.skip('exports CSV for 10k rows (BUG-1842)', () => {
  // still keep the body so the intent is not lost
  cy.get('[data-cy=export-csv]').click()
  cy.readFile('cypress/downloads/invoices.csv').should('exist')
})

Equivalent form:

xit('exports CSV for 10k rows (BUG-1842)', () => {
  // ...
})

Title hygiene for skips:

  • Include a ticket id when the skip is defect-driven.
  • Include a date or milestone when the skip is feature-flag driven.
  • Prefer keeping the test body, not deleting it, so re-enablement is easy.

Pending tests (declared without implementation) signal unfinished work. Prefer explicit it.skip with a reason over mysterious empty placeholders that confuse newcomers.

Skipping is a contract with your future self. A skip without a reason becomes permanent dead weight.

4. Skip Suites With describe.skip and xdescribe

describe.skip('Legacy PayPal checkout (remove after 2026-09)', () => {
  it('completes payment with PayPal sandbox', () => {
    // ...
  })
})

Suite skips are ideal when an entire integration is disabled in an environment, or when a partner API is in planned maintenance and every child example would fail for the same external reason. Prefer suite skip over twenty individual skips for the same cause.

Caveat: hooks inside a skipped suite do not run, which is usually what you want. Hooks outside the skipped suite still run for other suites in the file. Structure files so a skipped island does not force expensive global setup for no reason, or accept that root before hooks still execute when other suites remain active.

5. Exclusive Focus: it.only and describe.only Are Not Skips

Focus is the inverse operation:

it.only('calculates tax for CA addresses', () => {
  // ...
})

Focused tests skip everything else by exclusion from the run set. That is useful locally and dangerous in main. Policy:

  1. Lint against exclusive tests in CI.
  2. Code review rejects .only on protected branches.
  3. Teach juniors the difference on day one.

A common interview trap is treating .only as a permanent way to "group smoke tests." That is wrong. Smoke grouping belongs in tags or dedicated smoke specs, not exclusive markers.

6. Compare Skip, Only, and Grouping Techniques

Technique Effect on suite Good for Bad for
Nested describe Organizes, shares hooks Features, states Tiny files with one test
it.skip Excludes one example Known bugs, WIP Hiding flakes forever
describe.skip Excludes a branch Dead features, env gaps Silent long-term disablement
it.only Runs focus set only Local debug CI, shared branches
Tags + grep Filters across files Smoke, squad ownership Ad hoc one-off names
Commented-out tests None (dead code) Never Everything

If your strategy is mostly commented blocks, you do not have a skip strategy. You have archaeology.

7. Conditional Skips Based on Environment and Feature Flags

Sometimes a test should run only in staging, or only when a flag is on:

const canRunPayments = Cypress.env('PAYMENTS_E2E') === true

describe('Card payments', () => {
  before(function () {
    if (!canRunPayments) {
      this.skip()
    }
  })

  it('charges a visa test card', () => {
    cy.visit('/checkout')
    // ...
  })
})

this.skip() inside a hook or test (function form, not arrow, when relying on Mocha this) dynamically skips at runtime. Arrow functions do not bind Mocha's this, so use function () {} when calling this.skip().

Alternative structure:

const describePayments = canRunPayments ? describe : describe.skip

describePayments('Card payments', () => {
  it('charges a visa test card', () => {
    // ...
  })
})

Document the env var in README. Conditional skips that nobody can turn on are permanent skips in disguise.

Feature flag example:

it('shows the new invoice timeline', function () {
  if (!Cypress.env('FF_INVOICE_TIMELINE')) {
    this.skip()
  }
  cy.visit('/billing/invoices/inv_1')
  cy.get('[data-cy=invoice-timeline]').should('be.visible')
})

Prefer enabling flags in test environments deterministically over skipping large areas forever.

8. Hooks, Order, and What Still Runs When You Skip

Understanding hooks prevents surprise cost and surprise failures:

describe('Workspace settings', () => {
  before(() => {
    // runs once if any test in this suite runs
    cy.task('seed:workspace', { id: 'ws_demo' })
  })

  beforeEach(() => {
    cy.session('owner', () => cy.loginAs('owner@example.com'))
    cy.visit('/settings')
  })

  it('updates workspace name', () => { /* ... */ })

  it.skip('deletes workspace (blocked on BACKEND-77)', () => {
    // body kept for later
  })
})

The skipped example does not run its body. beforeEach still runs for the active examples. If every example is skipped, Mocha generally does not execute the suite the same way as an active suite; do not depend on skipped suites for seeding side effects.

Root support file hooks in cypress/support/e2e.ts still load. Skipping tests does not unload global listeners. Keep global hooks lightweight.

For auth-heavy suites, cy.session grouping by role inside nested contexts keeps setup coherent without re-login cost on every example.

9. Group Across Files With Folders, Naming, and Tags

File system grouping:

cypress/e2e/
  auth/
    login.cy.ts
    logout.cy.ts
  billing/
    invoices.cy.ts
    taxes.cy.ts
  smoke/
    critical-path.cy.ts

Config can include patterns per job:

// illustrative: different CI jobs pass different --spec globs
// smoke job:
// npx cypress run --spec "cypress/e2e/smoke/**/*.cy.ts"

Tag grouping inside tests (with a grep-style plugin):

it('logs in with email and password', { tags: ['@smoke', '@auth'] }, () => {
  // ...
})
npx cypress run --env grepTags=@smoke

Tags excel when a smoke path cuts across folders. Folders excel when ownership maps to domains. Use both.

Related: headed investigation of a group often uses --spec on a folder. See how to run tests in headed mode in Cypress.

10. Cypress How to Skip and Group Tests in CI Policy

A healthy CI policy looks like this:

  1. Default branch runs the full non-skipped suite (plus intentional env filters).
  2. Skips require tickets and appear in a weekly triage list.
  3. .only fails lint on PRs.
  4. Smoke job uses tags or a smoke folder, not exclusive tests.
  5. Quarantine moves chronically flaky tests to skip with owner and expiry, not silent retries forever.

Example PR check idea: fail if the diff introduces it.skip without a BUG- or TICKET- pattern in the title. Perfect automation is optional; review culture is not.

Track skip count as a metric. A rising skip curve means quality debt, not velocity.

11. Anti-Patterns That Rot a Suite

  1. Commenting out tests instead of it.skip.
  2. Skipping flakes permanently without root cause ownership.
  3. Using .only as smoke selection.
  4. Mega-describes with unrelated examples and one giant beforeEach.
  5. Duplicate groups across files with divergent setup helpers.
  6. Empty skipped suites left for years after a feature dies (delete instead).
  7. Conditional skips with secret env vars nobody sets locally.
  8. Grouping only by ticket number instead of user-facing behavior.

Replace comments with skips, skips with fixes, and fixes with smaller groups that match risk.

12. Refactor Playbook: From Flat File to Structured Suite

Starting point:

describe('settings', () => {
  it('test1', () => {})
  it('test2', () => {})
  // ... 40 more
})

Target structure:

  1. Split by user capability: profile, security, notifications.
  2. Move shared login to beforeEach or cy.session.
  3. Create context('validation') vs context('happy path').
  4. Extract true end-to-end journeys that span apps into a smoke folder.
  5. Skip only the examples blocked externally, with tickets.

Refactor in vertical slices. Do not rewrite the entire folder in one PR if the suite is critical; structure is a series of small moves with green CI between them.

When failures concentrate in one context, isolation commands help:

npx cypress run --spec "cypress/e2e/settings/security.cy.ts"

Pair with title grep if a single context is still large.

13. Reporting, Pending Counts, and Stakeholder Communication

Reporters show pending or skipped counts. Stakeholders sometimes panic when pending rises after a release freeze. Communicate proactively:

  • "Twelve skips are partner-sandbox tests disabled during Acme API migration (EPIC-55)."
  • "Two skips are quarantined flakes with owners and expiry dates."

Never hide skips by deleting assertions that protected revenue paths. Transparency beats vanity green.

Open mode displays skipped tests differently from failed tests. Teach the team to read the runner summary: failed means fix now; skipped means manage the backlog; passed means still not a substitute for exploratory testing.

14. Worked Example: Permissions Matrix Grouping

describe('Project permissions', () => {
  const roles = [
    { name: 'owner', canDelete: true },
    { name: 'editor', canDelete: false },
    { name: 'viewer', canDelete: false },
  ] as const

  roles.forEach((role) => {
    context(`as ${role.name}`, () => {
      beforeEach(() => {
        cy.loginAs(`${role.name}@example.com`)
        cy.visit('/projects/prj_1')
      })

      it('views project overview', () => {
        cy.get('h1').should('contain', 'Apollo')
      })

      it(`${role.canDelete ? 'allows' : 'blocks'} project deletion`, () => {
        if (role.canDelete) {
          cy.get('[data-cy=delete-project]').should('be.enabled')
        } else {
          cy.get('[data-cy=delete-project]').should('not.exist')
        }
      })
    })
  })
})

This groups by role without three nearly identical files. If viewer deletion UI is temporarily wrong in staging only, you can skip a single generated-style example carefully, but dynamic skips inside loops need clear titles so the pending list stays readable.

Dynamic generation is powerful; keep titles stable for flake tracking and screenshots.

Skip and Group Governance Checklist

Confirm every skip has a reason and owner, no .only on main, groups match product language, hooks sit at the correct nesting level, smoke is tag or folder based, conditional skips are documented, pending counts are reviewed weekly, and dead skipped features are deleted. Confirm new engineers can find the right file from the folder tree without tribal knowledge. Confirm interview candidates from your team can explain skip versus only without mixing them. This governance keeps Mocha structure from decaying into a junk drawer.

Add one more operational habit: when you skip a test because of an upstream outage, create a calendar reminder or ticket expiry. Skips without expiry become folklore. When the outage ends, re-enable deliberately and watch the first pipeline closely. If the test fails for a new reason, fix it then, do not re-skip blindly. Grouping and skipping are stewardship tools. Used well, they tell a true story about what your suite covers today. Used poorly, they invent a story that shipping is safer than it is. Prefer truth.

Also align naming with product analytics events when possible. If the product calls a surface "Workspace preferences," do not name the suite "Settings misc." Shared language helps engineers, PMs, and support map failures to features quickly. That is the hidden productivity win of good grouping: faster triage meetings, not prettier trees for their own sake. Skip titles should be equally searchable in your issue tracker so a BUG id jumps from Cypress pending lists to Jira without guesswork during incident response and release readiness reviews weekly.

15. Migration Notes From Other Runners and Team Training Scripts

Engineers arriving from Jest, Pytest, or TestNG often reach for familiar mental models. Jest's describe.skip maps cleanly. Pytest's markers are closer to Cypress tags than to Mocha nested suites. TestNG groups resemble tag filters. During onboarding, draw that map explicitly so people do not invent a second organization system beside the one already in the repo.

A 30-minute training outline you can reuse:

  1. Open a well-structured spec and walk the describe tree in cypress open.
  2. Demonstrate it.skip and show the pending count.
  3. Demonstrate it.only, show the danger, then remove it.
  4. Run a @smoke grep job in CLI.
  5. Open the skip triage board and assign one quarantine ticket.

Record that session once. New hires watch it before they write their first PR. This lowers review friction more than any style guide paragraph alone.

If you migrate from Cucumber-style feature files, do not force Gherkin syntax into Mocha unless the team truly practices BDD collaboration with non-engineers. Grouping by describe names that read like behaviors often gives most of the communication benefit with less tooling. Choose ceremony levels deliberately.

Finally, when two squads share one Cypress project, agree who owns which top-level folder and which tags. Ownership vacuums create orphan skips. A CODEOWNERS file pointing at cypress/e2e/billing/ does more for skip hygiene than another wiki page about "please don't skip tests." Pair ownership with the weekly pending-count review and the process stays alive.

Interview Questions and Answers

Q: How do you skip a test in Cypress?

I use it.skip or xit and keep the body with a ticket reference in the title. For whole areas I use describe.skip.

Q: How do you group tests?

I nest describe or context blocks by feature and state, share hooks at the right level, and use folders plus tags for cross-file groups.

Q: What is the difference between skip and only?

Skip excludes specific tests from running while the rest execute. Only focuses a subset and excludes everything else. Only is for local debugging, not permanent suite design.

Q: How do you skip tests conditionally?

I check Cypress.env and call this.skip() inside a function form hook or test, or I select describe versus describe.skip when defining the suite.

Q: How should smoke tests be organized?

With a smoke folder and/or @smoke tags filtered in CI. Not with committed .only markers.

Q: What is dangerous about commenting out tests?

They disappear from pending metrics, rot without context, and are hard to re-enable safely. Explicit skips remain visible.

Q: How do hooks behave with skipped tests?

Skipped examples do not run their bodies. Active siblings still get suite-level hooks. I do not rely on fully skipped suites to perform required seeding.

Common Mistakes

  • Committing it.only as a fake grouping strategy.
  • Skipping flakes forever without owners.
  • Commenting out large blocks instead of it.skip.
  • Putting unrelated tests in one describe with a bloated beforeEach.
  • Using arrow functions with this.skip() and wondering why it fails.
  • Conditional skips tied to undocumented env vars.
  • Deleting skipped coverage for revenue paths to make reports look clean.
  • Nesting describes six levels deep instead of splitting files.
  • Mixing ticket-only names that mean nothing to new engineers.
  • Forgetting to re-enable skips after the blocking ticket closes.

Conclusion

For cypress how to skip and group tests, structure suites with nested describe/context, exclude intentionally with it.skip/describe.skip plus reasons, and reserve .only for ephemeral local focus. Scale grouping with folders and tags, and manage skips as visible debt rather than hidden comments.

Open your largest flat spec this week, split it into two contexts with tighter hooks, convert any commented tests into ticketed skips, and lint against exclusive tests. That modest cleanup improves readability, CI honesty, and onboarding immediately.

Interview Questions and Answers

How do you organize a large Cypress suite?

I group by product domain in folders, nest describe/context by user state or capability, keep hooks local to the smallest sensible suite, and use tags for smoke and squad filters. I avoid mega-files with unrelated examples.

When do you skip versus delete a test?

I skip when coverage should return soon or is blocked externally. I delete when the feature is gone or the test no longer maps to a user risk. Skips need reasons; deletions need agreement that the risk is gone.

How do you prevent it.only from breaking CI coverage?

ESLint rules, CI greps, and review checklists block exclusive tests on protected branches. I also educate the team that smoke selection uses tags or folders, not only.

Explain conditional skipping for multi-environment pipelines.

I gate suites with Cypress.env flags documented in README, using this.skip() or describe.skip selection. Local defaults should make the common path easy, and CI sets flags explicitly per job.

How do tags differ from describe blocks?

describe structures a file and hook hierarchy. Tags classify examples across files for selection. A test can live in a billing describe and still carry @smoke for the release job.

What metrics would you track around skips?

Skip count over time, age of skips, percent with ticket ids, and reopen rate after re-enablement. Rising unmanaged skips are a quality signal leadership should see.

How do hooks interact with skipped tests?

Skipped tests do not execute their bodies. Sibling active tests still run suite hooks. I never depend on a fully skipped suite to seed data required elsewhere.

Frequently Asked Questions

How do I skip a test in Cypress?

Use it.skip('title', () => { ... }) or xit(...). Prefer keeping the body and a ticket id in the title so the skip is visible and reversible.

How do I skip a whole suite?

Use describe.skip or xdescribe around the group. This is better than skipping every child individually when they share the same blocker.

What is the difference between it.skip and it.only?

it.skip excludes that test while others run. it.only focuses one or more tests and skips the non-focused rest. only is for local debugging.

How do I group tests in Cypress?

Nest describe or context blocks by feature and state, share beforeEach hooks appropriately, and organize files into domain folders. Add tags for cross-cutting smoke sets.

Can I skip a test based on an environment variable?

Yes. Check Cypress.env and call this.skip() inside a function-form test or hook, or choose describe versus describe.skip when defining the suite.

Why does this.skip fail in an arrow function?

Arrow functions do not bind Mocha's this. Use a classic function () {} callback when you need this.skip().

How should we handle flaky tests?

Quarantine with it.skip plus owner and ticket, fix the root cause, and re-enable. Do not leave permanent skips or unlimited retries as the only strategy.

Related Guides