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 all commits
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
155 changes: 155 additions & 0 deletions lib/plugin/puppeteerCoverage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
const Container = require('../container');
const recorder = require('../recorder');
const event = require('../event');
const output = require('../output');
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) {
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');
return; // no helpers for screenshot
}

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

event.dispatcher.on(event.all.before, async (suite) => {
output.debug('*** Collecting coverage for tests ****');
});

/**
* 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) => {
recorder.add(
'starting coverage',
async () => {
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),
);
output.print(`writing ${coveragePath}`);
fs.writeFileSync(coveragePath, JSON.stringify(coverage));
}
} catch (err) {
console.error(err);
}
},
true,
);
});
};