Skip to content

Commit 5c031ef

Browse files
authored
Require soft assertions (#118)
* Better scope for no-skipped-tests * Add examples folder for testing * Better scope for no-useless-not * max-nested-describe * missing-playwright-await * no-focused-test * no-force-option * no-page-pause * no-restricted-matchers * Update ecma version * no-wait-for-timeout * prefer-lowercase-title * prefer-strict-equal * prefer-to-be * prefer-to-have-length * require-top-level-describe * valid-expect * no-conditional-in-test * Update example eslintrc * no-eval * Cleanup * no-element-handle * Order * Use dedent * Update examples * Docs * Finish rule * Update docs
1 parent e5958a0 commit 5c031ef

File tree

6 files changed

+115
-21
lines changed

6 files changed

+115
-21
lines changed

README.md

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,23 +49,24 @@ command line option.\
4949
💡: Some problems reported by this rule are manually fixable by editor
5050
[suggestions](https://eslint.org/docs/latest/developer-guide/working-with-rules#providing-suggestions).
5151

52-
|| 🔧 | 💡 | Rule | Description |
53-
| :-: | :-: | :-: | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
54-
|| | | [max-nested-describe](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/max-nested-describe.md) | Enforces a maximum depth to nested describe calls |
55-
|| 🔧 | | [missing-playwright-await](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/missing-playwright-await.md) | Enforce Playwright APIs to be awaited |
56-
|| | | [no-conditional-in-test](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-conditional-in-test.md) | Disallow conditional logic in tests |
57-
|| | 💡 | [no-element-handle](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-element-handle.md) | Disallow usage of element handles |
58-
|| | | [no-eval](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-eval.md) | Disallow usage of `page.$eval` and `page.$$eval` |
59-
|| | 💡 | [no-focused-test](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-focused-test.md) | Disallow usage of `.only` annotation |
60-
|| | | [no-force-option](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-force-option.md) | Disallow usage of the `{ force: true }` option |
61-
|| | | [no-page-pause](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-page-pause.md) | Disallow using `page.pause` |
62-
| | | | [no-restricted-matchers](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-restricted-matchers.md) | Disallow specific matchers & modifiers |
63-
|| | 💡 | [no-skipped-test](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-skipped-test.md) | Disallow usage of the `.skip` annotation |
64-
|| 🔧 | | [no-useless-not](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-useless-not.md) | Disallow usage of `not` matchers when a specific matcher exists |
65-
|| | 💡 | [no-wait-for-timeout](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-timeout.md) | Disallow usage of `page.waitForTimeout` |
66-
| | | 💡 | [prefer-strict-equal](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-strict-equal.md) | Suggest using `toStrictEqual()` |
67-
| | 🔧 | | [prefer-lowercase-title](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-lowercase-title.md) | Enforce lowercase test names |
68-
| | 🔧 | | [prefer-to-be](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-be.md) | Suggest using `toBe()` |
69-
| | 🔧 | | [prefer-to-have-length](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-have-length.md) | Suggest using `toHaveLength()` |
70-
| | | | [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 |
71-
|| | | [valid-expect](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-expect.md) | Enforce valid `expect()` usage |
52+
|| 🔧 | 💡 | Rule | Description |
53+
| :-: | :-: | :-: | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
54+
|| | | [max-nested-describe](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/max-nested-describe.md) | Enforces a maximum depth to nested describe calls |
55+
|| 🔧 | | [missing-playwright-await](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/missing-playwright-await.md) | Enforce Playwright APIs to be awaited |
56+
|| | | [no-conditional-in-test](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-conditional-in-test.md) | Disallow conditional logic in tests |
57+
|| | 💡 | [no-element-handle](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-element-handle.md) | Disallow usage of element handles |
58+
|| | | [no-eval](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-eval.md) | Disallow usage of `page.$eval` and `page.$$eval` |
59+
|| | 💡 | [no-focused-test](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-focused-test.md) | Disallow usage of `.only` annotation |
60+
|| | | [no-force-option](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-force-option.md) | Disallow usage of the `{ force: true }` option |
61+
|| | | [no-page-pause](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-page-pause.md) | Disallow using `page.pause` |
62+
| | | | [no-restricted-matchers](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-restricted-matchers.md) | Disallow specific matchers & modifiers |
63+
|| | 💡 | [no-skipped-test](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-skipped-test.md) | Disallow usage of the `.skip` annotation |
64+
|| 🔧 | | [no-useless-not](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-useless-not.md) | Disallow usage of `not` matchers when a specific matcher exists |
65+
|| | 💡 | [no-wait-for-timeout](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-timeout.md) | Disallow usage of `page.waitForTimeout` |
66+
| | | 💡 | [prefer-strict-equal](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-strict-equal.md) | Suggest using `toStrictEqual()` |
67+
| | 🔧 | | [prefer-lowercase-title](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-lowercase-title.md) | Enforce lowercase test names |
68+
| | 🔧 | | [prefer-to-be](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-be.md) | Suggest using `toBe()` |
69+
| | 🔧 | | [prefer-to-have-length](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-have-length.md) | Suggest using `toHaveLength()` |
70+
| | | | [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 |
71+
| | 🔧 | | [require-require-soft-assertions](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/require-require-soft-assertions.md) | Require assertions to use `expect.soft()` |
72+
|| | | [valid-expect](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-expect.md) | Enforce valid `expect()` usage |
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Require soft assertions (`require-soft-assertions`)
2+
3+
Some find it easier to write longer test that perform more assertions per test.
4+
In cases like these, it can be helpful to require
5+
[soft assertions](https://playwright.dev/docs/test-assertions#soft-assertions)
6+
in your tests.
7+
8+
This rule is not enabled by default and is only intended to be used it if fits
9+
your workflow. If you aren't sure if you should use this rule, you probably
10+
shouldn't 🙂.
11+
12+
## Rule Details
13+
14+
Examples of **incorrect** code for this rule:
15+
16+
```javascript
17+
await expect(page.locator('foo')).toHaveText('bar');
18+
await expect(page).toHaveTitle('baz');
19+
```
20+
21+
Examples of **correct** code for this rule:
22+
23+
```javascript
24+
await expect.soft(page.locator('foo')).toHaveText('bar');
25+
await expect.soft(page).toHaveTitle('baz');
26+
```

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import preferLowercaseTitle from './rules/prefer-lowercase-title';
1414
import preferToBe from './rules/prefer-to-be';
1515
import preferToHaveLength from './rules/prefer-to-have-length';
1616
import preferStrictEqual from './rules/prefer-strict-equal';
17+
import requireSoftAssertions from './rules/require-soft-assertions';
1718
import requireTopLevelDescribe from './rules/require-top-level-describe';
1819
import validExpect from './rules/valid-expect';
1920

@@ -91,6 +92,7 @@ export = {
9192
'prefer-to-be': preferToBe,
9293
'prefer-to-have-length': preferToHaveLength,
9394
'require-top-level-describe': requireTopLevelDescribe,
95+
'require-soft-assertions': requireSoftAssertions,
9496
'valid-expect': validExpect,
9597
},
9698
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Rule } from 'eslint';
2+
import { getExpectType } from '../utils/ast';
3+
4+
export default {
5+
create(context) {
6+
return {
7+
CallExpression(node) {
8+
if (getExpectType(node) === 'standalone') {
9+
context.report({
10+
node: node.callee,
11+
messageId: 'requireSoft',
12+
fix: (fixer) => fixer.insertTextAfter(node.callee, '.soft'),
13+
});
14+
}
15+
},
16+
};
17+
},
18+
meta: {
19+
docs: {
20+
description: 'Require all assertions to use `expect.soft`',
21+
recommended: false,
22+
url: 'https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/require-soft-assertions.md',
23+
},
24+
messages: {
25+
requireSoft: 'Unexpected non-soft assertion',
26+
},
27+
fixable: 'code',
28+
type: 'suggestion',
29+
schema: [],
30+
},
31+
} as Rule.RuleModule;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import rule from '../../src/rules/require-soft-assertions';
2+
import { runRuleTester } from '../utils/rule-tester';
3+
4+
const messageId = 'requireSoft';
5+
6+
runRuleTester('require-soft-assertions', rule, {
7+
valid: [
8+
'expect.soft(page).toHaveTitle("baz")',
9+
'expect.soft(page.locator("foo")).toHaveText("bar")',
10+
'expect["soft"](foo).toBe("bar")',
11+
'expect[`soft`](bar).toHaveText("bar")',
12+
'expect.poll(() => foo).toBe("bar")',
13+
'expect["poll"](() => foo).toBe("bar")',
14+
'expect[`poll`](() => foo).toBe("bar")',
15+
],
16+
invalid: [
17+
{
18+
code: 'expect(page).toHaveTitle("baz")',
19+
output: 'expect.soft(page).toHaveTitle("baz")',
20+
errors: [{ messageId, line: 1, column: 1, endColumn: 7 }],
21+
},
22+
{
23+
code: 'expect(page.locator("foo")).toHaveText("bar")',
24+
output: 'expect.soft(page.locator("foo")).toHaveText("bar")',
25+
errors: [{ messageId, line: 1, column: 1, endColumn: 7 }],
26+
},
27+
{
28+
code: 'await expect(page.locator("foo")).toHaveText("bar")',
29+
output: 'await expect.soft(page.locator("foo")).toHaveText("bar")',
30+
errors: [{ messageId, line: 1, column: 7, endColumn: 13 }],
31+
},
32+
],
33+
});

test/utils/rule-tester.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import { RuleTester } from 'eslint';
1212
export function runRuleTester(...args: Parameters<RuleTester['run']>) {
1313
const config = {
1414
parserOptions: {
15-
ecmaVersion: 2018,
15+
ecmaVersion: 2022,
16+
sourceType: 'module',
1617
},
1718
};
1819

0 commit comments

Comments
 (0)