mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-24 05:05:39 +00:00
Fix/runner results enhancement (#7040)
* 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> * Enhance error handling for script execution by introducing isScriptError flag in test results (#7029) * fix: Enhance error handling for script execution by introducing isScriptError flag in test results Enhance error reporting in script execution by adding isScriptError flag to error responses fix: Mark pre-request script errors as failures in runner summary --------- Co-authored-by: Karan Pradhan <78605930+KaranPradhan266@users.noreply.github.com> Co-authored-by: Pragadesh-45 <temporaryg7904@gmail.com>
This commit is contained in:
@@ -206,27 +206,75 @@ 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) {
|
||||
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',
|
||||
skipped: true,
|
||||
assertionResults: [],
|
||||
testResults: [],
|
||||
preRequestTestResults: result?.results || [],
|
||||
postResponseTestResults: [],
|
||||
shouldStopRunnerExecution
|
||||
};
|
||||
}
|
||||
|
||||
preRequestTestResults = result?.results || [];
|
||||
} catch (error) {
|
||||
// Pre-request errors are treated as request errors (we return early with status: 'error'), not as failures. Unlike post-response and test script errors, we do not add a synthetic fail and continue.
|
||||
console.error('Pre-request script execution error:', error);
|
||||
console.log(chalk.red(stripExtension(relativeItemPathname)) + chalk.dim(` (${error.message})`));
|
||||
|
||||
// Extract partial results from the error (tests that passed before the error)
|
||||
preRequestTestResults = error?.partialResults?.results || [];
|
||||
|
||||
// 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');
|
||||
|
||||
// Pre-request script error: execution didn't complete (request never sent). Return early so we don't run the HTTP request, post-response script, assertions, or tests.
|
||||
return {
|
||||
test: {
|
||||
filename: relativeItemPathname
|
||||
@@ -238,23 +286,23 @@ const runSingleRequest = async function (
|
||||
data: request.data
|
||||
},
|
||||
response: {
|
||||
status: 'skipped',
|
||||
statusText: 'request skipped via pre-request script',
|
||||
status: 'error',
|
||||
statusText: null,
|
||||
headers: null,
|
||||
data: null,
|
||||
url: null,
|
||||
responseTime: 0
|
||||
},
|
||||
error: null,
|
||||
status: 'skipped',
|
||||
skipped: true,
|
||||
error: error?.message || 'An error occurred while executing the pre-request script.',
|
||||
status: 'error',
|
||||
assertionResults: [],
|
||||
testResults: [],
|
||||
preRequestTestResults: result?.results || [],
|
||||
preRequestTestResults,
|
||||
postResponseTestResults: [],
|
||||
nextRequestName: nextRequestName,
|
||||
shouldStopRunnerExecution
|
||||
};
|
||||
}
|
||||
|
||||
preRequestTestResults = result?.results || [];
|
||||
}
|
||||
|
||||
// interpolate variables inside request
|
||||
@@ -732,6 +780,27 @@ 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.',
|
||||
isScriptError: true
|
||||
}
|
||||
];
|
||||
|
||||
if (error?.partialResults?.nextRequestName !== undefined) {
|
||||
nextRequestName = error.partialResults.nextRequestName;
|
||||
}
|
||||
|
||||
if (error?.partialResults?.stopExecution) {
|
||||
shouldStopRunnerExecution = true;
|
||||
}
|
||||
|
||||
logResults(postResponseTestResults, 'Post-Response Tests');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -781,6 +850,27 @@ 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.',
|
||||
isScriptError: true
|
||||
}
|
||||
];
|
||||
|
||||
if (error?.partialResults?.nextRequestName !== undefined) {
|
||||
nextRequestName = error.partialResults.nextRequestName;
|
||||
}
|
||||
|
||||
if (error?.partialResults?.stopExecution) {
|
||||
shouldStopRunnerExecution = true;
|
||||
}
|
||||
|
||||
logResults(testResults, 'Tests');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@ export const getRunnerSummary = (results: T_RunnerRequestExecutionResult[]): T_R
|
||||
for (const result of results || []) {
|
||||
const { status, testResults, assertionResults, preRequestTestResults, postResponseTestResults } = result;
|
||||
totalRequests += 1;
|
||||
totalTests += Number(testResults?.length) || 0;
|
||||
totalTests += Number(testResults?.filter((r) => !r.isScriptError).length) || 0;
|
||||
totalAssertions += Number(assertionResults?.length) || 0;
|
||||
totalPreRequestTests += Number(preRequestTestResults?.length) || 0;
|
||||
totalPostResponseTests += Number(postResponseTestResults?.length) || 0;
|
||||
totalPreRequestTests += Number(preRequestTestResults?.filter((r) => !r.isScriptError).length) || 0;
|
||||
totalPostResponseTests += Number(postResponseTestResults?.filter((r) => !r.isScriptError).length) || 0;
|
||||
|
||||
if (status === 'skipped') {
|
||||
skippedRequests += 1;
|
||||
@@ -35,6 +35,10 @@ export const getRunnerSummary = (results: T_RunnerRequestExecutionResult[]): T_R
|
||||
|
||||
let anyFailed = false;
|
||||
for (const testResult of testResults || []) {
|
||||
if (testResult.isScriptError) {
|
||||
anyFailed = true;
|
||||
continue;
|
||||
}
|
||||
if (testResult.status === 'pass') {
|
||||
passedTests += 1;
|
||||
} else {
|
||||
@@ -51,6 +55,10 @@ export const getRunnerSummary = (results: T_RunnerRequestExecutionResult[]): T_R
|
||||
}
|
||||
}
|
||||
for (const preRequestTestResult of preRequestTestResults || []) {
|
||||
if (preRequestTestResult.isScriptError) {
|
||||
anyFailed = true;
|
||||
continue;
|
||||
}
|
||||
if (preRequestTestResult.status === 'pass') {
|
||||
passedPreRequestTests += 1;
|
||||
} else {
|
||||
@@ -59,6 +67,10 @@ export const getRunnerSummary = (results: T_RunnerRequestExecutionResult[]): T_R
|
||||
}
|
||||
}
|
||||
for (const postResponseTestResult of postResponseTestResults || []) {
|
||||
if (postResponseTestResult.isScriptError) {
|
||||
anyFailed = true;
|
||||
continue;
|
||||
}
|
||||
if (postResponseTestResult.status === 'pass') {
|
||||
passedPostResponseTests += 1;
|
||||
} else {
|
||||
|
||||
@@ -23,6 +23,7 @@ type T_TestPassResult = {
|
||||
status: string;
|
||||
description: string;
|
||||
uid?: string;
|
||||
isScriptError?: boolean;
|
||||
};
|
||||
|
||||
type T_TestFailResult = {
|
||||
@@ -30,6 +31,7 @@ type T_TestFailResult = {
|
||||
description: string;
|
||||
error: string;
|
||||
uid?: string;
|
||||
isScriptError?: boolean;
|
||||
};
|
||||
|
||||
type T_TestResult = T_TestPassResult | T_TestFailResult;
|
||||
|
||||
@@ -443,6 +443,39 @@ 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.',
|
||||
isScriptError: true
|
||||
}
|
||||
];
|
||||
|
||||
return {
|
||||
...(scriptResult || {}),
|
||||
results
|
||||
};
|
||||
};
|
||||
|
||||
const runPreRequest = async (
|
||||
request,
|
||||
requestUid,
|
||||
@@ -713,6 +746,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',
|
||||
@@ -878,6 +917,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',
|
||||
@@ -951,6 +999,8 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
}
|
||||
}
|
||||
|
||||
testResults = appendScriptErrorResult('test', testResults, testError);
|
||||
|
||||
!runInBackground && mainWindow.webContents.send('main:run-request-event', {
|
||||
type: 'test-results',
|
||||
results: testResults.results,
|
||||
@@ -1336,6 +1386,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',
|
||||
@@ -1558,6 +1614,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,
|
||||
@@ -1644,6 +1708,8 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
}
|
||||
}
|
||||
|
||||
testResults = appendScriptErrorResult('test', testResults, testError);
|
||||
|
||||
if (testResults?.nextRequestName !== undefined) {
|
||||
nextRequestName = testResults.nextRequestName;
|
||||
}
|
||||
|
||||
@@ -68,35 +68,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),
|
||||
@@ -106,7 +80,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(
|
||||
@@ -166,35 +185,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),
|
||||
@@ -204,7 +197,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user