eslint-plugin-playwright 0.7.0 → 0.9.0
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 +172 -0
- package/lib/index.js +19 -0
- package/lib/rules/missing-playwright-await.js +1 -0
- package/lib/rules/no-element-handle.js +47 -0
- package/lib/rules/no-eval.js +27 -0
- package/lib/rules/no-focused-test.js +49 -0
- package/lib/rules/no-force-option.js +52 -0
- package/lib/rules/no-page-pause.js +4 -2
- package/lib/rules/no-skipped-test.js +66 -0
- package/lib/rules/no-wait-for-timeout.js +40 -0
- package/lib/utils/ast.js +61 -0
- package/lib/utils/rule-tester.js +34 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,3 +98,175 @@ Example of **correct** code for this rule:
|
|
|
98
98
|
await page.click('button');
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
### `no-element-handle`
|
|
102
|
+
|
|
103
|
+
Disallow the creation of element handles with `page.$` or `page.$$`.
|
|
104
|
+
|
|
105
|
+
Examples of **incorrect** code for this rule:
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
// Element Handle
|
|
109
|
+
const buttonHandle = await page.$('button');
|
|
110
|
+
await buttonHandle.click();
|
|
111
|
+
|
|
112
|
+
// Element Handles
|
|
113
|
+
const linkHandles = await page.$$('a');
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Example of **correct** code for this rule:
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
const buttonLocator = page.locator('button');
|
|
120
|
+
await buttonLocator.click();
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### `no-eval`
|
|
124
|
+
|
|
125
|
+
Disallow usage of `page.$eval` and `page.$$eval`.
|
|
126
|
+
|
|
127
|
+
Examples of **incorrect** code for this rule:
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
const searchValue = await page.$eval('#search', el => el.value);
|
|
131
|
+
|
|
132
|
+
const divCounts = await page.$$eval('div', (divs, min) => divs.length >= min, 10);
|
|
133
|
+
|
|
134
|
+
await page.$eval('#search', el => el.value);
|
|
135
|
+
|
|
136
|
+
await page.$$eval('#search', el => el.value);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Example of **correct** code for this rule:
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
await page.locator('button').evaluate(node => node.innerText);
|
|
143
|
+
|
|
144
|
+
await page.locator('div').evaluateAll((divs, min) => divs.length >= min, 10);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### `no-focused-test`
|
|
148
|
+
|
|
149
|
+
Disallow usage of `.only()` annotation
|
|
150
|
+
|
|
151
|
+
Examples of **incorrect** code for this rule:
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
test.only('focus this test', async ({ page }) => {});
|
|
155
|
+
|
|
156
|
+
test.describe.only('focus two tests', () => {
|
|
157
|
+
test('one', async ({ page }) => {});
|
|
158
|
+
test('two', async ({ page }) => {});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test.describe.parallel.only('focus two tests in parallel mode', () => {
|
|
162
|
+
test('one', async ({ page }) => {});
|
|
163
|
+
test('two', async ({ page }) => {});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test.describe.serial.only('focus two tests in serial mode', () => {
|
|
167
|
+
test('one', async ({ page }) => {});
|
|
168
|
+
test('two', async ({ page }) => {});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Examples of **correct** code for this rule:
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
test('this test', async ({ page }) => {});
|
|
177
|
+
|
|
178
|
+
test.describe('two tests', () => {
|
|
179
|
+
test('one', async ({ page }) => {});
|
|
180
|
+
test('two', async ({ page }) => {});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test.describe.parallel('two tests in parallel mode', () => {
|
|
184
|
+
test('one', async ({ page }) => {});
|
|
185
|
+
test('two', async ({ page }) => {});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test.describe.serial('two tests in serial mode', () => {
|
|
189
|
+
test('one', async ({ page }) => {});
|
|
190
|
+
test('two', async ({ page }) => {});
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### `no-wait-for-timeout`
|
|
195
|
+
|
|
196
|
+
Disallow usage of `page.waitForTimeout()`.
|
|
197
|
+
|
|
198
|
+
Example of **incorrect** code for this rule:
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
await page.waitForTimeout(5000);
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Examples of **correct** code for this rule:
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
// Use signals such as network events, selectors becoming visible and others instead.
|
|
208
|
+
await page.waitForLoadState();
|
|
209
|
+
|
|
210
|
+
await page.waitForUrl('/home');
|
|
211
|
+
|
|
212
|
+
await page.waitForFunction(() => window.innerWidth < 100);
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### `no-skipped-test`
|
|
216
|
+
|
|
217
|
+
Disallow usage of the `.skip()` annotation.
|
|
218
|
+
|
|
219
|
+
Examples of **incorrect** code for this rule:
|
|
220
|
+
|
|
221
|
+
```js
|
|
222
|
+
test.skip('skip this test', async ({ page }) => {});
|
|
223
|
+
|
|
224
|
+
test.describe.skip('skip two tests', () => {
|
|
225
|
+
test('one', async ({ page }) => {});
|
|
226
|
+
test('two', async ({ page }) => {});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test.describe('skip test inside describe', () => {
|
|
230
|
+
test.skip();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test.describe('skip test conditionally', async ({ browserName }) => {
|
|
234
|
+
test.skip(browserName === 'firefox', 'Working on it');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Examples of **correct** code for this rule:
|
|
240
|
+
|
|
241
|
+
```js
|
|
242
|
+
test('this test', async ({ page }) => {});
|
|
243
|
+
|
|
244
|
+
test.describe('two tests', () => {
|
|
245
|
+
test('one', async ({ page }) => {});
|
|
246
|
+
test('two', async ({ page }) => {});
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### `no-force-option`
|
|
251
|
+
|
|
252
|
+
Disallow usage of the `{ force: true }` option.
|
|
253
|
+
|
|
254
|
+
Examples of **incorrect** code for this rule:
|
|
255
|
+
|
|
256
|
+
```js
|
|
257
|
+
await page.locator('button').click({ force: true });
|
|
258
|
+
|
|
259
|
+
await page.locator('check').check({ force: true });
|
|
260
|
+
|
|
261
|
+
await page.locator('input').fill('something', { force: true });
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Examples of **correct** code for this rule:
|
|
265
|
+
|
|
266
|
+
```js
|
|
267
|
+
await page.locator('button').click();
|
|
268
|
+
|
|
269
|
+
await page.locator('check').check();
|
|
270
|
+
|
|
271
|
+
await page.locator('input').fill('something');
|
|
272
|
+
```
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
const missingPlaywrightAwait = require("./rules/missing-playwright-await");
|
|
2
|
+
const noPagePause = require("./rules/no-page-pause");
|
|
3
|
+
const noElementHandle = require("./rules/no-element-handle");
|
|
4
|
+
const noEval = require("./rules/no-eval");
|
|
5
|
+
const noFocusedTest = require("./rules/no-focused-test");
|
|
6
|
+
const noSkippedTest = require("./rules/no-skipped-test");
|
|
7
|
+
const noWaitForTimeout = require("./rules/no-wait-for-timeout");
|
|
8
|
+
const noForceOption = require("./rules/no-force-option");
|
|
2
9
|
|
|
3
10
|
module.exports = {
|
|
4
11
|
configs: {
|
|
@@ -11,6 +18,12 @@ module.exports = {
|
|
|
11
18
|
"no-empty-pattern": "off",
|
|
12
19
|
"playwright/missing-playwright-await": "error",
|
|
13
20
|
"playwright/no-page-pause": "warn",
|
|
21
|
+
"playwright/no-element-handle": "warn",
|
|
22
|
+
"playwright/no-eval": "warn",
|
|
23
|
+
"playwright/no-focused-test": "error",
|
|
24
|
+
"playwright/no-skipped-test": "warn",
|
|
25
|
+
"playwright/no-wait-for-timeout": "warn",
|
|
26
|
+
"playwright/no-force-option": "warn",
|
|
14
27
|
},
|
|
15
28
|
},
|
|
16
29
|
"jest-playwright": {
|
|
@@ -49,5 +62,11 @@ module.exports = {
|
|
|
49
62
|
rules: {
|
|
50
63
|
"missing-playwright-await": missingPlaywrightAwait,
|
|
51
64
|
"no-page-pause": noPagePause,
|
|
65
|
+
"no-element-handle": noElementHandle,
|
|
66
|
+
"no-eval": noEval,
|
|
67
|
+
"no-focused-test": noFocusedTest,
|
|
68
|
+
"no-skipped-test": noSkippedTest,
|
|
69
|
+
"no-wait-for-timeout": noWaitForTimeout,
|
|
70
|
+
"no-force-option": noForceOption,
|
|
52
71
|
},
|
|
53
72
|
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const { isObject, isCalleeProperty } = require('../utils/ast');
|
|
2
|
+
|
|
3
|
+
function getRange(node) {
|
|
4
|
+
const start = node.parent && node.parent.type === 'AwaitExpression'
|
|
5
|
+
? node.parent.range[0]
|
|
6
|
+
: node.callee.object.range[0];
|
|
7
|
+
|
|
8
|
+
return [start, node.callee.property.range[1]];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
create(context) {
|
|
13
|
+
return {
|
|
14
|
+
CallExpression(node) {
|
|
15
|
+
if (isObject(node, 'page') && (isCalleeProperty(node, '$') || isCalleeProperty(node, '$$'))) {
|
|
16
|
+
context.report({
|
|
17
|
+
messageId: 'noElementHandle',
|
|
18
|
+
suggest: [
|
|
19
|
+
{
|
|
20
|
+
messageId: isCalleeProperty(node, '$')
|
|
21
|
+
? 'replaceElementHandleWithLocator'
|
|
22
|
+
: 'replaceElementHandlesWithLocator',
|
|
23
|
+
fix: (fixer) => fixer.replaceTextRange(getRange(node), 'page.locator'),
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
node,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
meta: {
|
|
33
|
+
docs: {
|
|
34
|
+
category: 'Possible Errors',
|
|
35
|
+
description: 'The use of ElementHandle is discouraged, use Locator instead',
|
|
36
|
+
recommended: true,
|
|
37
|
+
url: 'https://github.com/playwright-community/eslint-plugin-playwright#no-element-handle',
|
|
38
|
+
},
|
|
39
|
+
hasSuggestions: true,
|
|
40
|
+
messages: {
|
|
41
|
+
noElementHandle: 'Unexpected use of element handles.',
|
|
42
|
+
replaceElementHandleWithLocator: 'Replace `page.$` with `page.locator`',
|
|
43
|
+
replaceElementHandlesWithLocator: 'Replace `page.$$` with `page.locator`',
|
|
44
|
+
},
|
|
45
|
+
type: 'suggestion',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const { isObject, isCalleeProperty } = require('../utils/ast');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
create(context) {
|
|
5
|
+
return {
|
|
6
|
+
CallExpression(node) {
|
|
7
|
+
if (isObject(node, 'page') && (isCalleeProperty(node, '$eval') || isCalleeProperty(node, '$$eval'))) {
|
|
8
|
+
context.report({ messageId: isCalleeProperty(node, '$eval') ? 'noEval' : 'noEvalAll', node });
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
},
|
|
13
|
+
meta: {
|
|
14
|
+
docs: {
|
|
15
|
+
category: 'Possible Errors',
|
|
16
|
+
description:
|
|
17
|
+
'The use of `page.$eval` and `page.$$eval` are discouraged, use `locator.evaluate` or `locator.evaluateAll` instead',
|
|
18
|
+
recommended: true,
|
|
19
|
+
url: 'https://github.com/playwright-community/eslint-plugin-playwright#no-eval',
|
|
20
|
+
},
|
|
21
|
+
messages: {
|
|
22
|
+
noEval: 'Unexpected use of page.$eval().',
|
|
23
|
+
noEvalAll: 'Unexpected use of page.$$eval().',
|
|
24
|
+
},
|
|
25
|
+
type: 'problem',
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const { isTestIdentifier, hasAnnotation } = require('../utils/ast');
|
|
2
|
+
|
|
3
|
+
function isTestGroup(node) {
|
|
4
|
+
const testGroups = new Set(['describe', 'parallel', 'serial']);
|
|
5
|
+
|
|
6
|
+
return (
|
|
7
|
+
node.object &&
|
|
8
|
+
node.object.type === 'MemberExpression' &&
|
|
9
|
+
node.object.property.type === 'Identifier' &&
|
|
10
|
+
testGroups.has(node.object.property.name)
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
15
|
+
module.exports = {
|
|
16
|
+
create(context) {
|
|
17
|
+
return {
|
|
18
|
+
MemberExpression(node) {
|
|
19
|
+
if ((isTestIdentifier(node) || isTestGroup(node)) && hasAnnotation(node, 'only')) {
|
|
20
|
+
context.report({
|
|
21
|
+
messageId: 'noFocusedTest',
|
|
22
|
+
suggest: [
|
|
23
|
+
{
|
|
24
|
+
messageId: 'removeFocusedTestAnnotation',
|
|
25
|
+
// - 1 to remove the `.only` annotation with dot notation
|
|
26
|
+
fix: (fixer) => fixer.removeRange([node.property.range[0] - 1, node.property.range[1]]),
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
node,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
meta: {
|
|
36
|
+
docs: {
|
|
37
|
+
category: 'Possible Errors',
|
|
38
|
+
description: 'Prevent usage of `.only()` focus test annotation',
|
|
39
|
+
recommended: true,
|
|
40
|
+
url: 'https://github.com/playwright-community/eslint-plugin-playwright#no-focused-test',
|
|
41
|
+
},
|
|
42
|
+
hasSuggestions: true,
|
|
43
|
+
messages: {
|
|
44
|
+
noFocusedTest: 'Unexpected use of .only() annotation.',
|
|
45
|
+
removeFocusedTestAnnotation: 'Remove .only() annotation',
|
|
46
|
+
},
|
|
47
|
+
type: 'problem',
|
|
48
|
+
},
|
|
49
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
function isForceOptionEnabled({ parent }) {
|
|
2
|
+
return (
|
|
3
|
+
parent &&
|
|
4
|
+
parent.arguments &&
|
|
5
|
+
parent.arguments.length &&
|
|
6
|
+
parent.arguments.some(
|
|
7
|
+
(argument) =>
|
|
8
|
+
argument.type === 'ObjectExpression' &&
|
|
9
|
+
argument.properties.some(({ key, value }) => key && key.name === 'force' && value && value.value === true)
|
|
10
|
+
)
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// https://playwright.dev/docs/api/class-locator
|
|
15
|
+
const methodsWithForceOption = new Set([
|
|
16
|
+
'check',
|
|
17
|
+
'uncheck',
|
|
18
|
+
'click',
|
|
19
|
+
'dblclick',
|
|
20
|
+
'dragTo',
|
|
21
|
+
'fill',
|
|
22
|
+
'hover',
|
|
23
|
+
'selectOption',
|
|
24
|
+
'selectText',
|
|
25
|
+
'setChecked',
|
|
26
|
+
'tap',
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
30
|
+
module.exports = {
|
|
31
|
+
create(context) {
|
|
32
|
+
return {
|
|
33
|
+
MemberExpression(node) {
|
|
34
|
+
if (node.property && methodsWithForceOption.has(node.property.name) && isForceOptionEnabled(node)) {
|
|
35
|
+
context.report({ messageId: 'noForceOption', node });
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
meta: {
|
|
41
|
+
docs: {
|
|
42
|
+
category: 'Best Practices',
|
|
43
|
+
description: 'Prevent usage of `{ force: true }` option.',
|
|
44
|
+
recommended: true,
|
|
45
|
+
url: 'https://github.com/playwright-community/eslint-plugin-playwright#no-force-option',
|
|
46
|
+
},
|
|
47
|
+
messages: {
|
|
48
|
+
noForceOption: 'Unexpected use of { force: true } option.',
|
|
49
|
+
},
|
|
50
|
+
type: 'suggestion',
|
|
51
|
+
},
|
|
52
|
+
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
const { isObject, isCalleeProperty } = require('../utils/ast');
|
|
2
|
+
|
|
1
3
|
module.exports = {
|
|
2
4
|
create(context) {
|
|
3
5
|
return {
|
|
4
|
-
|
|
5
|
-
if (node
|
|
6
|
+
CallExpression(node) {
|
|
7
|
+
if (isObject(node, 'page') && isCalleeProperty(node, 'pause')) {
|
|
6
8
|
context.report({ messageId: "noPagePause", node });
|
|
7
9
|
}
|
|
8
10
|
},
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const {
|
|
2
|
+
isTestIdentifier,
|
|
3
|
+
isObjectProperty,
|
|
4
|
+
hasAnnotation,
|
|
5
|
+
isStringLiteral,
|
|
6
|
+
isBooleanLiteral,
|
|
7
|
+
isBinaryExpression,
|
|
8
|
+
} = require('../utils/ast');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* This function returns needed range to remove skip annotation.
|
|
12
|
+
*
|
|
13
|
+
* To cover the standalone cases:
|
|
14
|
+
* 1. test.skip() => when there's no arguments
|
|
15
|
+
* 2. test.skip(browserName === 'firefox', 'Working on it') => when there's first argument is a binary expression and the second argument is a string literal
|
|
16
|
+
* 3. test.skip(true, 'Working on it') => when there's first argument is a boolean literal and the second argument is a string literal
|
|
17
|
+
*
|
|
18
|
+
* So, if it's standalone skip then we need to remove the whole line
|
|
19
|
+
*
|
|
20
|
+
* Otherwise we need to remove the range of `.skip` annotation - 1 (dot notation).
|
|
21
|
+
*/
|
|
22
|
+
function getSkipRange(node) {
|
|
23
|
+
const [first, second] = node.parent.arguments;
|
|
24
|
+
|
|
25
|
+
const isStandaloneSkip =
|
|
26
|
+
!node.parent.arguments.length ||
|
|
27
|
+
((isBinaryExpression(first) || isBooleanLiteral(first)) && isStringLiteral(second));
|
|
28
|
+
|
|
29
|
+
return isStandaloneSkip ? node.parent.parent.range : [node.property.range[0] - 1, node.property.range[1]];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
33
|
+
module.exports = {
|
|
34
|
+
create(context) {
|
|
35
|
+
return {
|
|
36
|
+
MemberExpression(node) {
|
|
37
|
+
if ((isTestIdentifier(node) || isObjectProperty(node, 'describe')) && hasAnnotation(node, 'skip')) {
|
|
38
|
+
context.report({
|
|
39
|
+
messageId: 'noSkippedTest',
|
|
40
|
+
suggest: [
|
|
41
|
+
{
|
|
42
|
+
messageId: 'removeSkippedTestAnnotation',
|
|
43
|
+
fix: (fixer) => fixer.removeRange(getSkipRange(node)),
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
node,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
meta: {
|
|
53
|
+
docs: {
|
|
54
|
+
category: 'Best Practices',
|
|
55
|
+
description: 'Prevent usage of the `.skip()` skip test annotation.',
|
|
56
|
+
recommended: true,
|
|
57
|
+
url: 'https://github.com/playwright-community/eslint-plugin-playwright#no-skipped-test',
|
|
58
|
+
},
|
|
59
|
+
hasSuggestions: true,
|
|
60
|
+
messages: {
|
|
61
|
+
noSkippedTest: 'Unexpected use of the `.skip()` annotation.',
|
|
62
|
+
removeSkippedTestAnnotation: 'Remove the `.skip()` annotation.',
|
|
63
|
+
},
|
|
64
|
+
type: 'suggestion',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const { isObject, isCalleeProperty } = require('../utils/ast');
|
|
2
|
+
|
|
3
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
4
|
+
module.exports = {
|
|
5
|
+
create(context) {
|
|
6
|
+
return {
|
|
7
|
+
CallExpression(node) {
|
|
8
|
+
if (isObject(node, 'page') && isCalleeProperty(node, 'waitForTimeout')) {
|
|
9
|
+
context.report({
|
|
10
|
+
messageId: 'noWaitForTimeout',
|
|
11
|
+
suggest: [
|
|
12
|
+
{
|
|
13
|
+
messageId: 'removeWaitForTimeout',
|
|
14
|
+
fix: (fixer) =>
|
|
15
|
+
fixer.remove(
|
|
16
|
+
node.parent && node.parent.type !== 'AwaitExpression' ? node.parent : node.parent.parent
|
|
17
|
+
),
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
node,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
meta: {
|
|
27
|
+
docs: {
|
|
28
|
+
category: 'Best Practices',
|
|
29
|
+
description: 'Prevent usage of page.waitForTimeout()',
|
|
30
|
+
recommended: true,
|
|
31
|
+
url: 'https://github.com/playwright-community/eslint-plugin-playwright#no-wait-for-timeout',
|
|
32
|
+
},
|
|
33
|
+
hasSuggestions: true,
|
|
34
|
+
messages: {
|
|
35
|
+
noWaitForTimeout: 'Unexpected use of page.waitForTimeout().',
|
|
36
|
+
removeWaitForTimeout: 'Remove the page.waitForTimeout() method.',
|
|
37
|
+
},
|
|
38
|
+
type: 'suggestion',
|
|
39
|
+
},
|
|
40
|
+
};
|
package/lib/utils/ast.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
function isObject({ callee }, name) {
|
|
2
|
+
return (
|
|
3
|
+
callee &&
|
|
4
|
+
callee.type === 'MemberExpression' &&
|
|
5
|
+
callee.object.type === 'Identifier' &&
|
|
6
|
+
callee.object.name === name
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isCalleeProperty({ callee }, name) {
|
|
11
|
+
return (
|
|
12
|
+
callee &&
|
|
13
|
+
callee.property &&
|
|
14
|
+
callee.property.type === 'Identifier' &&
|
|
15
|
+
callee.property.name === name
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isTestIdentifier(node) {
|
|
20
|
+
return node.object && node.object.type === 'Identifier' && node.object.name === 'test';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function hasAnnotation(node, annotation) {
|
|
24
|
+
return (
|
|
25
|
+
node.property &&
|
|
26
|
+
node.property.type === 'Identifier' &&
|
|
27
|
+
node.property.name === annotation
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isObjectProperty({ object }, name) {
|
|
32
|
+
return (
|
|
33
|
+
object &&
|
|
34
|
+
object.type === 'MemberExpression' &&
|
|
35
|
+
object.property.type === 'Identifier' &&
|
|
36
|
+
object.property.name === name
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isStringLiteral(node) {
|
|
41
|
+
return node && node.type === 'Literal' && typeof node.value === 'string';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isBooleanLiteral(node) {
|
|
45
|
+
return node && node.type === 'Literal' && typeof node.value === 'boolean';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isBinaryExpression(node) {
|
|
49
|
+
return node && node.type === 'BinaryExpression';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
isObject,
|
|
54
|
+
isCalleeProperty,
|
|
55
|
+
isTestIdentifier,
|
|
56
|
+
hasAnnotation,
|
|
57
|
+
isObjectProperty,
|
|
58
|
+
isStringLiteral,
|
|
59
|
+
isBooleanLiteral,
|
|
60
|
+
isBinaryExpression,
|
|
61
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const { RuleTester } = require('eslint');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {string} name - The name of the rule
|
|
6
|
+
* @param {string} rule - Path to the rule to test
|
|
7
|
+
* @param {Object} tests - The tests to run
|
|
8
|
+
* @param {string[]} tests.valid - Valid tests
|
|
9
|
+
* @param {string[]} tests.invalid - Invalid tests
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const rule = require('../lib/rules/missing-playwright-await');
|
|
13
|
+
*
|
|
14
|
+
* runRuleTester('missing-playwright-await', rule, {
|
|
15
|
+
* valid: ['await expect(page.locator('checkbox')).toBeChecked()'],
|
|
16
|
+
* invalid: ['expect(page.locator('checkbox')).toBeChecked()'],
|
|
17
|
+
* });
|
|
18
|
+
*/
|
|
19
|
+
function runRuleTester(name, rule, tests) {
|
|
20
|
+
const config = {
|
|
21
|
+
parserOptions: {
|
|
22
|
+
ecmaVersion: 2018,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return new RuleTester(config).run(name, rule, tests);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const wrapInTest = (input) => `test('test', async () => { ${input} })`;
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
runRuleTester,
|
|
33
|
+
wrapInTest,
|
|
34
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-playwright",
|
|
3
3
|
"description": "ESLint plugin for Playwright testing.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"repository": "https://github.com/playwright-community/eslint-plugin-playwright",
|
|
7
7
|
"author": "Max Schmitt <max@schmitt.mx>",
|