eslint-plugin-playwright 2.2.1 → 2.3.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 CHANGED
@@ -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
- __privateAdd(this, _buildChain);
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 __privateGet(this, _leaves).has(node);
94
+ return this.#leaves.has(node);
119
95
  }
120
96
  get nodes() {
121
- return __privateGet(this, _nodes);
97
+ return this.#nodes;
122
98
  }
123
- };
124
- _nodes = new WeakMap();
125
- _leaves = new WeakMap();
126
- _buildChain = new WeakSet();
127
- buildChain_fn = function(node, insideCall = false) {
128
- if (isSupportedAccessor(node)) {
129
- if (insideCall) {
130
- __privateGet(this, _leaves).add(node);
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) => {
@@ -982,7 +955,7 @@ var no_duplicate_hooks_default = createRule({
982
955
  }
983
956
  const currentLayer = hookContexts[hookContexts.length - 1];
984
957
  const name = node.callee.type === "MemberExpression" ? getStringValue(node.callee.property) : "";
985
- currentLayer[name] || (currentLayer[name] = 0);
958
+ currentLayer[name] ||= 0;
986
959
  currentLayer[name] += 1;
987
960
  if (currentLayer[name] > 1) {
988
961
  context.report({
@@ -1856,6 +1829,35 @@ var no_unsafe_references_default = createRule({
1856
1829
  }
1857
1830
  });
1858
1831
 
1832
+ // src/rules/no-unused-locators.ts
1833
+ var LOCATOR_REGEX = /locator|getBy(Role|Text|Label|Placeholder|AltText|Title|TestId)/;
1834
+ var no_unused_locators_default = createRule({
1835
+ create(context) {
1836
+ return {
1837
+ CallExpression(node) {
1838
+ if (!isPageMethod(node, LOCATOR_REGEX)) {
1839
+ return;
1840
+ }
1841
+ if (node.parent.type === "ExpressionStatement" || node.parent.type === "AwaitExpression") {
1842
+ context.report({ messageId: "noUnusedLocator", node });
1843
+ }
1844
+ }
1845
+ };
1846
+ },
1847
+ meta: {
1848
+ docs: {
1849
+ category: "Possible Errors",
1850
+ description: `Disallow usage of page locators that are not used`,
1851
+ recommended: true,
1852
+ url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-unused-locators.md"
1853
+ },
1854
+ messages: {
1855
+ noUnusedLocator: "Unused locator"
1856
+ },
1857
+ type: "problem"
1858
+ }
1859
+ });
1860
+
1859
1861
  // src/rules/no-useless-await.ts
1860
1862
  var locatorMethods = /* @__PURE__ */ new Set([
1861
1863
  "and",
@@ -3083,7 +3085,7 @@ var prefer_web_first_assertions_default = createRule({
3083
3085
  return;
3084
3086
  const method = getStringValue(call.callee.property);
3085
3087
  const methodConfig = methods3[method];
3086
- if (!methodConfig)
3088
+ if (!Object.hasOwn(methods3, method))
3087
3089
  return;
3088
3090
  const notModifier = fnCall.modifiers.find(
3089
3091
  (mod) => getStringValue(mod) === "not"
@@ -3857,6 +3859,9 @@ var valid_test_tags_default = createRule({
3857
3859
  );
3858
3860
  }
3859
3861
  }
3862
+ const extractTagsFromTitle = (title) => {
3863
+ return title.match(/@[\S]+/g) || [];
3864
+ };
3860
3865
  const validateTag = (tag, node) => {
3861
3866
  if (!tag.startsWith("@")) {
3862
3867
  context.report({
@@ -3899,6 +3904,15 @@ var valid_test_tags_default = createRule({
3899
3904
  const { type } = call;
3900
3905
  if (type !== "test" && type !== "describe" && type !== "step")
3901
3906
  return;
3907
+ if (node.arguments.length > 0) {
3908
+ const titleArg = node.arguments[0];
3909
+ if (titleArg && titleArg.type === "Literal" && typeof titleArg.value === "string") {
3910
+ const titleTags = extractTagsFromTitle(titleArg.value);
3911
+ for (const tag of titleTags) {
3912
+ validateTag(tag, node);
3913
+ }
3914
+ }
3915
+ }
3902
3916
  if (node.arguments.length < 2)
3903
3917
  return;
3904
3918
  const optionsArg = node.arguments[1];
@@ -3938,7 +3952,7 @@ var valid_test_tags_default = createRule({
3938
3952
  },
3939
3953
  meta: {
3940
3954
  docs: {
3941
- description: "Enforce valid tag format in Playwright test blocks",
3955
+ description: "Enforce valid tag format in Playwright test blocks and titles",
3942
3956
  recommended: true
3943
3957
  },
3944
3958
  messages: {
@@ -3955,7 +3969,11 @@ var valid_test_tags_default = createRule({
3955
3969
  items: {
3956
3970
  oneOf: [
3957
3971
  { type: "string" },
3958
- { properties: { source: { type: "string" } }, type: "object" }
3972
+ {
3973
+ additionalProperties: false,
3974
+ properties: { source: { type: "string" } },
3975
+ type: "object"
3976
+ }
3959
3977
  ]
3960
3978
  },
3961
3979
  type: "array"
@@ -3964,7 +3982,11 @@ var valid_test_tags_default = createRule({
3964
3982
  items: {
3965
3983
  oneOf: [
3966
3984
  { type: "string" },
3967
- { properties: { source: { type: "string" } }, type: "object" }
3985
+ {
3986
+ additionalProperties: false,
3987
+ properties: { source: { type: "string" } },
3988
+ type: "object"
3989
+ }
3968
3990
  ]
3969
3991
  },
3970
3992
  type: "array"
@@ -4223,6 +4245,7 @@ var index = {
4223
4245
  "no-slowed-test": no_slowed_test_default,
4224
4246
  "no-standalone-expect": no_standalone_expect_default,
4225
4247
  "no-unsafe-references": no_unsafe_references_default,
4248
+ "no-unused-locators": no_unused_locators_default,
4226
4249
  "no-useless-await": no_useless_await_default,
4227
4250
  "no-useless-not": no_useless_not_default,
4228
4251
  "no-wait-for-navigation": no_wait_for_navigation_default,
@@ -4270,6 +4293,7 @@ var sharedConfig = {
4270
4293
  "playwright/no-skipped-test": "warn",
4271
4294
  "playwright/no-standalone-expect": "error",
4272
4295
  "playwright/no-unsafe-references": "error",
4296
+ "playwright/no-unused-locators": "error",
4273
4297
  "playwright/no-useless-await": "warn",
4274
4298
  "playwright/no-useless-not": "warn",
4275
4299
  "playwright/no-wait-for-navigation": "error",
@@ -4279,6 +4303,7 @@ var sharedConfig = {
4279
4303
  "playwright/valid-describe-callback": "error",
4280
4304
  "playwright/valid-expect": "error",
4281
4305
  "playwright/valid-expect-in-promise": "error",
4306
+ "playwright/valid-test-tags": "error",
4282
4307
  "playwright/valid-title": "error"
4283
4308
  }
4284
4309
  };
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.2.1",
4
+ "version": "2.3.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.6.0"
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": "^13.23.0"
33
+ "globals": "^16.4.0"
34
34
  }
35
35
  }