Skip to content

Commit a5c44a3

Browse files
authored
Add support for test.step in missing-playwright-await rule. (#29)
* Fix security vulnerabilities * Add tests for test.step part of rule * Update docs * Finish rule updates * Update test.step message * Fix registry info
1 parent 8140198 commit a5c44a3

File tree

4 files changed

+68
-35
lines changed

4 files changed

+68
-35
lines changed

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,25 +43,33 @@ This plugin bundles two configurations to work with both `@playwright/test` or `
4343

4444
### `missing-playwright-await` 🔧
4545

46-
Enforce Playwright expect statements to be awaited.
46+
Identify false positives when async Playwright APIs are not properly awaited.
4747

4848
#### Example
4949

5050
Example of **incorrect** code for this rule:
5151

5252
```js
5353
expect(page).toMatchText("text");
54+
55+
test.step("clicks the button", async () => {
56+
await page.click("button");
57+
});
5458
```
5559

5660
Example of **correct** code for this rule:
5761

5862
```js
5963
await expect(page).toMatchText("text");
64+
65+
await test.step("clicks the button", async () => {
66+
await page.click("button");
67+
});
6068
```
6169

6270
#### Options
6371

64-
The rule accepts a non-required option which can be used to specify custom matchers which this rule should also warn about. This is useful when creating your own async matchers.
72+
The rule accepts a non-required option which can be used to specify custom matchers which this rule should also warn about. This is useful when creating your own async `expect` matchers.
6573

6674
```json
6775
{

lib/rules/missing-playwright-await.js

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
1-
function getPromiseMemberExpressionNode(node, matchers) {
2-
if (node.property.type === "Identifier" && matchers.has(node.property.name)) {
3-
return node;
1+
function getMemberPartName(node, part) {
2+
return node[part].type === "Identifier" ? node[part].name : undefined;
3+
}
4+
5+
function getMemberExpressionNode(node, matchers) {
6+
const propertyName = getMemberPartName(node, "property");
7+
8+
if (getMemberPartName(node, "object") === "test") {
9+
return propertyName === "step" ? { node, type: "testStep" } : undefined;
410
}
11+
12+
return matchers.has(propertyName) ? { node, type: "expect" } : undefined;
513
}
614

7-
function isValidExpect(node) {
8-
const parentType =
15+
function isValid(node) {
16+
const grandparentType =
917
node.parent && node.parent.parent && node.parent.parent.type;
1018

11-
// Don't report on nodes which are already awaited or returned
1219
return (
13-
parentType === "AwaitExpression" ||
14-
parentType === "ReturnStatement" ||
15-
parentType === "ArrowFunctionExpression"
20+
grandparentType === "AwaitExpression" ||
21+
grandparentType === "ReturnStatement" ||
22+
grandparentType === "ArrowFunctionExpression"
1623
);
1724
}
1825

@@ -70,28 +77,28 @@ module.exports = {
7077

7178
return {
7279
MemberExpression(statement) {
73-
const node = getPromiseMemberExpressionNode(statement, matchers);
74-
if (!node || isValidExpect(node)) return;
80+
const result = getMemberExpressionNode(statement, matchers);
7581

76-
context.report({
77-
fix(fixer) {
78-
return fixer.insertTextBefore(node, "await ");
79-
},
80-
messageId: "missingAwait",
81-
node,
82-
});
82+
if (result && !isValid(result.node)) {
83+
context.report({
84+
fix: (fixer) => fixer.insertTextBefore(result.node, "await "),
85+
messageId: result.type,
86+
node: result.node,
87+
});
88+
}
8389
},
8490
};
8591
},
8692
meta: {
8793
docs: {
8894
category: "Possible Errors",
89-
description: "Enforce expect-playwright matchers to be awaited.",
95+
description: `Identify false positives when async Playwright APIs are not properly awaited.`,
9096
recommended: true,
9197
},
9298
fixable: "code",
9399
messages: {
94-
missingAwait: "expect-playwright matchers must be awaited or returned.",
100+
expect: "'expect' matchers must be awaited or returned.",
101+
testStep: "'test.step' must be awaited or returned.",
95102
},
96103
schema: [
97104
{

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/missing-playwright-await.spec.js

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ RuleTester.setDefaultConfig({
99

1010
const wrapInTest = (input) => `test('a', async () => { ${input} })`;
1111

12-
const invalid = (code, output, options = []) => ({
12+
const invalid = (messageId, code, output, options = []) => ({
1313
code: wrapInTest(code),
14-
errors: [{ messageId: "missingAwait" }],
14+
errors: [{ messageId }],
1515
options,
1616
output: wrapInTest(output),
1717
});
@@ -25,32 +25,46 @@ const options = [{ customMatchers: ["toBeCustomThing"] }];
2525

2626
new RuleTester().run("missing-playwright-await", rule, {
2727
invalid: [
28-
invalid(`expect(page).toBeChecked()`, `await expect(page).toBeChecked()`),
2928
invalid(
30-
`expect(page).not.toBeEnabled()`,
31-
`await expect(page).not.toBeEnabled()`
29+
"expect",
30+
"expect(page).toBeChecked()",
31+
"await expect(page).toBeChecked()"
32+
),
33+
invalid(
34+
"expect",
35+
"expect(page).not.toBeEnabled()",
36+
"await expect(page).not.toBeEnabled()"
3237
),
3338

3439
// Custom matchers
3540
invalid(
36-
`expect(page).toBeCustomThing(false)`,
37-
`await expect(page).toBeCustomThing(false)`,
41+
"expect",
42+
"expect(page).toBeCustomThing(false)",
43+
"await expect(page).toBeCustomThing(false)",
3844
options
3945
),
4046
invalid(
41-
`expect(page).not.toBeCustomThing(true)`,
42-
`await expect(page).not.toBeCustomThing(true)`,
47+
"expect",
48+
"expect(page).not.toBeCustomThing(true)",
49+
"await expect(page).not.toBeCustomThing(true)",
4350
options
4451
),
52+
53+
// test.step
54+
invalid(
55+
"testStep",
56+
"test.step('foo', async () => {})",
57+
"await test.step('foo', async () => {})"
58+
),
4559
],
4660
valid: [
47-
valid(`await expect(page).toEqualTitle("text")`),
48-
valid(`await expect(page).not.toHaveText("text")`),
61+
valid('await expect(page).toEqualTitle("text")'),
62+
valid('await expect(page).not.toHaveText("text")'),
4963

5064
// Doesn't require an await when returning
51-
valid(`return expect(page).toHaveText("text")`),
65+
valid('return expect(page).toHaveText("text")'),
5266
{
53-
code: `const a = () => expect(page).toHaveText("text")`,
67+
code: 'const a = () => expect(page).toHaveText("text")',
5468
options,
5569
},
5670

@@ -60,5 +74,8 @@ new RuleTester().run("missing-playwright-await", rule, {
6074
valid("await expect(page).toBeCustomThing(true)", options),
6175
valid("await expect(page).toBeCustomThing(true)"),
6276
valid("expect(page).toBeCustomThing(true)"),
77+
78+
// test.step
79+
valid("await test.step('foo', async () => {})"),
6380
],
6481
});

0 commit comments

Comments
 (0)