eslint-plugin-playwright 2.9.0 → 2.10.1

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.
Files changed (2) hide show
  1. package/dist/index.cjs +175 -21
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -392,8 +392,21 @@ function findParent(node, type) {
392
392
  function dig(node, identifier) {
393
393
  return node.type === "MemberExpression" ? dig(node.property, identifier) : node.type === "CallExpression" ? dig(node.callee, identifier) : node.type === "Identifier" ? isIdentifier(node, identifier) : false;
394
394
  }
395
+ var pageFrameFullPattern = /(^(page|frame)|(Page|Frame)$)/;
396
+ var pageFramePrefixPattern = /^(page|frame)/;
395
397
  function isPageMethod(node, name) {
396
- return node.callee.type === "MemberExpression" && dig(node.callee.object, /(^(page|frame)|(Page|Frame)$)/) && isPropertyAccessor(node.callee, name);
398
+ if (node.callee.type !== "MemberExpression") {
399
+ return false;
400
+ }
401
+ if (!isPropertyAccessor(node.callee, name)) {
402
+ return false;
403
+ }
404
+ const obj = node.callee.object;
405
+ if (obj.type === "MemberExpression") {
406
+ const pattern = obj.object.type === "ThisExpression" ? pageFrameFullPattern : pageFramePrefixPattern;
407
+ return isIdentifier(obj.property, pattern);
408
+ }
409
+ return dig(obj, pageFrameFullPattern);
397
410
  }
398
411
  function isFunction(node) {
399
412
  return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
@@ -849,6 +862,9 @@ var max_nested_describe_default = createRule({
849
862
 
850
863
  // src/rules/missing-playwright-await.ts
851
864
  var validTypes = /* @__PURE__ */ new Set(["AwaitExpression", "ReturnStatement", "ArrowFunctionExpression"]);
865
+ function isArrayLike(node) {
866
+ return node.type === "ArrayExpression" || node.type === "NewExpression" && isIdentifier(node.callee, "Array") || node.type === "CallExpression" && node.callee.type === "MemberExpression" && isIdentifier(node.callee.object, "Array");
867
+ }
852
868
  var waitForMethods = [
853
869
  "waitForConsoleMessage",
854
870
  "waitForDownload",
@@ -861,6 +877,105 @@ var waitForMethods = [
861
877
  "waitForWebSocket"
862
878
  ];
863
879
  var waitForMethodsRegex = new RegExp(`^(${waitForMethods.join("|")})$`);
880
+ var pageMethods = /* @__PURE__ */ new Set([
881
+ "addInitScript",
882
+ "addScriptTag",
883
+ "addStyleTag",
884
+ "bringToFront",
885
+ "check",
886
+ "click",
887
+ "close",
888
+ "dblclick",
889
+ "dispatchEvent",
890
+ "dragAndDrop",
891
+ "emulateMedia",
892
+ "evaluate",
893
+ "evaluateHandle",
894
+ "exposeBinding",
895
+ "exposeFunction",
896
+ "fill",
897
+ "focus",
898
+ "getAttribute",
899
+ "goBack",
900
+ "goForward",
901
+ "goto",
902
+ "hover",
903
+ "innerHTML",
904
+ "innerText",
905
+ "inputValue",
906
+ "isChecked",
907
+ "isDisabled",
908
+ "isEditable",
909
+ "isEnabled",
910
+ "isHidden",
911
+ "isVisible",
912
+ "pdf",
913
+ "press",
914
+ "reload",
915
+ "route",
916
+ "routeFromHAR",
917
+ "screenshot",
918
+ "selectOption",
919
+ "setBypassCSP",
920
+ "setContent",
921
+ "setChecked",
922
+ "setExtraHTTPHeaders",
923
+ "setInputFiles",
924
+ "setViewportSize",
925
+ "tap",
926
+ "textContent",
927
+ "title",
928
+ "type",
929
+ "uncheck",
930
+ "unroute",
931
+ "unrouteAll",
932
+ "waitForLoadState",
933
+ "waitForTimeout",
934
+ "waitForURL"
935
+ ]);
936
+ var locatorMethods = /* @__PURE__ */ new Set([
937
+ "all",
938
+ "allInnerTexts",
939
+ "allTextContents",
940
+ "blur",
941
+ "boundingBox",
942
+ "check",
943
+ "clear",
944
+ "click",
945
+ "count",
946
+ "dblclick",
947
+ "dispatchEvent",
948
+ "dragTo",
949
+ "evaluate",
950
+ "evaluateAll",
951
+ "evaluateHandle",
952
+ "fill",
953
+ "focus",
954
+ "getAttribute",
955
+ "hover",
956
+ "innerHTML",
957
+ "innerText",
958
+ "inputValue",
959
+ "isChecked",
960
+ "isDisabled",
961
+ "isEditable",
962
+ "isEnabled",
963
+ "isHidden",
964
+ "isVisible",
965
+ "press",
966
+ "pressSequentially",
967
+ "screenshot",
968
+ "scrollIntoViewIfNeeded",
969
+ "selectOption",
970
+ "selectText",
971
+ "setChecked",
972
+ "setInputFiles",
973
+ "tap",
974
+ "textContent",
975
+ "type",
976
+ "uncheck",
977
+ "waitFor"
978
+ ]);
864
979
  var expectPlaywrightMatchers = [
865
980
  "toBeChecked",
866
981
  "toBeDisabled",
@@ -916,14 +1031,18 @@ function getReportNode(node) {
916
1031
  }
917
1032
  function getCallType(call, awaitableMatchers) {
918
1033
  if (call.type === "step") {
919
- return { messageId: "testStep", node: call.head.node };
1034
+ return {
1035
+ data: { name: "test.step" },
1036
+ messageId: "missingAwait",
1037
+ node: call.head.node
1038
+ };
920
1039
  }
921
1040
  if (call.type === "expect") {
922
1041
  const isPoll = call.modifiers.some((m) => getStringValue(m) === "poll");
923
1042
  if (isPoll || awaitableMatchers.has(call.matcherName)) {
924
1043
  return {
925
- data: { matcherName: call.matcherName },
926
- messageId: isPoll ? "expectPoll" : "expect",
1044
+ data: { name: isPoll ? "expect.poll" : call.matcherName },
1045
+ messageId: "missingAwait",
927
1046
  node: call.head.node
928
1047
  };
929
1048
  }
@@ -932,6 +1051,7 @@ function getCallType(call, awaitableMatchers) {
932
1051
  var missing_playwright_await_default = createRule({
933
1052
  create(context) {
934
1053
  const options = context.options[0] || {};
1054
+ const includePageLocatorMethods = !!options.includePageLocatorMethods;
935
1055
  const awaitableMatchers = /* @__PURE__ */ new Set([
936
1056
  ...expectPlaywrightMatchers,
937
1057
  ...playwrightTestMatchers,
@@ -992,9 +1112,18 @@ var missing_playwright_await_default = createRule({
992
1112
  if (parent.type === "SpreadElement") {
993
1113
  return checkValidity(parent, visited);
994
1114
  }
995
- if (parent.type === "CallExpression" && parent.callee.type === "MemberExpression" && isIdentifier(parent.callee.object, "Promise") && isIdentifier(parent.callee.property, "all")) {
1115
+ if (parent.type === "CallExpression" && parent.callee.type === "MemberExpression" && isIdentifier(parent.callee.object, "Promise") && isIdentifier(parent.callee.property, /^(all|allSettled|race|any)$/)) {
996
1116
  return true;
997
1117
  }
1118
+ if (parent.type === "MemberExpression" && parent.object === node && getStringValue(parent.property) === "resolves" && node.type === "CallExpression" && isIdentifier(node.callee, "expect")) {
1119
+ return checkValidity(parent, visited);
1120
+ }
1121
+ if (parent.type === "MemberExpression" && parent.object === node) {
1122
+ return checkValidity(parent, visited);
1123
+ }
1124
+ if (parent.type === "CallExpression" && parent.callee === node) {
1125
+ return checkValidity(parent, visited);
1126
+ }
998
1127
  if (parent.type === "VariableDeclarator") {
999
1128
  return isVariableConsumed(parent, checkValidity, validTypes, visited);
1000
1129
  }
@@ -1006,13 +1135,27 @@ var missing_playwright_await_default = createRule({
1006
1135
  if (!checkValidity(node, /* @__PURE__ */ new Set())) {
1007
1136
  const methodName = getStringValue(node.callee.property);
1008
1137
  context.report({
1009
- data: { methodName },
1010
- messageId: "waitFor",
1138
+ data: { name: methodName },
1139
+ messageId: "missingAwait",
1011
1140
  node
1012
1141
  });
1013
1142
  }
1014
1143
  return;
1015
1144
  }
1145
+ if (includePageLocatorMethods && node.callee.type === "MemberExpression") {
1146
+ const methodName = getStringValue(node.callee.property);
1147
+ const isPlaywrightMethod = !isArrayLike(node.callee.object) && (locatorMethods.has(methodName) || pageMethods.has(methodName) && isPageMethod(node, methodName));
1148
+ if (isPlaywrightMethod) {
1149
+ if (!checkValidity(node, /* @__PURE__ */ new Set())) {
1150
+ context.report({
1151
+ data: { name: methodName },
1152
+ messageId: "missingAwait",
1153
+ node
1154
+ });
1155
+ }
1156
+ return;
1157
+ }
1158
+ }
1016
1159
  const call = parseFnCall(context, node);
1017
1160
  if (call?.type !== "step" && call?.type !== "expect") {
1018
1161
  return;
@@ -1038,10 +1181,7 @@ var missing_playwright_await_default = createRule({
1038
1181
  },
1039
1182
  fixable: "code",
1040
1183
  messages: {
1041
- expect: "'{{matcherName}}' must be awaited or returned.",
1042
- expectPoll: "'expect.poll' matchers must be awaited or returned.",
1043
- testStep: "'test.step' must be awaited or returned.",
1044
- waitFor: "'{{methodName}}' must be awaited or returned."
1184
+ missingAwait: "'{{name}}' must be awaited or returned."
1045
1185
  },
1046
1186
  schema: [
1047
1187
  {
@@ -1050,6 +1190,9 @@ var missing_playwright_await_default = createRule({
1050
1190
  customMatchers: {
1051
1191
  items: { type: "string" },
1052
1192
  type: "array"
1193
+ },
1194
+ includePageLocatorMethods: {
1195
+ type: "boolean"
1053
1196
  }
1054
1197
  },
1055
1198
  type: "object"
@@ -1962,11 +2105,15 @@ var no_skipped_test_default = createRule({
1962
2105
  CallExpression(node) {
1963
2106
  const options = context.options[0] || {};
1964
2107
  const allowConditional = !!options.allowConditional;
2108
+ const disallowFixme = !!options.disallowFixme;
1965
2109
  const call = parseFnCall(context, node);
1966
2110
  if (call?.group !== "test" && call?.group !== "describe" && call?.group !== "step") {
1967
2111
  return;
1968
2112
  }
1969
- const skipNode = call.members.find((s) => getStringValue(s) === "skip");
2113
+ const skipNode = call.members.find((member) => {
2114
+ const value = getStringValue(member);
2115
+ return value === "skip" || disallowFixme && value === "fixme";
2116
+ });
1970
2117
  if (!skipNode) {
1971
2118
  return;
1972
2119
  }
@@ -1974,18 +2121,21 @@ var no_skipped_test_default = createRule({
1974
2121
  if (isStandalone && allowConditional && (node.arguments.length !== 0 || findParent(node, "BlockStatement")?.parent?.type === "IfStatement" || findParent(node, "SwitchCase") !== void 0)) {
1975
2122
  return;
1976
2123
  }
2124
+ const annotation = getStringValue(skipNode);
1977
2125
  context.report({
2126
+ data: { annotation },
1978
2127
  messageId: "noSkippedTest",
1979
2128
  node: isStandalone ? node : skipNode,
1980
2129
  suggest: [
1981
2130
  {
2131
+ data: { annotation: getStringValue(skipNode) },
1982
2132
  fix: (fixer) => {
1983
2133
  return isStandalone ? fixer.remove(node.parent) : fixer.removeRange([
1984
2134
  skipNode.range[0] - 1,
1985
2135
  skipNode.range[1] + Number(skipNode.type !== "Identifier")
1986
2136
  ]);
1987
2137
  },
1988
- messageId: "removeSkippedTestAnnotation"
2138
+ messageId: "removeAnnotation"
1989
2139
  }
1990
2140
  ]
1991
2141
  });
@@ -2000,8 +2150,8 @@ var no_skipped_test_default = createRule({
2000
2150
  },
2001
2151
  hasSuggestions: true,
2002
2152
  messages: {
2003
- noSkippedTest: "Unexpected use of the `.skip()` annotation.",
2004
- removeSkippedTestAnnotation: "Remove the `.skip()` annotation."
2153
+ noSkippedTest: "Unexpected use of the `.{{annotation}}()` annotation.",
2154
+ removeAnnotation: "Remove the `.{{annotation}}()` annotation."
2005
2155
  },
2006
2156
  schema: [
2007
2157
  {
@@ -2010,6 +2160,10 @@ var no_skipped_test_default = createRule({
2010
2160
  allowConditional: {
2011
2161
  default: false,
2012
2162
  type: "boolean"
2163
+ },
2164
+ disallowFixme: {
2165
+ default: false,
2166
+ type: "boolean"
2013
2167
  }
2014
2168
  },
2015
2169
  type: "object"
@@ -2306,7 +2460,7 @@ var no_unused_locators_default = createRule({
2306
2460
  });
2307
2461
 
2308
2462
  // src/rules/no-useless-await.ts
2309
- var locatorMethods = /* @__PURE__ */ new Set([
2463
+ var locatorMethods2 = /* @__PURE__ */ new Set([
2310
2464
  "and",
2311
2465
  "first",
2312
2466
  "getByAltText",
@@ -2321,7 +2475,7 @@ var locatorMethods = /* @__PURE__ */ new Set([
2321
2475
  "nth",
2322
2476
  "or"
2323
2477
  ]);
2324
- var pageMethods = /* @__PURE__ */ new Set([
2478
+ var pageMethods2 = /* @__PURE__ */ new Set([
2325
2479
  "childFrames",
2326
2480
  "frame",
2327
2481
  "frameLocator",
@@ -2370,7 +2524,7 @@ function isSupportedMethod(node) {
2370
2524
  return false;
2371
2525
  }
2372
2526
  const name = getStringValue(node.callee.property);
2373
- return locatorMethods.has(name) || pageMethods.has(name) && isPageMethod(node, name);
2527
+ return locatorMethods2.has(name) || pageMethods2.has(name) && isPageMethod(node, name);
2374
2528
  }
2375
2529
  var no_useless_await_default = createRule({
2376
2530
  create(context) {
@@ -2881,7 +3035,7 @@ var prefer_hooks_on_top_default = createRule({
2881
3035
  });
2882
3036
 
2883
3037
  // src/rules/prefer-locator.ts
2884
- var pageMethods2 = /* @__PURE__ */ new Set([
3038
+ var pageMethods3 = /* @__PURE__ */ new Set([
2885
3039
  "click",
2886
3040
  "dblclick",
2887
3041
  "dispatchEvent",
@@ -2911,7 +3065,7 @@ function isSupportedMethod2(node) {
2911
3065
  return false;
2912
3066
  }
2913
3067
  const name = getStringValue(node.callee.property);
2914
- return pageMethods2.has(name) && isPageMethod(node, name);
3068
+ return pageMethods3.has(name) && isPageMethod(node, name);
2915
3069
  }
2916
3070
  var prefer_locator_default = createRule({
2917
3071
  create(context) {
@@ -4015,7 +4169,7 @@ var isPromiseMethodThatUsesValue = (node, identifier) => {
4015
4169
  }
4016
4170
  if (node.argument.type === "CallExpression" && node.argument.arguments.length > 0) {
4017
4171
  const nodeName = getNodeName(node.argument);
4018
- if (["Promise.all", "Promise.allSettled"].includes(nodeName)) {
4172
+ if (/^Promise\.(all|allSettled|race|any)$/.test(nodeName)) {
4019
4173
  const [firstArg] = node.argument.arguments;
4020
4174
  if (firstArg.type === "ArrayExpression" && firstArg.elements.some((nod) => nod && isIdentifier(nod, name))) {
4021
4175
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-playwright",
3
- "version": "2.9.0",
3
+ "version": "2.10.1",
4
4
  "description": "ESLint plugin for Playwright testing.",
5
5
  "license": "MIT",
6
6
  "author": "Mark Skelton <mark@mskelton.dev>",