diff --git a/packages/bruno-cli/examples/report.html b/packages/bruno-cli/examples/report.html
new file mode 100644
index 000000000..75bad93fa
--- /dev/null
+++ b/packages/bruno-cli/examples/report.html
@@ -0,0 +1,1049 @@
+
+
+
+
+
+
+
+
+ Bruno
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark
+ Light
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js
index 6811bb3cd..1caf610c0 100644
--- a/packages/bruno-cli/src/commands/run.js
+++ b/packages/bruno-cli/src/commands/run.js
@@ -6,6 +6,7 @@ const { exists, isFile, isDirectory } = require('../utils/filesystem');
const { runSingleRequest } = require('../runner/run-single-request');
const { bruToEnvJson, getEnvVars } = require('../utils/bru');
const makeJUnitOutput = require('../reporters/junit');
+const makeHtmlOutput = require('../reporters/html');
const { rpad } = require('../utils/common');
const { bruToJson, getOptions, collectionBruToJson } = require('../utils/bru');
const { dotenvToJson } = require('@usebruno/lang');
@@ -203,7 +204,7 @@ const builder = async (yargs) => {
})
.option('format', {
alias: 'f',
- describe: 'Format of the file results; available formats are "json" (default) or "junit"',
+ describe: 'Format of the file results; available formats are "json" (default), "junit" or "html"',
default: 'json',
type: 'string'
})
@@ -235,6 +236,10 @@ const builder = async (yargs) => {
'$0 run request.bru --output results.xml --format junit',
'Run a request and write the results to results.xml in junit format in the current directory'
)
+ .example(
+ '$0 run request.bru --output results.html --format html',
+ 'Run a request and write the results to results.html in html format in the current directory'
+ )
.example('$0 run request.bru --test-only', 'Run all requests that have a test');
};
@@ -331,8 +336,8 @@ const handler = async function (argv) {
}
}
- if (['json', 'junit'].indexOf(format) === -1) {
- console.error(chalk.red(`Format must be one of "json" or "junit"`));
+ if (['json', 'junit', 'html'].indexOf(format) === -1) {
+ console.error(chalk.red(`Format must be one of "json", "junit or "html"`));
return;
}
@@ -483,6 +488,8 @@ const handler = async function (argv) {
fs.writeFileSync(outputPath, JSON.stringify(outputJson, null, 2));
} else if (format === 'junit') {
makeJUnitOutput(results, outputPath);
+ } else if (format === 'html') {
+ makeHtmlOutput(outputJson, outputPath);
}
console.log(chalk.dim(chalk.grey(`Wrote results to ${outputPath}`)));
diff --git a/packages/bruno-cli/src/reporters/html-template.html b/packages/bruno-cli/src/reporters/html-template.html
new file mode 100644
index 000000000..b4cbca0ac
--- /dev/null
+++ b/packages/bruno-cli/src/reporters/html-template.html
@@ -0,0 +1,637 @@
+
+
+
+
+
+
+
+
+ Bruno
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dark
+ Light
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/bruno-cli/src/reporters/html.js b/packages/bruno-cli/src/reporters/html.js
new file mode 100644
index 000000000..7fb594224
--- /dev/null
+++ b/packages/bruno-cli/src/reporters/html.js
@@ -0,0 +1,13 @@
+const fs = require('fs');
+const path = require('path');
+
+const makeHtmlOutput = async (results, outputPath) => {
+ const resultsJson = JSON.stringify(results, null, 2);
+
+ const reportPath = path.join(__dirname, 'html-template.html');
+ const template = fs.readFileSync(reportPath, 'utf8');
+
+ fs.writeFileSync(outputPath, template.replace('__RESULTS_JSON__', resultsJson));
+};
+
+module.exports = makeHtmlOutput;
diff --git a/packages/bruno-cli/tests/reporters/html.spec.js b/packages/bruno-cli/tests/reporters/html.spec.js
new file mode 100644
index 000000000..b45e57f41
--- /dev/null
+++ b/packages/bruno-cli/tests/reporters/html.spec.js
@@ -0,0 +1,81 @@
+const { describe, it, expect } = require('@jest/globals');
+const fs = require('fs');
+
+const makeHtmlOutput = require('../../src/reporters/html');
+
+describe('makeHtmlOutput', () => {
+ beforeEach(() => {
+ jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('should produce an html report', () => {
+ const outputJson = {
+ summary: {
+ totalRequests: 1,
+ passedRequests: 1,
+ failedRequests: 1,
+ totalAssertions: 1,
+ passedAssertions: 1,
+ failedAssertions: 1,
+ totalTests: 1,
+ passedTests: 1,
+ failedTests: 1
+ },
+ results: [
+ {
+ description: 'description provided',
+ suitename: 'Tests/Suite A',
+ request: {
+ method: 'GET',
+ url: 'https://ima.test'
+ },
+ assertionResults: [
+ {
+ lhsExpr: 'res.status',
+ rhsExpr: 'eq 200',
+ status: 'pass'
+ },
+ {
+ lhsExpr: 'res.status',
+ rhsExpr: 'neq 200',
+ status: 'fail',
+ error: 'expected 200 to not equal 200'
+ }
+ ],
+ runtime: 1.2345678
+ },
+ {
+ request: {
+ method: 'GET',
+ url: 'https://imanother.test'
+ },
+ suitename: 'Tests/Suite B',
+ testResults: [
+ {
+ lhsExpr: 'res.status',
+ rhsExpr: 'eq 200',
+ description: 'A test that passes',
+ status: 'pass'
+ },
+ {
+ description: 'A test that fails',
+ status: 'fail',
+ error: 'expected 200 to not equal 200',
+ status: 'fail'
+ }
+ ],
+ runtime: 2.3456789
+ }
+ ]
+ };
+
+ makeHtmlOutput(outputJson, '/tmp/testfile.html');
+
+ const htmlReport = fs.writeFileSync.mock.calls[0][1];
+ expect(htmlReport).toContain(JSON.stringify(outputJson, null, 2));
+ });
+});