From fc697bf81b39de3cbaf3781376abee4c75e787d9 Mon Sep 17 00:00:00 2001 From: Pooja Date: Tue, 10 Jun 2025 22:41:11 +0530 Subject: [PATCH] feat: support chai in scripts (#4552) feat: support chai in scripts --- .../ResponsePane/TestResults/StyledWrapper.js | 28 ++- .../ResponsePane/TestResults/index.js | 190 +++++++++++++----- .../ResponsePane/TestResultsLabel/index.js | 18 +- .../src/components/ResponsePane/index.js | 28 ++- .../RunnerResults/ResponsePane/index.js | 16 +- .../ReduxStore/slices/collections/index.js | 10 + packages/bruno-cli/src/commands/run.js | 76 ++++--- .../src/runner/run-single-request.js | 64 ++++-- .../bruno-common/src/runner/runner-summary.ts | 32 ++- .../bruno-common/src/runner/types/index.ts | 8 + .../src/postman/postman-to-bruno.js | 31 ++- .../scripts/translate-postman-scripts.js | 7 +- .../bruno-electron/src/ipc/network/index.js | 24 ++- .../bruno-js/src/runtime/script-runtime.js | 27 ++- packages/bruno-js/src/runtime/test-runtime.js | 55 +---- packages/bruno-js/src/utils.js | 1 + packages/bruno-js/src/utils/results.js | 80 ++++++++ 17 files changed, 501 insertions(+), 194 deletions(-) create mode 100644 packages/bruno-js/src/utils/results.js diff --git a/packages/bruno-app/src/components/ResponsePane/TestResults/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/TestResults/StyledWrapper.js index 001b4dc29..5b029386e 100644 --- a/packages/bruno-app/src/components/ResponsePane/TestResults/StyledWrapper.js +++ b/packages/bruno-app/src/components/ResponsePane/TestResults/StyledWrapper.js @@ -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}; } `; diff --git a/packages/bruno-app/src/components/ResponsePane/TestResults/index.js b/packages/bruno-app/src/components/ResponsePane/TestResults/index.js index 074fac9e1..8157df2ee 100644 --- a/packages/bruno-app/src/components/ResponsePane/TestResults/index.js +++ b/packages/bruno-app/src/components/ResponsePane/TestResults/index.js @@ -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 }) => ( + + {status === 'pass' ? ( + + ) : ( + + )} + +); + +const ErrorMessage = ({ error }) => error && ( + <> +
+ + {error} + + +); + +const ResultItem = ({ result, type }) => ( +
+ + + {type === 'assertion' + ? `${result.lhsExpr}: ${result.rhsExpr}` + : result.description + } + + +
+); + +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 ( +
+
+ + {isExpanded ? + : + + } + + + {title} ({results.length}), Passed: {passedResults.length}, Failed: {failedResults.length} + +
+ {isExpanded && ( + + )} +
+ ); +}; + +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
No tests found
; } - 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 ( - -
- Tests ({results.length}/{results.length}), Passed: {passedTests.length}, Failed: {failedTests.length} -
- + + toggleSection('preRequest')} + type="test" + /> -
- Assertions ({assertionResults.length}/{assertionResults.length}), Passed: {passedAssertions.length}, Failed:{' '} - {failedAssertions.length} -
-
    - {assertionResults.map((result) => ( -
  • - {result.status === 'pass' ? ( - - ✔  {result.lhsExpr}: {result.rhsExpr} - - ) : ( - <> - - ✘  {result.lhsExpr}: {result.rhsExpr} - -
    - {result.error} - - )} -
  • - ))} -
