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 <temporaryg7904@gmail.com>
This commit is contained in:
Karan Pradhan
2026-02-02 03:19:19 -08:00
committed by GitHub
parent 679cb91549
commit cf3ce0f450
3 changed files with 273 additions and 105 deletions

View File

@@ -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');
}
}

View File

@@ -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;
}

View File

@@ -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();
}
}