eslint-plugin-playwright 2.2.2 → 2.4.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 +5 -4
- package/dist/index.cjs +100 -59
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -38,19 +38,19 @@ file patterns.
|
|
|
38
38
|
(**eslint.config.js**)
|
|
39
39
|
|
|
40
40
|
```javascript
|
|
41
|
+
import { defineConfig } from '@eslint/config'
|
|
41
42
|
import playwright from 'eslint-plugin-playwright'
|
|
42
43
|
|
|
43
|
-
export default [
|
|
44
|
+
export default defineConfig([
|
|
44
45
|
{
|
|
45
|
-
...playwright.configs['flat/recommended'],
|
|
46
46
|
files: ['tests/**'],
|
|
47
|
+
extends: [playwright.configs['flat/recommended']],
|
|
47
48
|
rules: {
|
|
48
|
-
...playwright.configs['flat/recommended'].rules,
|
|
49
49
|
// Customize Playwright rules
|
|
50
50
|
// ...
|
|
51
51
|
},
|
|
52
52
|
},
|
|
53
|
-
]
|
|
53
|
+
])
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
[Legacy config](https://eslint.org/docs/latest/use/configure/configuration-files)
|
|
@@ -142,6 +142,7 @@ CLI option\
|
|
|
142
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 | | | 💡 |
|
|
143
143
|
| [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 | ✅ | | |
|
|
144
144
|
| [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()` | ✅ | 🔧 | |
|
|
145
|
+
| [no-unused-locators](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-unused-locators.md) | Disallow usage of page locators that are not used | ✅ | | |
|
|
145
146
|
| [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 | ✅ | 🔧 | |
|
|
146
147
|
| [no-useless-not](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-useless-not.md) | Disallow usage of `not` matchers when a specific matcher exists | ✅ | 🔧 | |
|
|
147
148
|
| [no-wait-for-navigation](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-navigation.md) | Disallow usage of `page.waitForNavigation()` | ✅ | | 💡 |
|
package/dist/index.cjs
CHANGED
|
@@ -21,28 +21,6 @@ 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
|
-
};
|
|
46
24
|
|
|
47
25
|
// src/index.ts
|
|
48
26
|
var import_globals = __toESM(require("globals"), 1);
|
|
@@ -106,43 +84,38 @@ var VALID_CHAINS = /* @__PURE__ */ new Set([
|
|
|
106
84
|
]);
|
|
107
85
|
var joinChains = (a, b) => a && b ? [...a, ...b] : null;
|
|
108
86
|
var isSupportedAccessor = (node, value) => isIdentifier(node, value) || isStringNode(node, value);
|
|
109
|
-
var _nodes, _leaves, _buildChain, buildChain_fn;
|
|
110
87
|
var Chain = class {
|
|
88
|
+
#nodes = null;
|
|
89
|
+
#leaves = /* @__PURE__ */ new WeakSet();
|
|
111
90
|
constructor(node) {
|
|
112
|
-
|
|
113
|
-
__privateAdd(this, _nodes, null);
|
|
114
|
-
__privateAdd(this, _leaves, /* @__PURE__ */ new WeakSet());
|
|
115
|
-
__privateSet(this, _nodes, __privateMethod(this, _buildChain, buildChain_fn).call(this, node));
|
|
91
|
+
this.#nodes = this.#buildChain(node);
|
|
116
92
|
}
|
|
117
93
|
isLeaf(node) {
|
|
118
|
-
return
|
|
94
|
+
return this.#leaves.has(node);
|
|
119
95
|
}
|
|
120
96
|
get nodes() {
|
|
121
|
-
return
|
|
97
|
+
return this.#nodes;
|
|
122
98
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
99
|
+
#buildChain(node, insideCall = false) {
|
|
100
|
+
if (isSupportedAccessor(node)) {
|
|
101
|
+
if (insideCall) {
|
|
102
|
+
this.#leaves.add(node);
|
|
103
|
+
}
|
|
104
|
+
return [node];
|
|
105
|
+
}
|
|
106
|
+
switch (node.type) {
|
|
107
|
+
case "TaggedTemplateExpression":
|
|
108
|
+
return this.#buildChain(node.tag);
|
|
109
|
+
case "MemberExpression":
|
|
110
|
+
return joinChains(
|
|
111
|
+
this.#buildChain(node.object),
|
|
112
|
+
this.#buildChain(node.property, insideCall)
|
|
113
|
+
);
|
|
114
|
+
case "CallExpression":
|
|
115
|
+
return this.#buildChain(node.callee, true);
|
|
116
|
+
default:
|
|
117
|
+
return null;
|
|
131
118
|
}
|
|
132
|
-
return [node];
|
|
133
|
-
}
|
|
134
|
-
switch (node.type) {
|
|
135
|
-
case "TaggedTemplateExpression":
|
|
136
|
-
return __privateMethod(this, _buildChain, buildChain_fn).call(this, node.tag);
|
|
137
|
-
case "MemberExpression":
|
|
138
|
-
return joinChains(
|
|
139
|
-
__privateMethod(this, _buildChain, buildChain_fn).call(this, node.object),
|
|
140
|
-
__privateMethod(this, _buildChain, buildChain_fn).call(this, node.property, insideCall)
|
|
141
|
-
);
|
|
142
|
-
case "CallExpression":
|
|
143
|
-
return __privateMethod(this, _buildChain, buildChain_fn).call(this, node.callee, true);
|
|
144
|
-
default:
|
|
145
|
-
return null;
|
|
146
119
|
}
|
|
147
120
|
};
|
|
148
121
|
var resolvePossibleAliasedGlobal = (context, global) => {
|
|
@@ -462,8 +435,12 @@ var expect_expect_default = createRule({
|
|
|
462
435
|
create(context) {
|
|
463
436
|
const options = {
|
|
464
437
|
assertFunctionNames: [],
|
|
438
|
+
assertFunctionPatterns: [],
|
|
465
439
|
...context.options?.[0] ?? {}
|
|
466
440
|
};
|
|
441
|
+
const patterns = options.assertFunctionPatterns.map(
|
|
442
|
+
(pattern) => new RegExp(pattern)
|
|
443
|
+
);
|
|
467
444
|
const unchecked = [];
|
|
468
445
|
function checkExpressions(nodes) {
|
|
469
446
|
for (const node of nodes) {
|
|
@@ -474,12 +451,21 @@ var expect_expect_default = createRule({
|
|
|
474
451
|
}
|
|
475
452
|
}
|
|
476
453
|
}
|
|
454
|
+
function matches(node) {
|
|
455
|
+
if (options.assertFunctionNames.some((name) => dig(node.callee, name))) {
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
if (patterns.some((pattern) => dig(node.callee, pattern))) {
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
477
463
|
return {
|
|
478
464
|
CallExpression(node) {
|
|
479
465
|
const call = parseFnCall(context, node);
|
|
480
466
|
if (call?.type === "test") {
|
|
481
467
|
unchecked.push(node);
|
|
482
|
-
} else if (call?.type === "expect" ||
|
|
468
|
+
} else if (call?.type === "expect" || matches(node)) {
|
|
483
469
|
const ancestors = context.sourceCode.getAncestors(node);
|
|
484
470
|
checkExpressions(ancestors);
|
|
485
471
|
}
|
|
@@ -508,6 +494,10 @@ var expect_expect_default = createRule({
|
|
|
508
494
|
assertFunctionNames: {
|
|
509
495
|
items: [{ type: "string" }],
|
|
510
496
|
type: "array"
|
|
497
|
+
},
|
|
498
|
+
assertFunctionPatterns: {
|
|
499
|
+
items: [{ type: "string" }],
|
|
500
|
+
type: "array"
|
|
511
501
|
}
|
|
512
502
|
},
|
|
513
503
|
type: "object"
|
|
@@ -720,14 +710,17 @@ var missing_playwright_await_default = createRule({
|
|
|
720
710
|
// Add any custom matchers to the set
|
|
721
711
|
...options.customMatchers || []
|
|
722
712
|
]);
|
|
723
|
-
function checkValidity(node) {
|
|
713
|
+
function checkValidity(node, visited) {
|
|
724
714
|
const parent = getParent(node);
|
|
725
715
|
if (!parent)
|
|
726
716
|
return false;
|
|
717
|
+
if (visited.has(parent))
|
|
718
|
+
return false;
|
|
719
|
+
visited.add(parent);
|
|
727
720
|
if (validTypes.has(parent.type))
|
|
728
721
|
return true;
|
|
729
722
|
if (parent.type === "ArrayExpression") {
|
|
730
|
-
return checkValidity(parent);
|
|
723
|
+
return checkValidity(parent, visited);
|
|
731
724
|
}
|
|
732
725
|
if (parent.type === "CallExpression" && parent.callee.type === "MemberExpression" && isIdentifier(parent.callee.object, "Promise") && isIdentifier(parent.callee.property, "all")) {
|
|
733
726
|
return true;
|
|
@@ -736,9 +729,11 @@ var missing_playwright_await_default = createRule({
|
|
|
736
729
|
const scope = context.sourceCode.getScope(parent.parent);
|
|
737
730
|
for (const ref of scope.references) {
|
|
738
731
|
const refParent = ref.identifier.parent;
|
|
732
|
+
if (visited.has(refParent))
|
|
733
|
+
continue;
|
|
739
734
|
if (validTypes.has(refParent.type))
|
|
740
735
|
return true;
|
|
741
|
-
if (checkValidity(refParent))
|
|
736
|
+
if (checkValidity(refParent, visited))
|
|
742
737
|
return true;
|
|
743
738
|
}
|
|
744
739
|
}
|
|
@@ -750,7 +745,7 @@ var missing_playwright_await_default = createRule({
|
|
|
750
745
|
if (call?.type !== "step" && call?.type !== "expect")
|
|
751
746
|
return;
|
|
752
747
|
const result = getCallType(call, awaitableMatchers);
|
|
753
|
-
const isValid = result ? checkValidity(node) : false;
|
|
748
|
+
const isValid = result ? checkValidity(node, /* @__PURE__ */ new Set()) : false;
|
|
754
749
|
if (result && !isValid) {
|
|
755
750
|
context.report({
|
|
756
751
|
data: result.data,
|
|
@@ -982,7 +977,7 @@ var no_duplicate_hooks_default = createRule({
|
|
|
982
977
|
}
|
|
983
978
|
const currentLayer = hookContexts[hookContexts.length - 1];
|
|
984
979
|
const name = node.callee.type === "MemberExpression" ? getStringValue(node.callee.property) : "";
|
|
985
|
-
currentLayer[name]
|
|
980
|
+
currentLayer[name] ||= 0;
|
|
986
981
|
currentLayer[name] += 1;
|
|
987
982
|
if (currentLayer[name] > 1) {
|
|
988
983
|
context.report({
|
|
@@ -1729,13 +1724,16 @@ var no_standalone_expect_default = createRule({
|
|
|
1729
1724
|
if (call?.type === "hook") {
|
|
1730
1725
|
callStack.push("hook");
|
|
1731
1726
|
}
|
|
1727
|
+
if (node.callee.type === "MemberExpression" && isPropertyAccessor(node.callee, "extend")) {
|
|
1728
|
+
callStack.push("fixture");
|
|
1729
|
+
}
|
|
1732
1730
|
if (node.callee.type === "TaggedTemplateExpression") {
|
|
1733
1731
|
callStack.push("template");
|
|
1734
1732
|
}
|
|
1735
1733
|
},
|
|
1736
1734
|
"CallExpression:exit"(node) {
|
|
1737
1735
|
const top = callStack.at(-1);
|
|
1738
|
-
if (top === "test" && isTypeOfFnCall(context, node, ["test"]) && node.callee.type !== "MemberExpression" || top === "template" && node.callee.type === "TaggedTemplateExpression") {
|
|
1736
|
+
if (top === "test" && isTypeOfFnCall(context, node, ["test"]) && node.callee.type !== "MemberExpression" || top === "template" && node.callee.type === "TaggedTemplateExpression" || top === "fixture" && node.callee.type === "MemberExpression" && isPropertyAccessor(node.callee, "extend")) {
|
|
1739
1737
|
callStack.pop();
|
|
1740
1738
|
}
|
|
1741
1739
|
}
|
|
@@ -1856,6 +1854,35 @@ var no_unsafe_references_default = createRule({
|
|
|
1856
1854
|
}
|
|
1857
1855
|
});
|
|
1858
1856
|
|
|
1857
|
+
// src/rules/no-unused-locators.ts
|
|
1858
|
+
var LOCATOR_REGEX = /locator|getBy(Role|Text|Label|Placeholder|AltText|Title|TestId)/;
|
|
1859
|
+
var no_unused_locators_default = createRule({
|
|
1860
|
+
create(context) {
|
|
1861
|
+
return {
|
|
1862
|
+
CallExpression(node) {
|
|
1863
|
+
if (!isPageMethod(node, LOCATOR_REGEX)) {
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
if (node.parent.type === "ExpressionStatement" || node.parent.type === "AwaitExpression") {
|
|
1867
|
+
context.report({ messageId: "noUnusedLocator", node });
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1871
|
+
},
|
|
1872
|
+
meta: {
|
|
1873
|
+
docs: {
|
|
1874
|
+
category: "Possible Errors",
|
|
1875
|
+
description: `Disallow usage of page locators that are not used`,
|
|
1876
|
+
recommended: true,
|
|
1877
|
+
url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-unused-locators.md"
|
|
1878
|
+
},
|
|
1879
|
+
messages: {
|
|
1880
|
+
noUnusedLocator: "Unused locator"
|
|
1881
|
+
},
|
|
1882
|
+
type: "problem"
|
|
1883
|
+
}
|
|
1884
|
+
});
|
|
1885
|
+
|
|
1859
1886
|
// src/rules/no-useless-await.ts
|
|
1860
1887
|
var locatorMethods = /* @__PURE__ */ new Set([
|
|
1861
1888
|
"and",
|
|
@@ -3857,6 +3884,9 @@ var valid_test_tags_default = createRule({
|
|
|
3857
3884
|
);
|
|
3858
3885
|
}
|
|
3859
3886
|
}
|
|
3887
|
+
const extractTagsFromTitle = (title) => {
|
|
3888
|
+
return title.match(/@[\S]+/g) || [];
|
|
3889
|
+
};
|
|
3860
3890
|
const validateTag = (tag, node) => {
|
|
3861
3891
|
if (!tag.startsWith("@")) {
|
|
3862
3892
|
context.report({
|
|
@@ -3899,6 +3929,15 @@ var valid_test_tags_default = createRule({
|
|
|
3899
3929
|
const { type } = call;
|
|
3900
3930
|
if (type !== "test" && type !== "describe" && type !== "step")
|
|
3901
3931
|
return;
|
|
3932
|
+
if (node.arguments.length > 0) {
|
|
3933
|
+
const titleArg = node.arguments[0];
|
|
3934
|
+
if (titleArg && titleArg.type === "Literal" && typeof titleArg.value === "string") {
|
|
3935
|
+
const titleTags = extractTagsFromTitle(titleArg.value);
|
|
3936
|
+
for (const tag of titleTags) {
|
|
3937
|
+
validateTag(tag, node);
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3902
3941
|
if (node.arguments.length < 2)
|
|
3903
3942
|
return;
|
|
3904
3943
|
const optionsArg = node.arguments[1];
|
|
@@ -3938,7 +3977,7 @@ var valid_test_tags_default = createRule({
|
|
|
3938
3977
|
},
|
|
3939
3978
|
meta: {
|
|
3940
3979
|
docs: {
|
|
3941
|
-
description: "Enforce valid tag format in Playwright test blocks",
|
|
3980
|
+
description: "Enforce valid tag format in Playwright test blocks and titles",
|
|
3942
3981
|
recommended: true
|
|
3943
3982
|
},
|
|
3944
3983
|
messages: {
|
|
@@ -4231,6 +4270,7 @@ var index = {
|
|
|
4231
4270
|
"no-slowed-test": no_slowed_test_default,
|
|
4232
4271
|
"no-standalone-expect": no_standalone_expect_default,
|
|
4233
4272
|
"no-unsafe-references": no_unsafe_references_default,
|
|
4273
|
+
"no-unused-locators": no_unused_locators_default,
|
|
4234
4274
|
"no-useless-await": no_useless_await_default,
|
|
4235
4275
|
"no-useless-not": no_useless_not_default,
|
|
4236
4276
|
"no-wait-for-navigation": no_wait_for_navigation_default,
|
|
@@ -4278,6 +4318,7 @@ var sharedConfig = {
|
|
|
4278
4318
|
"playwright/no-skipped-test": "warn",
|
|
4279
4319
|
"playwright/no-standalone-expect": "error",
|
|
4280
4320
|
"playwright/no-unsafe-references": "error",
|
|
4321
|
+
"playwright/no-unused-locators": "error",
|
|
4281
4322
|
"playwright/no-useless-await": "warn",
|
|
4282
4323
|
"playwright/no-useless-not": "warn",
|
|
4283
4324
|
"playwright/no-wait-for-navigation": "error",
|
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": "2.
|
|
4
|
+
"version": "2.4.0",
|
|
5
5
|
"repository": "https://github.com/playwright-community/eslint-plugin-playwright",
|
|
6
6
|
"author": "Mark Skelton <mark@mskelton.dev>",
|
|
7
7
|
"contributors": [
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
],
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"engines": {
|
|
12
|
-
"node": ">=16.
|
|
12
|
+
"node": ">=16.9.0"
|
|
13
13
|
},
|
|
14
14
|
"type": "module",
|
|
15
15
|
"types": "./index.d.ts",
|
|
@@ -30,6 +30,6 @@
|
|
|
30
30
|
"eslint": ">=8.40.0"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"globals": "^
|
|
33
|
+
"globals": "^16.4.0"
|
|
34
34
|
}
|
|
35
35
|
}
|