Skip to content

Commit 5bc943f

Browse files
authored
feat: Add no-wait-for-selector rule (#198)
1 parent f2aa1ec commit 5bc943f

File tree

5 files changed

+311
-0
lines changed

5 files changed

+311
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ command line option.\
162162
| | | | [no-restricted-matchers](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-restricted-matchers.md) | Disallow specific matchers & modifiers |
163163
|| | 💡 | [no-skipped-test](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-skipped-test.md) | Disallow usage of the `.skip` annotation |
164164
|| 🔧 | | [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 |
165+
|| | 💡 | [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` |
165166
|| | 💡 | [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` |
166167
| | | 💡 | [prefer-strict-equal](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-strict-equal.md) | Suggest using `toStrictEqual()` |
167168
| | 🔧 | | [prefer-lowercase-title](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-lowercase-title.md) | Enforce lowercase test names |

docs/rules/no-wait-for-selector.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Disallow usage of `page.waitForSelector` (`no-wait-for-selector`)
2+
3+
## Rule Details
4+
5+
Example of **incorrect** code for this rule:
6+
7+
```javascript
8+
await page.waitForSelector('#foo');
9+
```
10+
11+
Examples of **correct** code for this rule:
12+
13+
```javascript
14+
await page.waitForLoadState();
15+
await page.waitForUrl('/home');
16+
await page.waitForFunction(() => window.innerWidth < 100);
17+
```

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import noRestrictedMatchers from './rules/no-restricted-matchers';
1616
import noSkippedTest from './rules/no-skipped-test';
1717
import noUselessAwait from './rules/no-useless-await';
1818
import noUselessNot from './rules/no-useless-not';
19+
import noWaitForSelector from './rules/no-wait-for-selector';
1920
import noWaitForTimeout from './rules/no-wait-for-timeout';
2021
import preferLowercaseTitle from './rules/prefer-lowercase-title';
2122
import preferStrictEqual from './rules/prefer-strict-equal';
@@ -49,6 +50,7 @@ const index = {
4950
'no-skipped-test': noSkippedTest,
5051
'no-useless-await': noUselessAwait,
5152
'no-useless-not': noUselessNot,
53+
'no-wait-for-selector': noWaitForSelector,
5254
'no-wait-for-timeout': noWaitForTimeout,
5355
'prefer-lowercase-title': preferLowercaseTitle,
5456
'prefer-strict-equal': preferStrictEqual,
@@ -81,6 +83,7 @@ const sharedConfig = {
8183
'playwright/no-skipped-test': 'warn',
8284
'playwright/no-useless-await': 'warn',
8385
'playwright/no-useless-not': 'warn',
86+
'playwright/no-wait-for-selector': 'warn',
8487
'playwright/no-wait-for-timeout': 'warn',
8588
'playwright/prefer-web-first-assertions': 'error',
8689
'playwright/valid-expect': 'error',

src/rules/no-wait-for-selector.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Rule } from 'eslint';
2+
import { isPageMethod } from '../utils/ast';
3+
4+
export default {
5+
create(context) {
6+
return {
7+
CallExpression(node) {
8+
if (isPageMethod(node, 'waitForSelector')) {
9+
context.report({
10+
messageId: 'noWaitForSelector',
11+
node,
12+
suggest: [
13+
{
14+
fix: (fixer) =>
15+
fixer.remove(
16+
node.parent && node.parent.type !== 'AwaitExpression'
17+
? node.parent
18+
: node.parent.parent,
19+
),
20+
messageId: 'removeWaitForSelector',
21+
},
22+
],
23+
});
24+
}
25+
},
26+
};
27+
},
28+
meta: {
29+
docs: {
30+
category: 'Best Practices',
31+
description: 'Prevent usage of page.waitForSelector()',
32+
recommended: true,
33+
url: 'https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-selector.md',
34+
},
35+
hasSuggestions: true,
36+
messages: {
37+
noWaitForSelector: 'Unexpected use of page.waitForSelector().',
38+
removeWaitForSelector: 'Remove the page.waitForSelector() method.',
39+
},
40+
type: 'suggestion',
41+
},
42+
} as Rule.RuleModule;
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
import rule from '../../src/rules/no-wait-for-selector';
2+
import { runRuleTester } from '../utils/rule-tester';
3+
4+
const messageId = 'noWaitForSelector';
5+
6+
runRuleTester('no-wait-for-selector', rule, {
7+
invalid: [
8+
{
9+
code: 'async function fn() { await page.waitForSelector(".bar") }',
10+
errors: [
11+
{
12+
column: 29,
13+
endColumn: 57,
14+
endLine: 1,
15+
line: 1,
16+
messageId,
17+
suggestions: [
18+
{
19+
messageId: 'removeWaitForSelector',
20+
output: 'async function fn() { }',
21+
},
22+
],
23+
},
24+
],
25+
},
26+
{
27+
code: 'async function fn() { await this.page.waitForSelector(".bar") }',
28+
errors: [
29+
{
30+
column: 29,
31+
endColumn: 62,
32+
endLine: 1,
33+
line: 1,
34+
messageId,
35+
suggestions: [
36+
{
37+
messageId: 'removeWaitForSelector',
38+
output: 'async function fn() { }',
39+
},
40+
],
41+
},
42+
],
43+
},
44+
{
45+
code: 'async function fn() { await page["waitForSelector"](".bar") }',
46+
errors: [
47+
{
48+
column: 29,
49+
endColumn: 60,
50+
line: 1,
51+
messageId,
52+
suggestions: [
53+
{
54+
messageId: 'removeWaitForSelector',
55+
output: 'async function fn() { }',
56+
},
57+
],
58+
},
59+
],
60+
},
61+
{
62+
code: 'async function fn() { await page[`waitForSelector`](".bar") }',
63+
errors: [
64+
{
65+
column: 29,
66+
endColumn: 60,
67+
line: 1,
68+
messageId,
69+
suggestions: [
70+
{
71+
messageId: 'removeWaitForSelector',
72+
output: 'async function fn() { }',
73+
},
74+
],
75+
},
76+
],
77+
},
78+
{
79+
code: 'async function fn() { return page.waitForSelector(".bar"); }',
80+
errors: [
81+
{
82+
column: 30,
83+
endColumn: 58,
84+
line: 1,
85+
messageId,
86+
suggestions: [
87+
{
88+
messageId: 'removeWaitForSelector',
89+
output: 'async function fn() { }',
90+
},
91+
],
92+
},
93+
],
94+
},
95+
{
96+
code: 'async function fn() { page.waitForSelector(".bar"); }',
97+
errors: [
98+
{
99+
column: 23,
100+
endColumn: 51,
101+
line: 1,
102+
messageId,
103+
suggestions: [
104+
{
105+
messageId: 'removeWaitForSelector',
106+
output: 'async function fn() { }',
107+
},
108+
],
109+
},
110+
],
111+
},
112+
{
113+
code: '(async function() { await page.waitForSelector("#foo"); })();',
114+
errors: [
115+
{
116+
column: 27,
117+
endColumn: 55,
118+
line: 1,
119+
messageId,
120+
suggestions: [
121+
{
122+
messageId: 'removeWaitForSelector',
123+
output: '(async function() { })();',
124+
},
125+
],
126+
},
127+
],
128+
},
129+
{
130+
code: 'page.waitForSelector("#foo")',
131+
errors: [
132+
{
133+
column: 1,
134+
endColumn: 29,
135+
line: 1,
136+
messageId,
137+
suggestions: [{ messageId: 'removeWaitForSelector', output: '' }],
138+
},
139+
],
140+
},
141+
{
142+
code: 'page["waitForSelector"]("#foo")',
143+
errors: [
144+
{
145+
column: 1,
146+
endColumn: 32,
147+
line: 1,
148+
messageId,
149+
suggestions: [{ messageId: 'removeWaitForSelector', output: '' }],
150+
},
151+
],
152+
},
153+
{
154+
code: 'page[`waitForSelector`]("#foo")',
155+
errors: [
156+
{
157+
column: 1,
158+
endColumn: 32,
159+
line: 1,
160+
messageId,
161+
suggestions: [{ messageId: 'removeWaitForSelector', output: '' }],
162+
},
163+
],
164+
},
165+
{
166+
code: 'foo.page().waitForSelector("#foo")',
167+
errors: [
168+
{
169+
column: 1,
170+
endColumn: 35,
171+
line: 1,
172+
messageId,
173+
suggestions: [{ messageId: 'removeWaitForSelector', output: '' }],
174+
},
175+
],
176+
},
177+
{
178+
code: 'this.foo().page().waitForSelector("#foo")',
179+
errors: [
180+
{
181+
column: 1,
182+
endColumn: 42,
183+
line: 1,
184+
messageId,
185+
suggestions: [{ messageId: 'removeWaitForSelector', output: '' }],
186+
},
187+
],
188+
},
189+
{
190+
code: 'page2.waitForSelector("#foo")',
191+
errors: [
192+
{
193+
column: 1,
194+
endColumn: 30,
195+
line: 1,
196+
messageId,
197+
suggestions: [{ messageId: 'removeWaitForSelector', output: '' }],
198+
},
199+
],
200+
},
201+
{
202+
code: 'this.page2.waitForSelector("#foo")',
203+
errors: [
204+
{
205+
column: 1,
206+
endColumn: 35,
207+
line: 1,
208+
messageId,
209+
suggestions: [{ messageId: 'removeWaitForSelector', output: '' }],
210+
},
211+
],
212+
},
213+
{
214+
code: 'myPage.waitForSelector("#foo")',
215+
errors: [
216+
{
217+
column: 1,
218+
endColumn: 31,
219+
line: 1,
220+
messageId,
221+
suggestions: [{ messageId: 'removeWaitForSelector', output: '' }],
222+
},
223+
],
224+
},
225+
{
226+
code: 'this.myPage.waitForSelector("#foo")',
227+
errors: [
228+
{
229+
column: 1,
230+
endColumn: 36,
231+
line: 1,
232+
messageId,
233+
suggestions: [{ messageId: 'removeWaitForSelector', output: '' }],
234+
},
235+
],
236+
},
237+
],
238+
valid: [
239+
'function waitForSelector() {}',
240+
'async function fn() { await waitForSelector("#foo"); }',
241+
'async function fn() { await this.foo.waitForSelector("#foo"); }',
242+
'(async function() { await page.waitForTimeout(2000); })();',
243+
'page.waitForTimeout(2000);',
244+
'page["waitForTimeout"](2000);',
245+
'rampage.waitForSelector("#foo");',
246+
'myPage2.waitForSelector("#foo");',
247+
],
248+
});

0 commit comments

Comments
 (0)