Cypress E2E skill for Codex

Aliased intercepts and cached sessions, no fixed waits. Codex writes Cypress specs that work with the retry model rather than against it: queries paired with assertions, every network dependency aliased and awaited, login cached with cy.session, and data seeded through cy.request. It removes fixed waits and force clicks, and sets up component testing with a provider-aware mount.

  • Testing & QA
  • $cypress-e2e
  • 216 lines
  • 2 files
  • ~1,850 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- cypress-e2e

Installs to ~/.agents/skills/cypress-e2e/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $cypress-e2e, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Writes and de-flakes Cypress end-to-end and component tests - retry-able queries and assertions, cy.intercept with aliases instead of fixed waits, cy.session for login caching, cy.request for data setup, component testing with cy.mount, test isolation, and CI runs with retries and parallel spec splitting. Use when a project uses Cypress, specs fail intermittently, tests wait with cy.wait(ms), login is repeated in every spec, or component tests need configuring in cypress.config.ts.

Use it when

  • The project's E2E suite runs on Cypress
  • Specs fail intermittently or depend on fixed waits
  • Login or data setup goes through the UI in every test
  • Configuring Cypress component testing

Use something else when

How to check the result

The changed spec passes three consecutive headless runs on its own, and the diff contains no numeric cy.wait calls or unexplained force clicks.

The mistake it prevents

Registering cy.intercept after cy.visit, so the request fires before the route exists and the alias wait times out or the stub never applies.

~/.agents/skills/cypress-e2e/SKILL.md

--- name: cypress-e2e description: Writes and de-flakes Cypress end-to-end and component tests - retry-able queries and assertions, cy.intercept with aliases instead of fixed waits,… license: MIT ---

A stable Cypress suite relies on retry-ability instead of waits, aliases every network call it depends on, builds state through cy.request or cy.session, and keeps each spec independent so it passes when run alone or in any order.

Inspect first

  1. Read AGENTS.md, cypress.config.{ts,js}, cypress/support/e2e.*, cypress/support/commands.* and package.json scripts.
  2. Check the version: npx cypress --version. Test isolation (fresh page, cookies and storage per test) is on by default since Cypress 12.
  3. Look for @testing-library/cypress; if present use its findByRole queries.
  4. Note how the app is started for tests (start-server-and-test, a Docker compose file, or a deployed URL). cypress run downloads nothing, but the first npm ci fetches the Cypress binary, which needs network.

Config baseline

// cypress.config.ts
import { defineConfig } from "cypress";

export default defineConfig({
  e2e: {
    baseUrl: "http://localhost:3000",
    specPattern: "cypress/e2e/**/*.cy.ts",
    retries: { runMode: 2, openMode: 0 },
    defaultCommandTimeout: 6000,
    video: false,
    screenshotOnRunFailure: true,
  },
  component: {
    devServer: { framework: "react", bundler: "vite" },
    specPattern: "src/**/*.cy.tsx",
  },
});

Secrets come from CYPRESS_* environment variables (read with Cypress.env("USER")), not from cypress.env.json committed to git.

The retry model

Cypress retries the last query in a chain until the assertion after it passes. Actions (click, type) do not retry.

// Good: query + assertion retry together
cy.findByRole("button", { name: "Place order" }).should("be.enabled").click();
cy.findByRole("status").should("have.text", "Order confirmed");

// Bad: stored value is a snapshot, never re-queried
const total = Cypress.$("[data-cy=total]").text();
  • Never assign Cypress command results to variables; use .then(), .as() aliases, or cy.wrap.
  • Long chains after an action re-query only from the action’s subject. Split them: cy.get("form").submit(); cy.get("[data-cy=errors]").should("be.visible");
  • Use .should() callbacks for complex checks; they retry. .then() callbacks do not.

Selectors

SelectorUse
cy.findByRole("button", { name: "Save" })first choice when Testing Library is installed
cy.get("[data-cy=cart-total]")stable contract for elements without a good role
cy.contains("button", "Save")quick text match scoped to a tag
CSS classes, nth-childavoid; break on styling changes

Network: intercept, alias, wait

it("shows search results", () => {
  cy.intercept("GET", "/api/search*").as("search");
  cy.visit("/search");
  cy.findByRole("searchbox").type("lamp{enter}");
  cy.wait("@search").its("response.statusCode").should("eq", 200);
  cy.findAllByRole("article").should("have.length.at.least", 1);
});

