diff --git a/packages/bruno-app/src/components/RunnerResults/index.jsx b/packages/bruno-app/src/components/RunnerResults/index.jsx index 4b0b68cba..f411404a4 100644 --- a/packages/bruno-app/src/components/RunnerResults/index.jsx +++ b/packages/bruno-app/src/components/RunnerResults/index.jsx @@ -59,7 +59,7 @@ export default function RunnerResults({ collection }) { pathname: info.pathname, relativePath: getRelativePath(collection.pathname, info.pathname) }; - if (newItem.status !== 'error') { + if (newItem.status !== 'error' && newItem.status !== 'skipped') { if (newItem.testResults) { const failed = newItem.testResults.filter((result) => result.status === 'fail'); newItem.testStatus = failed.length ? 'fail' : 'pass'; @@ -169,18 +169,18 @@ export default function RunnerResults({ collection }) {
- {item.status !== 'error' && item.testStatus === 'pass' ? ( + {item.status !== 'error' && item.testStatus === 'pass' && item.status !== 'skipped' ? ( ) : ( )} {item.relativePath} - {item.status !== 'error' && item.status !== 'completed' ? ( + {item.status !== 'error' && item.status !== 'skipped' && item.status !== 'completed' ? ( ) : item.responseReceived?.status ? ( setSelectedItem(item)}> 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 2b5a532c3..dc0dd50f6 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -1712,6 +1712,12 @@ export const collectionsSlice = createSlice({ item.responseReceived = action.payload.responseReceived; item.status = 'error'; } + + if (type === 'request-skipped') { + const item = collection.runnerResult.items.findLast((i) => i.uid === request.uid); + item.status = 'skipped'; + item.responseReceived = action.payload.responseReceived; + } } }, resetCollectionRunner: (state, action) => { diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 9e37c8289..f3bf6b583 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -39,6 +39,7 @@ const Oauth2Store = require('../../store/oauth2'); const iconv = require('iconv-lite'); const FormData = require('form-data'); const { createFormData } = require('../../utils/form-data'); +const { findItemInCollectionByPathname } = require('../../utils/collection'); const safeStringifyJSON = (data) => { try { @@ -393,7 +394,8 @@ const registerNetworkIpc = (mainWindow) => { collectionUid, runtimeVariables, processEnvVars, - scriptingConfig + scriptingConfig, + runRequestByItemPathname ) => { // run pre-request script let scriptResult; @@ -408,7 +410,8 @@ const registerNetworkIpc = (mainWindow) => { collectionPath, onConsoleLog, processEnvVars, - scriptingConfig + scriptingConfig, + runRequestByItemPathname ); mainWindow.webContents.send('main:script-environment-update', { @@ -458,7 +461,8 @@ const registerNetworkIpc = (mainWindow) => { collectionUid, runtimeVariables, processEnvVars, - scriptingConfig + scriptingConfig, + runRequestByItemPathname ) => { // run post-response vars const postResponseVars = get(request, 'vars.res', []); @@ -506,7 +510,8 @@ const registerNetworkIpc = (mainWindow) => { collectionPath, onConsoleLog, processEnvVars, - scriptingConfig + scriptingConfig, + runRequestByItemPathname ); mainWindow.webContents.send('main:script-environment-update', { @@ -523,14 +528,24 @@ const registerNetworkIpc = (mainWindow) => { return scriptResult; }; - // handler for sending http request - ipcMain.handle('send-http-request', async (event, item, collection, environment, runtimeVariables) => { + const runRequest = async ({ item, collection, environment, runtimeVariables, runInBackground = false }) => { const collectionUid = collection.uid; const collectionPath = collection.pathname; const cancelTokenUid = uuid(); const requestUid = uuid(); - mainWindow.webContents.send('main:run-request-event', { + const runRequestByItemPathname = async ({ itemPathname }) => { + return new Promise(async (resolve, reject) => { + const _item = findItemInCollectionByPathname(collection, itemPathname); + if(_item) { + const res = await runRequest({ item: _item, collection, environment, runtimeVariables, runInBackground: true }); + resolve(res); + } + reject(`bru.runRequest: invalid request path - ${itemPathname}`); + }); + } + + !runInBackground && mainWindow.webContents.send('main:run-request-event', { type: 'request-queued', requestUid, collectionUid, @@ -561,7 +576,8 @@ const registerNetworkIpc = (mainWindow) => { collectionUid, runtimeVariables, processEnvVars, - scriptingConfig + scriptingConfig, + runRequestByItemPathname ); const axiosInstance = await configureRequest( @@ -573,7 +589,7 @@ const registerNetworkIpc = (mainWindow) => { collectionPath ); - mainWindow.webContents.send('main:run-request-event', { + !runInBackground && mainWindow.webContents.send('main:run-request-event', { type: 'request-sent', requestSent: { url: request.url, @@ -645,7 +661,8 @@ const registerNetworkIpc = (mainWindow) => { collectionUid, runtimeVariables, processEnvVars, - scriptingConfig + scriptingConfig, + runRequestByItemPathname ); // run assertions @@ -661,7 +678,7 @@ const registerNetworkIpc = (mainWindow) => { processEnvVars ); - mainWindow.webContents.send('main:run-request-event', { + !runInBackground && mainWindow.webContents.send('main:run-request-event', { type: 'assertion-results', results: results, itemUid: item.uid, @@ -682,10 +699,11 @@ const registerNetworkIpc = (mainWindow) => { collectionPath, onConsoleLog, processEnvVars, - scriptingConfig + scriptingConfig, + runRequestByItemPathname ); - mainWindow.webContents.send('main:run-request-event', { + !runInBackground && mainWindow.webContents.send('main:run-request-event', { type: 'test-results', results: testResults.results, itemUid: item.uid, @@ -719,6 +737,11 @@ const registerNetworkIpc = (mainWindow) => { return Promise.reject(error); } + } + + // handler for sending http request + ipcMain.handle('send-http-request', async (event, item, collection, environment, runtimeVariables) => { + return await runRequest({ item, collection, environment, runtimeVariables }); }); ipcMain.handle('send-collection-oauth2-request', async (event, collection, environment, runtimeVariables) => { @@ -996,6 +1019,41 @@ const registerNetworkIpc = (mainWindow) => { nextRequestName = preRequestScriptResult.nextRequestName; } + if (preRequestScriptResult?.stopExecution) { + deleteCancelToken(cancelTokenUid); + mainWindow.webContents.send('main:run-folder-event', { + type: 'response-received', + error: 'Request has been stopped from pre-request script', + responseReceived: { + status: 'terminated', + statusText: 'Request execution stopped!', + data: null + }, + ...eventData + }); + mainWindow.webContents.send('main:run-folder-event', { + type: 'testrun-ended', + collectionUid, + folderUid + }); + break; + } + + if (preRequestScriptResult?.skipRequest) { + mainWindow.webContents.send('main:run-folder-event', { + type: 'request-skipped', + error: 'Request has been skipped from pre-request script', + responseReceived: { + status: 'skipped', + statusText: 'request skipped via pre-request script', + data: null + }, + ...eventData + }); + currentRequestIndex++; + continue; + } + // todo: // i have no clue why electron can't send the request object // without safeParseJSON(safeStringifyJSON(request.data)) @@ -1114,6 +1172,41 @@ const registerNetworkIpc = (mainWindow) => { nextRequestName = postRequestScriptResult.nextRequestName; } + if (postRequestScriptResult?.stopExecution) { + deleteCancelToken(cancelTokenUid); + mainWindow.webContents.send('main:run-folder-event', { + type: 'response-received', + error: 'Request has been stopped from post-response script', + responseReceived: { + status: 'terminated', + statusText: 'Request execution stopped!', + data: null + }, + ...eventData + }); + mainWindow.webContents.send('main:run-folder-event', { + type: 'testrun-ended', + collectionUid, + folderUid + }); + break; + } + + if (postRequestScriptResult?.skipRequest) { + mainWindow.webContents.send('main:run-folder-event', { + type: 'request-skipped', + error: 'Request has been skipped from post-response script', + responseReceived: { + status: 'skipped', + statusText: 'request skipped via post-response script', + data: null + }, + ...eventData + }); + currentRequestIndex++; + continue; + } + // run assertions const assertions = get(item, 'request.assertions'); if (assertions) { diff --git a/packages/bruno-electron/src/utils/collection.js b/packages/bruno-electron/src/utils/collection.js index 5b9c3452e..872321b50 100644 --- a/packages/bruno-electron/src/utils/collection.js +++ b/packages/bruno-electron/src/utils/collection.js @@ -203,9 +203,31 @@ const getTreePathFromCollectionToItem = (collection, _item) => { return path; }; +const slash = (path) => { + const isExtendedLengthPath = /^\\\\\?\\/.test(path); + if (isExtendedLengthPath) { + return path; + } + return path.replace(/\\/g, '/'); +}; + +const findItemByPathname = (items = [], pathname) => { + return find(items, (i) => slash(i.pathname) === slash(pathname)); +}; + +const findItemInCollectionByPathname = (collection, pathname) => { + let flattenedItems = flattenItems(collection.items); + + return findItemByPathname(flattenedItems, pathname); +}; + + module.exports = { mergeHeaders, mergeVars, mergeScripts, - getTreePathFromCollectionToItem + getTreePathFromCollectionToItem, + slash, + findItemByPathname, + findItemInCollectionByPathname } \ No newline at end of file diff --git a/packages/bruno-js/src/bru.js b/packages/bruno-js/src/bru.js index 5a4d69426..946b62480 100644 --- a/packages/bruno-js/src/bru.js +++ b/packages/bruno-js/src/bru.js @@ -13,6 +13,14 @@ class Bru { this.requestVariables = requestVariables || {}; this.globalEnvironmentVariables = globalEnvironmentVariables || {}; this.collectionPath = collectionPath; + this.runner = { + skipRequest: () => { + this.skipRequest = true; + }, + stopExecution: () => { + this.stopExecution = true; + }, + }; } _interpolate = (str) => { diff --git a/packages/bruno-js/src/runtime/assert-runtime.js b/packages/bruno-js/src/runtime/assert-runtime.js index 4132fea9c..558dcaba1 100644 --- a/packages/bruno-js/src/runtime/assert-runtime.js +++ b/packages/bruno-js/src/runtime/assert-runtime.js @@ -408,6 +408,8 @@ class AssertRuntime { } } + request.assertionResults = assertionResults; + return assertionResults; } } diff --git a/packages/bruno-js/src/runtime/script-runtime.js b/packages/bruno-js/src/runtime/script-runtime.js index 0f82114b2..dcde3f27c 100644 --- a/packages/bruno-js/src/runtime/script-runtime.js +++ b/packages/bruno-js/src/runtime/script-runtime.js @@ -45,7 +45,8 @@ class ScriptRuntime { collectionPath, onConsoleLog, processEnvVars, - scriptingConfig + scriptingConfig, + runRequestByItemPathname ) { const globalEnvironmentVariables = request?.globalEnvironmentVariables || {}; const collectionVariables = request?.collectionVariables || {}; @@ -92,6 +93,10 @@ class ScriptRuntime { }; } + if(runRequestByItemPathname) { + context.bru.runRequest = runRequestByItemPathname; + } + if (this.runtime === 'quickjs') { await executeQuickJsVmAsync({ script: script, @@ -104,7 +109,9 @@ class ScriptRuntime { envVariables: cleanJson(envVariables), runtimeVariables: cleanJson(runtimeVariables), globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), - nextRequestName: bru.nextRequest + nextRequestName: bru.nextRequest, + skipRequest: bru.skipRequest, + stopExecution: bru.stopExecution }; } @@ -152,7 +159,9 @@ class ScriptRuntime { envVariables: cleanJson(envVariables), runtimeVariables: cleanJson(runtimeVariables), globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), - nextRequestName: bru.nextRequest + nextRequestName: bru.nextRequest, + skipRequest: bru.skipRequest, + stopExecution: bru.stopExecution }; } @@ -165,7 +174,8 @@ class ScriptRuntime { collectionPath, onConsoleLog, processEnvVars, - scriptingConfig + scriptingConfig, + runRequestByItemPathname ) { const globalEnvironmentVariables = request?.globalEnvironmentVariables || {}; const collectionVariables = request?.collectionVariables || {}; @@ -209,6 +219,10 @@ class ScriptRuntime { }; } + if(runRequestByItemPathname) { + context.bru.runRequest = runRequestByItemPathname; + } + if (this.runtime === 'quickjs') { await executeQuickJsVmAsync({ script: script, @@ -221,7 +235,9 @@ class ScriptRuntime { envVariables: cleanJson(envVariables), runtimeVariables: cleanJson(runtimeVariables), globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), - nextRequestName: bru.nextRequest + nextRequestName: bru.nextRequest, + skipRequest: bru.skipRequest, + stopExecution: bru.stopExecution }; } @@ -269,7 +285,9 @@ class ScriptRuntime { envVariables: cleanJson(envVariables), runtimeVariables: cleanJson(runtimeVariables), globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), - nextRequestName: bru.nextRequest + nextRequestName: bru.nextRequest, + skipRequest: bru.skipRequest, + stopExecution: bru.stopExecution }; } } diff --git a/packages/bruno-js/src/runtime/test-runtime.js b/packages/bruno-js/src/runtime/test-runtime.js index 5391f7e10..cb68eb5c4 100644 --- a/packages/bruno-js/src/runtime/test-runtime.js +++ b/packages/bruno-js/src/runtime/test-runtime.js @@ -46,12 +46,14 @@ class TestRuntime { collectionPath, onConsoleLog, processEnvVars, - scriptingConfig + scriptingConfig, + runRequestByItemPathname ) { const globalEnvironmentVariables = request?.globalEnvironmentVariables || {}; const collectionVariables = request?.collectionVariables || {}; const folderVariables = request?.folderVariables || {}; const requestVariables = request?.requestVariables || {}; + const assertionResults = request?.assertionResults || []; const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables); const req = new BrunoRequest(request); const res = new BrunoResponse(response); @@ -75,6 +77,7 @@ class TestRuntime { } const __brunoTestResults = new TestResults(); + const test = Test(__brunoTestResults, chai); if (!testsFile || !testsFile.length) { @@ -88,6 +91,14 @@ class TestRuntime { }; } + bru.getTestResults = async () => { + let results = __brunoTestResults.getResults(); + return results; + } + bru.getAssertionResults = async () => { + return assertionResults + } + const context = { test, bru, @@ -113,6 +124,10 @@ class TestRuntime { }; } + if(runRequestByItemPathname) { + context.bru.runRequest = runRequestByItemPathname; + } + if (this.runtime === 'quickjs') { await executeQuickJsVmAsync({ script: testsFile, diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/bru.js b/packages/bruno-js/src/sandbox/quickjs/shims/bru.js index 297add66a..6797104f3 100644 --- a/packages/bruno-js/src/sandbox/quickjs/shims/bru.js +++ b/packages/bruno-js/src/sandbox/quickjs/shims/bru.js @@ -1,3 +1,4 @@ +const { cleanJson } = require('../../../utils'); const { marshallToVm } = require('../utils'); const addBruShimToContext = (vm, bru) => { @@ -123,6 +124,70 @@ const addBruShimToContext = (vm, bru) => { vm.setProp(bruObject, 'getCollectionVar', getCollectionVar); getCollectionVar.dispose(); + let getTestResults = vm.newFunction('getTestResults', () => { + const promise = vm.newPromise(); + bru.getTestResults() + .then((results) => { + promise.resolve(marshallToVm(cleanJson(results), vm)); + }) + .catch((err) => { + promise.resolve( + marshallToVm( + cleanJson({ + message: err.message + }), + vm + ) + ); + }); + promise.settled.then(vm.runtime.executePendingJobs); + return promise.handle; + }); + getTestResults.consume((handle) => vm.setProp(bruObject, 'getTestResults', handle)); + + let getAssertionResults = vm.newFunction('getAssertionResults', () => { + const promise = vm.newPromise(); + bru.getAssertionResults() + .then((results) => { + promise.resolve(marshallToVm(cleanJson(results), vm)); + }) + .catch((err) => { + promise.resolve( + marshallToVm( + cleanJson({ + message: err.message + }), + vm + ) + ); + }); + promise.settled.then(vm.runtime.executePendingJobs); + return promise.handle; + }); + getAssertionResults.consume((handle) => vm.setProp(bruObject, 'getAssertionResults', handle)); + + let runRequestHandle = vm.newFunction('runRequest', (args) => { + const promise = vm.newPromise(); + bru.runRequest(vm.dump(args)) + .then((response) => { + const { status, headers, data, dataBuffer, size } = response || {}; + promise.resolve(marshallToVm(cleanJson({ status, headers, data, dataBuffer, size }), vm)); + }) + .catch((err) => { + promise.resolve( + marshallToVm( + cleanJson({ + message: err.message + }), + vm + ) + ); + }); + promise.settled.then(vm.runtime.executePendingJobs); + return promise.handle; + }); + runRequestHandle.consume((handle) => vm.setProp(bruObject, 'runRequest', handle)); + const sleep = vm.newFunction('sleep', (timer) => { const t = vm.getString(timer); const promise = vm.newPromise();