Fix: showing test script errors (#4902)

* fix: catch errors in tests
This commit is contained in:
naman-bruno
2025-06-14 22:20:24 +05:30
committed by GitHub
parent f03047a2f9
commit f2aedf780d
5 changed files with 227 additions and 118 deletions

View File

@@ -6,22 +6,48 @@ import StyledWrapper from './StyledWrapper';
const ScriptError = ({ item, onClose }) => {
const preRequestError = item?.preRequestScriptErrorMessage;
const postResponseError = item?.postResponseScriptErrorMessage;
const testScriptError = item?.testScriptErrorMessage;
if (!preRequestError && !postResponseError) return null;
if (!preRequestError && !postResponseError && !testScriptError) return null;
const errorMessage = preRequestError || postResponseError;
const errorTitle = preRequestError ? 'Pre-Request Script Error' : 'Post-Response Script Error';
const errors = [];
if (preRequestError) {
errors.push({
title: 'Pre-Request Script Error',
message: preRequestError
});
}
if (postResponseError) {
errors.push({
title: 'Post-Response Script Error',
message: postResponseError
});
}
if (testScriptError) {
errors.push({
title: 'Test Script Error',
message: testScriptError
});
}
return (
<StyledWrapper className="mt-4 mb-2">
<div className="flex items-start gap-3 px-4 py-3">
<div className="flex-1 min-w-0">
<div className="error-title">
{errorTitle}
</div>
<div className="error-message">
{errorMessage}
</div>
{errors.map((error, index) => (
<div key={index}>
{index > 0 && <div className="border-t border-gray-300 my-3 dark:border-gray-600"></div>}
<div className="error-title">
{error.title}
</div>
<div className="error-message">
{error.message}
</div>
</div>
))}
</div>
<div
className="close-button flex-shrink-0 cursor-pointer"

View File

@@ -33,10 +33,10 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
});
useEffect(() => {
if (item?.preRequestScriptErrorMessage || item?.postResponseScriptErrorMessage) {
if (item?.preRequestScriptErrorMessage || item?.postResponseScriptErrorMessage || item?.testScriptErrorMessage) {
setShowScriptErrorCard(true);
}
}, [item?.preRequestScriptErrorMessage, item?.postResponseScriptErrorMessage]);
}, [item?.preRequestScriptErrorMessage, item?.postResponseScriptErrorMessage, item?.testScriptErrorMessage]);
const selectTab = (tab) => {
dispatch(
@@ -128,7 +128,7 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
const responseHeadersCount = typeof response.headers === 'object' ? Object.entries(response.headers).length : 0;
const hasScriptError = item?.preRequestScriptErrorMessage || item?.postResponseScriptErrorMessage;
const hasScriptError = item?.preRequestScriptErrorMessage || item?.postResponseScriptErrorMessage || item?.testScriptErrorMessage;
return (
<StyledWrapper className="flex flex-col h-full relative">
@@ -174,7 +174,11 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
) : null}
</div>
<section
className={`flex flex-col flex-grow relative pl-3 pr-4 ${focusedTab.responsePaneTab === 'response' ? '' : 'mt-4'}`}
className={`flex flex-col min-h-0 relative pl-3 pr-4 auto`}
style={{
flex: '1 1 0',
height: hasScriptError && showScriptErrorCard ? 'auto' : '100%'
}}
>
{isLoading ? <Overlay item={item} collection={collection} /> : null}
{hasScriptError && showScriptErrorCard && (
@@ -183,17 +187,19 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
onClose={() => setShowScriptErrorCard(false)}
/>
)}
{!item?.response ? (
focusedTab?.responsePaneTab === "timeline" && requestTimeline?.length ? (
<Timeline
collection={collection}
item={item}
width={rightPaneWidth}
/>
) : null
) : (
<>{getTabPanel(focusedTab.responsePaneTab)}</>
)}
<div className='flex-1 overflow-hidden min-h-[200px]'>
{!item?.response ? (
focusedTab?.responsePaneTab === "timeline" && requestTimeline?.length ? (
<Timeline
collection={collection}
item={item}
width={rightPaneWidth}
/>
) : null
) : (
<>{getTabPanel(focusedTab.responsePaneTab)}</>
)}
</div>
</section>
</StyledWrapper>
);

