feat: support chai in scripts (#4552)

feat: support chai in scripts
This commit is contained in:
Pooja
2025-06-10 22:41:11 +05:30
committed by GitHub
parent 9bc07afc77
commit fc697bf81b
17 changed files with 501 additions and 194 deletions

View File

@@ -1,6 +1,18 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
color: ${(props) => props.theme.text};
.test-summary {
transition: background-color 0.2s;
border-bottom: 1px solid ${(props) => props.theme.sidebar.collection.item.indentBorder};
color: ${(props) => props.theme.text};
&:hover {
background-color: ${(props) => props.theme.sidebar.collection.item.hoverBg};
}
}
.test-success {
color: ${(props) => props.theme.colors.text.green};
}
@@ -9,12 +21,24 @@ const StyledWrapper = styled.div`
color: ${(props) => props.theme.colors.text.danger};
}
.test-success-count {
color: ${(props) => props.theme.colors.text.green};
}
.test-failure-count {
color: ${(props) => props.theme.colors.text.danger};
}
.error-message {
color: ${(props) => props.theme.colors.text.muted};
}
.skipped-request {
color: ${(props) => props.theme.colors.text.muted};
.test-results-list {
transition: all 0.3s ease;
}
.dropdown-icon {
color: ${(props) => props.theme.sidebar.dropdownIcon.color};
}
`;

View File

@@ -1,63 +1,151 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import StyledWrapper from './StyledWrapper';
import {
IconChevronDown,
IconChevronRight,
IconCircleCheck,
IconCircleX
} from '@tabler/icons';
const TestResults = ({ results, assertionResults }) => {
const ResultIcon = ({ status }) => (
<span className={`inline-flex items-center ${status === 'pass' ? 'test-success' : 'test-failure'}`}>
{status === 'pass' ? (
<IconCircleCheck size={14} className="mr-1" aria-label="Test passed" />
) : (
<IconCircleX size={14} className="mr-1" aria-label="Test failed" />
)}
</span>
);
const ErrorMessage = ({ error }) => error && (
<>
<br />
<span className="error-message pl-8" role="alert">
{error}
</span>
</>
);
const ResultItem = ({ result, type }) => (
<div className="test-result-item">
<ResultIcon status={result.status} />
<span className={result.status === 'pass' ? 'test-success' : 'test-failure'}>
{type === 'assertion'
? `${result.lhsExpr}: ${result.rhsExpr}`
: result.description
}
</span>
<ErrorMessage error={result.error} />
</div>
);
const TestSection = ({
title,
results,
isExpanded,
onToggle,
type = 'test'
}) => {
const passedResults = results.filter((result) => result.status === 'pass');
const failedResults = results.filter((result) => result.status === 'fail');
if (results.length === 0) return null;
return (
<div className='mb-4'>
<div
className="font-medium test-summary flex items-center cursor-pointer hover:bg-opacity-10 hover:bg-gray-500 rounded py-2"
onClick={onToggle}
>
<span className="dropdown-icon mr-2 flex items-center">
{isExpanded ?
<IconChevronDown size={18} stroke={1.5} /> :
<IconChevronRight size={18} stroke={1.5} />
}
</span>
<span className="flex-grow">
{title} ({results.length}), Passed: {passedResults.length}, Failed: {failedResults.length}
</span>
</div>
{isExpanded && (
<ul className="ml-5">
{results.map((result) => (
<li key={result.uid} className="py-1">
<ResultItem result={result} type={type} />
</li>
))}
</ul>
)}
</div>
);
};
const TestResults = ({ results, assertionResults, preRequestTestResults, postResponseTestResults }) => {
results = results || [];
assertionResults = assertionResults || [];
if (!results.length && !assertionResults.length) {
preRequestTestResults = preRequestTestResults || [];
postResponseTestResults = postResponseTestResults || [];
const [expandedSections, setExpandedSections] = useState({
preRequest: true,
tests: true,
postResponse: true,
assertions: true
});
useEffect(() => {
setExpandedSections({
preRequest: preRequestTestResults.length > 0,
tests: results.length > 0,
postResponse: postResponseTestResults.length > 0,
assertions: assertionResults.length > 0
});
}, [results.length, assertionResults.length, preRequestTestResults.length, postResponseTestResults.length]);
const toggleSection = (section) => {
setExpandedSections({
...expandedSections,
[section]: !expandedSections[section]
});
};
if (!results.length && !assertionResults.length && !preRequestTestResults.length && !postResponseTestResults.length) {
return <div className="px-3">No tests found</div>;
}
const passedTests = results.filter((result) => result.status === 'pass');
const failedTests = results.filter((result) => result.status === 'fail');
const passedAssertions = assertionResults.filter((result) => result.status === 'pass');
const failedAssertions = assertionResults.filter((result) => result.status === 'fail');
return (
<StyledWrapper className="flex flex-col">
<div className="pb-2 font-medium test-summary">
Tests ({results.length}/{results.length}), Passed: {passedTests.length}, Failed: {failedTests.length}
</div>
<ul className="">
{results.map((result) => (
<li key={result.uid} className="py-1">
{result.status === 'pass' ? (
<span className="test-success">&#x2714;&nbsp; {result.description}</span>
) : (
<>
<span className="test-failure">&#x2718;&nbsp; {result.description}</span>
<br />
<span className="error-message pl-8">{result.error}</span>
</>
)}
</li>
))}
</ul>
<StyledWrapper className="flex flex-col px-3">
<TestSection
title="Pre-Request Tests"
results={preRequestTestResults}
isExpanded={expandedSections.preRequest}
onToggle={() => toggleSection('preRequest')}
type="test"
/>
<div className="py-2 font-medium test-summary">
Assertions ({assertionResults.length}/{assertionResults.length}), Passed: {passedAssertions.length}, Failed:{' '}
{failedAssertions.length}
</div>
<ul className="">
{assertionResults.map((result) => (
<li key={result.uid} className="py-1">
{result.status === 'pass' ? (
<span className="test-success">
&#x2714;&nbsp; {result.lhsExpr}: {result.rhsExpr}
</span>
) : (
<>
<span className="test-failure">
&#x2718;&nbsp; {result.lhsExpr}: {result.rhsExpr}
</span>
<br />
<span className="error-message pl-8">{result.error}</span>
</>
)}
</li>
))}
</ul>
<TestSection
title="Post-Response Tests"
results={postResponseTestResults}
isExpanded={expandedSections.postResponse}
onToggle={() => toggleSection('postResponse')}
type="test"
/>
<TestSection
title="Tests"
results={results}
isExpanded={expandedSections.tests}
onToggle={() => toggleSection('tests')}
type="test"
/>
<TestSection
title="Assertions"
results={assertionResults}
isExpanded={expandedSections.assertions}
onToggle={() => toggleSection('assertions')}
type="assertion"
/>
</StyledWrapper>
);
};

View File

@@ -1,9 +1,13 @@
import React from 'react';
import { IconCircleCheck, IconCircleX } from '@tabler/icons';
const TestResultsLabel = ({ results, assertionResults }) => {
const TestResultsLabel = ({ results, assertionResults, preRequestTestResults, postResponseTestResults }) => {
results = results || [];
assertionResults = assertionResults || [];
if (!results.length && !assertionResults.length) {
preRequestTestResults = preRequestTestResults || [];
postResponseTestResults = postResponseTestResults || [];
if (!results.length && !assertionResults.length && !preRequestTestResults.length && !postResponseTestResults.length) {
return 'Tests';
}
@@ -13,8 +17,14 @@ const TestResultsLabel = ({ results, assertionResults }) => {
const numberOfAssertions = assertionResults.length;
const numberOfFailedAssertions = assertionResults.filter((result) => result.status === 'fail').length;
const totalNumberOfTests = numberOfTests + numberOfAssertions;
const totalNumberOfFailedTests = numberOfFailedTests + numberOfFailedAssertions;
const numberOfPreRequestTests = preRequestTestResults.length;
const numberOfFailedPreRequestTests = preRequestTestResults.filter((result) => result.status === 'fail').length;
const numberOfPostResponseTests = postResponseTestResults.length;
const numberOfFailedPostResponseTests = postResponseTestResults.filter((result) => result.status === 'fail').length;
const totalNumberOfTests = numberOfTests + numberOfAssertions + numberOfPreRequestTests + numberOfPostResponseTests;
const totalNumberOfFailedTests = numberOfFailedTests + numberOfFailedAssertions + numberOfFailedPreRequestTests + numberOfFailedPostResponseTests;
return (
<div className="flex items-center">

View File

@@ -73,7 +73,12 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
return <Timeline collection={collection} item={item} width={rightPaneWidth} />;
}
case 'tests': {
return <TestResults results={item.testResults} assertionResults={item.assertionResults} />;
return <TestResults
results={item.testResults}
assertionResults={item.assertionResults}
preRequestTestResults={item.preRequestTestResults}
postResponseTestResults={item.postResponseTestResults}
/>;
}
default: {
@@ -122,7 +127,7 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
};
const responseHeadersCount = typeof response.headers === 'object' ? Object.entries(response.headers).length : 0;
const hasScriptError = item?.preRequestScriptErrorMessage || item?.postResponseScriptErrorMessage;
return (
@@ -139,14 +144,19 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
Timeline
</div>
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
<TestResultsLabel results={item.testResults} assertionResults={item.assertionResults} />
<TestResultsLabel
results={item.testResults}
assertionResults={item.assertionResults}
preRequestTestResults={item.preRequestTestResults}
postResponseTestResults={item.postResponseTestResults}
/>
</div>
{!isLoading ? (
<div className="flex flex-grow justify-end items-center">
{hasScriptError && !showScriptErrorCard && (
<ScriptErrorIcon
itemUid={item.uid}
onClick={() => setShowScriptErrorCard(true)}
<ScriptErrorIcon
itemUid={item.uid}
onClick={() => setShowScriptErrorCard(true)}
/>
)}
{focusedTab?.responsePaneTab === "timeline" ? (
@@ -168,9 +178,9 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
>
{isLoading ? <Overlay item={item} collection={collection} /> : null}
{hasScriptError && showScriptErrorCard && (
<ScriptError
item={item}
onClose={() => setShowScriptErrorCard(false)}
<ScriptError
item={item}
onClose={() => setShowScriptErrorCard(false)}
/>
)}
{!item?.response ? (

View File

@@ -16,7 +16,7 @@ import RunnerTimeline from 'components/ResponsePane/RunnerTimeline';
const ResponsePane = ({ rightPaneWidth, item, collection }) => {
const [selectedTab, setSelectedTab] = useState('response');
const { requestSent, responseReceived, testResults, assertionResults, error } = item;
const { requestSent, responseReceived, testResults, assertionResults, preRequestTestResults, postResponseTestResults, error } = item;
const headers = get(item, 'responseReceived.headers', []);
const status = get(item, 'responseReceived.status', 0);
@@ -49,7 +49,12 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
return <RunnerTimeline request={requestSent} response={responseReceived} />;
}
case 'tests': {
return <TestResults results={testResults} assertionResults={assertionResults} />;
return <TestResults
results={testResults}
assertionResults={assertionResults}
preRequestTestResults={preRequestTestResults}
postResponseTestResults={postResponseTestResults}
/>;
}
default: {
@@ -86,7 +91,12 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
Timeline
</div>
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
<TestResultsLabel results={testResults} assertionResults={assertionResults} />
<TestResultsLabel
results={testResults}
assertionResults={assertionResults}
preRequestTestResults={preRequestTestResults}
postResponseTestResults={postResponseTestResults}
/>
</div>
<div className="flex flex-grow justify-end items-center">
<StatusCode status={status} />

View File

@@ -2015,6 +2015,16 @@ export const collectionsSlice = createSlice({
const { results } = action.payload;
item.testResults = results;
}
if (type === 'test-results-pre-request') {
const { results } = action.payload;
item.preRequestTestResults = results;
}
if (type === 'test-results-post-response') {
const { results } = action.payload;
item.postResponseTestResults = results;
}
}
}
},

View File

@@ -16,6 +16,20 @@ const { findItemInCollection, getAllRequestsInFolder, createCollectionJsonFromPa
const command = 'run [filename]';
const desc = 'Run a request';
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`);
return parts.join(', ');
};
const printRunSummary = (results) => {
const {
totalRequests,
@@ -28,38 +42,40 @@ const printRunSummary = (results) => {
failedAssertions,
totalTests,
passedTests,
failedTests
failedTests,
totalPreRequestTests,
passedPreRequestTests,
failedPreRequestTests,
totalPostResponseTests,
passedPostResponseTests,
failedPostResponseTests
} = getRunnerSummary(results);
const maxLength = 12;
let requestSummary = `${rpad('Requests:', maxLength)} ${chalk.green(`${passedRequests} passed`)}`;
if (failedRequests > 0) {
requestSummary += `, ${chalk.red(`${failedRequests} failed`)}`;
}
if (errorRequests > 0) {
requestSummary += `, ${chalk.red(`${errorRequests} error`)}`;
}
if (skippedRequests > 0) {
requestSummary += `, ${chalk.magenta(`${skippedRequests} skipped`)}`;
}
requestSummary += `, ${totalRequests} total`;
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 assertSummary = `${rpad('Tests:', maxLength)} ${chalk.green(`${passedTests} passed`)}`;
if (failedTests > 0) {
assertSummary += `, ${chalk.red(`${failedTests} failed`)}`;
let preRequestTestSummary = '';
if (totalPreRequestTests > 0) {
preRequestTestSummary = formatTestSummary('Pre-Request Tests:', maxLength, passedPreRequestTests, failedPreRequestTests, totalPreRequestTests);
}
assertSummary += `, ${totalTests} total`;
let testSummary = `${rpad('Assertions:', maxLength)} ${chalk.green(`${passedAssertions} passed`)}`;
if (failedAssertions > 0) {
testSummary += `, ${chalk.red(`${failedAssertions} failed`)}`;
let postResponseTestSummary = '';
if (totalPostResponseTests > 0) {
postResponseTestSummary = formatTestSummary('Post-Response Tests:', maxLength, passedPostResponseTests, failedPostResponseTests, totalPostResponseTests);
}
testSummary += `, ${totalAssertions} total`;
console.log('\n' + chalk.bold(requestSummary));
console.log(chalk.bold(assertSummary));
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,
@@ -72,7 +88,13 @@ const printRunSummary = (results) => {
failedAssertions,
totalTests,
passedTests,
failedTests
failedTests,
totalPreRequestTests,
passedPreRequestTests,
failedPreRequestTests,
totalPostResponseTests,
passedPostResponseTests,
failedPostResponseTests
}
};
@@ -498,7 +520,7 @@ const handler = async function (argv) {
if(Number.isNaN(delay) && !isLastRun){
console.log(chalk.red(`Ignoring delay because it's not a valid number.`));
}
results.push({
...result,
runtime: process.hrtime(start)[0] + process.hrtime(start)[1] / 1e9,
@@ -539,7 +561,9 @@ const handler = async function (argv) {
const requestFailure = result?.error && !result?.skipped;
const testFailure = result?.testResults?.find((iter) => iter.status === 'fail');
const assertionFailure = result?.assertionResults?.find((iter) => iter.status === 'fail');
if (requestFailure || testFailure || assertionFailure) {
const preRequestTestFailure = result?.preRequestTestResults?.find((iter) => iter.status === 'fail');
const postResponseTestFailure = result?.postResponseTestResults?.find((iter) => iter.status === 'fail');
if (requestFailure || testFailure || assertionFailure || preRequestTestFailure || postResponseTestFailure) {
break;
}
}
@@ -550,7 +574,7 @@ const handler = async function (argv) {
if (result?.shouldStopRunnerExecution) {
break;
}
if (nextRequestName !== undefined) {
nJumps++;
if (nJumps > 10000) {
@@ -617,7 +641,7 @@ const handler = async function (argv) {
}
}
if ((summary.failedAssertions + summary.failedTests + summary.failedRequests > 0) || (summary?.errorRequests > 0)) {
if ((summary.failedAssertions + summary.failedTests + summary.failedPreRequestTests + summary.failedPostResponseTests + summary.failedRequests > 0) || (summary?.errorRequests > 0)) {
process.exit(constants.EXIT_STATUS.ERROR_FAILED_COLLECTION);
}
} catch (err) {

View File

@@ -45,10 +45,33 @@ const runSingleRequest = async function (
) {
const { pathname: itemPathname } = item;
const relativeItemPathname = path.relative(collectionPath, itemPathname);
const logResults = (results, title) => {
if (results?.length) {
if (title) {
console.log(chalk.dim(title));
}
each(results, (r) => {
const message = r.description || `${r.lhsExpr}: ${r.rhsExpr}`;
if (r.status === 'pass') {
console.log(chalk.green(``) + chalk.dim(message));
} else {
console.log(chalk.red(``) + chalk.red(message));
if (r.error) {
console.log(chalk.red(` ${r.error}`));
}
}
});
}
};
try {
let request;
let nextRequestName;
let shouldStopRunnerExecution = false;
let preRequestTestResults = [];
let postResponseTestResults = [];
request = prepareRequest(item, collection);
request.__bruno__executionMode = 'cli';
@@ -103,9 +126,13 @@ const runSingleRequest = async function (
skipped: true,
assertionResults: [],
testResults: [],
preRequestTestResults: result?.results || [],
postResponseTestResults: [],
shouldStopRunnerExecution
};
}
preRequestTestResults = result?.results || [];
}
// interpolate variables inside request
@@ -428,6 +455,8 @@ const runSingleRequest = async function (
status: 'error',
assertionResults: [],
testResults: [],
preRequestTestResults,
postResponseTestResults,
nextRequestName: nextRequestName,
shouldStopRunnerExecution
};
@@ -441,6 +470,9 @@ const runSingleRequest = async function (
chalk.dim(` (${response.status} ${response.statusText}) - ${responseTime} ms`)
);
// Log pre-request test results
logResults(preRequestTestResults, 'Pre-Request Tests');
// run post-response vars
const postResponseVars = get(item, 'request.vars.res');
if (postResponseVars?.length) {
@@ -480,9 +512,11 @@ const runSingleRequest = async function (
if (result?.stopExecution) {
shouldStopRunnerExecution = true;
}
postResponseTestResults = result?.results || [];
logResults(postResponseTestResults, 'Post-Response Tests');
}
// run assertions
let assertionResults = [];
const assertions = get(item, 'request.assertions');
if (assertions) {
@@ -495,15 +529,6 @@ const runSingleRequest = async function (
runtimeVariables,
processEnvVars
);
each(assertionResults, (r) => {
if (r.status === 'pass') {
console.log(chalk.green(``) + chalk.dim(`assert: ${r.lhsExpr}: ${r.rhsExpr}`));
} else {
console.log(chalk.red(``) + chalk.red(`assert: ${r.lhsExpr}: ${r.rhsExpr}`));
console.log(chalk.red(` ${r.error}`));
}
});
}
// run tests
@@ -533,17 +558,12 @@ const runSingleRequest = async function (
if (result?.stopExecution) {
shouldStopRunnerExecution = true;
}
logResults(testResults, 'Tests');
}
if (testResults?.length) {
each(testResults, (testResult) => {
if (testResult.status === 'pass') {
console.log(chalk.green(``) + chalk.dim(testResult.description));
} else {
console.log(chalk.red(``) + chalk.red(testResult.description));
}
});
}
logResults(assertionResults, 'Assertions');
return {
test: {
@@ -566,6 +586,8 @@ const runSingleRequest = async function (
status: 'pass',
assertionResults,
testResults,
preRequestTestResults,
postResponseTestResults,
nextRequestName: nextRequestName,
shouldStopRunnerExecution
};
@@ -591,7 +613,9 @@ const runSingleRequest = async function (
status: 'error',
error: err.message,
assertionResults: [],
testResults: []
testResults: [],
preRequestTestResults: [],
postResponseTestResults: []
};
}
};

View File

@@ -13,12 +13,20 @@ export const getRunnerSummary = (results: T_RunnerRequestExecutionResult[]): T_R
let totalTests = 0;
let passedTests = 0;
let failedTests = 0;
let totalPreRequestTests = 0;
let passedPreRequestTests = 0;
let failedPreRequestTests = 0;
let totalPostResponseTests = 0;
let passedPostResponseTests = 0;
let failedPostResponseTests = 0;
for (const result of results || []) {
const { status, testResults, assertionResults } = result;
const { status, testResults, assertionResults, preRequestTestResults, postResponseTestResults } = result;
totalRequests += 1;
totalTests += Number(testResults?.length) || 0;
totalAssertions += Number(assertionResults?.length) || 0;
totalPreRequestTests += Number(preRequestTestResults?.length) || 0;
totalPostResponseTests += Number(postResponseTestResults?.length) || 0;
if (status === 'skipped') {
skippedRequests += 1;
@@ -42,6 +50,22 @@ export const getRunnerSummary = (results: T_RunnerRequestExecutionResult[]): T_R
failedAssertions += 1;
}
}
for (const preRequestTestResult of preRequestTestResults || []) {
if (preRequestTestResult.status === "pass") {
passedPreRequestTests += 1;
} else {
anyFailed = true;
failedPreRequestTests += 1;
}
}
for (const postResponseTestResult of postResponseTestResults || []) {
if (postResponseTestResult.status === "pass") {
passedPostResponseTests += 1;
} else {
anyFailed = true;
failedPostResponseTests += 1;
}
}
if (!anyFailed && status !== "error") {
passedRequests += 1;
@@ -64,5 +88,11 @@ export const getRunnerSummary = (results: T_RunnerRequestExecutionResult[]): T_R
totalTests,
passedTests,
failedTests,
totalPreRequestTests,
passedPreRequestTests,
failedPreRequestTests,
totalPostResponseTests,
passedPostResponseTests,
failedPostResponseTests,
};
};

View File

@@ -89,6 +89,8 @@ export type T_RunnerRequestExecutionResult = {
error: null | undefined | string;
assertionResults?: T_AssertionResult[];
testResults?: T_TestResult[];
preRequestTestResults?: T_TestResult[];
postResponseTestResults?: T_TestResult[];
runDuration: number;
}
@@ -112,4 +114,10 @@ export type T_RunSummary = {
totalTests: number;
passedTests: number;
failedTests: number;
totalPreRequestTests: number;
passedPreRequestTests: number;
failedPreRequestTests: number;
totalPostResponseTests: number;
passedPostResponseTests: number;
failedPostResponseTests: number;
}

View File

@@ -103,14 +103,14 @@ const importScriptsFromEvents = (events, requestObject) => {
}
if (event.listen === 'test') {
if (!requestObject.tests) {
requestObject.tests = {};
if (!requestObject.script) {
requestObject.script = {};
}
if (event.script.exec && event.script.exec.length > 0) {
requestObject.tests = postmanTranslation(event.script.exec)
requestObject.script.res = postmanTranslation(event.script.exec)
} else {
requestObject.tests = '';
requestObject.script.res = '';
console.warn('Unexpected event.script.exec type', typeof event.script.exec);
}
}
@@ -376,16 +376,17 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, { useWorke
}
}
if (event.listen === 'test' && event.script && event.script.exec) {
if (!brunoRequestItem.request?.tests) {
brunoRequestItem.request.tests = {};
if (!brunoRequestItem.request?.script) {
brunoRequestItem.request.script = {};
}
if (event.script.exec && event.script.exec.length > 0) {
brunoRequestItem.request.tests = postmanTranslation(event.script.exec)
brunoRequestItem.request.script.res = postmanTranslation(event.script.exec)
} else {
brunoRequestItem.request.tests = '';
brunoRequestItem.request.script.res = '';
console.warn('Unexpected event.script.exec type', typeof event.script.exec);
}
}
});
}
}
@@ -581,15 +582,12 @@ const importPostmanV2Collection = async (collection, { useWorkers = false }) =>
if (!item.root.request.script) {
item.root.request.script = {};
}
if (!item.root.request.tests) {
item.root.request.tests = '';
}
const script = translatedScripts.get(item.uid).request?.script?.req;
const tests = translatedScripts.get(item.uid).request?.tests;
const tests = translatedScripts.get(item.uid).request?.script?.res;
item.root.request.script.req = script && script.length > 0 ? script : '';
item.root.request.tests = tests && tests.length > 0 ? tests : '';
item.root.request.script.res = tests && tests.length > 0 ? tests : '';
}
// Recursively apply to nested items
@@ -601,15 +599,12 @@ const importPostmanV2Collection = async (collection, { useWorkers = false }) =>
if (!item.request.script) {
item.request.script = {};
}
if (!item.request.tests) {
item.request.tests = '';
}
const script = translatedScripts.get(item.uid).request?.script?.req;
const tests = translatedScripts.get(item.uid).request?.tests;
const tests = translatedScripts.get(item.uid).request?.script?.res;
item.request.script.req = script && script.length > 0 ? script : '';
item.request.tests = tests && tests.length > 0 ? tests : '';
item.request.script.res = tests && tests.length > 0 ? tests : '';
}
}
});

View File

@@ -6,8 +6,7 @@ parentPort.on('message', (workerData) => {
const { scripts } = workerData;
const modScripts = scripts.map(([uid, { events }]) => {
const requestObject = {
script: {},
tests: {}
script: {}
}
if (events && Array.isArray(events)) {
@@ -23,9 +22,9 @@ parentPort.on('message', (workerData) => {
if(event.listen === 'test') {
if(event.script.exec && event.script.exec.length > 0) {
requestObject.tests = postmanTranslation(event.script.exec);
requestObject.script.res = postmanTranslation(event.script.exec);
} else {
requestObject.tests = '';
requestObject.script.res = '';
}
}
}

View File

@@ -596,7 +596,7 @@ const registerNetworkIpc = (mainWindow) => {
try {
await runPreRequest(
const preRequestScriptResult = await runPreRequest(
request,
requestUid,
envVars,
@@ -609,6 +609,16 @@ const registerNetworkIpc = (mainWindow) => {
runRequestByItemPathname
);
if (preRequestScriptResult?.results) {
mainWindow.webContents.send('main:run-request-event', {
type: 'test-results-pre-request',
results: preRequestScriptResult.results,
itemUid: item.uid,
requestUid,
collectionUid
});
}
!runInBackground && mainWindow.webContents.send('main:run-request-event', {
type: 'pre-request-script-execution',
requestUid,
@@ -724,7 +734,7 @@ const registerNetworkIpc = (mainWindow) => {
mainWindow.webContents.send('main:cookies-update', safeParseJSON(safeStringifyJSON(domainsWithCookies)));
try {
await runPostResponse(
const postResponseScriptResult = await runPostResponse(
request,
response,
requestUid,
@@ -737,6 +747,16 @@ const registerNetworkIpc = (mainWindow) => {
scriptingConfig,
runRequestByItemPathname
);
if (postResponseScriptResult?.results) {
mainWindow.webContents.send('main:run-request-event', {
type: 'test-results-post-response',
results: postResponseScriptResult.results,
itemUid: item.uid,
requestUid,
collectionUid
});
}
!runInBackground && mainWindow.webContents.send('main:run-request-event', {
type: 'post-response-script-execution',
requestUid,

View File

@@ -12,7 +12,10 @@ const { get } = require('lodash');
const Bru = require('../bru');
const BrunoRequest = require('../bruno-request');
const BrunoResponse = require('../bruno-response');
const Test = require('../test');
const TestResults = require('../test-results');
const { cleanJson } = require('../utils');
const { createBruTestResultMethods } = require('../utils/results');
// Inbuilt Library Support
const ajv = require('ajv');
@@ -57,6 +60,7 @@ class ScriptRuntime {
const collectionVariables = request?.collectionVariables || {};
const folderVariables = request?.folderVariables || {};
const requestVariables = request?.requestVariables || {};
const assertionResults = request?.assertionResults || [];
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, collectionName);
const req = new BrunoRequest(request);
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
@@ -78,9 +82,16 @@ class ScriptRuntime {
}
}
// extend bru with result getter methods
const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai);
const context = {
bru,
req
req,
test,
expect: chai.expect,
assert: chai.assert,
__brunoTestResults: __brunoTestResults
};
if (onConsoleLog && typeof onConsoleLog === 'function') {
@@ -114,6 +125,7 @@ class ScriptRuntime {
envVariables: cleanJson(envVariables),
runtimeVariables: cleanJson(runtimeVariables),
globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
results: cleanJson(__brunoTestResults.getResults()),
nextRequestName: bru.nextRequest,
skipRequest: bru.skipRequest,
stopExecution: bru.stopExecution
@@ -168,6 +180,7 @@ class ScriptRuntime {
envVariables: cleanJson(envVariables),
runtimeVariables: cleanJson(runtimeVariables),
globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
results: cleanJson(__brunoTestResults.getResults()),
nextRequestName: bru.nextRequest,
skipRequest: bru.skipRequest,
stopExecution: bru.stopExecution
@@ -192,6 +205,7 @@ class ScriptRuntime {
const collectionVariables = request?.collectionVariables || {};
const folderVariables = request?.folderVariables || {};
const requestVariables = request?.requestVariables || {};
const assertionResults = request?.assertionResults || [];
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, collectionName);
const req = new BrunoRequest(request);
const res = new BrunoResponse(response);
@@ -214,10 +228,17 @@ class ScriptRuntime {
}
}
// extend bru with result getter methods
const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai);
const context = {
bru,
req,
res
res,
test,
expect: chai.expect,
assert: chai.assert,
__brunoTestResults: __brunoTestResults
};
if (onConsoleLog && typeof onConsoleLog === 'function') {
@@ -251,6 +272,7 @@ class ScriptRuntime {
envVariables: cleanJson(envVariables),
runtimeVariables: cleanJson(runtimeVariables),
globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
results: cleanJson(__brunoTestResults.getResults()),
nextRequestName: bru.nextRequest,
skipRequest: bru.skipRequest,
stopExecution: bru.stopExecution
@@ -305,6 +327,7 @@ class ScriptRuntime {
envVariables: cleanJson(envVariables),
runtimeVariables: cleanJson(runtimeVariables),
globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
results: cleanJson(__brunoTestResults.getResults()),
nextRequestName: bru.nextRequest,
skipRequest: bru.skipRequest,
stopExecution: bru.stopExecution

View File

@@ -16,6 +16,7 @@ const BrunoResponse = require('../bruno-response');
const Test = require('../test');
const TestResults = require('../test-results');
const { cleanJson } = require('../utils');
const { createBruTestResultMethods } = require('../utils/results');
// Inbuilt Library Support
const ajv = require('ajv');
@@ -35,25 +36,6 @@ const cheerio = require('cheerio');
const tv4 = require('tv4');
const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
const getResultsSummary = (results) => {
const summary = {
total: results.length,
passed: 0,
failed: 0,
skipped: 0,
};
results.forEach((r) => {
const passed = r.status === "pass";
if (passed) summary.passed += 1;
else if (r.status === "fail") summary.failed += 1;
else summary.skipped += 1;
});
return summary;
}
class TestRuntime {
constructor(props) {
this.runtime = props?.runtime || 'vm2';
@@ -99,9 +81,8 @@ class TestRuntime {
}
}
const __brunoTestResults = new TestResults();
const test = Test(__brunoTestResults, chai);
// extend bru with result getter methods
const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai);
if (!testsFile || !testsFile.length) {
return {
@@ -114,36 +95,6 @@ class TestRuntime {
};
}
bru.getTestResults = async () => {
let results = await __brunoTestResults.getResults();
const summary = getResultsSummary(results);
return {
summary,
results: results?.map?.(r => ({
status: r?.status,
description: r?.description,
expected: r?.expected,
actual: r?.actual,
error: r?.error
}))
};
}
bru.getAssertionResults = async () => {
let results = assertionResults;
const summary = getResultsSummary(results);
return {
summary,
results: results?.map?.(r => ({
status: r?.status,
lhsExpr: r?.lhsExpr,
rhsExpr: r?.rhsExpr,
operator: r?.operator,
rhsOperand: r?.rhsOperand,
error: r?.error
}))
};
}
const context = {
test,
bru,

View File

@@ -144,6 +144,7 @@ const cleanJson = (data) => {
}
};
module.exports = {
evaluateJsExpression,
evaluateJsTemplateLiteral,

View File

@@ -0,0 +1,80 @@
const TestResults = require('../test-results');
const Test = require('../test');
// Calculate summary statistics for test results
const getResultsSummary = (results) => {
const summary = {
total: results.length,
passed: 0,
failed: 0,
skipped: 0,
};
results.forEach((r) => {
const passed = r.status === 'pass';
if (passed) summary.passed += 1;
else if (r.status === 'fail') summary.failed += 1;
else summary.skipped += 1;
});
return summary;
};
const createBruTestResultMethods = (bru, assertionResults, chai) => {
const __brunoTestResults = new TestResults();
const test = Test(__brunoTestResults, chai);
setupBruTestMethods(bru, __brunoTestResults, assertionResults);
return { __brunoTestResults, test };
};
const setupBruTestMethods = (bru, __brunoTestResults, assertionResults) => {
const getTestResults = async () => {
let results = await __brunoTestResults.getResults();
const summary = getResultsSummary(results);
return {
summary,
results: results.map(r => ({
status: r.status,
description: r.description,
expected: r.expected,
actual: r.actual,
error: r.error
}))
};
};
const getAssertionResults = async () => {
let results = assertionResults;
const summary = getResultsSummary(results);
return {
summary,
results: results.map(r => ({
status: r.status,
lhsExpr: r.lhsExpr,
rhsExpr: r.rhsExpr,
operator: r.operator,
rhsOperand: r.rhsOperand,
error: r.error
}))
};
};
// Set methods on bru object if provided
if (bru) {
bru.getTestResults = getTestResults;
bru.getAssertionResults = getAssertionResults;
}
// Also return the methods for direct use
return {
getTestResults,
getAssertionResults
};
};
module.exports = {
getResultsSummary,
createBruTestResultMethods,
setupBruTestMethods
};