eslint-plugin-cypress 2.10.3 → 2.11.3
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/README.md +54 -3
- package/circle.yml +64 -9
- package/docs/rules/no-async-tests.md +52 -0
- package/index.js +1 -0
- package/jest.config.js +6 -0
- package/lib/config/recommended.js +1 -0
- package/lib/rules/assertion-before-screenshot.js +14 -14
- package/lib/rules/no-assigning-return-values.js +9 -7
- package/lib/rules/no-async-tests.js +47 -0
- package/lib/rules/no-force.js +1 -6
- package/lib/rules/no-unnecessary-waiting.js +2 -6
- package/lib/rules/require-data-selectors.js +8 -6
- package/package.json +5 -2
- package/tests/config.js +4 -3
- package/tests/lib/rules/no-assigning-return-values.js +1 -0
- package/tests/lib/rules/no-async-tests.js +25 -0
- package/tests/lib/rules/no-force.js +8 -7
- package/tests/lib/rules/no-unnecessary-waiting.js +23 -0
- package/tests/lib/rules/require-data-selectors.js +2 -0
package/README.md
CHANGED
|
@@ -9,6 +9,10 @@ Note: If you installed ESLint globally then you must also install `eslint-plugin
|
|
|
9
9
|
```sh
|
|
10
10
|
npm install eslint-plugin-cypress --save-dev
|
|
11
11
|
```
|
|
12
|
+
or
|
|
13
|
+
```sh
|
|
14
|
+
yarn add eslint-plugin-cypress --dev
|
|
15
|
+
```
|
|
12
16
|
|
|
13
17
|
## Usage
|
|
14
18
|
|
|
@@ -30,12 +34,13 @@ You can add rules:
|
|
|
30
34
|
"cypress/no-assigning-return-values": "error",
|
|
31
35
|
"cypress/no-unnecessary-waiting": "error",
|
|
32
36
|
"cypress/assertion-before-screenshot": "warn",
|
|
33
|
-
"cypress/no-force": "warn"
|
|
37
|
+
"cypress/no-force": "warn",
|
|
38
|
+
"cypress/no-async-tests": "error"
|
|
34
39
|
}
|
|
35
40
|
}
|
|
36
41
|
```
|
|
37
42
|
|
|
38
|
-
You can
|
|
43
|
+
You can allow certain globals provided by Cypress:
|
|
39
44
|
|
|
40
45
|
```json
|
|
41
46
|
{
|
|
@@ -57,6 +62,51 @@ Use the recommended configuration and you can forego configuring _plugins_, _rul
|
|
|
57
62
|
}
|
|
58
63
|
```
|
|
59
64
|
|
|
65
|
+
## Disable rules
|
|
66
|
+
|
|
67
|
+
You can disable specific rules per file, for a portion of a file, or for a single line.
|
|
68
|
+
|
|
69
|
+
Disable the `cypress/no-unnecessary-waiting` rule for the entire file by placing this at the start of the file:
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
/* eslint-disable cypress/no-unnecessary-waiting */
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Disable the `cypress/no-unnecessary-waiting` rule for a portion of the file:
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
it('waits for a second', () => {
|
|
79
|
+
...
|
|
80
|
+
/* eslint-disable cypress/no-unnecessary-waiting */
|
|
81
|
+
cy.wait(1000)
|
|
82
|
+
/* eslint-enable cypress/no-unnecessary-waiting */
|
|
83
|
+
...
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Disable the `cypress/no-unnecessary-waiting` rule for a specific line:
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
it('waits for a second', () => {
|
|
91
|
+
...
|
|
92
|
+
cy.wait(1000) // eslint-disable-line cypress/no-unnecessary-waiting
|
|
93
|
+
...
|
|
94
|
+
})
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
You can also disable a rule for the next line:
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
it('waits for a second', () => {
|
|
101
|
+
...
|
|
102
|
+
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
|
103
|
+
cy.wait(1000)
|
|
104
|
+
...
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
For more, see the [ESLint rules](https://eslint.org/docs/user-guide/configuring/rules) documentation.
|
|
109
|
+
|
|
60
110
|
## Rules
|
|
61
111
|
|
|
62
112
|
These rules enforce some of the [best practices recommended for using Cypress](https://on.cypress.io/best-practices).
|
|
@@ -69,7 +119,8 @@ Rules with a check mark (✅) are enabled by default while using the `plugin:cyp
|
|
|
69
119
|
| :-- | :------------------------------------------------------------------------- | :-------------------------------------------------------------- |
|
|
70
120
|
| ✅ | [no-assigning-return-values](./docs/rules/no-assigning-return-values.md) | Prevent assigning return values of cy calls |
|
|
71
121
|
| ✅ | [no-unnecessary-waiting](./docs/rules/no-unnecessary-waiting.md) | Prevent waiting for arbitrary time periods |
|
|
72
|
-
|
|
|
122
|
+
| ✅ | [no-async-tests](./docs/rules/no-async-tests.md) | Prevent using async/await in Cypress test case |
|
|
123
|
+
| | [no-force](./docs/rules/no-force.md) | Disallow using `force: true` with action commands |
|
|
73
124
|
| | [assertion-before-screenshot](./docs/rules/assertion-before-screenshot.md) | Ensure screenshots are preceded by an assertion |
|
|
74
125
|
| | [require-data-selectors](./docs/rules/require-data-selectors.md) | Only allow data-\* attribute selectors (require-data-selectors) |
|
|
75
126
|
|
package/circle.yml
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
|
-
version: 2
|
|
1
|
+
version: 2.1
|
|
2
2
|
|
|
3
3
|
workflows:
|
|
4
|
-
version: 2
|
|
5
|
-
|
|
4
|
+
version: 2.1
|
|
5
|
+
main:
|
|
6
6
|
jobs:
|
|
7
|
-
-
|
|
7
|
+
- lint
|
|
8
|
+
- test-v4
|
|
9
|
+
- test-v5
|
|
10
|
+
- test-v6
|
|
11
|
+
- release:
|
|
12
|
+
requires:
|
|
13
|
+
- lint
|
|
14
|
+
- test-v4
|
|
15
|
+
- test-v5
|
|
16
|
+
- test-v6
|
|
17
|
+
filters:
|
|
18
|
+
branches:
|
|
19
|
+
only:
|
|
20
|
+
- master
|
|
8
21
|
|
|
9
22
|
jobs:
|
|
10
|
-
|
|
23
|
+
lint:
|
|
11
24
|
docker:
|
|
12
25
|
- image: circleci/node:8
|
|
13
26
|
steps:
|
|
@@ -18,9 +31,51 @@ jobs:
|
|
|
18
31
|
- run:
|
|
19
32
|
name: Lint code
|
|
20
33
|
command: npm run lint
|
|
34
|
+
|
|
35
|
+
test-v4:
|
|
36
|
+
docker:
|
|
37
|
+
- image: circleci/node:8
|
|
38
|
+
steps:
|
|
39
|
+
- checkout
|
|
40
|
+
- run:
|
|
41
|
+
name: Install dependencies
|
|
42
|
+
command: npm install
|
|
43
|
+
- run:
|
|
44
|
+
name: Test ESLint 4
|
|
45
|
+
command: npm run test:v4
|
|
46
|
+
|
|
47
|
+
test-v5:
|
|
48
|
+
docker:
|
|
49
|
+
- image: circleci/node:8
|
|
50
|
+
steps:
|
|
51
|
+
- checkout
|
|
52
|
+
- run:
|
|
53
|
+
name: Install dependencies
|
|
54
|
+
command: npm install
|
|
21
55
|
- run:
|
|
22
|
-
name:
|
|
23
|
-
command: npm test
|
|
56
|
+
name: Test ESLint 5
|
|
57
|
+
command: npm run test
|
|
58
|
+
|
|
59
|
+
test-v6:
|
|
60
|
+
docker:
|
|
61
|
+
- image: circleci/node:8
|
|
62
|
+
steps:
|
|
63
|
+
- checkout
|
|
64
|
+
- run:
|
|
65
|
+
name: Install dependencies
|
|
66
|
+
command: npm install
|
|
67
|
+
- run:
|
|
68
|
+
name: Test ESLint 6
|
|
69
|
+
command: npm run test:v6
|
|
70
|
+
|
|
71
|
+
release:
|
|
72
|
+
docker:
|
|
73
|
+
- image: circleci/node:8
|
|
74
|
+
steps:
|
|
75
|
+
- checkout
|
|
76
|
+
- run:
|
|
77
|
+
name: Install dependencies
|
|
78
|
+
command: npm install
|
|
24
79
|
- run:
|
|
25
|
-
name:
|
|
26
|
-
command: npm run semantic-release
|
|
80
|
+
name: Run semantic release
|
|
81
|
+
command: npm run semantic-release
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Prevent using async/await in Cypress test cases (no-async-tests)
|
|
2
|
+
|
|
3
|
+
Cypress tests [that return a promise will error](https://docs.cypress.io/guides/references/error-messages.html#Cypress-detected-that-you-returned-a-promise-from-a-command-while-also-invoking-one-or-more-cy-commands-in-that-promise) and cannot run successfully. An `async` function returns a promise under the hood, so a test using an `async` function will also error.
|
|
4
|
+
|
|
5
|
+
## Rule Details
|
|
6
|
+
|
|
7
|
+
This rule disallows using `async` test functions.
|
|
8
|
+
|
|
9
|
+
Examples of **incorrect** code for this rule:
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
describe('my feature', () => {
|
|
13
|
+
it('my test case', async () => {
|
|
14
|
+
await cy.get('.myClass')
|
|
15
|
+
// other operations
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
describe('my feature', () => {
|
|
22
|
+
it('my test case', async () => {
|
|
23
|
+
cy
|
|
24
|
+
.get('.myClass')
|
|
25
|
+
.click()
|
|
26
|
+
|
|
27
|
+
await someAsyncFunction()
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Examples of **correct** code for this rule:
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
describe('my feature', () => {
|
|
36
|
+
it('my test case', () => {
|
|
37
|
+
cy.get('.myClass')
|
|
38
|
+
// other operations
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## When Not To Use It
|
|
45
|
+
|
|
46
|
+
If there are genuine use-cases for using `async/await` in your test cases then you may not want to include this rule (or at least demote it to a warning).
|
|
47
|
+
|
|
48
|
+
## Further Reading
|
|
49
|
+
|
|
50
|
+
- [Commands Are Asynchronous](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Commands-Are-Asynchronous)
|
|
51
|
+
- [Commands Are Promises](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Commands-Are-Promises)
|
|
52
|
+
- [Commands Are Not Promises](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress.html#Commands-Are-Not-Promises)
|
package/index.js
CHANGED
|
@@ -4,6 +4,7 @@ module.exports = {
|
|
|
4
4
|
rules: {
|
|
5
5
|
'no-assigning-return-values': require('./lib/rules/no-assigning-return-values'),
|
|
6
6
|
'no-unnecessary-waiting': require('./lib/rules/no-unnecessary-waiting'),
|
|
7
|
+
'no-async-tests': require('./lib/rules/no-async-tests'),
|
|
7
8
|
'assertion-before-screenshot': require('./lib/rules/assertion-before-screenshot'),
|
|
8
9
|
'require-data-selectors': require('./lib/rules/require-data-selectors'),
|
|
9
10
|
'no-force': require('./lib/rules/no-force'),
|
package/jest.config.js
ADDED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Assert on the page state before taking a screenshot, so the screenshot is consistent
|
|
3
|
-
* @author Luke Page
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
'use strict'
|
|
7
2
|
|
|
8
3
|
const assertionCommands = [
|
|
@@ -17,7 +12,7 @@ const assertionCommands = [
|
|
|
17
12
|
// not an assertion, but unlikely to require waiting for render
|
|
18
13
|
'scrollIntoView',
|
|
19
14
|
'scrollTo',
|
|
20
|
-
]
|
|
15
|
+
]
|
|
21
16
|
|
|
22
17
|
module.exports = {
|
|
23
18
|
meta: {
|
|
@@ -42,19 +37,22 @@ module.exports = {
|
|
|
42
37
|
},
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
function isRootCypress(node) {
|
|
46
|
-
while(node.type === 'CallExpression') {
|
|
40
|
+
function isRootCypress (node) {
|
|
41
|
+
while (node.type === 'CallExpression') {
|
|
47
42
|
if (node.callee.type !== 'MemberExpression') return false
|
|
43
|
+
|
|
48
44
|
if (node.callee.object.type === 'Identifier' &&
|
|
49
45
|
node.callee.object.name === 'cy') {
|
|
50
46
|
return true
|
|
51
47
|
}
|
|
48
|
+
|
|
52
49
|
node = node.callee.object
|
|
53
50
|
}
|
|
51
|
+
|
|
54
52
|
return false
|
|
55
53
|
}
|
|
56
54
|
|
|
57
|
-
function getPreviousInChain(node) {
|
|
55
|
+
function getPreviousInChain (node) {
|
|
58
56
|
return node.type === 'CallExpression' &&
|
|
59
57
|
node.callee.type === 'MemberExpression' &&
|
|
60
58
|
node.callee.object.type === 'CallExpression' &&
|
|
@@ -63,7 +61,7 @@ function getPreviousInChain(node) {
|
|
|
63
61
|
node.callee.object.callee.property.name
|
|
64
62
|
}
|
|
65
63
|
|
|
66
|
-
function getCallExpressionCypressCommand(node) {
|
|
64
|
+
function getCallExpressionCypressCommand (node) {
|
|
67
65
|
return isRootCypress(node) &&
|
|
68
66
|
node.callee.property.type === 'Identifier' &&
|
|
69
67
|
node.callee.property.name
|
|
@@ -73,14 +71,14 @@ function isCallingCyScreenshot (node) {
|
|
|
73
71
|
return getCallExpressionCypressCommand(node) === 'screenshot'
|
|
74
72
|
}
|
|
75
73
|
|
|
76
|
-
function getPreviousCypressCommand(node) {
|
|
74
|
+
function getPreviousCypressCommand (node) {
|
|
77
75
|
const previousInChain = getPreviousInChain(node)
|
|
78
76
|
|
|
79
77
|
if (previousInChain) {
|
|
80
78
|
return previousInChain
|
|
81
79
|
}
|
|
82
80
|
|
|
83
|
-
while(node.parent && !node.parent.body) {
|
|
81
|
+
while (node.parent && !node.parent.body) {
|
|
84
82
|
node = node.parent
|
|
85
83
|
}
|
|
86
84
|
|
|
@@ -93,18 +91,20 @@ function getPreviousCypressCommand(node) {
|
|
|
93
91
|
// in the case of a function declaration it won't be found
|
|
94
92
|
if (index < 0) return null
|
|
95
93
|
|
|
96
|
-
if (index === 0) return getPreviousCypressCommand(node.parent)
|
|
94
|
+
if (index === 0) return getPreviousCypressCommand(node.parent)
|
|
97
95
|
|
|
98
96
|
const previousStatement = body[index - 1]
|
|
99
97
|
|
|
100
98
|
if (previousStatement.type !== 'ExpressionStatement' ||
|
|
101
|
-
previousStatement.expression.type !== 'CallExpression')
|
|
99
|
+
previousStatement.expression.type !== 'CallExpression') {
|
|
102
100
|
return null
|
|
101
|
+
}
|
|
103
102
|
|
|
104
103
|
return getCallExpressionCypressCommand(previousStatement.expression)
|
|
105
104
|
}
|
|
106
105
|
|
|
107
106
|
function isPreviousAnAssertion (node) {
|
|
108
107
|
const previousCypressCommand = getPreviousCypressCommand(node)
|
|
108
|
+
|
|
109
109
|
return assertionCommands.indexOf(previousCypressCommand) >= 0
|
|
110
110
|
}
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Prevent assigning return value of cy calls
|
|
3
|
-
* @author Elad Shahar
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
'use strict'
|
|
7
2
|
|
|
8
3
|
// safely get nested object property
|
|
9
4
|
function get (obj, propertyString = '') {
|
|
10
5
|
const properties = propertyString.split('.')
|
|
6
|
+
|
|
11
7
|
for (let i = 0; i < properties.length; i++) {
|
|
12
8
|
const value = (obj || {})[properties[i]]
|
|
9
|
+
|
|
13
10
|
if (value == null) return value
|
|
11
|
+
|
|
14
12
|
obj = value
|
|
15
13
|
}
|
|
14
|
+
|
|
16
15
|
return obj
|
|
17
16
|
}
|
|
18
17
|
|
|
@@ -40,7 +39,7 @@ module.exports = {
|
|
|
40
39
|
},
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
const
|
|
42
|
+
const allowedCommands = {
|
|
44
43
|
now: true,
|
|
45
44
|
spy: true,
|
|
46
45
|
state: true,
|
|
@@ -57,9 +56,12 @@ function isCypressCommandDeclaration (declarator) {
|
|
|
57
56
|
|
|
58
57
|
if (!object) return
|
|
59
58
|
}
|
|
59
|
+
|
|
60
60
|
const commandName = get(declarator, 'init.callee.property.name')
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
const parent = get(object, 'parent.property.name') || get(declarator, 'id.name')
|
|
63
|
+
|
|
64
|
+
if (commandName && (allowedCommands[commandName] || allowedCommands[parent])) return
|
|
63
65
|
|
|
64
66
|
return object.name === 'cy'
|
|
65
67
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
meta: {
|
|
5
|
+
docs: {
|
|
6
|
+
description: 'Prevent using async/await in Cypress test cases',
|
|
7
|
+
category: 'Possible Errors',
|
|
8
|
+
recommended: true,
|
|
9
|
+
},
|
|
10
|
+
messages: {
|
|
11
|
+
unexpected: 'Avoid using async functions with Cypress tests',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
create (context) {
|
|
16
|
+
function isTestBlock (callExpressionNode) {
|
|
17
|
+
const { type, name } = callExpressionNode.callee
|
|
18
|
+
|
|
19
|
+
return type === 'Identifier'
|
|
20
|
+
&& name === 'it' || name === 'test'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isTestAsync (node) {
|
|
24
|
+
return node.arguments
|
|
25
|
+
&& node.arguments.length >= 2
|
|
26
|
+
&& node.arguments[1].async === true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
Identifier (node) {
|
|
31
|
+
if (node.name === 'cy' || node.name === 'Cypress') {
|
|
32
|
+
const ancestors = context.getAncestors()
|
|
33
|
+
const asyncTestBlocks = ancestors
|
|
34
|
+
.filter((n) => n.type === 'CallExpression')
|
|
35
|
+
.filter(isTestBlock)
|
|
36
|
+
.filter(isTestAsync)
|
|
37
|
+
|
|
38
|
+
if (asyncTestBlocks.length >= 1) {
|
|
39
|
+
asyncTestBlocks.forEach((node) => {
|
|
40
|
+
context.report({ node, messageId: 'unexpected' })
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
}
|
package/lib/rules/no-force.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Disallow using of \'force: true\' option for click and type calls
|
|
3
|
-
* @author Alex Kuznetsov
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
'use strict'
|
|
7
2
|
|
|
8
3
|
//------------------------------------------------------------------------------
|
|
@@ -47,7 +42,7 @@ module.exports = {
|
|
|
47
42
|
|
|
48
43
|
return node.arguments && node.arguments.length &&
|
|
49
44
|
node.arguments.some((arg) => {
|
|
50
|
-
return arg.type === 'ObjectExpression' && arg.properties.some((propNode) => propNode.key.name === 'force')
|
|
45
|
+
return arg.type === 'ObjectExpression' && arg.properties.some((propNode) => propNode.key && propNode.key.name === 'force')
|
|
51
46
|
})
|
|
52
47
|
}
|
|
53
48
|
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Prevent waiting for arbitrary time periods
|
|
3
|
-
* @author Elad Shahar
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
'use strict'
|
|
7
2
|
|
|
8
3
|
module.exports = {
|
|
@@ -61,7 +56,8 @@ function isIdentifierNumberConstArgument (node, scope) {
|
|
|
61
56
|
|
|
62
57
|
if (node.arguments[0].type !== 'Identifier') return false
|
|
63
58
|
|
|
64
|
-
const
|
|
59
|
+
const identifier = node.arguments[0]
|
|
60
|
+
const resolvedIdentifier = scope.references.find((ref) => ref.identifier === identifier).resolved
|
|
65
61
|
const definition = resolvedIdentifier.defs[0]
|
|
66
62
|
const isVariable = definition.type === 'Variable'
|
|
67
63
|
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Use data-* attribute selectors instead of classes or tag names
|
|
3
|
-
* @author Sandeep Baldawa
|
|
4
|
-
*/
|
|
5
1
|
'use strict'
|
|
6
2
|
|
|
7
3
|
module.exports = {
|
|
@@ -40,7 +36,13 @@ function isCallingCyGet (node) {
|
|
|
40
36
|
function isDataArgument (node) {
|
|
41
37
|
return node.arguments.length > 0 &&
|
|
42
38
|
(
|
|
43
|
-
(node.arguments[0].type === 'Literal' && String(node.arguments[0].value)
|
|
44
|
-
(node.arguments[0].type === 'TemplateLiteral' && String(node.arguments[0].quasis[0].value.cooked)
|
|
39
|
+
(node.arguments[0].type === 'Literal' && isAliasOrDataSelector(String(node.arguments[0].value))) ||
|
|
40
|
+
(node.arguments[0].type === 'TemplateLiteral' && isAliasOrDataSelector(String(node.arguments[0].quasis[0].value.cooked)))
|
|
45
41
|
)
|
|
46
42
|
}
|
|
43
|
+
|
|
44
|
+
function isAliasOrDataSelector (selector) {
|
|
45
|
+
return ['[data-', '@'].some(function (validValue) {
|
|
46
|
+
return selector.startsWith(validValue)
|
|
47
|
+
})
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-cypress",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.3",
|
|
4
4
|
"description": "An ESLint plugin for projects using Cypress",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Chris Breiding (chris@cypress.io)",
|
|
@@ -35,10 +35,13 @@
|
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"precommit": "npm run lint",
|
|
38
|
-
"lint": "eslint *.js",
|
|
38
|
+
"lint": "eslint '*.js' '**/**/*.js'",
|
|
39
|
+
"lint-fix": "npm run lint -- --fix",
|
|
39
40
|
"semantic-release": "semantic-release",
|
|
40
41
|
"start": "yarn run test-watch",
|
|
41
42
|
"test": "jest",
|
|
43
|
+
"test:v4": "npm i eslint@4.x && npm run test",
|
|
44
|
+
"test:v6": "npm i eslint@6.x && npm run test",
|
|
42
45
|
"test-watch": "jest --watchAll"
|
|
43
46
|
},
|
|
44
47
|
"jest": {
|
package/tests/config.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* global describe, it, expect */
|
|
1
2
|
'use strict'
|
|
2
3
|
|
|
3
4
|
const globals = require('globals')
|
|
@@ -9,17 +10,17 @@ describe('environments globals', () => {
|
|
|
9
10
|
it('should not mutate globals', () => {
|
|
10
11
|
expect(globals.browser).not.toHaveProperty('cy')
|
|
11
12
|
expect(globals.mocha).not.toHaveProperty('cy')
|
|
12
|
-
})
|
|
13
|
+
})
|
|
13
14
|
|
|
14
15
|
it('should include other globals', () => {
|
|
15
16
|
expect(env.globals).toEqual(expect.objectContaining(globals.browser))
|
|
16
17
|
expect(env.globals).toEqual(expect.objectContaining(globals.mocha))
|
|
17
|
-
})
|
|
18
|
+
})
|
|
18
19
|
|
|
19
20
|
it('should include cypress globals', () => {
|
|
20
21
|
expect(env.globals).toEqual(expect.objectContaining({
|
|
21
22
|
cy: false,
|
|
22
23
|
Cypress: false,
|
|
23
24
|
}))
|
|
24
|
-
})
|
|
25
|
+
})
|
|
25
26
|
})
|
|
@@ -16,6 +16,7 @@ ruleTester.run('no-assigning-return-values', rule, {
|
|
|
16
16
|
{ code: 'const foo = bar();', parserOptions },
|
|
17
17
|
{ code: 'const foo = bar().baz();', parserOptions },
|
|
18
18
|
{ code: 'const spy = cy.spy();', parserOptions },
|
|
19
|
+
{ code: 'const spy = cy.spy().as();', parserOptions },
|
|
19
20
|
{ code: 'const stub = cy.stub();', parserOptions },
|
|
20
21
|
{ code: 'const result = cy.now();', parserOptions },
|
|
21
22
|
{ code: 'const state = cy.state();', parserOptions },
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const rule = require('../../../lib/rules/no-async-tests')
|
|
4
|
+
const RuleTester = require('eslint').RuleTester
|
|
5
|
+
|
|
6
|
+
const ruleTester = new RuleTester()
|
|
7
|
+
|
|
8
|
+
const errors = [{ messageId: 'unexpected' }]
|
|
9
|
+
// async functions are an ES2017 feature
|
|
10
|
+
const parserOptions = { ecmaVersion: 8 }
|
|
11
|
+
|
|
12
|
+
ruleTester.run('no-async-tests', rule, {
|
|
13
|
+
valid: [
|
|
14
|
+
{ code: 'it(\'a test case\', () => { cy.get(\'.someClass\'); })', parserOptions },
|
|
15
|
+
{ code: 'it(\'a test case\', async () => { await somethingAsync(); })', parserOptions },
|
|
16
|
+
{ code: 'async function nonTestFn () { return await somethingAsync(); }', parserOptions },
|
|
17
|
+
{ code: 'const nonTestArrowFn = async () => { await somethingAsync(); }', parserOptions },
|
|
18
|
+
],
|
|
19
|
+
invalid: [
|
|
20
|
+
{ code: 'it(\'a test case\', async () => { cy.get(\'.someClass\'); })', parserOptions, errors },
|
|
21
|
+
{ code: 'test(\'a test case\', async () => { cy.get(\'.someClass\'); })', parserOptions, errors },
|
|
22
|
+
{ code: 'it(\'a test case\', async function () { cy.get(\'.someClass\'); })', parserOptions, errors },
|
|
23
|
+
{ code: 'test(\'a test case\', async function () { cy.get(\'.someClass\'); })', parserOptions, errors },
|
|
24
|
+
],
|
|
25
|
+
})
|
|
@@ -9,7 +9,7 @@ const rule = require('../../../lib/rules/no-force')
|
|
|
9
9
|
const RuleTester = require('eslint').RuleTester
|
|
10
10
|
|
|
11
11
|
const errors = [{ messageId: 'unexpected' }]
|
|
12
|
-
const parserOptions = { ecmaVersion:
|
|
12
|
+
const parserOptions = { ecmaVersion: 2018 }
|
|
13
13
|
|
|
14
14
|
//------------------------------------------------------------------------------
|
|
15
15
|
// Tests
|
|
@@ -24,12 +24,13 @@ ruleTester.run('no-force', rule, {
|
|
|
24
24
|
{ code: `cy.get('button').click({multiple: true})`, parserOptions },
|
|
25
25
|
{ code: `cy.get('button').dblclick()`, parserOptions },
|
|
26
26
|
{ code: `cy.get('input').type('somth')`, parserOptions },
|
|
27
|
-
{ code: `cy.get('input').type('somth', {anyoption: true})`, parserOptions
|
|
28
|
-
{ code: `cy.get('input').trigger('click', {anyoption: true})`, parserOptions
|
|
29
|
-
{ code: `cy.get('input').rightclick({anyoption: true})`, parserOptions
|
|
30
|
-
{ code: `cy.get('input').check()`, parserOptions
|
|
31
|
-
{ code: `cy.get('input').select()`, parserOptions
|
|
32
|
-
{ code: `cy.get('input').focus()`, parserOptions
|
|
27
|
+
{ code: `cy.get('input').type('somth', {anyoption: true})`, parserOptions },
|
|
28
|
+
{ code: `cy.get('input').trigger('click', {anyoption: true})`, parserOptions },
|
|
29
|
+
{ code: `cy.get('input').rightclick({anyoption: true})`, parserOptions },
|
|
30
|
+
{ code: `cy.get('input').check()`, parserOptions },
|
|
31
|
+
{ code: `cy.get('input').select()`, parserOptions },
|
|
32
|
+
{ code: `cy.get('input').focus()`, parserOptions },
|
|
33
|
+
{ code: `cy.document().trigger("keydown", { ...event })`, parserOptions },
|
|
33
34
|
],
|
|
34
35
|
|
|
35
36
|
invalid: [
|
|
@@ -26,6 +26,29 @@ ruleTester.run('no-unnecessary-waiting', rule, {
|
|
|
26
26
|
{ code: 'const customWait = (alias = "@someRequest") => { cy.wait(alias) }', parserOptions, errors },
|
|
27
27
|
{ code: 'function customWait (ms) { cy.wait(ms) }', parserOptions, errors },
|
|
28
28
|
{ code: 'const customWait = (ms) => { cy.wait(ms) }', parserOptions, errors },
|
|
29
|
+
|
|
30
|
+
// disable the eslint rule
|
|
31
|
+
{
|
|
32
|
+
code: `
|
|
33
|
+
cy.wait(100); // eslint-disable-line no-unnecessary-waiting
|
|
34
|
+
`,
|
|
35
|
+
parserOptions,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
code: `
|
|
39
|
+
/* eslint-disable-next-line no-unnecessary-waiting */
|
|
40
|
+
cy.wait(100)
|
|
41
|
+
`,
|
|
42
|
+
parserOptions,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
code: `
|
|
46
|
+
/* eslint-disable no-unnecessary-waiting */
|
|
47
|
+
cy.wait(100)
|
|
48
|
+
/* eslint-enable no-unnecessary-waiting */
|
|
49
|
+
`,
|
|
50
|
+
parserOptions,
|
|
51
|
+
},
|
|
29
52
|
],
|
|
30
53
|
|
|
31
54
|
invalid: [
|
|
@@ -16,6 +16,8 @@ ruleTester.run('require-data-selectors', rule, {
|
|
|
16
16
|
{ code: 'cy.scrollTo(0, 10)', parserOptions },
|
|
17
17
|
{ code: 'cy.tick(500)', parserOptions },
|
|
18
18
|
{ code: 'cy.get(\`[data-cy=${1}]\`)', parserOptions },
|
|
19
|
+
{ code: 'cy.get("@my-alias")', parserOptions, errors },
|
|
20
|
+
{ code: 'cy.get(`@my-alias`)', parserOptions, errors },
|
|
19
21
|
],
|
|
20
22
|
|
|
21
23
|
invalid: [
|