diff --git a/.circleci/config.yml b/.circleci/config.yml
index 58e7a99c..bccc2604 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -75,6 +75,7 @@ workflows:
- run: npx nyc report --check-coverage true --lines 100 --include cypress/unit.js
- cypress/run:
+ # TODO switch to separate example in "examples/..."
name: backend coverage
requires:
- cypress/install
@@ -96,7 +97,7 @@ workflows:
path: coverage
# print code coverage summary to the terminal
# and make sure there the coverage is above certain limit
- - run: npx nyc report --check-coverage true --lines 85
+ - run: npx nyc report --check-coverage true --lines 72
# and look at the server index file - should be fully covered
- run: npx nyc report --check-coverage true --lines 100 --include test-backend/index.js
@@ -280,6 +281,35 @@ workflows:
node ../../scripts/only-covered main.js
working_directory: examples/use-plugins-and-support
+ - cypress/run:
+ attach-workspace: true
+ name: example-one-spec
+ requires:
+ - cypress/install
+ # there are no jobs to follow this one
+ # so no need to save the workspace files (saves time)
+ no-workspace: true
+ command: npx cypress run --project examples/one-spec
+ # store screenshots and videos
+ store_artifacts: true
+ post-steps:
+ - run: cat examples/one-spec/.nyc_output/out.json
+ # store the created coverage report folder
+ # you can click on it in the CircleCI UI
+ # to see live static HTML site
+ - store_artifacts:
+ path: examples/one-spec/coverage
+ # make sure the examples captures 100% of code
+ - run:
+ command: npx nyc report --check-coverage true --lines 100
+ working_directory: examples/one-spec
+ - run:
+ name: Check code coverage 📈
+ command: |
+ node ../../scripts/check-coverage main.js
+ node ../../scripts/only-covered main.js
+ working_directory: examples/one-spec
+
- publish:
filters:
branches:
@@ -296,3 +326,4 @@ workflows:
- example-same-folder
- example-support-files
- example-use-plugins-and-support
+ - example-one-spec
diff --git a/cypress/integration/filtering.js b/cypress/integration/filtering.js
new file mode 100644
index 00000000..212c8618
--- /dev/null
+++ b/cypress/integration/filtering.js
@@ -0,0 +1,84 @@
+const { filterSpecsFromCoverage } = require('../../utils')
+
+describe('minimatch', () => {
+ it('string matches', () => {
+ expect(
+ Cypress.minimatch('/path/to/specA.js', '/path/to/specA.js'),
+ 'matches full strings'
+ ).to.be.true
+
+ expect(
+ Cypress.minimatch('/path/to/specA.js', 'specA.js'),
+ 'does not match just the end'
+ ).to.be.false
+
+ expect(
+ Cypress.minimatch('/path/to/specA.js', '**/specA.js'),
+ 'matches using **'
+ ).to.be.true
+ })
+})
+
+describe('filtering specs', () => {
+ it('filters list of specs by single string', () => {
+ const config = cy.stub()
+ config.withArgs('testFiles').returns(['specA.js'])
+ config.withArgs('integrationFolder').returns('/path/to/integration/')
+
+ const totalCoverage = {
+ '/path/to/specA.js': {},
+ '/path/to/specB.js': {}
+ }
+ const result = filterSpecsFromCoverage(totalCoverage, config)
+ expect(result).to.deep.equal({
+ '/path/to/specB.js': {}
+ })
+ })
+
+ it('filters list of specs by pattern', () => {
+ const config = cy.stub()
+ config.withArgs('testFiles').returns(['**/*B.js'])
+ config.withArgs('integrationFolder').returns('/path/to/integration/')
+
+ const totalCoverage = {
+ '/path/to/specA.js': {},
+ '/path/to/specB.js': {}
+ }
+ const result = filterSpecsFromCoverage(totalCoverage, config)
+ expect(result).to.deep.equal({
+ '/path/to/specA.js': {}
+ })
+ })
+
+ it('filters list of specs by pattern and single spec', () => {
+ const config = cy.stub()
+ config.withArgs('testFiles').returns(['**/*B.js', 'specA.js'])
+ config.withArgs('integrationFolder').returns('/path/to/integration/')
+
+ const totalCoverage = {
+ '/path/to/specA.js': {},
+ '/path/to/specB.js': {}
+ }
+ const result = filterSpecsFromCoverage(totalCoverage, config)
+ expect(result, 'all specs have been filtered out').to.deep.equal({})
+ })
+
+ it('filters list of specs in integration folder', () => {
+ const config = cy.stub()
+ config.withArgs('testFiles').returns('**/*.*') // default pattern
+ config.withArgs('integrationFolder').returns('/path/to/integration/')
+
+ const totalCoverage = {
+ '/path/to/specA.js': {},
+ '/path/to/specB.js': {},
+ // these files should be removed
+ '/path/to/integration/spec1.js': {},
+ '/path/to/integration/spec2.js': {}
+ }
+ const result = filterSpecsFromCoverage(totalCoverage, config)
+ expect(result).to.deep.equal({
+ '/path/to/specA.js': {},
+ '/path/to/specB.js': {}
+ })
+ })
+})
diff --git a/examples/one-spec/.babelrc b/examples/one-spec/.babelrc
new file mode 100644
index 00000000..7a016cf8
--- /dev/null
+++ b/examples/one-spec/.babelrc
@@ -0,0 +1,3 @@
+{
+ "plugins": ["istanbul"]
+}
diff --git a/examples/one-spec/README.md b/examples/one-spec/README.md
new file mode 100644
index 00000000..8341c68a
--- /dev/null
+++ b/examples/one-spec/README.md
@@ -0,0 +1,3 @@
+# example: one-spec
+
+Only running a single spec
diff --git a/examples/one-spec/cypress.json b/examples/one-spec/cypress.json
new file mode 100644
index 00000000..e5a25c6e
--- /dev/null
+++ b/examples/one-spec/cypress.json
@@ -0,0 +1,5 @@
+{
+ "fixturesFolder": false,
+ "pluginsFile": "cypress/plugins/index.js",
+ "testFiles": ["spec-a.js"]
+}
diff --git a/examples/one-spec/cypress/integration/spec-a.js b/examples/one-spec/cypress/integration/spec-a.js
new file mode 100644
index 00000000..92b53c22
--- /dev/null
+++ b/examples/one-spec/cypress/integration/spec-a.js
@@ -0,0 +1,13 @@
+///
+it('spec a', () => {
+ cy.visit('index.html')
+ cy.contains('Page body')
+
+ cy.window()
+ .invoke('add', 2, 3)
+ .should('equal', 5)
+
+ cy.window()
+ .invoke('sub', 2, 3)
+ .should('equal', -1)
+})
diff --git a/examples/one-spec/cypress/integration/spec-b.js b/examples/one-spec/cypress/integration/spec-b.js
new file mode 100644
index 00000000..a9a644cd
--- /dev/null
+++ b/examples/one-spec/cypress/integration/spec-b.js
@@ -0,0 +1,5 @@
+///
+it('spec b', () => {
+ // should not run
+ throw new Error('Spec b should not run')
+})
diff --git a/examples/one-spec/cypress/plugins/index.js b/examples/one-spec/cypress/plugins/index.js
new file mode 100644
index 00000000..b17c48db
--- /dev/null
+++ b/examples/one-spec/cypress/plugins/index.js
@@ -0,0 +1,5 @@
+module.exports = (on, config) => {
+ require('../../../../task')(on, config)
+ on('file:preprocessor', require('../../../../use-babelrc'))
+ return config
+}
diff --git a/examples/one-spec/cypress/support/index.js b/examples/one-spec/cypress/support/index.js
new file mode 100644
index 00000000..dd60efa2
--- /dev/null
+++ b/examples/one-spec/cypress/support/index.js
@@ -0,0 +1 @@
+import '../../../../support'
diff --git a/examples/one-spec/index.html b/examples/one-spec/index.html
new file mode 100644
index 00000000..b6691c8a
--- /dev/null
+++ b/examples/one-spec/index.html
@@ -0,0 +1,4 @@
+
+ Page body
+
+
diff --git a/examples/one-spec/main-instrumented.js b/examples/one-spec/main-instrumented.js
new file mode 100644
index 00000000..0550e9bb
--- /dev/null
+++ b/examples/one-spec/main-instrumented.js
@@ -0,0 +1,146 @@
+function cov_6k5v991cn() {
+ var path = 'main.js'
+ var hash = 'd384017ecd51a8d90283ba0dec593332209519de'
+ var global = new Function('return this')()
+ var gcv = '__coverage__'
+ var coverageData = {
+ path: 'main.js',
+ statementMap: {
+ '0': {
+ start: {
+ line: 1,
+ column: 0
+ },
+ end: {
+ line: 1,
+ column: 28
+ }
+ },
+ '1': {
+ start: {
+ line: 1,
+ column: 23
+ },
+ end: {
+ line: 1,
+ column: 28
+ }
+ },
+ '2': {
+ start: {
+ line: 3,
+ column: 0
+ },
+ end: {
+ line: 3,
+ column: 28
+ }
+ },
+ '3': {
+ start: {
+ line: 3,
+ column: 23
+ },
+ end: {
+ line: 3,
+ column: 28
+ }
+ }
+ },
+ fnMap: {
+ '0': {
+ name: '(anonymous_0)',
+ decl: {
+ start: {
+ line: 1,
+ column: 13
+ },
+ end: {
+ line: 1,
+ column: 14
+ }
+ },
+ loc: {
+ start: {
+ line: 1,
+ column: 23
+ },
+ end: {
+ line: 1,
+ column: 28
+ }
+ },
+ line: 1
+ },
+ '1': {
+ name: '(anonymous_1)',
+ decl: {
+ start: {
+ line: 3,
+ column: 13
+ },
+ end: {
+ line: 3,
+ column: 14
+ }
+ },
+ loc: {
+ start: {
+ line: 3,
+ column: 23
+ },
+ end: {
+ line: 3,
+ column: 28
+ }
+ },
+ line: 3
+ }
+ },
+ branchMap: {},
+ s: {
+ '0': 0,
+ '1': 0,
+ '2': 0,
+ '3': 0
+ },
+ f: {
+ '0': 0,
+ '1': 0
+ },
+ b: {},
+ _coverageSchema: '1a1c01bbd47fc00a2c39e90264f33305004495a9',
+ hash: 'd384017ecd51a8d90283ba0dec593332209519de'
+ }
+ var coverage = global[gcv] || (global[gcv] = {})
+
+ if (!coverage[path] || coverage[path].hash !== hash) {
+ coverage[path] = coverageData
+ }
+
+ var actualCoverage = coverage[path]
+
+ cov_6k5v991cn = function() {
+ return actualCoverage
+ }
+
+ return actualCoverage
+}
+
+cov_6k5v991cn()
+cov_6k5v991cn().s[0]++
+
+window.add = (a, b) => {
+ cov_6k5v991cn().f[0]++
+ cov_6k5v991cn().s[1]++
+ return a + b
+}
+
+cov_6k5v991cn().s[2]++
+
+window.sub = (a, b) => {
+ cov_6k5v991cn().f[1]++
+ cov_6k5v991cn().s[3]++
+ return a - b
+}
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4uanMiXSwibmFtZXMiOlsid2luZG93IiwiYWRkIiwiYSIsImIiLCJzdWIiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBQSxNQUFNLENBQUNDLEdBQVAsR0FBYSxDQUFDQyxDQUFELEVBQUlDLENBQUosS0FBVTtBQUFBO0FBQUE7QUFBQSxTQUFBRCxDQUFDLEdBQUdDLENBQUo7QUFBSyxDQUE1Qjs7OztBQUVBSCxNQUFNLENBQUNJLEdBQVAsR0FBYSxDQUFDRixDQUFELEVBQUlDLENBQUosS0FBVTtBQUFBO0FBQUE7QUFBQSxTQUFBRCxDQUFDLEdBQUdDLENBQUo7QUFBSyxDQUE1QiIsInNvdXJjZXNDb250ZW50IjpbIndpbmRvdy5hZGQgPSAoYSwgYikgPT4gYSArIGJcblxud2luZG93LnN1YiA9IChhLCBiKSA9PiBhIC0gYlxuIl19
diff --git a/examples/one-spec/main.js b/examples/one-spec/main.js
new file mode 100644
index 00000000..5dd69be2
--- /dev/null
+++ b/examples/one-spec/main.js
@@ -0,0 +1,3 @@
+window.add = (a, b) => a + b
+
+window.sub = (a, b) => a - b
diff --git a/examples/one-spec/package.json b/examples/one-spec/package.json
new file mode 100644
index 00000000..a3db5393
--- /dev/null
+++ b/examples/one-spec/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "example-one-spec",
+ "description": "Only running a single spec",
+ "scripts": {
+ "cy:open": "../../node_modules/.bin/cypress open"
+ }
+}
diff --git a/support.js b/support.js
index fc2c052d..9351290f 100644
--- a/support.js
+++ b/support.js
@@ -1,4 +1,7 @@
///
+// @ts-check
+
+const { filterSpecsFromCoverage } = require('./utils')
/**
* Sends collected code coverage object to the backend code
@@ -53,28 +56,6 @@ const filterSupportFilesFromCoverage = totalCoverage => {
return coverage
}
-/**
- * remove coverage for the spec files themselves,
- * only keep "external" application source file coverage
- */
-const filterSpecsFromCoverage = totalCoverage => {
- const integrationFolder = Cypress.config('integrationFolder')
- const testFilePattern = Cypress.config('testFiles')
- const isUsingDefaultTestPattern = testFilePattern === '**/*.*'
-
- const isInIntegrationFolder = filename =>
- filename.startsWith(integrationFolder)
- const isTestFile = filename => Cypress.minimatch(filename, testFilePattern)
-
- const isA = (fileCoverge, filename) => isInIntegrationFolder(filename)
- const isB = (fileCoverge, filename) => isTestFile(filename)
-
- const isTestFileFilter = isUsingDefaultTestPattern ? isA : isB
-
- const coverage = Cypress._.omitBy(totalCoverage, isTestFileFilter)
- return coverage
-}
-
const registerHooks = () => {
let windowCoverageObjects
diff --git a/utils.js b/utils.js
index ba778d76..75a43f84 100644
--- a/utils.js
+++ b/utils.js
@@ -1,19 +1,64 @@
-module.exports = {
- /**
- * Replace source-map's path by the corresponding absolute file path
- * (coverage report wouldn't work with source-map path being relative
- * or containing Webpack loaders and query parameters)
- */
- fixSourcePathes(coverage) {
- Object.values(coverage).forEach(file => {
- const { path: absolutePath, inputSourceMap } = file
- const fileName = /([^\/\\]+)$/.exec(absolutePath)[1]
- if (!inputSourceMap || !fileName) return
-
- if (inputSourceMap.sourceRoot) inputSourceMap.sourceRoot = ''
- inputSourceMap.sources = inputSourceMap.sources.map(source =>
- source.includes(fileName) ? absolutePath : source
- )
- })
+///
+
+/**
+ * remove coverage for the spec files themselves,
+ * only keep "external" application source file coverage
+ */
+const filterSpecsFromCoverage = (totalCoverage, config = Cypress.config) => {
+ const integrationFolder = config('integrationFolder')
+ const testFilePattern = config('testFiles')
+
+ // test files chould be:
+ // wild card string "**/*.*" (default)
+ // wild card string "**/*spec.js"
+ // list of wild card strings or names ["**/*spec.js", "spec-one.js"]
+ const testFilePatterns = Array.isArray(testFilePattern)
+ ? testFilePattern
+ : [testFilePattern]
+
+ const isUsingDefaultTestPattern = testFilePattern === '**/*.*'
+
+ const isTestFile = filename => {
+ const matchedPattern = testFilePatterns.some(specPattern =>
+ Cypress.minimatch(filename, specPattern)
+ )
+ const matchedEndOfPath = testFilePatterns.some(specPattern =>
+ filename.endsWith(specPattern)
+ )
+ return matchedPattern || matchedEndOfPath
}
+
+ const isInIntegrationFolder = filename =>
+ filename.startsWith(integrationFolder)
+
+ const isA = (fileCoverge, filename) => isInIntegrationFolder(filename)
+ const isB = (fileCoverge, filename) => isTestFile(filename)
+
+ const isTestFileFilter = isUsingDefaultTestPattern ? isA : isB
+
+ const coverage = Cypress._.omitBy(totalCoverage, isTestFileFilter)
+ return coverage
+}
+
+/**
+ * Replace source-map's path by the corresponding absolute file path
+ * (coverage report wouldn't work with source-map path being relative
+ * or containing Webpack loaders and query parameters)
+ */
+function fixSourcePathes(coverage) {
+ Object.values(coverage).forEach(file => {
+ const { path: absolutePath, inputSourceMap } = file
+ const fileName = /([^\/\\]+)$/.exec(absolutePath)[1]
+ if (!inputSourceMap || !fileName) return
+
+ if (inputSourceMap.sourceRoot) inputSourceMap.sourceRoot = ''
+ inputSourceMap.sources = inputSourceMap.sources.map(source =>
+ source.includes(fileName) ? absolutePath : source
+ )
+ })
+}
+
+module.exports = {
+ fixSourcePathes,
+ filterSpecsFromCoverage
}