Skip to content

Custom reporter #86

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 32 commits into from
Nov 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
daf122e
Adding genernate-report command
suryart Nov 2, 2020
fffa36e
Adding basic HTML skeleton for reporter
suryart Nov 2, 2020
150216b
Adding CSS assets file
suryart Nov 3, 2020
57fa47b
Fixing the command file for generate report
suryart Nov 4, 2020
a1177f4
Completing the writing of HTML file to the disk from creating it with…
suryart Nov 4, 2020
faa5d83
Fetching the report file content via API and saving it via reporterHTML
suryart Nov 4, 2020
4f56512
Adding constant for failure case of report generation
suryart Nov 4, 2020
fae5038
loadInlineCss not needed in modules
suryart Nov 4, 2020
269a30b
Fixing the logger and saving JSON file as well
suryart Nov 4, 2020
5ff4851
Making filename to be browserstack-cypress-report.(html|json)
suryart Nov 5, 2020
5add16d
generating reports inside the reports directory
suryart Nov 5, 2020
d64a382
Renaming file reporter-html.js to reporterHTML
suryart Nov 6, 2020
331a914
Moving the generation part to reporterHTML file
suryart Nov 6, 2020
85a1f12
Adding unit test files for generateReport.js and reporterHTML.js file
suryart Nov 6, 2020
32d9ecf
Merge branch 'master' into custom-reporter
suryart Nov 10, 2020
27379b5
Adding required fixture for generateReport module
suryart Nov 18, 2020
ddcdf86
usage reporting and adding unit tests for the generateReport module
suryart Nov 18, 2020
6eb3547
request module require not needed
suryart Nov 18, 2020
827dc33
Removing dependencies and correcting code and unit tests
suryart Nov 18, 2020
8afb40a
Ignore logs
suryart Nov 18, 2020
3744c3a
Changes in args to reporter and fixing specs
suryart Nov 19, 2020
ce3bc71
Fixed missing dependencies
suryart Nov 19, 2020
d09308b
Adding tests for request calls
suryart Nov 19, 2020
c293408
Adding results dir to gitignore
suryart Nov 19, 2020
35d0f65
Removing self signed certificate ignore option
suryart Nov 19, 2020
ef9d6ec
Merge branch 'master' into custom-reporter
suryart Nov 19, 2020
36b7639
Fixing the defaultHash function name
suryart Nov 20, 2020
6cee1d3
generate custom report in sync cli mode
suryart Nov 20, 2020
6d9848f
correcting the flow of generating report
suryart Nov 20, 2020
23f3f14
Fixing the report generation code
suryart Nov 20, 2020
942094f
Merge branch 'master' into custom-reporter
suryart Nov 24, 2020
f0cf99f
Fixing the extra > in build-id
suryart Nov 25, 2020
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ tests.zip
package-lock.json
.nyc_output/
.env.*
log/*.log
results/*
39 changes: 39 additions & 0 deletions bin/commands/generateReport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict';

const logger = require("../helpers/logger").winstonLogger,
Constants = require("../helpers/constants"),
utils = require("../helpers/utils"),
reporterHTML = require('../helpers/reporterHTML');


module.exports = function generateReport(args) {
let bsConfigPath = utils.getConfigPath(args.cf);
let reportGenerator = reporterHTML.reportGenerator;

return utils.validateBstackJson(bsConfigPath).then(function (bsConfig) {
// setting setDefaults to {} if not present and set via env variables or via args.
utils.setDefaults(bsConfig, args);

// accept the username from command line if provided
utils.setUsername(bsConfig, args);

// accept the access key from command line if provided
utils.setAccessKey(bsConfig, args);

utils.setUsageReportingFlag(bsConfig, args.disableUsageReporting);

// set cypress config filename
utils.setCypressConfigFilename(bsConfig, args);

let messageType = Constants.messageTypes.INFO;
let errorCode = null;
let buildId = args._[1];

reportGenerator(bsConfig, buildId, args);
utils.sendUsageReport(bsConfig, args, 'generate-report called', messageType, errorCode);
}).catch(function (err) {
logger.error(err);
utils.setUsageReportingFlag(null, args.disableUsageReporting);
utils.sendUsageReport(null, args, err.message, Constants.messageTypes.ERROR, utils.getErrorCodeFromErr(err));
});
};
10 changes: 7 additions & 3 deletions bin/commands/runs.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ const archiver = require("../helpers/archiver"),
Constants = require("../helpers/constants"),
utils = require("../helpers/utils"),
fileHelpers = require("../helpers/fileHelpers"),
syncRunner = require("../helpers/syncRunner");
syncRunner = require("../helpers/syncRunner"),
reportGenerator = require('../helpers/reporterHTML').reportGenerator;

module.exports = function run(args) {
let bsConfigPath = utils.getConfigPath(args.cf);
Expand Down Expand Up @@ -73,8 +74,11 @@ module.exports = function run(args) {
}
if (args.sync) {
syncRunner.pollBuildStatus(bsConfig, data).then((exitCode) => {
utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null);
utils.handleSyncExit(exitCode, data.dashboard_url)
// Generate custom report!
reportGenerator(bsConfig, data.build_id, args, function(){
utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null);
utils.handleSyncExit(exitCode, data.dashboard_url);
});
});
}

Expand Down
4 changes: 4 additions & 0 deletions bin/helpers/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const syncCLI = {

const userMessages = {
BUILD_FAILED: "Build creation failed.",
BUILD_GENERATE_REPORT_FAILED: "Generating report for the build <build-id> failed.",
BUILD_CREATED: "Build created",
BUILD_INFO_FAILED: "Failed to get build info.",
BUILD_STOP_FAILED: "Failed to stop build.",
Expand Down Expand Up @@ -98,6 +99,9 @@ const cliMessages = {
ACCESS_KEY: "Your BrowserStack access key",
NO_NPM_WARNING: "No NPM warning if npm_dependencies is empty",
},
GENERATE_REPORT: {
INFO: "Generates the build report"
},
};

const messageTypes = {
Expand Down
198 changes: 198 additions & 0 deletions bin/helpers/reporterHTML.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
const fs = require('fs'),
path = require('path'),
request = require('request'),
logger = require('./logger').winstonLogger,
utils = require("./utils"),
Constants = require('./constants'),
config = require("./config");

let templatesDir = path.join(__dirname, '../', 'templates');

function loadInlineCss() {
return loadFile(path.join(templatesDir, 'assets', 'browserstack-cypress-report.css'));
}

function loadFile(fileName) {
return fs.readFileSync(fileName, 'utf8');
}

function createBodyBuildHeader(report_data){
let projectNameSpan = `<span class='project-name'> ${report_data.project_name} </span>`;
let buildNameSpan = `<span class='build-name'> ${report_data.build_name} </span>`;
let buildMeta = `<div class='build-meta'> ${buildNameSpan} ${projectNameSpan} </div>`;
let buildLink = `<div class='build-link'> <a href='${report_data.build_url}' rel='noreferrer noopener' target='_blank'> View on BrowserStack </a> </div>`;
let buildHeader = `<div class='build-header'> ${buildMeta} ${buildLink} </div>`;
return buildHeader;
}

function createBodyBuildTable(report_data) {
let specs = Object.keys(report_data.rows),
specRow = '',
specSessions = '',
sessionBlocks = '',
specData,
specNameSpan,
specPathSpan,
specStats,
specStatsSpan,
specMeta,
sessionStatus,
sessionClass,
sessionStatusIcon,
sessionLink;

specs.forEach((specName) => {
specData = report_data.rows[specName];

specNameSpan = `<span class='spec-name'> ${specName} </span>`;
specPathSpan = `<span class='spec-path'> ${specData.path} </span>`;

specStats = buildSpecStats(specData.meta);
specStatsSpan = `<span class='spec-stats ${specStats.cssClass}'> ${specStats.label} </span>`;

specMeta = `<div class='spec-meta'> ${specNameSpan} ${specPathSpan} ${specStatsSpan} </div>`;
sessionBlocks = '';
specData.sessions.forEach((specSession) => {

sessionStatus = specSession.status;
sessionClass = sessionStatus === 'passed' ? 'session-passed' : 'session-failed';
sessionStatusIcon = sessionStatus === 'passed' ? "&#10004; " : "&#x2717; ";

sessionLink = `<a href="${specSession.link}" rel="noreferrer noopener" target="_blank"> ${sessionStatusIcon} ${specSession.name} </a>`;

sessionDetail = `<div class="session-detail ${sessionClass}"> ${sessionLink} </div>`;
sessionBlocks = `${sessionBlocks} ${sessionDetail}`;
});
specSessions = `<div class='spec-sessions'> ${sessionBlocks} </div>`;
specRow = `${specRow} <div class='spec-row'> ${specMeta} ${specSessions} </div>`;
});


return `<div class='build-table'> ${specRow} </div>`;
}

function buildSpecStats(specMeta) {
let failedSpecs = specMeta.failed,
passedSpecs = specMeta.passed,
totalSpecs = specMeta.total,
specStats = {};

if (failedSpecs) {
specStats.label = `${failedSpecs}/${totalSpecs} FAILED`;
specStats.cssClass = 'spec-stats-failed';
} else {
specStats.label = `${passedSpecs}/${totalSpecs} PASSED`;
specStats.cssClass = 'spec-stats-passed';
}

return specStats;
}

let reportGenerator = (bsConfig, buildId, args, cb) => {
let options = {
url: `${config.buildUrl}${buildId}/custom_report`,
auth: {
user: bsConfig.auth.username,
password: bsConfig.auth.access_key,
},
headers: {
'User-Agent': utils.getUserAgent(),
},
};

return request.get(options, function (err, resp, body) {
let message = null;
let messageType = null;
let errorCode = null;
let build;

if (err) {
message = Constants.userMessages.BUILD_INFO_FAILED;
messageType = Constants.messageTypes.ERROR;
errorCode = 'api_failed_build_info';

logger.info(message);
} else {
try {
build = JSON.parse(body);
} catch (error) {
build = null;
}
}

if (resp.statusCode == 299) {
messageType = Constants.messageTypes.INFO;
errorCode = 'api_deprecated';

if (build) {
message = build.message;
logger.info(message);
} else {
message = Constants.userMessages.API_DEPRECATED;
logger.info(message);
}
} else if (resp.statusCode != 200) {
messageType = Constants.messageTypes.ERROR;
errorCode = 'api_failed_build_generate_report';

if (build) {
message = `${
Constants.userMessages.BUILD_GENERATE_REPORT_FAILED.replace('<build-id>', buildId)
} with error: \n${JSON.stringify(build, null, 2)}`;
logger.error(message);
if (build.message === 'Unauthorized') errorCode = 'api_auth_failed';
} else {
message = Constants.userMessages.BUILD_GENERATE_REPORT_FAILED.replace('<build-id>', buildId);
logger.error(message);
}
} else {
messageType = Constants.messageTypes.SUCCESS;
message = `Report for build: ${buildId} was successfully created.`;
renderReportHTML(build);
logger.info(message);
}
utils.sendUsageReport(bsConfig, args, message, messageType, errorCode);
if (cb){
cb();
}
});
}

function renderReportHTML(report_data) {
let resultsDir = 'results';
let metaCharSet = `<meta charset="utf-8">`;
let metaViewPort = `<meta name="viewport" content="width=device-width, initial-scale=1"> `;
let pageTitle = `<title> Browserstack Cypress Report </title>`;
let inlineCss = `<style type="text/css"> ${loadInlineCss()} </style>`;
let head = `<head> ${metaCharSet} ${metaViewPort} ${pageTitle} ${inlineCss} </head>`;
let htmlOpenTag = `<!DOCTYPE HTML><html>`;
let htmlClosetag = `</html>`;
let bodyBuildHeader = createBodyBuildHeader(report_data);
let bodyBuildTable = createBodyBuildTable(report_data);
let bodyReporterContainer = `<div class='report-container'> ${bodyBuildHeader} ${bodyBuildTable} </div>`;
let body = `<body> ${bodyReporterContainer} </body>`;
let html = `${htmlOpenTag} ${head} ${body} ${htmlClosetag}`;


if (!fs.existsSync(resultsDir)){
fs.mkdirSync(resultsDir);
}

// Writing the JSON used in creating the HTML file.
fs.writeFileSync(`${resultsDir}/browserstack-cypress-report.json`, JSON.stringify(report_data), () => {
if(err) {
return logger.error(err);
}
logger.info("The JSON file is saved");
});

// Writing the HTML file generated from the JSON data.
fs.writeFileSync(`${resultsDir}/browserstack-cypress-report.html`, html, () => {
if(err) {
return logger.error(err);
}
logger.info("The HTML file was saved!");
});
}

exports.reportGenerator = reportGenerator;
40 changes: 40 additions & 0 deletions bin/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,46 @@ var argv = yargs
return require('./commands/runs')(argv);
}
})
.command('generate-report', Constants.cliMessages.GENERATE_REPORT.INFO, function(yargs) {
argv = yargs
.usage('usage: $0 generate-report <buildId>')
.demand(1, Constants.cliMessages.BUILD.DEMAND)
.options({
'cf': {
alias: 'config-file',
describe: Constants.cliMessages.BUILD.DESC,
default: 'browserstack.json',
type: 'string',
nargs: 1,
demand: true,
demand: Constants.cliMessages.BUILD.CONFIG_DEMAND
},
'disable-usage-reporting': {
default: undefined,
description: Constants.cliMessages.COMMON.DISABLE_USAGE_REPORTING,
type: "boolean"
},
'u': {
alias: 'username',
describe: Constants.cliMessages.COMMON.USERNAME,
type: "string",
default: undefined
},
'k': {
alias: 'key',
describe: Constants.cliMessages.COMMON.ACCESS_KEY,
type: "string",
default: undefined
},
})
.help('help')
.wrap(null)
.argv
if (checkCommands(yargs, argv, 1)) {
logger.info(Constants.cliMessages.BUILD.INFO_MESSAGE + argv._[1]);
return require('./commands/generateReport')(argv);
}
})
.help('help')
.wrap(null)
.argv
Loading