Skip to content

Commit eefdb0e

Browse files
authored
Add no-networkidle rule (#134)
* Shell * Tests * Finish rule
1 parent d711795 commit eefdb0e

File tree

4 files changed

+139
-0
lines changed

4 files changed

+139
-0
lines changed

docs/rules/no-networkidle.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Disallow usage of the `networkidle` option (`no-networkidle`)
2+
3+
Using `networkidle` is discouraged in favor of using
4+
[web first assertions](https://playwright.dev/docs/best-practices#use-web-first-assertions).
5+
6+
## Rule Details
7+
8+
Examples of **incorrect** code for this rule:
9+
10+
```javascript
11+
await page.waitForLoadState('networkidle');
12+
await page.waitForURL('...', { waitUntil: 'networkidle' });
13+
await page.goto('...', { waitUntil: 'networkidle' });
14+
```

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import noElementHandle from './rules/no-element-handle';
55
import noEval from './rules/no-eval';
66
import noFocusedTest from './rules/no-focused-test';
77
import noForceOption from './rules/no-force-option';
8+
import noNetworkidle from './rules/no-networkidle';
89
import noPagePause from './rules/no-page-pause';
910
import noRestrictedMatchers from './rules/no-restricted-matchers';
1011
import noSkippedTest from './rules/no-skipped-test';
@@ -33,6 +34,7 @@ const recommended = {
3334
'playwright/no-eval': 'warn',
3435
'playwright/no-focused-test': 'error',
3536
'playwright/no-force-option': 'warn',
37+
'playwright/no-networkidle': 'error',
3638
'playwright/no-page-pause': 'warn',
3739
'playwright/no-skipped-test': 'warn',
3840
'playwright/no-useless-not': 'warn',
@@ -87,6 +89,7 @@ export = {
8789
'no-eval': noEval,
8890
'no-focused-test': noFocusedTest,
8991
'no-force-option': noForceOption,
92+
'no-networkidle': noNetworkidle,
9093
'no-page-pause': noPagePause,
9194
'no-restricted-matchers': noRestrictedMatchers,
9295
'no-skipped-test': noSkippedTest,

src/rules/no-networkidle.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { Rule } from 'eslint';
2+
import * as ESTree from 'estree';
3+
import { getStringValue, isStringLiteral } from '../utils/ast';
4+
5+
const messageId = 'noNetworkidle';
6+
const methods = new Set([
7+
'goBack',
8+
'goForward',
9+
'goto',
10+
'reload',
11+
'setContent',
12+
'waitForLoadState',
13+
'waitForURL',
14+
]);
15+
16+
export default {
17+
create(context) {
18+
return {
19+
CallExpression(node) {
20+
if (node.callee.type !== 'MemberExpression') return;
21+
22+
const methodName = getStringValue(node.callee.property);
23+
if (!methods.has(methodName)) return;
24+
25+
// waitForLoadState has a single string argument
26+
if (methodName === 'waitForLoadState') {
27+
const arg = node.arguments[0];
28+
29+
if (isStringLiteral(arg, 'networkidle')) {
30+
context.report({ messageId, node: arg });
31+
}
32+
33+
return;
34+
}
35+
36+
// All other methods have an options object
37+
if (node.arguments.length >= 2) {
38+
const [_, arg] = node.arguments;
39+
if (arg.type !== 'ObjectExpression') return;
40+
41+
const property = arg.properties
42+
.filter((p): p is ESTree.Property => p.type === 'Property')
43+
.find((p) => isStringLiteral(p.value, 'networkidle'));
44+
45+
if (property) {
46+
context.report({ messageId, node: property.value });
47+
}
48+
}
49+
},
50+
};
51+
},
52+
meta: {
53+
docs: {
54+
category: 'Possible Errors',
55+
description: 'Prevent usage of the networkidle option',
56+
recommended: true,
57+
},
58+
messages: {
59+
noNetworkidle: 'Unexpected use of networkidle.',
60+
},
61+
type: 'problem',
62+
},
63+
} as Rule.RuleModule;

test/spec/no-networkidle.spec.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import rule from '../../src/rules/no-networkidle';
2+
import { runRuleTester } from '../utils/rule-tester';
3+
4+
const messageId = 'noNetworkidle';
5+
6+
runRuleTester('no-networkidle', rule, {
7+
invalid: [
8+
{
9+
code: 'page.waitForLoadState("networkidle")',
10+
errors: [{ column: 23, endColumn: 36, line: 1, messageId }],
11+
},
12+
{
13+
code: 'page.waitForURL(url, { waitUntil: "networkidle" })',
14+
errors: [{ column: 35, endColumn: 48, line: 1, messageId }],
15+
},
16+
{
17+
code: 'page["waitForURL"](url, { waitUntil: "networkidle" })',
18+
errors: [{ column: 38, endColumn: 51, line: 1, messageId }],
19+
},
20+
{
21+
code: 'page[`waitForURL`](url, { waitUntil: "networkidle" })',
22+
errors: [{ column: 38, endColumn: 51, line: 1, messageId }],
23+
},
24+
{
25+
code: 'page.goto(url, { waitUntil: "networkidle" })',
26+
errors: [{ column: 29, endColumn: 42, line: 1, messageId }],
27+
},
28+
{
29+
code: 'page.reload(url, { waitUntil: "networkidle" })',
30+
errors: [{ column: 31, endColumn: 44, line: 1, messageId }],
31+
},
32+
{
33+
code: 'page.setContent(url, { waitUntil: "networkidle" })',
34+
errors: [{ column: 35, endColumn: 48, line: 1, messageId }],
35+
},
36+
{
37+
code: 'page.goBack(url, { waitUntil: "networkidle" })',
38+
errors: [{ column: 31, endColumn: 44, line: 1, messageId }],
39+
},
40+
{
41+
code: 'page.goForward(url, { waitUntil: "networkidle" })',
42+
errors: [{ column: 34, endColumn: 47, line: 1, messageId }],
43+
},
44+
],
45+
valid: [
46+
'foo("networkidle")',
47+
'foo(url, { waitUntil: "networkidle" })',
48+
'foo.bar("networkidle")',
49+
'foo.bar(url, { waitUntil: "networkidle" })',
50+
'page.hi("networkidle")',
51+
'page.hi(url, { waitUntil: "networkidle" })',
52+
'frame.hi("networkidle")',
53+
'frame.hi(url, { waitUntil: "networkidle" })',
54+
55+
// Other options are valid
56+
'page.waitForLoadState({ waitUntil: "load" })',
57+
'page.waitForUrl(url, { waitUntil: "load" })',
58+
],
59+
});

0 commit comments

Comments
 (0)