Merge pull request #4984 from pooja-bruno/add/script-error-card-in-collection-runner

add: script error card in collection runner
This commit is contained in:
lohit
2025-07-04 18:51:36 +05:30
committed by GitHub
9 changed files with 307 additions and 241 deletions

View File

@@ -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 (
<sup className="ml-[.125rem] opacity-80 font-medium">
<DotIcon width="10"></DotIcon>
</sup>
);
};
const CollectionSettings = ({ collection }) => {
const dispatch = useDispatch();
const tab = collection.settingsSelectedTab;
@@ -155,26 +147,26 @@ const CollectionSettings = ({ collection }) => {
</div>
<div className={getTabClassname('auth')} role="tab" onClick={() => setTab('auth')}>
Auth
{authMode !== 'none' && <ContentIndicator />}
{authMode !== 'none' && <StatusDot />}
</div>
<div className={getTabClassname('script')} role="tab" onClick={() => setTab('script')}>
Script
{hasScripts && <ContentIndicator />}
{hasScripts && <StatusDot />}
</div>
<div className={getTabClassname('tests')} role="tab" onClick={() => setTab('tests')}>
Tests
{hasTests && <ContentIndicator />}
{hasTests && <StatusDot />}
</div>
<div className={getTabClassname('presets')} role="tab" onClick={() => setTab('presets')}>
Presets
</div>
<div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}>
Proxy
{Object.keys(proxyConfig).length > 0 && <ContentIndicator />}
{Object.keys(proxyConfig).length > 0 && <StatusDot />}
</div>
<div className={getTabClassname('clientCert')} role="tab" onClick={() => setTab('clientCert')}>
Client Certificates
{clientCertConfig.length > 0 && <ContentIndicator />}
{clientCertConfig.length > 0 && <StatusDot />}
</div>
</div>
<section className="mt-4 h-full">{getTabPanel(tab)}</section>

View File

@@ -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 (
<sup className="ml-[.125rem] opacity-80 font-medium">
<DotIcon width="10"></DotIcon>
</sup>
);
};
const FolderSettings = ({ collection, folder }) => {
const dispatch = useDispatch();
let tab = 'headers';
@@ -91,11 +83,11 @@ const FolderSettings = ({ collection, folder }) => {
</div>
<div className={getTabClassname('script')} role="tab" onClick={() => setTab('script')}>
Script
{hasScripts && <ContentIndicator />}
{hasScripts && <StatusDot />}
</div>
<div className={getTabClassname('test')} role="tab" onClick={() => setTab('test')}>
Test
{hasTests && <ContentIndicator />}
{hasTests && <StatusDot />}
</div>
<div className={getTabClassname('vars')} role="tab" onClick={() => setTab('vars')}>
Vars
@@ -103,7 +95,7 @@ const FolderSettings = ({ collection, folder }) => {
</div>
<div className={getTabClassname('auth')} role="tab" onClick={() => setTab('auth')}>
Auth
{hasAuth && <ContentIndicator />}
{hasAuth && <StatusDot />}
</div>
<div className={getTabClassname('docs')} role="tab" onClick={() => setTab('docs')}>
Docs

View File

@@ -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 (
<sup className="ml-[.125rem] opacity-80 font-medium">
<DotIcon width="10"></DotIcon>
</sup>
);
};
const ErrorIndicator = () => {
return (
<sup className="ml-[.125rem] opacity-80 font-medium text-red-500">
<DotIcon width="10" ></DotIcon>
</sup>
);
};
import StatusDot from 'components/StatusDot';
const HttpRequestPane = ({ item, collection }) => {
const dispatch = useDispatch();
@@ -136,7 +120,7 @@ const HttpRequestPane = ({ item, collection }) => {
</div>
<div className={getTabClassname('body')} role="tab" onClick={() => selectTab('body')}>
Body
{body.mode !== 'none' && <ContentIndicator />}
{body.mode !== 'none' && <StatusDot />}
</div>
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
Headers
@@ -144,7 +128,7 @@ const HttpRequestPane = ({ item, collection }) => {
</div>
<div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>
Auth
{auth.mode !== 'none' && <ContentIndicator />}
{auth.mode !== 'none' && <StatusDot />}
</div>
<div className={getTabClassname('vars')} role="tab" onClick={() => selectTab('vars')}>
Vars
@@ -153,9 +137,9 @@ const HttpRequestPane = ({ item, collection }) => {
<div className={getTabClassname('script')} role="tab" onClick={() => selectTab('script')}>
Script
{(script.req || script.res) && (
item.preRequestScriptErrorMessage || item.postResponseScriptErrorMessage ?
<ErrorIndicator /> :
<ContentIndicator />
item.preRequestScriptErrorMessage || item.postResponseScriptErrorMessage ?
<StatusDot type="error" /> :
<StatusDot />
)}
</div>
<div className={getTabClassname('assert')} role="tab" onClick={() => selectTab('assert')}>
@@ -164,11 +148,15 @@ const HttpRequestPane = ({ item, collection }) => {
</div>
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
Tests
{tests && tests.length > 0 && <ContentIndicator />}
{tests && tests.length > 0 && (
item.testScriptErrorMessage ?
<StatusDot type="error" /> :
<StatusDot />
)}
</div>
<div className={getTabClassname('docs')} role="tab" onClick={() => selectTab('docs')}>
Docs
{docs && docs.length > 0 && <ContentIndicator />}
{docs && docs.length > 0 && <StatusDot />}
</div>
{focusedTab.requestPaneTab === 'body' ? (
<div className="flex flex-grow justify-end items-center">

View File

@@ -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 }) => {
/>
</div>
<div className="flex flex-grow justify-end items-center">
{hasScriptError && !showScriptErrorCard && (
<ScriptErrorIcon
itemUid={item.uid}
onClick={() => setShowScriptErrorCard(true)}
/>
)}
<StatusCode status={status} />
<ResponseTime duration={duration} />
<ResponseSize size={size} />
</div>
</div>
<section className="flex flex-grow mt-5">{getTabPanel(selectedTab)}</section>
<section className="flex flex-col flex-grow">
{hasScriptError && showScriptErrorCard && (
<ScriptError
item={item}
onClose={() => setShowScriptErrorCard(false)}
/>
)}
<div className='flex-1'>
{getTabPanel(selectedTab)}
</div>
</section>
</StyledWrapper>
);
};

View File

@@ -0,0 +1,15 @@
import React from 'react';
import DotIcon from 'components/Icons/Dot';
const StatusDot = ({ type = 'default' }) => (
<sup
className={`ml-[.125rem] opacity-80 font-medium ${
type === 'error' ? 'text-red-500' : ''
}`}
>
<DotIcon width="10" />
</sup>
);
export default StatusDot;

View File

@@ -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) => {

View File

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

View File

@@ -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', {

View File

@@ -177,7 +177,6 @@ class TestRuntime {
}
} catch (error) {
scriptError = error;
console.error('Test script execution error:', error);
}
const result = {