eslint-plugin-cypress 0.0.0-dev.1109
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +80 -0
- package/LICENSE +21 -0
- package/README.md +299 -0
- package/docs/rules/assertion-before-screenshot.md +28 -0
- package/docs/rules/no-and.md +37 -0
- package/docs/rules/no-assigning-return-values.md +11 -0
- package/docs/rules/no-async-before.md +52 -0
- package/docs/rules/no-async-tests.md +55 -0
- package/docs/rules/no-chained-get.md +21 -0
- package/docs/rules/no-debug.md +22 -0
- package/docs/rules/no-force.md +51 -0
- package/docs/rules/no-pause.md +22 -0
- package/docs/rules/no-unnecessary-waiting.md +11 -0
- package/docs/rules/no-xpath.md +34 -0
- package/docs/rules/require-data-selectors.md +59 -0
- package/docs/rules/unsafe-to-chain-command.md +33 -0
- package/lib/config/recommended.js +14 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.js +65 -0
- package/lib/rules/assertion-before-screenshot.js +99 -0
- package/lib/rules/no-and.js +56 -0
- package/lib/rules/no-assigning-return-values.js +68 -0
- package/lib/rules/no-async-before.js +53 -0
- package/lib/rules/no-async-tests.js +53 -0
- package/lib/rules/no-chained-get.js +71 -0
- package/lib/rules/no-debug.js +52 -0
- package/lib/rules/no-force.js +79 -0
- package/lib/rules/no-pause.js +52 -0
- package/lib/rules/no-unnecessary-waiting.js +95 -0
- package/lib/rules/no-xpath.js +43 -0
- package/lib/rules/require-data-selectors.js +77 -0
- package/lib/rules/unsafe-to-chain-command.js +123 -0
- package/lib/utils/is-root-cypress.js +46 -0
- package/package.json +68 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# cypress/no-force
|
|
2
|
+
|
|
3
|
+
📝 Disallow using `force: true` with action commands.
|
|
4
|
+
|
|
5
|
+
<!-- end auto-generated rule header -->
|
|
6
|
+
|
|
7
|
+
Using `force: true` on inputs appears to be confusing rather than helpful.
|
|
8
|
+
It usually silences the actual problem instead of providing a way to overcome it.
|
|
9
|
+
See [Cypress Core Concepts](https://docs.cypress.io/app/core-concepts/interacting-with-elements.html#Forcing).
|
|
10
|
+
|
|
11
|
+
If enabling this rule, it's recommended to set the severity to `warn`.
|
|
12
|
+
|
|
13
|
+
## Rule Details
|
|
14
|
+
|
|
15
|
+
This rule disallows using the `force` option on:[`.click()`](https://on.cypress.io/click),
|
|
16
|
+
[`.dblclick()`](https://on.cypress.io/dblclick), [`.type()`](https://on.cypress.io/type),
|
|
17
|
+
[`.rightclick()`](https://on.cypress.io/rightclick), [`.select()`](https://on.cypress.io/select),
|
|
18
|
+
[`.focus()`](https://on.cypress.io/focus), [`.check()`](https://on.cypress.io/check),
|
|
19
|
+
and [`.trigger()`](https://on.cypress.io/trigger).
|
|
20
|
+
|
|
21
|
+
Examples of **incorrect** code for this rule:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
cy.get('button').click({ force: true })
|
|
25
|
+
cy.get('button').dblclick({ force: true })
|
|
26
|
+
cy.get('input').type('somth', { force: true })
|
|
27
|
+
cy.get('div').find('.foo').find('.bar').trigger('change', { force: true })
|
|
28
|
+
cy.get('input').trigger('click', { force: true })
|
|
29
|
+
cy.get('input').rightclick({ force: true })
|
|
30
|
+
cy.get('input').check({ force: true })
|
|
31
|
+
cy.get('input').select({ force: true })
|
|
32
|
+
cy.get('input').focus({ force: true })
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Examples of **correct** code for this rule:
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
cy.get('button').click()
|
|
39
|
+
cy.get('button').click({ multiple: true })
|
|
40
|
+
cy.get('button').dblclick()
|
|
41
|
+
cy.get('input').type('somth')
|
|
42
|
+
cy.get('input').trigger('click', { anyoption: true })
|
|
43
|
+
cy.get('input').rightclick({ anyoption: true })
|
|
44
|
+
cy.get('input').check()
|
|
45
|
+
cy.get('input').select()
|
|
46
|
+
cy.get('input').focus()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## When Not To Use It
|
|
50
|
+
|
|
51
|
+
If you don't mind using `{ force: true }` with action commands, then turn this rule off.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# cypress/no-pause
|
|
2
|
+
|
|
3
|
+
📝 Disallow using `cy.pause()` calls.
|
|
4
|
+
|
|
5
|
+
<!-- end auto-generated rule header -->
|
|
6
|
+
|
|
7
|
+
It is recommended to remove any [cy.pause](https://on.cypress.io/pause) commands before committing specs to avoid other developers getting unexpected results.
|
|
8
|
+
|
|
9
|
+
## Rule Details
|
|
10
|
+
|
|
11
|
+
Examples of **incorrect** code for this rule:
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
cy.pause()
|
|
15
|
+
cy.get('selector').pause()
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Examples of **correct** code for this rule:
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
cy.get('selector')
|
|
22
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# cypress/no-unnecessary-waiting
|
|
2
|
+
|
|
3
|
+
📝 Disallow waiting for arbitrary time periods.
|
|
4
|
+
|
|
5
|
+
💼 This rule is enabled in the ✅ `recommended` config.
|
|
6
|
+
|
|
7
|
+
<!-- end auto-generated rule header -->
|
|
8
|
+
|
|
9
|
+
## Further Reading
|
|
10
|
+
|
|
11
|
+
See [the Cypress Best Practices guide](https://on.cypress.io/best-practices#Unnecessary-Waiting).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# cypress/no-xpath
|
|
2
|
+
|
|
3
|
+
📝 Disallow using `cy.xpath()` calls.
|
|
4
|
+
|
|
5
|
+
❌ This rule is deprecated. The reason for rule deprecation is the underlying deprecation and removal of support for the `@cypress/xpath` npm module in 2023. The rule is currently provided as a convenience to identify existing code that requires migration to supported API calls and it is planned for removal in a future major release of the plugin.
|
|
6
|
+
|
|
7
|
+
<!-- end auto-generated rule header -->
|
|
8
|
+
|
|
9
|
+
This rule disallows the usage of `cy.xpath()` for selecting elements.
|
|
10
|
+
|
|
11
|
+
## Rule Details
|
|
12
|
+
|
|
13
|
+
Examples of **incorrect** code for this rule:
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
cy.xpath('//div[@class=\"container\"]').click()
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Examples of **correct** code for this rule:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
cy.get('[data-cy="container"]').click()
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Migration
|
|
26
|
+
|
|
27
|
+
Both [@cypress/xpath](https://www.npmjs.com/package/@cypress/xpath) and the earlier [cypress-xpath](https://www.npmjs.com/package/cypress-xpath) npm packages are deprecated and no longer supported.
|
|
28
|
+
These packages previously provided the command `cy.xpath()` and they should be uninstalled from the Cypress project after migrating `cy.xpath()` selection to `cy.get()`. See below for more details.
|
|
29
|
+
|
|
30
|
+
After migrating away from `cy.xpath()`, the rule can be set to `off` or can be permanently removed from the ESLint configuration.
|
|
31
|
+
|
|
32
|
+
## Further Reading
|
|
33
|
+
|
|
34
|
+
See [the Cypress Best Practices guide](https://docs.cypress.io/app/core-concepts/best-practices.html#Selecting-Elements).
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# cypress/require-data-selectors
|
|
2
|
+
|
|
3
|
+
📝 Require `data-*` attribute selectors.
|
|
4
|
+
|
|
5
|
+
<!-- end auto-generated rule header -->
|
|
6
|
+
|
|
7
|
+
Require `cy.get` to use only selectors that target `data-*` attributes.
|
|
8
|
+
|
|
9
|
+
> Note: If you use this rule, consider only using the `warn` error level, since using `data-*` attribute selectors may not always be possible.
|
|
10
|
+
|
|
11
|
+
## Rule Details
|
|
12
|
+
|
|
13
|
+
This rule checks the first parameter of each `cy.get`. It supports template literals, variable (re-)assignments and conditional expressions. For conditionals, all branches must conform to this rule.
|
|
14
|
+
|
|
15
|
+
Cypress aliases (as defined by `cy.as`) are always allowed.
|
|
16
|
+
|
|
17
|
+
Examples of **incorrect** code for this rule:
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
cy.get('.a')
|
|
21
|
+
cy.get('[daedta-cy=submit]').click()
|
|
22
|
+
cy.get('[d-cy=submit]')
|
|
23
|
+
cy.get('.btn-large').click()
|
|
24
|
+
cy.get('.btn-.large').click()
|
|
25
|
+
cy.get(condition ? '[data-cy=submit]' : '.btn-large')
|
|
26
|
+
|
|
27
|
+
const CLASS_SELECTOR = '.my-class'
|
|
28
|
+
cy.get(CLASS_SELECTOR)
|
|
29
|
+
|
|
30
|
+
const TYPE = 'submit'
|
|
31
|
+
const SUBMIT_TEMPLATE = `.${TYPE}`
|
|
32
|
+
cy.get(SUBMIT_TEMPLATE)
|
|
33
|
+
|
|
34
|
+
const MY_SUBMIT_TEMPLATE = SUBMIT_TEMPLATE
|
|
35
|
+
cy.get(MY_SUBMIT_TEMPLATE)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Examples of **correct** code for this rule:
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
cy.get('[data-cy=submit]').click()
|
|
42
|
+
cy.get('[data-QA=submit]')
|
|
43
|
+
cy.get(`[data-QA=submit]`)
|
|
44
|
+
cy.get(condition ? '[data-cy=submit]' : '[data-QA=submit]')
|
|
45
|
+
|
|
46
|
+
const ASSESSMENT_SUBMIT = '[data-cy=assessment-submit]'
|
|
47
|
+
cy.get(ASSESSMENT_SUBMIT).click()
|
|
48
|
+
|
|
49
|
+
const TYPE = 'submit'
|
|
50
|
+
const SUBMIT_TEMPLATE = `[data-QA=${TYPE}]`
|
|
51
|
+
cy.get(SUBMIT_TEMPLATE)
|
|
52
|
+
|
|
53
|
+
const MY_SUBMIT_TEMPLATE = SUBMIT_TEMPLATE
|
|
54
|
+
cy.get(MY_SUBMIT_TEMPLATE)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Further Reading
|
|
58
|
+
|
|
59
|
+
See [the Cypress Best Practices guide](https://docs.cypress.io/app/core-concepts/best-practices.html#Selecting-Elements).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# cypress/unsafe-to-chain-command
|
|
2
|
+
|
|
3
|
+
📝 Disallow actions within chains.
|
|
4
|
+
|
|
5
|
+
💼 This rule is enabled in the ✅ `recommended` config.
|
|
6
|
+
|
|
7
|
+
<!-- end auto-generated rule header -->
|
|
8
|
+
|
|
9
|
+
### Options
|
|
10
|
+
|
|
11
|
+
<!-- begin auto-generated rule options list -->
|
|
12
|
+
|
|
13
|
+
| Name | Description | Type | Default |
|
|
14
|
+
| :-------- | :---------------------------------------------------------- | :---- | :------ |
|
|
15
|
+
| `methods` | An additional list of methods to check for unsafe chaining. | Array | `[]` |
|
|
16
|
+
|
|
17
|
+
<!-- end auto-generated rule options list -->
|
|
18
|
+
|
|
19
|
+
## Typed Linting
|
|
20
|
+
|
|
21
|
+
If [Typed Linting](../../README.md#typed-linting) is enabled, this rule also catches unsafe chaining even when the Cypress chain was started from a helper function.
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
function getTodo() {
|
|
25
|
+
return cy.get('.todo')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getTodo().type('todo A{enter}').type('todo B{enter}')
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Further Reading
|
|
32
|
+
|
|
33
|
+
See [retry-ability guide](https://docs.cypress.io/app/core-concepts/retry-ability#Actions-should-be-at-the-end-of-chains-not-the-middle).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
plugins: ['cypress'],
|
|
5
|
+
env: {
|
|
6
|
+
'cypress/globals': true,
|
|
7
|
+
},
|
|
8
|
+
rules: {
|
|
9
|
+
'cypress/no-assigning-return-values': 'error',
|
|
10
|
+
'cypress/no-unnecessary-waiting': 'error',
|
|
11
|
+
'cypress/no-async-tests': 'error',
|
|
12
|
+
'cypress/unsafe-to-chain-command': 'error',
|
|
13
|
+
},
|
|
14
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ESLint, Linter } from 'eslint'
|
|
2
|
+
|
|
3
|
+
declare const plugin: {
|
|
4
|
+
meta: { name: string, version: string }
|
|
5
|
+
configs: {
|
|
6
|
+
globals: Linter.Config<Linter.RulesRecord>
|
|
7
|
+
recommended: Linter.Config<Linter.RulesRecord>
|
|
8
|
+
}
|
|
9
|
+
rules: NonNullable<ESLint.Plugin['rules']>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export = plugin
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const globals = require('globals')
|
|
2
|
+
const { name, version } = require('../package.json')
|
|
3
|
+
|
|
4
|
+
const plugin = {
|
|
5
|
+
meta: { name, version },
|
|
6
|
+
configs: {},
|
|
7
|
+
rules: {
|
|
8
|
+
'assertion-before-screenshot': require('./rules/assertion-before-screenshot'),
|
|
9
|
+
'no-and': require('./rules/no-and'),
|
|
10
|
+
'no-assigning-return-values': require('./rules/no-assigning-return-values'),
|
|
11
|
+
'no-async-before': require('./rules/no-async-before'),
|
|
12
|
+
'no-async-tests': require('./rules/no-async-tests'),
|
|
13
|
+
'no-chained-get': require('./rules/no-chained-get'),
|
|
14
|
+
'no-debug': require('./rules/no-debug'),
|
|
15
|
+
'no-force': require('./rules/no-force'),
|
|
16
|
+
'no-pause': require('./rules/no-pause'),
|
|
17
|
+
'no-unnecessary-waiting': require('./rules/no-unnecessary-waiting'),
|
|
18
|
+
'no-xpath': require('./rules/no-xpath'),
|
|
19
|
+
'require-data-selectors': require('./rules/require-data-selectors'),
|
|
20
|
+
'unsafe-to-chain-command': require('./rules/unsafe-to-chain-command'),
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const commonGlobals
|
|
25
|
+
= Object.assign({
|
|
26
|
+
cy: false,
|
|
27
|
+
Cypress: false,
|
|
28
|
+
expect: false,
|
|
29
|
+
assert: false,
|
|
30
|
+
chai: false,
|
|
31
|
+
}, globals.browser, globals.mocha)
|
|
32
|
+
|
|
33
|
+
Object.assign(plugin.configs, {
|
|
34
|
+
globals: {
|
|
35
|
+
name: 'cypress/globals',
|
|
36
|
+
plugins: {
|
|
37
|
+
cypress: plugin,
|
|
38
|
+
},
|
|
39
|
+
languageOptions: {
|
|
40
|
+
globals:
|
|
41
|
+
commonGlobals,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
Object.assign(plugin.configs, {
|
|
47
|
+
recommended: {
|
|
48
|
+
name: 'cypress/recommended',
|
|
49
|
+
plugins: {
|
|
50
|
+
cypress: plugin,
|
|
51
|
+
},
|
|
52
|
+
rules: {
|
|
53
|
+
'cypress/no-assigning-return-values': 'error',
|
|
54
|
+
'cypress/no-unnecessary-waiting': 'error',
|
|
55
|
+
'cypress/no-async-tests': 'error',
|
|
56
|
+
'cypress/unsafe-to-chain-command': 'error',
|
|
57
|
+
},
|
|
58
|
+
languageOptions: {
|
|
59
|
+
globals:
|
|
60
|
+
commonGlobals,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
module.exports = plugin
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { isRootCypress } = require('../utils/is-root-cypress')
|
|
4
|
+
|
|
5
|
+
const assertionCommands = [
|
|
6
|
+
// assertions
|
|
7
|
+
'should',
|
|
8
|
+
'and',
|
|
9
|
+
'contains',
|
|
10
|
+
|
|
11
|
+
// retries until it gets something
|
|
12
|
+
'get',
|
|
13
|
+
|
|
14
|
+
// not an assertion, but unlikely to require waiting for render
|
|
15
|
+
'scrollIntoView',
|
|
16
|
+
'scrollTo',
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
meta: {
|
|
21
|
+
type: 'problem',
|
|
22
|
+
docs: {
|
|
23
|
+
description: 'require screenshots to be preceded by an assertion',
|
|
24
|
+
category: 'Possible Errors',
|
|
25
|
+
recommended: false,
|
|
26
|
+
url: 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/assertion-before-screenshot.md',
|
|
27
|
+
},
|
|
28
|
+
schema: [],
|
|
29
|
+
messages: {
|
|
30
|
+
unexpected: 'Make an assertion on the page state before taking a screenshot',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
create(context) {
|
|
34
|
+
return {
|
|
35
|
+
CallExpression(node) {
|
|
36
|
+
if (isCallingCyScreenshot(node) && !isPreviousAnAssertion(node)) {
|
|
37
|
+
context.report({ node, messageId: 'unexpected' })
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getPreviousInChain(node) {
|
|
45
|
+
return node.type === 'CallExpression'
|
|
46
|
+
&& node.callee.type === 'MemberExpression'
|
|
47
|
+
&& node.callee.object.type === 'CallExpression'
|
|
48
|
+
&& node.callee.object.callee.type === 'MemberExpression'
|
|
49
|
+
&& node.callee.object.callee.property.type === 'Identifier'
|
|
50
|
+
&& node.callee.object.callee.property.name
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getCallExpressionCypressCommand(node) {
|
|
54
|
+
return isRootCypress(node)
|
|
55
|
+
&& node.callee.property.type === 'Identifier'
|
|
56
|
+
&& node.callee.property.name
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isCallingCyScreenshot(node) {
|
|
60
|
+
return getCallExpressionCypressCommand(node) === 'screenshot'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getPreviousCypressCommand(node) {
|
|
64
|
+
const previousInChain = getPreviousInChain(node)
|
|
65
|
+
|
|
66
|
+
if (previousInChain) {
|
|
67
|
+
return previousInChain
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
while (node.parent && !node.parent.body) {
|
|
71
|
+
node = node.parent
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!node.parent || !node.parent.body) return null
|
|
75
|
+
|
|
76
|
+
const body = node.parent.body.type === 'BlockStatement' ? node.parent.body.body : node.parent.body
|
|
77
|
+
|
|
78
|
+
const index = body.indexOf(node)
|
|
79
|
+
|
|
80
|
+
// in the case of a function declaration it won't be found
|
|
81
|
+
if (index < 0) return null
|
|
82
|
+
|
|
83
|
+
if (index === 0) return getPreviousCypressCommand(node.parent)
|
|
84
|
+
|
|
85
|
+
const previousStatement = body[index - 1]
|
|
86
|
+
|
|
87
|
+
if (previousStatement.type !== 'ExpressionStatement'
|
|
88
|
+
|| previousStatement.expression.type !== 'CallExpression') {
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return getCallExpressionCypressCommand(previousStatement.expression)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isPreviousAnAssertion(node) {
|
|
96
|
+
const previousCypressCommand = getPreviousCypressCommand(node)
|
|
97
|
+
|
|
98
|
+
return assertionCommands.indexOf(previousCypressCommand) >= 0
|
|
99
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { isRootCypress } = require('../utils/is-root-cypress')
|
|
4
|
+
|
|
5
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
6
|
+
module.exports = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'suggestion',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'enforce `.should()` over `.and()` for starting assertion chains',
|
|
11
|
+
recommended: false,
|
|
12
|
+
url: 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/no-and.md',
|
|
13
|
+
},
|
|
14
|
+
fixable: 'code',
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: {
|
|
17
|
+
unexpected:
|
|
18
|
+
'Use .should() here; .and() is only allowed after .should(), .and(), or .contains().',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
create(context) {
|
|
23
|
+
const allowAndAfter = new Set(['should', 'and', 'contains'])
|
|
24
|
+
|
|
25
|
+
function isAllowedAfter(node) {
|
|
26
|
+
const object = node.callee.object
|
|
27
|
+
if (
|
|
28
|
+
object
|
|
29
|
+
&& object.type === 'CallExpression'
|
|
30
|
+
&& object.callee.type === 'MemberExpression'
|
|
31
|
+
) {
|
|
32
|
+
return allowAndAfter.has(object.callee.property.name)
|
|
33
|
+
}
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
CallExpression(node) {
|
|
39
|
+
if (
|
|
40
|
+
node.callee.type === 'MemberExpression'
|
|
41
|
+
&& node.callee.property.name === 'and'
|
|
42
|
+
&& isRootCypress(node)
|
|
43
|
+
&& !isAllowedAfter(node)
|
|
44
|
+
) {
|
|
45
|
+
context.report({
|
|
46
|
+
node,
|
|
47
|
+
messageId: 'unexpected',
|
|
48
|
+
fix(fixer) {
|
|
49
|
+
return fixer.replaceText(node.callee.property, 'should')
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// safely get nested object property
|
|
4
|
+
function get(obj, propertyString = '') {
|
|
5
|
+
const properties = propertyString.split('.')
|
|
6
|
+
|
|
7
|
+
for (let i = 0; i < properties.length; i++) {
|
|
8
|
+
const value = (obj || {})[properties[i]]
|
|
9
|
+
|
|
10
|
+
if (value == null) return value
|
|
11
|
+
|
|
12
|
+
obj = value
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return obj
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
meta: {
|
|
20
|
+
type: 'problem',
|
|
21
|
+
docs: {
|
|
22
|
+
description: 'disallow assigning return values of `cy` calls',
|
|
23
|
+
category: 'Possible Errors',
|
|
24
|
+
recommended: true,
|
|
25
|
+
url: 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/no-assigning-return-values.md',
|
|
26
|
+
},
|
|
27
|
+
schema: [],
|
|
28
|
+
messages: {
|
|
29
|
+
unexpected: 'Do not assign the return value of a Cypress command',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
create(context) {
|
|
33
|
+
return {
|
|
34
|
+
VariableDeclaration(node) {
|
|
35
|
+
if (node.declarations.some(isCypressCommandDeclaration)) {
|
|
36
|
+
context.report({ node, messageId: 'unexpected' })
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const allowedCommands = {
|
|
44
|
+
now: true,
|
|
45
|
+
spy: true,
|
|
46
|
+
state: true,
|
|
47
|
+
stub: true,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isCypressCommandDeclaration(declarator) {
|
|
51
|
+
let object = get(declarator, 'init.callee.object')
|
|
52
|
+
|
|
53
|
+
if (!object) return
|
|
54
|
+
|
|
55
|
+
while (object.callee) {
|
|
56
|
+
object = object.callee.object
|
|
57
|
+
|
|
58
|
+
if (!object) return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const commandName = get(declarator, 'init.callee.property.name')
|
|
62
|
+
|
|
63
|
+
const parent = get(object, 'parent.property.name') || get(declarator, 'id.name')
|
|
64
|
+
|
|
65
|
+
if (commandName && (allowedCommands[commandName] || allowedCommands[parent])) return
|
|
66
|
+
|
|
67
|
+
return object.name === 'cy'
|
|
68
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
meta: {
|
|
5
|
+
type: 'problem',
|
|
6
|
+
docs: {
|
|
7
|
+
description: 'disallow using `async`/`await` in Cypress `before` methods',
|
|
8
|
+
category: 'Possible Errors',
|
|
9
|
+
recommended: true,
|
|
10
|
+
url: 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/no-async-before.md',
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
messages: {
|
|
14
|
+
unexpected: 'Avoid using async functions with Cypress before / beforeEach functions',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
create(context) {
|
|
19
|
+
function isBeforeBlock(callExpressionNode) {
|
|
20
|
+
const { type, name } = callExpressionNode.callee
|
|
21
|
+
|
|
22
|
+
return type === 'Identifier'
|
|
23
|
+
&& (name === 'before' || name === 'beforeEach')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isBeforeAsync(node) {
|
|
27
|
+
return node.arguments
|
|
28
|
+
&& node.arguments.length >= 2
|
|
29
|
+
&& node.arguments[1].async === true
|
|
30
|
+
}
|
|
31
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode()
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
Identifier(node) {
|
|
35
|
+
if (node.name === 'cy' || node.name === 'Cypress') {
|
|
36
|
+
const ancestors = sourceCode.getAncestors
|
|
37
|
+
? sourceCode.getAncestors(node)
|
|
38
|
+
: context.getAncestors()
|
|
39
|
+
const asyncTestBlocks = ancestors
|
|
40
|
+
.filter((n) => n.type === 'CallExpression')
|
|
41
|
+
.filter(isBeforeBlock)
|
|
42
|
+
.filter(isBeforeAsync)
|
|
43
|
+
|
|
44
|
+
if (asyncTestBlocks.length >= 1) {
|
|
45
|
+
asyncTestBlocks.forEach((node) => {
|
|
46
|
+
context.report({ node, messageId: 'unexpected' })
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
meta: {
|
|
5
|
+
type: 'problem',
|
|
6
|
+
docs: {
|
|
7
|
+
description: 'disallow using `async`/`await` in Cypress test cases',
|
|
8
|
+
category: 'Possible Errors',
|
|
9
|
+
recommended: true,
|
|
10
|
+
url: 'https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/no-async-tests.md',
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
messages: {
|
|
14
|
+
unexpected: 'Avoid using async functions with Cypress tests',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
create(context) {
|
|
19
|
+
function isTestBlock(callExpressionNode) {
|
|
20
|
+
const { type, name } = callExpressionNode.callee
|
|
21
|
+
|
|
22
|
+
return type === 'Identifier'
|
|
23
|
+
&& (name === 'it' || name === 'test')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isTestAsync(node) {
|
|
27
|
+
return node.arguments
|
|
28
|
+
&& node.arguments.length >= 2
|
|
29
|
+
&& node.arguments[1].async === true
|
|
30
|
+
}
|
|
31
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode()
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
Identifier(node) {
|
|
35
|
+
if (node.name === 'cy' || node.name === 'Cypress') {
|
|
36
|
+
const ancestors = sourceCode.getAncestors
|
|
37
|
+
? sourceCode.getAncestors(node)
|
|
38
|
+
: context.getAncestors()
|
|
39
|
+
const asyncTestBlocks = ancestors
|
|
40
|
+
.filter((n) => n.type === 'CallExpression')
|
|
41
|
+
.filter(isTestBlock)
|
|
42
|
+
.filter(isTestAsync)
|
|
43
|
+
|
|
44
|
+
if (asyncTestBlocks.length >= 1) {
|
|
45
|
+
asyncTestBlocks.forEach((node) => {
|
|
46
|
+
context.report({ node, messageId: 'unexpected' })
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
}
|