eslint-plugin-playwright 1.6.1 → 1.7.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 +2 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +294 -49
- package/dist/index.mjs +323 -61
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -194,6 +194,8 @@ CLI option\
|
|
|
194
194
|
| [prefer-hooks-in-order](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-hooks-in-order.md) | Prefer having hooks in a consistent order | | | |
|
|
195
195
|
| [prefer-hooks-on-top](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-hooks-on-top.md) | Suggest having hooks before any test cases | | | |
|
|
196
196
|
| [prefer-lowercase-title](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-lowercase-title.md) | Enforce lowercase test names | | 🔧 | |
|
|
197
|
+
| [prefer-native-locators](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-native-locators.md) | Suggest built-in locators over `page.locator()` | | 🔧 | |
|
|
198
|
+
| [prefer-locator](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-locator.md) | Suggest locators over page methods | | | |
|
|
197
199
|
| [prefer-strict-equal](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-strict-equal.md) | Suggest using `toStrictEqual()` | | | 💡 |
|
|
198
200
|
| [prefer-to-be](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-be.md) | Suggest using `toBe()` | | 🔧 | |
|
|
199
201
|
| [prefer-to-contain](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-contain.md) | Suggest using `toContain()` | | 🔧 | |
|
package/dist/index.d.mts
CHANGED
|
@@ -41,7 +41,9 @@ declare const _default: {
|
|
|
41
41
|
'prefer-equality-matcher': eslint.Rule.RuleModule;
|
|
42
42
|
'prefer-hooks-in-order': eslint.Rule.RuleModule;
|
|
43
43
|
'prefer-hooks-on-top': eslint.Rule.RuleModule;
|
|
44
|
+
'prefer-locator': eslint.Rule.RuleModule;
|
|
44
45
|
'prefer-lowercase-title': eslint.Rule.RuleModule;
|
|
46
|
+
'prefer-native-locators': eslint.Rule.RuleModule;
|
|
45
47
|
'prefer-strict-equal': eslint.Rule.RuleModule;
|
|
46
48
|
'prefer-to-be': eslint.Rule.RuleModule;
|
|
47
49
|
'prefer-to-contain': eslint.Rule.RuleModule;
|
|
@@ -106,7 +108,9 @@ declare const _default: {
|
|
|
106
108
|
'prefer-equality-matcher': eslint.Rule.RuleModule;
|
|
107
109
|
'prefer-hooks-in-order': eslint.Rule.RuleModule;
|
|
108
110
|
'prefer-hooks-on-top': eslint.Rule.RuleModule;
|
|
111
|
+
'prefer-locator': eslint.Rule.RuleModule;
|
|
109
112
|
'prefer-lowercase-title': eslint.Rule.RuleModule;
|
|
113
|
+
'prefer-native-locators': eslint.Rule.RuleModule;
|
|
110
114
|
'prefer-strict-equal': eslint.Rule.RuleModule;
|
|
111
115
|
'prefer-to-be': eslint.Rule.RuleModule;
|
|
112
116
|
'prefer-to-contain': eslint.Rule.RuleModule;
|
|
@@ -273,7 +277,9 @@ declare const _default: {
|
|
|
273
277
|
'prefer-equality-matcher': eslint.Rule.RuleModule;
|
|
274
278
|
'prefer-hooks-in-order': eslint.Rule.RuleModule;
|
|
275
279
|
'prefer-hooks-on-top': eslint.Rule.RuleModule;
|
|
280
|
+
'prefer-locator': eslint.Rule.RuleModule;
|
|
276
281
|
'prefer-lowercase-title': eslint.Rule.RuleModule;
|
|
282
|
+
'prefer-native-locators': eslint.Rule.RuleModule;
|
|
277
283
|
'prefer-strict-equal': eslint.Rule.RuleModule;
|
|
278
284
|
'prefer-to-be': eslint.Rule.RuleModule;
|
|
279
285
|
'prefer-to-contain': eslint.Rule.RuleModule;
|
package/dist/index.d.ts
CHANGED
|
@@ -41,7 +41,9 @@ declare const _default: {
|
|
|
41
41
|
'prefer-equality-matcher': eslint.Rule.RuleModule;
|
|
42
42
|
'prefer-hooks-in-order': eslint.Rule.RuleModule;
|
|
43
43
|
'prefer-hooks-on-top': eslint.Rule.RuleModule;
|
|
44
|
+
'prefer-locator': eslint.Rule.RuleModule;
|
|
44
45
|
'prefer-lowercase-title': eslint.Rule.RuleModule;
|
|
46
|
+
'prefer-native-locators': eslint.Rule.RuleModule;
|
|
45
47
|
'prefer-strict-equal': eslint.Rule.RuleModule;
|
|
46
48
|
'prefer-to-be': eslint.Rule.RuleModule;
|
|
47
49
|
'prefer-to-contain': eslint.Rule.RuleModule;
|
|
@@ -106,7 +108,9 @@ declare const _default: {
|
|
|
106
108
|
'prefer-equality-matcher': eslint.Rule.RuleModule;
|
|
107
109
|
'prefer-hooks-in-order': eslint.Rule.RuleModule;
|
|
108
110
|
'prefer-hooks-on-top': eslint.Rule.RuleModule;
|
|
111
|
+
'prefer-locator': eslint.Rule.RuleModule;
|
|
109
112
|
'prefer-lowercase-title': eslint.Rule.RuleModule;
|
|
113
|
+
'prefer-native-locators': eslint.Rule.RuleModule;
|
|
110
114
|
'prefer-strict-equal': eslint.Rule.RuleModule;
|
|
111
115
|
'prefer-to-be': eslint.Rule.RuleModule;
|
|
112
116
|
'prefer-to-contain': eslint.Rule.RuleModule;
|
|
@@ -273,7 +277,9 @@ declare const _default: {
|
|
|
273
277
|
'prefer-equality-matcher': eslint.Rule.RuleModule;
|
|
274
278
|
'prefer-hooks-in-order': eslint.Rule.RuleModule;
|
|
275
279
|
'prefer-hooks-on-top': eslint.Rule.RuleModule;
|
|
280
|
+
'prefer-locator': eslint.Rule.RuleModule;
|
|
276
281
|
'prefer-lowercase-title': eslint.Rule.RuleModule;
|
|
282
|
+
'prefer-native-locators': eslint.Rule.RuleModule;
|
|
277
283
|
'prefer-strict-equal': eslint.Rule.RuleModule;
|
|
278
284
|
'prefer-to-be': eslint.Rule.RuleModule;
|
|
279
285
|
'prefer-to-contain': eslint.Rule.RuleModule;
|
|
@@ -291,4 +297,4 @@ declare const _default: {
|
|
|
291
297
|
};
|
|
292
298
|
};
|
|
293
299
|
|
|
294
|
-
export
|
|
300
|
+
export = _default;
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,28 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
22
|
mod
|
|
23
23
|
));
|
|
24
|
+
var __accessCheck = (obj, member, msg) => {
|
|
25
|
+
if (!member.has(obj))
|
|
26
|
+
throw TypeError("Cannot " + msg);
|
|
27
|
+
};
|
|
28
|
+
var __privateGet = (obj, member, getter) => {
|
|
29
|
+
__accessCheck(obj, member, "read from private field");
|
|
30
|
+
return getter ? getter.call(obj) : member.get(obj);
|
|
31
|
+
};
|
|
32
|
+
var __privateAdd = (obj, member, value) => {
|
|
33
|
+
if (member.has(obj))
|
|
34
|
+
throw TypeError("Cannot add the same private member more than once");
|
|
35
|
+
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
36
|
+
};
|
|
37
|
+
var __privateSet = (obj, member, value, setter) => {
|
|
38
|
+
__accessCheck(obj, member, "write to private field");
|
|
39
|
+
setter ? setter.call(obj, value) : member.set(obj, value);
|
|
40
|
+
return value;
|
|
41
|
+
};
|
|
42
|
+
var __privateMethod = (obj, member, method) => {
|
|
43
|
+
__accessCheck(obj, member, "access private method");
|
|
44
|
+
return method;
|
|
45
|
+
};
|
|
24
46
|
|
|
25
47
|
// src/index.ts
|
|
26
48
|
var import_globals = __toESM(require("globals"));
|
|
@@ -82,20 +104,45 @@ var VALID_CHAINS = /* @__PURE__ */ new Set([
|
|
|
82
104
|
]);
|
|
83
105
|
var joinChains = (a, b) => a && b ? [...a, ...b] : null;
|
|
84
106
|
var isSupportedAccessor = (node, value) => isIdentifier(node, value) || isStringNode(node, value);
|
|
85
|
-
|
|
107
|
+
var _nodes, _leaves, _buildChain, buildChain_fn;
|
|
108
|
+
var Chain = class {
|
|
109
|
+
constructor(node) {
|
|
110
|
+
__privateAdd(this, _buildChain);
|
|
111
|
+
__privateAdd(this, _nodes, null);
|
|
112
|
+
__privateAdd(this, _leaves, /* @__PURE__ */ new WeakSet());
|
|
113
|
+
__privateSet(this, _nodes, __privateMethod(this, _buildChain, buildChain_fn).call(this, node));
|
|
114
|
+
}
|
|
115
|
+
isLeaf(node) {
|
|
116
|
+
return __privateGet(this, _leaves).has(node);
|
|
117
|
+
}
|
|
118
|
+
get nodes() {
|
|
119
|
+
return __privateGet(this, _nodes);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
_nodes = new WeakMap();
|
|
123
|
+
_leaves = new WeakMap();
|
|
124
|
+
_buildChain = new WeakSet();
|
|
125
|
+
buildChain_fn = function(node, insideCall = false) {
|
|
86
126
|
if (isSupportedAccessor(node)) {
|
|
127
|
+
if (insideCall) {
|
|
128
|
+
__privateGet(this, _leaves).add(node);
|
|
129
|
+
}
|
|
87
130
|
return [node];
|
|
88
131
|
}
|
|
89
132
|
switch (node.type) {
|
|
90
133
|
case "TaggedTemplateExpression":
|
|
91
|
-
return
|
|
134
|
+
return __privateMethod(this, _buildChain, buildChain_fn).call(this, node.tag);
|
|
92
135
|
case "MemberExpression":
|
|
93
|
-
return joinChains(
|
|
136
|
+
return joinChains(
|
|
137
|
+
__privateMethod(this, _buildChain, buildChain_fn).call(this, node.object),
|
|
138
|
+
__privateMethod(this, _buildChain, buildChain_fn).call(this, node.property, insideCall)
|
|
139
|
+
);
|
|
94
140
|
case "CallExpression":
|
|
95
|
-
return
|
|
141
|
+
return __privateMethod(this, _buildChain, buildChain_fn).call(this, node.callee, true);
|
|
142
|
+
default:
|
|
143
|
+
return null;
|
|
96
144
|
}
|
|
97
|
-
|
|
98
|
-
}
|
|
145
|
+
};
|
|
99
146
|
var resolvePossibleAliasedGlobal = (context, global) => {
|
|
100
147
|
const globalAliases = context.settings.playwright?.globalAliases ?? {};
|
|
101
148
|
const alias = Object.entries(globalAliases).find(
|
|
@@ -126,7 +173,7 @@ function determinePlaywrightFnGroup(name) {
|
|
|
126
173
|
return "unknown";
|
|
127
174
|
}
|
|
128
175
|
var modifiers = /* @__PURE__ */ new Set(["not", "resolves", "rejects"]);
|
|
129
|
-
var findModifiersAndMatcher = (members) => {
|
|
176
|
+
var findModifiersAndMatcher = (chain, members, stage) => {
|
|
130
177
|
const modifiers2 = [];
|
|
131
178
|
for (const member of members) {
|
|
132
179
|
const name = getStringValue(member);
|
|
@@ -140,6 +187,9 @@ var findModifiersAndMatcher = (members) => {
|
|
|
140
187
|
return "modifier-unknown";
|
|
141
188
|
}
|
|
142
189
|
} else if (name !== "not") {
|
|
190
|
+
if (stage === "modifiers") {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
143
193
|
if (member.parent?.type === "MemberExpression" && member.parent.parent?.type === "CallExpression") {
|
|
144
194
|
return {
|
|
145
195
|
matcher: member,
|
|
@@ -150,6 +200,9 @@ var findModifiersAndMatcher = (members) => {
|
|
|
150
200
|
}
|
|
151
201
|
return "modifier-unknown";
|
|
152
202
|
}
|
|
203
|
+
if (chain.isLeaf(member)) {
|
|
204
|
+
stage = "matchers";
|
|
205
|
+
}
|
|
153
206
|
modifiers2.push(member);
|
|
154
207
|
}
|
|
155
208
|
return "matcher-not-found";
|
|
@@ -157,8 +210,15 @@ var findModifiersAndMatcher = (members) => {
|
|
|
157
210
|
function getExpectArguments(call) {
|
|
158
211
|
return findParent(call.head.node, "CallExpression")?.arguments ?? [];
|
|
159
212
|
}
|
|
160
|
-
var parseExpectCall = (call) => {
|
|
161
|
-
const modifiersAndMatcher = findModifiersAndMatcher(
|
|
213
|
+
var parseExpectCall = (chain, call, stage) => {
|
|
214
|
+
const modifiersAndMatcher = findModifiersAndMatcher(
|
|
215
|
+
chain,
|
|
216
|
+
call.members,
|
|
217
|
+
stage
|
|
218
|
+
);
|
|
219
|
+
if (!modifiersAndMatcher) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
162
222
|
if (typeof modifiersAndMatcher === "string") {
|
|
163
223
|
return modifiersAndMatcher;
|
|
164
224
|
}
|
|
@@ -190,19 +250,15 @@ var findTopMostCallExpression = (node) => {
|
|
|
190
250
|
return top;
|
|
191
251
|
};
|
|
192
252
|
function parse(context, node) {
|
|
193
|
-
const chain =
|
|
194
|
-
if (!chain?.length)
|
|
253
|
+
const chain = new Chain(node);
|
|
254
|
+
if (!chain.nodes?.length)
|
|
195
255
|
return null;
|
|
196
|
-
|
|
197
|
-
const [first, ...rest] = chain;
|
|
256
|
+
const [first, ...rest] = chain.nodes;
|
|
198
257
|
const resolved = resolveToPlaywrightFn(context, first);
|
|
199
258
|
if (!resolved)
|
|
200
259
|
return null;
|
|
201
260
|
let name = resolved.original ?? resolved.local;
|
|
202
261
|
const links = [name, ...rest.map((link) => getStringValue(link))];
|
|
203
|
-
if (name !== "expect" && !VALID_CHAINS.has(links.join("."))) {
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
262
|
if (name === "test" && links.length > 1) {
|
|
207
263
|
const nextLinkName = links[1];
|
|
208
264
|
const nextLinkGroup = determinePlaywrightFnGroup(nextLinkName);
|
|
@@ -210,6 +266,9 @@ function parse(context, node) {
|
|
|
210
266
|
name = nextLinkName;
|
|
211
267
|
}
|
|
212
268
|
}
|
|
269
|
+
if (name !== "expect" && !VALID_CHAINS.has(links.join("."))) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
213
272
|
const parsedFnCall = {
|
|
214
273
|
head: { ...resolved, node: first },
|
|
215
274
|
// every member node must have a member expression as their parent
|
|
@@ -219,7 +278,14 @@ function parse(context, node) {
|
|
|
219
278
|
};
|
|
220
279
|
const group = determinePlaywrightFnGroup(name);
|
|
221
280
|
if (group === "expect") {
|
|
222
|
-
|
|
281
|
+
let stage = chain.isLeaf(parsedFnCall.head.node) ? "matchers" : "modifiers";
|
|
282
|
+
if (isIdentifier(rest[0], "expect")) {
|
|
283
|
+
stage = chain.isLeaf(rest[0]) ? "matchers" : "modifiers";
|
|
284
|
+
parsedFnCall.members.shift();
|
|
285
|
+
}
|
|
286
|
+
const result = parseExpectCall(chain, parsedFnCall, stage);
|
|
287
|
+
if (!result)
|
|
288
|
+
return null;
|
|
223
289
|
if (typeof result === "string" && findTopMostCallExpression(node) !== node) {
|
|
224
290
|
return null;
|
|
225
291
|
}
|
|
@@ -230,7 +296,7 @@ function parse(context, node) {
|
|
|
230
296
|
}
|
|
231
297
|
return result;
|
|
232
298
|
}
|
|
233
|
-
if (chain.slice(0, chain.length - 1).some((n) => getParent(n)?.type !== "MemberExpression")) {
|
|
299
|
+
if (chain.nodes.slice(0, chain.nodes.length - 1).some((n) => getParent(n)?.type !== "MemberExpression")) {
|
|
234
300
|
return null;
|
|
235
301
|
}
|
|
236
302
|
const parent = getParent(node);
|
|
@@ -275,7 +341,7 @@ function getRawValue(node) {
|
|
|
275
341
|
return node.type === "Literal" ? node.raw : void 0;
|
|
276
342
|
}
|
|
277
343
|
function isIdentifier(node, name) {
|
|
278
|
-
return node
|
|
344
|
+
return node?.type === "Identifier" && (!name || (typeof name === "string" ? node.name === name : name.test(node.name)));
|
|
279
345
|
}
|
|
280
346
|
function isLiteral(node, type, value) {
|
|
281
347
|
return node.type === "Literal" && (value === void 0 ? typeof node.value === type : node.value === value);
|
|
@@ -292,7 +358,8 @@ function isStringNode(node, value) {
|
|
|
292
358
|
return node && (isStringLiteral(node, value) || isTemplateLiteral(node, value));
|
|
293
359
|
}
|
|
294
360
|
function isPropertyAccessor(node, name) {
|
|
295
|
-
|
|
361
|
+
const value = getStringValue(node.property);
|
|
362
|
+
return typeof name === "string" ? value === name : name.test(value);
|
|
296
363
|
}
|
|
297
364
|
function getParent(node) {
|
|
298
365
|
return node.parent;
|
|
@@ -329,6 +396,24 @@ function getNodeName(node) {
|
|
|
329
396
|
}
|
|
330
397
|
return null;
|
|
331
398
|
}
|
|
399
|
+
var isVariableDeclarator = (node) => node.type === "VariableDeclarator";
|
|
400
|
+
var isAssignmentExpression = (node) => node.type === "AssignmentExpression";
|
|
401
|
+
function isNodeLastAssignment(node, assignment) {
|
|
402
|
+
if (node.range && assignment.range && node.range[0] < assignment.range[1]) {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
return assignment.left.type === "Identifier" && assignment.left.name === node.name;
|
|
406
|
+
}
|
|
407
|
+
function dereference(context, node) {
|
|
408
|
+
if (node?.type !== "Identifier") {
|
|
409
|
+
return node;
|
|
410
|
+
}
|
|
411
|
+
const scope = context.sourceCode.getScope(node);
|
|
412
|
+
const parents = scope.references.map((ref) => ref.identifier).map((ident) => ident.parent);
|
|
413
|
+
const decl = parents.filter(isVariableDeclarator).find((p) => p.id.type === "Identifier" && p.id.name === node.name);
|
|
414
|
+
const expr = parents.filter(isAssignmentExpression).reverse().find((assignment) => isNodeLastAssignment(node, assignment));
|
|
415
|
+
return expr?.right ?? decl?.init;
|
|
416
|
+
}
|
|
332
417
|
|
|
333
418
|
// src/utils/createRule.ts
|
|
334
419
|
function interpolate(str, data) {
|
|
@@ -2222,6 +2307,66 @@ var prefer_hooks_on_top_default = createRule({
|
|
|
2222
2307
|
}
|
|
2223
2308
|
});
|
|
2224
2309
|
|
|
2310
|
+
// src/rules/prefer-locator.ts
|
|
2311
|
+
var pageMethods2 = /* @__PURE__ */ new Set([
|
|
2312
|
+
"click",
|
|
2313
|
+
"dblclick",
|
|
2314
|
+
"dispatchEvent",
|
|
2315
|
+
"fill",
|
|
2316
|
+
"focus",
|
|
2317
|
+
"getAttribute",
|
|
2318
|
+
"hover",
|
|
2319
|
+
"innerHTML",
|
|
2320
|
+
"innerText",
|
|
2321
|
+
"inputValue",
|
|
2322
|
+
"isChecked",
|
|
2323
|
+
"isDisabled",
|
|
2324
|
+
"isEditable",
|
|
2325
|
+
"isEnabled",
|
|
2326
|
+
"isHidden",
|
|
2327
|
+
"isVisible",
|
|
2328
|
+
"press",
|
|
2329
|
+
"selectOption",
|
|
2330
|
+
"setChecked",
|
|
2331
|
+
"setInputFiles",
|
|
2332
|
+
"tap",
|
|
2333
|
+
"textContent",
|
|
2334
|
+
"uncheck"
|
|
2335
|
+
]);
|
|
2336
|
+
function isSupportedMethod2(node) {
|
|
2337
|
+
if (node.callee.type !== "MemberExpression")
|
|
2338
|
+
return false;
|
|
2339
|
+
const name = getStringValue(node.callee.property);
|
|
2340
|
+
return pageMethods2.has(name) && isPageMethod(node, name);
|
|
2341
|
+
}
|
|
2342
|
+
var prefer_locator_default = createRule({
|
|
2343
|
+
create(context) {
|
|
2344
|
+
return {
|
|
2345
|
+
CallExpression(node) {
|
|
2346
|
+
if (!isSupportedMethod2(node))
|
|
2347
|
+
return;
|
|
2348
|
+
context.report({
|
|
2349
|
+
messageId: "preferLocator",
|
|
2350
|
+
node
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
};
|
|
2354
|
+
},
|
|
2355
|
+
meta: {
|
|
2356
|
+
docs: {
|
|
2357
|
+
category: "Best Practices",
|
|
2358
|
+
description: "Suggest locators over page methods",
|
|
2359
|
+
recommended: false,
|
|
2360
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-locator.md"
|
|
2361
|
+
},
|
|
2362
|
+
messages: {
|
|
2363
|
+
preferLocator: "Prefer locator methods instead of page methods"
|
|
2364
|
+
},
|
|
2365
|
+
schema: [],
|
|
2366
|
+
type: "suggestion"
|
|
2367
|
+
}
|
|
2368
|
+
});
|
|
2369
|
+
|
|
2225
2370
|
// src/rules/prefer-lowercase-title.ts
|
|
2226
2371
|
var prefer_lowercase_title_default = createRule({
|
|
2227
2372
|
create(context) {
|
|
@@ -2317,6 +2462,113 @@ var prefer_lowercase_title_default = createRule({
|
|
|
2317
2462
|
}
|
|
2318
2463
|
});
|
|
2319
2464
|
|
|
2465
|
+
// src/rules/prefer-native-locators.ts
|
|
2466
|
+
var compilePatterns = ({
|
|
2467
|
+
testIdAttribute
|
|
2468
|
+
}) => {
|
|
2469
|
+
const patterns = [
|
|
2470
|
+
{
|
|
2471
|
+
attribute: "aria-label",
|
|
2472
|
+
messageId: "unexpectedLabelQuery",
|
|
2473
|
+
replacement: "getByLabel"
|
|
2474
|
+
},
|
|
2475
|
+
{
|
|
2476
|
+
attribute: "role",
|
|
2477
|
+
messageId: "unexpectedRoleQuery",
|
|
2478
|
+
replacement: "getByRole"
|
|
2479
|
+
},
|
|
2480
|
+
{
|
|
2481
|
+
attribute: "placeholder",
|
|
2482
|
+
messageId: "unexpectedPlaceholderQuery",
|
|
2483
|
+
replacement: "getByPlaceholder"
|
|
2484
|
+
},
|
|
2485
|
+
{
|
|
2486
|
+
attribute: "alt",
|
|
2487
|
+
messageId: "unexpectedAltTextQuery",
|
|
2488
|
+
replacement: "getByAltText"
|
|
2489
|
+
},
|
|
2490
|
+
{
|
|
2491
|
+
attribute: "title",
|
|
2492
|
+
messageId: "unexpectedTitleQuery",
|
|
2493
|
+
replacement: "getByTitle"
|
|
2494
|
+
},
|
|
2495
|
+
{
|
|
2496
|
+
attribute: testIdAttribute,
|
|
2497
|
+
messageId: "unexpectedTestIdQuery",
|
|
2498
|
+
replacement: "getByTestId"
|
|
2499
|
+
}
|
|
2500
|
+
];
|
|
2501
|
+
return patterns.map(({ attribute, ...pattern }) => ({
|
|
2502
|
+
...pattern,
|
|
2503
|
+
pattern: new RegExp(`^\\[${attribute}=['"]?(.+?)['"]?\\]$`)
|
|
2504
|
+
}));
|
|
2505
|
+
};
|
|
2506
|
+
var prefer_native_locators_default = createRule({
|
|
2507
|
+
create(context) {
|
|
2508
|
+
const { testIdAttribute } = {
|
|
2509
|
+
testIdAttribute: "data-testid",
|
|
2510
|
+
...context.options?.[0] ?? {}
|
|
2511
|
+
};
|
|
2512
|
+
const patterns = compilePatterns({ testIdAttribute });
|
|
2513
|
+
return {
|
|
2514
|
+
CallExpression(node) {
|
|
2515
|
+
if (node.callee.type !== "MemberExpression")
|
|
2516
|
+
return;
|
|
2517
|
+
const query = getStringValue(node.arguments[0]);
|
|
2518
|
+
if (!isPageMethod(node, "locator"))
|
|
2519
|
+
return;
|
|
2520
|
+
for (const pattern of patterns) {
|
|
2521
|
+
const match = query.match(pattern.pattern);
|
|
2522
|
+
if (match) {
|
|
2523
|
+
context.report({
|
|
2524
|
+
fix(fixer) {
|
|
2525
|
+
const start = node.callee.type === "MemberExpression" ? node.callee.property.range[0] : node.range[0];
|
|
2526
|
+
const end = node.range[1];
|
|
2527
|
+
const rangeToReplace = [start, end];
|
|
2528
|
+
const newText = `${pattern.replacement}("${match[1]}")`;
|
|
2529
|
+
return fixer.replaceTextRange(rangeToReplace, newText);
|
|
2530
|
+
},
|
|
2531
|
+
messageId: pattern.messageId,
|
|
2532
|
+
node
|
|
2533
|
+
});
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
};
|
|
2539
|
+
},
|
|
2540
|
+
meta: {
|
|
2541
|
+
docs: {
|
|
2542
|
+
category: "Best Practices",
|
|
2543
|
+
description: "Prefer native locator functions",
|
|
2544
|
+
recommended: false,
|
|
2545
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-native-locators.md"
|
|
2546
|
+
},
|
|
2547
|
+
fixable: "code",
|
|
2548
|
+
messages: {
|
|
2549
|
+
unexpectedAltTextQuery: "Use getByAltText() instead",
|
|
2550
|
+
unexpectedLabelQuery: "Use getByLabel() instead",
|
|
2551
|
+
unexpectedPlaceholderQuery: "Use getByPlaceholder() instead",
|
|
2552
|
+
unexpectedRoleQuery: "Use getByRole() instead",
|
|
2553
|
+
unexpectedTestIdQuery: "Use getByTestId() instead",
|
|
2554
|
+
unexpectedTitleQuery: "Use getByTitle() instead"
|
|
2555
|
+
},
|
|
2556
|
+
schema: [
|
|
2557
|
+
{
|
|
2558
|
+
additionalProperties: false,
|
|
2559
|
+
properties: {
|
|
2560
|
+
testIdAttribute: {
|
|
2561
|
+
default: "data-testid",
|
|
2562
|
+
type: "string"
|
|
2563
|
+
}
|
|
2564
|
+
},
|
|
2565
|
+
type: "object"
|
|
2566
|
+
}
|
|
2567
|
+
],
|
|
2568
|
+
type: "suggestion"
|
|
2569
|
+
}
|
|
2570
|
+
});
|
|
2571
|
+
|
|
2320
2572
|
// src/rules/prefer-strict-equal.ts
|
|
2321
2573
|
var prefer_strict_equal_default = createRule({
|
|
2322
2574
|
create(context) {
|
|
@@ -2526,16 +2778,18 @@ var prefer_to_contain_default = createRule({
|
|
|
2526
2778
|
});
|
|
2527
2779
|
|
|
2528
2780
|
// src/rules/prefer-to-have-count.ts
|
|
2781
|
+
var matchers = /* @__PURE__ */ new Set([...equalityMatchers, "toHaveLength"]);
|
|
2529
2782
|
var prefer_to_have_count_default = createRule({
|
|
2530
2783
|
create(context) {
|
|
2531
2784
|
return {
|
|
2532
2785
|
CallExpression(node) {
|
|
2533
2786
|
const call = parseFnCall(context, node);
|
|
2534
|
-
if (call?.type !== "expect" || !
|
|
2787
|
+
if (call?.type !== "expect" || !matchers.has(call.matcherName)) {
|
|
2535
2788
|
return;
|
|
2536
2789
|
}
|
|
2537
|
-
const
|
|
2538
|
-
|
|
2790
|
+
const accessor = call.matcherName === "toHaveLength" ? "all" : "count";
|
|
2791
|
+
const argument = dereference(context, call.args[0]);
|
|
2792
|
+
if (argument?.type !== "AwaitExpression" || argument.argument.type !== "CallExpression" || argument.argument.callee.type !== "MemberExpression" || !isPropertyAccessor(argument.argument.callee, accessor)) {
|
|
2539
2793
|
return;
|
|
2540
2794
|
}
|
|
2541
2795
|
const callee = argument.argument.callee;
|
|
@@ -2669,24 +2923,6 @@ var supportedMatchers = /* @__PURE__ */ new Set([
|
|
|
2669
2923
|
"toBeTruthy",
|
|
2670
2924
|
"toBeFalsy"
|
|
2671
2925
|
]);
|
|
2672
|
-
var isVariableDeclarator = (node) => node.type === "VariableDeclarator";
|
|
2673
|
-
var isAssignmentExpression = (node) => node.type === "AssignmentExpression";
|
|
2674
|
-
function isNodeLastAssignment(node, assignment) {
|
|
2675
|
-
if (node.range && assignment.range && node.range[0] < assignment.range[1]) {
|
|
2676
|
-
return false;
|
|
2677
|
-
}
|
|
2678
|
-
return assignment.left.type === "Identifier" && assignment.left.name === node.name;
|
|
2679
|
-
}
|
|
2680
|
-
function dereference(context, node) {
|
|
2681
|
-
if (node?.type !== "Identifier") {
|
|
2682
|
-
return node;
|
|
2683
|
-
}
|
|
2684
|
-
const scope = context.sourceCode.getScope(node);
|
|
2685
|
-
const parents = scope.references.map((ref) => ref.identifier).map((ident) => ident.parent);
|
|
2686
|
-
const decl = parents.filter(isVariableDeclarator).find((p) => p.id.type === "Identifier" && p.id.name === node.name);
|
|
2687
|
-
const expr = parents.filter(isAssignmentExpression).reverse().find((assignment) => isNodeLastAssignment(node, assignment));
|
|
2688
|
-
return expr?.right ?? decl?.init;
|
|
2689
|
-
}
|
|
2690
2926
|
var prefer_web_first_assertions_default = createRule({
|
|
2691
2927
|
create(context) {
|
|
2692
2928
|
return {
|
|
@@ -3479,17 +3715,19 @@ var compileMatcherPattern = (matcherMaybeWithMessage) => {
|
|
|
3479
3715
|
const [matcher, message] = Array.isArray(matcherMaybeWithMessage) ? matcherMaybeWithMessage : [matcherMaybeWithMessage];
|
|
3480
3716
|
return [new RegExp(matcher, "u"), message];
|
|
3481
3717
|
};
|
|
3482
|
-
var compileMatcherPatterns = (
|
|
3483
|
-
if (typeof
|
|
3484
|
-
const compiledMatcher = compileMatcherPattern(
|
|
3718
|
+
var compileMatcherPatterns = (matchers2) => {
|
|
3719
|
+
if (typeof matchers2 === "string" || Array.isArray(matchers2)) {
|
|
3720
|
+
const compiledMatcher = compileMatcherPattern(matchers2);
|
|
3485
3721
|
return {
|
|
3486
3722
|
describe: compiledMatcher,
|
|
3723
|
+
step: compiledMatcher,
|
|
3487
3724
|
test: compiledMatcher
|
|
3488
3725
|
};
|
|
3489
3726
|
}
|
|
3490
3727
|
return {
|
|
3491
|
-
describe:
|
|
3492
|
-
|
|
3728
|
+
describe: matchers2.describe ? compileMatcherPattern(matchers2.describe) : null,
|
|
3729
|
+
step: matchers2.step ? compileMatcherPattern(matchers2.step) : null,
|
|
3730
|
+
test: matchers2.test ? compileMatcherPattern(matchers2.test) : null
|
|
3493
3731
|
};
|
|
3494
3732
|
};
|
|
3495
3733
|
var MatcherAndMessageSchema = {
|
|
@@ -3506,6 +3744,7 @@ var valid_title_default = createRule({
|
|
|
3506
3744
|
disallowedWords = [],
|
|
3507
3745
|
ignoreSpaces = false,
|
|
3508
3746
|
ignoreTypeOfDescribeName = false,
|
|
3747
|
+
ignoreTypeOfStepName = true,
|
|
3509
3748
|
ignoreTypeOfTestName = false,
|
|
3510
3749
|
mustMatch,
|
|
3511
3750
|
mustNotMatch
|
|
@@ -3519,7 +3758,7 @@ var valid_title_default = createRule({
|
|
|
3519
3758
|
return {
|
|
3520
3759
|
CallExpression(node) {
|
|
3521
3760
|
const call = parseFnCall(context, node);
|
|
3522
|
-
if (call?.type !== "test" && call?.type !== "describe") {
|
|
3761
|
+
if (call?.type !== "test" && call?.type !== "describe" && call?.type !== "step") {
|
|
3523
3762
|
return;
|
|
3524
3763
|
}
|
|
3525
3764
|
const [argument] = node.arguments;
|
|
@@ -3529,7 +3768,7 @@ var valid_title_default = createRule({
|
|
|
3529
3768
|
if (argument.type === "BinaryExpression" && doesBinaryExpressionContainStringNode(argument)) {
|
|
3530
3769
|
return;
|
|
3531
3770
|
}
|
|
3532
|
-
if (!(call.type === "describe" && ignoreTypeOfDescribeName || call.type === "test" && ignoreTypeOfTestName) && argument.type !== "TemplateLiteral") {
|
|
3771
|
+
if (!(call.type === "describe" && ignoreTypeOfDescribeName || call.type === "test" && ignoreTypeOfTestName || call.type === "step" && ignoreTypeOfStepName) && argument.type !== "TemplateLiteral") {
|
|
3533
3772
|
context.report({
|
|
3534
3773
|
loc: argument.loc,
|
|
3535
3774
|
messageId: "titleMustBeString"
|
|
@@ -3643,7 +3882,7 @@ var valid_title_default = createRule({
|
|
|
3643
3882
|
additionalProperties: {
|
|
3644
3883
|
oneOf: [{ type: "string" }, MatcherAndMessageSchema]
|
|
3645
3884
|
},
|
|
3646
|
-
propertyNames: { enum: ["describe", "test"] },
|
|
3885
|
+
propertyNames: { enum: ["describe", "test", "step"] },
|
|
3647
3886
|
type: "object"
|
|
3648
3887
|
}
|
|
3649
3888
|
]
|
|
@@ -3662,6 +3901,10 @@ var valid_title_default = createRule({
|
|
|
3662
3901
|
default: false,
|
|
3663
3902
|
type: "boolean"
|
|
3664
3903
|
},
|
|
3904
|
+
ignoreTypeOfStepName: {
|
|
3905
|
+
default: true,
|
|
3906
|
+
type: "boolean"
|
|
3907
|
+
},
|
|
3665
3908
|
ignoreTypeOfTestName: {
|
|
3666
3909
|
default: false,
|
|
3667
3910
|
type: "boolean"
|
|
@@ -3709,7 +3952,9 @@ var index = {
|
|
|
3709
3952
|
"prefer-equality-matcher": prefer_equality_matcher_default,
|
|
3710
3953
|
"prefer-hooks-in-order": prefer_hooks_in_order_default,
|
|
3711
3954
|
"prefer-hooks-on-top": prefer_hooks_on_top_default,
|
|
3955
|
+
"prefer-locator": prefer_locator_default,
|
|
3712
3956
|
"prefer-lowercase-title": prefer_lowercase_title_default,
|
|
3957
|
+
"prefer-native-locators": prefer_native_locators_default,
|
|
3713
3958
|
"prefer-strict-equal": prefer_strict_equal_default,
|
|
3714
3959
|
"prefer-to-be": prefer_to_be_default,
|
|
3715
3960
|
"prefer-to-contain": prefer_to_contain_default,
|
package/dist/index.mjs
CHANGED
|
@@ -5,22 +5,30 @@ var __esm = (fn, res) => function __init() {
|
|
|
5
5
|
var __commonJS = (cb, mod) => function __require() {
|
|
6
6
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
7
7
|
};
|
|
8
|
+
var __accessCheck = (obj, member, msg) => {
|
|
9
|
+
if (!member.has(obj))
|
|
10
|
+
throw TypeError("Cannot " + msg);
|
|
11
|
+
};
|
|
12
|
+
var __privateGet = (obj, member, getter) => {
|
|
13
|
+
__accessCheck(obj, member, "read from private field");
|
|
14
|
+
return getter ? getter.call(obj) : member.get(obj);
|
|
15
|
+
};
|
|
16
|
+
var __privateAdd = (obj, member, value) => {
|
|
17
|
+
if (member.has(obj))
|
|
18
|
+
throw TypeError("Cannot add the same private member more than once");
|
|
19
|
+
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
20
|
+
};
|
|
21
|
+
var __privateSet = (obj, member, value, setter) => {
|
|
22
|
+
__accessCheck(obj, member, "write to private field");
|
|
23
|
+
setter ? setter.call(obj, value) : member.set(obj, value);
|
|
24
|
+
return value;
|
|
25
|
+
};
|
|
26
|
+
var __privateMethod = (obj, member, method) => {
|
|
27
|
+
__accessCheck(obj, member, "access private method");
|
|
28
|
+
return method;
|
|
29
|
+
};
|
|
8
30
|
|
|
9
31
|
// src/utils/parseFnCall.ts
|
|
10
|
-
function getNodeChain(node) {
|
|
11
|
-
if (isSupportedAccessor(node)) {
|
|
12
|
-
return [node];
|
|
13
|
-
}
|
|
14
|
-
switch (node.type) {
|
|
15
|
-
case "TaggedTemplateExpression":
|
|
16
|
-
return getNodeChain(node.tag);
|
|
17
|
-
case "MemberExpression":
|
|
18
|
-
return joinChains(getNodeChain(node.object), getNodeChain(node.property));
|
|
19
|
-
case "CallExpression":
|
|
20
|
-
return getNodeChain(node.callee);
|
|
21
|
-
}
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
32
|
function determinePlaywrightFnGroup(name) {
|
|
25
33
|
if (name === "step")
|
|
26
34
|
return "step";
|
|
@@ -38,19 +46,15 @@ function getExpectArguments(call) {
|
|
|
38
46
|
return findParent(call.head.node, "CallExpression")?.arguments ?? [];
|
|
39
47
|
}
|
|
40
48
|
function parse(context, node) {
|
|
41
|
-
const chain =
|
|
42
|
-
if (!chain?.length)
|
|
49
|
+
const chain = new Chain(node);
|
|
50
|
+
if (!chain.nodes?.length)
|
|
43
51
|
return null;
|
|
44
|
-
|
|
45
|
-
const [first, ...rest] = chain;
|
|
52
|
+
const [first, ...rest] = chain.nodes;
|
|
46
53
|
const resolved = resolveToPlaywrightFn(context, first);
|
|
47
54
|
if (!resolved)
|
|
48
55
|
return null;
|
|
49
56
|
let name = resolved.original ?? resolved.local;
|
|
50
57
|
const links = [name, ...rest.map((link) => getStringValue(link))];
|
|
51
|
-
if (name !== "expect" && !VALID_CHAINS.has(links.join("."))) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
58
|
if (name === "test" && links.length > 1) {
|
|
55
59
|
const nextLinkName = links[1];
|
|
56
60
|
const nextLinkGroup = determinePlaywrightFnGroup(nextLinkName);
|
|
@@ -58,6 +62,9 @@ function parse(context, node) {
|
|
|
58
62
|
name = nextLinkName;
|
|
59
63
|
}
|
|
60
64
|
}
|
|
65
|
+
if (name !== "expect" && !VALID_CHAINS.has(links.join("."))) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
61
68
|
const parsedFnCall = {
|
|
62
69
|
head: { ...resolved, node: first },
|
|
63
70
|
// every member node must have a member expression as their parent
|
|
@@ -67,7 +74,14 @@ function parse(context, node) {
|
|
|
67
74
|
};
|
|
68
75
|
const group = determinePlaywrightFnGroup(name);
|
|
69
76
|
if (group === "expect") {
|
|
70
|
-
|
|
77
|
+
let stage = chain.isLeaf(parsedFnCall.head.node) ? "matchers" : "modifiers";
|
|
78
|
+
if (isIdentifier(rest[0], "expect")) {
|
|
79
|
+
stage = chain.isLeaf(rest[0]) ? "matchers" : "modifiers";
|
|
80
|
+
parsedFnCall.members.shift();
|
|
81
|
+
}
|
|
82
|
+
const result = parseExpectCall(chain, parsedFnCall, stage);
|
|
83
|
+
if (!result)
|
|
84
|
+
return null;
|
|
71
85
|
if (typeof result === "string" && findTopMostCallExpression(node) !== node) {
|
|
72
86
|
return null;
|
|
73
87
|
}
|
|
@@ -78,7 +92,7 @@ function parse(context, node) {
|
|
|
78
92
|
}
|
|
79
93
|
return result;
|
|
80
94
|
}
|
|
81
|
-
if (chain.slice(0, chain.length - 1).some((n) => getParent(n)?.type !== "MemberExpression")) {
|
|
95
|
+
if (chain.nodes.slice(0, chain.nodes.length - 1).some((n) => getParent(n)?.type !== "MemberExpression")) {
|
|
82
96
|
return null;
|
|
83
97
|
}
|
|
84
98
|
const parent = getParent(node);
|
|
@@ -107,7 +121,7 @@ function parseFnCall(context, node) {
|
|
|
107
121
|
const call = parseFnCallWithReason(context, node);
|
|
108
122
|
return typeof call === "string" ? null : call;
|
|
109
123
|
}
|
|
110
|
-
var testHooks, VALID_CHAINS, joinChains, isSupportedAccessor, resolvePossibleAliasedGlobal, resolveToPlaywrightFn, modifiers, findModifiersAndMatcher, parseExpectCall, findTopMostCallExpression, cache, isTypeOfFnCall;
|
|
124
|
+
var testHooks, VALID_CHAINS, joinChains, isSupportedAccessor, _nodes, _leaves, _buildChain, buildChain_fn, Chain, resolvePossibleAliasedGlobal, resolveToPlaywrightFn, modifiers, findModifiersAndMatcher, parseExpectCall, findTopMostCallExpression, cache, isTypeOfFnCall;
|
|
111
125
|
var init_parseFnCall = __esm({
|
|
112
126
|
"src/utils/parseFnCall.ts"() {
|
|
113
127
|
"use strict";
|
|
@@ -168,6 +182,44 @@ var init_parseFnCall = __esm({
|
|
|
168
182
|
]);
|
|
169
183
|
joinChains = (a, b) => a && b ? [...a, ...b] : null;
|
|
170
184
|
isSupportedAccessor = (node, value) => isIdentifier(node, value) || isStringNode(node, value);
|
|
185
|
+
Chain = class {
|
|
186
|
+
constructor(node) {
|
|
187
|
+
__privateAdd(this, _buildChain);
|
|
188
|
+
__privateAdd(this, _nodes, null);
|
|
189
|
+
__privateAdd(this, _leaves, /* @__PURE__ */ new WeakSet());
|
|
190
|
+
__privateSet(this, _nodes, __privateMethod(this, _buildChain, buildChain_fn).call(this, node));
|
|
191
|
+
}
|
|
192
|
+
isLeaf(node) {
|
|
193
|
+
return __privateGet(this, _leaves).has(node);
|
|
194
|
+
}
|
|
195
|
+
get nodes() {
|
|
196
|
+
return __privateGet(this, _nodes);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
_nodes = new WeakMap();
|
|
200
|
+
_leaves = new WeakMap();
|
|
201
|
+
_buildChain = new WeakSet();
|
|
202
|
+
buildChain_fn = function(node, insideCall = false) {
|
|
203
|
+
if (isSupportedAccessor(node)) {
|
|
204
|
+
if (insideCall) {
|
|
205
|
+
__privateGet(this, _leaves).add(node);
|
|
206
|
+
}
|
|
207
|
+
return [node];
|
|
208
|
+
}
|
|
209
|
+
switch (node.type) {
|
|
210
|
+
case "TaggedTemplateExpression":
|
|
211
|
+
return __privateMethod(this, _buildChain, buildChain_fn).call(this, node.tag);
|
|
212
|
+
case "MemberExpression":
|
|
213
|
+
return joinChains(
|
|
214
|
+
__privateMethod(this, _buildChain, buildChain_fn).call(this, node.object),
|
|
215
|
+
__privateMethod(this, _buildChain, buildChain_fn).call(this, node.property, insideCall)
|
|
216
|
+
);
|
|
217
|
+
case "CallExpression":
|
|
218
|
+
return __privateMethod(this, _buildChain, buildChain_fn).call(this, node.callee, true);
|
|
219
|
+
default:
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
171
223
|
resolvePossibleAliasedGlobal = (context, global) => {
|
|
172
224
|
const globalAliases = context.settings.playwright?.globalAliases ?? {};
|
|
173
225
|
const alias = Object.entries(globalAliases).find(
|
|
@@ -185,7 +237,7 @@ var init_parseFnCall = __esm({
|
|
|
185
237
|
};
|
|
186
238
|
};
|
|
187
239
|
modifiers = /* @__PURE__ */ new Set(["not", "resolves", "rejects"]);
|
|
188
|
-
findModifiersAndMatcher = (members) => {
|
|
240
|
+
findModifiersAndMatcher = (chain, members, stage) => {
|
|
189
241
|
const modifiers2 = [];
|
|
190
242
|
for (const member of members) {
|
|
191
243
|
const name = getStringValue(member);
|
|
@@ -199,6 +251,9 @@ var init_parseFnCall = __esm({
|
|
|
199
251
|
return "modifier-unknown";
|
|
200
252
|
}
|
|
201
253
|
} else if (name !== "not") {
|
|
254
|
+
if (stage === "modifiers") {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
202
257
|
if (member.parent?.type === "MemberExpression" && member.parent.parent?.type === "CallExpression") {
|
|
203
258
|
return {
|
|
204
259
|
matcher: member,
|
|
@@ -209,12 +264,22 @@ var init_parseFnCall = __esm({
|
|
|
209
264
|
}
|
|
210
265
|
return "modifier-unknown";
|
|
211
266
|
}
|
|
267
|
+
if (chain.isLeaf(member)) {
|
|
268
|
+
stage = "matchers";
|
|
269
|
+
}
|
|
212
270
|
modifiers2.push(member);
|
|
213
271
|
}
|
|
214
272
|
return "matcher-not-found";
|
|
215
273
|
};
|
|
216
|
-
parseExpectCall = (call) => {
|
|
217
|
-
const modifiersAndMatcher = findModifiersAndMatcher(
|
|
274
|
+
parseExpectCall = (chain, call, stage) => {
|
|
275
|
+
const modifiersAndMatcher = findModifiersAndMatcher(
|
|
276
|
+
chain,
|
|
277
|
+
call.members,
|
|
278
|
+
stage
|
|
279
|
+
);
|
|
280
|
+
if (!modifiersAndMatcher) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
218
283
|
if (typeof modifiersAndMatcher === "string") {
|
|
219
284
|
return modifiersAndMatcher;
|
|
220
285
|
}
|
|
@@ -263,7 +328,7 @@ function getRawValue(node) {
|
|
|
263
328
|
return node.type === "Literal" ? node.raw : void 0;
|
|
264
329
|
}
|
|
265
330
|
function isIdentifier(node, name) {
|
|
266
|
-
return node
|
|
331
|
+
return node?.type === "Identifier" && (!name || (typeof name === "string" ? node.name === name : name.test(node.name)));
|
|
267
332
|
}
|
|
268
333
|
function isLiteral(node, type, value) {
|
|
269
334
|
return node.type === "Literal" && (value === void 0 ? typeof node.value === type : node.value === value);
|
|
@@ -278,7 +343,8 @@ function isStringNode(node, value) {
|
|
|
278
343
|
return node && (isStringLiteral(node, value) || isTemplateLiteral(node, value));
|
|
279
344
|
}
|
|
280
345
|
function isPropertyAccessor(node, name) {
|
|
281
|
-
|
|
346
|
+
const value = getStringValue(node.property);
|
|
347
|
+
return typeof name === "string" ? value === name : name.test(value);
|
|
282
348
|
}
|
|
283
349
|
function getParent(node) {
|
|
284
350
|
return node.parent;
|
|
@@ -313,7 +379,23 @@ function getNodeName(node) {
|
|
|
313
379
|
}
|
|
314
380
|
return null;
|
|
315
381
|
}
|
|
316
|
-
|
|
382
|
+
function isNodeLastAssignment(node, assignment) {
|
|
383
|
+
if (node.range && assignment.range && node.range[0] < assignment.range[1]) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
return assignment.left.type === "Identifier" && assignment.left.name === node.name;
|
|
387
|
+
}
|
|
388
|
+
function dereference(context, node) {
|
|
389
|
+
if (node?.type !== "Identifier") {
|
|
390
|
+
return node;
|
|
391
|
+
}
|
|
392
|
+
const scope = context.sourceCode.getScope(node);
|
|
393
|
+
const parents = scope.references.map((ref) => ref.identifier).map((ident) => ident.parent);
|
|
394
|
+
const decl = parents.filter(isVariableDeclarator).find((p) => p.id.type === "Identifier" && p.id.name === node.name);
|
|
395
|
+
const expr = parents.filter(isAssignmentExpression).reverse().find((assignment) => isNodeLastAssignment(node, assignment));
|
|
396
|
+
return expr?.right ?? decl?.init;
|
|
397
|
+
}
|
|
398
|
+
var isTemplateLiteral, equalityMatchers, joinNames, isVariableDeclarator, isAssignmentExpression;
|
|
317
399
|
var init_ast = __esm({
|
|
318
400
|
"src/utils/ast.ts"() {
|
|
319
401
|
"use strict";
|
|
@@ -322,6 +404,8 @@ var init_ast = __esm({
|
|
|
322
404
|
(value === void 0 || node.quasis[0].value.raw === value);
|
|
323
405
|
equalityMatchers = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
|
|
324
406
|
joinNames = (a, b) => a && b ? `${a}.${b}` : null;
|
|
407
|
+
isVariableDeclarator = (node) => node.type === "VariableDeclarator";
|
|
408
|
+
isAssignmentExpression = (node) => node.type === "AssignmentExpression";
|
|
325
409
|
}
|
|
326
410
|
});
|
|
327
411
|
|
|
@@ -2498,6 +2582,74 @@ var init_prefer_hooks_on_top = __esm({
|
|
|
2498
2582
|
}
|
|
2499
2583
|
});
|
|
2500
2584
|
|
|
2585
|
+
// src/rules/prefer-locator.ts
|
|
2586
|
+
function isSupportedMethod2(node) {
|
|
2587
|
+
if (node.callee.type !== "MemberExpression")
|
|
2588
|
+
return false;
|
|
2589
|
+
const name = getStringValue(node.callee.property);
|
|
2590
|
+
return pageMethods2.has(name) && isPageMethod(node, name);
|
|
2591
|
+
}
|
|
2592
|
+
var pageMethods2, prefer_locator_default;
|
|
2593
|
+
var init_prefer_locator = __esm({
|
|
2594
|
+
"src/rules/prefer-locator.ts"() {
|
|
2595
|
+
"use strict";
|
|
2596
|
+
init_ast();
|
|
2597
|
+
init_createRule();
|
|
2598
|
+
pageMethods2 = /* @__PURE__ */ new Set([
|
|
2599
|
+
"click",
|
|
2600
|
+
"dblclick",
|
|
2601
|
+
"dispatchEvent",
|
|
2602
|
+
"fill",
|
|
2603
|
+
"focus",
|
|
2604
|
+
"getAttribute",
|
|
2605
|
+
"hover",
|
|
2606
|
+
"innerHTML",
|
|
2607
|
+
"innerText",
|
|
2608
|
+
"inputValue",
|
|
2609
|
+
"isChecked",
|
|
2610
|
+
"isDisabled",
|
|
2611
|
+
"isEditable",
|
|
2612
|
+
"isEnabled",
|
|
2613
|
+
"isHidden",
|
|
2614
|
+
"isVisible",
|
|
2615
|
+
"press",
|
|
2616
|
+
"selectOption",
|
|
2617
|
+
"setChecked",
|
|
2618
|
+
"setInputFiles",
|
|
2619
|
+
"tap",
|
|
2620
|
+
"textContent",
|
|
2621
|
+
"uncheck"
|
|
2622
|
+
]);
|
|
2623
|
+
prefer_locator_default = createRule({
|
|
2624
|
+
create(context) {
|
|
2625
|
+
return {
|
|
2626
|
+
CallExpression(node) {
|
|
2627
|
+
if (!isSupportedMethod2(node))
|
|
2628
|
+
return;
|
|
2629
|
+
context.report({
|
|
2630
|
+
messageId: "preferLocator",
|
|
2631
|
+
node
|
|
2632
|
+
});
|
|
2633
|
+
}
|
|
2634
|
+
};
|
|
2635
|
+
},
|
|
2636
|
+
meta: {
|
|
2637
|
+
docs: {
|
|
2638
|
+
category: "Best Practices",
|
|
2639
|
+
description: "Suggest locators over page methods",
|
|
2640
|
+
recommended: false,
|
|
2641
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-locator.md"
|
|
2642
|
+
},
|
|
2643
|
+
messages: {
|
|
2644
|
+
preferLocator: "Prefer locator methods instead of page methods"
|
|
2645
|
+
},
|
|
2646
|
+
schema: [],
|
|
2647
|
+
type: "suggestion"
|
|
2648
|
+
}
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2651
|
+
});
|
|
2652
|
+
|
|
2501
2653
|
// src/rules/prefer-lowercase-title.ts
|
|
2502
2654
|
var prefer_lowercase_title_default;
|
|
2503
2655
|
var init_prefer_lowercase_title = __esm({
|
|
@@ -2602,6 +2754,121 @@ var init_prefer_lowercase_title = __esm({
|
|
|
2602
2754
|
}
|
|
2603
2755
|
});
|
|
2604
2756
|
|
|
2757
|
+
// src/rules/prefer-native-locators.ts
|
|
2758
|
+
var compilePatterns, prefer_native_locators_default;
|
|
2759
|
+
var init_prefer_native_locators = __esm({
|
|
2760
|
+
"src/rules/prefer-native-locators.ts"() {
|
|
2761
|
+
"use strict";
|
|
2762
|
+
init_ast();
|
|
2763
|
+
init_createRule();
|
|
2764
|
+
compilePatterns = ({
|
|
2765
|
+
testIdAttribute
|
|
2766
|
+
}) => {
|
|
2767
|
+
const patterns = [
|
|
2768
|
+
{
|
|
2769
|
+
attribute: "aria-label",
|
|
2770
|
+
messageId: "unexpectedLabelQuery",
|
|
2771
|
+
replacement: "getByLabel"
|
|
2772
|
+
},
|
|
2773
|
+
{
|
|
2774
|
+
attribute: "role",
|
|
2775
|
+
messageId: "unexpectedRoleQuery",
|
|
2776
|
+
replacement: "getByRole"
|
|
2777
|
+
},
|
|
2778
|
+
{
|
|
2779
|
+
attribute: "placeholder",
|
|
2780
|
+
messageId: "unexpectedPlaceholderQuery",
|
|
2781
|
+
replacement: "getByPlaceholder"
|
|
2782
|
+
},
|
|
2783
|
+
{
|
|
2784
|
+
attribute: "alt",
|
|
2785
|
+
messageId: "unexpectedAltTextQuery",
|
|
2786
|
+
replacement: "getByAltText"
|
|
2787
|
+
},
|
|
2788
|
+
{
|
|
2789
|
+
attribute: "title",
|
|
2790
|
+
messageId: "unexpectedTitleQuery",
|
|
2791
|
+
replacement: "getByTitle"
|
|
2792
|
+
},
|
|
2793
|
+
{
|
|
2794
|
+
attribute: testIdAttribute,
|
|
2795
|
+
messageId: "unexpectedTestIdQuery",
|
|
2796
|
+
replacement: "getByTestId"
|
|
2797
|
+
}
|
|
2798
|
+
];
|
|
2799
|
+
return patterns.map(({ attribute, ...pattern }) => ({
|
|
2800
|
+
...pattern,
|
|
2801
|
+
pattern: new RegExp(`^\\[${attribute}=['"]?(.+?)['"]?\\]$`)
|
|
2802
|
+
}));
|
|
2803
|
+
};
|
|
2804
|
+
prefer_native_locators_default = createRule({
|
|
2805
|
+
create(context) {
|
|
2806
|
+
const { testIdAttribute } = {
|
|
2807
|
+
testIdAttribute: "data-testid",
|
|
2808
|
+
...context.options?.[0] ?? {}
|
|
2809
|
+
};
|
|
2810
|
+
const patterns = compilePatterns({ testIdAttribute });
|
|
2811
|
+
return {
|
|
2812
|
+
CallExpression(node) {
|
|
2813
|
+
if (node.callee.type !== "MemberExpression")
|
|
2814
|
+
return;
|
|
2815
|
+
const query = getStringValue(node.arguments[0]);
|
|
2816
|
+
if (!isPageMethod(node, "locator"))
|
|
2817
|
+
return;
|
|
2818
|
+
for (const pattern of patterns) {
|
|
2819
|
+
const match = query.match(pattern.pattern);
|
|
2820
|
+
if (match) {
|
|
2821
|
+
context.report({
|
|
2822
|
+
fix(fixer) {
|
|
2823
|
+
const start = node.callee.type === "MemberExpression" ? node.callee.property.range[0] : node.range[0];
|
|
2824
|
+
const end = node.range[1];
|
|
2825
|
+
const rangeToReplace = [start, end];
|
|
2826
|
+
const newText = `${pattern.replacement}("${match[1]}")`;
|
|
2827
|
+
return fixer.replaceTextRange(rangeToReplace, newText);
|
|
2828
|
+
},
|
|
2829
|
+
messageId: pattern.messageId,
|
|
2830
|
+
node
|
|
2831
|
+
});
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
};
|
|
2837
|
+
},
|
|
2838
|
+
meta: {
|
|
2839
|
+
docs: {
|
|
2840
|
+
category: "Best Practices",
|
|
2841
|
+
description: "Prefer native locator functions",
|
|
2842
|
+
recommended: false,
|
|
2843
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-native-locators.md"
|
|
2844
|
+
},
|
|
2845
|
+
fixable: "code",
|
|
2846
|
+
messages: {
|
|
2847
|
+
unexpectedAltTextQuery: "Use getByAltText() instead",
|
|
2848
|
+
unexpectedLabelQuery: "Use getByLabel() instead",
|
|
2849
|
+
unexpectedPlaceholderQuery: "Use getByPlaceholder() instead",
|
|
2850
|
+
unexpectedRoleQuery: "Use getByRole() instead",
|
|
2851
|
+
unexpectedTestIdQuery: "Use getByTestId() instead",
|
|
2852
|
+
unexpectedTitleQuery: "Use getByTitle() instead"
|
|
2853
|
+
},
|
|
2854
|
+
schema: [
|
|
2855
|
+
{
|
|
2856
|
+
additionalProperties: false,
|
|
2857
|
+
properties: {
|
|
2858
|
+
testIdAttribute: {
|
|
2859
|
+
default: "data-testid",
|
|
2860
|
+
type: "string"
|
|
2861
|
+
}
|
|
2862
|
+
},
|
|
2863
|
+
type: "object"
|
|
2864
|
+
}
|
|
2865
|
+
],
|
|
2866
|
+
type: "suggestion"
|
|
2867
|
+
}
|
|
2868
|
+
});
|
|
2869
|
+
}
|
|
2870
|
+
});
|
|
2871
|
+
|
|
2605
2872
|
// src/rules/prefer-strict-equal.ts
|
|
2606
2873
|
var prefer_strict_equal_default;
|
|
2607
2874
|
var init_prefer_strict_equal = __esm({
|
|
@@ -2839,7 +3106,7 @@ var init_prefer_to_contain = __esm({
|
|
|
2839
3106
|
});
|
|
2840
3107
|
|
|
2841
3108
|
// src/rules/prefer-to-have-count.ts
|
|
2842
|
-
var prefer_to_have_count_default;
|
|
3109
|
+
var matchers, prefer_to_have_count_default;
|
|
2843
3110
|
var init_prefer_to_have_count = __esm({
|
|
2844
3111
|
"src/rules/prefer-to-have-count.ts"() {
|
|
2845
3112
|
"use strict";
|
|
@@ -2847,16 +3114,18 @@ var init_prefer_to_have_count = __esm({
|
|
|
2847
3114
|
init_createRule();
|
|
2848
3115
|
init_fixer();
|
|
2849
3116
|
init_parseFnCall();
|
|
3117
|
+
matchers = /* @__PURE__ */ new Set([...equalityMatchers, "toHaveLength"]);
|
|
2850
3118
|
prefer_to_have_count_default = createRule({
|
|
2851
3119
|
create(context) {
|
|
2852
3120
|
return {
|
|
2853
3121
|
CallExpression(node) {
|
|
2854
3122
|
const call = parseFnCall(context, node);
|
|
2855
|
-
if (call?.type !== "expect" || !
|
|
3123
|
+
if (call?.type !== "expect" || !matchers.has(call.matcherName)) {
|
|
2856
3124
|
return;
|
|
2857
3125
|
}
|
|
2858
|
-
const
|
|
2859
|
-
|
|
3126
|
+
const accessor = call.matcherName === "toHaveLength" ? "all" : "count";
|
|
3127
|
+
const argument = dereference(context, call.args[0]);
|
|
3128
|
+
if (argument?.type !== "AwaitExpression" || argument.argument.type !== "CallExpression" || argument.argument.callee.type !== "MemberExpression" || !isPropertyAccessor(argument.argument.callee, accessor)) {
|
|
2860
3129
|
return;
|
|
2861
3130
|
}
|
|
2862
3131
|
const callee = argument.argument.callee;
|
|
@@ -2961,23 +3230,7 @@ var init_prefer_to_have_length = __esm({
|
|
|
2961
3230
|
});
|
|
2962
3231
|
|
|
2963
3232
|
// src/rules/prefer-web-first-assertions.ts
|
|
2964
|
-
|
|
2965
|
-
if (node.range && assignment.range && node.range[0] < assignment.range[1]) {
|
|
2966
|
-
return false;
|
|
2967
|
-
}
|
|
2968
|
-
return assignment.left.type === "Identifier" && assignment.left.name === node.name;
|
|
2969
|
-
}
|
|
2970
|
-
function dereference(context, node) {
|
|
2971
|
-
if (node?.type !== "Identifier") {
|
|
2972
|
-
return node;
|
|
2973
|
-
}
|
|
2974
|
-
const scope = context.sourceCode.getScope(node);
|
|
2975
|
-
const parents = scope.references.map((ref) => ref.identifier).map((ident) => ident.parent);
|
|
2976
|
-
const decl = parents.filter(isVariableDeclarator).find((p) => p.id.type === "Identifier" && p.id.name === node.name);
|
|
2977
|
-
const expr = parents.filter(isAssignmentExpression).reverse().find((assignment) => isNodeLastAssignment(node, assignment));
|
|
2978
|
-
return expr?.right ?? decl?.init;
|
|
2979
|
-
}
|
|
2980
|
-
var methods3, supportedMatchers, isVariableDeclarator, isAssignmentExpression, prefer_web_first_assertions_default;
|
|
3233
|
+
var methods3, supportedMatchers, prefer_web_first_assertions_default;
|
|
2981
3234
|
var init_prefer_web_first_assertions = __esm({
|
|
2982
3235
|
"src/rules/prefer-web-first-assertions.ts"() {
|
|
2983
3236
|
"use strict";
|
|
@@ -3025,8 +3278,6 @@ var init_prefer_web_first_assertions = __esm({
|
|
|
3025
3278
|
"toBeTruthy",
|
|
3026
3279
|
"toBeFalsy"
|
|
3027
3280
|
]);
|
|
3028
|
-
isVariableDeclarator = (node) => node.type === "VariableDeclarator";
|
|
3029
|
-
isAssignmentExpression = (node) => node.type === "AssignmentExpression";
|
|
3030
3281
|
prefer_web_first_assertions_default = createRule({
|
|
3031
3282
|
create(context) {
|
|
3032
3283
|
return {
|
|
@@ -3892,17 +4143,19 @@ var init_valid_title = __esm({
|
|
|
3892
4143
|
const [matcher, message] = Array.isArray(matcherMaybeWithMessage) ? matcherMaybeWithMessage : [matcherMaybeWithMessage];
|
|
3893
4144
|
return [new RegExp(matcher, "u"), message];
|
|
3894
4145
|
};
|
|
3895
|
-
compileMatcherPatterns = (
|
|
3896
|
-
if (typeof
|
|
3897
|
-
const compiledMatcher = compileMatcherPattern(
|
|
4146
|
+
compileMatcherPatterns = (matchers2) => {
|
|
4147
|
+
if (typeof matchers2 === "string" || Array.isArray(matchers2)) {
|
|
4148
|
+
const compiledMatcher = compileMatcherPattern(matchers2);
|
|
3898
4149
|
return {
|
|
3899
4150
|
describe: compiledMatcher,
|
|
4151
|
+
step: compiledMatcher,
|
|
3900
4152
|
test: compiledMatcher
|
|
3901
4153
|
};
|
|
3902
4154
|
}
|
|
3903
4155
|
return {
|
|
3904
|
-
describe:
|
|
3905
|
-
|
|
4156
|
+
describe: matchers2.describe ? compileMatcherPattern(matchers2.describe) : null,
|
|
4157
|
+
step: matchers2.step ? compileMatcherPattern(matchers2.step) : null,
|
|
4158
|
+
test: matchers2.test ? compileMatcherPattern(matchers2.test) : null
|
|
3906
4159
|
};
|
|
3907
4160
|
};
|
|
3908
4161
|
MatcherAndMessageSchema = {
|
|
@@ -3919,6 +4172,7 @@ var init_valid_title = __esm({
|
|
|
3919
4172
|
disallowedWords = [],
|
|
3920
4173
|
ignoreSpaces = false,
|
|
3921
4174
|
ignoreTypeOfDescribeName = false,
|
|
4175
|
+
ignoreTypeOfStepName = true,
|
|
3922
4176
|
ignoreTypeOfTestName = false,
|
|
3923
4177
|
mustMatch,
|
|
3924
4178
|
mustNotMatch
|
|
@@ -3932,7 +4186,7 @@ var init_valid_title = __esm({
|
|
|
3932
4186
|
return {
|
|
3933
4187
|
CallExpression(node) {
|
|
3934
4188
|
const call = parseFnCall(context, node);
|
|
3935
|
-
if (call?.type !== "test" && call?.type !== "describe") {
|
|
4189
|
+
if (call?.type !== "test" && call?.type !== "describe" && call?.type !== "step") {
|
|
3936
4190
|
return;
|
|
3937
4191
|
}
|
|
3938
4192
|
const [argument] = node.arguments;
|
|
@@ -3942,7 +4196,7 @@ var init_valid_title = __esm({
|
|
|
3942
4196
|
if (argument.type === "BinaryExpression" && doesBinaryExpressionContainStringNode(argument)) {
|
|
3943
4197
|
return;
|
|
3944
4198
|
}
|
|
3945
|
-
if (!(call.type === "describe" && ignoreTypeOfDescribeName || call.type === "test" && ignoreTypeOfTestName) && argument.type !== "TemplateLiteral") {
|
|
4199
|
+
if (!(call.type === "describe" && ignoreTypeOfDescribeName || call.type === "test" && ignoreTypeOfTestName || call.type === "step" && ignoreTypeOfStepName) && argument.type !== "TemplateLiteral") {
|
|
3946
4200
|
context.report({
|
|
3947
4201
|
loc: argument.loc,
|
|
3948
4202
|
messageId: "titleMustBeString"
|
|
@@ -4056,7 +4310,7 @@ var init_valid_title = __esm({
|
|
|
4056
4310
|
additionalProperties: {
|
|
4057
4311
|
oneOf: [{ type: "string" }, MatcherAndMessageSchema]
|
|
4058
4312
|
},
|
|
4059
|
-
propertyNames: { enum: ["describe", "test"] },
|
|
4313
|
+
propertyNames: { enum: ["describe", "test", "step"] },
|
|
4060
4314
|
type: "object"
|
|
4061
4315
|
}
|
|
4062
4316
|
]
|
|
@@ -4075,6 +4329,10 @@ var init_valid_title = __esm({
|
|
|
4075
4329
|
default: false,
|
|
4076
4330
|
type: "boolean"
|
|
4077
4331
|
},
|
|
4332
|
+
ignoreTypeOfStepName: {
|
|
4333
|
+
default: true,
|
|
4334
|
+
type: "boolean"
|
|
4335
|
+
},
|
|
4078
4336
|
ignoreTypeOfTestName: {
|
|
4079
4337
|
default: false,
|
|
4080
4338
|
type: "boolean"
|
|
@@ -4124,7 +4382,9 @@ var require_src = __commonJS({
|
|
|
4124
4382
|
init_prefer_equality_matcher();
|
|
4125
4383
|
init_prefer_hooks_in_order();
|
|
4126
4384
|
init_prefer_hooks_on_top();
|
|
4385
|
+
init_prefer_locator();
|
|
4127
4386
|
init_prefer_lowercase_title();
|
|
4387
|
+
init_prefer_native_locators();
|
|
4128
4388
|
init_prefer_strict_equal();
|
|
4129
4389
|
init_prefer_to_be();
|
|
4130
4390
|
init_prefer_to_contain();
|
|
@@ -4173,7 +4433,9 @@ var require_src = __commonJS({
|
|
|
4173
4433
|
"prefer-equality-matcher": prefer_equality_matcher_default,
|
|
4174
4434
|
"prefer-hooks-in-order": prefer_hooks_in_order_default,
|
|
4175
4435
|
"prefer-hooks-on-top": prefer_hooks_on_top_default,
|
|
4436
|
+
"prefer-locator": prefer_locator_default,
|
|
4176
4437
|
"prefer-lowercase-title": prefer_lowercase_title_default,
|
|
4438
|
+
"prefer-native-locators": prefer_native_locators_default,
|
|
4177
4439
|
"prefer-strict-equal": prefer_strict_equal_default,
|
|
4178
4440
|
"prefer-to-be": prefer_to_be_default,
|
|
4179
4441
|
"prefer-to-contain": prefer_to_contain_default,
|
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": "1.
|
|
4
|
+
"version": "1.7.0",
|
|
5
5
|
"repository": "https://github.com/playwright-community/eslint-plugin-playwright",
|
|
6
6
|
"author": "Mark Skelton <mark@mskelton.dev>",
|
|
7
7
|
"packageManager": "pnpm@8.12.0",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dist"
|
|
31
31
|
],
|
|
32
32
|
"scripts": {
|
|
33
|
-
"build": "tsup src/index.ts --format cjs,esm --dts --out-dir dist",
|
|
33
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --out-dir dist --cjsInterop",
|
|
34
34
|
"lint": "eslint .",
|
|
35
35
|
"fmt": "prettier --write .",
|
|
36
36
|
"fmt:check": "prettier --check .",
|