Skip to content
Open
Show file tree
Hide file tree
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
230 changes: 127 additions & 103 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/bruno-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"axios-ntlm": "^1.4.2",
"chai": "^4.3.7",
"chalk": "^3.0.0",
"cli-table3": "^0.6.5",
Comment thread
sharan-bruno marked this conversation as resolved.
"decomment": "^0.9.5",
"form-data": "4.0.4",
"fs-extra": "^10.1.0",
Expand Down
191 changes: 114 additions & 77 deletions packages/bruno-cli/src/commands/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ const path = require('path');
const yaml = require('js-yaml');
const { forOwn, cloneDeep } = require('lodash');
const { getRunnerSummary } = require('@usebruno/common/runner');
const { exists, isFile, isDirectory } = require('../utils/filesystem');
const { exists, isFile, isDirectory, stripExtension } = require('../utils/filesystem');
const { runSingleRequest } = require('../runner/run-single-request');
const { getEnvVars } = require('../utils/bru');
const { parseEnvironmentJson } = require('../utils/environment');
const { isRequestTagsIncluded } = require('@usebruno/common');
const makeJUnitOutput = require('../reporters/junit');
const makeHtmlOutput = require('../reporters/html');
const { rpad } = require('../utils/common');
const { getOptions } = require('../utils/bru');
const { parseDotEnv, parseEnvironment } = require('@usebruno/filestore');
const constants = require('../constants');
const Table = require('cli-table3');
const { findItemInCollection, createCollectionJsonFromPathname, getCallStack, FORMAT_CONFIG } = require('../utils/collection');
const { hasExecutableTestInScript } = require('../utils/request');
const { createSkippedFileResults } = require('../utils/run');
Expand All @@ -23,86 +23,64 @@ const { getSystemProxy } = require('@usebruno/requests');
const command = 'run [paths...]';
const desc = 'Run one or more requests/folders';

