find-cypress-specs 1.25.3 → 1.27.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/README.md +37 -1
- package/bin/find.js +41 -86
- package/package.json +7 -6
- package/src/index.js +124 -19
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# find-cypress-specs [![renovate-app badge][renovate-badge]][renovate-app]  [](https://github.com/bahmutov/find-cypress-specs/actions/workflows/ci.yml)
|
|
2
2
|
|
|
3
3
|
> Find Cypress spec files using the config settings
|
|
4
4
|
|
|
@@ -10,6 +10,14 @@ cypress/e2e/spec.js,cypress/e2e/featureA/user.js
|
|
|
10
10
|
|
|
11
11
|
Supports JS and TS specs
|
|
12
12
|
|
|
13
|
+
## Component specs
|
|
14
|
+
|
|
15
|
+
By default, it finds the E2E specs and tests. You can find component specs using `--component` CLI option
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
$ npx find-cypress-specs --component
|
|
19
|
+
```
|
|
20
|
+
|
|
13
21
|
## against branch
|
|
14
22
|
|
|
15
23
|
By default, this module simply prints all spec filenames. You can add `--branch` parameter to only print the specs changed against that `origin/branch`.
|
|
@@ -224,6 +232,8 @@ You can see how Cypress finds the specs using `DEBUG=cypress:cli,cypress:server:
|
|
|
224
232
|
|
|
225
233
|
Run the utility with environment variable `DEBUG=find-cypress-specs` to see the verbose logs
|
|
226
234
|
|
|
235
|
+