it("renders the empty state", () => {
  cy.intercept("GET", "/api/search*", { body: { items: [] } }).as("search");
  cy.visit("/search?q=zzz");
  cy.wait("@search");
  cy.findByText("No results").should("be.visible");
});
  • Register cy.intercept before the action that triggers the request, usually before cy.visit.
  • cy.wait("@alias") waits for a specific request; cy.wait(2000) is never acceptable.
  • Stub third-party and slow endpoints; keep at least one spec per critical flow hitting the real backend.

State setup without the UI

// cypress/support/commands.ts
Cypress.Commands.add("login", (email: string, password: string) => {
  cy.session(
    [email],
    () => {
      cy.request("POST", "/api/login", { email, password });
    },
    {
      validate() {
        cy.request("/api/me").its("status").should("eq", 200);
      },
      cacheAcrossSpecs: true,
    },
  );
});

declare global {
  namespace Cypress {
    interface Chainable {
      login(email: string, password: string): Chainable<void>;
    }
  }
}
beforeEach(() => {
  cy.login(Cypress.env("E2E_USER"), Cypress.env("E2E_PASSWORD"));
  cy.request("POST", "/api/test/seed", { scenario: "cart-with-two-items" });
});
  • cy.session caches cookies and storage for the given id; validate re-runs login when the session expired.
  • Test-only seed and reset endpoints must be disabled outside test environments.
  • Each test sets up its own data. A test that relies on the previous test fails when run alone with --spec or it.only.

Time

cy.clock(new Date("2026-03-01T10:00:00Z").getTime());
cy.visit("/billing");
cy.tick(15 * 60 * 1000);
cy.findByRole("alert").should("contain", "Session expired");

Call cy.clock before cy.visit so the page’s timers are controlled from load.

Component testing

// src/components/QuantityPicker.cy.tsx
import { QuantityPicker } from "./QuantityPicker";

it("never goes below 1", () => {
  const onChange = cy.stub().as("onChange");
  cy.mount(<QuantityPicker value={1} onChange={onChange} />);
  cy.findByRole("button", { name: "Decrease" }).click();
  cy.get("@onChange").should("not.have.been.called");
});

Wrap providers (router, query client, theme) in a custom mount in cypress/support/component.tsx. Use component tests for interaction-heavy widgets; keep pure logic in unit tests.

Cross-origin

Visiting a second origin (an identity provider) needs cy.origin("https://idp.example", () => { ... }). Prefer logging in through an API or test-mode IdP over driving third-party login pages, which also tend to block automation.

Running

npx cypress run --spec "cypress/e2e/checkout.cy.ts"
npx cypress run --browser chrome --headless
npx cypress run --component
npx cypress open                      # interactive, needs a display; not for Codex sandboxes

Splitting specs across CI machines needs either Cypress Cloud (--record --parallel, requires a record key) or a community splitter that assigns spec files per job by timing; choose based on whether the team already pays for Cloud.

Flake checklist

  • No cy.wait(<number>); every network dependency is an aliased intercept
  • Intercepts registered before the triggering action
  • Assertions follow the query they depend on (no stale variables)
  • Data created per test via API; no reliance on spec order
  • Animations do not block clicks (disable them in test builds, or assert visibility first)
  • retries.runMode failures reviewed: a test that needed a retry is flaky, not passing

Gotchas

  • cy.get(...).click({ force: true }) hides real bugs like overlays covering a button; use it only with a comment explaining why.
  • .then() returning a non-Cypress value inside a chain can swallow retries.
  • Detached DOM errors mean the element re-rendered between query and action; re-query after the state change instead of reusing an alias to the element.
  • Uncaught app exceptions fail tests; fix them, or filter one known third-party error in Cypress.on("uncaught:exception"), never all.

Verification

  1. Run the changed spec alone with --spec, three times in a row.
  2. Run the whole suite headless in the CI browser.
  3. Search the diff for cy.wait( followed by a number and for force: true.
  4. Confirm a deliberate break fails with a screenshot showing the expected state.

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Cypress E2E"
  short_description: "Aliased intercepts and cached sessions, no fixed waits"
  default_prompt: "$cypress-e2e Remove all the cy.wait(3000) calls from our Cypress specs and make them stable"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem