diff --git a/docs/playwright.md b/docs/playwright.md index ed8307142..01c7e2e81 100644 --- a/docs/playwright.md +++ b/docs/playwright.md @@ -540,25 +540,13 @@ Code coverage can be captured, by enabling the `coverage` plugin in `codecept.co Once all the tests are completed, `codecept` will create and store coverage in `output/coverage` folder, as shown below. -![](https://user-images.githubusercontent.com/16587779/131362352-30ee9c51-705f-4098-b665-53035ea9275f.png) - -Then you need to [convert code coverage from Playwright's format into Istanbul format](https://github.com/codeceptjs/CodeceptJS/wiki/Converting-Playwright-to-Istanbul-Coverage). - -Once the istanbul compatible coverage is generated, use [`nyc`](https://www.npmjs.com/package/nyc) to generate your coverage report in your desired format. - -``` -npx nyc report --reporter html -t coverage -``` - -The above command will generate will generate coverage in an interactive html format. It should generate `html` files in the directory where your code coverage is present, something like shown below. - -![](https://user-images.githubusercontent.com/16587779/131858419-cbc7df7d-0851-47b9-b086-b5e3b9165674.png) +![](https://private-user-images.githubusercontent.com/7845001/313117208-f7165429-426a-44c5-af27-df5536ef492d.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTA0OTEyNTIsIm5iZiI6MTcxMDQ5MDk1MiwicGF0aCI6Ii83ODQ1MDAxLzMxMzExNzIwOC1mNzE2NTQyOS00MjZhLTQ0YzUtYWYyNy1kZjU1MzZlZjQ5MmQucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDMxNSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDAzMTVUMDgyMjMyWiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9NjRmNTFmOWUyYzJjYWE3ZWE0MDA0MGI3ODY5NzY5MDRlYjQyNTMxODRjZGU1ZWM5ZDdjNDJiNGRmZGUxM2FlOCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ._gfEYZn2AK0NVA0bt-sFmzMoMKlyBVpxd7m4590Ux1M) Open `index.html` in your browser to view the full interactive coverage report. -![](https://user-images.githubusercontent.com/16587779/131858993-87d1aafc-8ef1-4a82-867d-e64a13e36106.png) +![](https://private-user-images.githubusercontent.com/7845001/313117882-cb77beea-c478-49ea-8677-b025d5614545.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTA0OTEzOTAsIm5iZiI6MTcxMDQ5MTA5MCwicGF0aCI6Ii83ODQ1MDAxLzMxMzExNzg4Mi1jYjc3YmVlYS1jNDc4LTQ5ZWEtODY3Ny1iMDI1ZDU2MTQ1NDUucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDMxNSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDAzMTVUMDgyNDUwWiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9YmY0YTkxOTM0ZjUyNjZmMzk0NmM4MjU5MGFjZTZlZDc0Njc3OTZkNTNjMjc5YzQxODI3OWUyODJkNTU2NGViZSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.LATp5aqrvm-8Nn4tRGSuMrOjJQ4L-NIJEwsB3BbB5po) -![](https://user-images.githubusercontent.com/16587779/131859006-c6f17d18-c603-44a5-9d59-0670177276cf.png) +![](https://private-user-images.githubusercontent.com/7845001/313117886-92a11844-0a2b-4bcb-8c73-aef5cf518dbf.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTA0OTEzOTAsIm5iZiI6MTcxMDQ5MTA5MCwicGF0aCI6Ii83ODQ1MDAxLzMxMzExNzg4Ni05MmExMTg0NC0wYTJiLTRiY2ItOGM3My1hZWY1Y2Y1MThkYmYucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDMxNSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDAzMTVUMDgyNDUwWiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MTQ4ODUyMTVlM2MwODRhMzE0Yjg2YWQxYTRiNmYzOWJkYWU2ZWIyNDA2YWMyYjIxYTMyNTZkYjg5NGU3Zjk4OCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.gxyQ9RgAAtFcCUIqqjzponc53ZRn9sG0hN4cBeQovMg) ## Extending Helper diff --git a/docs/plugins.md b/docs/plugins.md index b583ef00a..0bc85545f 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -392,15 +392,21 @@ Dumps code coverage from Playwright/Puppeteer after every test. ```js plugins: { coverage: { - enabled: true + enabled: true, + debug: true, + name: 'CodeceptJS Coverage Report', + outputDir: 'output/coverage' } } ``` -Possible config options: +Possible config options, More could be found at [monocart-coverage-reports][1] -- `coverageDir`: directory to dump coverage files -- `uniqueFileName`: generate a unique filename by adding uuid +- `debug`: debug info. By default, false. +- `name`: coverage report name. +- `outputDir`: path to coverage report. +- `sourceFilter`: filter the source files. +- `sourcePath`: option to resolve a custom path. ### Parameters @@ -408,7 +414,7 @@ Possible config options: ## customLocator -Creates a [custom locator][1] by using special attributes in HTML. +Creates a [custom locator][2] by using special attributes in HTML. If you have a convention to use `data-test-id` or `data-qa` attributes to mark active elements for e2e tests, you can enable this plugin to simplify matching elements with these attributes: @@ -582,9 +588,9 @@ This method works with WebDriver, Playwright, Puppeteer, Appium helpers. Function parameter `el` represents a matched element. Depending on a helper API of `el` can be different. Refer to API of corresponding browser testing engine for a complete API list: -- [Playwright ElementHandle][2] -- [Puppeteer][3] -- [webdriverio element][4] +- [Playwright ElementHandle][3] +- [Puppeteer][4] +- [webdriverio element][5] #### Configuration @@ -598,11 +604,11 @@ const eachElement = codeceptjs.container.plugins('eachElement'); ### Parameters -- `purpose` **[string][5]** +- `purpose` **[string][6]** - `locator` **CodeceptJS.LocatorOrString** -- `fn` **[Function][6]** +- `fn` **[Function][7]** -Returns **([Promise][7]<any> | [undefined][8])** +Returns **([Promise][8]<any> | [undefined][9])** ## fakerTransform @@ -680,7 +686,7 @@ Steps to heal: ## pauseOnFail -Automatically launches [interactive pause][9] when a test fails. +Automatically launches [interactive pause][10] when a test fails. Useful for debugging flaky tests on local environment. Add this plugin to config file: @@ -863,14 +869,14 @@ Possible config options: ## selenoid -[Selenoid][10] plugin automatically starts browsers and video recording. +[Selenoid][11] plugin automatically starts browsers and video recording. Works with WebDriver helper. ### Prerequisite This plugin **requires Docker** to be installed. -> If you have issues starting Selenoid with this plugin consider using the official [Configuration Manager][11] tool from Selenoid +> If you have issues starting Selenoid with this plugin consider using the official [Configuration Manager][12] tool from Selenoid ### Usage @@ -899,7 +905,7 @@ plugins: { } ``` -When `autoCreate` is enabled it will pull the [latest Selenoid from DockerHub][12] and start Selenoid automatically. +When `autoCreate` is enabled it will pull the [latest Selenoid from DockerHub][13] and start Selenoid automatically. It will also create `browsers.json` file required by Selenoid. In automatic mode the latest version of browser will be used for tests. It is recommended to specify exact version of each browser inside `browsers.json` file. @@ -911,10 +917,10 @@ In automatic mode the latest version of browser will be used for tests. It is re While this plugin can create containers for you for better control it is recommended to create and launch containers manually. This is especially useful for Continous Integration server as you can configure scaling for Selenoid containers. -> Use [Selenoid Configuration Manager][11] to create and start containers semi-automatically. +> Use [Selenoid Configuration Manager][12] to create and start containers semi-automatically. 1. Create `browsers.json` file in the same directory `codecept.conf.js` is located - [Refer to Selenoid documentation][13] to know more about browsers.json. + [Refer to Selenoid documentation][14] to know more about browsers.json. _Sample browsers.json_ @@ -939,7 +945,7 @@ _Sample browsers.json_ 2. Create Selenoid container -Run the following command to create a container. To know more [refer here][14] +Run the following command to create a container. To know more [refer here][15] ```bash docker create \ @@ -972,7 +978,7 @@ When `allure` plugin is enabled a video is attached to report automatically. | enableVideo | Enable video recording and use `video` folder of output (default: false) | | enableLog | Enable log recording and use `logs` folder of output (default: false) | | deletePassed | Delete video and logs of passed tests (default : true) | -| additionalParams | example: `additionalParams: '--env TEST=test'` [Refer here][15] to know more | +| additionalParams | example: `additionalParams: '--env TEST=test'` [Refer here][16] to know more | ### Parameters @@ -980,7 +986,7 @@ When `allure` plugin is enabled a video is attached to report automatically. ## stepByStepReport -![step-by-step-report][16] +![step-by-step-report][17] Generates step by step report for a test. After each step in a test a screenshot is created. After test executed screenshots are combined into slideshow. @@ -1161,7 +1167,7 @@ This plugin allows to run webdriverio services like: - browserstack - appium -A complete list of all available services can be found on [webdriverio website][17]. +A complete list of all available services can be found on [webdriverio website][18]. #### Setup @@ -1173,7 +1179,7 @@ See examples below: #### Selenium Standalone Service -Install `@wdio/selenium-standalone-service` package, as [described here][18]. +Install `@wdio/selenium-standalone-service` package, as [described here][19]. It is important to make sure it is compatible with current webdriverio version. Enable `wdio` plugin in plugins list and add `selenium-standalone` service: @@ -1190,7 +1196,7 @@ plugins: { #### Sauce Service -Install `@wdio/sauce-service` package, as [described here][19]. +Install `@wdio/sauce-service` package, as [described here][20]. It is important to make sure it is compatible with current webdriverio version. Enable `wdio` plugin in plugins list and add `sauce` service: @@ -1220,40 +1226,42 @@ In the same manner additional services from webdriverio can be installed, enable - `config` -[1]: https://codecept.io/locators#custom-locators +[1]: https://github.com/cenfun/monocart-coverage-reports?tab=readme-ov-file#default-options + +[2]: https://codecept.io/locators#custom-locators -[2]: https://playwright.dev/docs/api/class-elementhandle +[3]: https://playwright.dev/docs/api/class-elementhandle -[3]: https://pptr.dev/#?product=Puppeteer&show=api-class-elementhandle +[4]: https://pptr.dev/#?product=Puppeteer&show=api-class-elementhandle -[4]: https://webdriver.io/docs/api +[5]: https://webdriver.io/docs/api -[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String +[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String -[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function +[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function -[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise +[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise -[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined +[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined -[9]: /basics/#pause +[10]: /basics/#pause -[10]: https://aerokube.com/selenoid/ +[11]: https://aerokube.com/selenoid/ -[11]: https://aerokube.com/cm/latest/ +[12]: https://aerokube.com/cm/latest/ -[12]: https://hub.docker.com/u/selenoid +[13]: https://hub.docker.com/u/selenoid -[13]: https://aerokube.com/selenoid/latest/#_prepare_configuration +[14]: https://aerokube.com/selenoid/latest/#_prepare_configuration -[14]: https://aerokube.com/selenoid/latest/#_option_2_start_selenoid_container +[15]: https://aerokube.com/selenoid/latest/#_option_2_start_selenoid_container -[15]: https://docs.docker.com/engine/reference/commandline/create/ +[16]: https://docs.docker.com/engine/reference/commandline/create/ -[16]: https://codecept.io/img/codeceptjs-slideshow.gif +[17]: https://codecept.io/img/codeceptjs-slideshow.gif -[17]: https://webdriver.io +[18]: https://webdriver.io -[18]: https://webdriver.io/docs/selenium-standalone-service.html +[19]: https://webdriver.io/docs/selenium-standalone-service.html -[19]: https://webdriver.io/docs/sauce-service.html +[20]: https://webdriver.io/docs/sauce-service.html diff --git a/docs/puppeteer.md b/docs/puppeteer.md index f0fec00e4..32b7cfe67 100644 --- a/docs/puppeteer.md +++ b/docs/puppeteer.md @@ -288,6 +288,30 @@ I.usePuppeteerTo('emulate offline mode', async (Puppeteer) => { }); ``` +## Capturing Code Coverage + +Code coverage can be captured, by enabling the `coverage` plugin in `codecept.config.js`. + +```js +{ + plugins: { + coverage: { + enabled: true + } + } +} +``` + +Once all the tests are completed, `codecept` will create and store coverage in `output/coverage` folder, as shown below. + +![](https://private-user-images.githubusercontent.com/7845001/313117208-f7165429-426a-44c5-af27-df5536ef492d.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTA0OTEyNTIsIm5iZiI6MTcxMDQ5MDk1MiwicGF0aCI6Ii83ODQ1MDAxLzMxMzExNzIwOC1mNzE2NTQyOS00MjZhLTQ0YzUtYWYyNy1kZjU1MzZlZjQ5MmQucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDMxNSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDAzMTVUMDgyMjMyWiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9NjRmNTFmOWUyYzJjYWE3ZWE0MDA0MGI3ODY5NzY5MDRlYjQyNTMxODRjZGU1ZWM5ZDdjNDJiNGRmZGUxM2FlOCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ._gfEYZn2AK0NVA0bt-sFmzMoMKlyBVpxd7m4590Ux1M) + +Open `index.html` in your browser to view the full interactive coverage report. + +![](https://private-user-images.githubusercontent.com/7845001/313117882-cb77beea-c478-49ea-8677-b025d5614545.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTA0OTEzOTAsIm5iZiI6MTcxMDQ5MTA5MCwicGF0aCI6Ii83ODQ1MDAxLzMxMzExNzg4Mi1jYjc3YmVlYS1jNDc4LTQ5ZWEtODY3Ny1iMDI1ZDU2MTQ1NDUucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDMxNSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDAzMTVUMDgyNDUwWiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9YmY0YTkxOTM0ZjUyNjZmMzk0NmM4MjU5MGFjZTZlZDc0Njc3OTZkNTNjMjc5YzQxODI3OWUyODJkNTU2NGViZSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.LATp5aqrvm-8Nn4tRGSuMrOjJQ4L-NIJEwsB3BbB5po) + +![](https://private-user-images.githubusercontent.com/7845001/313117886-92a11844-0a2b-4bcb-8c73-aef5cf518dbf.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTA0OTEzOTAsIm5iZiI6MTcxMDQ5MTA5MCwicGF0aCI6Ii83ODQ1MDAxLzMxMzExNzg4Ni05MmExMTg0NC0wYTJiLTRiY2ItOGM3My1hZWY1Y2Y1MThkYmYucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDMxNSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDAzMTVUMDgyNDUwWiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MTQ4ODUyMTVlM2MwODRhMzE0Yjg2YWQxYTRiNmYzOWJkYWU2ZWIyNDA2YWMyYjIxYTMyNTZkYjg5NGU3Zjk4OCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.gxyQ9RgAAtFcCUIqqjzponc53ZRn9sG0hN4cBeQovMg) + ## Extending Helper diff --git a/lib/plugin/coverage.js b/lib/plugin/coverage.js index e6126a519..65a6bc22e 100644 --- a/lib/plugin/coverage.js +++ b/lib/plugin/coverage.js @@ -1,44 +1,67 @@ const debugModule = require('debug'); -const fs = require('fs'); -const path = require('path'); - +const { CoverageReport } = require('monocart-coverage-reports'); const Container = require('../container'); const recorder = require('../recorder'); const event = require('../event'); const output = require('../output'); -const { clearString } = require('../utils'); +const { deepMerge } = require('../utils'); const defaultConfig = { - coverageDir: 'output/coverage', - uniqueFileName: true, + name: 'CodeceptJS Coverage Report', + outputDir: 'output/coverage', }; const supportedHelpers = ['Puppeteer', 'Playwright']; -function buildFileName(test, uniqueFileName) { - let fileName = clearString(test.title); - - // This prevent data driven to be included in the failed screenshot file name - if (fileName.indexOf('{') !== -1) { - fileName = fileName.substr(0, fileName.indexOf('{') - 3).trim(); - } - - if (test.ctx && test.ctx.test && test.ctx.test.type === 'hook') { - fileName = clearString(`${test.title}_${test.ctx.test.title}`); - } - - if (uniqueFileName) { - const uuid = test.uuid - || test.ctx.test.uuid - || Math.floor(new Date().getTime() / 1000); - - fileName = `${fileName.substring(0, 10)}_${uuid}.coverage.json`; - } else { - fileName = `${fileName}.coverage.json`; - } - - return fileName; -} +const v8CoverageHelpers = { + Playwright: { + startCoverage: async (page) => { + await Promise.all([ + page.coverage.startJSCoverage({ + resetOnNavigation: false, + }), + page.coverage.startCSSCoverage({ + resetOnNavigation: false, + }), + ]); + }, + takeCoverage: async (page, coverageReport) => { + const [jsCoverage, cssCoverage] = await Promise.all([ + page.coverage.stopJSCoverage(), + page.coverage.stopCSSCoverage(), + ]); + const coverageList = [...jsCoverage, ...cssCoverage]; + await coverageReport.add(coverageList); + }, + }, + Puppeteer: { + startCoverage: async (page) => { + await Promise.all([ + page.coverage.startJSCoverage({ + resetOnNavigation: false, + includeRawScriptCoverage: true, + }), + page.coverage.startCSSCoverage({ + resetOnNavigation: false, + }), + ]); + }, + takeCoverage: async (page, coverageReport) => { + const [jsCoverage, cssCoverage] = await Promise.all([ + page.coverage.stopJSCoverage(), + page.coverage.stopCSSCoverage(), + ]); + // to raw V8 script coverage + const coverageList = [...jsCoverage.map((it) => { + return { + source: it.text, + ...it.rawScriptCoverage, + }; + }), ...cssCoverage]; + await coverageReport.add(coverageList); + }, + }, +}; /** * Dumps code coverage from Playwright/Puppeteer after every test. @@ -49,94 +72,84 @@ function buildFileName(test, uniqueFileName) { * ```js * plugins: { * coverage: { - * enabled: true + * enabled: true, + * debug: true, + * name: 'CodeceptJS Coverage Report', + * outputDir: 'output/coverage' * } * } * ``` * - * Possible config options: + * Possible config options, More could be found at [monocart-coverage-reports](https://github.com/cenfun/monocart-coverage-reports?tab=readme-ov-file#default-options) + * + * * `debug`: debug info. By default, false. + * * `name`: coverage report name. + * * `outputDir`: path to coverage report. + * * `sourceFilter`: filter the source files. + * * `sourcePath`: option to resolve a custom path. * - * * `coverageDir`: directory to dump coverage files - * * `uniqueFileName`: generate a unique filename by adding uuid */ module.exports = function (config) { + config = deepMerge(defaultConfig, config); + + if (config.debug) config.logging = 'debug'; + const helpers = Container.helpers(); let coverageRunning = false; - let helper; - let debug; - for (const helperName of supportedHelpers) { - if (Object.keys(helpers).indexOf(helperName) > -1) { - helper = helpers[helperName]; - debug = debugModule(`codeceptjs:plugin:${helperName.toLowerCase()}Coverage`); - } + const v8Names = Object.keys(v8CoverageHelpers); + const helperName = Object.keys(helpers).find((it) => v8Names.includes(it)); + if (!helperName) { + console.error(`Coverage is only supported in ${supportedHelpers.join(' or ')}`); + // no helpers for screenshot + return; } - if (!helper) { - console.error('Coverage is only supported in Puppeteer, Playwright'); - return; // no helpers for screenshot - } + config.name = `${config.name} - in ${helperName}`; + const debug = debugModule(`codeceptjs:plugin:${helperName.toLowerCase()}Coverage`); + + const helper = helpers[helperName]; + const v8Helper = v8CoverageHelpers[helperName]; - const options = Object.assign(defaultConfig, helper.options, config); + const coverageOptions = { + ...config, + }; + const coverageReport = new CoverageReport(coverageOptions); + coverageReport.cleanCache(); - event.dispatcher.on(event.all.before, async () => { - output.debug('*** Collecting coverage for tests ****'); + event.dispatcher.on(event.all.after, async () => { + output.print(`writing ${coverageOptions.outputDir}`); + await coverageReport.generate(); }); - // Hack! we're going to try to "start" coverage before each step because this is + // we're going to try to "start" coverage before each step because this is // when the browser is already up and is ready to start coverage. - event.dispatcher.on(event.step.before, async () => { - recorder.add( - 'starting coverage', - async () => { - try { - if (!coverageRunning && helper.page && helper.page.coverage) { - debug('--> starting coverage <--'); - coverageRunning = true; - await helper.page.coverage.startJSCoverage(); - } - } catch (err) { - console.error(err); - } - }, - true, - ); + event.dispatcher.on(event.step.before, () => { + recorder.add('start coverage', async () => { + if (coverageRunning) { + return; + } + if (!helper.page || !helper.page.coverage) { + return; + } + coverageRunning = true; + debug('--> starting coverage <--'); + await v8Helper.startCoverage(helper.page); + }, true); }); // Save coverage data after every test run - event.dispatcher.on(event.test.after, async (test) => { - recorder.add( - 'saving coverage', - async () => { - try { - if (coverageRunning && helper.page && helper.page.coverage) { - debug('--> stopping coverage <--'); - coverageRunning = false; - const coverage = await helper.page.coverage.stopJSCoverage(); - - const coverageDir = path.resolve( - process.cwd(), - options.coverageDir, - ); - - // Checking if coverageDir already exists, if not, create new one - - if (!fs.existsSync(coverageDir)) { - fs.mkdirSync(coverageDir, { recursive: true }); - } - - const coveragePath = path.resolve( - coverageDir, - buildFileName(test, options.uniqueFileName), - ); - output.print(`writing ${coveragePath}`); - fs.writeFileSync(coveragePath, JSON.stringify(coverage)); - } - } catch (err) { - console.error(err); - } - }, - true, - ); + event.dispatcher.on(event.test.after, (test) => { + recorder.add('take coverage', async () => { + if (!coverageRunning) { + return; + } + if (!helper.page || !helper.page.coverage) { + return; + } + coverageRunning = false; + debug('--> stopping coverage <--'); + await v8Helper.takeCoverage(helper.page, coverageReport); + }, true); }); }; diff --git a/package.json b/package.json index f3b11ea74..46efefcc9 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "lodash.merge": "4.6.2", "mkdirp": "1.0.4", "mocha": "10.3.0", + "monocart-coverage-reports": "2.7.1", "ms": "2.1.3", "openai": "3.2.1", "ora-classic": "5.4.2", diff --git a/test/acceptance/codecept.Playwright.coverage.js b/test/acceptance/codecept.Playwright.coverage.js new file mode 100644 index 000000000..f1c57c214 --- /dev/null +++ b/test/acceptance/codecept.Playwright.coverage.js @@ -0,0 +1,52 @@ +const TestHelper = require('../support/TestHelper'); + +module.exports.config = { + tests: './*_test.js', + timeout: 10000, + output: './output', + grep: '@Playwright', + helpers: { + Playwright: { + url: TestHelper.siteUrl(), + show: false, + restart: process.env.BROWSER_RESTART || false, + browser: process.env.BROWSER || 'chromium', + ignoreHTTPSErrors: true, + webkit: { + ignoreHTTPSErrors: true, + }, + }, + JSONResponse: { + requestHelper: 'Playwright', + }, + ScreenshotSessionHelper: { + require: '../support/ScreenshotSessionHelper.js', + outputPath: 'test/acceptance/output', + }, + Expect: {}, + }, + include: {}, + bootstrap: false, + mocha: {}, + plugins: { + screenshotOnFail: { + enabled: true, + }, + coverage: { + enabled: true, + debug: true, + name: 'CodeceptJS Coverage Report', + sourceFilter: '**/src/**', + sourcePath: { + 'todomvc-react/': '', + 'todomvc.com/examples/react/': '', + }, + outputDir: 'output/coverage', + }, + }, + name: 'acceptance', + gherkin: { + features: './gherkin/*.feature', + steps: ['./gherkin/steps.js'], + }, +}; diff --git a/test/acceptance/coverage_test.js b/test/acceptance/coverage_test.js new file mode 100644 index 000000000..bae31c345 --- /dev/null +++ b/test/acceptance/coverage_test.js @@ -0,0 +1,64 @@ +const { I } = inject(); + +Feature('Plugins'); + +Feature('TODO mvc demo'); + +Before(() => { + I.amOnPage('https://todomvc.com/examples/react/dist/'); + + I.say('Given I already have some todos'); + + I.executeScript(({ todoItems }) => { + localStorage.setItem('todos-angularjs', JSON.stringify(todoItems)); + }, [{ + title: 'Create a cypress like runner for CodeceptJS', completed: false, + }, { + title: 'Make it even better than cypress', completed: false, + }]); + + I.refreshPage(); + + I.executeScript(() => console.log('Some info')); + I.executeScript(() => console.error('Some error')); +}); + +Scenario('Create some todo items @coverage', () => { + I.say('When I focus the todo field'); + I.click('.new-todo'); + + I.say('Then I can add additional todos'); + I.fillField({ + css: '.new-todo', + }, 'Optimize Puppeteer support'); + I.pressKey('Enter'); + + I.fillField({ + css: '.new-todo', + }, 'Add a web REPL'); + I.pressKey('Enter'); + + I.fillField(locate('.new-todo').as('TODO Input'), 'Support Appium'); + I.pressKey('Enter'); + + I.fillField({ + css: '.new-todo', + }, 'Become REALLY productive writing E2E Tests with codepress and CodeceptJS'); + I.pressKey('Enter'); + + I.say('And I see them in the list'); + I.seeNumberOfVisibleElements('.todo-list li', 4); + I.see('Optimize Puppeteer support', { + css: 'li:nth-child(1) label', + }); + I.dontSee('Nightmare', '.main'); + + I.say('I complete a todo'); + I.click({ + css: 'li:nth-child(1) .toggle', + }); + I.seeElement('li:nth-child(1).completed'); + + I.say('I mark all as completed'); + I.saveScreenshot('create-multiple-todo-items.png'); +}); diff --git a/test/plugin/plugin_test.js b/test/plugin/plugin_test.js index 848935d7a..4255aeda8 100644 --- a/test/plugin/plugin_test.js +++ b/test/plugin/plugin_test.js @@ -31,4 +31,18 @@ describe('CodeceptJS plugin', function () { done(); }); }); + + it('should generate the coverage report', (done) => { + exec(`${config_run_config('codecept.Playwright.coverage.js', '@coverage')} --debug`, (err, stdout) => { + const lines = stdout.split('\n'); + expect(lines).toEqual( + expect.arrayContaining([ + expect.stringContaining('writing output/coverage'), + expect.stringContaining('generated coverage reports: output/coverage/index.html'), + ]), + ); + expect(err).toBeFalsy(); + done(); + }); + }); });