|
|
236
|
+
|
|
227
237
|
## Videos
|
|
228
238
|
|
|
229
239
|
- [Use Ava Snapshots And Execa-wrap To Write End-to-End Tests For CLI Utilities](https://youtu.be/rsw17RqP0G0)
|
|
@@ -237,6 +247,8 @@ Run the utility with environment variable `DEBUG=find-cypress-specs` to see the
|
|
|
237
247
|
|
|
238
248
|
You can use this module via its NPM module API.
|
|
239
249
|
|
|
250
|
+
### getSpecs
|
|
251
|
+
|
|
240
252
|
```js
|
|
241
253
|
const { getSpecs } = require('find-cypress-specs')
|
|
242
254
|
// somewhere in the cypress.config.js
|
|
@@ -246,6 +258,30 @@ setupNodeEvents(on, config) {
|
|
|
246
258
|
}
|
|
247
259
|
```
|
|
248
260
|
|
|
261
|
+
You can pass the `config` object to the `getSpecs` method. If there is no `config` parameter, it will read the config file automatically.
|
|
262
|
+
|
|
263
|
+
```js
|
|
264
|
+
const specs = getSpecs({
|
|
265
|
+
e2e: {
|
|
266
|
+
specPattern: '*/e2e/featureA/*.cy.ts',
|
|
267
|
+
},
|
|
268
|
+
})
|
|
269
|
+
// ['cypress/e2e/featureA/spec.cy.ts']
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### getTests
|
|
273
|
+
|
|
274
|
+
Returns an object with individual test information
|
|
275
|
+
|
|
276
|
+
```js
|
|
277
|
+
const { getTests } = require('find-cypress-specs')
|
|
278
|
+
const { jsonResults, tagTestCounts } = getTests()
|
|
279
|
+
// jsonResults is an object
|
|
280
|
+
// with an entry per spec file
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
See [get-tests.js](./test/npm/get-tests.js) for details and examples.
|
|
284
|
+
|
|
249
285
|
## Small print
|
|
250
286
|
|
|
251
287
|
Author: Gleb Bahmutov <gleb.bahmutov@gmail.com> © 2022
|
package/bin/find.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const arg = require('arg')
|
|
4
|
-
const { getSpecs,
|
|
5
|
-
const { pickTaggedTestsFrom, leavePendingTestsOnly } = require('../src/tagged')
|
|
6
|
-
const { addCounts } = require('../src/count')
|
|
4
|
+
const { getSpecs, findChangedFiles, getTests } = require('../src')
|
|
7
5
|
const { stringAllInfo } = require('../src/print')
|
|
8
6
|
|
|
9
7
|
const fs = require('fs')
|
|
@@ -42,6 +40,8 @@ const args = arg({
|
|
|
42
40
|
'--time-trace': Boolean,
|
|
43
41
|
// do not add more than this number of extra specs after tracing
|
|
44
42
|
'--max-added-traced-specs': Number,
|
|
43
|
+
// find component specs
|
|
44
|
+
'--component': Boolean,
|
|
45
45
|
// aliases
|
|
46
46
|
'-n': '--names',
|
|
47
47
|
'--name': '--names',
|
|
@@ -57,95 +57,50 @@ const args = arg({
|
|
|
57
57
|
})
|
|
58
58
|
|
|
59
59
|
debug('arguments %o', args)
|
|
60
|
+
const specType = args['--component'] ? 'component' : 'e2e'
|
|
60
61
|
|
|
61
|
-
const specs = getSpecs()
|
|
62
|
-
if (args['--names'] || args['--tags']) {
|
|
63
|
-
if (!specs.length) {
|
|
64
|
-
console.log('no specs found')
|
|
65
|
-
} else {
|
|
66
|
-
console.log('')
|
|
67
|
-
// counts the number of tests for each tag across all specs
|
|
68
|
-
const tagTestCounts = {}
|
|
69
|
-
const jsonResults = {}
|
|
70
|
-
|
|
71
|
-
specs.forEach((filename) => {
|
|
72
|
-
jsonResults[filename] = {
|
|
73
|
-
counts: {
|
|
74
|
-
tests: 0,
|
|
75
|
-
pending: 0,
|
|
76
|
-
},
|
|
77
|
-
tests: [],
|
|
78
|
-
}
|
|
79
|
-
const source = fs.readFileSync(filename, 'utf8')
|
|
80
|
-
const result = getTestNames(source, true)
|
|
81
|
-
// enable if need to debug the parsed test
|
|
82
|
-
// console.dir(result.structure, { depth: null })
|
|
83
|
-
collectResults(result.structure, jsonResults[filename].tests)
|
|
84
|
-
|
|
85
|
-
if (args['--tags']) {
|
|
86
|
-
const specTagCounts = countTags(result.structure)
|
|
87
|
-
Object.keys(specTagCounts).forEach((tag) => {
|
|
88
|
-
if (!(tag in tagTestCounts)) {
|
|
89
|
-
tagTestCounts[tag] = specTagCounts[tag]
|
|
90
|
-
} else {
|
|
91
|
-
tagTestCounts[tag] += specTagCounts[tag]
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
addCounts(jsonResults)
|
|
98
|
-
|
|
99
|
-
if (args['--names']) {
|
|
100
|
-
if (args['--tagged']) {
|
|
101
|
-
// filter all collected tests to those that have the given tag(s)
|
|
102
|
-
const splitTags = args['--tagged']
|
|
103
|
-
.split(',')
|
|
104
|
-
.map((s) => s.trim())
|
|
105
|
-
.filter(Boolean)
|
|
106
|
-
debug('filtering all tests by tag "%o"', splitTags)
|
|
107
|
-
pickTaggedTestsFrom(jsonResults, splitTags)
|
|
108
|
-
// recompute the number of tests
|
|
109
|
-
addCounts(jsonResults)
|
|
110
|
-
} else if (args['--skipped']) {
|
|
111
|
-
debug('leaving only skipped (pending) tests')
|
|
112
|
-
leavePendingTestsOnly(jsonResults)
|
|
113
|
-
// recompute the number of tests
|
|
114
|
-
addCounts(jsonResults)
|
|
115
|
-
}
|
|
62
|
+
const specs = getSpecs(undefined, specType)
|
|
116
63
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
console.log('')
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (args['--tags']) {
|
|
136
|
-
const tagEntries = Object.entries(tagTestCounts)
|
|
137
|
-
const sortedTagEntries = tagEntries.sort((a, b) => {
|
|
138
|
-
// every entry is [tag, count], so compare the tags
|
|
139
|
-
return a[0].localeCompare(b[0])
|
|
64
|
+
if (args['--names'] || args['--tags']) {
|
|
65
|
+
// counts the number of tests for each tag across all specs
|
|
66
|
+
const { jsonResults, tagTestCounts } = getTests(specs, {
|
|
67
|
+
tags: args['--tags'],
|
|
68
|
+
tagged: args['--tagged'],
|
|
69
|
+
skipped: args['--skipped'],
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
if (args['--names']) {
|
|
73
|
+
if (args['--count']) {
|
|
74
|
+
let n = 0
|
|
75
|
+
Object.keys(jsonResults).forEach((filename) => {
|
|
76
|
+
const skippedCount = jsonResults[filename].counts.pending
|
|
77
|
+
n += skippedCount
|
|
140
78
|
})
|
|
79
|
+
console.log(n)
|
|
80
|
+
} else {
|
|
141
81
|
if (args['--json']) {
|
|
142
|
-
|
|
143
|
-
const tagResults = Object.fromEntries(sortedTagEntries)
|
|
144
|
-
console.log(JSON.stringify(tagResults, null, 2))
|
|
82
|
+
console.log(JSON.stringify(jsonResults, null, 2))
|
|
145
83
|
} else {
|
|
146
|
-
const
|
|
147
|
-
console.log(
|
|
84
|
+
const str = stringAllInfo(jsonResults)
|
|
85
|
+
console.log(str)
|
|
148
86
|
}
|
|
87
|
+
console.log('')
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (args['--tags']) {
|
|
92
|
+
const tagEntries = Object.entries(tagTestCounts)
|
|
93
|
+
const sortedTagEntries = tagEntries.sort((a, b) => {
|
|
94
|
+
// every entry is [tag, count], so compare the tags
|
|
95
|
+
return a[0].localeCompare(b[0])
|
|
96
|
+
})
|
|
97
|
+
if (args['--json']) {
|
|
98
|
+
// assemble a json object with the tag counts
|
|
99
|
+
const tagResults = Object.fromEntries(sortedTagEntries)
|
|
100
|
+
console.log(JSON.stringify(tagResults, null, 2))
|
|
101
|
+
} else {
|
|
102
|
+
const table = consoleTable.getTable(['Tag', 'Tests'], sortedTagEntries)
|
|
103
|
+
console.log(table)
|
|
149
104
|
}
|
|
150
105
|
}
|
|
151
106
|
} else if (args['--branch']) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "find-cypress-specs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.0",
|
|
4
4
|
"description": "Find Cypress spec files using the config settings",
|
|
5
5
|
"main": "src",
|
|
6
6
|
"files": [
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
"test": "ava",
|
|
15
15
|
"cy:run": "DEBUG=cypress:cli,cypress:server:specs cypress run",
|
|
16
16
|
"demo": "DEBUG=find-cypress-specs node ./bin/find",
|
|
17
|
-
"demo-names": "node ./bin/find --names",
|
|
18
|
-
"demo-skipped-tests": "node ./bin/find --names --skipped",
|
|
19
|
-
"demo-count-skipped-tests": "node ./bin/find --names --skipped --count",
|
|
17
|
+
"demo-names": "DEBUG=find-cypress-specs node ./bin/find --names",
|
|
18
|
+
"demo-skipped-tests": "DEBUG=find-cypress-specs node ./bin/find --names --skipped",
|
|
19
|
+
"demo-count-skipped-tests": "DEBUG=find-cypress-specs node ./bin/find --names --skipped --count",
|
|
20
20
|
"demo-tags": "node ./bin/find --tags",
|
|
21
21
|
"demo-tags-json": "node ./bin/find --tags --json",
|
|
22
22
|
"demo-names-and-tags": "node ./bin/find --names --tags",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"count-changed-specs": "node ./bin/find --branch main --count",
|
|
28
28
|
"semantic-release": "semantic-release",
|
|
29
29
|
"deps": "spec-change --folder . --mask 'cypress/**/*.{js,ts}'",
|
|
30
|
-
"deps-changed": "DEBUG=find-cypress-specs node ./bin/find --branch main --parent --trace-imports cypress --time-trace --cache-trace"
|
|
30
|
+
"deps-changed": "DEBUG=find-cypress-specs node ./bin/find --branch main --parent --trace-imports cypress --time-trace --cache-trace",
|
|
31
|
+
"demo-component": "DEBUG=find-cypress-specs node ./bin/find --component --names"
|
|
31
32
|
},
|
|
32
33
|
"repository": {
|
|
33
34
|
"type": "git",
|
|
@@ -44,7 +45,7 @@
|
|
|
44
45
|
"homepage": "https://github.com/bahmutov/find-cypress-specs#readme",
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"ava": "^4.0.0",
|
|
47
|
-
"cypress": "12.
|
|
48
|
+
"cypress": "12.7.0",
|
|
48
49
|
"execa-wrap": "^1.4.0",
|
|
49
50
|
"prettier": "^2.5.1",
|
|
50
51
|
"really-need": "^1.9.2",
|
package/src/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
const { addCounts } = require('../src/count')
|
|
2
|
+
const { getTestNames, countTags } = require('find-test-names')
|
|
3
|
+
const { pickTaggedTestsFrom, leavePendingTestsOnly } = require('../src/tagged')
|
|
4
|
+
|
|
1
5
|
const debug = require('debug')('find-cypress-specs')
|
|
2
6
|
const fs = require('fs')
|
|
3
7
|
const path = require('path')
|
|
@@ -109,19 +113,36 @@ function findCypressSpecsV9(opts = {}) {
|
|
|
109
113
|
return filtered.map((file) => path.join(options.integrationFolder, file))
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
function findCypressSpecsV10(opts = {}) {
|
|
113
|
-
if (
|
|
114
|
-
throw new Error(
|
|
116
|
+
function findCypressSpecsV10(opts = {}, type = 'e2e') {
|
|
117
|
+
if (type !== 'e2e' && type !== 'component') {
|
|
118
|
+
throw new Error(`Unknown spec type ${type}`)
|
|
115
119
|
}
|
|
116
|
-
|
|
120
|
+
|
|
121
|
+
if (!(type in opts)) {
|
|
122
|
+
throw new Error(`Missing "${type}" object in the Cypress config object`)
|
|
123
|
+
}
|
|
124
|
+
const e2eDefaults = {
|
|
117
125
|
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
|
|
118
126
|
excludeSpecPattern: [],
|
|
119
127
|
}
|
|
120
|
-
const
|
|
121
|
-
specPattern:
|
|
122
|
-
excludeSpecPattern:
|
|
123
|
-
opts.e2e.excludeSpecPattern || defaults.excludeSpecPattern,
|
|
128
|
+
const componentDefaults = {
|
|
129
|
+
specPattern: '**/*.cy.{js,jsx,ts,tsx}',
|
|
130
|
+
excludeSpecPattern: ['/snapshots/*', '/image_snapshots/*'],
|
|
124
131
|
}
|
|
132
|
+
// https://on.cypress.io/configuration
|
|
133
|
+
const options = {}
|
|
134
|
+
|
|
135
|
+
if (type === 'e2e') {
|
|
136
|
+
options.specPattern = opts.e2e.specPattern || e2eDefaults.specPattern
|
|
137
|
+
options.excludeSpecPattern =
|
|
138
|
+
opts.e2e.excludeSpecPattern || e2eDefaults.excludeSpecPattern
|
|
139
|
+
} else if (type === 'component') {
|
|
140
|
+
options.specPattern =
|
|
141
|
+
opts.component.specPattern || componentDefaults.specPattern
|
|
142
|
+
options.excludeSpecPattern =
|
|
143
|
+
opts.component.excludeSpecPattern || componentDefaults.excludeSpecPattern
|
|
144
|
+
}
|
|
145
|
+
|
|
125
146
|
debug('options v10 %o', options)
|
|
126
147
|
|
|
127
148
|
const files = globby.sync(options.specPattern, {
|
|
@@ -133,6 +154,13 @@ function findCypressSpecsV10(opts = {}) {
|
|
|
133
154
|
// go through the files again and eliminate files that match
|
|
134
155
|
// the ignore patterns
|
|
135
156
|
const ignorePatterns = [].concat(options.excludeSpecPattern)
|
|
157
|
+
|
|
158
|
+
// when using component spec pattern, ignore all E2E specs
|
|
159
|
+
if (type === 'component') {
|
|
160
|
+
const e2eIgnorePattern = options.e2e?.specPattern || e2eDefaults.specPattern
|
|
161
|
+
ignorePatterns.push(e2eIgnorePattern)
|
|
162
|
+
}
|
|
163
|
+
|
|
136
164
|
debug('ignore patterns %o', ignorePatterns)
|
|
137
165
|
|
|
138
166
|
// a function which returns true if the file does NOT match
|
|
@@ -155,21 +183,35 @@ function findCypressSpecsV10(opts = {}) {
|
|
|
155
183
|
return filtered
|
|
156
184
|
}
|
|
157
185
|
|
|
158
|
-
function getSpecs() {
|
|
159
|
-
|
|
160
|
-
|
|
186
|
+
function getSpecs(options, type = 'e2e') {
|
|
187
|
+
if (typeof options === 'undefined') {
|
|
188
|
+
options = getConfig()
|
|
189
|
+
}
|
|
190
|
+
return findCypressSpecs(options, type)
|
|
161
191
|
}
|
|
162
192
|
|
|
163
|
-
function findCypressSpecs(options) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
193
|
+
function findCypressSpecs(options, type = 'e2e') {
|
|
194
|
+
debug('finding specs of type %s', type)
|
|
195
|
+
|
|
196
|
+
if (type === 'e2e') {
|
|
197
|
+
if (options.e2e) {
|
|
198
|
+
debug('config has "e2e" property, treating as Cypress v10+')
|
|
199
|
+
const specs = findCypressSpecsV10(options, type)
|
|
200
|
+
return specs
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
debug('reading Cypress config < v10')
|
|
204
|
+
const specs = findCypressSpecsV9(options)
|
|
167
205
|
return specs
|
|
206
|
+
} else if (type === 'component') {
|
|
207
|
+
debug('finding component specs')
|
|
208
|
+
const specs = findCypressSpecsV10(options, type)
|
|
209
|
+
return specs
|
|
210
|
+
} else {
|
|
211
|
+
console.error('Do not know how to find specs of type "%s"', type)
|
|
212
|
+
console.error('returning an empty list')
|
|
213
|
+
return []
|
|
168
214
|
}
|
|
169
|
-
|
|
170
|
-
debug('reading Cypress config < v10')
|
|
171
|
-
const specs = findCypressSpecsV9(options)
|
|
172
|
-
return specs
|
|
173
215
|
}
|
|
174
216
|
|
|
175
217
|
function collectResults(structure, results) {
|
|
@@ -266,6 +308,68 @@ function findChangedFiles(branch, useParent) {
|
|
|
266
308
|
}
|
|
267
309
|
}
|
|
268
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Collects all specs and for each finds all suits and tests with their tags.
|
|
313
|
+
*/
|
|
314
|
+
function getTests(specs, options = {}) {
|
|
315
|
+
if (!specs) {
|
|
316
|
+
specs = getSpecs()
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const { tags, tagged, skipped } = options
|
|
320
|
+
|
|
321
|
+
// counts the number of tests for each tag across all specs
|
|
322
|
+
const tagTestCounts = {}
|
|
323
|
+
const jsonResults = {}
|
|
324
|
+
|
|
325
|
+
specs.forEach((filename) => {
|
|
326
|
+
jsonResults[filename] = {
|
|
327
|
+
counts: {
|
|
328
|
+
tests: 0,
|
|
329
|
+
pending: 0,
|
|
330
|
+
},
|
|
331
|
+
tests: [],
|
|
332
|
+
}
|
|
333
|
+
const source = fs.readFileSync(filename, 'utf8')
|
|
334
|
+
const result = getTestNames(source, true)
|
|
335
|
+
// enable if need to debug the parsed test
|
|
336
|
+
// console.dir(result.structure, { depth: null })
|
|
337
|
+
collectResults(result.structure, jsonResults[filename].tests)
|
|
338
|
+
|
|
339
|
+
if (tags) {
|
|
340
|
+
const specTagCounts = countTags(result.structure)
|
|
341
|
+
Object.keys(specTagCounts).forEach((tag) => {
|
|
342
|
+
if (!(tag in tagTestCounts)) {
|
|
343
|
+
tagTestCounts[tag] = specTagCounts[tag]
|
|
344
|
+
} else {
|
|
345
|
+
tagTestCounts[tag] += specTagCounts[tag]
|
|
346
|
+
}
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
addCounts(jsonResults)
|
|
352
|
+
|
|
353
|
+
if (tagged) {
|
|
354
|
+
// filter all collected tests to those that have the given tag(s)
|
|
355
|
+
const splitTags = tagged
|
|
356
|
+
.split(',')
|
|
357
|
+
.map((s) => s.trim())
|
|
358
|
+
.filter(Boolean)
|
|
359
|
+
debug('filtering all tests by tag "%o"', splitTags)
|
|
360
|
+
pickTaggedTestsFrom(jsonResults, splitTags)
|
|
361
|
+
// recompute the number of tests
|
|
362
|
+
addCounts(jsonResults)
|
|
363
|
+
} else if (skipped) {
|
|
364
|
+
debug('leaving only skipped (pending) tests')
|
|
365
|
+
leavePendingTestsOnly(jsonResults)
|
|
366
|
+
// recompute the number of tests
|
|
367
|
+
addCounts(jsonResults)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return { jsonResults, tagTestCounts }
|
|
371
|
+
}
|
|
372
|
+
|
|
269
373
|
module.exports = {
|
|
270
374
|
getSpecs,
|
|
271
375
|
// individual utilities
|
|
@@ -273,4 +377,5 @@ module.exports = {
|
|
|
273
377
|
findCypressSpecs,
|
|
274
378
|
collectResults,
|
|
275
379
|
findChangedFiles,
|
|
380
|
+
getTests,
|
|
276
381
|
}
|