From cf3ce0f450569ac3ce6ff285b067dc02977ef288 Mon Sep 17 00:00:00 2001 From: Karan Pradhan <78605930+KaranPradhan266@users.noreply.github.com> Date: Mon, 2 Feb 2026 03:19:19 -0800 Subject: [PATCH] Mark test script errors as failed in runner (#6261) * Mark test script errors as failed in runner and CLI * Unify handling of post-response and pre-request script errors in both CLI and Electron * feat: Enhance error handling in script execution by preserving partial results for pre-request and post-response scripts across CLI and Electron. This ensures that tests passing before an error are still reported. * Preserving stopExecution in test script error handler --------- Co-authored-by: Pragadesh-45 --- .../src/runner/run-single-request.js | 155 ++++++++++++----- .../bruno-electron/src/ipc/network/index.js | 65 +++++++ .../bruno-js/src/runtime/script-runtime.js | 158 +++++++++++------- 3 files changed, 273 insertions(+), 105 deletions(-) diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index 701eec689..72ad5c19d 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -175,55 +175,80 @@ const runSingleRequest = async function ( const collectionName = collection?.brunoConfig?.name; if (requestScriptFile?.length) { const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime }); - const result = await scriptRuntime.runRequestScript( - decomment(requestScriptFile), - request, - envVariables, - runtimeVariables, - collectionPath, - onConsoleLog, - processEnvVars, - scriptingConfig, - runSingleRequestByPathname, - collectionName - ); - if (result?.nextRequestName !== undefined) { - nextRequestName = result.nextRequestName; - } + try { + const result = await scriptRuntime.runRequestScript(decomment(requestScriptFile), + request, + envVariables, + runtimeVariables, + collectionPath, + onConsoleLog, + processEnvVars, + scriptingConfig, + runSingleRequestByPathname, + collectionName); + if (result?.nextRequestName !== undefined) { + nextRequestName = result.nextRequestName; + } - if (result?.stopExecution) { - shouldStopRunnerExecution = true; - } + if (result?.stopExecution) { + shouldStopRunnerExecution = true; + } - if (result?.skipRequest) { - return { - test: { - filename: relativeItemPathname - }, - request: { - method: request.method, - url: request.url, - headers: request.headers, - data: request.data - }, - response: { + if (result?.skipRequest) { + return { + test: { + filename: relativeItemPathname + }, + request: { + method: request.method, + url: request.url, + headers: request.headers, + data: request.data + }, + response: { + status: 'skipped', + statusText: 'request skipped via pre-request script', + data: null, + responseTime: 0 + }, + error: null, status: 'skipped', - statusText: 'request skipped via pre-request script', - data: null, - responseTime: 0 - }, - error: null, - status: 'skipped', - skipped: true, - assertionResults: [], - testResults: [], - preRequestTestResults: result?.results || [], - postResponseTestResults: [], - shouldStopRunnerExecution - }; - } + skipped: true, + assertionResults: [], + testResults: [], + preRequestTestResults: result?.results || [], + postResponseTestResults: [], + shouldStopRunnerExecution + }; + } - preRequestTestResults = result?.results || []; + preRequestTestResults = result?.results || []; + } catch (error) { + console.error('Pre-request script execution error:', error); + + // Extract partial results from the error (tests that passed before the error) + const partialResults = error?.partialResults?.results || []; + preRequestTestResults = [ + ...partialResults, + { + status: 'fail', + description: 'Pre-Request Script Error', + error: error.message || 'An error occurred while executing the pre-request script.' + } + ]; + + // Preserve nextRequestName if it was set before the error + if (error?.partialResults?.nextRequestName !== undefined) { + nextRequestName = error.partialResults.nextRequestName; + } + + // Preserve stopExecution if it was set before the error + if (error?.partialResults?.stopExecution) { + shouldStopRunnerExecution = true; + } + + logResults(preRequestTestResults, 'Pre-Request Tests'); + } } // interpolate variables inside request @@ -702,6 +727,26 @@ const runSingleRequest = async function ( logResults(postResponseTestResults, 'Post-Response Tests'); } catch (error) { console.error('Post-response script execution error:', error); + + const partialResults = error?.partialResults?.results || []; + postResponseTestResults = [ + ...partialResults, + { + status: 'fail', + description: 'Post-Response Script Error', + error: error.message || 'An error occurred while executing the post-response script.' + } + ]; + + if (error?.partialResults?.nextRequestName !== undefined) { + nextRequestName = error.partialResults.nextRequestName; + } + + if (error?.partialResults?.stopExecution) { + shouldStopRunnerExecution = true; + } + + logResults(postResponseTestResults, 'Post-Response Tests'); } } @@ -751,6 +796,26 @@ const runSingleRequest = async function ( logResults(testResults, 'Tests'); } catch (error) { console.error('Test script execution error:', error); + + const partialResults = error?.partialResults?.results || []; + testResults = [ + ...partialResults, + { + status: 'fail', + description: 'Test Script Error', + error: error.message || 'An error occurred while executing the test script.' + } + ]; + + if (error?.partialResults?.nextRequestName !== undefined) { + nextRequestName = error.partialResults.nextRequestName; + } + + if (error?.partialResults?.stopExecution) { + shouldStopRunnerExecution = true; + } + + logResults(testResults, 'Tests'); } } diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 7aea48f39..205a8987d 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -443,6 +443,38 @@ const registerNetworkIpc = (mainWindow) => { }); }; + const appendScriptErrorResult = (scriptType, scriptResult, error) => { + if (!error) { + return scriptResult; + } + + const descriptionMap = { + 'test': 'Test Script Error', + 'post-response': 'Post-Response Script Error', + 'pre-request': 'Pre-Request Script Error' + }; + + const messageMap = { + 'test': 'An error occurred while executing the test script.', + 'post-response': 'An error occurred while executing the post-response script.', + 'pre-request': 'An error occurred while executing the pre-request script.' + }; + + const results = [ + ...(scriptResult?.results || []), + { + status: 'fail', + description: descriptionMap[scriptType] || 'Script Error', + error: error.message || messageMap[scriptType] || 'An error occurred while executing the script.' + } + ]; + + return { + ...(scriptResult || {}), + results + }; + }; + const runPreRequest = async ( request, requestUid, @@ -699,6 +731,12 @@ const registerNetworkIpc = (mainWindow) => { preRequestError = error; } + if (preRequestError?.partialResults) { + preRequestScriptResult = preRequestError.partialResults; + } + + preRequestScriptResult = appendScriptErrorResult('pre-request', preRequestScriptResult, preRequestError); + if (preRequestScriptResult?.results) { mainWindow.webContents.send('main:run-request-event', { type: 'test-results-pre-request', @@ -864,6 +902,15 @@ const registerNetworkIpc = (mainWindow) => { postResponseError = error; } + // Extract partial results from error if available + // This preserves any test() calls that passed before the script errored + // (e.g., if 2 tests pass then script throws, we still want to show those 2 passing tests) + if (postResponseError?.partialResults) { + postResponseScriptResult = postResponseError.partialResults; + } + + postResponseScriptResult = appendScriptErrorResult('post-response', postResponseScriptResult, postResponseError); + if (postResponseScriptResult?.results) { mainWindow.webContents.send('main:run-request-event', { type: 'test-results-post-response', @@ -937,6 +984,8 @@ const registerNetworkIpc = (mainWindow) => { } } + testResults = appendScriptErrorResult('test', testResults, testError); + !runInBackground && mainWindow.webContents.send('main:run-request-event', { type: 'test-results', results: testResults.results, @@ -1308,6 +1357,12 @@ const registerNetworkIpc = (mainWindow) => { preRequestError = error; } + if (preRequestError?.partialResults) { + preRequestScriptResult = preRequestError.partialResults; + } + + preRequestScriptResult = appendScriptErrorResult('pre-request', preRequestScriptResult, preRequestError); + if (preRequestScriptResult?.results) { mainWindow.webContents.send('main:run-folder-event', { type: 'test-results-pre-request', @@ -1530,6 +1585,14 @@ const registerNetworkIpc = (mainWindow) => { postResponseError = error; } + // Extract partial results from error if available + // (e.g., if 2 tests pass then script throws, we still want to show those 2 passing tests) + if (postResponseError?.partialResults) { + postResponseScriptResult = postResponseError.partialResults; + } + + postResponseScriptResult = appendScriptErrorResult('post-response', postResponseScriptResult, postResponseError); + notifyScriptExecution({ channel: 'main:run-folder-event', basePayload: eventData, @@ -1616,6 +1679,8 @@ const registerNetworkIpc = (mainWindow) => { } } + testResults = appendScriptErrorResult('test', testResults, testError); + if (testResults?.nextRequestName !== undefined) { nextRequestName = testResults.nextRequestName; } diff --git a/packages/bruno-js/src/runtime/script-runtime.js b/packages/bruno-js/src/runtime/script-runtime.js index 10777947d..dc3cecada 100644 --- a/packages/bruno-js/src/runtime/script-runtime.js +++ b/packages/bruno-js/src/runtime/script-runtime.js @@ -67,35 +67,9 @@ class ScriptRuntime { context.bru.runRequest = runRequestByItemPathname; } - if (this.runtime === 'nodevm') { - await runScriptInNodeVm({ - script, - context, - collectionPath, - scriptingConfig - }); - - return { - request, - envVariables: cleanJson(envVariables), - runtimeVariables: cleanJson(runtimeVariables), - persistentEnvVariables: bru.persistentEnvVariables, - globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), - results: cleanJson(__brunoTestResults.getResults()), - nextRequestName: bru.nextRequest, - skipRequest: bru.skipRequest, - stopExecution: bru.stopExecution - }; - } - - // default runtime is `quickjs` - await executeQuickJsVmAsync({ - script: script, - context: context, - collectionPath - }); - - return { + // Helper to build the result object for pre-request scripts + // Extracted to avoid duplication across runtime branches + const buildRequestScriptResult = () => ({ request, envVariables: cleanJson(envVariables), runtimeVariables: cleanJson(runtimeVariables), @@ -105,7 +79,52 @@ class ScriptRuntime { nextRequestName: bru.nextRequest, skipRequest: bru.skipRequest, stopExecution: bru.stopExecution - }; + }); + + // Track script errors to attach partial results before re-throwing + // This ensures that any test() calls that passed before the error are preserved + // Similar pattern to test-runtime.js which already handles this correctly + let scriptError = null; + + if (this.runtime === 'nodevm') { + try { + await runScriptInNodeVm({ + script, + context, + collectionPath, + scriptingConfig + }); + } catch (error) { + scriptError = error; + } + + // If script errored, attach partial results so callers can display passed tests + // before the error occurred (e.g., 2 tests pass, then script throws) + if (scriptError) { + scriptError.partialResults = buildRequestScriptResult(); + throw scriptError; + } + + return buildRequestScriptResult(); + } + + // default runtime is `quickjs` + try { + await executeQuickJsVmAsync({ + script: script, + context: context, + collectionPath + }); + } catch (error) { + scriptError = error; + } + + if (scriptError) { + scriptError.partialResults = buildRequestScriptResult(); + throw scriptError; + } + + return buildRequestScriptResult(); } async runResponseScript( @@ -164,35 +183,9 @@ class ScriptRuntime { context.bru.runRequest = runRequestByItemPathname; } - if (this.runtime === 'nodevm') { - await runScriptInNodeVm({ - script, - context, - collectionPath, - scriptingConfig - }); - - return { - response, - envVariables: cleanJson(envVariables), - persistentEnvVariables: cleanJson(bru.persistentEnvVariables), - runtimeVariables: cleanJson(runtimeVariables), - globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), - results: cleanJson(__brunoTestResults.getResults()), - nextRequestName: bru.nextRequest, - skipRequest: bru.skipRequest, - stopExecution: bru.stopExecution - }; - } - - // default runtime is `quickjs` - await executeQuickJsVmAsync({ - script: script, - context: context, - collectionPath - }); - - return { + // Helper to build the result object for post-response scripts + // Extracted to avoid duplication across runtime branches + const buildResponseScriptResult = () => ({ response, envVariables: cleanJson(envVariables), persistentEnvVariables: cleanJson(bru.persistentEnvVariables), @@ -202,7 +195,52 @@ class ScriptRuntime { nextRequestName: bru.nextRequest, skipRequest: bru.skipRequest, stopExecution: bru.stopExecution - }; + }); + + // Track script errors to attach partial results before re-throwing + // This ensures that any test() calls that passed before the error are preserved + // Similar pattern to test-runtime.js which already handles this correctly + let scriptError = null; + + if (this.runtime === 'nodevm') { + try { + await runScriptInNodeVm({ + script, + context, + collectionPath, + scriptingConfig + }); + } catch (error) { + scriptError = error; + } + + // If script errored, attach partial results so callers can display passed tests + // before the error occurred (e.g., 2 tests pass, then script throws) + if (scriptError) { + scriptError.partialResults = buildResponseScriptResult(); + throw scriptError; + } + + return buildResponseScriptResult(); + } + + // default runtime is `quickjs` + try { + await executeQuickJsVmAsync({ + script: script, + context: context, + collectionPath + }); + } catch (error) { + scriptError = error; + } + + if (scriptError) { + scriptError.partialResults = buildResponseScriptResult(); + throw scriptError; + } + + return buildResponseScriptResult(); } }