eslint-plugin-test-governance 0.13.2 → 0.14.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/CHANGELOG.md +90 -1
- package/README.md +118 -445
- package/dist/index.d.ts +3 -3
- package/dist/index.js +11 -3
- package/dist/rules/{no-conflicting-states.d.ts → consistent-tag-names.d.ts} +1 -1
- package/dist/rules/{no-conflicting-states.js → consistent-tag-names.js} +2 -29
- package/dist/rules/limit-retries.d.ts +2 -0
- package/dist/rules/limit-retries.js +28 -0
- package/dist/rules/marker-matches-state.js +4 -1
- package/dist/rules/require-ticket-for-commented-out-tests.js +4 -4
- package/dist/rules/require-ticket.js +5 -5
- package/dist/utils/analyze.d.ts +5 -5
- package/dist/utils/analyze.js +514 -205
- package/dist/utils/constants.d.ts +1 -4
- package/dist/utils/constants.js +1 -3
- package/dist/utils/create-rule.d.ts +1 -1
- package/dist/utils/fix.js +7 -6
- package/dist/utils/frameworks.d.ts +2 -0
- package/dist/utils/frameworks.js +7 -6
- package/dist/utils/options.d.ts +7 -10
- package/dist/utils/options.js +3 -6
- package/dist/utils/schema.js +12 -11
- package/dist/utils/tickets.js +10 -5
- package/dist/utils/validate.d.ts +2 -0
- package/dist/utils/validate.js +3 -0
- package/docs/configuration.md +400 -0
- package/docs/frameworks.md +228 -0
- package/docs/rules/consistent-tag-names.md +79 -0
- package/docs/rules/limit-retries.md +94 -0
- package/docs/rules/marker-matches-state.md +28 -11
- package/docs/rules/no-orphaned-marker.md +33 -14
- package/docs/rules/require-ticket-for-commented-out-tests.md +30 -12
- package/docs/rules/require-ticket-in-comments.md +19 -17
- package/docs/rules/require-ticket.md +130 -41
- package/docs/troubleshooting.md +77 -0
- package/docs/upgrading.md +103 -31
- package/package.json +1 -1
- package/docs/rules/no-conflicting-states.md +0 -85
package/README.md
CHANGED
|
@@ -4,513 +4,186 @@
|
|
|
4
4
|
[](https://github.com/Huldoser/eslint-plugin-test-governance/actions/workflows/ci.yml)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
Require tracking tickets for disabled tests and TODO/FIXME comments with ESLint. Supports Playwright,
|
|
8
|
+
Jest, Vitest, Mocha, Cypress, WebdriverIO, `node:test` and Jasmine. Custom tag policies and retry tracking
|
|
9
|
+
are optional.
|
|
9
10
|
|
|
10
11
|
<!-- example: valid -->
|
|
11
12
|
|
|
12
|
-
```
|
|
13
|
+
```js
|
|
13
14
|
// SKIP: TRADE-123
|
|
14
|
-
test.skip('
|
|
15
|
-
// ...
|
|
16
|
-
});
|
|
15
|
+
test.skip('cancels an open order', () => {});
|
|
17
16
|
```
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
Removing the skip leaves a stale marker, which the plugin reports too. Tickets can also come from
|
|
19
|
+
[Playwright annotations or Vitest metadata](docs/configuration.md#tickets-from-annotations).
|
|
20
|
+
Ticket formats are checked locally; the plugin does not verify that an issue exists or is open.
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
**Start here:** [Install](#install) · [First lint run](#usage) · [Choose a framework](#frameworks) ·
|
|
23
|
+
[Rules](#rules) · [Configuration](docs/configuration.md) · [Troubleshooting](docs/troubleshooting.md) ·
|
|
24
|
+
[Upgrading](docs/upgrading.md)
|
|
26
25
|
|
|
27
|
-
>
|
|
28
|
-
>
|
|
26
|
+
> Early release (0.x): options may change before 1.0. Check the [upgrade guide](docs/upgrading.md)
|
|
27
|
+
> when updating an existing configuration.
|
|
29
28
|
|
|
30
29
|
## Install
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
```
|
|
31
|
+
Requires ESLint 9 or 10 with flat config. The plugin requires Node.js 22.12 or later; ESLint may
|
|
32
|
+
require a newer version:
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
| ESLint | Node.js requirement for this plugin and ESLint together |
|
|
35
|
+
| ------ | ------------------------------------------------------- |
|
|
36
|
+
| 9 | 22.12 or later |
|
|
37
|
+
| 10 | 22.13 or later within Node 22, or Node 24 and later |
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
See [ESLint's prerequisites](https://eslint.org/docs/latest/use/getting-started#prerequisites).
|
|
40
|
+
Use a project that already has a supported test runner. Install the linter and plugin:
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
```sh
|
|
43
|
+
npm install --save-dev eslint eslint-plugin-test-governance
|
|
44
|
+
```
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
import tsParser from '@typescript-eslint/parser';
|
|
48
|
-
import testGovernance from 'eslint-plugin-test-governance';
|
|
46
|
+
For TypeScript tests, follow the [TypeScript setup](docs/configuration.md#typescript), which also
|
|
47
|
+
installs and configures a parser. The JavaScript quick start below needs no TypeScript parser.
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
{ files: ['**/*.ts'], languageOptions: { parser: tsParser } },
|
|
52
|
-
{
|
|
53
|
-
files: ['tests/**/*.{js,ts}'],
|
|
54
|
-
...testGovernance.configs.playwright,
|
|
55
|
-
},
|
|
56
|
-
];
|
|
57
|
-
```
|
|
49
|
+
## Usage
|
|
58
50
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
`vitest`, `mocha` or `jasmine-core`, the way the Jest, Vitest and Mocha ESLint plugins are set up. `node:test` has no globals, so in its config
|
|
63
|
-
they are the names imported or required from `node:test`, and `test` and `it` from `testFunctions`.
|
|
51
|
+
This example uses Playwright and JavaScript tests in `tests/`. Choose your runner in the
|
|
52
|
+
[framework table](#frameworks) and adjust `files` to match your test locations and extensions.
|
|
53
|
+
If you already have an ESLint config, add this object to its array rather than replacing the file.
|
|
64
54
|
|
|
65
|
-
|
|
66
|
-
same calls as `configs.mocha`. `webdriverio` means WebdriverIO's Mocha framework; for its Jasmine
|
|
67
|
-
framework, use `configs.jasmine`. Karma runs Jasmine or Mocha tests, so use `configs.jasmine` with
|
|
68
|
-
karma-jasmine, which Angular's karma builder sets up, and `configs.mocha` with karma-mocha. Puppeteer has
|
|
69
|
-
no test functions of its own, so use the config of the runner its tests run on: `configs.jest` for
|
|
70
|
-
jest-puppeteer, or `configs.vitest`, `configs.mocha`, `configs['node-test']` or `configs.jasmine`.
|
|
55
|
+
1. Create `eslint.config.mjs` at the project root:
|
|
71
56
|
|
|
72
|
-
|
|
57
|
+
<!-- config: javascript -->
|
|
73
58
|
|
|
74
59
|
```js
|
|
75
|
-
// eslint.config.
|
|
76
|
-
import { defineConfig } from 'eslint/config';
|
|
77
|
-
import tsParser from '@typescript-eslint/parser';
|
|
60
|
+
// eslint.config.mjs
|
|
78
61
|
import testGovernance from 'eslint-plugin-test-governance';
|
|
79
62
|
|
|
80
|
-
export default
|
|
81
|
-
{ files: ['**/*.ts'], languageOptions: { parser: tsParser } },
|
|
82
|
-
{
|
|
83
|
-
files: ['tests/**/*.{js,ts}'],
|
|
84
|
-
extends: [testGovernance.configs.playwright],
|
|
85
|
-
},
|
|
86
|
-
]);
|
|
63
|
+
export default [{ files: ['tests/**/*.js'], ...testGovernance.configs.playwright }];
|
|
87
64
|
```
|
|
88
65
|
|
|
89
|
-
|
|
90
|
-
`eslint.config.cjs` returns the plugin itself. Inside `defineConfig()`, once the plugin is registered
|
|
91
|
-
under `plugins`, the string form `extends: ['test-governance/playwright']` works too. ESLint loads
|
|
92
|
-
an `eslint.config.ts` through [`jiti`](https://github.com/unjs/jiti), so install it as a dev
|
|
93
|
-
dependency too.
|
|
66
|
+
2. In `tests/order.spec.js`, a skipped test without a ticket is reported:
|
|
94
67
|
|
|
95
|
-
|
|
96
|
-
framework's config with the options stored in `settings['test-governance']`, which all the rules read:
|
|
68
|
+
<!-- example: invalid -->
|
|
97
69
|
|
|
98
70
|
```js
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
...testGovernance.configure({
|
|
103
|
-
framework: 'playwright',
|
|
104
|
-
// Only accept Jira keys from these projects (or Jira URLs).
|
|
105
|
-
ticket: { preset: 'jira', projects: ['TRADE', 'RISK'] },
|
|
106
|
-
// Turn on the @new and @quarantined states.
|
|
107
|
-
lifecycleTags: true,
|
|
108
|
-
// Any tag your team uses, with the same rules as the built-ins.
|
|
109
|
-
customStates: {
|
|
110
|
-
'needs-data': { when: '@needs-data', marker: 'NEEDS-DATA' },
|
|
111
|
-
},
|
|
112
|
-
}),
|
|
113
|
-
},
|
|
114
|
-
];
|
|
71
|
+
import { test } from '@playwright/test';
|
|
72
|
+
|
|
73
|
+
test.skip('cancels an open order', () => {});
|
|
115
74
|
```
|
|
116
75
|
|
|
117
|
-
|
|
118
|
-
with a clear message rather than being ignored:
|
|
76
|
+
3. Run ESLint from the project root:
|
|
119
77
|
|
|
120
|
-
```
|
|
121
|
-
eslint
|
|
122
|
-
- lifecycleTag is not a known option; did you mean "lifecycleTags"?
|
|
123
|
-
- ticket.preset must be one of "any", "jira", "github", … (got "jria")
|
|
78
|
+
```sh
|
|
79
|
+
npx eslint tests/order.spec.js
|
|
124
80
|
```
|
|
125
81
|
|
|
126
|
-
|
|
82
|
+
ESLint reports `test-governance/require-ticket` and exits with status 1. Add the marker directly above
|
|
83
|
+
the test, then run the same command again:
|
|
127
84
|
|
|
128
|
-
|
|
129
|
-
files. To share options between them, pass the same options to each `configure()`:
|
|
85
|
+
<!-- example: valid -->
|
|
130
86
|
|
|
131
87
|
```js
|
|
132
|
-
|
|
88
|
+
import { test } from '@playwright/test';
|
|
133
89
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
{ files: ['e2e/**/*.spec.ts'], ...testGovernance.configure({ framework: 'playwright', ...options }) },
|
|
137
|
-
{ files: ['src/**/*.test.ts'], ...testGovernance.configure({ framework: 'vitest', ...options }) },
|
|
138
|
-
];
|
|
90
|
+
// SKIP: TRADE-123
|
|
91
|
+
test.skip('cancels an open order', () => {});
|
|
139
92
|
```
|
|
140
93
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
Each framework's config, such as `configs.playwright`, sets only the framework, so one listed after a
|
|
145
|
-
`configure()` for the same files keeps that config's options and changes only the framework. They all
|
|
146
|
-
turn on the same rules. What puts a test in a state in each framework:
|
|
147
|
-
|
|
148
|
-
| State (marker) | Playwright | Jest | Vitest | Mocha | Cypress | WebdriverIO | node-test | Jasmine |
|
|
149
|
-
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
150
|
-
| `skip` (`// SKIP:`) | `test.skip(...)`, `test.describe.skip`, `test.skip()` in a body, `testInfo.skip()`, `test.step.skip`, `step.skip()` | `test.skip`, `it.skip`, `describe.skip`, `xit`, `xtest`, `xdescribe` | `test.skip`, `it.skip`, `describe.skip`, `suite.skip`, `skipIf(true)`, `runIf(false)`, `{ skip: true }`, `context.skip()`, `context.skip('note')` or `skip()` in a body or in `beforeEach` | `it.skip`, `specify.skip`, `describe.skip`, `context.skip`, `xit`, `xspecify`, `xdescribe`, `xcontext`, TDD `test.skip` and `suite.skip`, `this.skip()` in a test or hook | as Mocha | as Mocha | `test.skip`, `it.skip`, `describe.skip`, `suite.skip`, `skip()`, `{ skip: true }` or `{ skip: 'reason' }`, `t.skip()`, `t.skip('reason')` or `this.skip()` in a test, `beforeEach` or `afterEach` | `xit`, `xdescribe`, `pending()`, `pending('reason')` or `notApplicable('reason')` in a test, `beforeEach` or `afterEach` |
|
|
151
|
-
| `todo` (`// TODO:`) | | `test.todo`, `it.todo` | `test.todo`, `describe.todo`, `{ todo: true }` | a test with only a title, `it('title')`, which Mocha runs as pending | as Mocha | as Mocha | `test.todo`, `it.todo`, `describe.todo`, `suite.todo`, `todo()`, `{ todo: true }` or `{ todo: 'reason' }`, `t.todo()`, `this.todo()` | a test with only a title, `it('title')`, which Jasmine reports as pending |
|
|
152
|
-
| `fixme` (`// FIXME:`) | `test.fixme(...)`, `test.describe.fixme`, `testInfo.fixme()` | | | | | | | |
|
|
153
|
-
| `fail` (`// FAIL:`), off by default | `test.fail(...)`, `testInfo.fail()` | `test.failing` | `test.fails`, `{ fails: true }` | | | | `test.expectFailure`, `expectFailure()`, `{ expectFailure }` | |
|
|
154
|
-
| `slow` (`// SLOW:`), off by default | `test.slow()`, `testInfo.slow()` | | | | | | | |
|
|
155
|
-
| `retry` (`// RETRY:`), off by default | `test.describe.configure({ retries })` | `jest.retryTimes()` | `{ retry }` | `this.retries()` in a describe, test or hook written as a `function` | as Mocha, and `{ retries }` | as Mocha | | |
|
|
156
|
-
| conditional skip, no ticket by default | `test.skip(condition)`, `if (...) test.skip()`, `(condition ? test.skip : test)(...)` | `(condition ? it.skip : it)(...)` | `skipIf(condition)`, `runIf(condition)`, `{ skip: condition }`, `context.skip(condition)`, `if (...) context.skip()`, `(condition ? it.skip : it)(...)` | `if (...) this.skip()`, `(condition ? it.skip : it)(...)` | as Mocha, and `{ browser: '!webkit' }` | as Mocha | `{ skip: condition }`, `{ todo: condition }`, `if (...) t.skip()`, `(condition ? it.skip : it)(...)` | `if (...) pending()`, `(condition ? xit : it)(...)` |
|
|
157
|
-
| tags such as `@new` (`// NEW:`) | the title, `{ tag }` | the title | the title, `{ tags }`, where `'new'` and `'@new'` both mean `@new` | the title | the title, `{ tags }` from @cypress/grep | the title | the title, `{ tags }`, as in Vitest | the title |
|
|
158
|
-
|
|
159
|
-
Table-driven tests count too: `test.skip.each(table)(...)`, `describe.skip.each` and Vitest's `.for`.
|
|
160
|
-
A test or describe whose function a condition picks, as in `(isMobile ? test.skip : test)('title', fn)`,
|
|
161
|
-
is a conditional skip in every framework.
|
|
162
|
-
A literal condition decides on its own: `skipIf(true)` and `runIf(false)` always skip, and `skipIf(false)`,
|
|
163
|
-
`runIf(true)` and `{ skip: false }` never do.
|
|
164
|
-
In Vitest, Mocha and `node:test`, `test.skip('title')` and `describe.skip('title')` with no body are skipped too,
|
|
165
|
-
as is `xit('title')` in Jasmine.
|
|
166
|
-
In Mocha, `this.skip()` counts in a test or hook written as a `function`; an arrow function gets no
|
|
167
|
-
Mocha context, so its `this` is not the test's. `this.slow(ms)` only sets Mocha's speed threshold, so
|
|
168
|
-
it isn't the `slow` state.
|
|
169
|
-
Vitest, and `node:test` since Node 26.2, write tag names without the `@`, as in `{ tags: ['flaky'] }`, so a
|
|
170
|
-
name in `tags` counts as the tag with an `@` in front: `'flaky'` is `@flaky`, and a custom state with
|
|
171
|
-
`when: '@flaky'` matches it.
|
|
172
|
-
In `node:test`, an option counts with any value but `false` or `undefined`, as Node reads it, so
|
|
173
|
-
`{ skip: 'broker sandbox is down' }` always skips. `t.skip()` and `t.todo()` take a message, not a
|
|
174
|
-
condition, and in a test or hook written as a `function`, `this.skip()` and `this.todo()` do the same.
|
|
175
|
-
Subtests declared with `t.test('title', options, fn)` are checked like tests, and a test or describe can
|
|
176
|
-
leave out its title and start with its options or body, as in `test({ skip: true }, fn)`.
|
|
177
|
-
A test with no function, `test('title')`, passes in Node, so it isn't a todo. A describe's body, and
|
|
178
|
-
`before` and `after` inside it, get the suite's context, which can't skip. Node reports a test in one
|
|
179
|
-
state only, skip before todo before `expectFailure`, so `describe.skip('title', { todo: 'why' }, fn)`
|
|
180
|
-
needs only a `// SKIP:` marker.
|
|
181
|
-
In Jasmine, `pending()` and Jasmine 7's `notApplicable()` stop the running test, so they count in a test,
|
|
182
|
-
`beforeEach` or `afterEach`; in `beforeAll`, `afterAll` or a describe's body Jasmine fails the suite
|
|
183
|
-
instead, so they are left alone there. Like `t.skip('reason')`, `pending('reason')` takes a reason, not a
|
|
184
|
-
condition. A function or variable of your own called `pending` is not Jasmine's.
|
|
94
|
+
With this config and file, ESLint prints nothing and exits with status 0. Use a real ticket from your
|
|
95
|
+
tracker. The default accepts `PROJ-123`, `#123`, `owner/repo#123` or an http(s) URL; a
|
|
96
|
+
[ticket preset](docs/configuration.md#ticket-presets) narrows the accepted format.
|
|
185
97
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
<!-- begin auto-generated rules list -->
|
|
189
|
-
|
|
190
|
-
💼 [Configurations](https://github.com/Huldoser/eslint-plugin-test-governance#usage) enabled in.\
|
|
191
|
-
⚠️ [Configurations](https://github.com/Huldoser/eslint-plugin-test-governance#usage) set to warn in.\
|
|
192
|
-
🌲 Set in the `cypress` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
193
|
-
🌼 Set in the `jasmine` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
194
|
-
🃏 Set in the `jest` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
195
|
-
☕ Set in the `mocha` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
196
|
-
🟢 Set in the `node-test` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
197
|
-
🎭 Set in the `playwright` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
198
|
-
⚡ Set in the `vitest` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
199
|
-
🤖 Set in the `webdriverio` [configuration](https://github.com/Huldoser/eslint-plugin-test-governance#usage).\
|
|
200
|
-
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
|
|
201
|
-
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
|
|
202
|
-
|
|
203
|
-
| Name | Description | 💼 | ⚠️ | 🔧 | 💡 |
|
|
204
|
-
| :--------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------- | :---------------------- | :---------------------- | :-- | :-- |
|
|
205
|
-
| [marker-matches-state](docs/rules/marker-matches-state.md) | Require the marker keyword to match the test's state | 🌲 🌼 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
|
|
206
|
-
| [no-conflicting-states](docs/rules/no-conflicting-states.md) | Disallow state tags that contradict each other, differ in case or look like typos | | 🌲 🌼 🃏 ☕ 🟢 🎭 ⚡ 🤖 | 🔧 | |
|
|
207
|
-
| [no-orphaned-marker](docs/rules/no-orphaned-marker.md) | Disallow marker comments that no longer match a test state | 🌲 🌼 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
|
|
208
|
-
| [require-ticket](docs/rules/require-ticket.md) | Require a ticket for skipped, fixme, todo, retried and tagged tests | 🌲 🌼 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
|
|
209
|
-
| [require-ticket-for-commented-out-tests](docs/rules/require-ticket-for-commented-out-tests.md) | Require a ticket marker on commented-out tests | | 🌲 🌼 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | 💡 |
|
|
210
|
-
| [require-ticket-in-comments](docs/rules/require-ticket-in-comments.md) | Require FIXME and TODO comments in test files to start with a ticket | 🌲 🌼 🃏 ☕ 🟢 🎭 ⚡ 🤖 | | | 💡 |
|
|
211
|
-
|
|
212
|
-
<!-- end auto-generated rules list -->
|
|
213
|
-
|
|
214
|
-
## Defaults
|
|
215
|
-
|
|
216
|
-
The defaults follow how most teams already work, so each framework config is useful with no options.
|
|
217
|
-
|
|
218
|
-
| Setting | Default | Why |
|
|
219
|
-
| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
220
|
-
| States that need a ticket | `skip`, `fixme`; in the other frameworks `skip`, `todo` | Every team skips tests. Chromium DevTools, GitLab and Slack all require a bug link on disabled tests. |
|
|
221
|
-
| `@new`, `@quarantined` | off; `lifecycleTags: true` turns both on | A promotion workflow is not universal. Teams that say `@flaky` instead [rename the tag](#tag-names). |
|
|
222
|
-
| Conditional skips (`test.skip(browserName === 'webkit', ...)`, `if (...) test.skip()`, a skip in `catch`) | no ticket needed | These are usually permanent platform limits, not bugs. `requireTicketForConditional: true` asks for a ticket, `requireReasonForConditional: true` for a reason. |
|
|
223
|
-
| `test.fail`, `test.slow`, Jest's `test.failing`, Vitest's `test.fails` | off | They don't remove coverage. Turn them on with `states: { fail: true, slow: true }`. |
|
|
224
|
-
| Retries: `describe.configure({ retries })`, `jest.retryTimes()`, `retry`, `this.retries()` | off | Retries hide flaky tests. `states: { retry: true }` needs a ticket wherever tests retry, even only on CI. |
|
|
225
|
-
| Marker format | `// SKIP: TRADE-123` or `// SKIP: TRADE-123, TRADE-124` | Uppercase keyword and colon, like `TODO:`. Easy to grep. |
|
|
226
|
-
| Notes after the ticket | not allowed; `allowNotes: true` allows them | The ticket is the source of truth. A note in a comment goes stale while the ticket stays current. |
|
|
227
|
-
| `FIXME` / `TODO` comments | must start with a ticket, e.g. `// TODO: TRADE-123` or `// TODO(TRADE-123)` | The ticket is the source of truth; an untracked `TODO` is never done. `comments: false` turns this off. |
|
|
228
|
-
| Placement | comment block directly above; other comments allowed; a blank line breaks it | Survives `eslint-disable-next-line` and Prettier. |
|
|
229
|
-
| Ticket format | `any`: `PROJ-123`, `#123`, `owner/repo#123`, or an http(s) URL | Works for Jira, Linear, GitHub and GitLab out of the box. Narrow it with a preset. |
|
|
230
|
-
| Placeholder tickets | rejected: `TODO`, `TBD`, `XXX-*`, and any ticket numbered 0 (`PROJ-0`, `#0`) | A fake ticket is worse than none, and no tracker issues number 0. |
|
|
231
|
-
| Describe blocks | a marker on a skipped or tagged describe covers its tests | One ticket per skipped suite. |
|
|
232
|
-
| Severity | `no-conflicting-states` and `require-ticket-for-commented-out-tests`: warn; every other rule: error | A marker must always match a real state, so a missing, wrong or leftover marker fails the lint. Tag spelling is cleanup, and commented-out tests are read from comment text. |
|
|
233
|
-
|
|
234
|
-
## Options
|
|
98
|
+
### Frameworks
|
|
235
99
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
of their own: they all read these shared settings, so they always agree on ticket formats, states
|
|
239
|
-
and markers. To turn a rule off, set it to `'off'` as usual.
|
|
240
|
-
|
|
241
|
-
| Option | Type | Default | Description |
|
|
242
|
-
| ----------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
243
|
-
| `framework` | `'playwright' \| 'jest' \| 'vitest' \| 'mocha' \| 'cypress' \| 'webdriverio' \| 'node-test' \| 'jasmine'` | required | The test framework of the files the config applies to. Each framework's config, such as `configs.playwright`, sets it, and `configure()` needs it. See [Frameworks](#frameworks). |
|
|
244
|
-
| `ticket` | `TicketSpec \| TicketSpec[]` | `{ preset: 'any' }` | Accepted ticket formats. An array accepts a ticket that matches any entry. See [Ticket presets](#ticket-presets). |
|
|
245
|
-
| `placeholders` | `string[]` | see above | Tickets rejected as placeholders. `*` matches any characters. Case-insensitive, and a leading `#` on the ticket or the placeholder is ignored. Your list replaces the default one, so repeat the defaults you want to keep. Tickets numbered 0 are always rejected. |
|
|
246
|
-
| `lifecycleTags` | `boolean` | `false` | Turns on the `new` (`@new`) and `quarantined` (`@quarantined`) states. |
|
|
247
|
-
| `states` | `{ [state]: boolean \| { enabled?, tag?, marker?, ticket? } }` | `skip`, `fixme` and `todo` on | Turns built-in states (`skip`, `fixme`, `fail`, `slow`, `todo`, `retry`, `new`, `quarantined`) on or off, renames their tag or marker, or gives them their own ticket format. `fail`, `slow` and `retry` are off by default; a setting for `new` or `quarantined` here wins over `lifecycleTags`. Only `new` and `quarantined` have a tag to rename; see [Tag names](#tag-names). `fixme` and `slow` exist only in Playwright, `todo` in every framework but Playwright, `fail` in Playwright, Jest, Vitest and node:test, and `retry` in every framework but node:test and Jasmine, so turning one on elsewhere has no effect. |
|
|
248
|
-
| `customStates` | `{ [name]: { when, marker, ticket? } }` | `{}` | Adds a state for a tag, e.g. `{ 'needs-data': { when: '@needs-data', marker: 'NEEDS-DATA' } }`. `when` is a tag and `marker` an uppercase keyword; neither can be shared with another state. |
|
|
249
|
-
| `knownTags` | `string[]` | `[]` | Your team's other tags, such as `['@smoke', '@regression']`, written with the `@`. `no-conflicting-states` reports them in the wrong case or misspelled, as it does state tags, and never reports a listed tag as a typo of a state tag. |
|
|
250
|
-
| `requireTicketForConditional` | `boolean` | `false` | Require a ticket for conditional skips too: `test.skip(condition, ...)`, `if (...) test.skip()`, `(condition ? test.skip : test)(...)`, a skip in a `catch` block, after other code in a `try` block with a `catch`, or after an early `return` or `throw`. |
|
|
251
|
-
| `requireReasonForConditional` | `boolean` | `false` | Require a reason, but no ticket, for conditional skips: a comment above the skip or at the end of its line, above the `if` around it or above its hook, step or test, or the reason the call takes, as `test.skip(condition, 'why')`. See [require-ticket](docs/rules/require-ticket.md#reasons-for-conditional-skips). |
|
|
252
|
-
| `allowBlankLine` | `boolean` | `false` | Let blank lines separate a marker from its test. |
|
|
253
|
-
| `allowNotes` | `boolean` | `false` | Allow free text after the tickets, e.g. `// SKIP: TRADE-123 flaky on CI`. Applies to markers and to `FIXME` / `TODO` comments. |
|
|
254
|
-
| `comments` | `false \| { keywords?: string[] }` | `{ keywords: ['FIXME', 'TODO'] }` | Comment keywords that must start with a ticket anywhere in a test file, e.g. `// TODO: TRADE-123`. Your keywords replace the default ones. `false` allows free-form comments. |
|
|
255
|
-
| `annotations` | `false \| { type?: string, only?: boolean }` | `{ type: 'issue' }` | Accept the ticket from a test annotation as well: Playwright's `annotation` of the given type or Vitest's `meta` key of that name. One valid annotation covers every state the test is in, and a describe's covers its tests. `only: true` makes it the only place for a test's or describe's ticket. See [Tickets from annotations](#tickets-from-annotations). |
|
|
256
|
-
| `testFunctions` | `string[]` | `['test']` | Names treated as the framework's `test`. Jest, Vitest and node:test default to `['test', 'it']`, Mocha, Cypress and WebdriverIO to `['it', 'specify', 'test']`, and Jasmine to `['it']`. Your list replaces the default one, so include those names if you still need them. See below. |
|
|
257
|
-
| `reportUnreadableTags` | `boolean` | `false` | When a tag state is on, report titles that aren't static text, and tags or details that can't be read in the file, such as ones imported from another module, since their tags can't be checked. |
|
|
258
|
-
|
|
259
|
-
### Tag names
|
|
260
|
-
|
|
261
|
-
Teams name the stages of a test differently, so `states` can rename the tag of `new` and
|
|
262
|
-
`quarantined`. The marker keeps its name unless you rename it too:
|
|
100
|
+
Each config sets the same rule defaults, adapted to the runner. There is no `recommended` or default config.
|
|
101
|
+
See [Framework syntax](docs/frameworks.md) for imports, supported calls and version-specific features.
|
|
263
102
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
103
|
+
| Runner | Config |
|
|
104
|
+
| ------------------------------------------- | ---------------------- |
|
|
105
|
+
| Playwright | `configs.playwright` |
|
|
106
|
+
| Jest | `configs.jest` |
|
|
107
|
+
| Vitest | `configs.vitest` |
|
|
108
|
+
| Mocha | `configs.mocha` |
|
|
109
|
+
| Cypress | `configs.cypress` |
|
|
110
|
+
| WebdriverIO with Mocha | `configs.webdriverio` |
|
|
111
|
+
| Node's built-in runner | `configs['node-test']` |
|
|
112
|
+
| Jasmine, including WebdriverIO with Jasmine | `configs.jasmine` |
|
|
271
113
|
|
|
272
|
-
|
|
114
|
+
For mixed suites, give each framework its own `files` pattern. See
|
|
115
|
+
[multiple frameworks](docs/configuration.md#multiple-frameworks).
|
|
273
116
|
|
|
274
|
-
|
|
275
|
-
// FLAKY: TRADE-311
|
|
276
|
-
test('streams level 2 quotes @flaky', async ({ page }) => {
|
|
277
|
-
// ...
|
|
278
|
-
});
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
The new tag replaces the default one, so `@quarantined` no longer puts a test in the state, and the
|
|
282
|
-
rules check `@flaky` as they checked `@quarantined`: `@new` together with `@flaky` is reported, and so
|
|
283
|
-
is a near-miss such as `@flakey`. For a tag that isn't one of these stages, such as `@needs-data`, add
|
|
284
|
-
a `customStates` entry.
|
|
285
|
-
|
|
286
|
-
### Ticket presets
|
|
287
|
-
|
|
288
|
-
| Preset | Accepts | Options |
|
|
289
|
-
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
290
|
-
| `any` | `PROJ-123`, `#123`, `owner/repo#123`, any http(s) URL | |
|
|
291
|
-
| `jira` | `TRADE-123`, `https://<host>/browse/TRADE-123` (also under a path, as in `/jira/browse/TRADE-123`), board URLs with `selectedIssue=TRADE-123` | `projects`; `host` (any host by default) |
|
|
292
|
-
| `github` | `#123`, `owner/repo#123`, `https://github.com/owner/repo/issues/123` (or `/pull/`) | `host` for GitHub Enterprise (`github.com` by default) |
|
|
293
|
-
| `gitlab` | `#123`, `group/project#123`, `https://gitlab.com/group/project/-/issues/123` | `host` for self-managed (`gitlab.com` by default) |
|
|
294
|
-
| `linear` | `RISK-123`, `https://linear.app/<workspace>/issue/RISK-123/...` | `teams` |
|
|
295
|
-
| `azure-devops` | `AB#123`, `#123`, `https://dev.azure.com/<org>/<project>/_workitems/edit/123` | `host` for Azure DevOps Server (`dev.azure.com` and `*.visualstudio.com` by default) |
|
|
296
|
-
| `trello` | card links, `https://trello.com/c/nqPiDKmw` with or without the `/9-title` after it | |
|
|
297
|
-
| `notion` | Unique IDs such as `TASK-123`, `https://www.notion.so/TASK-123`, page links that end in the page ID (also `https://app.notion.com/p/...`), database links with `?p=<page ID>` | `prefixes`; `host` for a custom domain (`notion.so`, `www.notion.so`, `app.notion.com` and `*.notion.site` by default) |
|
|
298
|
-
| `asana` | task links, `https://app.asana.com/0/<project>/<task>` and the newer `https://app.asana.com/1/<workspace>/.../task/<task>` | |
|
|
299
|
-
| `clickup` | `CU-86abc123`, `#86abc123` (not `#123`), custom IDs like `DEV-123`, `https://app.clickup.com/t/86abc123` or `/t/<workspace>/DEV-123`, view links `?p=<task>` | `prefixes` for custom IDs |
|
|
300
|
-
| `redmine` | `#123`, `#123-4` and `#123#note-4` (a note), `##123`; with `host`, `https://<host>/issues/123` (also under a path) | `host`, needed for links |
|
|
301
|
-
| `monday` | item links, `https://<account>.monday.com/boards/<board>/pulses/<item>`, and custom keys such as `TIX-001` | `prefixes` for custom keys |
|
|
302
|
-
| `youtrack` | `KT-123`, `https://<host>/issue/KT-123/...` or `/projects/KT/issues/KT-123` (also under a path, as in `/youtrack/issue/KT-123`) | `projects`; `host` for Server or a custom domain (`youtrack.jetbrains.com`, `*.youtrack.cloud` and `*.myjetbrains.com` by default) |
|
|
303
|
-
| `basecamp` | to-do and card links, `https://3.basecamp.com/<account>/buckets/<project>/todos/<id>` or `/card_tables/cards/<id>` | |
|
|
304
|
-
| `shortcut` | `sc-123`, `[sc-123]`, `https://app.shortcut.com/<workspace>/story/123/...` | |
|
|
305
|
-
| `openproject` | `#4821` and per-project IDs such as `AGILE-1`; with `host`, `https://<host>/projects/<identifier>/work_packages/AGILE-1` (also `/work_packages/4821` and the short `/wp/4821`) | `host`, needed for links |
|
|
306
|
-
| `bugzilla` | bug numbers `4821`; with `host`, `https://<host>/show_bug.cgi?id=4821`, also under a path as in `/bugzilla/show_bug.cgi` | `host`, needed for links |
|
|
307
|
-
| `phabricator` | task monograms `T4821`; with `host`, `https://<host>/T4821`. Phorge, Phabricator's maintained fork, shares the format | `host`, needed for links |
|
|
308
|
-
| `rally` | formatted IDs `US4821`, `DE4821`, `TA4821`, `TC4821`, never zero-padded (so MITRE ATT&CK's `TA0006` isn't one) | |
|
|
309
|
-
| `servicenow` | record numbers `STRY0010001` for the prefixes you list, any digit count | `prefixes`, required: record-number prefixes such as `STRY` |
|
|
310
|
-
| `plane` | `WEB-142`; `https://app.plane.so/<workspace>/<project>/issues/WEB-142` (or a UUID), and the same on a `host` for self-hosted | `projects`; `host` for self-hosted (`app.plane.so` by default) |
|
|
311
|
-
| `url` | links to the hosts you list, and nothing else | `hosts`, required: host names such as `tasks.example.com`, with no scheme, port or path; `*.example.com` matches its subdomains |
|
|
312
|
-
| `numeric` | `4821` | `minLength` (1), `maxLength` (20) |
|
|
313
|
-
| `pattern` | anything matching your regex, anchored at both ends | `pattern`, `flags` |
|
|
314
|
-
|
|
315
|
-
`projects` (Jira, YouTrack, Plane), `teams` (Linear) and `prefixes` (Notion, ClickUp, monday.com,
|
|
316
|
-
ServiceNow) are keys as they appear in tickets, in uppercase: `TRADE` for `TRADE-123`, `STRY` for
|
|
317
|
-
`STRY0010001`. YouTrack project IDs may also start with a digit, as `4DM`. Without them, any key passes;
|
|
318
|
-
ServiceNow's list is required. `host` is a host name such as `jira.example.com`, or `*.example.com` for
|
|
319
|
-
every subdomain, as in `{ preset: 'jira', host: '*.atlassian.net' }`. Every preset that takes `host`
|
|
320
|
-
requires a plain host name: a value with a scheme, port or path never matched a link, so it is an error.
|
|
321
|
-
Each preset
|
|
322
|
-
takes only its own options, so `host` on the `any` preset fails with a clear message instead of being
|
|
323
|
-
ignored. A `pattern` always matches the whole ticket, so the `g` and `y` flags make no difference and are
|
|
324
|
-
ignored.
|
|
325
|
-
|
|
326
|
-
A preset rejects links that aren't one ticket in its tracker, such as a Trello board, an Asana project or
|
|
327
|
-
a Notion database view. Redmine runs on your own server, and `/issues/123` is also GitHub's path, so its
|
|
328
|
-
links pass only once `host` is set. The same holds for OpenProject, Bugzilla and Phabricator, which also
|
|
329
|
-
run on their own servers.
|
|
330
|
-
|
|
331
|
-
For a tracker without a preset, use `url` with its host, for example
|
|
332
|
-
`{ preset: 'url', hosts: ['www.wrike.com'] }` for Wrike links such as
|
|
333
|
-
`https://www.wrike.com/open.htm?id=4821`; a link with `?id=0`, or a path that ends in `/0`, is a
|
|
334
|
-
placeholder. It works the same for Microsoft Planner (`tasks.office.com`), Airtable (`airtable.com`),
|
|
335
|
-
Smartsheet (`app.smartsheet.com`) and trackers of your own. For another
|
|
336
|
-
tracker's keys, such as `WEB-12`, `any` or `pattern` works. Gitea, Forgejo and Codeberg use GitHub's link layout,
|
|
337
|
-
so the `github` preset with their `host` covers them. Bitbucket has no issue tracker since August 2026,
|
|
338
|
-
so it has no preset.
|
|
339
|
-
|
|
340
|
-
Presets combine, and each state can have its own format. For example, bugs in Jira and flaky-test
|
|
341
|
-
reports in GitHub:
|
|
117
|
+
## Rules
|
|
342
118
|
|
|
343
|
-
|
|
344
|
-
testGovernance.configure({
|
|
345
|
-
framework: 'playwright',
|
|
346
|
-
ticket: { preset: 'jira', projects: ['TRADE'] },
|
|
347
|
-
lifecycleTags: true,
|
|
348
|
-
states: { quarantined: { ticket: { preset: 'github' } } },
|
|
349
|
-
});
|
|
350
|
-
```
|
|
119
|
+
All framework configs use the severities in [Defaults](#defaults).
|
|
351
120
|
|
|
352
|
-
|
|
353
|
-
so keep it free of nested quantifiers.
|
|
121
|
+
<!-- begin auto-generated rules list -->
|
|
354
122
|
|
|
355
|
-
|
|
123
|
+
| Name | Description |
|
|
124
|
+
| :--------------------------------------------------------------------------------------------- | :------------------------------------------------------------------- |
|
|
125
|
+
| [consistent-tag-names](docs/rules/consistent-tag-names.md) | Disallow tag names that differ in case or look like typos |
|
|
126
|
+
| [limit-retries](docs/rules/limit-retries.md) | Enforce a maximum retry count for tests |
|
|
127
|
+
| [marker-matches-state](docs/rules/marker-matches-state.md) | Require the marker keyword to match the test's state |
|
|
128
|
+
| [no-orphaned-marker](docs/rules/no-orphaned-marker.md) | Disallow marker comments that no longer match a test state |
|
|
129
|
+
| [require-ticket](docs/rules/require-ticket.md) | Require a ticket for skipped, fixme, todo, retried and tagged tests |
|
|
130
|
+
| [require-ticket-for-commented-out-tests](docs/rules/require-ticket-for-commented-out-tests.md) | Require a ticket marker on commented-out tests |
|
|
131
|
+
| [require-ticket-in-comments](docs/rules/require-ticket-in-comments.md) | Require FIXME and TODO comments in test files to start with a ticket |
|
|
356
132
|
|
|
357
|
-
|
|
358
|
-
`annotations` accepts it from there as well:
|
|
133
|
+
<!-- end auto-generated rules list -->
|
|
359
134
|
|
|
360
|
-
|
|
361
|
-
test.skip(
|
|
362
|
-
'rejects a margin order above the buying power',
|
|
363
|
-
{
|
|
364
|
-
annotation: { type: 'issue', description: 'TRADE-123' },
|
|
365
|
-
},
|
|
366
|
-
async () => {},
|
|
367
|
-
);
|
|
368
|
-
```
|
|
135
|
+
## Defaults
|
|
369
136
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
137
|
+
| Setting | Default |
|
|
138
|
+
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
139
|
+
| States that need a ticket | `skip`, `fixme`; in the other frameworks `skip`, `todo` |
|
|
140
|
+
| Tag ticket requirements | none by default; configure your own with `customStates` |
|
|
141
|
+
| Conditional skips | no ticket or reason required; opt in with `requireTicketForConditional` or `requireReasonForConditional` |
|
|
142
|
+
| `fail`, `slow`, `retry` | off; enable the states your framework supports with `states` |
|
|
143
|
+
| Marker format | uppercase keyword and colon, then comma-separated tickets: `// SKIP: TRADE-123, TRADE-124` |
|
|
144
|
+
| Notes after the ticket | not allowed; `allowNotes: true` allows them |
|
|
145
|
+
| `FIXME` / `TODO` comments | require tickets in test files; `comments: false` turns this check off |
|
|
146
|
+
| Placement | comment block directly above the test or skip call; a blank line breaks it |
|
|
147
|
+
| Placeholder tickets | rejected: `TODO`, `TBD`, `XXX-*`, and any ticket numbered 0 |
|
|
148
|
+
| Describe blocks | a marker on a skipped or tagged describe covers its tests |
|
|
149
|
+
| Severity | `consistent-tag-names` and `require-ticket-for-commented-out-tests`: warn; every other rule except the opt-in retry limit: error |
|
|
150
|
+
| Retry limit | `limit-retries`: off; enable the rule explicitly, then set `maxRetries` (default `2`) |
|
|
151
|
+
|
|
152
|
+
Tags have no built-in meaning. Configure `customStates` for tags that need ticket markers, or
|
|
153
|
+
`knownTags` for spelling checks alone. Other tags remain allowed. See [custom tag policies](docs/configuration.md#tag-names).
|
|
154
|
+
Warnings allow a successful lint run unless your command uses `--max-warnings 0`.
|
|
374
155
|
|
|
375
|
-
|
|
376
|
-
exactly. The value must be the ticket and nothing else, as in a marker. One valid annotation covers every
|
|
377
|
-
state the test is in, and a describe's annotation covers its tests. An annotation of another type is
|
|
378
|
-
left alone, and an annotation whose ticket isn't static text is reported with `reportUnreadableTags`.
|
|
379
|
-
`annotations: false` turns it off. A marker comment still works everywhere, and when both are written,
|
|
380
|
-
both must be valid. Jest, Mocha, Jasmine, node:test and Cypress have no annotations, so for them it is
|
|
381
|
-
comments only.
|
|
156
|
+
## Options
|
|
382
157
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
annotation, so their comments still count. `only` with another framework is a configuration error.
|
|
158
|
+
Use [`configure()`](docs/configuration.md#change-the-shared-policy) to change shared settings, or
|
|
159
|
+
[override rule severity](docs/troubleshooting.md#adopt-in-an-existing-suite). Options belong to the
|
|
160
|
+
plugin's shared settings, not individual rules.
|
|
387
161
|
|
|
388
|
-
|
|
162
|
+
<a name="tag-names"></a>
|
|
163
|
+
<a name="ticket-presets"></a>
|
|
164
|
+
<a name="tickets-from-annotations"></a>
|
|
165
|
+
<a name="markers-in-eslint-disable-comments"></a>
|
|
389
166
|
|
|
390
|
-
|
|
391
|
-
|
|
167
|
+
- [Custom tags and spelling checks](docs/configuration.md#tag-names)
|
|
168
|
+
- [Ticket presets](docs/configuration.md#ticket-presets)
|
|
169
|
+
- [Tickets from annotations](docs/configuration.md#tickets-from-annotations)
|
|
170
|
+
- [Markers in ESLint directives](docs/configuration.md#markers-in-eslint-disable-comments)
|
|
392
171
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
test.skip('rejects a margin order above the buying power', () => {});
|
|
396
|
-
```
|
|
172
|
+
<a name="which-functions-count-as-test"></a>
|
|
173
|
+
<a name="syntax-the-rules-understand"></a>
|
|
397
174
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
`eslint-disable` block comment, whose marker covers every test up to the `eslint-enable` of one of its rules, or of
|
|
401
|
-
every rule. A suggestion that removes or adds a marker keeps the directive working.
|
|
402
|
-
|
|
403
|
-
### Which functions count as `test`
|
|
404
|
-
|
|
405
|
-
In Playwright files:
|
|
406
|
-
|
|
407
|
-
- `test` imported from `@playwright/test`, `playwright/test` or a component-testing package such as
|
|
408
|
-
`@playwright/experimental-ct-react`, including the default import, aliases such as
|
|
409
|
-
`import { test as it }` or `const it = test`, and `const { test } = require('@playwright/test')`.
|
|
410
|
-
- `pw.test` on the whole module, after `import * as pw from '@playwright/test'`. The default import
|
|
411
|
-
and `const pw = require('@playwright/test')` are `test` itself, so `pw.skip(...)` and
|
|
412
|
-
`pw.test.skip(...)` both count.
|
|
413
|
-
- Fixtures made with `.extend()` or `mergeTests()` in the same file, such as
|
|
414
|
-
`const test = base.extend({...})`.
|
|
415
|
-
- Any name in `testFunctions`, for fixtures imported from your own modules. The default `['test']`
|
|
416
|
-
covers the common `import { test } from './fixtures'`.
|
|
417
|
-
|
|
418
|
-
In Jest and Vitest files:
|
|
419
|
-
|
|
420
|
-
- The globals `test`, `it` and `describe`, Jest's `xit`, `xtest`, `fit`, `xdescribe` and
|
|
421
|
-
`fdescribe`, and Vitest's `suite`, and the same names imported or required from `@jest/globals` or
|
|
422
|
-
`vitest`, including aliases and `import * as vi from 'vitest'`.
|
|
423
|
-
- Vitest fixtures made with `test.extend()` in the same file.
|
|
424
|
-
- Any name in `testFunctions`, which defaults to `['test', 'it']`.
|
|
425
|
-
|
|
426
|
-
In Mocha, Cypress and WebdriverIO files:
|
|
427
|
-
|
|
428
|
-
- The globals `it`, `specify`, `describe` and `context`, their `x` forms, the TDD names `suite` and
|
|
429
|
-
`test`, and the hooks `before`, `beforeEach`, `after`, `afterEach`, `suiteSetup`, `setup`,
|
|
430
|
-
`suiteTeardown` and `teardown`.
|
|
431
|
-
- The names imported or required from `mocha`, including aliases, and the whole module, as `mocha.it`
|
|
432
|
-
after `import * as mocha from 'mocha'`, `import mocha from 'mocha'` or `const mocha = require('mocha')`.
|
|
433
|
-
- Any name in `testFunctions`, which defaults to `['it', 'specify', 'test']`.
|
|
434
|
-
|
|
435
|
-
In `node:test` files:
|
|
436
|
-
|
|
437
|
-
- `test`, `it`, `describe`, `suite`, the hooks `before`, `beforeEach`, `after` and `afterEach`, and
|
|
438
|
-
the members exported by name, `skip`, `todo`, `only` and `expectFailure`, imported or required from
|
|
439
|
-
`node:test`, including aliases and `import * as nodeTest from 'node:test'`. The module is `test`
|
|
440
|
-
itself, so after `import test from 'node:test'` or `const test = require('node:test')`,
|
|
441
|
-
`test.describe(...)` is a describe.
|
|
442
|
-
- Subtests: `t.test(...)` on the context a test's body receives.
|
|
443
|
-
- Any name in `testFunctions`, which defaults to `['test', 'it']`. `node:test` has no globals.
|
|
444
|
-
|
|
445
|
-
In Jasmine files:
|
|
446
|
-
|
|
447
|
-
- The globals `it`, `xit`, `fit`, `describe`, `xdescribe`, `fdescribe`, and the hooks `beforeEach`,
|
|
448
|
-
`afterEach`, `beforeAll` and `afterAll`. With the runner's `globals: false`, Jasmine 7 takes the same
|
|
449
|
-
names from `jasmine-core`, so names imported or required from it count too, including aliases and
|
|
450
|
-
the whole module, as `core.xit` after `const core = require('jasmine-core')`.
|
|
451
|
-
- `pending()` and `notApplicable()`, as globals or imported by name.
|
|
452
|
-
- Any name in `testFunctions`, which defaults to `['it']`. Jasmine has no `test`.
|
|
453
|
-
|
|
454
|
-
A name imported or required from another test runner (Playwright, `vitest`, `@jest/globals`,
|
|
455
|
-
`node:test`, `jasmine-core`, `bun:test`, `mocha`, `ava`, `tap`, `uvu`), or a variable set from one such as
|
|
456
|
-
`const test = require('ava')`, is never treated as the framework's `test`, and neither is a local
|
|
457
|
-
variable or function declared inside a function that happens to be called `test`.
|
|
458
|
-
|
|
459
|
-
### Syntax the rules understand
|
|
460
|
-
|
|
461
|
-
The [Frameworks](#frameworks) table lists what each framework's tests can do. In Playwright:
|
|
462
|
-
|
|
463
|
-
- `test.skip('title', fn)`, `test.fixme(...)`, `test.fail(...)`, with or without a details object,
|
|
464
|
-
and with a body defined elsewhere, as in `test.skip('places a limit order', placeLimitOrder)`. The
|
|
465
|
-
title can also be a `const` declared in the same file.
|
|
466
|
-
- `test.describe.skip(...)` and `test.describe.fixme(...)`, including `serial` and `parallel` variants,
|
|
467
|
-
and `describe.skip(...)` after `const { describe } = test`. The state covers every test inside.
|
|
468
|
-
- Runtime calls inside a test, a describe or a hook such as `test.beforeEach`: `test.skip()`,
|
|
469
|
-
`test.skip(true)`, `test.fixme()`, `test.fail()`, `test.slow()`, the same methods on `testInfo`,
|
|
470
|
-
and `test.info().skip()`.
|
|
471
|
-
- Skipped steps: `test.step.skip('title', fn)`, and `step.skip()` on the `TestStepInfo` a step's body
|
|
472
|
-
receives. Only the step is skipped, so a marker above the step or above a skipped test around it
|
|
473
|
-
covers it.
|
|
474
|
-
- Tags in titles (string or template literal) and in `{ tag: '@new' }` / `{ tag: [...] }`, inherited
|
|
475
|
-
from enclosing describes. Tags, titles and details can also come from a `const` or an enum declared
|
|
476
|
-
in the same file, as in `{ tag: TAGS.NEW }` after `const TAGS = { NEW: '@new' } as const`, including
|
|
477
|
-
arrays and spreads of them.
|
|
478
|
-
|
|
479
|
-
Not covered in this version: tests generated in loops with dynamic titles, and tags or details imported
|
|
480
|
-
from other modules (see `reportUnreadableTags`), fixtures imported from other modules under a name not in
|
|
481
|
-
`testFunctions`, a Vitest test with only a title, which Vitest runs as a todo but which looks the same as a
|
|
482
|
-
helper called `test`, Cypress tests skipped with @cypress/skip-test's `skipOn()` and `onlyOn()`, `node:test`'s
|
|
483
|
-
`getTestContext()`, Jasmine's `pending()` in a helper function that tests call, Jasmine 5 and 6's
|
|
484
|
-
`require('jasmine-core').noGlobals()`, and other runners such as Bun.
|
|
175
|
+
See [recognized test functions and syntax](docs/frameworks.md), including
|
|
176
|
+
[static analysis limits](docs/frameworks.md#static-analysis-limits).
|
|
485
177
|
|
|
486
178
|
## TypeScript
|
|
487
179
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
`CustomState`, `StateOverride`, `FrameworkName`, `RuleName`, `ConfigName`, `FlatConfig` and
|
|
491
|
-
`TestGovernancePlugin`.
|
|
492
|
-
|
|
493
|
-
```ts
|
|
494
|
-
// eslint.config.ts
|
|
495
|
-
import tsParser from '@typescript-eslint/parser';
|
|
496
|
-
import testGovernance, { type GovernanceOptions } from 'eslint-plugin-test-governance';
|
|
497
|
-
|
|
498
|
-
const options: GovernanceOptions = {
|
|
499
|
-
framework: 'playwright',
|
|
500
|
-
lifecycleTags: true,
|
|
501
|
-
ticket: { preset: 'jira', projects: ['TRADE'] },
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
export default [
|
|
505
|
-
{ files: ['**/*.ts'], languageOptions: { parser: tsParser } },
|
|
506
|
-
{ files: ['tests/**/*.ts'], ...testGovernance.configure(options) },
|
|
507
|
-
];
|
|
508
|
-
```
|
|
180
|
+
See [TypeScript setup and exported types](docs/configuration.md#typescript). TypeScript test files
|
|
181
|
+
need a parser; TypeScript config files also need an appropriate config loader.
|
|
509
182
|
|
|
510
183
|
## Contributing
|
|
511
184
|
|
|
512
|
-
|
|
513
|
-
|
|
185
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) covers setup, checks and documentation maintenance.
|
|
186
|
+
[CHANGELOG.md](CHANGELOG.md) lists releases; [SECURITY.md](SECURITY.md) explains private vulnerability reports.
|
|
514
187
|
|
|
515
188
|
## License
|
|
516
189
|
|