@bahmutov/cy-grep 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +12 -1
- package/package.json +3 -3
- package/src/index.d.ts +14 -0
- package/src/support.js +21 -13
- package/src/utils.js +65 -33
package/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# @bahmutov/cy-grep ![cypress version](https://img.shields.io/badge/cypress-12.
|
1
|
+
# @bahmutov/cy-grep ![cypress version](https://img.shields.io/badge/cypress-12.3.0-brightgreen)
|
2
2
|
|
3
3
|
> Filter tests using substring or tag
|
4
4
|
|
@@ -46,6 +46,7 @@ Table of Contents
|
|
46
46
|
- [Omit filtered tests (grepOmitFiltered)](#omit-filtered-tests-grepomitfiltered)
|
47
47
|
- [Disable grep](#disable-grep)
|
48
48
|
- [Burn (repeat) tests](#burn-repeat-tests)
|
49
|
+
- [Required tags](#required-tags)
|
49
50
|
- [TypeScript support](#typescript-support)
|
50
51
|
- [General advice](#general-advice)
|
51
52
|
- [DevTools console](#devtools-console)
|
@@ -410,6 +411,16 @@ You can pass the number of times to run the tests via environment name `burn` or
|
|
410
411
|
|
411
412
|
If you do not specify the "grep" or "grep tags" option, the "burn" will repeat _every_ test.
|
412
413
|
|
414
|
+
## Required tags
|
415
|
+
|
416
|
+
Sometimes you might want to run a test or a suite of tests _only_ if a specific tag or tags are present. For example, you might have a test that cleans the data. This test is meant to run nightly, not on every test run. Thus you can set a `required` tag:
|
417
|
+
|
418
|
+
```js
|
419
|
+
it('cleans up the data', { requiredTags: '@nightly' }, () => {...})
|
420
|
+
```
|
421
|
+
|
422
|
+
When you run the tests now, this test will be skipped, as if it were `it.skip`. It will only run if you use the tag `@nightly`, for example: `npx cypress run --env grepTags=@nightly`.
|
423
|
+
|
413
424
|
## TypeScript support
|
414
425
|
|
415
426
|
Because the Cypress test config object type definition does not have the `tags` property we are using above, the TypeScript linter will show an error. Just add an ignore comment above the test:
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@bahmutov/cy-grep",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.3.0",
|
4
4
|
"description": "Filter Cypress tests using title or tags",
|
5
5
|
"main": "src/support.js",
|
6
6
|
"scripts": {
|
@@ -12,7 +12,7 @@
|
|
12
12
|
"dependencies": {
|
13
13
|
"cypress-plugin-config": "^1.2.0",
|
14
14
|
"debug": "^4.3.2",
|
15
|
-
"find-test-names": "
|
15
|
+
"find-test-names": "1.25.0",
|
16
16
|
"globby": "^11.0.4"
|
17
17
|
},
|
18
18
|
"devDependencies": {
|
@@ -20,7 +20,7 @@
|
|
20
20
|
"cypress-each": "^1.11.0",
|
21
21
|
"cypress-expect": "^2.5.3",
|
22
22
|
"prettier": "^2.8.1",
|
23
|
-
"semantic-release": "^20.0.
|
23
|
+
"semantic-release": "^20.0.3",
|
24
24
|
"typescript": "^4.7.4"
|
25
25
|
},
|
26
26
|
"peerDependencies": {
|
package/src/index.d.ts
CHANGED
@@ -8,8 +8,15 @@ declare namespace Cypress {
|
|
8
8
|
* describe('block with config tag', { tags: '@smoke' }, () => {})
|
9
9
|
* @example multiple tags
|
10
10
|
* describe('block with config tag', { tags: ['@smoke', '@slow'] }, () => {})
|
11
|
+
* @see https://github.com/bahmutov/cy-grep
|
11
12
|
*/
|
12
13
|
tags?: string | string[]
|
14
|
+
/**
|
15
|
+
* Provide a tag or a list of tags that is required for this suite to run.
|
16
|
+
* @example describe('mobile tests', { requiredTags: '@mobile' }, () => {})
|
17
|
+
* @see https://github.com/bahmutov/cy-grep
|
18
|
+
*/
|
19
|
+
requiredTags?: string | string[]
|
13
20
|
}
|
14
21
|
|
15
22
|
// specify additional properties in the TestConfig object
|
@@ -21,8 +28,15 @@ declare namespace Cypress {
|
|
21
28
|
* it('logs in', { tags: '@smoke' }, () => { ... })
|
22
29
|
* @example multiple tags
|
23
30
|
* it('works', { tags: ['@smoke', '@slow'] }, () => { ... })
|
31
|
+
* @see https://github.com/bahmutov/cy-grep
|
24
32
|
*/
|
25
33
|
tags?: string | string[]
|
34
|
+
/**
|
35
|
+
* Provide a tag or a list of tags that is required for this test to run.
|
36
|
+
* @example it('cleans the data', { requiredTags: '@nightly' }, () => {})
|
37
|
+
* @see https://github.com/bahmutov/cy-grep
|
38
|
+
*/
|
39
|
+
requiredTags?: string | string[]
|
26
40
|
}
|
27
41
|
|
28
42
|
interface Cypress {
|
package/src/support.js
CHANGED
@@ -21,7 +21,7 @@ const _describe = describe
|
|
21
21
|
* Wraps the "it" and "describe" functions that support tags.
|
22
22
|
* @see https://github.com/bahmutov/cy-grep
|
23
23
|
*/
|
24
|
-
function
|
24
|
+
function registerCyGrep() {
|
25
25
|
/** @type {string} Part of the test title go grep */
|
26
26
|
let grep = getPluginConfigValue('grep')
|
27
27
|
|
@@ -42,12 +42,12 @@ function cypressGrep() {
|
|
42
42
|
getPluginConfigValue('grepUntagged') ||
|
43
43
|
getPluginConfigValue('grep-untagged')
|
44
44
|
|
45
|
-
if (!grep && !grepTags && !burnSpecified && !grepUntagged) {
|
46
|
-
|
47
|
-
|
45
|
+
// if (!grep && !grepTags && !burnSpecified && !grepUntagged) {
|
46
|
+
// nothing to do, the user has no specified the "grep" string
|
47
|
+
// debug('Nothing to grep, version %s', version)
|
48
48
|
|
49
|
-
|
50
|
-
}
|
49
|
+
// return
|
50
|
+
// }
|
51
51
|
|
52
52
|
/** @type {number} Number of times to repeat each running test */
|
53
53
|
const grepBurn =
|
@@ -90,32 +90,41 @@ function cypressGrep() {
|
|
90
90
|
}
|
91
91
|
|
92
92
|
let configTags = options && options.tags
|
93
|
-
|
94
93
|
if (typeof configTags === 'string') {
|
95
94
|
configTags = [configTags]
|
96
95
|
}
|
96
|
+
let configRequiredTags = options && options.requiredTags
|
97
|
+
if (typeof configRequiredTags === 'string') {
|
98
|
+
configRequiredTags = [configRequiredTags]
|
99
|
+
}
|
97
100
|
|
98
101
|
const nameToGrep = suiteStack
|
99
102
|
.map((item) => item.name)
|
100
103
|
.concat(name)
|
101
104
|
.join(' ')
|
102
|
-
const
|
105
|
+
const effectiveTestTags = suiteStack
|
103
106
|
.flatMap((item) => item.tags)
|
104
107
|
.concat(configTags)
|
105
108
|
.filter(Boolean)
|
109
|
+
const requiredTestTags = suiteStack
|
110
|
+
.flatMap((item) => item.requiredTags)
|
111
|
+
.concat(configRequiredTags)
|
112
|
+
.filter(Boolean)
|
113
|
+
// console.log({ nameToGrep, effectiveTestTags, requiredTestTags })
|
106
114
|
|
107
115
|
const shouldRun = shouldTestRun(
|
108
116
|
parsedGrep,
|
109
117
|
nameToGrep,
|
110
|
-
|
118
|
+
effectiveTestTags,
|
111
119
|
grepUntagged,
|
120
|
+
requiredTestTags,
|
112
121
|
)
|
113
122
|
|
114
|
-
if (
|
123
|
+
if (effectiveTestTags && effectiveTestTags.length) {
|
115
124
|
debug(
|
116
125
|
'should test "%s" with tags %s run? %s',
|
117
126
|
name,
|
118
|
-
|
127
|
+
effectiveTestTags.join(','),
|
119
128
|
shouldRun,
|
120
129
|
)
|
121
130
|
} else {
|
@@ -171,7 +180,6 @@ function cypressGrep() {
|
|
171
180
|
}
|
172
181
|
|
173
182
|
let configTags = options && options.tags
|
174
|
-
|
175
183
|
if (typeof configTags === 'string') {
|
176
184
|
configTags = [configTags]
|
177
185
|
}
|
@@ -285,4 +293,4 @@ if (!Cypress.grepFailed) {
|
|
285
293
|
}
|
286
294
|
}
|
287
295
|
|
288
|
-
module.exports =
|
296
|
+
module.exports = registerCyGrep
|
package/src/utils.js
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
* The string can have "-" in front of it to invert the match.
|
8
8
|
* @param {string} s Input substring of the test title
|
9
9
|
*/
|
10
|
-
function parseTitleGrep
|
10
|
+
function parseTitleGrep(s) {
|
11
11
|
if (!s || typeof s !== 'string') {
|
12
12
|
return null
|
13
13
|
}
|
@@ -26,7 +26,7 @@ function parseTitleGrep (s) {
|
|
26
26
|
}
|
27
27
|
}
|
28
28
|
|
29
|
-
function parseFullTitleGrep
|
29
|
+
function parseFullTitleGrep(s) {
|
30
30
|
if (!s || typeof s !== 'string') {
|
31
31
|
return []
|
32
32
|
}
|
@@ -39,7 +39,7 @@ function parseFullTitleGrep (s) {
|
|
39
39
|
* Parses tags to grep for.
|
40
40
|
* @param {string} s Tags string like "@tag1+@tag2"
|
41
41
|
*/
|
42
|
-
function parseTagsGrep
|
42
|
+
function parseTagsGrep(s) {
|
43
43
|
if (!s) {
|
44
44
|
return []
|
45
45
|
}
|
@@ -48,37 +48,37 @@ function parseTagsGrep (s) {
|
|
48
48
|
|
49
49
|
// top level split - using space or comma, each part is OR
|
50
50
|
const ORS = s
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
51
|
+
.split(/[ ,]/)
|
52
|
+
// remove any empty tags
|
53
|
+
.filter(Boolean)
|
54
|
+
.map((part) => {
|
55
|
+
// now every part is an AND
|
56
|
+
if (part.startsWith('--')) {
|
57
|
+
explicitNotTags.push({
|
58
|
+
tag: part.slice(2),
|
59
|
+
invert: true,
|
60
|
+
})
|
61
61
|
|
62
|
-
|
63
|
-
|
62
|
+
return
|
63
|
+
}
|
64
|
+
|
65
|
+
const parsed = part.split('+').map((tag) => {
|
66
|
+
if (tag.startsWith('-')) {
|
67
|
+
return {
|
68
|
+
tag: tag.slice(1),
|
69
|
+
invert: true,
|
70
|
+
}
|
71
|
+
}
|
64
72
|
|
65
|
-
const parsed = part.split('+').map((tag) => {
|
66
|
-
if (tag.startsWith('-')) {
|
67
73
|
return {
|
68
|
-
tag
|
69
|
-
invert:
|
74
|
+
tag,
|
75
|
+
invert: false,
|
70
76
|
}
|
71
|
-
}
|
77
|
+
})
|
72
78
|
|
73
|
-
return
|
74
|
-
tag,
|
75
|
-
invert: false,
|
76
|
-
}
|
79
|
+
return parsed
|
77
80
|
})
|
78
81
|
|
79
|
-
return parsed
|
80
|
-
})
|
81
|
-
|
82
82
|
// filter out undefined from explicit not tags
|
83
83
|
const ORS_filtered = ORS.filter((x) => x !== undefined)
|
84
84
|
|
@@ -88,14 +88,29 @@ function parseTagsGrep (s) {
|
|
88
88
|
})
|
89
89
|
|
90
90
|
if (ORS_filtered.length === 0) {
|
91
|
-
ORS_filtered[
|
91
|
+
ORS_filtered[0] = explicitNotTags
|
92
92
|
}
|
93
93
|
}
|
94
94
|
|
95
95
|
return ORS_filtered
|
96
96
|
}
|
97
97
|
|
98
|
-
function
|
98
|
+
function shouldTestRunRequiredTags(parsedGrepTags, requiredTags = []) {
|
99
|
+
if (!requiredTags.length) {
|
100
|
+
// there are no tags to check
|
101
|
+
return true
|
102
|
+
}
|
103
|
+
|
104
|
+
return requiredTags.every((onlyTag) => {
|
105
|
+
return parsedGrepTags.some((orPart) => {
|
106
|
+
return orPart.some((p) => {
|
107
|
+
return !p.invert && p.tag === onlyTag
|
108
|
+
})
|
109
|
+
})
|
110
|
+
})
|
111
|
+
}
|
112
|
+
|
113
|
+
function shouldTestRunTags(parsedGrepTags, tags = []) {
|
99
114
|
if (!parsedGrepTags.length) {
|
100
115
|
// there are no parsed tags to search for, the test should run
|
101
116
|
return true
|
@@ -121,7 +136,7 @@ function shouldTestRunTags (parsedGrepTags, tags = []) {
|
|
121
136
|
return onePartMatched
|
122
137
|
}
|
123
138
|
|
124
|
-
function shouldTestRunTitle
|
139
|
+
function shouldTestRunTitle(parsedGrep, testName) {
|
125
140
|
if (!testName) {
|
126
141
|
// if there is no title, let it run
|
127
142
|
return true
|
@@ -152,7 +167,20 @@ function shouldTestRunTitle (parsedGrep, testName) {
|
|
152
167
|
}
|
153
168
|
|
154
169
|
// note: tags take precedence over the test name
|
155
|
-
|
170
|
+
/**
|
171
|
+
* Returns boolean if the test with the given name and effective tags
|
172
|
+
* should run, given the runtime grep (parsed) structure.
|
173
|
+
* @param {string|undefined} testName The full test title
|
174
|
+
* @param {string[]} tags The effective test tags
|
175
|
+
* @param {string[]} requiredTags The effective "required" test tags
|
176
|
+
*/
|
177
|
+
function shouldTestRun(
|
178
|
+
parsedGrep,
|
179
|
+
testName,
|
180
|
+
tags = [],
|
181
|
+
grepUntagged = false,
|
182
|
+
requiredTags = [],
|
183
|
+
) {
|
156
184
|
if (grepUntagged) {
|
157
185
|
return !tags.length
|
158
186
|
}
|
@@ -163,13 +191,16 @@ function shouldTestRun (parsedGrep, testName, tags = [], grepUntagged = false) {
|
|
163
191
|
testName = undefined
|
164
192
|
}
|
165
193
|
|
194
|
+
const combinedTagsAndRequiredTags = [...tags, ...requiredTags]
|
195
|
+
|
166
196
|
return (
|
167
197
|
shouldTestRunTitle(parsedGrep.title, testName) &&
|
168
|
-
shouldTestRunTags(parsedGrep.tags,
|
198
|
+
shouldTestRunTags(parsedGrep.tags, combinedTagsAndRequiredTags) &&
|
199
|
+
shouldTestRunRequiredTags(parsedGrep.tags, requiredTags)
|
169
200
|
)
|
170
201
|
}
|
171
202
|
|
172
|
-
function parseGrep
|
203
|
+
function parseGrep(titlePart, tags) {
|
173
204
|
return {
|
174
205
|
title: parseFullTitleGrep(titlePart),
|
175
206
|
tags: parseTagsGrep(tags),
|
@@ -183,5 +214,6 @@ module.exports = {
|
|
183
214
|
parseTagsGrep,
|
184
215
|
shouldTestRun,
|
185
216
|
shouldTestRunTags,
|
217
|
+
shouldTestRunRequiredTags,
|
186
218
|
shouldTestRunTitle,
|
187
219
|
}
|