View File

@@ -1987,6 +1987,10 @@ export const collectionsSlice = createSlice({
item.postResponseScriptErrorMessage = action.payload.errorMessage;
}
if(type === 'test-script-execution') {
item.testScriptErrorMessage = action.payload.errorMessage;
}
if (type === 'request-queued') {
const { cancelTokenUid } = action.payload;
// ignore if request is already in progress or completed

View File

@@ -805,19 +805,39 @@ const registerNetworkIpc = (mainWindow) => {
const collectionName = collection?.name
if (typeof testFile === 'string') {
const testRuntime = new TestRuntime({ runtime: scriptingConfig?.runtime });
const testResults = await testRuntime.runTests(
decomment(testFile),
request,
response,
envVars,
runtimeVariables,
collectionPath,
onConsoleLog,
processEnvVars,
scriptingConfig,
runRequestByItemPathname,
collectionName
);
let testResults = null;
let testError = null;
try {
testResults = await testRuntime.runTests(
decomment(testFile),
request,
response,
envVars,
runtimeVariables,
collectionPath,
onConsoleLog,
processEnvVars,
scriptingConfig,
runRequestByItemPathname,
collectionName
);
} catch (error) {
testError = error;
if (error.partialResults) {
testResults = error.partialResults;
} else {
testResults = {
request,
envVariables: envVars,
runtimeVariables,
globalEnvironmentVariables: request?.globalEnvironmentVariables || {},
results: [],
nextRequestName: null
};
}
}
!runInBackground && mainWindow.webContents.send('main:run-request-event', {
type: 'test-results',
@@ -839,6 +859,20 @@ const registerNetworkIpc = (mainWindow) => {
});
collection.globalEnvironmentVariables = testResults.globalEnvironmentVariables;
const testScriptExecutionEvent = {
type: 'test-script-execution',
requestUid,
collectionUid,
itemUid: item.uid,
errorMessage: null,
}
if (testError) {
const errorMessage = testError?.message || 'An error occurred in test script';
testScriptExecutionEvent.errorMessage = errorMessage;
}
!runInBackground && mainWindow.webContents.send('main:run-request-event', testScriptExecutionEvent);
}
return {
@@ -1213,42 +1247,67 @@ const registerNetworkIpc = (mainWindow) => {
const testFile = get(request, 'tests');
const collectionName = collection?.name
if (typeof testFile === 'string') {
const testRuntime = new TestRuntime({ runtime: scriptingConfig?.runtime });
const testResults = await testRuntime.runTests(
decomment(testFile),
request,
response,
envVars,
runtimeVariables,
collectionPath,
onConsoleLog,
processEnvVars,
scriptingConfig,
runRequestByItemPathname,
collectionName
);
try {
const testRuntime = new TestRuntime({ runtime: scriptingConfig?.runtime });
const testResults = await testRuntime.runTests(
decomment(testFile),
request,
response,
envVars,
runtimeVariables,
collectionPath,
onConsoleLog,
processEnvVars,
scriptingConfig,
runRequestByItemPathname,
collectionName
);
if (testResults?.nextRequestName !== undefined) {
nextRequestName = testResults.nextRequestName;
if (testResults?.nextRequestName !== undefined) {
nextRequestName = testResults.nextRequestName;
}
mainWindow.webContents.send('main:run-folder-event', {
type: 'test-results',
testResults: testResults.results,
...eventData
});
mainWindow.webContents.send('main:script-environment-update', {
envVariables: testResults.envVariables,
runtimeVariables: testResults.runtimeVariables,
collectionUid
});
mainWindow.webContents.send('main:global-environment-variables-update', {
globalEnvironmentVariables: testResults.globalEnvironmentVariables
});
collection.globalEnvironmentVariables = testResults.globalEnvironmentVariables;
} catch (testError) {
if (testError.partialResults && testError.partialResults.results.length > 0) {
// Send the partial test results
mainWindow.webContents.send('main:run-folder-event', {
type: 'test-results',
testResults: testError.partialResults.results,
...eventData
});
mainWindow.webContents.send('main:script-environment-update', {
envVariables: testError.partialResults.envVariables,
runtimeVariables: testError.partialResults.runtimeVariables,
collectionUid
});
mainWindow.webContents.send('main:global-environment-variables-update', {
globalEnvironmentVariables: testError.partialResults.globalEnvironmentVariables
});
collection.globalEnvironmentVariables = testError.partialResults.globalEnvironmentVariables;
}
}
mainWindow.webContents.send('main:run-folder-event', {
type: 'test-results',
testResults: testResults.results,
...eventData
});
mainWindow.webContents.send('main:script-environment-update', {
envVariables: testResults.envVariables,
runtimeVariables: testResults.runtimeVariables,
collectionUid
});
mainWindow.webContents.send('main:global-environment-variables-update', {
globalEnvironmentVariables: testResults.globalEnvironmentVariables
});
collection.globalEnvironmentVariables = testResults.globalEnvironmentVariables;
}
} catch (error) {
mainWindow.webContents.send('main:run-folder-event', {

View File

@@ -124,56 +124,63 @@ class TestRuntime {
context.bru.runRequest = runRequestByItemPathname;
}
if (this.runtime === 'quickjs') {
await executeQuickJsVmAsync({
script: testsFile,
context: context
});
} else {
// default runtime is vm2
const vm = new NodeVM({
sandbox: context,
require: {
context: 'sandbox',
external: true,
root: [collectionPath, ...additionalContextRootsAbsolute],
mock: {
// node libs
path,
stream,
util,
url,
http,
https,
punycode,
zlib,
// 3rd party libs
ajv,
'ajv-formats': addFormats,
btoa,
atob,
lodash,
moment,
uuid,
nanoid,
axios,
chai,
'node-fetch': fetch,
'crypto-js': CryptoJS,
'xml2js': xml2js,
cheerio,
tv4,
...whitelistedModules,
fs: allowScriptFilesystemAccess ? fs : undefined,
'node-vault': NodeVault
let scriptError = null;
try {
if (this.runtime === 'quickjs') {
await executeQuickJsVmAsync({
script: testsFile,
context: context
});
} else {
// default runtime is vm2
const vm = new NodeVM({
sandbox: context,
require: {
context: 'sandbox',
external: true,
root: [collectionPath, ...additionalContextRootsAbsolute],
mock: {
// node libs
path,
stream,
util,
url,
http,
https,
punycode,
zlib,
// 3rd party libs
ajv,
'ajv-formats': addFormats,
btoa,
atob,
lodash,
moment,
uuid,
nanoid,
axios,
chai,
'node-fetch': fetch,
'crypto-js': CryptoJS,
'xml2js': xml2js,
cheerio,
tv4,
...whitelistedModules,
fs: allowScriptFilesystemAccess ? fs : undefined,
'node-vault': NodeVault
}
}
}
});
const asyncVM = vm.run(`module.exports = async () => { ${testsFile}}`, path.join(collectionPath, 'vm.js'));
await asyncVM();
});
const asyncVM = vm.run(`module.exports = async () => { ${testsFile}}`, path.join(collectionPath, 'vm.js'));
await asyncVM();
}
} catch (error) {
scriptError = error;
console.error('Test script execution error:', error);
}
return {
const result = {
request,
envVariables: cleanJson(envVariables),
runtimeVariables: cleanJson(runtimeVariables),
@@ -181,6 +188,13 @@ class TestRuntime {
results: cleanJson(__brunoTestResults.getResults()),
nextRequestName: bru.nextRequest
};
if (scriptError) {
scriptError.partialResults = result;
throw scriptError;
}
return result;
}
}