eslint-plugin-playwright 2.7.1 → 2.9.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 +11 -11
- package/dist/index.cjs +338 -130
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -79,8 +79,8 @@ can configure this plugin to be aware of these additional names.
|
|
|
79
79
|
"settings": {
|
|
80
80
|
"playwright": {
|
|
81
81
|
"globalAliases": {
|
|
82
|
-
"test": ["
|
|
83
|
-
"expect": ["
|
|
82
|
+
"test": ["it"],
|
|
83
|
+
"expect": ["assert"]
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -127,13 +127,13 @@ CLI option\
|
|
|
127
127
|
| [no-commented-out-tests](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-commented-out-tests.md) | Disallow commented out tests | | | |
|
|
128
128
|
| [no-conditional-expect](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-conditional-expect.md) | Disallow calling `expect` conditionally | ✅ | | |
|
|
129
129
|
| [no-conditional-in-test](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-conditional-in-test.md) | Disallow conditional logic in tests | ✅ | | |
|
|
130
|
-
| [no-duplicate-hooks](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-duplicate-hooks.md) | Disallow duplicate setup and teardown hooks |
|
|
130
|
+
| [no-duplicate-hooks](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-duplicate-hooks.md) | Disallow duplicate setup and teardown hooks | ✅ | | |
|
|
131
131
|
| [no-duplicate-slow](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-duplicate-slow.md) | Disallow multiple `test.slow()` calls in the same test | ✅ | | |
|
|
132
132
|
| [no-element-handle](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-element-handle.md) | Disallow usage of element handles | ✅ | | 💡 |
|
|
133
133
|
| [no-eval](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-eval.md) | Disallow usage of `page.$eval()` and `page.$$eval()` | ✅ | | |
|
|
134
134
|
| [no-focused-test](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-focused-test.md) | Disallow usage of `.only` annotation | ✅ | | 💡 |
|
|
135
135
|
| [no-force-option](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-force-option.md) | Disallow usage of the `{ force: true }` option | ✅ | | |
|
|
136
|
-
| [no-get-by-title](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-get-by-title.md) | Disallow using `getByTitle()` | |
|
|
136
|
+
| [no-get-by-title](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-get-by-title.md) | Disallow using `getByTitle()` | | | |
|
|
137
137
|
| [no-hooks](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-hooks.md) | Disallow setup and teardown hooks | | | |
|
|
138
138
|
| [no-nested-step](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-nested-step.md) | Disallow nested `test.step()` methods | ✅ | | |
|
|
139
139
|
| [no-networkidle](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-networkidle.md) | Disallow usage of the `networkidle` option | ✅ | | |
|
|
@@ -145,7 +145,7 @@ CLI option\
|
|
|
145
145
|
| [no-restricted-roles](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-restricted-roles.md) | Disallow specific roles in `getByRole()` | | | |
|
|
146
146
|
| [no-skipped-test](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-skipped-test.md) | Disallow usage of the `.skip` annotation | ✅ | | 💡 |
|
|
147
147
|
| [no-slowed-test](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-slowed-test.md) | Disallow usage of the `.slow` annotation | | | 💡 |
|
|
148
|
-
| [no-standalone-expect](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-standalone-expect.md) | Disallow using expect outside of `test` blocks | ✅ |
|
|
148
|
+
| [no-standalone-expect](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-standalone-expect.md) | Disallow using expect outside of `test` blocks | ✅ | 🔧 | |
|
|
149
149
|
| [no-unsafe-references](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-unsafe-references.md) | Prevent unsafe variable references in `page.evaluate()` | ✅ | 🔧 | |
|
|
150
150
|
| [no-unused-locators](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-unused-locators.md) | Disallow usage of page locators that are not used | ✅ | | |
|
|
151
151
|
| [no-useless-await](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-useless-await.md) | Disallow unnecessary `await`s for Playwright methods | ✅ | 🔧 | |
|
|
@@ -155,16 +155,16 @@ CLI option\
|
|
|
155
155
|
| [no-wait-for-timeout](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-timeout.md) | Disallow usage of `page.waitForTimeout()` | ✅ | | 💡 |
|
|
156
156
|
| [prefer-comparison-matcher](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-comparison-matcher.md) | Suggest using the built-in comparison matchers | | 🔧 | |
|
|
157
157
|
| [prefer-equality-matcher](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-equality-matcher.md) | Suggest using the built-in equality matchers | | | 💡 |
|
|
158
|
-
| [prefer-hooks-in-order](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-hooks-in-order.md) | Prefer having hooks in a consistent order |
|
|
159
|
-
| [prefer-hooks-on-top](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-hooks-on-top.md) | Suggest having hooks before any test cases |
|
|
158
|
+
| [prefer-hooks-in-order](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-hooks-in-order.md) | Prefer having hooks in a consistent order | ✅ | | |
|
|
159
|
+
| [prefer-hooks-on-top](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-hooks-on-top.md) | Suggest having hooks before any test cases | ✅ | | |
|
|
160
160
|
| [prefer-lowercase-title](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-lowercase-title.md) | Enforce lowercase test names | | 🔧 | |
|
|
161
161
|
| [prefer-native-locators](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-native-locators.md) | Suggest built-in locators over `page.locator()` | | 🔧 | |
|
|
162
|
-
| [prefer-locator](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-locator.md) | Suggest locators over page methods |
|
|
163
|
-
| [prefer-strict-equal](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-strict-equal.md) | Suggest using `toStrictEqual()` | |
|
|
162
|
+
| [prefer-locator](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-locator.md) | Suggest locators over page methods | ✅ | | |
|
|
163
|
+
| [prefer-strict-equal](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-strict-equal.md) | Suggest using `toStrictEqual()` | | 🔧 | 💡 |
|
|
164
164
|
| [prefer-to-be](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-be.md) | Suggest using `toBe()` | | 🔧 | |
|
|
165
165
|
| [prefer-to-contain](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-contain.md) | Suggest using `toContain()` | | 🔧 | |
|
|
166
|
-
| [prefer-to-have-count](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-have-count.md) | Suggest using `toHaveCount()` |
|
|
167
|
-
| [prefer-to-have-length](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-have-length.md) | Suggest using `toHaveLength()` |
|
|
166
|
+
| [prefer-to-have-count](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-have-count.md) | Suggest using `toHaveCount()` | ✅ | 🔧 | |
|
|
167
|
+
| [prefer-to-have-length](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-have-length.md) | Suggest using `toHaveLength()` | ✅ | 🔧 | |
|
|
168
168
|
| [prefer-web-first-assertions](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/prefer-web-first-assertions.md) | Suggest using web first assertions | ✅ | 🔧 | |
|
|
169
169
|
| [require-hook](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/require-hook.md) | Require setup and teardown code to be within a hook | | | |
|
|
170
170
|
| [require-soft-assertions](https://github.com/mskelton/eslint-plugin-playwright/tree/main/docs/rules/require-soft-assertions.md) | Require assertions to use `expect.soft()` | | 🔧 | |
|
package/dist/index.cjs
CHANGED
|
@@ -118,27 +118,55 @@ var Chain = class {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
};
|
|
121
|
-
|
|
121
|
+
function resolvePossibleAliasedGlobal(context, name) {
|
|
122
122
|
const settings = context.settings;
|
|
123
123
|
const globalAliases = settings?.playwright?.globalAliases ?? {};
|
|
124
|
-
const alias = Object.entries(globalAliases).find(([, aliases]) => aliases.includes(
|
|
124
|
+
const alias = Object.entries(globalAliases).find(([, aliases]) => aliases.includes(name));
|
|
125
125
|
return alias?.[0] ?? null;
|
|
126
|
-
}
|
|
126
|
+
}
|
|
127
|
+
function resolveImportAlias(context, node) {
|
|
128
|
+
if (node.type !== "Identifier") {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const scope = context.sourceCode.getScope(node);
|
|
132
|
+
const ref = scope.references.find((r) => r.identifier === node);
|
|
133
|
+
for (const def of ref?.resolved?.defs ?? []) {
|
|
134
|
+
if (def.type === "ImportBinding" && def.node.type === "ImportSpecifier") {
|
|
135
|
+
const imported = getStringValue(def.node.imported);
|
|
136
|
+
if (imported !== node.name) {
|
|
137
|
+
return imported;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
127
143
|
var resolveToPlaywrightFn = (context, accessor) => {
|
|
128
144
|
const ident = getStringValue(accessor);
|
|
129
145
|
const resolved = /(^expect|Expect)$/.test(ident) ? "expect" : ident;
|
|
146
|
+
if (resolved === "test" || resolved === "expect") {
|
|
147
|
+
return { original: null, local: resolved };
|
|
148
|
+
}
|
|
130
149
|
return {
|
|
131
|
-
|
|
132
|
-
original: resolvePossibleAliasedGlobal(context, resolved),
|
|
150
|
+
original: resolvePossibleAliasedGlobal(context, resolved) ?? resolveImportAlias(context, accessor),
|
|
133
151
|
local: resolved
|
|
134
152
|
};
|
|
135
153
|
};
|
|
136
154
|
function determinePlaywrightFnGroup(name) {
|
|
137
|
-
if (name === "step")
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (name === "
|
|
141
|
-
|
|
155
|
+
if (name === "step") {
|
|
156
|
+
return "step";
|
|
157
|
+
}
|
|
158
|
+
if (name === "expect") {
|
|
159
|
+
return "expect";
|
|
160
|
+
}
|
|
161
|
+
if (name === "describe") {
|
|
162
|
+
return "describe";
|
|
163
|
+
}
|
|
164
|
+
if (name === "test") {
|
|
165
|
+
return "test";
|
|
166
|
+
}
|
|
167
|
+
if (testHooks.has(name)) {
|
|
168
|
+
return "hook";
|
|
169
|
+
}
|
|
142
170
|
return "unknown";
|
|
143
171
|
}
|
|
144
172
|
var modifiers = /* @__PURE__ */ new Set(["not", "resolves", "rejects"]);
|
|
@@ -213,14 +241,43 @@ var findTopMostCallExpression = (node) => {
|
|
|
213
241
|
}
|
|
214
242
|
return top;
|
|
215
243
|
};
|
|
244
|
+
function isTestExtendCall(context, node) {
|
|
245
|
+
if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression" || !isPropertyAccessor(node.callee, "extend")) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
const object = node.callee.object;
|
|
249
|
+
if (object.type === "Identifier") {
|
|
250
|
+
const resolved = resolveToPlaywrightFn(context, object);
|
|
251
|
+
if ((resolved?.original ?? resolved?.local) === "test") {
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
const dereferenced = dereference(context, object);
|
|
255
|
+
if (dereferenced) {
|
|
256
|
+
return isTestExtendCall(context, dereferenced);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
216
261
|
function parse(context, node) {
|
|
217
262
|
const chain = new Chain(node);
|
|
218
|
-
if (!chain.nodes?.length)
|
|
263
|
+
if (!chain.nodes?.length) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
219
266
|
const [first, ...rest] = chain.nodes;
|
|
220
|
-
|
|
221
|
-
if (!resolved)
|
|
267
|
+
let resolved = resolveToPlaywrightFn(context, first);
|
|
268
|
+
if (!resolved) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
222
271
|
let name = resolved.original ?? resolved.local;
|
|
223
272
|
const links = [name, ...rest.map((link) => getStringValue(link))];
|
|
273
|
+
if (determinePlaywrightFnGroup(name) === "unknown") {
|
|
274
|
+
const dereferenced = dereference(context, first);
|
|
275
|
+
if (dereferenced && isTestExtendCall(context, dereferenced)) {
|
|
276
|
+
name = "test";
|
|
277
|
+
links[0] = "test";
|
|
278
|
+
resolved = { local: resolved.local, original: "test" };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
224
281
|
if (name === "test" && links.length > 1) {
|
|
225
282
|
const nextLinkName = links[1];
|
|
226
283
|
const nextLinkGroup = determinePlaywrightFnGroup(nextLinkName);
|
|
@@ -246,7 +303,9 @@ function parse(context, node) {
|
|
|
246
303
|
parsedFnCall.members.shift();
|
|
247
304
|
}
|
|
248
305
|
const result = parseExpectCall(chain, parsedFnCall, stage);
|
|
249
|
-
if (!result)
|
|
306
|
+
if (!result) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
250
309
|
if (typeof result === "string" && findTopMostCallExpression(node) !== node) {
|
|
251
310
|
return null;
|
|
252
311
|
}
|
|
@@ -294,7 +353,9 @@ var isTypeOfFnCall = (context, node, types) => {
|
|
|
294
353
|
|
|
295
354
|
// src/utils/ast.ts
|
|
296
355
|
function getStringValue(node) {
|
|
297
|
-
if (!node)
|
|
356
|
+
if (!node) {
|
|
357
|
+
return "";
|
|
358
|
+
}
|
|
298
359
|
return node.type === "Identifier" ? node.name : node.type === "TemplateLiteral" ? node.quasis[0].value.raw : node.type === "Literal" && typeof node.value === "string" ? node.value : "";
|
|
299
360
|
}
|
|
300
361
|
function getRawValue(node) {
|
|
@@ -323,7 +384,9 @@ function isPropertyAccessor(node, name) {
|
|
|
323
384
|
}
|
|
324
385
|
function findParent(node, type) {
|
|
325
386
|
const parent = node.parent;
|
|
326
|
-
if (!parent)
|
|
387
|
+
if (!parent) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
327
390
|
return parent.type === type ? parent : findParent(parent, type);
|
|
328
391
|
}
|
|
329
392
|
function dig(node, identifier) {
|
|
@@ -394,6 +457,9 @@ var getPaddingLineSequences = (prevNode, nextNode, sourceCode) => {
|
|
|
394
457
|
return pairs;
|
|
395
458
|
};
|
|
396
459
|
var areTokensOnSameLine = (left, right) => left.loc.end.line === right.loc.start.line;
|
|
460
|
+
var isPromiseAccessor = (node) => {
|
|
461
|
+
return node.type === "MemberExpression" && isIdentifier(node.property, /^(then|catch|finally)$/);
|
|
462
|
+
};
|
|
397
463
|
|
|
398
464
|
// src/utils/createRule.ts
|
|
399
465
|
function interpolate(str, data) {
|
|
@@ -683,7 +749,7 @@ var max_expects_default = createRule({
|
|
|
683
749
|
"ArrowFunctionExpression:exit": maybeResetCount,
|
|
684
750
|
"CallExpression"(node) {
|
|
685
751
|
const call = parseFnCall(context, node);
|
|
686
|
-
if (call?.type !== "expect"
|
|
752
|
+
if (call?.type !== "expect") {
|
|
687
753
|
return;
|
|
688
754
|
}
|
|
689
755
|
count += 1;
|
|
@@ -872,29 +938,65 @@ var missing_playwright_await_default = createRule({
|
|
|
872
938
|
// Add any custom matchers to the set
|
|
873
939
|
...options.customMatchers || []
|
|
874
940
|
]);
|
|
941
|
+
function isVariableConsumed(declarator, checkValidity2, validTypes2, visited) {
|
|
942
|
+
const variables = context.sourceCode.getDeclaredVariables(declarator);
|
|
943
|
+
for (const variable of variables) {
|
|
944
|
+
for (const ref of variable.references) {
|
|
945
|
+
if (!ref.isRead()) {
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
const refParent = ref.identifier.parent;
|
|
949
|
+
if (visited.has(refParent)) {
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
if (validTypes2.has(refParent.type)) {
|
|
953
|
+
return true;
|
|
954
|
+
}
|
|
955
|
+
if (refParent.type === "VariableDeclarator") {
|
|
956
|
+
if (checkValidity2(ref.identifier, visited)) {
|
|
957
|
+
return true;
|
|
958
|
+
}
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
if (checkValidity2(refParent, visited)) {
|
|
962
|
+
return true;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return false;
|
|
967
|
+
}
|
|
875
968
|
function checkValidity(node, visited) {
|
|
876
969
|
const parent = node.parent;
|
|
877
|
-
if (!parent)
|
|
878
|
-
|
|
970
|
+
if (!parent) {
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
if (visited.has(parent)) {
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
879
976
|
visited.add(parent);
|
|
880
|
-
if (validTypes.has(parent.type))
|
|
881
|
-
|
|
977
|
+
if (validTypes.has(parent.type)) {
|
|
978
|
+
return true;
|
|
979
|
+
}
|
|
980
|
+
if (isPromiseAccessor(parent) && parent.parent?.type === "CallExpression") {
|
|
882
981
|
return checkValidity(parent.parent, visited);
|
|
883
982
|
}
|
|
983
|
+
if (parent.type === "CallExpression" && parent.callee === node && isPromiseAccessor(node)) {
|
|
984
|
+
return checkValidity(parent, visited);
|
|
985
|
+
}
|
|
884
986
|
if (parent.type === "ArrayExpression") {
|
|
885
987
|
return checkValidity(parent, visited);
|
|
886
988
|
}
|
|
989
|
+
if (parent.type === "ConditionalExpression") {
|
|
990
|
+
return checkValidity(parent, visited);
|
|
991
|
+
}
|
|
992
|
+
if (parent.type === "SpreadElement") {
|
|
993
|
+
return checkValidity(parent, visited);
|
|
994
|
+
}
|
|
887
995
|
if (parent.type === "CallExpression" && parent.callee.type === "MemberExpression" && isIdentifier(parent.callee.object, "Promise") && isIdentifier(parent.callee.property, "all")) {
|
|
888
996
|
return true;
|
|
889
997
|
}
|
|
890
998
|
if (parent.type === "VariableDeclarator") {
|
|
891
|
-
|
|
892
|
-
for (const ref of scope.references) {
|
|
893
|
-
const refParent = ref.identifier.parent;
|
|
894
|
-
if (visited.has(refParent)) continue;
|
|
895
|
-
if (validTypes.has(refParent.type)) return true;
|
|
896
|
-
if (checkValidity(refParent, visited)) return true;
|
|
897
|
-
}
|
|
999
|
+
return isVariableConsumed(parent, checkValidity, validTypes, visited);
|
|
898
1000
|
}
|
|
899
1001
|
return false;
|
|
900
1002
|
}
|
|
@@ -912,7 +1014,9 @@ var missing_playwright_await_default = createRule({
|
|
|
912
1014
|
return;
|
|
913
1015
|
}
|
|
914
1016
|
const call = parseFnCall(context, node);
|
|
915
|
-
if (call?.type !== "step" && call?.type !== "expect")
|
|
1017
|
+
if (call?.type !== "step" && call?.type !== "expect") {
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
916
1020
|
const result = getCallType(call, awaitableMatchers);
|
|
917
1021
|
const isValid = result ? checkValidity(node, /* @__PURE__ */ new Set()) : false;
|
|
918
1022
|
if (result && !isValid) {
|
|
@@ -970,7 +1074,9 @@ function hasTests(context, node) {
|
|
|
970
1074
|
var no_commented_out_tests_default = createRule({
|
|
971
1075
|
create(context) {
|
|
972
1076
|
function checkNode(node) {
|
|
973
|
-
if (!hasTests(context, node))
|
|
1077
|
+
if (!hasTests(context, node)) {
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
974
1080
|
context.report({
|
|
975
1081
|
messageId: "commentedTests",
|
|
976
1082
|
node
|
|
@@ -997,18 +1103,6 @@ var no_commented_out_tests_default = createRule({
|
|
|
997
1103
|
|
|
998
1104
|
// src/rules/no-conditional-expect.ts
|
|
999
1105
|
var isCatchCall = (node) => node.callee.type === "MemberExpression" && isPropertyAccessor(node.callee, "catch");
|
|
1000
|
-
var getTestCallExpressionsFromDeclaredVariables = (context, declaredVariables) => {
|
|
1001
|
-
return declaredVariables.reduce(
|
|
1002
|
-
(acc, { references }) => [
|
|
1003
|
-
...acc,
|
|
1004
|
-
...references.map(({ identifier }) => identifier.parent).filter(
|
|
1005
|
-
// ESLint types are infurating
|
|
1006
|
-
(node) => node?.type === "CallExpression" && isTypeOfFnCall(context, node, ["test"])
|
|
1007
|
-
)
|
|
1008
|
-
],
|
|
1009
|
-
[]
|
|
1010
|
-
);
|
|
1011
|
-
};
|
|
1012
1106
|
var no_conditional_expect_default = createRule({
|
|
1013
1107
|
create(context) {
|
|
1014
1108
|
let conditionalDepth = 0;
|
|
@@ -1050,16 +1144,6 @@ var no_conditional_expect_default = createRule({
|
|
|
1050
1144
|
"CatchClause:exit": decreaseConditionalDepth,
|
|
1051
1145
|
"ConditionalExpression": increaseConditionalDepth,
|
|
1052
1146
|
"ConditionalExpression:exit": decreaseConditionalDepth,
|
|
1053
|
-
"FunctionDeclaration"(node) {
|
|
1054
|
-
const declaredVariables = context.sourceCode.getDeclaredVariables(node);
|
|
1055
|
-
const testCallExpressions = getTestCallExpressionsFromDeclaredVariables(
|
|
1056
|
-
context,
|
|
1057
|
-
declaredVariables
|
|
1058
|
-
);
|
|
1059
|
-
if (testCallExpressions.length > 0) {
|
|
1060
|
-
inTestCase = true;
|
|
1061
|
-
}
|
|
1062
|
-
},
|
|
1063
1147
|
"IfStatement": increaseConditionalDepth,
|
|
1064
1148
|
"IfStatement:exit": decreaseConditionalDepth,
|
|
1065
1149
|
"LogicalExpression": increaseConditionalDepth,
|
|
@@ -1089,11 +1173,15 @@ var no_conditional_in_test_default = createRule({
|
|
|
1089
1173
|
return;
|
|
1090
1174
|
}
|
|
1091
1175
|
const call = findParent(node, "CallExpression");
|
|
1092
|
-
if (!call)
|
|
1176
|
+
if (!call) {
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1093
1179
|
if (isTypeOfFnCall(context, call, ["test", "step"])) {
|
|
1094
1180
|
const testFunction = call.arguments[call.arguments.length - 1];
|
|
1095
1181
|
const functionBody = findParent(node, "BlockStatement");
|
|
1096
|
-
if (!functionBody)
|
|
1182
|
+
if (!functionBody) {
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1097
1185
|
let currentParent = functionBody.parent;
|
|
1098
1186
|
while (currentParent && currentParent !== testFunction) {
|
|
1099
1187
|
currentParent = currentParent.parent;
|
|
@@ -1131,7 +1219,9 @@ var no_duplicate_hooks_default = createRule({
|
|
|
1131
1219
|
return {
|
|
1132
1220
|
"CallExpression"(node) {
|
|
1133
1221
|
const call = parseFnCall(context, node);
|
|
1134
|
-
if (!call)
|
|
1222
|
+
if (!call) {
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1135
1225
|
if (call.type === "describe") {
|
|
1136
1226
|
hookContexts.push({});
|
|
1137
1227
|
}
|
|
@@ -1177,7 +1267,9 @@ var no_duplicate_slow_default = createRule({
|
|
|
1177
1267
|
return {
|
|
1178
1268
|
"CallExpression"(node) {
|
|
1179
1269
|
const call = parseFnCall(context, node);
|
|
1180
|
-
if (!call)
|
|
1270
|
+
if (!call) {
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1181
1273
|
if (call.type === "test" || call.type === "describe") {
|
|
1182
1274
|
scopes.push(scopes[scopes.length - 1]);
|
|
1183
1275
|
}
|
|
@@ -1298,7 +1390,9 @@ var no_focused_test_default = createRule({
|
|
|
1298
1390
|
return;
|
|
1299
1391
|
}
|
|
1300
1392
|
const onlyNode = call.members.find((s) => getStringValue(s) === "only");
|
|
1301
|
-
if (!onlyNode)
|
|
1393
|
+
if (!onlyNode) {
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1302
1396
|
context.report({
|
|
1303
1397
|
messageId: "noFocusedTest",
|
|
1304
1398
|
node: onlyNode,
|
|
@@ -1383,7 +1477,7 @@ var no_get_by_title_default = createRule({
|
|
|
1383
1477
|
create(context) {
|
|
1384
1478
|
return {
|
|
1385
1479
|
CallExpression(node) {
|
|
1386
|
-
if (
|
|
1480
|
+
if (node.callee.type === "MemberExpression" && getStringValue(node.callee.property) === "getByTitle") {
|
|
1387
1481
|
context.report({ messageId: "noGetByTitle", node });
|
|
1388
1482
|
}
|
|
1389
1483
|
}
|
|
@@ -1412,7 +1506,9 @@ var no_hooks_default = createRule({
|
|
|
1412
1506
|
return {
|
|
1413
1507
|
CallExpression(node) {
|
|
1414
1508
|
const call = parseFnCall(context, node);
|
|
1415
|
-
if (!call)
|
|
1509
|
+
if (!call) {
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1416
1512
|
if (call.type === "hook" && !options.allow.includes(call.name)) {
|
|
1417
1513
|
context.report({
|
|
1418
1514
|
data: { hookName: call.name },
|
|
@@ -1507,20 +1603,26 @@ var no_networkidle_default = createRule({
|
|
|
1507
1603
|
create(context) {
|
|
1508
1604
|
return {
|
|
1509
1605
|
CallExpression(node) {
|
|
1510
|
-
if (node.callee.type !== "MemberExpression")
|
|
1606
|
+
if (node.callee.type !== "MemberExpression") {
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1511
1609
|
const methodName = getStringValue(node.callee.property);
|
|
1512
|
-
if (!methods.has(methodName))
|
|
1610
|
+
if (!methods.has(methodName)) {
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1513
1613
|
if (methodName === "waitForLoadState") {
|
|
1514
1614
|
const arg = node.arguments[0];
|
|
1515
|
-
if (arg &&
|
|
1615
|
+
if (arg && isStringNode(arg, "networkidle")) {
|
|
1516
1616
|
context.report({ messageId, node: arg });
|
|
1517
1617
|
}
|
|
1518
1618
|
return;
|
|
1519
1619
|
}
|
|
1520
1620
|
if (node.arguments.length >= 2) {
|
|
1521
1621
|
const [_, arg] = node.arguments;
|
|
1522
|
-
if (arg.type !== "ObjectExpression")
|
|
1523
|
-
|
|
1622
|
+
if (arg.type !== "ObjectExpression") {
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
const property = arg.properties.filter((p) => p.type === "Property").find((p) => isStringNode(p.value, "networkidle"));
|
|
1524
1626
|
if (property) {
|
|
1525
1627
|
context.report({ messageId, node: property.value });
|
|
1526
1628
|
}
|
|
@@ -1547,9 +1649,13 @@ var no_nth_methods_default = createRule({
|
|
|
1547
1649
|
create(context) {
|
|
1548
1650
|
return {
|
|
1549
1651
|
CallExpression(node) {
|
|
1550
|
-
if (node.callee.type !== "MemberExpression")
|
|
1652
|
+
if (node.callee.type !== "MemberExpression") {
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1551
1655
|
const method = getStringValue(node.callee.property);
|
|
1552
|
-
if (!methods2.has(method))
|
|
1656
|
+
if (!methods2.has(method)) {
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1553
1659
|
context.report({
|
|
1554
1660
|
data: { method },
|
|
1555
1661
|
loc: {
|
|
@@ -1614,13 +1720,12 @@ var no_raw_locators_default = createRule({
|
|
|
1614
1720
|
}
|
|
1615
1721
|
return {
|
|
1616
1722
|
CallExpression(node) {
|
|
1617
|
-
if (node.callee.type !== "MemberExpression" || node.
|
|
1723
|
+
if (node.callee.type !== "MemberExpression" || !isPropertyAccessor(node.callee, "locator")) {
|
|
1618
1724
|
return;
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
const isLocator = isPageMethod(node, "locator") || method === "locator";
|
|
1622
|
-
if (isLocator && !isAllowed(arg)) {
|
|
1725
|
+
}
|
|
1726
|
+
if ((node.arguments.length === 0 || isStringNode(node.arguments[0])) && !isAllowed(getStringValue(node.arguments[0]))) {
|
|
1623
1727
|
context.report({ messageId: "noRawLocator", node });
|
|
1728
|
+
return;
|
|
1624
1729
|
}
|
|
1625
1730
|
}
|
|
1626
1731
|
};
|
|
@@ -1673,7 +1778,7 @@ var no_restricted_locators_default = createRule({
|
|
|
1673
1778
|
return;
|
|
1674
1779
|
}
|
|
1675
1780
|
for (const [restrictedType, message] of restrictionMap.entries()) {
|
|
1676
|
-
if (
|
|
1781
|
+
if (getStringValue(node.callee.property) === restrictedType) {
|
|
1677
1782
|
context.report({
|
|
1678
1783
|
data: {
|
|
1679
1784
|
message: message ?? "",
|
|
@@ -1728,7 +1833,9 @@ var no_restricted_matchers_default = createRule({
|
|
|
1728
1833
|
return {
|
|
1729
1834
|
CallExpression(node) {
|
|
1730
1835
|
const call = parseFnCall(context, node);
|
|
1731
|
-
if (call?.type !== "expect")
|
|
1836
|
+
if (call?.type !== "expect") {
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1732
1839
|
Object.entries(restrictedChains).map(([restriction, message]) => {
|
|
1733
1840
|
const chain = call.members;
|
|
1734
1841
|
const restrictionLinks = restriction.split(".").length;
|
|
@@ -1797,7 +1904,7 @@ var no_restricted_roles_default = createRule({
|
|
|
1797
1904
|
}
|
|
1798
1905
|
return {
|
|
1799
1906
|
CallExpression(node) {
|
|
1800
|
-
if (!
|
|
1907
|
+
if (node.callee.type !== "MemberExpression" || !isPropertyAccessor(node.callee, "getByRole")) {
|
|
1801
1908
|
return;
|
|
1802
1909
|
}
|
|
1803
1910
|
const role = getStringValue(node.arguments[0]);
|
|
@@ -1860,9 +1967,11 @@ var no_skipped_test_default = createRule({
|
|
|
1860
1967
|
return;
|
|
1861
1968
|
}
|
|
1862
1969
|
const skipNode = call.members.find((s) => getStringValue(s) === "skip");
|
|
1863
|
-
if (!skipNode)
|
|
1970
|
+
if (!skipNode) {
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1864
1973
|
const isStandalone = call.type === "config";
|
|
1865
|
-
if (isStandalone && allowConditional && (node.arguments.length !== 0 || findParent(node, "BlockStatement")?.parent?.type === "IfStatement")) {
|
|
1974
|
+
if (isStandalone && allowConditional && (node.arguments.length !== 0 || findParent(node, "BlockStatement")?.parent?.type === "IfStatement" || findParent(node, "SwitchCase") !== void 0)) {
|
|
1866
1975
|
return;
|
|
1867
1976
|
}
|
|
1868
1977
|
context.report({
|
|
@@ -1922,7 +2031,9 @@ var no_slowed_test_default = createRule({
|
|
|
1922
2031
|
return;
|
|
1923
2032
|
}
|
|
1924
2033
|
const slowNode = call.members.find((s) => getStringValue(s) === "slow");
|
|
1925
|
-
if (!slowNode)
|
|
2034
|
+
if (!slowNode) {
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
1926
2037
|
const isStandalone = call.type === "config";
|
|
1927
2038
|
if (isStandalone && allowConditional && (node.arguments.length !== 0 || findParent(node, "BlockStatement")?.parent?.type === "IfStatement")) {
|
|
1928
2039
|
return;
|
|
@@ -2046,7 +2157,7 @@ var no_standalone_expect_default = createRule({
|
|
|
2046
2157
|
},
|
|
2047
2158
|
"CallExpression:exit"(node) {
|
|
2048
2159
|
const top = callStack.at(-1);
|
|
2049
|
-
if (top === "test" && isTypeOfFnCall(context, node, ["test"]) && node
|
|
2160
|
+
if (top === "test" && isTypeOfFnCall(context, node, ["test"]) || top === "hook" && isTypeOfFnCall(context, node, ["hook"]) || top === "template" && node.callee.type === "TaggedTemplateExpression" || top === "fixture" && node.callee.type === "MemberExpression" && isPropertyAccessor(node.callee, "extend")) {
|
|
2050
2161
|
callStack.pop();
|
|
2051
2162
|
}
|
|
2052
2163
|
}
|
|
@@ -2075,16 +2186,22 @@ var truthy = Boolean;
|
|
|
2075
2186
|
|
|
2076
2187
|
// src/rules/no-unsafe-references.ts
|
|
2077
2188
|
function collectVariables(scope) {
|
|
2078
|
-
if (!scope || scope.type === "global")
|
|
2189
|
+
if (!scope || scope.type === "global") {
|
|
2190
|
+
return [];
|
|
2191
|
+
}
|
|
2079
2192
|
return [...collectVariables(scope.upper), ...scope.variables.map((ref) => ref.name)];
|
|
2080
2193
|
}
|
|
2081
2194
|
function addArgument(fixer, node, refs) {
|
|
2082
|
-
if (!node.arguments.length)
|
|
2195
|
+
if (!node.arguments.length) {
|
|
2196
|
+
return;
|
|
2197
|
+
}
|
|
2083
2198
|
if (node.arguments.length === 1) {
|
|
2084
2199
|
return fixer.insertTextAfter(node.arguments[0], `, [${refs}]`);
|
|
2085
2200
|
}
|
|
2086
2201
|
const arg = node.arguments.at(-1);
|
|
2087
|
-
if (!arg)
|
|
2202
|
+
if (!arg) {
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2088
2205
|
if (arg.type !== "ArrayExpression") {
|
|
2089
2206
|
return fixer.replaceText(arg, `[${getStringValue(arg)}, ${refs}]`);
|
|
2090
2207
|
}
|
|
@@ -2110,9 +2227,13 @@ var no_unsafe_references_default = createRule({
|
|
|
2110
2227
|
create(context) {
|
|
2111
2228
|
return {
|
|
2112
2229
|
CallExpression(node) {
|
|
2113
|
-
if (!isPageMethod(node, "evaluate") && !isPageMethod(node, "addInitScript"))
|
|
2230
|
+
if (!isPageMethod(node, "evaluate") && !isPageMethod(node, "addInitScript")) {
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2114
2233
|
const [fn] = node.arguments;
|
|
2115
|
-
if (!fn || !isFunction(fn))
|
|
2234
|
+
if (!fn || !isFunction(fn)) {
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2116
2237
|
const { through, upper } = context.sourceCode.getScope(fn.body);
|
|
2117
2238
|
const allRefs = new Set(collectVariables(upper));
|
|
2118
2239
|
through.filter((ref) => {
|
|
@@ -2162,7 +2283,7 @@ var no_unused_locators_default = createRule({
|
|
|
2162
2283
|
create(context) {
|
|
2163
2284
|
return {
|
|
2164
2285
|
CallExpression(node) {
|
|
2165
|
-
if (!
|
|
2286
|
+
if (node.callee.type !== "MemberExpression" || !isPropertyAccessor(node.callee, LOCATOR_REGEX)) {
|
|
2166
2287
|
return;
|
|
2167
2288
|
}
|
|
2168
2289
|
if (node.parent.type === "ExpressionStatement" || node.parent.type === "AwaitExpression") {
|
|
@@ -2245,7 +2366,9 @@ var expectMatchers = /* @__PURE__ */ new Set([
|
|
|
2245
2366
|
"toThrowError"
|
|
2246
2367
|
]);
|
|
2247
2368
|
function isSupportedMethod(node) {
|
|
2248
|
-
if (node.callee.type !== "MemberExpression")
|
|
2369
|
+
if (node.callee.type !== "MemberExpression") {
|
|
2370
|
+
return false;
|
|
2371
|
+
}
|
|
2249
2372
|
const name = getStringValue(node.callee.property);
|
|
2250
2373
|
return locatorMethods.has(name) || pageMethods.has(name) && isPageMethod(node, name);
|
|
2251
2374
|
}
|
|
@@ -2300,7 +2423,9 @@ function replaceAccessorFixer(fixer, node, text) {
|
|
|
2300
2423
|
}
|
|
2301
2424
|
function removePropertyFixer(fixer, property) {
|
|
2302
2425
|
const parent = property.parent;
|
|
2303
|
-
if (parent?.type !== "ObjectExpression")
|
|
2426
|
+
if (parent?.type !== "ObjectExpression") {
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2304
2429
|
if (parent.properties.length === 1) {
|
|
2305
2430
|
return fixer.remove(parent);
|
|
2306
2431
|
}
|
|
@@ -2324,7 +2449,9 @@ var matcherConfig = {
|
|
|
2324
2449
|
};
|
|
2325
2450
|
function getOptions(call, name) {
|
|
2326
2451
|
const [arg] = call.matcherArgs;
|
|
2327
|
-
if (arg?.type !== "ObjectExpression")
|
|
2452
|
+
if (arg?.type !== "ObjectExpression") {
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2328
2455
|
const property = arg.properties.find(
|
|
2329
2456
|
(p) => p.type === "Property" && getStringValue(p.key) === name && isBooleanLiteral(p.value)
|
|
2330
2457
|
);
|
|
@@ -2339,13 +2466,21 @@ var no_useless_not_default = createRule({
|
|
|
2339
2466
|
return {
|
|
2340
2467
|
CallExpression(node) {
|
|
2341
2468
|
const call = parseFnCall(context, node);
|
|
2342
|
-
if (call?.type !== "expect")
|
|
2469
|
+
if (call?.type !== "expect") {
|
|
2470
|
+
return;
|
|
2471
|
+
}
|
|
2343
2472
|
const config = matcherConfig[call.matcherName];
|
|
2344
|
-
if (!config)
|
|
2473
|
+
if (!config) {
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2345
2476
|
const options = config.argName ? getOptions(call, config.argName) : void 0;
|
|
2346
|
-
if (options?.arg && options.value === void 0)
|
|
2477
|
+
if (options?.arg && options.value === void 0) {
|
|
2478
|
+
return;
|
|
2479
|
+
}
|
|
2347
2480
|
const notModifier = call.modifiers.find((mod) => getStringValue(mod) === "not");
|
|
2348
|
-
if (!notModifier && !options?.property)
|
|
2481
|
+
if (!notModifier && !options?.property) {
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2349
2484
|
const isInverted = !!notModifier !== (options?.value === false);
|
|
2350
2485
|
const newMatcherName = isInverted ? config.inverse : call.matcherName;
|
|
2351
2486
|
context.report({
|
|
@@ -2438,7 +2573,7 @@ var no_wait_for_selector_default = createRule({
|
|
|
2438
2573
|
suggest: [
|
|
2439
2574
|
{
|
|
2440
2575
|
fix: (fixer) => fixer.remove(
|
|
2441
|
-
node.parent && node.parent.type !== "AwaitExpression" ? node.parent : node.parent.parent
|
|
2576
|
+
node.parent && node.parent.type !== "AwaitExpression" && node.parent.type !== "VariableDeclarator" ? node.parent : node.parent.parent
|
|
2442
2577
|
),
|
|
2443
2578
|
messageId: "removeWaitForSelector"
|
|
2444
2579
|
}
|
|
@@ -2475,7 +2610,7 @@ var no_wait_for_timeout_default = createRule({
|
|
|
2475
2610
|
suggest: [
|
|
2476
2611
|
{
|
|
2477
2612
|
fix: (fixer) => fixer.remove(
|
|
2478
|
-
node.parent && node.parent.type !== "AwaitExpression" ? node.parent : node.parent.parent
|
|
2613
|
+
node.parent && node.parent.type !== "AwaitExpression" && node.parent.type !== "VariableDeclarator" ? node.parent : node.parent.parent
|
|
2479
2614
|
),
|
|
2480
2615
|
messageId: "removeWaitForTimeout"
|
|
2481
2616
|
}
|
|
@@ -2507,6 +2642,7 @@ var isString = (node) => {
|
|
|
2507
2642
|
var isComparingToString = (expression) => {
|
|
2508
2643
|
return isString(expression.left) || isString(expression.right);
|
|
2509
2644
|
};
|
|
2645
|
+
var skipModifiers = /* @__PURE__ */ new Set(["not", "soft", "poll"]);
|
|
2510
2646
|
var invertedOperators = {
|
|
2511
2647
|
"<": ">=",
|
|
2512
2648
|
"<=": ">",
|
|
@@ -2528,9 +2664,16 @@ var prefer_comparison_matcher_default = createRule({
|
|
|
2528
2664
|
return {
|
|
2529
2665
|
CallExpression(node) {
|
|
2530
2666
|
const call = parseFnCall(context, node);
|
|
2531
|
-
if (call?.type !== "expect" || call.matcherArgs.length === 0)
|
|
2532
|
-
|
|
2533
|
-
|
|
2667
|
+
if (call?.type !== "expect" || call.matcherArgs.length === 0) {
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2670
|
+
let expect = call.head.node.parent;
|
|
2671
|
+
while (expect?.type === "MemberExpression") {
|
|
2672
|
+
expect = expect.parent;
|
|
2673
|
+
}
|
|
2674
|
+
if (expect?.type !== "CallExpression") {
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2534
2677
|
const [comparison] = expect.arguments;
|
|
2535
2678
|
const expectCallEnd = expect.range[1];
|
|
2536
2679
|
const [matcherArg] = call.matcherArgs;
|
|
@@ -2549,7 +2692,7 @@ var prefer_comparison_matcher_default = createRule({
|
|
|
2549
2692
|
data: { preferredMatcher },
|
|
2550
2693
|
fix(fixer) {
|
|
2551
2694
|
const [modifier] = call.modifiers;
|
|
2552
|
-
const modifierText = modifier && getStringValue(modifier)
|
|
2695
|
+
const modifierText = modifier && !skipModifiers.has(getStringValue(modifier)) ? `.${getStringValue(modifier)}` : "";
|
|
2553
2696
|
return [
|
|
2554
2697
|
// Replace the comparison argument with the left-hand side of the comparison
|
|
2555
2698
|
fixer.replaceText(comparison, context.sourceCode.getText(comparison.left)),
|
|
@@ -2588,9 +2731,13 @@ var prefer_equality_matcher_default = createRule({
|
|
|
2588
2731
|
return {
|
|
2589
2732
|
CallExpression(node) {
|
|
2590
2733
|
const call = parseFnCall(context, node);
|
|
2591
|
-
if (call?.type !== "expect" || call.matcherArgs.length === 0)
|
|
2734
|
+
if (call?.type !== "expect" || call.matcherArgs.length === 0) {
|
|
2735
|
+
return;
|
|
2736
|
+
}
|
|
2592
2737
|
const expect = call.head.node.parent;
|
|
2593
|
-
if (expect?.type !== "CallExpression")
|
|
2738
|
+
if (expect?.type !== "CallExpression") {
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2594
2741
|
const [comparison] = expect.arguments;
|
|
2595
2742
|
const expectCallEnd = expect.range[1];
|
|
2596
2743
|
const [matcherArg] = call.matcherArgs;
|
|
@@ -2652,7 +2799,9 @@ var prefer_hooks_in_order_default = createRule({
|
|
|
2652
2799
|
let inHook = false;
|
|
2653
2800
|
return {
|
|
2654
2801
|
"CallExpression"(node) {
|
|
2655
|
-
if (inHook)
|
|
2802
|
+
if (inHook) {
|
|
2803
|
+
return;
|
|
2804
|
+
}
|
|
2656
2805
|
const call = parseFnCall(context, node);
|
|
2657
2806
|
if (call?.type !== "hook") {
|
|
2658
2807
|
previousHookIndex = -1;
|
|
@@ -2679,7 +2828,9 @@ var prefer_hooks_in_order_default = createRule({
|
|
|
2679
2828
|
inHook = false;
|
|
2680
2829
|
return;
|
|
2681
2830
|
}
|
|
2682
|
-
if (inHook)
|
|
2831
|
+
if (inHook) {
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2683
2834
|
previousHookIndex = -1;
|
|
2684
2835
|
}
|
|
2685
2836
|
};
|
|
@@ -2756,7 +2907,9 @@ var pageMethods2 = /* @__PURE__ */ new Set([
|
|
|
2756
2907
|
"uncheck"
|
|
2757
2908
|
]);
|
|
2758
2909
|
function isSupportedMethod2(node) {
|
|
2759
|
-
if (node.callee.type !== "MemberExpression")
|
|
2910
|
+
if (node.callee.type !== "MemberExpression") {
|
|
2911
|
+
return false;
|
|
2912
|
+
}
|
|
2760
2913
|
const name = getStringValue(node.callee.property);
|
|
2761
2914
|
return pageMethods2.has(name) && isPageMethod(node, name);
|
|
2762
2915
|
}
|
|
@@ -2764,7 +2917,9 @@ var prefer_locator_default = createRule({
|
|
|
2764
2917
|
create(context) {
|
|
2765
2918
|
return {
|
|
2766
2919
|
CallExpression(node) {
|
|
2767
|
-
if (!isSupportedMethod2(node))
|
|
2920
|
+
if (!isSupportedMethod2(node)) {
|
|
2921
|
+
return;
|
|
2922
|
+
}
|
|
2768
2923
|
context.report({
|
|
2769
2924
|
messageId: "preferLocator",
|
|
2770
2925
|
node
|
|
@@ -2925,9 +3080,13 @@ var prefer_native_locators_default = createRule({
|
|
|
2925
3080
|
const patterns = compilePatterns({ testIdAttribute });
|
|
2926
3081
|
return {
|
|
2927
3082
|
CallExpression(node) {
|
|
2928
|
-
if (node.callee.type !== "MemberExpression")
|
|
3083
|
+
if (node.callee.type !== "MemberExpression") {
|
|
3084
|
+
return;
|
|
3085
|
+
}
|
|
2929
3086
|
const query = getStringValue(node.arguments[0]);
|
|
2930
|
-
if (!
|
|
3087
|
+
if (!isPropertyAccessor(node.callee, "locator")) {
|
|
3088
|
+
return;
|
|
3089
|
+
}
|
|
2931
3090
|
for (const pattern of patterns) {
|
|
2932
3091
|
const match = query.match(pattern.pattern);
|
|
2933
3092
|
if (match) {
|
|
@@ -2985,7 +3144,9 @@ var prefer_strict_equal_default = createRule({
|
|
|
2985
3144
|
return {
|
|
2986
3145
|
CallExpression(node) {
|
|
2987
3146
|
const call = parseFnCall(context, node);
|
|
2988
|
-
if (call?.type !== "expect")
|
|
3147
|
+
if (call?.type !== "expect") {
|
|
3148
|
+
return;
|
|
3149
|
+
}
|
|
2989
3150
|
if (call.matcherName === "toEqual") {
|
|
2990
3151
|
context.report({
|
|
2991
3152
|
messageId: "useToStrictEqual",
|
|
@@ -3053,7 +3214,9 @@ var prefer_to_be_default = createRule({
|
|
|
3053
3214
|
return {
|
|
3054
3215
|
CallExpression(node) {
|
|
3055
3216
|
const call = parseFnCall(context, node);
|
|
3056
|
-
if (call?.type !== "expect")
|
|
3217
|
+
if (call?.type !== "expect") {
|
|
3218
|
+
return;
|
|
3219
|
+
}
|
|
3057
3220
|
const notMatchers = ["toBeUndefined", "toBeDefined"];
|
|
3058
3221
|
const notModifier = call.modifiers.find((node2) => getStringValue(node2) === "not");
|
|
3059
3222
|
if (notModifier && notMatchers.includes(call.matcherName)) {
|
|
@@ -3110,9 +3273,13 @@ var prefer_to_contain_default = createRule({
|
|
|
3110
3273
|
return {
|
|
3111
3274
|
CallExpression(node) {
|
|
3112
3275
|
const call = parseFnCall(context, node);
|
|
3113
|
-
if (call?.type !== "expect" || call.matcherArgs.length === 0)
|
|
3276
|
+
if (call?.type !== "expect" || call.matcherArgs.length === 0) {
|
|
3277
|
+
return;
|
|
3278
|
+
}
|
|
3114
3279
|
const expect = call.head.node.parent;
|
|
3115
|
-
if (expect?.type !== "CallExpression")
|
|
3280
|
+
if (expect?.type !== "CallExpression") {
|
|
3281
|
+
return;
|
|
3282
|
+
}
|
|
3116
3283
|
const [includesCall] = expect.arguments;
|
|
3117
3284
|
const { matcher } = call;
|
|
3118
3285
|
const [matcherArg] = call.matcherArgs;
|
|
@@ -3307,19 +3474,29 @@ var prefer_web_first_assertions_default = createRule({
|
|
|
3307
3474
|
return {
|
|
3308
3475
|
CallExpression(node) {
|
|
3309
3476
|
const fnCall = parseFnCall(context, node);
|
|
3310
|
-
if (fnCall?.type !== "expect")
|
|
3477
|
+
if (fnCall?.type !== "expect") {
|
|
3478
|
+
return;
|
|
3479
|
+
}
|
|
3311
3480
|
const expect = findParent(fnCall.head.node, "CallExpression");
|
|
3312
|
-
if (!expect)
|
|
3481
|
+
if (!expect) {
|
|
3482
|
+
return;
|
|
3483
|
+
}
|
|
3313
3484
|
const arg = dereference(context, fnCall.args[0]);
|
|
3314
|
-
if (!arg)
|
|
3485
|
+
if (!arg) {
|
|
3486
|
+
return;
|
|
3487
|
+
}
|
|
3315
3488
|
const call = arg.type === "AwaitExpression" ? arg.argument : arg;
|
|
3316
3489
|
if (call.type !== "CallExpression" || call.callee.type !== "MemberExpression") {
|
|
3317
3490
|
return;
|
|
3318
3491
|
}
|
|
3319
|
-
if (!supportedMatchers.has(fnCall.matcherName))
|
|
3492
|
+
if (!supportedMatchers.has(fnCall.matcherName)) {
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3320
3495
|
const method = getStringValue(call.callee.property);
|
|
3321
3496
|
const methodConfig = methods3[method];
|
|
3322
|
-
if (!Object.hasOwn(methods3, method))
|
|
3497
|
+
if (!Object.hasOwn(methods3, method)) {
|
|
3498
|
+
return;
|
|
3499
|
+
}
|
|
3323
3500
|
const notModifier = fnCall.modifiers.find((mod) => getStringValue(mod) === "not");
|
|
3324
3501
|
const isFalsy = methodConfig.type === "boolean" && (!!fnCall.matcherArgs.length && isBooleanLiteral(fnCall.matcherArgs[0], false) || fnCall.matcherName === "toBeFalsy");
|
|
3325
3502
|
const isInverse = methodConfig.inverse ? notModifier || isFalsy : notModifier && isFalsy;
|
|
@@ -3519,10 +3696,11 @@ function hasTagInOptions(node) {
|
|
|
3519
3696
|
}
|
|
3520
3697
|
function hasTagInTitle(node) {
|
|
3521
3698
|
const title = node.arguments[0];
|
|
3522
|
-
if (!title
|
|
3699
|
+
if (!title) {
|
|
3523
3700
|
return false;
|
|
3524
3701
|
}
|
|
3525
|
-
|
|
3702
|
+
const value = getStringValue(title);
|
|
3703
|
+
return !!value && tagRegex.test(value);
|
|
3526
3704
|
}
|
|
3527
3705
|
function hasTags(node) {
|
|
3528
3706
|
return hasTagInTitle(node) || hasTagInOptions(node);
|
|
@@ -3617,7 +3795,9 @@ var require_to_throw_message_default = createRule({
|
|
|
3617
3795
|
return {
|
|
3618
3796
|
CallExpression(node) {
|
|
3619
3797
|
const call = parseFnCall(context, node);
|
|
3620
|
-
if (call?.type !== "expect")
|
|
3798
|
+
if (call?.type !== "expect") {
|
|
3799
|
+
return;
|
|
3800
|
+
}
|
|
3621
3801
|
if (call.matcherArgs.length === 0 && ["toThrow", "toThrowError"].includes(call.matcherName) && !call.modifiers.some((nod) => getStringValue(nod) === "not")) {
|
|
3622
3802
|
context.report({
|
|
3623
3803
|
data: { matcherName: call.matcherName },
|
|
@@ -3654,7 +3834,9 @@ var require_top_level_describe_default = createRule({
|
|
|
3654
3834
|
return {
|
|
3655
3835
|
"CallExpression"(node) {
|
|
3656
3836
|
const call = parseFnCall(context, node);
|
|
3657
|
-
if (!call)
|
|
3837
|
+
if (!call) {
|
|
3838
|
+
return;
|
|
3839
|
+
}
|
|
3658
3840
|
if (call.type === "describe") {
|
|
3659
3841
|
describeCount++;
|
|
3660
3842
|
if (describeCount === 1) {
|
|
@@ -3723,7 +3905,9 @@ var valid_describe_callback_default = createRule({
|
|
|
3723
3905
|
return {
|
|
3724
3906
|
CallExpression(node) {
|
|
3725
3907
|
const call = parseFnCall(context, node);
|
|
3726
|
-
if (call?.group !== "describe")
|
|
3908
|
+
if (call?.group !== "describe") {
|
|
3909
|
+
return;
|
|
3910
|
+
}
|
|
3727
3911
|
if (call.members.some((s) => getStringValue(s) === "configure")) {
|
|
3728
3912
|
return;
|
|
3729
3913
|
}
|
|
@@ -3826,7 +4010,9 @@ var isTestCaseCallWithCallbackArg = (context, node) => {
|
|
|
3826
4010
|
};
|
|
3827
4011
|
var isPromiseMethodThatUsesValue = (node, identifier) => {
|
|
3828
4012
|
const name = getStringValue(identifier);
|
|
3829
|
-
if (node.argument == null)
|
|
4013
|
+
if (node.argument == null) {
|
|
4014
|
+
return false;
|
|
4015
|
+
}
|
|
3830
4016
|
if (node.argument.type === "CallExpression" && node.argument.arguments.length > 0) {
|
|
3831
4017
|
const nodeName = getNodeName(node.argument);
|
|
3832
4018
|
if (["Promise.all", "Promise.allSettled"].includes(nodeName)) {
|
|
@@ -4083,7 +4269,9 @@ var valid_expect_default = createRule({
|
|
|
4083
4269
|
return;
|
|
4084
4270
|
}
|
|
4085
4271
|
const { parent: expect } = call.head.node;
|
|
4086
|
-
if (expect?.type !== "CallExpression")
|
|
4272
|
+
if (expect?.type !== "CallExpression") {
|
|
4273
|
+
return;
|
|
4274
|
+
}
|
|
4087
4275
|
if (expect.arguments.length < minArgs) {
|
|
4088
4276
|
const expectLength = getStringValue(call.head.node).length;
|
|
4089
4277
|
const loc = {
|
|
@@ -4132,6 +4320,7 @@ var valid_expect_default = createRule({
|
|
|
4132
4320
|
messages: {
|
|
4133
4321
|
matcherNotCalled: "Matchers must be called to assert.",
|
|
4134
4322
|
matcherNotFound: "Expect must have a corresponding matcher call.",
|
|
4323
|
+
modifierUnknown: "Expect has an unknown modifier.",
|
|
4135
4324
|
notEnoughArgs: "Expect requires at least {{amount}} argument{{s}}.",
|
|
4136
4325
|
tooManyArgs: "Expect takes at most {{amount}} argument{{s}}."
|
|
4137
4326
|
},
|
|
@@ -4209,9 +4398,13 @@ var valid_test_tags_default = createRule({
|
|
|
4209
4398
|
return {
|
|
4210
4399
|
CallExpression(node) {
|
|
4211
4400
|
const call = parseFnCall(context, node);
|
|
4212
|
-
if (!call)
|
|
4401
|
+
if (!call) {
|
|
4402
|
+
return;
|
|
4403
|
+
}
|
|
4213
4404
|
const { type } = call;
|
|
4214
|
-
if (type !== "test" && type !== "describe" && type !== "step")
|
|
4405
|
+
if (type !== "test" && type !== "describe" && type !== "step") {
|
|
4406
|
+
return;
|
|
4407
|
+
}
|
|
4215
4408
|
if (node.arguments.length > 0) {
|
|
4216
4409
|
const titleArg = node.arguments[0];
|
|
4217
4410
|
if (titleArg && titleArg.type === "Literal" && typeof titleArg.value === "string") {
|
|
@@ -4221,14 +4414,20 @@ var valid_test_tags_default = createRule({
|
|
|
4221
4414
|
}
|
|
4222
4415
|
}
|
|
4223
4416
|
}
|
|
4224
|
-
if (node.arguments.length < 2)
|
|
4417
|
+
if (node.arguments.length < 2) {
|
|
4418
|
+
return;
|
|
4419
|
+
}
|
|
4225
4420
|
const optionsArg = node.arguments[1];
|
|
4226
|
-
if (!optionsArg || optionsArg.type !== "ObjectExpression")
|
|
4421
|
+
if (!optionsArg || optionsArg.type !== "ObjectExpression") {
|
|
4422
|
+
return;
|
|
4423
|
+
}
|
|
4227
4424
|
const tagProperty = optionsArg.properties.find(
|
|
4228
4425
|
(prop) => prop.type === "Property" && !("argument" in prop) && // Ensure it's not a spread element
|
|
4229
4426
|
prop.key.type === "Identifier" && prop.key.name === "tag"
|
|
4230
4427
|
);
|
|
4231
|
-
if (!tagProperty)
|
|
4428
|
+
if (!tagProperty) {
|
|
4429
|
+
return;
|
|
4430
|
+
}
|
|
4232
4431
|
const tagValue = tagProperty.value;
|
|
4233
4432
|
if (tagValue.type === "Literal") {
|
|
4234
4433
|
if (typeof tagValue.value !== "string") {
|
|
@@ -4365,7 +4564,9 @@ var valid_title_default = createRule({
|
|
|
4365
4564
|
}
|
|
4366
4565
|
const [argument] = node.arguments;
|
|
4367
4566
|
const title = dereference(context, argument) ?? argument;
|
|
4368
|
-
if (!title)
|
|
4567
|
+
if (!title) {
|
|
4568
|
+
return;
|
|
4569
|
+
}
|
|
4369
4570
|
if (!isStringNode(title)) {
|
|
4370
4571
|
if (title.type === "BinaryExpression" && doesBinaryExpressionContainStringNode(title)) {
|
|
4371
4572
|
return;
|
|
@@ -4414,15 +4615,15 @@ var valid_title_default = createRule({
|
|
|
4414
4615
|
node: title
|
|
4415
4616
|
});
|
|
4416
4617
|
}
|
|
4417
|
-
const [firstWord] = titleString.split(" ");
|
|
4618
|
+
const [firstWord, ...rest] = titleString.split(" ");
|
|
4418
4619
|
if (firstWord.toLowerCase() === functionName) {
|
|
4419
4620
|
context.report({
|
|
4420
|
-
fix: (fixer) => [
|
|
4621
|
+
fix: rest.length > 0 ? (fixer) => [
|
|
4421
4622
|
fixer.replaceTextRange(
|
|
4422
4623
|
title.range,
|
|
4423
4624
|
quoteStringValue(title).replace(/^([`'"]).+? /u, "$1")
|
|
4424
4625
|
)
|
|
4425
|
-
],
|
|
4626
|
+
] : void 0,
|
|
4426
4627
|
messageId: "duplicatePrefix",
|
|
4427
4628
|
node: title
|
|
4428
4629
|
});
|
|
@@ -4594,6 +4795,8 @@ var sharedConfig = {
|
|
|
4594
4795
|
"playwright/missing-playwright-await": "error",
|
|
4595
4796
|
"playwright/no-conditional-expect": "warn",
|
|
4596
4797
|
"playwright/no-conditional-in-test": "warn",
|
|
4798
|
+
"playwright/no-duplicate-hooks": "warn",
|
|
4799
|
+
"playwright/no-duplicate-slow": "warn",
|
|
4597
4800
|
"playwright/no-element-handle": "warn",
|
|
4598
4801
|
"playwright/no-eval": "warn",
|
|
4599
4802
|
"playwright/no-focused-test": "error",
|
|
@@ -4610,6 +4813,11 @@ var sharedConfig = {
|
|
|
4610
4813
|
"playwright/no-wait-for-navigation": "error",
|
|
4611
4814
|
"playwright/no-wait-for-selector": "warn",
|
|
4612
4815
|
"playwright/no-wait-for-timeout": "warn",
|
|
4816
|
+
"playwright/prefer-hooks-in-order": "warn",
|
|
4817
|
+
"playwright/prefer-hooks-on-top": "warn",
|
|
4818
|
+
"playwright/prefer-locator": "warn",
|
|
4819
|
+
"playwright/prefer-to-have-count": "warn",
|
|
4820
|
+
"playwright/prefer-to-have-length": "warn",
|
|
4613
4821
|
"playwright/prefer-web-first-assertions": "error",
|
|
4614
4822
|
"playwright/valid-describe-callback": "error",
|
|
4615
4823
|
"playwright/valid-expect": "error",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-playwright",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "ESLint plugin for Playwright testing.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Mark Skelton <mark@mskelton.dev>",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"lint": "eslint .",
|
|
32
32
|
"format": "oxfmt .",
|
|
33
33
|
"format:check": "oxfmt --check .",
|
|
34
|
-
"test": "vitest --
|
|
34
|
+
"test": "vitest --hideSkippedTests",
|
|
35
35
|
"typecheck": "tsc --noEmit",
|
|
36
36
|
"ci": "yarn format:check && yarn lint && yarn typecheck"
|
|
37
37
|
},
|