Repository: cypress-io/eslint-plugin-cypress
Pull request #30 (2019-09-16 01:13:46)

feat(rule): add 'require-data-selectors' rule
Goal :- Add best practices rule related to https://docs.cypress.io/guides/references/best-practices.html#Selecting-Elements

Testing:- Added few unit tests
```
npm run test require-data-selectors

> eslint-plugin-cypress@0.0.0-development test /Users/sb/contri_sandeep/eslint-plugin-cypress
> jest "require-data-selectors"

 PASS  tests/lib/rules/require-data-selectors.js
  no-dynamic-id-classes
    valid
      ✓ cy.get('[data-cy=submit]').click() (40ms)
      ✓ cy.get('[data-QA=submit]') (2ms)
      ✓ cy.clock(5000) (1ms)
      ✓ cy.scrollTo(0, 10) (1ms)
      ✓ cy.tick(500) (1ms)
    invalid
      ✓ cy.get('[daedta-cy=submit]').click() (3ms)
      ✓ cy.get('[d-cy=submit]') (1ms)
      ✓ cy.get(".btn-large").click() (2ms)
      ✓ cy.get(".btn-.large").click() (1ms)
      ✓ cy.get(".a") (1ms)
```




sandeepbaldawa: Ready for a re-review 🙇 
bahmutov: @Bkucera could you review this PR again - the author has addressed your comments I think

Patch:
diff --git a/docs/rules/require-data-selectors.md b/docs/rules/require-data-selectors.md
new file mode 100644
index 0000000..4c073e7
--- /dev/null
+++ b/docs/rules/require-data-selectors.md
@@ -0,0 +1,23 @@
+## Only allow `data-*` attribute selectors (require-data-selectors)
+only allow `cy.get` to allow selectors that target `data-*` attributes
+
+See [the Cypress Best Practices guide](https://docs.cypress.io/guides/references/best-practices.html#Selecting-Elements).
+
+> Note: If you use this rule, consider only using the `warn` error level, since using `data-*` attribute selectors may not always be possible.
+
+### Rule Details
+
+examples of **incorrect** code with `require-data-selectors`:
+```js
+cy.get(".a")
+cy.get('[daedta-cy=submit]').click()
+cy.get('[d-cy=submit]')
+cy.get(".btn-large").click()
+cy.get(".btn-.large").click()
+```
+
+examples of **correct** code with `require-data-selectors`:
+```js
+cy.get('[data-cy=submit]').click()
+cy.get('[data-QA=submit]')
+```
diff --git a/index.js b/index.js
index 044c699..e1eb471 100644
--- a/index.js
+++ b/index.js
@@ -5,6 +5,7 @@ module.exports = {
     'no-assigning-return-values': require('./lib/rules/no-assigning-return-values'),
     'no-unnecessary-waiting': require('./lib/rules/no-unnecessary-waiting'),
     'assertion-before-screenshot': require('./lib/rules/assertion-before-screenshot'),
+    'require-data-selectors': require('./lib/rules/require-data-selectors'),
   },
   configs: {
     recommended: require('./lib/config/recommended'),
diff --git a/lib/rules/require-data-selectors.js b/lib/rules/require-data-selectors.js
new file mode 100644
index 0000000..b8d5203
--- /dev/null
+++ b/lib/rules/require-data-selectors.js
@@ -0,0 +1,45 @@
+/**
+ * @fileoverview Use data-* attribute selectors instead of classes or tag names
+ * @author Sandeep Baldawa
+ */
+'use strict'
+
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Use data-* attributes to provide context to your selectors and insulate them from CSS or JS changes https://docs.cypress.io/guides/references/best-practices.html#Selecting-Elements',
+      category: 'Possible Errors',
+      recommended: false,
+      url: 'https://docs.cypress.io/guides/references/best-practices.html#Selecting-Elements',
+    },
+    schema: [],
+    messages: {
+      unexpected: 'use data-* attribute selectors instead of classes or tag names',
+    },
+  },
+
+  create (context) {
+    return {
+      CallExpression (node) {
+        if (isCallingCyGet(node) && !isDataArgument(node)) {
+          context.report({ node, messageId: 'unexpected' })
+        }
+      },
+    }
+  },
+}
+
+function isCallingCyGet (node) {
+  return node.callee.type === 'MemberExpression' &&
+         node.callee.object.type === 'Identifier' &&
+         node.callee.object.name === 'cy' &&
+         node.callee.property.type === 'Identifier' &&
+         node.callee.property.name === 'get'
+}
+
+function isDataArgument (node) {
+  return node.arguments.length > 0 &&
+  node.arguments[0].type === 'Literal' &&
+  String(node.arguments[0].value).startsWith('[data-')
+
+}


Test patch:
diff --git a/tests/lib/rules/require-data-selectors.js b/tests/lib/rules/require-data-selectors.js
new file mode 100644
index 0000000..be6b17d
--- /dev/null
+++ b/tests/lib/rules/require-data-selectors.js
@@ -0,0 +1,27 @@
+'use strict'
+
+const rule = require('../../../lib/rules/require-data-selectors')
+const RuleTester = require('eslint').RuleTester
+
+const ruleTester = new RuleTester()
+
+const errors = [{ messageId: 'unexpected' }]
+const parserOptions = { ecmaVersion: 6 }
+
+ruleTester.run('no-dynamic-id-classes', rule, {
+  valid: [
+    { code: 'cy.get(\'[data-cy=submit]\').click()', parserOptions },
+    { code: 'cy.get(\'[data-QA=submit]\')', parserOptions },
+    { code: 'cy.clock(5000)', parserOptions },
+    { code: 'cy.scrollTo(0, 10)', parserOptions },
+    { code: 'cy.tick(500)', parserOptions },
+  ],
+
+  invalid: [
+    { code: 'cy.get(\'[daedta-cy=submit]\').click()', parserOptions, errors },
+    { code: 'cy.get(\'[d-cy=submit]\')', parserOptions, errors },
+    { code: 'cy.get(".btn-large").click()', parserOptions, errors },
+    { code: 'cy.get(".btn-.large").click()', parserOptions, errors },
+    { code: 'cy.get(".a")', parserOptions, errors },
+  ],
+})