Skip to content

Commit

Permalink
Require soft assertions (#118)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
mskelton authored Jan 17, 2023
1 parent e5958a0 commit 5c031ef
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 21 deletions.
41 changes: 21 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,23 +49,24 @@ command line option.\
💡: Some problems reported by this rule are manually fixable by editor
[suggestions](https://eslint.org/docs/latest/developer-guide/working-with-rules#providing-suggestions).

|| 🔧 | 💡 | Rule | Description |
| :-: | :-: | :-: | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
|| | | [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 |
|| 🔧 | | [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 |
|| | | [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 |
|| | 💡 | [no-element-handle](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-element-handle.md) | Disallow usage of element handles |
|| | | [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` |
|| | 💡 | [no-focused-test](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-focused-test.md) | Disallow usage of `.only` annotation |
|| | | [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 |
|| | | [no-page-pause](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-page-pause.md) | Disallow using `page.pause` |
| | | | [no-restricted-matchers](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-restricted-matchers.md) | Disallow specific matchers & modifiers |
|| | 💡 | [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 |
|| 🔧 | | [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 |
|| | 💡 | [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` |
| | | 💡 | [prefer-strict-equal](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-strict-equal.md) | Suggest using `toStrictEqual()` |
| | 🔧 | | [prefer-lowercase-title](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-lowercase-title.md) | Enforce lowercase test names |
| | 🔧 | | [prefer-to-be](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-be.md) | Suggest using `toBe()` |
| | 🔧 | | [prefer-to-have-length](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-have-length.md) | Suggest using `toHaveLength()` |
| | | | [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 |
|| | | [valid-expect](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-expect.md) | Enforce valid `expect()` usage |
|| 🔧 | 💡 | Rule | Description |
| :-: | :-: | :-: | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
|| | | [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 |
|| 🔧 | | [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 |
|| | | [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 |
|| | 💡 | [no-element-handle](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-element-handle.md) | Disallow usage of element handles |
|| | | [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` |
|| | 💡 | [no-focused-test](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-focused-test.md) | Disallow usage of `.only` annotation |
|| | | [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 |
|| | | [no-page-pause](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-page-pause.md) | Disallow using `page.pause` |
| | | | [no-restricted-matchers](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-restricted-matchers.md) | Disallow specific matchers & modifiers |
|| | 💡 | [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 |
|| 🔧 | | [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 |
|| | 💡 | [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` |
| | | 💡 | [prefer-strict-equal](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-strict-equal.md) | Suggest using `toStrictEqual()` |
| | 🔧 | | [prefer-lowercase-title](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-lowercase-title.md) | Enforce lowercase test names |
| | 🔧 | | [prefer-to-be](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-be.md) | Suggest using `toBe()` |
| | 🔧 | | [prefer-to-have-length](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-to-have-length.md) | Suggest using `toHaveLength()` |
| | | | [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 |
| | 🔧 | | [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()` |
|| | | [valid-expect](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-expect.md) | Enforce valid `expect()` usage |
26 changes: 26 additions & 0 deletions docs/rules/require-soft-assertions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Require soft assertions (`require-soft-assertions`)

Some find it easier to write longer test that perform more assertions per test.
In cases like these, it can be helpful to require
[soft assertions](https://playwright.dev/docs/test-assertions#soft-assertions)
in your tests.

This rule is not enabled by default and is only intended to be used it if fits
your workflow. If you aren't sure if you should use this rule, you probably
shouldn't 🙂.

## Rule Details

Examples of **incorrect** code for this rule:

```javascript
await expect(page.locator('foo')).toHaveText('bar');
await expect(page).toHaveTitle('baz');
```

Examples of **correct** code for this rule:

```javascript
await expect.soft(page.locator('foo')).toHaveText('bar');
await expect.soft(page).toHaveTitle('baz');
```
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import preferLowercaseTitle from './rules/prefer-lowercase-title';
import preferToBe from './rules/prefer-to-be';
import preferToHaveLength from './rules/prefer-to-have-length';
import preferStrictEqual from './rules/prefer-strict-equal';
import requireSoftAssertions from './rules/require-soft-assertions';
import requireTopLevelDescribe from './rules/require-top-level-describe';
import validExpect from './rules/valid-expect';

Expand Down Expand Up @@ -91,6 +92,7 @@ export = {
'prefer-to-be': preferToBe,
'prefer-to-have-length': preferToHaveLength,
'require-top-level-describe': requireTopLevelDescribe,
'require-soft-assertions': requireSoftAssertions,
'valid-expect': validExpect,
},
};
31 changes: 31 additions & 0 deletions src/rules/require-soft-assertions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Rule } from 'eslint';
import { getExpectType } from '../utils/ast';

export default {
create(context) {
return {
CallExpression(node) {
if (getExpectType(node) === 'standalone') {
context.report({
node: node.callee,
messageId: 'requireSoft',
fix: (fixer) => fixer.insertTextAfter(node.callee, '.soft'),
});
}
},
};
},
meta: {
docs: {
description: 'Require all assertions to use `expect.soft`',
recommended: false,
url: 'https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/require-soft-assertions.md',
},
messages: {
requireSoft: 'Unexpected non-soft assertion',
},
fixable: 'code',
type: 'suggestion',
schema: [],
},
} as Rule.RuleModule;
33 changes: 33 additions & 0 deletions test/spec/require-soft-assertions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import rule from '../../src/rules/require-soft-assertions';
import { runRuleTester } from '../utils/rule-tester';

const messageId = 'requireSoft';

runRuleTester('require-soft-assertions', rule, {
valid: [
'expect.soft(page).toHaveTitle("baz")',
'expect.soft(page.locator("foo")).toHaveText("bar")',
'expect["soft"](foo).toBe("bar")',
'expect[`soft`](bar).toHaveText("bar")',
'expect.poll(() => foo).toBe("bar")',
'expect["poll"](() => foo).toBe("bar")',
'expect[`poll`](() => foo).toBe("bar")',
],
invalid: [
{
code: 'expect(page).toHaveTitle("baz")',
output: 'expect.soft(page).toHaveTitle("baz")',
errors: [{ messageId, line: 1, column: 1, endColumn: 7 }],
},
{
code: 'expect(page.locator("foo")).toHaveText("bar")',
output: 'expect.soft(page.locator("foo")).toHaveText("bar")',
errors: [{ messageId, line: 1, column: 1, endColumn: 7 }],
},
{
code: 'await expect(page.locator("foo")).toHaveText("bar")',
output: 'await expect.soft(page.locator("foo")).toHaveText("bar")',
errors: [{ messageId, line: 1, column: 7, endColumn: 13 }],
},
],
});
3 changes: 2 additions & 1 deletion test/utils/rule-tester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { RuleTester } from 'eslint';
export function runRuleTester(...args: Parameters<RuleTester['run']>) {
const config = {
parserOptions: {
ecmaVersion: 2018,
ecmaVersion: 2022,
sourceType: 'module',
},
};

Expand Down

0 comments on commit 5c031ef

Please sign in to comment.