diff --git a/packages/bruno-app/src/components/CollectionSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/index.js index a4d011be3..415ff4bfa 100644 --- a/packages/bruno-app/src/components/CollectionSettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/index.js @@ -15,17 +15,9 @@ import Test from './Tests'; import Presets from './Presets'; import StyledWrapper from './StyledWrapper'; import Vars from './Vars/index'; -import DotIcon from 'components/Icons/Dot'; +import StatusDot from 'components/StatusDot'; import Overview from './Overview/index'; -const ContentIndicator = () => { - return ( - - - - ); -}; - const CollectionSettings = ({ collection }) => { const dispatch = useDispatch(); const tab = collection.settingsSelectedTab; @@ -155,26 +147,26 @@ const CollectionSettings = ({ collection }) => {
setTab('auth')}> Auth - {authMode !== 'none' && } + {authMode !== 'none' && }
setTab('script')}> Script - {hasScripts && } + {hasScripts && }
setTab('tests')}> Tests - {hasTests && } + {hasTests && }
setTab('presets')}> Presets
setTab('proxy')}> Proxy - {Object.keys(proxyConfig).length > 0 && } + {Object.keys(proxyConfig).length > 0 && }
setTab('clientCert')}> Client Certificates - {clientCertConfig.length > 0 && } + {clientCertConfig.length > 0 && }
{getTabPanel(tab)}
diff --git a/packages/bruno-app/src/components/FolderSettings/index.js b/packages/bruno-app/src/components/FolderSettings/index.js index 621ae6815..063747807 100644 --- a/packages/bruno-app/src/components/FolderSettings/index.js +++ b/packages/bruno-app/src/components/FolderSettings/index.js @@ -9,17 +9,9 @@ import StyledWrapper from './StyledWrapper'; import Vars from './Vars'; import Documentation from './Documentation'; import Auth from './Auth'; -import DotIcon from 'components/Icons/Dot'; +import StatusDot from 'components/StatusDot'; import get from 'lodash/get'; -const ContentIndicator = () => { - return ( - - - - ); -}; - const FolderSettings = ({ collection, folder }) => { const dispatch = useDispatch(); let tab = 'headers'; @@ -91,11 +83,11 @@ const FolderSettings = ({ collection, folder }) => {
setTab('script')}> Script - {hasScripts && } + {hasScripts && }
setTab('test')}> Test - {hasTests && } + {hasTests && }
setTab('vars')}> Vars @@ -103,7 +95,7 @@ const FolderSettings = ({ collection, folder }) => {
setTab('auth')}> Auth - {hasAuth && } + {hasAuth && }
setTab('docs')}> Docs diff --git a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js index 126613504..6be64a43c 100644 --- a/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js +++ b/packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js @@ -7,7 +7,6 @@ import RequestHeaders from 'components/RequestPane/RequestHeaders'; import RequestBody from 'components/RequestPane/RequestBody'; import RequestBodyMode from 'components/RequestPane/RequestBody/RequestBodyMode'; import Auth from 'components/RequestPane/Auth'; -import DotIcon from 'components/Icons/Dot'; import Vars from 'components/RequestPane/Vars'; import Assertions from 'components/RequestPane/Assertions'; import Script from 'components/RequestPane/Script'; @@ -17,22 +16,7 @@ import { find, get } from 'lodash'; import Documentation from 'components/Documentation/index'; import HeightBoundContainer from 'ui/HeightBoundContainer'; import { useEffect } from 'react'; - -const ContentIndicator = () => { - return ( - - - - ); -}; - -const ErrorIndicator = () => { - return ( - - - - ); -}; +import StatusDot from 'components/StatusDot'; const HttpRequestPane = ({ item, collection }) => { const dispatch = useDispatch(); @@ -136,7 +120,7 @@ const HttpRequestPane = ({ item, collection }) => {
selectTab('body')}> Body - {body.mode !== 'none' && } + {body.mode !== 'none' && }
selectTab('headers')}> Headers @@ -144,7 +128,7 @@ const HttpRequestPane = ({ item, collection }) => {
selectTab('auth')}> Auth - {auth.mode !== 'none' && } + {auth.mode !== 'none' && }
selectTab('vars')}> Vars @@ -153,9 +137,9 @@ const HttpRequestPane = ({ item, collection }) => {
selectTab('script')}> Script {(script.req || script.res) && ( - item.preRequestScriptErrorMessage || item.postResponseScriptErrorMessage ? - : - + item.preRequestScriptErrorMessage || item.postResponseScriptErrorMessage ? + : + )}
selectTab('assert')}> @@ -164,11 +148,15 @@ const HttpRequestPane = ({ item, collection }) => {
selectTab('tests')}> Tests - {tests && tests.length > 0 && } + {tests && tests.length > 0 && ( + item.testScriptErrorMessage ? + : + + )}
selectTab('docs')}> Docs - {docs && docs.length > 0 && } + {docs && docs.length > 0 && }
{focusedTab.requestPaneTab === 'body' ? (
diff --git a/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js b/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js index 21f02406e..76a65d0cd 100644 --- a/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js +++ b/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import get from 'lodash/get'; import classnames from 'classnames'; import { safeStringifyJSON } from 'utils/common'; @@ -12,17 +12,28 @@ import TestResultsLabel from 'components/ResponsePane/TestResultsLabel'; import StyledWrapper from './StyledWrapper'; import SkippedRequest from 'components/ResponsePane/SkippedRequest'; import RunnerTimeline from 'components/ResponsePane/RunnerTimeline'; +import ScriptError from 'components/ResponsePane/ScriptError'; +import ScriptErrorIcon from 'components/ResponsePane/ScriptErrorIcon'; const ResponsePane = ({ rightPaneWidth, item, collection }) => { const [selectedTab, setSelectedTab] = useState('response'); + const [showScriptErrorCard, setShowScriptErrorCard] = useState(false); const { requestSent, responseReceived, testResults, assertionResults, preRequestTestResults, postResponseTestResults, error } = item; + useEffect(() => { + if (item?.preRequestScriptErrorMessage || item?.postResponseScriptErrorMessage || item?.testScriptErrorMessage) { + setShowScriptErrorCard(true); + } + }, [item?.preRequestScriptErrorMessage, item?.postResponseScriptErrorMessage, item?.testScriptErrorMessage]); + const headers = get(item, 'responseReceived.headers', []); const status = get(item, 'responseReceived.status', 0); const size = get(item, 'responseReceived.size', 0); const duration = get(item, 'responseReceived.duration', 0); + const hasScriptError = item?.preRequestScriptErrorMessage || item?.postResponseScriptErrorMessage || item?.testScriptErrorMessage; + const selectTab = (tab) => setSelectedTab(tab); const getTabPanel = (tab) => { @@ -99,12 +110,28 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => { />
+ {hasScriptError && !showScriptErrorCard && ( + setShowScriptErrorCard(true)} + /> + )}
-
{getTabPanel(selectedTab)}
+
+ {hasScriptError && showScriptErrorCard && ( + setShowScriptErrorCard(false)} + /> + )} +
+ {getTabPanel(selectedTab)} +
+
); }; diff --git a/packages/bruno-app/src/components/StatusDot/index.js b/packages/bruno-app/src/components/StatusDot/index.js new file mode 100644 index 000000000..d5c090efe --- /dev/null +++ b/packages/bruno-app/src/components/StatusDot/index.js @@ -0,0 +1,15 @@ +import React from 'react'; +import DotIcon from 'components/Icons/Dot'; + +const StatusDot = ({ type = 'default' }) => ( + + + +); + + +export default StatusDot; \ No newline at end of file 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 9139ec599..9e2a8a907 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -2033,6 +2033,9 @@ export const collectionsSlice = createSlice({ item.requestState = null; item.requestUid = requestUid; item.requestStartTime = Date.now(); + item.preRequestScriptErrorMessage = null; + item.postResponseScriptErrorMessage = null; + item.testScriptErrorMessage = null; }, runRequestEvent: (state, action) => { const { itemUid, collectionUid, type, requestUid } = action.payload; @@ -2177,6 +2180,21 @@ export const collectionsSlice = createSlice({ item.status = 'skipped'; item.responseReceived = action.payload.responseReceived; } + + if (type === 'post-response-script-execution') { + const item = collection.runnerResult.items.findLast((i) => i.uid === request.uid); + item.postResponseScriptErrorMessage = action.payload.errorMessage; + } + + if (type === 'test-script-execution') { + const item = collection.runnerResult.items.findLast((i) => i.uid === request.uid); + item.testScriptErrorMessage = action.payload.errorMessage; + } + + if (type === 'pre-request-script-execution') { + const item = collection.runnerResult.items.findLast((i) => i.uid === request.uid); + item.preRequestScriptErrorMessage = action.payload.errorMessage; + } } }, resetCollectionRunner: (state, action) => { diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index 50aaf823b..a7a0c3ca3 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -495,29 +495,33 @@ const runSingleRequest = async function ( const responseScriptFile = get(request, 'script.res'); if (responseScriptFile?.length) { const scriptRuntime = new ScriptRuntime({ runtime: scriptingConfig?.runtime }); - const result = await scriptRuntime.runResponseScript( - decomment(responseScriptFile), - request, - response, - envVariables, - runtimeVariables, - collectionPath, - null, - processEnvVars, - scriptingConfig, - runSingleRequestByPathname, - collectionName - ); - if (result?.nextRequestName !== undefined) { - nextRequestName = result.nextRequestName; - } + try { + const result = await scriptRuntime.runResponseScript( + decomment(responseScriptFile), + request, + response, + envVariables, + runtimeVariables, + collectionPath, + null, + processEnvVars, + scriptingConfig, + runSingleRequestByPathname, + collectionName + ); + if (result?.nextRequestName !== undefined) { + nextRequestName = result.nextRequestName; + } - if (result?.stopExecution) { - shouldStopRunnerExecution = true; - } + if (result?.stopExecution) { + shouldStopRunnerExecution = true; + } - postResponseTestResults = result?.results || []; - logResults(postResponseTestResults, 'Post-Response Tests'); + postResponseTestResults = result?.results || []; + logResults(postResponseTestResults, 'Post-Response Tests'); + } catch (error) { + console.error('Post-response script execution error:', error); + } } let assertionResults = []; @@ -539,30 +543,34 @@ const runSingleRequest = async function ( const testFile = get(request, 'tests'); if (typeof testFile === 'string') { const testRuntime = new TestRuntime({ runtime: scriptingConfig?.runtime }); - const result = await testRuntime.runTests( - decomment(testFile), - request, - response, - envVariables, - runtimeVariables, - collectionPath, - null, - processEnvVars, - scriptingConfig, - runSingleRequestByPathname, - collectionName - ); - testResults = get(result, 'results', []); + try { + const result = await testRuntime.runTests( + decomment(testFile), + request, + response, + envVariables, + runtimeVariables, + collectionPath, + null, + processEnvVars, + scriptingConfig, + runSingleRequestByPathname, + collectionName + ); + testResults = get(result, 'results', []); - if (result?.nextRequestName !== undefined) { - nextRequestName = result.nextRequestName; + if (result?.nextRequestName !== undefined) { + nextRequestName = result.nextRequestName; + } + + if (result?.stopExecution) { + shouldStopRunnerExecution = true; + } + + logResults(testResults, 'Tests'); + } catch (error) { + console.error('Test script execution error:', error); } - - if (result?.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 aa70ab8c6..a2fbef705 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -398,6 +398,19 @@ const registerNetworkIpc = (mainWindow) => { }); }; + const notifyScriptExecution = ({ + channel, // 'main:run-request-event' | 'main:run-folder-event' + basePayload, // request-level or runner-level identifiers + scriptType, // 'pre-request' | 'post-response' | 'test' + error // optional Error + }) => { + mainWindow.webContents.send(channel, { + type: `${scriptType}-script-execution`, + ...basePayload, + errorMessage: error ? (error.message || `An error occurred in ${scriptType.replace('-', ' ')} script`) : null + }); + }; + const runPreRequest = async ( request, requestUid, @@ -594,9 +607,10 @@ const registerNetworkIpc = (mainWindow) => { request.signal = abortController.signal; saveCancelToken(cancelTokenUid, abortController); - + let preRequestScriptResult = null; + let preRequestError = null; try { - const preRequestScriptResult = await runPreRequest( + preRequestScriptResult = await runPreRequest( request, requestUid, envVars, @@ -608,34 +622,29 @@ const registerNetworkIpc = (mainWindow) => { scriptingConfig, 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, - collectionUid, - itemUid: item.uid, - errorMessage: null, - }); - } catch (error) { - !runInBackground && mainWindow.webContents.send('main:run-request-event', { - type: 'pre-request-script-execution', - requestUid, - collectionUid, + preRequestError = error; + } + + if (preRequestScriptResult?.results) { + mainWindow.webContents.send('main:run-request-event', { + type: 'test-results-pre-request', + results: preRequestScriptResult.results, itemUid: item.uid, - errorMessage: error?.message || 'An error occurred in pre-request script', + requestUid, + collectionUid }); - return Promise.reject(error); + } + + !runInBackground && notifyScriptExecution({ + channel: 'main:run-request-event', + basePayload: { requestUid, collectionUid, itemUid: item.uid }, + scriptType: 'pre-request', + error: preRequestError + }); + + if (preRequestError) { + return Promise.reject(preRequestError); } const axiosInstance = await configureRequest( collectionUid, @@ -734,8 +743,10 @@ const registerNetworkIpc = (mainWindow) => { mainWindow.webContents.send('main:cookies-update', safeParseJSON(safeStringifyJSON(domainsWithCookies))); + let postResponseScriptResult = null; + let postResponseError = null; try { - const postResponseScriptResult = await runPostResponse( + postResponseScriptResult = await runPostResponse( request, response, requestUid, @@ -748,38 +759,28 @@ 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, - collectionUid, - errorMessage: null, - itemUid: item.uid, - }); } catch (error) { console.error('Post-response script error:', error); - - // Format a more readable error message - const errorMessage = error?.message || 'An error occurred in post-response script'; - - !runInBackground && mainWindow.webContents.send('main:run-request-event', { - type: 'post-response-script-execution', - requestUid, - errorMessage, - collectionUid, + postResponseError = error; + } + + if (postResponseScriptResult?.results) { + mainWindow.webContents.send('main:run-request-event', { + type: 'test-results-post-response', + results: postResponseScriptResult.results, itemUid: item.uid, + requestUid, + collectionUid }); } + !runInBackground && notifyScriptExecution({ + channel: 'main:run-request-event', + basePayload: { requestUid, collectionUid, itemUid: item.uid }, + scriptType: 'post-response', + error: postResponseError + }); + // run assertions const assertions = get(request, 'assertions'); if (assertions) { @@ -861,19 +862,12 @@ 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); + !runInBackground && notifyScriptExecution({ + channel: 'main:run-request-event', + basePayload: { requestUid, collectionUid, itemUid: item.uid }, + scriptType: 'test', + error: testError + }); } return { @@ -1032,18 +1026,44 @@ const registerNetworkIpc = (mainWindow) => { const requestUid = uuid(); try { - const preRequestScriptResult = await runPreRequest( - request, - requestUid, - envVars, - collectionPath, - collection, - collectionUid, - runtimeVariables, - processEnvVars, - scriptingConfig, - runRequestByItemPathname - ); + let preRequestScriptResult; + let preRequestError = null; + try { + preRequestScriptResult = await runPreRequest( + request, + requestUid, + envVars, + collectionPath, + collection, + collectionUid, + runtimeVariables, + processEnvVars, + scriptingConfig, + runRequestByItemPathname + ); + } catch (error) { + console.error('Pre-request script error:', error); + preRequestError = error; + } + + if (preRequestScriptResult?.results) { + mainWindow.webContents.send('main:run-folder-event', { + type: 'test-results-pre-request', + preRequestTestResults: preRequestScriptResult.results, + ...eventData + }); + } + + notifyScriptExecution({ + channel: 'main:run-folder-event', + basePayload: eventData, + scriptType: 'pre-request', + error: preRequestError + }); + + if (preRequestError) { + throw preRequestError; + } if (preRequestScriptResult?.nextRequestName !== undefined) { nextRequestName = preRequestScriptResult.nextRequestName; @@ -1053,15 +1073,6 @@ const registerNetworkIpc = (mainWindow) => { stopRunnerExecution = true; } - // Send pre-request test results if available - if (preRequestScriptResult?.results) { - mainWindow.webContents.send('main:run-folder-event', { - type: 'test-results-pre-request', - preRequestTestResults: preRequestScriptResult.results, - ...eventData - }); - } - if (preRequestScriptResult?.skipRequest) { mainWindow.webContents.send('main:run-folder-event', { type: 'runner-request-skipped', @@ -1200,19 +1211,33 @@ const registerNetworkIpc = (mainWindow) => { } } - const postResponseScriptResult = await runPostResponse( - request, - response, - requestUid, - envVars, - collectionPath, - collection, - collectionUid, - runtimeVariables, - processEnvVars, - scriptingConfig, - runRequestByItemPathname - ); + let postResponseScriptResult; + let postResponseError = null; + try { + postResponseScriptResult = await runPostResponse( + request, + response, + requestUid, + envVars, + collectionPath, + collection, + collectionUid, + runtimeVariables, + processEnvVars, + scriptingConfig, + runRequestByItemPathname + ); + } catch (error) { + console.error('Post-response script error:', error); + postResponseError = error; + } + + notifyScriptExecution({ + channel: 'main:run-folder-event', + basePayload: eventData, + scriptType: 'post-response', + error: postResponseError + }); if (postResponseScriptResult?.nextRequestName !== undefined) { nextRequestName = postResponseScriptResult.nextRequestName; @@ -1255,9 +1280,12 @@ const registerNetworkIpc = (mainWindow) => { const testFile = get(request, 'tests'); const collectionName = collection?.name if (typeof testFile === 'string') { + let testResults = null; + let testError = null; + try { const testRuntime = new TestRuntime({ runtime: scriptingConfig?.runtime }); - const testResults = await testRuntime.runTests( + testResults = await testRuntime.runTests( decomment(testFile), request, response, @@ -1270,52 +1298,51 @@ const registerNetworkIpc = (mainWindow) => { runRequestByItemPathname, collectionName ); - - 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 - }); + } catch (error) { + testError = error; - 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; + if (error.partialResults) { + testResults = error.partialResults; + } else { + testResults = { + request, + envVariables: envVars, + runtimeVariables, + globalEnvironmentVariables: request?.globalEnvironmentVariables || {}, + results: [], + nextRequestName: null + }; } } + + 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; + + notifyScriptExecution({ + channel: 'main:run-folder-event', + basePayload: eventData, + scriptType: 'test', + error: testError + }); } } catch (error) { mainWindow.webContents.send('main:run-folder-event', { diff --git a/packages/bruno-js/src/runtime/test-runtime.js b/packages/bruno-js/src/runtime/test-runtime.js index 163ca9aad..2088a5cb7 100644 --- a/packages/bruno-js/src/runtime/test-runtime.js +++ b/packages/bruno-js/src/runtime/test-runtime.js @@ -177,7 +177,6 @@ class TestRuntime { } } catch (error) { scriptError = error; - console.error('Test script execution error:', error); } const result = {