Skip to content

Commit cbd8cff

Browse files
committed
Feat: add linting rule for before/beforeEach just like the async rule for test
1 parent 0c5f68b commit cbd8cff

File tree

4 files changed

+125
-0
lines changed

4 files changed

+125
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ You can add rules:
3636
"cypress/assertion-before-screenshot": "warn",
3737
"cypress/no-force": "warn",
3838
"cypress/no-async-tests": "error",
39+
"cypress/no-async-before": "error",
3940
"cypress/no-pause": "error"
4041
}
4142
}

docs/rules/no-async-before.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Prevent using async/await in Cypress test cases (no-async-tests)
2+
3+
Cypress before that return a promise will cause tests to prematurely start, can cause unexpected behavior. An `async` function returns a promise under the hood, so a before using an `async` function might also error.
4+
5+
## Rule Details
6+
7+
This rule disallows using `async` before and beforeEach functions.
8+
9+
Examples of **incorrect** code for this rule:
10+
11+
```js
12+
describe('my feature', () => {
13+
before('my test case', async () => {
14+
await cy.get('.myClass')
15+
// other operations
16+
})
17+
})
18+
```
19+
20+
```js
21+
describe('my feature', () => {
22+
before('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+
before('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 before 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)

lib/rules/no-async-before.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict'
2+
3+
module.exports = {
4+
meta: {
5+
docs: {
6+
description: 'Prevent using async/await in Cypress before methods',
7+
category: 'Possible Errors',
8+
recommended: true,
9+
},
10+
messages: {
11+
unexpected: 'Avoid using async functions with Cypress before / beforeEach functions',
12+
},
13+
},
14+
15+
create (context) {
16+
function isBeforeBlock (callExpressionNode) {
17+
const { type, name } = callExpressionNode.callee
18+
19+
return type === 'Identifier'
20+
&& name === 'before' || name === 'beforeEach'
21+
}
22+
23+
function isBeforeAsync (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(isBeforeBlock)
36+
.filter(isBeforeAsync)
37+
38+
if (asyncTestBlocks.length >= 1) {
39+
asyncTestBlocks.forEach((node) => {
40+
context.report({ node, messageId: 'unexpected' })
41+
})
42+
}
43+
}
44+
},
45+
}
46+
},
47+
}

tests/lib/rules/no-async-before.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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: 'before(\'a before case\', () => { cy.get(\'.someClass\'); })', parserOptions },
15+
{ code: 'before(\'a before 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: 'before(\'a test case\', async () => { cy.get(\'.someClass\'); })', parserOptions, errors },
21+
{ code: 'beforeEach(\'a test case\', async () => { cy.get(\'.someClass\'); })', parserOptions, errors },
22+
{ code: 'before(\'a test case\', async function () { cy.get(\'.someClass\'); })', parserOptions, errors },
23+
{ code: 'beforeEach(\'a test case\', async function () { cy.get(\'.someClass\'); })', parserOptions, errors },
24+
],
25+
})

0 commit comments

Comments
 (0)