find-cypress-specs 1.12.0 → 1.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/README.md +16 -1
- package/bin/find.js +32 -45
- package/package.json +4 -2
- package/src/count.js +56 -0
- package/src/print.js +50 -0
- package/src/tagged.js +72 -0
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
|
|
|
@@ -70,6 +70,21 @@ Each tag count includes the tests that use the tag directly, and the _effective_
|
|
|
70
70
|
|
|
71
71
|
You can print the results in JSON format using `--json` or `-j` option.
|
|
72
72
|
|
|
73
|
+
## Test names filtered by a tag
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
$ npx find-cypress-specs --names --tagged <single tag>
|
|
77
|
+
# finds all specs and tests, then filters the output by a single tag
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Test names filtered by multiple tags
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
$ npx find-cypress-specs --names --tagged <tag1>,<tag2>,<tag3>,...
|
|
84
|
+
# finds all specs and tests, then filters the output showing all tests
|
|
85
|
+
# tagged with tag1 or tag2 or tag3 or ...
|
|
86
|
+
```
|
|
87
|
+
|
|
73
88
|
## Details
|
|
74
89
|
|
|
75
90
|
Cypress uses the resolved [configuration values](https://on.cypress.io/configuration) to find the spec files to run. It searches the `integrationFolder` for all patterns listed in `testFiles` and removes any files matching the `ignoreTestFiles` patterns.
|
package/bin/find.js
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
const arg = require('arg')
|
|
4
4
|
const { getSpecs, collectResults, findChangedFiles } = require('../src')
|
|
5
|
+
const { pickTaggedTestsFrom } = require('../src/tagged')
|
|
6
|
+
const { addCounts } = require('../src/count')
|
|
7
|
+
const { stringAllInfo } = require('../src/print')
|
|
8
|
+
|
|
5
9
|
const fs = require('fs')
|
|
6
|
-
const
|
|
7
|
-
const { getTestNames, formatTestList, countTags } = require('find-test-names')
|
|
10
|
+
const { getTestNames, countTags } = require('find-test-names')
|
|
8
11
|
const consoleTable = require('console.table')
|
|
9
12
|
const debug = require('debug')('find-cypress-specs')
|
|
10
13
|
|
|
@@ -15,7 +18,9 @@ const args = arg({
|
|
|
15
18
|
'--json': Boolean,
|
|
16
19
|
// find the specs that have changed against this Git branch
|
|
17
20
|
'--branch': String,
|
|
18
|
-
'--count':
|
|
21
|
+
'--count': Boolean,
|
|
22
|
+
// filter all tests to those that have the given tag
|
|
23
|
+
'--tagged': String,
|
|
19
24
|
|
|
20
25
|
// aliases
|
|
21
26
|
'-n': '--names',
|
|
@@ -34,43 +39,23 @@ if (args['--names'] || args['--tags']) {
|
|
|
34
39
|
console.log('no specs found')
|
|
35
40
|
} else {
|
|
36
41
|
console.log('')
|
|
37
|
-
let testsN = 0
|
|
38
|
-
let pendingTestsN = 0
|
|
39
|
-
|
|
40
42
|
// counts the number of tests for each tag across all specs
|
|
41
43
|
const tagTestCounts = {}
|
|
42
|
-
|
|
43
44
|
const jsonResults = {}
|
|
44
45
|
|
|
45
46
|
specs.forEach((filename) => {
|
|
46
|
-
jsonResults[filename] =
|
|
47
|
+
jsonResults[filename] = {
|
|
48
|
+
counts: {
|
|
49
|
+
tests: 0,
|
|
50
|
+
pending: 0,
|
|
51
|
+
},
|
|
52
|
+
tests: [],
|
|
53
|
+
}
|
|
47
54
|
const source = fs.readFileSync(filename, 'utf8')
|
|
48
55
|
const result = getTestNames(source, true)
|
|
49
56
|
// enable if need to debug the parsed test
|
|
50
57
|
// console.dir(result.structure, { depth: null })
|
|
51
|
-
|
|
52
|
-
testsN += result.testCount
|
|
53
|
-
const testCount = pluralize('test', result.testNames.length, true)
|
|
54
|
-
pendingTestsN += result.pendingTestCount
|
|
55
|
-
|
|
56
|
-
if (args['--names']) {
|
|
57
|
-
if (args['--json']) {
|
|
58
|
-
collectResults(result.structure, jsonResults[filename])
|
|
59
|
-
} else {
|
|
60
|
-
if (result.pendingTestCount) {
|
|
61
|
-
console.log(
|
|
62
|
-
'%s (%s, %d pending)',
|
|
63
|
-
filename,
|
|
64
|
-
testCount,
|
|
65
|
-
result.pendingTestCount,
|
|
66
|
-
)
|
|
67
|
-
} else {
|
|
68
|
-
console.log('%s (%s)', filename, testCount)
|
|
69
|
-
}
|
|
70
|
-
console.log(formatTestList(result.structure))
|
|
71
|
-
console.log('')
|
|
72
|
-
}
|
|
73
|
-
}
|
|
58
|
+
collectResults(result.structure, jsonResults[filename].tests)
|
|
74
59
|
|
|
75
60
|
if (args['--tags']) {
|
|
76
61
|
const specTagCounts = countTags(result.structure)
|
|
@@ -84,24 +69,26 @@ if (args['--names'] || args['--tags']) {
|
|
|
84
69
|
}
|
|
85
70
|
})
|
|
86
71
|
|
|
72
|
+
addCounts(jsonResults)
|
|
73
|
+
|
|
87
74
|
if (args['--names']) {
|
|
75
|
+
if (args['--tagged']) {
|
|
76
|
+
// filter all collected tests to those that have the given tag(s)
|
|
77
|
+
const splitTags = args['--tagged']
|
|
78
|
+
.split(',')
|
|
79
|
+
.map((s) => s.trim())
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
debug('filtering all tests by tag "%o"', splitTags)
|
|
82
|
+
pickTaggedTestsFrom(jsonResults, splitTags)
|
|
83
|
+
// recompute the number of tests
|
|
84
|
+
addCounts(jsonResults)
|
|
85
|
+
}
|
|
86
|
+
|
|
88
87
|
if (args['--json']) {
|
|
89
88
|
console.log(JSON.stringify(jsonResults, null, 2))
|
|
90
89
|
} else {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
'found %s (%s, %d pending)',
|
|
94
|
-
pluralize('spec', specs.length, true),
|
|
95
|
-
pluralize('test', testsN, true),
|
|
96
|
-
pendingTestsN,
|
|
97
|
-
)
|
|
98
|
-
} else {
|
|
99
|
-
console.log(
|
|
100
|
-
'found %s (%s)',
|
|
101
|
-
pluralize('spec', specs.length, true),
|
|
102
|
-
pluralize('test', testsN, true),
|
|
103
|
-
)
|
|
104
|
-
}
|
|
90
|
+
const str = stringAllInfo(jsonResults)
|
|
91
|
+
console.log(str)
|
|
105
92
|
}
|
|
106
93
|
console.log('')
|
|
107
94
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "find-cypress-specs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Find Cypress spec files using the config settings",
|
|
5
5
|
"main": "src",
|
|
6
6
|
"files": [
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
"demo-names-and-tags": "node ./bin/find --names --tags",
|
|
21
21
|
"demo-names-and-tags-json": "node ./bin/find --names --tags --json",
|
|
22
22
|
"demo-names-json": "node ./bin/find --names --json",
|
|
23
|
+
"demo-names-tagged": "node ./bin/find --names --tagged @user",
|
|
23
24
|
"print-changed-specs": "node ./bin/find --branch main",
|
|
25
|
+
"count-changed-specs": "node ./bin/find --branch main --count",
|
|
24
26
|
"semantic-release": "semantic-release"
|
|
25
27
|
},
|
|
26
28
|
"repository": {
|
|
@@ -38,7 +40,7 @@
|
|
|
38
40
|
"homepage": "https://github.com/bahmutov/find-cypress-specs#readme",
|
|
39
41
|
"devDependencies": {
|
|
40
42
|
"ava": "^4.0.0",
|
|
41
|
-
"cypress": "9.
|
|
43
|
+
"cypress": "9.5.1",
|
|
42
44
|
"execa-wrap": "^1.4.0",
|
|
43
45
|
"prettier": "^2.5.1",
|
|
44
46
|
"semantic-release": "19.0.2"
|
package/src/count.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* For each file and each
|
|
3
|
+
*/
|
|
4
|
+
function addCounts(json) {
|
|
5
|
+
Object.keys(json).forEach((filename) => {
|
|
6
|
+
const fileInfo = json[filename]
|
|
7
|
+
if (!fileInfo.counts) {
|
|
8
|
+
fileInfo.counts = {
|
|
9
|
+
tests: 0,
|
|
10
|
+
pending: 0,
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
fileInfo.counts.tests =
|
|
15
|
+
countTests(fileInfo.tests) + countTests(fileInfo.suites)
|
|
16
|
+
fileInfo.counts.pending =
|
|
17
|
+
countPendingTests(fileInfo.tests) + countPendingTests(fileInfo.suites)
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function countTests(testsOrSuites) {
|
|
22
|
+
if (!testsOrSuites) {
|
|
23
|
+
return 0
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return testsOrSuites.reduce((count, test) => {
|
|
27
|
+
if (test.type === 'test') {
|
|
28
|
+
return count + 1
|
|
29
|
+
} else if (test.type === 'suite') {
|
|
30
|
+
return count + countTests(test.tests) + countTests(test.suites)
|
|
31
|
+
}
|
|
32
|
+
}, 0)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function countPendingTests(testsOrSuites) {
|
|
36
|
+
if (!testsOrSuites) {
|
|
37
|
+
return 0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return testsOrSuites.reduce((count, test) => {
|
|
41
|
+
if (test.type === 'test') {
|
|
42
|
+
return test.pending ? count + 1 : count
|
|
43
|
+
} else if (test.type === 'suite') {
|
|
44
|
+
if (test.pending) {
|
|
45
|
+
// all tests inside should count as pending
|
|
46
|
+
return count + countTests(test.tests) + countTests(test.suites)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
count + countPendingTests(test.tests) + countPendingTests(test.suites)
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
}, 0)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { addCounts }
|
package/src/print.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const pluralize = require('pluralize')
|
|
2
|
+
const { formatTestList } = require('find-test-names')
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Outputs a string representation of the json test results object,
|
|
6
|
+
* like a tree of suites and tests.
|
|
7
|
+
*/
|
|
8
|
+
function stringFileTests(fileName, fileInfo) {
|
|
9
|
+
const testCount = pluralize('test', fileInfo.counts.tests, true)
|
|
10
|
+
const headerLine = fileInfo.counts.pending
|
|
11
|
+
? `${fileName} (${testCount}, ${fileInfo.counts.pending} pending)`
|
|
12
|
+
: `${fileName} (${testCount})`
|
|
13
|
+
|
|
14
|
+
const body = formatTestList(fileInfo.tests)
|
|
15
|
+
|
|
16
|
+
return headerLine + '\n' + body + '\n'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function stringAllInfo(allInfo) {
|
|
20
|
+
let fileCount = 0
|
|
21
|
+
let testCount = 0
|
|
22
|
+
let pendingTestCount = 0
|
|
23
|
+
|
|
24
|
+
const allInfoString = Object.keys(allInfo)
|
|
25
|
+
.map((fileName) => {
|
|
26
|
+
const fileInfo = allInfo[fileName]
|
|
27
|
+
fileCount += 1
|
|
28
|
+
testCount += fileInfo.counts.tests
|
|
29
|
+
pendingTestCount += fileInfo.counts.pending
|
|
30
|
+
return stringFileTests(fileName, fileInfo)
|
|
31
|
+
})
|
|
32
|
+
.join('\n')
|
|
33
|
+
|
|
34
|
+
// footer line is something like
|
|
35
|
+
// found 2 specs (4 tests, 1 pending)
|
|
36
|
+
let footer = `found ${pluralize('spec', fileCount, true)}`
|
|
37
|
+
const testWord = pluralize('test', testCount, true)
|
|
38
|
+
if (pendingTestCount) {
|
|
39
|
+
footer += ` (${testWord}, ${pendingTestCount} pending)`
|
|
40
|
+
} else {
|
|
41
|
+
footer += ` (${testWord})`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return allInfoString + '\n' + footer
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = {
|
|
48
|
+
stringFileTests,
|
|
49
|
+
stringAllInfo,
|
|
50
|
+
}
|
package/src/tagged.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const { addCounts } = require('./count')
|
|
2
|
+
|
|
3
|
+
function arraysOverlap(a, b) {
|
|
4
|
+
if (!Array.isArray(a) || !Array.isArray(b)) {
|
|
5
|
+
return false
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (a.length < b.length) {
|
|
9
|
+
return a.some((item) => b.includes(item))
|
|
10
|
+
} else {
|
|
11
|
+
return b.some((item) => a.includes(item))
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// note: modifies the tests in place
|
|
16
|
+
function pickTaggedTests(tests, tag) {
|
|
17
|
+
if (!Array.isArray(tests)) {
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
const tags = Array.isArray(tag) ? tag : [tag]
|
|
21
|
+
const filteredTests = tests.filter((test) => {
|
|
22
|
+
if (test.type === 'test') {
|
|
23
|
+
return test.tags && arraysOverlap(test.tags, tags)
|
|
24
|
+
} else if (test.type === 'suite') {
|
|
25
|
+
if (test.tags && arraysOverlap(test.tags, tags)) {
|
|
26
|
+
return true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// maybe there is some test inside this suite
|
|
30
|
+
// with the tag? Filter all other tests
|
|
31
|
+
return (
|
|
32
|
+
pickTaggedTests(test.tests, tags) || pickTaggedTests(test.suites, tags)
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
tests.length = 0
|
|
37
|
+
tests.push(...filteredTests)
|
|
38
|
+
return filteredTests.length > 0
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function removeEmptyNodes(json) {
|
|
42
|
+
Object.keys(json).forEach((filename) => {
|
|
43
|
+
const fileTests = json[filename].tests
|
|
44
|
+
if (!fileTests.length) {
|
|
45
|
+
delete json[filename]
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
return json
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Takes an object of tests collected from all files,
|
|
53
|
+
* and removes all tests that do not have the given tag applied.
|
|
54
|
+
* Modifies the given object in place.
|
|
55
|
+
*/
|
|
56
|
+
function pickTaggedTestsFrom(json, tag) {
|
|
57
|
+
Object.keys(json).forEach((filename) => {
|
|
58
|
+
const fileTests = json[filename].tests
|
|
59
|
+
pickTaggedTests(fileTests, tag)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const result = removeEmptyNodes(json)
|
|
63
|
+
addCounts(result)
|
|
64
|
+
return result
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
arraysOverlap,
|
|
69
|
+
pickTaggedTestsFrom,
|
|
70
|
+
removeEmptyNodes,
|
|
71
|
+
pickTaggedTests,
|
|
72
|
+
}
|