Skip to content

Commit 53df693

Browse files
authored
feat: Add no-slow-tests rule (#302)
Fixes #296
1 parent 35e37a1 commit 53df693

File tree

5 files changed

+324
-0
lines changed

5 files changed

+324
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ CLI option\
139139
| [no-raw-locators](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-raw-locators.md) | Disallow using raw locators | | | |
140140
| [no-restricted-matchers](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-restricted-matchers.md) | Disallow specific matchers & modifiers | | | |
141141
| [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 || | 💡 |
142+
| [no-slowed-test](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-slowed-test.md) | Disallow usage of the `.slow` annotation || | 💡 |
142143
| [no-standalone-expect](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-standalone-expect.md) | Disallow using expect outside of `test` blocks || | |
143144
| [no-unsafe-references](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-unsafe-references.md) | Prevent unsafe variable references in `page.evaluate()` || 🔧 | |
144145
| [no-useless-await](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-useless-await.md) | Disallow unnecessary `await`s for Playwright methods || 🔧 | |

docs/rules/no-slowed-test.md

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Disallow usage of the `.slow` annotation (`no-slowed-test`)
2+
3+
## Rule Details
4+
5+
Examples of **incorrect** code for this rule:
6+
7+
```javascript
8+
test.slow('slow this test', async ({ page }) => {})
9+
10+
test.describe('slow test inside describe', () => {
11+
test.slow()
12+
})
13+
14+
test.describe('slow test conditionally', async ({ browserName }) => {
15+
test.slow(browserName === 'firefox', 'Working on it')
16+
})
17+
```
18+
19+
Examples of **correct** code for this rule:
20+
21+
```javascript
22+
test('this test', async ({ page }) => {})
23+
24+
test.describe('two tests', () => {
25+
test('one', async ({ page }) => {})
26+
test('two', async ({ page }) => {})
27+
})
28+
```
29+
30+
## Options
31+
32+
```json
33+
{
34+
"playwright/no-slowed-test": [
35+
"error",
36+
{
37+
"allowConditional": false
38+
}
39+
]
40+
}
41+
```
42+
43+
### `allowConditional`
44+
45+
Setting this option to `true` will allow using `test.slow()` to conditionally
46+
mark a test as slow. This can be helpful if you want to prevent usage of
47+
`test.slow` being added by mistake but still allow slow tests based on
48+
browser/environment setup.
49+
50+
Example of **correct** code for the `{ "allowConditional": true }` option:
51+
52+
```javascript
53+
test('foo', ({ browserName }) => {
54+
test.slow(browserName === 'firefox', 'Still working on it')
55+
})
56+
```

src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import noPagePause from './rules/no-page-pause.js'
2020
import noRawLocators from './rules/no-raw-locators.js'
2121
import noRestrictedMatchers from './rules/no-restricted-matchers.js'
2222
import noSkippedTest from './rules/no-skipped-test.js'
23+
import noSlowedTests from './rules/no-slowed-test.js'
2324
import noStandaloneExpect from './rules/no-standalone-expect.js'
2425
import noUnsafeReferences from './rules/no-unsafe-references.js'
2526
import noUselessAwait from './rules/no-useless-await.js'
@@ -72,6 +73,7 @@ const index = {
7273
'no-raw-locators': noRawLocators,
7374
'no-restricted-matchers': noRestrictedMatchers,
7475
'no-skipped-test': noSkippedTest,
76+
'no-slowed-test': noSlowedTests,
7577
'no-standalone-expect': noStandaloneExpect,
7678
'no-unsafe-references': noUnsafeReferences,
7779
'no-useless-await': noUselessAwait,

src/rules/no-slowed-test.test.ts

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import { runRuleTester } from '../utils/rule-tester.js'
2+
import rule from './no-slowed-test.js'
3+
4+
const messageId = 'removeSlowedTestAnnotation'
5+
6+
runRuleTester('no-slowed-test', rule, {
7+
invalid: [
8+
{
9+
code: 'test.slow("slow this test", async ({ page }) => {});',
10+
errors: [
11+
{
12+
column: 6,
13+
endColumn: 10,
14+
line: 1,
15+
messageId: 'noSlowedTest',
16+
suggestions: [
17+
{
18+
messageId,
19+
output: 'test("slow this test", async ({ page }) => {});',
20+
},
21+
],
22+
},
23+
],
24+
},
25+
{
26+
code: 'test["slow"]("slow this test", async ({ page }) => {});',
27+
errors: [
28+
{
29+
column: 6,
30+
endColumn: 12,
31+
line: 1,
32+
messageId: 'noSlowedTest',
33+
suggestions: [
34+
{
35+
messageId,
36+
output: 'test("slow this test", async ({ page }) => {});',
37+
},
38+
],
39+
},
40+
],
41+
},
42+
{
43+
code: 'test[`slow`]("slow this test", async ({ page }) => {});',
44+
errors: [
45+
{
46+
column: 6,
47+
endColumn: 12,
48+
line: 1,
49+
messageId: 'noSlowedTest',
50+
suggestions: [
51+
{
52+
messageId,
53+
output: 'test("slow this test", async ({ page }) => {});',
54+
},
55+
],
56+
},
57+
],
58+
},
59+
{
60+
code: 'test.slow("a test", { tag: ["@fast", "@login"] }, () => {})',
61+
errors: [
62+
{
63+
column: 6,
64+
endColumn: 10,
65+
line: 1,
66+
messageId: 'noSlowedTest',
67+
suggestions: [
68+
{
69+
messageId,
70+
output: 'test("a test", { tag: ["@fast", "@login"] }, () => {})',
71+
},
72+
],
73+
},
74+
],
75+
},
76+
{
77+
code: 'test.slow(browserName === "firefox");',
78+
errors: [
79+
{
80+
column: 1,
81+
endColumn: 37,
82+
line: 1,
83+
messageId: 'noSlowedTest',
84+
suggestions: [{ messageId, output: '' }],
85+
},
86+
],
87+
},
88+
{
89+
code: 'test.slow(browserName === "firefox", "Still working on it");',
90+
errors: [
91+
{
92+
column: 1,
93+
endColumn: 60,
94+
line: 1,
95+
messageId: 'noSlowedTest',
96+
suggestions: [{ messageId, output: '' }],
97+
},
98+
],
99+
},
100+
{
101+
code: 'test.slow()',
102+
errors: [
103+
{
104+
column: 1,
105+
endColumn: 12,
106+
line: 1,
107+
messageId: 'noSlowedTest',
108+
suggestions: [{ messageId, output: '' }],
109+
},
110+
],
111+
},
112+
{
113+
code: 'test["slow"]()',
114+
errors: [
115+
{
116+
column: 1,
117+
endColumn: 15,
118+
line: 1,
119+
messageId: 'noSlowedTest',
120+
suggestions: [{ messageId, output: '' }],
121+
},
122+
],
123+
},
124+
{
125+
code: 'test[`slow`]()',
126+
errors: [
127+
{
128+
column: 1,
129+
endColumn: 15,
130+
line: 1,
131+
messageId: 'noSlowedTest',
132+
suggestions: [{ messageId, output: '' }],
133+
},
134+
],
135+
},
136+
// Global aliases
137+
{
138+
code: 'it.slow("slow this test", async ({ page }) => {});',
139+
errors: [
140+
{
141+
column: 4,
142+
endColumn: 8,
143+
line: 1,
144+
messageId: 'noSlowedTest',
145+
suggestions: [
146+
{
147+
messageId,
148+
output: 'it("slow this test", async ({ page }) => {});',
149+
},
150+
],
151+
},
152+
],
153+
settings: {
154+
playwright: {
155+
globalAliases: { test: ['it'] },
156+
},
157+
},
158+
},
159+
],
160+
valid: [
161+
'test("a test", () => {});',
162+
'test("a test", { tag: "@fast" }, () => {});',
163+
'test("a test", { tag: ["@fast", "@report"] }, () => {});',
164+
'test("one", async ({ page }) => {});',
165+
'test.only(isMobile, "Settings page does not work in mobile yet");',
166+
'test.skip();',
167+
'test["skip"]();',
168+
'test[`skip`]();',
169+
'const slow = true;',
170+
'function slow() { return null };',
171+
'this.slow();',
172+
'this["slow"]();',
173+
'this[`slow`]();',
174+
{
175+
code: 'test.slow(browserName === "firefox", "Still working on it");',
176+
options: [{ allowConditional: true }],
177+
},
178+
// Global aliases
179+
{
180+
code: 'it("a test", () => {});',
181+
settings: {
182+
playwright: {
183+
globalAliases: { test: ['it'] },
184+
},
185+
},
186+
},
187+
],
188+
})

src/rules/no-slowed-test.ts

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { getStringValue } from '../utils/ast.js'
2+
import { createRule } from '../utils/createRule.js'
3+
import { parseFnCall } from '../utils/parseFnCall.js'
4+
5+
export default createRule({
6+
create(context) {
7+
return {
8+
CallExpression(node) {
9+
const options = context.options[0] || {}
10+
const allowConditional = !!options.allowConditional
11+
12+
const call = parseFnCall(context, node)
13+
if (call?.group !== 'test') {
14+
return
15+
}
16+
17+
const slowNode = call.members.find((s) => getStringValue(s) === 'slow')
18+
if (!slowNode) return
19+
20+
// If the call is a standalone `test.slow()` call, and not a test
21+
// annotation, we have to treat it a bit differently.
22+
const isStandalone = call.type === 'config'
23+
24+
// If allowConditional is enabled and it's not a test function,
25+
// we ignore any `test.slow` calls that have no arguments.
26+
if (isStandalone && allowConditional) {
27+
return
28+
}
29+
30+
context.report({
31+
messageId: 'noSlowedTest',
32+
node: isStandalone ? node : slowNode,
33+
suggest: [
34+
{
35+
fix: (fixer) => {
36+
return isStandalone
37+
? fixer.remove(node.parent)
38+
: fixer.removeRange([
39+
slowNode.range![0] - 1,
40+
slowNode.range![1] +
41+
Number(slowNode.type !== 'Identifier'),
42+
])
43+
},
44+
messageId: 'removeSlowedTestAnnotation',
45+
},
46+
],
47+
})
48+
},
49+
}
50+
},
51+
meta: {
52+
docs: {
53+
category: 'Best Practices',
54+
description: 'Prevent usage of the `.slow()` slow test annotation.',
55+
recommended: true,
56+
url: 'https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-slowed-test.md',
57+
},
58+
hasSuggestions: true,
59+
messages: {
60+
noSlowedTest: 'Unexpected use of the `.slow()` annotation.',
61+
removeSlowedTestAnnotation: 'Remove the `.slow()` annotation.',
62+
},
63+
schema: [
64+
{
65+
additionalProperties: false,
66+
properties: {
67+
allowConditional: {
68+
default: false,
69+
type: 'boolean',
70+
},
71+
},
72+
type: 'object',
73+
},
74+
],
75+
type: 'suggestion',
76+
},
77+
})

0 commit comments

Comments
 (0)