const formatTestSummary = (label, maxLength, passed, failed, total, errorCount = 0, skippedCount = 0) => {
const parts = [
`${rpad(label, maxLength)} ${chalk.green(`${passed} passed`)}`
];

if (failed > 0) parts.push(chalk.red(`${failed} failed`));
if (errorCount > 0) parts.push(chalk.red(`${errorCount} error`));
if (skippedCount > 0) parts.push(chalk.magenta(`${skippedCount} skipped`));

parts.push(`${total} total`);
const formatRequestsCellFromSummary = (summary) => {
const total = summary.totalRequests || 0;
const passed = summary.passedRequests || 0;
const failedOrErrored = (summary.failedRequests || 0) + (summary.errorRequests || 0);
const totalSkipped = summary.skippedRequests || 0;
const skippedByBail = summary.skippedByBail || 0;
const skippedByUser = Math.max(totalSkipped - skippedByBail, 0);

const parts = [];
if (passed > 0) parts.push(chalk.green(`${passed} Passed`));
if (failedOrErrored > 0) parts.push(chalk.red(`${failedOrErrored} Failed`));
if (skippedByUser > 0) parts.push(chalk.magenta(`${skippedByUser} Skipped`));
if (skippedByBail > 0) parts.push(chalk.hex(constants.COLORS.ORANGE)(`${skippedByBail} Skipped (Bail)`));

return parts.length ? `${total} (${parts.join(', ')})` : `${total}`;
};

return parts.join(', ');
const printGenericTable = (headers, rows, title) => {
const colAligns = headers.map((_, idx) => (idx === 0 ? 'left' : 'center'));
const table = new Table({ head: headers, style: { head: [], border: [] }, colAligns });
rows.forEach((row) => table.push(row));
console.log('\n' + chalk.bold(title));
console.log(table.toString());
};

const printRunSummary = (results) => {
const {
totalRequests,
passedRequests,
failedRequests,
skippedRequests,
errorRequests,
totalAssertions,
passedAssertions,
failedAssertions,
totalTests,
passedTests,
failedTests,
totalPreRequestTests,
passedPreRequestTests,
failedPreRequestTests,
totalPostResponseTests,
passedPostResponseTests,
failedPostResponseTests
} = getRunnerSummary(results);

const maxLength = 12;

const requestSummary = formatTestSummary('Requests:', maxLength, passedRequests, failedRequests, totalRequests, errorRequests, skippedRequests);
const testSummary = formatTestSummary('Tests:', maxLength, passedTests, failedTests, totalTests);
const assertSummary = formatTestSummary('Assertions:', maxLength, passedAssertions, failedAssertions, totalAssertions);

let preRequestTestSummary = '';
if (totalPreRequestTests > 0) {
preRequestTestSummary = formatTestSummary('Pre-Request Tests:', maxLength, passedPreRequestTests, failedPreRequestTests, totalPreRequestTests);
}
const summary = getRunnerSummary(results);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think the summary should already get bail information extracted using getRunnerSummary method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

addressed now its coming from getRunnerSummary(results)


const duration = Math.round(
results.reduce((acc, res) => acc + (res.runDuration || 0), 0) * 1000
);

const hasFailures
= summary.failedRequests > 0
|| summary.failedAssertions > 0
|| summary.failedTests > 0
|| (summary.errorRequests || 0) > 0;

const status = hasFailures
? chalk.red.bold('✗ FAIL')
: chalk.green.bold('✓ PASS');

const requests = formatRequestsCellFromSummary(summary);
const tests = `${summary.passedTests}/${summary.totalTests}`;
const assertions = `${summary.passedAssertions}/${summary.totalAssertions}`;

const headers = [chalk.bold('Metric'), chalk.bold('Result')];
const rows = [
['Status', status],
['Requests', requests],
['Tests', tests],
['Assertions', assertions],
['Duration (ms)', duration]
];

let postResponseTestSummary = '';
if (totalPostResponseTests > 0) {
postResponseTestSummary = formatTestSummary('Post-Response Tests:', maxLength, passedPostResponseTests, failedPostResponseTests, totalPostResponseTests);
}
printGenericTable(headers, rows, '📊 Execution Summary');

console.log('\n' + chalk.bold(requestSummary));
if (preRequestTestSummary) {
console.log(chalk.bold(preRequestTestSummary));
}
if (postResponseTestSummary) {
console.log(chalk.bold(postResponseTestSummary));
}
console.log(chalk.bold(testSummary));
console.log(chalk.bold(assertSummary));

return {
totalRequests,
passedRequests,
failedRequests,
skippedRequests,
errorRequests,
totalAssertions,
passedAssertions,
failedAssertions,
totalTests,
passedTests,
failedTests,
totalPreRequestTests,
passedPreRequestTests,
failedPreRequestTests,
totalPostResponseTests,
passedPostResponseTests,
failedPostResponseTests
};
return summary;
};

const getJsSandboxRuntime = (sandbox) => {
Expand Down Expand Up @@ -679,6 +657,7 @@ const handler = async function (argv) {

let currentRequestIndex = 0;
let nJumps = 0; // count the number of jumps to avoid infinite loops
let bailInfo = null; // populated only if --bail triggers
while (currentRequestIndex < requestItems.length) {
const requestItem = cloneDeep(requestItems[currentRequestIndex]);
const { name, pathname } = requestItem;
Expand Down Expand Up @@ -712,7 +691,7 @@ const handler = async function (argv) {
results.push({
...result,
runDuration: process.hrtime(start)[0] + process.hrtime(start)[1] / 1e9,
suitename: pathname.replace('.bru', ''),
suitename: stripExtension(pathname),
name,
path: result.test?.filename || path.relative(collectionPath, pathname)
});
Expand All @@ -732,6 +711,64 @@ const handler = async function (argv) {
const preRequestTestFailure = result?.preRequestTestResults?.find((iter) => iter.status === 'fail');
const postResponseTestFailure = result?.postResponseTestResults?.find((iter) => iter.status === 'fail');
if (requestFailure || testFailure || assertionFailure || preRequestTestFailure || postResponseTestFailure) {
// Pick the most specific reason for the user-facing message
let bailReason;
if (requestFailure) bailReason = 'request failure';
else if (assertionFailure) bailReason = 'assertion failure';
else if (preRequestTestFailure) bailReason = 'pre-request test failure';
else if (postResponseTestFailure) bailReason = 'post-response test failure';
else bailReason = 'test failure';

const remainingItems = requestItems.slice(currentRequestIndex + 1);

// Synthesize "Skipped (Bail)" placeholder results for the requests that never
// ran due to bail. These let getRunnerSummary count them as skipped, and the
// summary table can distinguish them from user-initiated skips via skipReason.
for (const ri of remainingItems) {
const relativePath = path.relative(collectionPath, ri.pathname);
results.push({
test: {
filename: relativePath
},
request: {
method: null,
url: null,
headers: null,
data: null
},
response: {
status: 'skipped',
statusText: null,
data: null,
responseTime: 0
},
status: 'skipped',
skipped: true,
skipReason: 'bail',
testResults: [],
assertionResults: [],
preRequestTestResults: [],
postResponseTestResults: [],
runDuration: 0,
suitename: stripExtension(ri.pathname),
name: ri.name,
path: relativePath
});
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

bailInfo = {
bailed: true,
bailReason,
bailedAt: name,
skippedByBail: remainingItems.length
};

console.log(
'\n' + chalk.hex(constants.COLORS.ORANGE)(
`Bail: Stopping run, ${bailReason} in "${name}". Remaining ${remainingItems.length} request(s) skipped.`
)
);

break;
}
}
Expand Down
6 changes: 5 additions & 1 deletion packages/bruno-cli/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ const { version } = require('../package.json');

const CLI_EPILOGUE = `Documentation: https://docs.usebruno.com (v${version})`;
const CLI_VERSION = version;
const COLORS = {
ORANGE: '#FFA500'
};

// Exit codes
const EXIT_STATUS = {
Expand Down Expand Up @@ -38,5 +41,6 @@ const EXIT_STATUS = {
module.exports = {
CLI_EPILOGUE,
CLI_VERSION,
EXIT_STATUS
EXIT_STATUS,
COLORS
};
3 changes: 3 additions & 0 deletions packages/bruno-common/src/runner/runner-summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const getRunnerSummary = (results: T_RunnerRequestExecutionResult[]): T_R
let failedRequests = 0;
let errorRequests = 0;
let skippedRequests = 0;
let skippedByBail = 0;
let totalAssertions = 0;
let passedAssertions = 0;
let failedAssertions = 0;
Expand All @@ -30,6 +31,7 @@ export const getRunnerSummary = (results: T_RunnerRequestExecutionResult[]): T_R

if (status === 'skipped') {
skippedRequests += 1;
if (result.skipReason === 'bail') skippedByBail += 1;
continue;
}

Expand Down Expand Up @@ -94,6 +96,7 @@ export const getRunnerSummary = (results: T_RunnerRequestExecutionResult[]): T_R
failedRequests,
errorRequests,
skippedRequests,
skippedByBail,
totalAssertions,
passedAssertions,
failedAssertions,
Expand Down
3 changes: 3 additions & 0 deletions packages/bruno-common/src/runner/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ export type T_RunnerRequestExecutionResult = {
request: T_EmptyRequest | T_Request;
response: T_EmptyResponse | T_Response | T_SkippedResponse;
status: null | undefined | string;
skipped?: boolean;
skipReason?: string;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we are missing skipped: boolean here. skipReason should coexist with skipped property.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added the missing skipped in the type

error: null | undefined | string;
assertionResults?: T_AssertionResult[];
testResults?: T_TestResult[];
Expand All @@ -110,6 +112,7 @@ export type T_RunSummary = {
failedRequests: number;
errorRequests: number;
skippedRequests: number;
skippedByBail: number;
totalAssertions: number;
passedAssertions: number;
failedAssertions: number;
Expand Down
Loading