-
Notifications
You must be signed in to change notification settings - Fork 42
/
Copy pathprefer-native-locators.ts
123 lines (116 loc) · 3.28 KB
/
prefer-native-locators.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { AST } from 'eslint'
import { getStringValue, isPageMethod } from '../utils/ast.js'
import { createRule } from '../utils/createRule.js'
type Pattern = {
messageId: string
pattern: RegExp
replacement: string
}
const compilePatterns = ({
testIdAttribute,
}: {
testIdAttribute: string
}): Pattern[] => {
const patterns = [
{
attribute: 'aria-label',
messageId: 'unexpectedLabelQuery',
replacement: 'getByLabel',
},
{
attribute: 'role',
messageId: 'unexpectedRoleQuery',
replacement: 'getByRole',
},
{
attribute: 'placeholder',
messageId: 'unexpectedPlaceholderQuery',
replacement: 'getByPlaceholder',
},
{
attribute: 'alt',
messageId: 'unexpectedAltTextQuery',
replacement: 'getByAltText',
},
{
attribute: 'title',
messageId: 'unexpectedTitleQuery',
replacement: 'getByTitle',
},
{
attribute: testIdAttribute,
messageId: 'unexpectedTestIdQuery',
replacement: 'getByTestId',
},
]
return patterns.map(({ attribute, ...pattern }) => ({
...pattern,
pattern: new RegExp(`^\\[${attribute}=['"]?(.+?)['"]?\\]$`),
}))
}
export default createRule({
create(context) {
const { testIdAttribute } = {
testIdAttribute: 'data-testid',
...((context.options?.[0] as Record<string, unknown>) ?? {}),
}
const patterns = compilePatterns({ testIdAttribute })
return {
CallExpression(node) {
if (node.callee.type !== 'MemberExpression') return
const query = getStringValue(node.arguments[0])
if (!isPageMethod(node, 'locator')) return
for (const pattern of patterns) {
const match = query.match(pattern.pattern)
if (match) {
context.report({
fix(fixer) {
const start =
node.callee.type === 'MemberExpression'
? node.callee.property.range![0]
: node.range![0]
const end = node.range![1]
const rangeToReplace: AST.Range = [start, end]
const newText = `${pattern.replacement}("${match[1]}")`
return fixer.replaceTextRange(rangeToReplace, newText)
},
messageId: pattern.messageId,
node,
})
return
}
}
},
}
},
meta: {
docs: {
category: 'Best Practices',
description: 'Prefer native locator functions',
recommended: false,
url: 'https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-native-locators.md',
},
fixable: 'code',
messages: {
unexpectedAltTextQuery: 'Use getByAltText() instead',
unexpectedLabelQuery: 'Use getByLabel() instead',
unexpectedPlaceholderQuery: 'Use getByPlaceholder() instead',
unexpectedRoleQuery: 'Use getByRole() instead',
unexpectedTestIdQuery: 'Use getByTestId() instead',
unexpectedTitleQuery: 'Use getByTitle() instead',
},
schema: [
{
additionalProperties: false,
properties: {
testIdAttribute: {
default: 'data-testid',
type: 'string',
},
},
type: 'object',
},
],
type: 'suggestion',
},
})