Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ These rules enforce some of the [best practices recommended for using Cypress](h
| [no-assigning-return-values](docs/rules/no-assigning-return-values.md) | disallow assigning return values of `cy` calls | ✅ |
| [no-async-before](docs/rules/no-async-before.md) | disallow using `async`/`await` in Cypress `before` methods | |
| [no-async-tests](docs/rules/no-async-tests.md) | disallow using `async`/`await` in Cypress test cases | ✅ |
| [no-chained-get](docs/rules/no-chained-get.md) | disallow chain of `cy.get()` calls | |
| [no-debug](docs/rules/no-debug.md) | disallow using `cy.debug()` calls | |
| [no-force](docs/rules/no-force.md) | disallow using `force: true` with action commands | |
| [no-pause](docs/rules/no-pause.md) | disallow using `cy.pause()` calls | |
Expand Down
28 changes: 28 additions & 0 deletions docs/rules/no-chained-get.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Disallow chain of `cy.get()` calls (`cypress/no-chained-get`)

<!-- end auto-generated rule header -->
This rule disallow the usage of chained `.get()` calls as `cy.get()` always starts its search from the cy.root element.

## Rule Details

Examples of **incorrect** code for this rule:

```js
cy.get('parent').get('child')
```

Examples of **correct** code for this rule:

```js
cy.get('parent')
.find('child')
```

```js
cy.get('parent').within(() => {
cy.get('child')
})
```

## Further Reading

Copy link
Collaborator

@MikeMcC399 MikeMcC399 Mar 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly this should link to [cy.get()](https://on.cypress.io/api/get) (cy.get())

In the section https://docs.cypress.io/api/commands/get#Get-vs-Find it shows an example valid use of cy.get('#comparison').get('div')

cy.get('#comparison')
  .get('div')
  // finds the div.test-title outside the #comparison
  // and the div.feature inside
  .should('have.class', 'test-title')
  .and('have.class', 'feature')
cy.get('#comparison')
  .find('div')
  // the search is limited to the tree at #comparison element
  // so it finds div.feature only
  .should('have.length', 1)
  .and('have.class', 'feature')

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@duranbe

I suggest that you either remove the heading ## Further Reading if there is no content for this section, or you link to the documentation page

1 change: 1 addition & 0 deletions legacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
'no-pause': require('./lib/rules/no-pause'),
'no-debug': require('./lib/rules/no-debug'),
'no-xpath': require('./lib/rules/no-xpath'),
'no-chained-get': require('./lib/rules/no-chained-get'),
},
configs: {
recommended: require('./lib/config/recommended'),
Expand Down
1 change: 1 addition & 0 deletions lib/flat.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const plugin = {
'no-pause': require('./rules/no-pause'),
'no-debug': require('./rules/no-debug'),
'no-xpath': require('./rules/no-xpath'),
'no-chained-get': require('./rules/no-chained-get'),
},
}

Expand Down
87 changes: 87 additions & 0 deletions lib/rules/no-chained-get.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
'use strict';

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'disallow chain of `cy.get()` calls',
recommended: false,
url: 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/no-chained-get.md',
},
fixable: null,
schema: [],
messages: {
unexpected: 'Avoid chaining multiple cy.get() calls.',
},
},

create(context) {
const isRootCypress = (node) => {
if (
node.type !== 'CallExpression' ||
node.callee.type !== 'MemberExpression'
) {
return false;
}

if (
node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'cy'
) {
return true;
}

return isRootCypress(node.callee.object);
};

const hasChainedGet = (node) => {
// Check if this node is a get() call
const isGetCall =
node.callee &&
node.callee.type === 'MemberExpression' &&
node.callee.property &&
node.callee.property.type === 'Identifier' &&
node.callee.property.name === 'get';

if (!isGetCall) {
return false;
}

const obj = node.callee.object;

if (obj.type === 'CallExpression') {
const objCallee = obj.callee;

if (
objCallee &&
objCallee.type === 'MemberExpression' &&
objCallee.property &&
objCallee.property.type === 'Identifier' &&
objCallee.property.name === 'get'
) {
return true;
}

return hasChainedGet(obj);
}

return false;
};

return {
CallExpression(node) {
if (isRootCypress(node) && hasChainedGet(node)) {
context.report({
node,
messageId: 'unexpected',
});
}
},
};
},
};
31 changes: 31 additions & 0 deletions tests/lib/rules/no-chained-get.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @fileoverview disallow chain of `cy.get()` calls
* @author benoit
*/
"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const rule = require("../../../lib/rules/no-chained-get"),
RuleTester = require("eslint").RuleTester;

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester();
ruleTester.run("no-chained-get", rule, {
valid: [
{ code: "cy.get('div')" },
{ code: "cy.get('.div').find().get()" },
{ code: "cy.get('input').should('be.disabled')" },
],
invalid: [
{
code: "cy.get('div').get('div')",
errors: [{ messageId: "unexpected" }],
},
],
});