Skip to content

Commit 39610ae

Browse files
authored
Merge pull request #224 from playwright-community/prefer-hooks-on-top
Add `prefer-hooks-on-top` rule
2 parents 94b5d17 + 24a51d2 commit 39610ae

File tree

5 files changed

+404
-0
lines changed

5 files changed

+404
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ command line option.\
142142
|| 🔧 | | [no-useless-not](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-useless-not.md) | Disallow usage of `not` matchers when a specific matcher exists |
143143
|| | 💡 | [no-wait-for-selector](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-selector.md) | Disallow usage of `page.waitForSelector()` |
144144
|| | 💡 | [no-wait-for-timeout](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-timeout.md) | Disallow usage of `page.waitForTimeout()` |
145+
| | | | [prefer-hooks-on-top](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-hooks-on-top.md) | Suggest having hooks before any test cases |
145146
| | | 💡 | [prefer-strict-equal](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-strict-equal.md) | Suggest using `toStrictEqual()` |
146147
| | 🔧 | | [prefer-lowercase-title](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-lowercase-title.md) | Enforce lowercase test names |
147148
| | 🔧 | | [prefer-to-be](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-be.md) | Suggest using `toBe()` |

docs/rules/prefer-hooks-on-top.md

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Suggest having hooks before any test cases (`prefer-hooks-on-top`)
2+
3+
While hooks can be setup anywhere in a test file, they are always called in a
4+
specific order, which means it can be confusing if they're intermixed with test
5+
cases.
6+
7+
This rule helps to ensure that hooks are always defined before test cases.
8+
9+
## Rule details
10+
11+
Examples of **incorrect** code for this rule
12+
13+
```js
14+
/* eslint playwright/prefer-hooks-on-top: "error" */
15+
16+
test.describe('foo', () => {
17+
test.beforeEach(() => {
18+
seedMyDatabase();
19+
});
20+
21+
test('accepts this input', () => {
22+
// ...
23+
});
24+
25+
test.beforeAll(() => {
26+
createMyDatabase();
27+
});
28+
29+
test('returns that value', () => {
30+
// ...
31+
});
32+
33+
test.describe('when the database has specific values', () => {
34+
const specificValue = '...';
35+
36+
test.beforeEach(() => {
37+
seedMyDatabase(specificValue);
38+
});
39+
40+
test('accepts that input', () => {
41+
// ...
42+
});
43+
44+
test('throws an error', () => {
45+
// ...
46+
});
47+
48+
test.afterEach(() => {
49+
clearLogger();
50+
});
51+
52+
test.beforeEach(() => {
53+
mockLogger();
54+
});
55+
56+
test('logs a message', () => {
57+
// ...
58+
});
59+
});
60+
61+
test.afterAll(() => {
62+
removeMyDatabase();
63+
});
64+
});
65+
```
66+
67+
Examples of **correct** code for this rule
68+
69+
```js
70+
/* eslint playwright/prefer-hooks-on-top: "error" */
71+
72+
test.describe('foo', () => {
73+
test.beforeAll(() => {
74+
createMyDatabase();
75+
});
76+
77+
test.beforeEach(() => {
78+
seedMyDatabase();
79+
});
80+
81+
test.afterAll(() => {
82+
clearMyDatabase();
83+
});
84+
85+
test('accepts this input', () => {
86+
// ...
87+
});
88+
89+
test('returns that value', () => {
90+
// ...
91+
});
92+
93+
test.describe('when the database has specific values', () => {
94+
const specificValue = '...';
95+
96+
beforeEach(() => {
97+
seedMyDatabase(specificValue);
98+
});
99+
100+
beforeEach(() => {
101+
mockLogger();
102+
});
103+
104+
afterEach(() => {
105+
clearLogger();
106+
});
107+
108+
test('accepts that input', () => {
109+
// ...
110+
});
111+
112+
test('throws an error', () => {
113+
// ...
114+
});
115+
116+
test('logs a message', () => {
117+
// ...
118+
});
119+
});
120+
});
121+
```

src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import noUselessAwait from './rules/no-useless-await';
2222
import noUselessNot from './rules/no-useless-not';
2323
import noWaitForSelector from './rules/no-wait-for-selector';
2424
import noWaitForTimeout from './rules/no-wait-for-timeout';
25+
import preferHooksOnTop from './rules/prefer-hooks-on-top';
2526
import preferLowercaseTitle from './rules/prefer-lowercase-title';
2627
import preferStrictEqual from './rules/prefer-strict-equal';
2728
import preferToBe from './rules/prefer-to-be';
@@ -60,6 +61,7 @@ const index = {
6061
'no-useless-not': noUselessNot,
6162
'no-wait-for-selector': noWaitForSelector,
6263
'no-wait-for-timeout': noWaitForTimeout,
64+
'prefer-hooks-on-top': preferHooksOnTop,
6365
'prefer-lowercase-title': preferLowercaseTitle,
6466
'prefer-strict-equal': preferStrictEqual,
6567
'prefer-to-be': preferToBe,

src/rules/prefer-hooks-on-top.ts

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Rule } from 'eslint';
2+
import { isTestCall, isTestHook } from '../utils/ast';
3+
4+
export default {
5+
create(context) {
6+
const stack = [false];
7+
8+
return {
9+
CallExpression(node) {
10+
if (isTestCall(context, node)) {
11+
stack[stack.length - 1] = true;
12+
}
13+
14+
if (stack.at(-1) && isTestHook(context, node)) {
15+
context.report({
16+
messageId: 'noHookOnTop',
17+
node,
18+
});
19+
}
20+
21+
stack.push(false);
22+
},
23+
'CallExpression:exit'() {
24+
stack.pop();
25+
},
26+
};
27+
},
28+
meta: {
29+
docs: {
30+
category: 'Best Practices',
31+
description: 'Suggest having hooks before any test cases',
32+
recommended: false,
33+
url: 'https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-hooks-on-top.md',
34+
},
35+
messages: {
36+
noHookOnTop: 'Hooks should come before test cases',
37+
},
38+
type: 'suggestion',
39+
},
40+
} as Rule.RuleModule;

0 commit comments

Comments
 (0)