Skip to content

Commit 13afef3

Browse files
authored
Add new "noFocusedTest" rule (playwright-community#44)
* 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 * feat: add new noFocusedTest rule * chore: remove unneeded function in test. change DS for isTestGroup function * chore: remove valid function
1 parent 6a6c4c0 commit 13afef3

File tree

5 files changed

+165
-0
lines changed

5 files changed

+165
-0
lines changed

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,50 @@ await page.locator('button').evaluate(node => node.innerText);
143143

144144
await page.locator('div').evaluateAll((divs, min) => divs.length >= min, 10);
145145
```
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+
```

lib/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const missingPlaywrightAwait = require("./rules/missing-playwright-await");
22
const noPagePause = require("./rules/no-page-pause");
33
const noElementHandle = require("./rules/no-element-handle");
44
const noEval = require("./rules/no-eval");
5+
const noFocusedTest = require("./rules/no-focused-test");
56

67
module.exports = {
78
configs: {
@@ -16,6 +17,7 @@ module.exports = {
1617
"playwright/no-page-pause": "warn",
1718
"playwright/no-element-handle": "warn",
1819
"playwright/no-eval": "warn",
20+
"playwright/no-focused-test": "error",
1921
},
2022
},
2123
"jest-playwright": {
@@ -56,5 +58,6 @@ module.exports = {
5658
"no-page-pause": noPagePause,
5759
"no-element-handle": noElementHandle,
5860
"no-eval": noEval,
61+
"no-focused-test": noFocusedTest,
5962
},
6063
};

lib/rules/no-focused-test.js

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

lib/utils/ast.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,21 @@ function isCalleeProperty({ callee }, name) {
1616
);
1717
}
1818

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+
1931
module.exports = {
2032
isPageIdentifier,
2133
isCalleeProperty,
34+
isTestIdentifier,
35+
hasAnnotation,
2236
};

test/no-focused-test.spec.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const { RuleTester } = require('eslint');
2+
const rule = require('../lib/rules/no-focused-test');
3+
4+
RuleTester.setDefaultConfig({
5+
parserOptions: {
6+
ecmaVersion: 2018,
7+
},
8+
});
9+
10+
const invalid = (code, output) => ({
11+
code,
12+
errors: [
13+
{
14+
messageId: 'noFocusedTest',
15+
suggestions: [{ messageId: 'removeFocusedTestAnnotation', output }],
16+
},
17+
],
18+
});
19+
20+
new RuleTester().run('no-focused-test', rule, {
21+
invalid: [
22+
invalid(
23+
'test.describe.only("skip this describe", () => {});',
24+
'test.describe("skip this describe", () => {});'
25+
),
26+
27+
invalid(
28+
'test.describe.parallel.only("skip this describe", () => {});',
29+
'test.describe.parallel("skip this describe", () => {});'
30+
),
31+
32+
invalid(
33+
'test.describe.serial.only("skip this describe", () => {});',
34+
'test.describe.serial("skip this describe", () => {});'
35+
),
36+
37+
invalid(
38+
'test.only("skip this test", async ({ page }) => {});',
39+
'test("skip this test", async ({ page }) => {});'
40+
),
41+
],
42+
valid: [
43+
'test.describe("describe tests", () => {});',
44+
'test.describe.skip("describe tests", () => {});',
45+
'test("one", async ({ page }) => {});',
46+
'test.fixme(isMobile, "Settings page does not work in mobile yet");',
47+
'test.slow();',
48+
'const only = true;',
49+
'function only() { return null };',
50+
'this.only();',
51+
],
52+
});

0 commit comments

Comments
 (0)