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.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- cypress-e2e Installs to ~/.agents/skills/cypress-e2e/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install cypress-e2e The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project cypress-e2e Installs to .agents/skills/cypress-e2e/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/cypress-e2e.zipunzip -o cypress-e2e.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) cypress-e2e PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
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
- You are starting a new E2E suite with multi-browser and tab needs: use playwright-e2e
- Unit-level React component behavior is enough: use react-testing-library
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.
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
- Read
AGENTS.md,cypress.config.{ts,js},cypress/support/e2e.*,cypress/support/commands.*andpackage.jsonscripts. - Check the version:
npx cypress --version. Test isolation (fresh page, cookies and storage per test) is on by default since Cypress 12. - Look for
@testing-library/cypress; if present use itsfindByRolequeries. - Note how the app is started for tests (
start-server-and-test, a Docker compose file, or a deployed URL).cypress rundownloads nothing, but the firstnpm cifetches 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, orcy.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
| Selector | Use |
|---|---|
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-child | avoid; 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.interceptbefore the action that triggers the request, usually beforecy.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.sessioncaches cookies and storage for the given id;validatere-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
--specorit.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.runModefailures 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
- Run the changed spec alone with
--spec, three times in a row. - Run the whole suite headless in the CI browser.
- Search the diff for
cy.wait(followed by a number and forforce: true. - 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.yaml
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.