eslint-plugin-playwright 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/lib/index.js +3 -0
- package/lib/rules/expect-expect.js +7 -3
- package/lib/rules/no-conditional-in-test.js +1 -1
- package/lib/rules/no-focused-test.js +1 -1
- package/lib/rules/no-skipped-test.js +20 -1
- package/lib/rules/prefer-lowercase-title.js +2 -5
- package/lib/rules/prefer-to-contain.js +1 -1
- package/lib/rules/require-top-level-describe.js +1 -1
- package/lib/rules/valid-title.js +210 -0
- package/lib/utils/ast.js +11 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,7 +101,7 @@ under the `playwright` key. It supports the following settings:
|
|
|
101
101
|
the presence of at least one assertion per test case. This allows such rules
|
|
102
102
|
to recognise custom assertion functions as valid assertions. The global
|
|
103
103
|
setting applies to all modules. The
|
|
104
|
-
[`expect-expect` rule accepts an option by the same name](
|
|
104
|
+
[`expect-expect` rule accepts an option by the same name](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/expect-expect.md#additionalassertfunctionnames)
|
|
105
105
|
to enable per-module configuration (.e.g, for module-specific custom assert
|
|
106
106
|
functions).
|
|
107
107
|
|
|
@@ -173,3 +173,4 @@ command line option.\
|
|
|
173
173
|
| | | | [require-top-level-describe](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/require-top-level-describe.md) | Require test cases and hooks to be inside a `test.describe` block |
|
|
174
174
|
| | 🔧 | | [require-soft-assertions](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/require-soft-assertions.md) | Require assertions to use `expect.soft()` |
|
|
175
175
|
| ✔ | | | [valid-expect](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-expect.md) | Enforce valid `expect()` usage |
|
|
176
|
+
| ✔ | 🔧 | | [valid-title](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-title.md) | Enforce valid titles |
|
package/lib/index.js
CHANGED
|
@@ -28,6 +28,7 @@ const prefer_web_first_assertions_1 = require("./rules/prefer-web-first-assertio
|
|
|
28
28
|
const require_soft_assertions_1 = require("./rules/require-soft-assertions");
|
|
29
29
|
const require_top_level_describe_1 = require("./rules/require-top-level-describe");
|
|
30
30
|
const valid_expect_1 = require("./rules/valid-expect");
|
|
31
|
+
const valid_title_1 = require("./rules/valid-title");
|
|
31
32
|
const index = {
|
|
32
33
|
configs: {},
|
|
33
34
|
rules: {
|
|
@@ -59,6 +60,7 @@ const index = {
|
|
|
59
60
|
'require-soft-assertions': require_soft_assertions_1.default,
|
|
60
61
|
'require-top-level-describe': require_top_level_describe_1.default,
|
|
61
62
|
'valid-expect': valid_expect_1.default,
|
|
63
|
+
'valid-title': valid_title_1.default,
|
|
62
64
|
},
|
|
63
65
|
};
|
|
64
66
|
const sharedConfig = {
|
|
@@ -81,6 +83,7 @@ const sharedConfig = {
|
|
|
81
83
|
'playwright/no-wait-for-timeout': 'warn',
|
|
82
84
|
'playwright/prefer-web-first-assertions': 'error',
|
|
83
85
|
'playwright/valid-expect': 'error',
|
|
86
|
+
'playwright/valid-title': 'error',
|
|
84
87
|
},
|
|
85
88
|
};
|
|
86
89
|
const legacyConfig = {
|
|
@@ -4,10 +4,11 @@ const ast_1 = require("../utils/ast");
|
|
|
4
4
|
const misc_1 = require("../utils/misc");
|
|
5
5
|
function isAssertionCall(node, additionalAssertFunctionNames) {
|
|
6
6
|
return ((0, ast_1.isExpectCall)(node) ||
|
|
7
|
-
additionalAssertFunctionNames.find((name) => (0, ast_1.
|
|
7
|
+
additionalAssertFunctionNames.find((name) => (0, ast_1.dig)(node.callee, name)));
|
|
8
8
|
}
|
|
9
9
|
exports.default = {
|
|
10
10
|
create(context) {
|
|
11
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
11
12
|
const unchecked = [];
|
|
12
13
|
const additionalAssertFunctionNames = (0, misc_1.getAdditionalAssertFunctionNames)(context);
|
|
13
14
|
function checkExpressions(nodes) {
|
|
@@ -21,11 +22,14 @@ exports.default = {
|
|
|
21
22
|
}
|
|
22
23
|
return {
|
|
23
24
|
CallExpression(node) {
|
|
24
|
-
if ((0, ast_1.
|
|
25
|
+
if ((0, ast_1.isTestCall)(node, ['fixme', 'only', 'skip'])) {
|
|
25
26
|
unchecked.push(node);
|
|
26
27
|
}
|
|
27
28
|
else if (isAssertionCall(node, additionalAssertFunctionNames)) {
|
|
28
|
-
|
|
29
|
+
const ancestors = sourceCode.getAncestors
|
|
30
|
+
? sourceCode.getAncestors(node)
|
|
31
|
+
: context.getAncestors();
|
|
32
|
+
checkExpressions(ancestors);
|
|
29
33
|
}
|
|
30
34
|
},
|
|
31
35
|
'Program:exit'() {
|
|
@@ -5,7 +5,7 @@ exports.default = {
|
|
|
5
5
|
create(context) {
|
|
6
6
|
function checkConditional(node) {
|
|
7
7
|
const call = (0, ast_1.findParent)(node, 'CallExpression');
|
|
8
|
-
if (call && (0, ast_1.
|
|
8
|
+
if (call && (0, ast_1.isTestCall)(call)) {
|
|
9
9
|
context.report({ messageId: 'conditionalInTest', node });
|
|
10
10
|
}
|
|
11
11
|
}
|
|
@@ -5,7 +5,7 @@ exports.default = {
|
|
|
5
5
|
create(context) {
|
|
6
6
|
return {
|
|
7
7
|
CallExpression(node) {
|
|
8
|
-
if (((0, ast_1.
|
|
8
|
+
if (((0, ast_1.isTestCall)(node) || (0, ast_1.isDescribeCall)(node)) &&
|
|
9
9
|
node.callee.type === 'MemberExpression' &&
|
|
10
10
|
(0, ast_1.isPropertyAccessor)(node.callee, 'only')) {
|
|
11
11
|
const { callee } = node;
|
|
@@ -5,11 +5,18 @@ exports.default = {
|
|
|
5
5
|
create(context) {
|
|
6
6
|
return {
|
|
7
7
|
CallExpression(node) {
|
|
8
|
+
const options = context.options[0] || {};
|
|
9
|
+
const allowConditional = !!options.allowConditional;
|
|
8
10
|
const { callee } = node;
|
|
9
11
|
if (((0, ast_1.isTestIdentifier)(callee) || (0, ast_1.isDescribeCall)(node)) &&
|
|
10
12
|
callee.type === 'MemberExpression' &&
|
|
11
13
|
(0, ast_1.isPropertyAccessor)(callee, 'skip')) {
|
|
12
|
-
const isHook = (0, ast_1.
|
|
14
|
+
const isHook = (0, ast_1.isTestCall)(node) || (0, ast_1.isDescribeCall)(node);
|
|
15
|
+
// If allowConditional is enabled and it's not a test/describe hook,
|
|
16
|
+
// we ignore any `test.skip` calls that have no arguments.
|
|
17
|
+
if (!isHook && allowConditional && node.arguments.length) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
13
20
|
context.report({
|
|
14
21
|
messageId: 'noSkippedTest',
|
|
15
22
|
node: isHook ? callee.property : node,
|
|
@@ -43,6 +50,18 @@ exports.default = {
|
|
|
43
50
|
noSkippedTest: 'Unexpected use of the `.skip()` annotation.',
|
|
44
51
|
removeSkippedTestAnnotation: 'Remove the `.skip()` annotation.',
|
|
45
52
|
},
|
|
53
|
+
schema: [
|
|
54
|
+
{
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
properties: {
|
|
57
|
+
allowConditional: {
|
|
58
|
+
default: false,
|
|
59
|
+
type: 'boolean',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
type: 'object',
|
|
63
|
+
},
|
|
64
|
+
],
|
|
46
65
|
type: 'suggestion',
|
|
47
66
|
},
|
|
48
67
|
};
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const ast_1 = require("../utils/ast");
|
|
4
|
-
function isString(node) {
|
|
5
|
-
return node && ((0, ast_1.isStringLiteral)(node) || node.type === 'TemplateLiteral');
|
|
6
|
-
}
|
|
7
4
|
exports.default = {
|
|
8
5
|
create(context) {
|
|
9
6
|
const { allowedPrefixes, ignore, ignoreTopLevelDescribe } = {
|
|
@@ -17,7 +14,7 @@ exports.default = {
|
|
|
17
14
|
CallExpression(node) {
|
|
18
15
|
const method = (0, ast_1.isDescribeCall)(node)
|
|
19
16
|
? 'test.describe'
|
|
20
|
-
: (0, ast_1.
|
|
17
|
+
: (0, ast_1.isTestCall)(node)
|
|
21
18
|
? 'test'
|
|
22
19
|
: null;
|
|
23
20
|
if (method === 'test.describe') {
|
|
@@ -30,7 +27,7 @@ exports.default = {
|
|
|
30
27
|
return;
|
|
31
28
|
}
|
|
32
29
|
const [title] = node.arguments;
|
|
33
|
-
if (!
|
|
30
|
+
if (!(0, ast_1.isStringNode)(title)) {
|
|
34
31
|
return;
|
|
35
32
|
}
|
|
36
33
|
const description = (0, ast_1.getStringValue)(title);
|
|
@@ -28,7 +28,7 @@ exports.default = {
|
|
|
28
28
|
const notModifier = expectCall.modifiers.find((node) => (0, ast_1.getStringValue)(node) === 'not');
|
|
29
29
|
context.report({
|
|
30
30
|
fix(fixer) {
|
|
31
|
-
const sourceCode = context.getSourceCode();
|
|
31
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
32
32
|
// We need to negate the expectation if the current expected
|
|
33
33
|
// value is itself negated by the "not" modifier
|
|
34
34
|
const addNotModifier = matcherArg.type === 'Literal' &&
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ast_1 = require("../utils/ast");
|
|
4
|
+
const doesBinaryExpressionContainStringNode = (binaryExp) => {
|
|
5
|
+
if ((0, ast_1.isStringNode)(binaryExp.right)) {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
if (binaryExp.left.type === 'BinaryExpression') {
|
|
9
|
+
return doesBinaryExpressionContainStringNode(binaryExp.left);
|
|
10
|
+
}
|
|
11
|
+
return (0, ast_1.isStringNode)(binaryExp.left);
|
|
12
|
+
};
|
|
13
|
+
const quoteStringValue = (node) => node.type === 'TemplateLiteral'
|
|
14
|
+
? `\`${node.quasis[0].value.raw}\``
|
|
15
|
+
: node.raw ?? '';
|
|
16
|
+
const compileMatcherPattern = (matcherMaybeWithMessage) => {
|
|
17
|
+
const [matcher, message] = Array.isArray(matcherMaybeWithMessage)
|
|
18
|
+
? matcherMaybeWithMessage
|
|
19
|
+
: [matcherMaybeWithMessage];
|
|
20
|
+
return [new RegExp(matcher, 'u'), message];
|
|
21
|
+
};
|
|
22
|
+
const compileMatcherPatterns = (matchers) => {
|
|
23
|
+
if (typeof matchers === 'string' || Array.isArray(matchers)) {
|
|
24
|
+
const compiledMatcher = compileMatcherPattern(matchers);
|
|
25
|
+
return {
|
|
26
|
+
describe: compiledMatcher,
|
|
27
|
+
test: compiledMatcher,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
describe: matchers.describe
|
|
32
|
+
? compileMatcherPattern(matchers.describe)
|
|
33
|
+
: null,
|
|
34
|
+
test: matchers.test ? compileMatcherPattern(matchers.test) : null,
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
const MatcherAndMessageSchema = {
|
|
38
|
+
additionalItems: false,
|
|
39
|
+
items: { type: 'string' },
|
|
40
|
+
maxItems: 2,
|
|
41
|
+
minItems: 1,
|
|
42
|
+
type: 'array',
|
|
43
|
+
};
|
|
44
|
+
exports.default = {
|
|
45
|
+
create(context) {
|
|
46
|
+
const opts = context.options?.[0] ?? {};
|
|
47
|
+
const { disallowedWords = [], ignoreSpaces = false, ignoreTypeOfDescribeName = false, ignoreTypeOfTestName = false, mustMatch, mustNotMatch, } = opts;
|
|
48
|
+
const disallowedWordsRegexp = new RegExp(`\\b(${disallowedWords.join('|')})\\b`, 'iu');
|
|
49
|
+
const mustNotMatchPatterns = compileMatcherPatterns(mustNotMatch ?? {});
|
|
50
|
+
const mustMatchPatterns = compileMatcherPatterns(mustMatch ?? {});
|
|
51
|
+
return {
|
|
52
|
+
CallExpression(node) {
|
|
53
|
+
const isDescribe = (0, ast_1.isDescribeCall)(node);
|
|
54
|
+
const isTest = (0, ast_1.isTestCall)(node);
|
|
55
|
+
if (!isDescribe && !isTest) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const [argument] = node.arguments;
|
|
59
|
+
if (!argument) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (!(0, ast_1.isStringNode)(argument)) {
|
|
63
|
+
if (argument.type === 'BinaryExpression' &&
|
|
64
|
+
doesBinaryExpressionContainStringNode(argument)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (!((isDescribe && ignoreTypeOfDescribeName) ||
|
|
68
|
+
(isTest && ignoreTypeOfTestName)) &&
|
|
69
|
+
argument.type !== 'TemplateLiteral') {
|
|
70
|
+
context.report({
|
|
71
|
+
loc: argument.loc,
|
|
72
|
+
messageId: 'titleMustBeString',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const title = (0, ast_1.getStringValue)(argument);
|
|
78
|
+
const functionName = isDescribe ? 'describe' : 'test';
|
|
79
|
+
if (!title) {
|
|
80
|
+
context.report({
|
|
81
|
+
data: { functionName },
|
|
82
|
+
messageId: 'emptyTitle',
|
|
83
|
+
node,
|
|
84
|
+
});
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (disallowedWords.length > 0) {
|
|
88
|
+
const disallowedMatch = disallowedWordsRegexp.exec(title);
|
|
89
|
+
if (disallowedMatch) {
|
|
90
|
+
context.report({
|
|
91
|
+
data: { word: disallowedMatch[1] },
|
|
92
|
+
messageId: 'disallowedWord',
|
|
93
|
+
node: argument,
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (ignoreSpaces === false && title.trim().length !== title.length) {
|
|
99
|
+
context.report({
|
|
100
|
+
fix: (fixer) => [
|
|
101
|
+
fixer.replaceTextRange(argument.range, quoteStringValue(argument)
|
|
102
|
+
.replace(/^([`'"]) +?/u, '$1')
|
|
103
|
+
.replace(/ +?([`'"])$/u, '$1')),
|
|
104
|
+
],
|
|
105
|
+
messageId: 'accidentalSpace',
|
|
106
|
+
node: argument,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const [firstWord] = title.split(' ');
|
|
110
|
+
if (firstWord.toLowerCase() === functionName) {
|
|
111
|
+
context.report({
|
|
112
|
+
fix: (fixer) => [
|
|
113
|
+
fixer.replaceTextRange(argument.range, quoteStringValue(argument).replace(/^([`'"]).+? /u, '$1')),
|
|
114
|
+
],
|
|
115
|
+
messageId: 'duplicatePrefix',
|
|
116
|
+
node: argument,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const [mustNotMatchPattern, mustNotMatchMessage] = mustNotMatchPatterns[functionName] ?? [];
|
|
120
|
+
if (mustNotMatchPattern && mustNotMatchPattern.test(title)) {
|
|
121
|
+
context.report({
|
|
122
|
+
data: {
|
|
123
|
+
functionName,
|
|
124
|
+
message: mustNotMatchMessage ?? '',
|
|
125
|
+
pattern: String(mustNotMatchPattern),
|
|
126
|
+
},
|
|
127
|
+
messageId: mustNotMatchMessage
|
|
128
|
+
? 'mustNotMatchCustom'
|
|
129
|
+
: 'mustNotMatch',
|
|
130
|
+
node: argument,
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const [mustMatchPattern, mustMatchMessage] = mustMatchPatterns[functionName] ?? [];
|
|
135
|
+
if (mustMatchPattern && !mustMatchPattern.test(title)) {
|
|
136
|
+
context.report({
|
|
137
|
+
data: {
|
|
138
|
+
functionName,
|
|
139
|
+
message: mustMatchMessage ?? '',
|
|
140
|
+
pattern: String(mustMatchPattern),
|
|
141
|
+
},
|
|
142
|
+
messageId: mustMatchMessage ? 'mustMatchCustom' : 'mustMatch',
|
|
143
|
+
node: argument,
|
|
144
|
+
});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
meta: {
|
|
151
|
+
docs: {
|
|
152
|
+
category: 'Best Practices',
|
|
153
|
+
description: 'Enforce valid titles',
|
|
154
|
+
recommended: true,
|
|
155
|
+
url: 'https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-title.md',
|
|
156
|
+
},
|
|
157
|
+
fixable: 'code',
|
|
158
|
+
messages: {
|
|
159
|
+
accidentalSpace: 'should not have leading or trailing spaces',
|
|
160
|
+
disallowedWord: '"{{ word }}" is not allowed in test titles',
|
|
161
|
+
duplicatePrefix: 'should not have duplicate prefix',
|
|
162
|
+
emptyTitle: '{{ functionName }} should not have an empty title',
|
|
163
|
+
mustMatch: '{{ functionName }} should match {{ pattern }}',
|
|
164
|
+
mustMatchCustom: '{{ message }}',
|
|
165
|
+
mustNotMatch: '{{ functionName }} should not match {{ pattern }}',
|
|
166
|
+
mustNotMatchCustom: '{{ message }}',
|
|
167
|
+
titleMustBeString: 'Title must be a string',
|
|
168
|
+
},
|
|
169
|
+
schema: [
|
|
170
|
+
{
|
|
171
|
+
additionalProperties: false,
|
|
172
|
+
patternProperties: {
|
|
173
|
+
[/^must(?:Not)?Match$/u.source]: {
|
|
174
|
+
oneOf: [
|
|
175
|
+
{ type: 'string' },
|
|
176
|
+
MatcherAndMessageSchema,
|
|
177
|
+
{
|
|
178
|
+
additionalProperties: {
|
|
179
|
+
oneOf: [{ type: 'string' }, MatcherAndMessageSchema],
|
|
180
|
+
},
|
|
181
|
+
propertyNames: { enum: ['describe', 'test'] },
|
|
182
|
+
type: 'object',
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
properties: {
|
|
188
|
+
disallowedWords: {
|
|
189
|
+
items: { type: 'string' },
|
|
190
|
+
type: 'array',
|
|
191
|
+
},
|
|
192
|
+
ignoreSpaces: {
|
|
193
|
+
default: false,
|
|
194
|
+
type: 'boolean',
|
|
195
|
+
},
|
|
196
|
+
ignoreTypeOfDescribeName: {
|
|
197
|
+
default: false,
|
|
198
|
+
type: 'boolean',
|
|
199
|
+
},
|
|
200
|
+
ignoreTypeOfTestName: {
|
|
201
|
+
default: false,
|
|
202
|
+
type: 'boolean',
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
type: 'object',
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
type: 'suggestion',
|
|
209
|
+
},
|
|
210
|
+
};
|
package/lib/utils/ast.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isPageMethod = exports.getMatchers = exports.isExpectCall = exports.getExpectType = exports.isTestHook = exports.
|
|
3
|
+
exports.isPageMethod = exports.dig = exports.getMatchers = exports.isExpectCall = exports.getExpectType = exports.isTestHook = exports.isTestCall = exports.findParent = exports.isDescribeCall = exports.isTestIdentifier = exports.isPropertyAccessor = exports.isStringNode = exports.isBooleanLiteral = exports.isStringLiteral = exports.isIdentifier = exports.getRawValue = exports.getStringValue = void 0;
|
|
4
4
|
function getStringValue(node) {
|
|
5
5
|
if (!node)
|
|
6
6
|
return '';
|
|
@@ -28,6 +28,9 @@ function isLiteral(node, type, value) {
|
|
|
28
28
|
? typeof node.value === type
|
|
29
29
|
: node.value === value));
|
|
30
30
|
}
|
|
31
|
+
const isTemplateLiteral = (node, value) => node.type === 'TemplateLiteral' &&
|
|
32
|
+
node.quasis.length === 1 && // bail out if not simple
|
|
33
|
+
(value === undefined || node.quasis[0].value.raw === value);
|
|
31
34
|
function isStringLiteral(node, value) {
|
|
32
35
|
return isLiteral(node, 'string', value);
|
|
33
36
|
}
|
|
@@ -36,6 +39,10 @@ function isBooleanLiteral(node, value) {
|
|
|
36
39
|
return isLiteral(node, 'boolean', value);
|
|
37
40
|
}
|
|
38
41
|
exports.isBooleanLiteral = isBooleanLiteral;
|
|
42
|
+
function isStringNode(node) {
|
|
43
|
+
return node && (isStringLiteral(node) || isTemplateLiteral(node));
|
|
44
|
+
}
|
|
45
|
+
exports.isStringNode = isStringNode;
|
|
39
46
|
function isPropertyAccessor(node, name) {
|
|
40
47
|
return getStringValue(node.property) === name;
|
|
41
48
|
}
|
|
@@ -76,7 +83,7 @@ function findParent(node, type) {
|
|
|
76
83
|
: findParent(node.parent, type);
|
|
77
84
|
}
|
|
78
85
|
exports.findParent = findParent;
|
|
79
|
-
function
|
|
86
|
+
function isTestCall(node, modifiers) {
|
|
80
87
|
return (isTestIdentifier(node.callee) &&
|
|
81
88
|
!isDescribeCall(node) &&
|
|
82
89
|
(node.callee.type !== 'MemberExpression' ||
|
|
@@ -85,7 +92,7 @@ function isTest(node, modifiers) {
|
|
|
85
92
|
node.arguments.length === 2 &&
|
|
86
93
|
['ArrowFunctionExpression', 'FunctionExpression'].includes(node.arguments[1].type));
|
|
87
94
|
}
|
|
88
|
-
exports.
|
|
95
|
+
exports.isTestCall = isTestCall;
|
|
89
96
|
const testHooks = new Set(['afterAll', 'afterEach', 'beforeAll', 'beforeEach']);
|
|
90
97
|
function isTestHook(node) {
|
|
91
98
|
return (node.callee.type === 'MemberExpression' &&
|
|
@@ -132,6 +139,7 @@ function dig(node, identifier) {
|
|
|
132
139
|
? isIdentifier(node, identifier)
|
|
133
140
|
: false;
|
|
134
141
|
}
|
|
142
|
+
exports.dig = dig;
|
|
135
143
|
function isPageMethod(node, name) {
|
|
136
144
|
return (node.callee.type === 'MemberExpression' &&
|
|
137
145
|
dig(node.callee.object, /(^(page|frame)|(Page|Frame)$)/) &&
|
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": "0.
|
|
4
|
+
"version": "0.19.0",
|
|
5
5
|
"packageManager": "pnpm@8.8.0",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"repository": "https://github.com/playwright-community/eslint-plugin-playwright",
|