+ toggleSection('postResponse')} + type="test" + /> + + toggleSection('tests')} + type="test" + /> + + toggleSection('assertions')} + type="assertion" + />
); }; diff --git a/packages/bruno-app/src/components/ResponsePane/TestResultsLabel/index.js b/packages/bruno-app/src/components/ResponsePane/TestResultsLabel/index.js index f894d1f76..51d6f94cc 100644 --- a/packages/bruno-app/src/components/ResponsePane/TestResultsLabel/index.js +++ b/packages/bruno-app/src/components/ResponsePane/TestResultsLabel/index.js @@ -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 (
diff --git a/packages/bruno-app/src/components/ResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/index.js index 1fb120ae9..71e55cdd5 100644 --- a/packages/bruno-app/src/components/ResponsePane/index.js +++ b/packages/bruno-app/src/components/ResponsePane/index.js @@ -73,7 +73,12 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => { return ; } case 'tests': { - return ; + return ; } 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
selectTab('tests')}> - +
{!isLoading ? (
{hasScriptError && !showScriptErrorCard && ( - setShowScriptErrorCard(true)} + setShowScriptErrorCard(true)} /> )} {focusedTab?.responsePaneTab === "timeline" ? ( @@ -168,9 +178,9 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => { > {isLoading ? : null} {hasScriptError && showScriptErrorCard && ( - setShowScriptErrorCard(false)} + setShowScriptErrorCard(false)} /> )} {!item?.response ? ( diff --git a/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js b/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js index 5591dbfea..21f02406e 100644 --- a/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js +++ b/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js @@ -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 ; } case 'tests': { - return ; + return ; } default: { @@ -86,7 +91,12 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => { Timeline
selectTab('tests')}> - +
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index 67c099428..15ce34f62 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -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; + } } } }, diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js index af4cf4ae3..5cf4f67f6 100644 --- a/packages/bruno-cli/src/commands/run.js +++ b/packages/bruno-cli/src/commands/run.js @@ -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) { diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index c660367e8..6fd575f90 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -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: [] }; } }; diff --git a/packages/bruno-common/src/runner/runner-summary.ts b/packages/bruno-common/src/runner/runner-summary.ts index 5862729bb..4fb276a29 100644 --- a/packages/bruno-common/src/runner/runner-summary.ts +++ b/packages/bruno-common/src/runner/runner-summary.ts @@ -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, }; }; \ No newline at end of file diff --git a/packages/bruno-common/src/runner/types/index.ts b/packages/bruno-common/src/runner/types/index.ts index 33840f0ad..3565930f2 100644 --- a/packages/bruno-common/src/runner/types/index.ts +++ b/packages/bruno-common/src/runner/types/index.ts @@ -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; } \ No newline at end of file diff --git a/packages/bruno-converters/src/postman/postman-to-bruno.js b/packages/bruno-converters/src/postman/postman-to-bruno.js index 8ae8e195e..bef21b46e 100644 --- a/packages/bruno-converters/src/postman/postman-to-bruno.js +++ b/packages/bruno-converters/src/postman/postman-to-bruno.js @@ -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 : ''; } } }); diff --git a/packages/bruno-converters/src/workers/scripts/translate-postman-scripts.js b/packages/bruno-converters/src/workers/scripts/translate-postman-scripts.js index 31b7d9008..816a08f03 100644 --- a/packages/bruno-converters/src/workers/scripts/translate-postman-scripts.js +++ b/packages/bruno-converters/src/workers/scripts/translate-postman-scripts.js @@ -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 = ''; } } } diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index f5a49ca1d..674cb5754 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -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, diff --git a/packages/bruno-js/src/runtime/script-runtime.js b/packages/bruno-js/src/runtime/script-runtime.js index a8f2abbee..a18c94917 100644 --- a/packages/bruno-js/src/runtime/script-runtime.js +++ b/packages/bruno-js/src/runtime/script-runtime.js @@ -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 diff --git a/packages/bruno-js/src/runtime/test-runtime.js b/packages/bruno-js/src/runtime/test-runtime.js index b172fc7b7..bed0589ca 100644 --- a/packages/bruno-js/src/runtime/test-runtime.js +++ b/packages/bruno-js/src/runtime/test-runtime.js @@ -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, diff --git a/packages/bruno-js/src/utils.js b/packages/bruno-js/src/utils.js index 55b454d02..289bf8dcc 100644 --- a/packages/bruno-js/src/utils.js +++ b/packages/bruno-js/src/utils.js @@ -144,6 +144,7 @@ const cleanJson = (data) => { } }; + module.exports = { evaluateJsExpression, evaluateJsTemplateLiteral, diff --git a/packages/bruno-js/src/utils/results.js b/packages/bruno-js/src/utils/results.js new file mode 100644 index 000000000..0ed38638a --- /dev/null +++ b/packages/bruno-js/src/utils/results.js @@ -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 +}; \ No newline at end of file