ESLint Plugin: Cypress Silent Pass
One ESLint rule for a blind spot that eslint-plugin-cypress does not cover: chai assertions on a Cypress query that can never fail.
// ❌ always passes — cy.get() yields a chainable object that always exists
expect(cy.get('.badge')).to.exist;
expect(cy.get('.badge')).to.be.ok;
expect(cy.find('.row')).to.not.be.null;
expect(cy.contains('Save')).to.be.an('object');
// ✅ retrying assertion that actually checks the element
cy.get('.badge').should('be.visible');cy.get() / cy.find() / cy.contains() don't return the element — they return a Cypress chainable object, which always exists, is always truthy, and is never null. So a chai expect(cy.get(...)).to.exist asserts nothing about the DOM; the test stays green whether the element is there or not.
Why a new rule?
eslint-plugin-cypress ships no rule for this. eslint-plugin-ui-testing has missing-assertion-in-test (catches tests with no assertion) but not the always-true assertion above. This rule fills that gap and is designed to avoid false positives: it only fires when the expect subject is an inline cy.* query chain, so plain values and non-Cypress code are never touched.
The always-pass class isn't hypothetical — fixes for silent-pass E2E assertions have been reviewed and merged into real projects (see e2e-skills · Proven in OSS, 8 merged PRs). This rule catches the inline cy-query slice of that class automatically.
Install
# npm
npm i -D eslint-plugin-cypress-silent-pass
# yarn
yarn add -D eslint-plugin-cypress-silent-pass
# pnpm
pnpm add -D eslint-plugin-cypress-silent-pass
# bun
bun add -d eslint-plugin-cypress-silent-passUsage (flat config, ESLint 9+)
// eslint.config.js
import silentPass from "eslint-plugin-cypress-silent-pass";
export default [
silentPass.configs["flat/recommended"],
];Or wire it yourself:
import silentPass from "eslint-plugin-cypress-silent-pass";
export default [
{
plugins: { "cypress-silent-pass": silentPass },
rules: { "cypress-silent-pass/no-silent-pass": "error" },
},
];Legacy .eslintrc
{
"plugins": ["cypress-silent-pass"],
"rules": { "cypress-silent-pass/no-silent-pass": "error" }
}Run
npx eslint . # or: npx eslint --fix .
bunx eslint . # or: bunx eslint --fix .Rule: no-silent-pass
Flags expect(<inline cy query>) followed by an always-true chai assertion:
| Assertion | Why it always passes on a chainable |
|---|---|
.to.exist |
the chainable object always exists |
.to.be.ok |
the chainable object is always truthy |
.to.not.be.null |
the chainable object is never null |
.to.not.be.undefined |
the chainable object is never undefined |
.to.be.an('object') |
the chainable is always an object |
A query is recognized as a cy.* chain containing one of: get, find, contains, eq, first, last, filter, children, parent, parents, siblings, closest, next, prev, within, focused, root.
Auto-fixable. eslint --fix rewrites inline cy-query violations to cy.get(...).should('be.visible'). The opt-in identifier heuristic is reported only, never auto-fixed.
Options
"cypress-silent-pass/no-silent-pass": ["error", { "checkIdentifiers": true }]checkIdentifiers(defaultfalse) — also flagexpect(<identifier>)when the identifier looks like a yielded jQuery element ($el,$row,userBadge…), e.g. inside a.then(($el) => { expect($el).to.exist })callback. A heuristic; no autofix is offered for this case because the correct rewrite depends on context.
Scope
Catches the mechanical, inline always-true case. Semantic silent-pass smells — a test that clicks Delete and never checks the row is gone, asserting the pre-state instead of the post-state — are not decidable by AST and are out of scope for any linter.
Related
Part of a small family for catching tests that pass but prove nothing:
- eslint-plugin-playwright-silent-pass — the same always-pass check for Playwright.
- e2e-skills — the full agent-skill catalog: 24 Playwright/Cypress anti-patterns, including the semantic silent-pass smells a linter can't decide (nameassertion mismatch, missing post-state checks, missing auth setup, …).
This plugin is the mechanical, AST-decidable slice; e2e-skills covers the rest.
License
Apache-2.0 voidmatcha. See LICENSE.