Skip to content

New puppeteer plugin to dump code coverage #1504

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 24, 2019
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions lib/plugin/puppeteerCoverage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
const Container = require("../container");
const recorder = require("../recorder");
const event = require("../event");
const fs = require("fs");
const path = require("path");
const { clearString } = require("../utils");
const debug = require("debug")("codeceptjs:plugin:puppeteerCoverage");

const defaultConfig = {
coverageDir: "output/coverage",
uniqueFileName: true
};

const supportedHelpers = ["Puppeteer"];

/**
* Builds the filename based on the test title
*/
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;
}

/**
* Dumps puppeteers code coverage after every test.
*
* ##### Configuration
*
* Configuration can either be taken from a corresponding helper (deprecated) or a from plugin config (recommended).
*
* ```js
* "plugins": {
* "puppeteerCoverage": {
* "enabled": true
* }
* }
* ```
*
* Possible config options:
*
* * `outputDir`: directory to dump coverage files
* * `uniqueFileName`: generate a unique filename by adding uuid
*
* Notes:
* First of all, YMMV!
* To work, you need the client javascript code to be NOT uglified. They need to be built in "development" mode.
* And the end of your tests, you'll get a directory full of coverage per test run. Now what?
* You'll need to convert the coverage code to something istanbul can read. Good news is someone wrote the code
* for you (see puppeteer-to-istanbul link below). Then using istanbul you need to combine the converted
* coverage and create a report. Good luck!
*
* Links:
* * https://github.com/GoogleChrome/puppeteer/blob/v1.12.2/docs/api.md#class-coverage
* * https://github.com/istanbuljs/puppeteer-to-istanbul
* * https://github.com/gotwarlost/istanbul
*/
module.exports = function(config) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space before function parentheses space-before-function-paren

const helpers = Container.helpers();
let coverageRunning = false;
let helper;

for (const helperName of supportedHelpers) {
if (Object.keys(helpers).indexOf(helperName) > -1) {
helper = helpers[helperName];
}
}

if (!helper) {
console.error("Coverage is only supported in Puppeteer");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strings must use singlequote quotes

return; // no helpers for screenshot
}

const options = Object.assign(defaultConfig, helper.options, config);

event.dispatcher.on(event.all.before, async suite => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected parentheses around arrow function argument having a body with curly braces arrow-parens

debug("*** Collecting coverage for tests ****");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strings must use singlequote quotes

});

/**
* Hack! 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 step => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected parentheses around arrow function argument having a body with curly braces arrow-parens

recorder.add("starting coverage", async () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strings must use singlequote quotes

try {
if (!coverageRunning) {
debug("--> starting coverage <--");
coverageRunning = true;
await helper.page.coverage.startJSCoverage();
}
} catch (err) {
console.error(err);
}
}, true);
});

/**
* Save puppeteer coverage data after every test run
*/
event.dispatcher.on(event.test.after, async test => {
recorder.add("saving coverage", async () => {
try {
if (coverageRunning) {
debug("--> stopping coverage <--");
coverageRunning = false;
const coverage = await helper.page.coverage.stopJSCoverage();

const coverageDir = path.resolve(process.cwd(), options.coverageDir);
fs.mkdirSync(coverageDir, {recursive: true});

const coveragePath = path.resolve(coverageDir, buildFileName(test, options.uniqueFileName));
debug(`writing ${coveragePath}`);
fs.writeFileSync(coveragePath, JSON.stringify(coverage));
}
} catch (err) {
console.error(err);
}
}, true);
});
};