Skip to content

Commit ad54147

Browse files
authored
Feature add no force option rule (playwright-community#47)
* rule: add new noElementHandle rule * chore: move end range to const * fix: getRange method for non-awaiting statements * test: add additional tests for the noElementHandle rule * chore: improve noElementHandle rule * docs: update the wording of noElementHandle rule * rule: update message to match with other rule naming conventions * rule: change noElementHandle to warn level * rule: noElementHandle change fixable to suggestion * test: add valid tests for noElementHandle rule * rule: change type for noElementHandle to suggestion * rule: update noElementHandle rule's message for suggestions * rule: update noElementHandle with getRange method * rule: add noForceOption * docs: change Example > Examples * test: add wrapInTest function * chore: use helper function
1 parent 30d737c commit ad54147

File tree

4 files changed

+125
-0
lines changed

4 files changed

+125
-0
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,27 @@ test.describe('two tests', () => {
246246
test('two', async ({ page }) => {});
247247
});
248248
```
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+
```

lib/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const noEval = require("./rules/no-eval");
55
const noFocusedTest = require("./rules/no-focused-test");
66
const noSkippedTest = require("./rules/no-skipped-test");
77
const noWaitForTimeout = require("./rules/no-wait-for-timeout");
8+
const noForceOption = require("./rules/no-force-option");
89

910
module.exports = {
1011
configs: {
@@ -22,6 +23,7 @@ module.exports = {
2223
"playwright/no-focused-test": "error",
2324
"playwright/no-skipped-test": "warn",
2425
"playwright/no-wait-for-timeout": "warn",
26+
"playwright/no-force-option": "warn",
2527
},
2628
},
2729
"jest-playwright": {
@@ -65,5 +67,6 @@ module.exports = {
6567
"no-focused-test": noFocusedTest,
6668
"no-skipped-test": noSkippedTest,
6769
"no-wait-for-timeout": noWaitForTimeout,
70+
"no-force-option": noForceOption,
6871
},
6972
};

lib/rules/no-force-option.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
};

test/no-force-option.spec.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const { runRuleTester, wrapInTest } = require('../lib/utils/rule-tester');
2+
const rule = require('../lib/rules/no-force-option');
3+
4+
const invalid = (code) => ({
5+
code: wrapInTest(code),
6+
errors: [{ messageId: 'noForceOption' }],
7+
});
8+
9+
const valid = (code) => ({
10+
code: wrapInTest(code),
11+
});
12+
13+
runRuleTester('no-force-option', rule, {
14+
invalid: [
15+
invalid('await page.locator("check").check({ force: true })'),
16+
invalid('await page.locator("check").uncheck({ force: true })'),
17+
invalid('await page.locator("button").click({ force: true })'),
18+
invalid('const button = page.locator("button"); await button.click({ force: true })'),
19+
invalid('await page.locator("button").locator("btn").click({ force: true })'),
20+
invalid('await page.locator("button").dblclick({ force: true })'),
21+
invalid('await page.locator("input").dragTo({ force: true })'),
22+
invalid('await page.locator("input").fill("test", { force: true })'),
23+
invalid('await page.locator("elm").hover({ force: true })'),
24+
invalid('await page.locator("select").selectOption({ label: "Blue" }, { force: true })'),
25+
invalid('await page.locator("select").selectText({ force: true })'),
26+
invalid('await page.locator("checkbox").setChecked(true, { force: true })'),
27+
invalid('await page.locator("button").tap({ force: true })'),
28+
],
29+
valid: [
30+
valid("await page.locator('check').check()"),
31+
valid("await page.locator('check').uncheck()"),
32+
valid("await page.locator('button').click()"),
33+
valid("await page.locator('button').locator('btn').click()"),
34+
valid("await page.locator('button').click({ delay: 500, noWaitAfter: true })"),
35+
valid("await page.locator('button').dblclick()"),
36+
valid("await page.locator('input').dragTo()"),
37+
valid("await page.locator('input').fill('something', { timeout: 1000 })"),
38+
valid("await page.locator('elm').hover()"),
39+
valid("await page.locator('select').selectOption({ label: 'Blue' })"),
40+
valid("await page.locator('select').selectText()"),
41+
valid("await page.locator('checkbox').setChecked(true)"),
42+
valid("await page.locator('button').tap()"),
43+
valid('doSomething({ force: true })'),
44+
valid('await doSomething({ force: true })'),
45+
],
46+
});

0 commit comments

Comments
 (0)