From cypress-consistency
Write, review, refactor, or debug Cypress end-to-end tests (cy.get, cy.intercept, cy.visit, custom commands, fixtures) using one canonical, modern idiom set. Use this skill whenever code creates or fixes Cypress specs, migrates off removed APIs (cy.route, cy.server), deflakes a suite, or when the user hits "cy.get is not a function" outside the chain, elements detaching mid-test, values that are "undefined" because a command result was assigned to a variable, or asks "why does cy.wait(3000) feel wrong." Trigger it even when the user just says "write an e2e test for the login page" or shows a failing Cypress run — without saying "Cypress idioms."
How this skill is triggered — by the user, by Claude, or both
Slash command
/cypress-consistency:cypress-consistencyThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Cypress is stable and heavily represented in training data, but generated tests fight its
Cypress is stable and heavily represented in training data, but generated tests fight its
core model: commands are enqueued, retried, and asynchronous, not values. Code drifts
between removed APIs (cy.route, cy.server), hard sleeps, and Promise-style misuse
(await cy.get(...), const el = cy.get(...)). This skill pins one canonical idiom
set — Cypress 12+ semantics — so every spec respects the command queue and retry-ability.
| Always | Never | Why |
|---|---|---|
cy.get("[data-cy=submit]").click() (one chain) | const btn = cy.get(...); btn.click() | Commands return chainers, not elements; the variable holds a queue handle, not a button. |
cy.intercept("POST", "/api/login").as("login") then cy.wait("@login") | cy.wait(3000) | Waiting on the event is deterministic; waiting on time is flaky and slow. |
cy.intercept(...) | cy.server() + cy.route() | cy.route/cy.server were removed in Cypress 12. |
.then(($el) => { ... }) / aliases (.as) to use a value | let x; cy.get(...).then(v => x = v) then using x synchronously below | The synchronous code runs before any command executes; x is still undefined. |
cy.session(user, loginFn) for auth | logging in through the UI in every test | One UI login spec is enough; sessions cache cookies/storage per user across specs. |
[data-cy=...] / [data-testid=...] selectors | brittle CSS chains, nth-child, auto-generated classes | Test-dedicated attributes survive refactors; styling selectors don't. |
assertions that retry: cy.get(...).should("have.length", 3) | expect($els.length).to.eq(3) inside a bare .then | .should re-runs the query until it passes; inside .then the snapshot is frozen. |
fresh state per test via beforeEach (visit, seed, intercept) | tests depending on the previous test's end state | Order-dependent suites break under .only, retries, and parallelization. |
cy.request/task for setup (seed via API) | building all preconditions through the UI | UI setup multiplies runtime and flake surface. |
House style for a spec:
describe("checkout", () => {
beforeEach(() => {
cy.session("shopper", () => cy.login("[email protected]"));
cy.intercept("POST", "/api/orders").as("createOrder");
cy.visit("/cart");
});
it("places an order", () => {
cy.get("[data-cy=checkout]").click();
cy.wait("@createOrder").its("response.statusCode").should("eq", 201);
cy.location("pathname").should("eq", "/orders/confirmation");
cy.contains("[data-cy=banner]", "Thank you").should("be.visible");
});
});
async/await does not work with Cypress commands. await cy.get(...) neither waits
nor errors usefully; the command queue is not a Promise chain. Use .then().if ($el.is(":visible"))) is a race by construction;
control the application state instead (seed data, stub the response).$el across it..then don't retry — move them to .should() (with a callback if
complex: .should(($el) => { expect(...) }), which Cypress retries).cy.wait("@alias") without an assertion passes even on a 500 — chain
.its("response.statusCode").should(...).cy state via closures across tests breaks under isolation; use aliases
within a test and fixtures/tasks across tests.cy.origin() — silent failures otherwise.Target Cypress 12+ (13/14 keep the same idioms). The key breaking lines: 10
(config moved to cypress.config.js, spec pattern, e2e/component split), 12 (removed
cy.route/cy.server, test isolation on by default, cy.session stable). If a project is
pre-10, flag the config migration rather than mixing both config styles.
cy.session for auth,
cy.request/cy.task for data, cy.intercept for network control. UI only for the
behavior under test.cy.visit/the triggering action; alias them.data-cy selectors..should(...) on elements, locations, and aliased responses.cy.wait(<number>),
async test callbacks, conditional DOM logic, cy.route. Rewrite in canonical form.For the fuller migration map (removed APIs → modern ones), aliasing and custom-command
patterns, and expanded retry-ability rules, read references/cypress-patterns.md.
Provides a checklist for code reviews covering functionality, security, performance, maintainability, tests, and quality. Use for pull requests, audits, team standards, and developer training.
npx claudepluginhub guidogl/cypress-consistency --plugin cypress-consistency