Merge pull request #3719 from lohxt1/feat/bru-runner-fns

feat: bru util fns -- skipRequest, stopExecution, getTestResults, getAssertionResults, runRequest
This commit is contained in:
lohit
2025-01-02 18:02:42 +05:30
committed by GitHub
14 changed files with 353 additions and 34 deletions

20
package-lock.json generated
View File

@@ -24287,10 +24287,30 @@
"name": "@usebruno/schema", "name": "@usebruno/schema",
"version": "0.7.0", "version": "0.7.0",
"license": "MIT", "license": "MIT",
"dependencies": {
"nanoid": "3.3.8"
},
"peerDependencies": { "peerDependencies": {
"yup": "^0.32.11" "yup": "^0.32.11"
} }
}, },
"packages/bruno-schema/node_modules/nanoid": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"packages/bruno-tests": { "packages/bruno-tests": {
"name": "@usebruno/tests", "name": "@usebruno/tests",
"version": "0.0.1", "version": "0.0.1",

View File

@@ -76,7 +76,11 @@ if (!SERVER_RENDERED) {
'bru.getRequestVar(key)', 'bru.getRequestVar(key)',
'bru.sleep(ms)', 'bru.sleep(ms)',
'bru.getGlobalEnvVar(key)', 'bru.getGlobalEnvVar(key)',
'bru.setGlobalEnvVar(key, value)' 'bru.setGlobalEnvVar(key, value)',
'bru.runner',
'bru.runner.setNextRequest(requestName)',
'bru.runner.skipRequest()',
'bru.runner.stopExecution()'
]; ];
CodeMirror.registerHelper('hint', 'brunoJS', (editor, options) => { CodeMirror.registerHelper('hint', 'brunoJS', (editor, options) => {
const cursor = editor.getCursor(); const cursor = editor.getCursor();
@@ -98,7 +102,7 @@ if (!SERVER_RENDERED) {
if (curWordBru) { if (curWordBru) {
hintWords.forEach((h) => { hintWords.forEach((h) => {
if (h.includes('.') == curWordBru.includes('.') && h.startsWith(curWordBru)) { if (h.includes('.') == curWordBru.includes('.') && h.startsWith(curWordBru)) {
result.list.push(curWordBru.includes('.') ? h.split('.')[1] : h); result.list.push(curWordBru.includes('.') ? h.split('.')?.at(-1) : h);
} }
}); });
result.list?.sort(); result.list?.sort();

View File

@@ -11,7 +11,7 @@ const ResponseSave = ({ item }) => {
const saveResponseToFile = () => { const saveResponseToFile = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ipcRenderer ipcRenderer
.invoke('renderer:save-response-to-file', response, item.requestSent.url) .invoke('renderer:save-response-to-file', response, item?.requestSent?.url)
.then(resolve) .then(resolve)
.catch((err) => { .catch((err) => {
toast.error(get(err, 'error.message') || 'Something went wrong!'); toast.error(get(err, 'error.message') || 'Something went wrong!');

View File

@@ -43,7 +43,7 @@ const Timeline = ({ request, response }) => {
<div className="mt-4"> <div className="mt-4">
<pre className="line response font-bold"> <pre className="line response font-bold">
<span className="arrow">{'<'}</span> {response.status} {response.statusText} <span className="arrow">{'<'}</span> {response.status} - {response.statusText}
</pre> </pre>
{responseHeaders.map((h) => { {responseHeaders.map((h) => {

View File

@@ -59,7 +59,7 @@ export default function RunnerResults({ collection }) {
pathname: info.pathname, pathname: info.pathname,
relativePath: getRelativePath(collection.pathname, info.pathname) relativePath: getRelativePath(collection.pathname, info.pathname)
}; };
if (newItem.status !== 'error') { if (newItem.status !== 'error' && newItem.status !== 'skipped') {
if (newItem.testResults) { if (newItem.testResults) {
const failed = newItem.testResults.filter((result) => result.status === 'fail'); const failed = newItem.testResults.filter((result) => result.status === 'fail');
newItem.testStatus = failed.length ? 'fail' : 'pass'; newItem.testStatus = failed.length ? 'fail' : 'pass';
@@ -163,29 +163,35 @@ export default function RunnerResults({ collection }) {
<div className="pb-2 font-medium test-summary"> <div className="pb-2 font-medium test-summary">
Total Requests: {items.length}, Passed: {passedRequests.length}, Failed: {failedRequests.length} Total Requests: {items.length}, Passed: {passedRequests.length}, Failed: {failedRequests.length}
</div> </div>
{runnerInfo?.statusText ?
<div className="pb-2 font-medium danger">
{runnerInfo?.statusText}
</div>
: null}
{items.map((item) => { {items.map((item) => {
return ( return (
<div key={item.uid}> <div key={item.uid}>
<div className="item-path mt-2"> <div className="item-path mt-2">
<div className="flex items-center"> <div className="flex items-center">
<span> <span>
{item.status !== 'error' && item.testStatus === 'pass' ? ( {item.status !== 'error' && item.testStatus === 'pass' && item.status !== 'skipped' ? (
<IconCircleCheck className="test-success" size={20} strokeWidth={1.5} /> <IconCircleCheck className="test-success" size={20} strokeWidth={1.5} />
) : ( ) : (
<IconCircleX className="test-failure" size={20} strokeWidth={1.5} /> <IconCircleX className="test-failure" size={20} strokeWidth={1.5} />
)} )}
</span> </span>
<span <span
className={`mr-1 ml-2 ${item.status == 'error' || item.testStatus == 'fail' ? 'danger' : ''}`} className={`mr-1 ml-2 ${item.status == 'error' || item.status == 'skipped' || item.testStatus == 'fail' ? 'danger' : ''}`}
> >
{item.relativePath} {item.relativePath}
</span> </span>
{item.status !== 'error' && item.status !== 'completed' ? ( {item.status !== 'error' && item.status !== 'skipped' && item.status !== 'completed' ? (
<IconRefresh className="animate-spin ml-1" size={18} strokeWidth={1.5} /> <IconRefresh className="animate-spin ml-1" size={18} strokeWidth={1.5} />
) : item.responseReceived?.status ? ( ) : item.responseReceived?.status ? (
<span className="text-xs link cursor-pointer" onClick={() => setSelectedItem(item)}> <span className="text-xs link cursor-pointer" onClick={() => setSelectedItem(item)}>
(<span className="mr-1">{item.responseReceived?.status}</span> <span className="mr-1">{item.responseReceived?.status}</span>
<span>{item.responseReceived?.statusText}</span>) -&nbsp;
<span>{item.responseReceived?.statusText}</span>
</span> </span>
) : ( ) : (
<span className="danger text-xs cursor-pointer" onClick={() => setSelectedItem(item)}> <span className="danger text-xs cursor-pointer" onClick={() => setSelectedItem(item)}>

View File

@@ -1675,6 +1675,9 @@ export const collectionsSlice = createSlice({
if (type === 'testrun-ended') { if (type === 'testrun-ended') {
const info = collection.runnerResult.info; const info = collection.runnerResult.info;
info.status = 'ended'; info.status = 'ended';
if (action.payload.statusText) {
info.statusText = action.payload.statusText;
}
} }
if (type === 'request-queued') { if (type === 'request-queued') {
@@ -1712,6 +1715,12 @@ export const collectionsSlice = createSlice({
item.responseReceived = action.payload.responseReceived; item.responseReceived = action.payload.responseReceived;
item.status = 'error'; item.status = 'error';
} }
if (type === 'runner-request-skipped') {
const item = collection.runnerResult.items.findLast((i) => i.uid === request.uid);
item.status = 'skipped';
item.responseReceived = action.payload.responseReceived;
}
} }
}, },
resetCollectionRunner: (state, action) => { resetCollectionRunner: (state, action) => {

View File

@@ -39,6 +39,7 @@ const Oauth2Store = require('../../store/oauth2');
const iconv = require('iconv-lite'); const iconv = require('iconv-lite');
const FormData = require('form-data'); const FormData = require('form-data');
const { createFormData } = require('../../utils/form-data'); const { createFormData } = require('../../utils/form-data');
const { findItemInCollectionByPathname } = require('../../utils/collection');
const safeStringifyJSON = (data) => { const safeStringifyJSON = (data) => {
try { try {
@@ -397,7 +398,8 @@ const registerNetworkIpc = (mainWindow) => {
collectionUid, collectionUid,
runtimeVariables, runtimeVariables,
processEnvVars, processEnvVars,
scriptingConfig scriptingConfig,
runRequestByItemPathname
) => { ) => {
// run pre-request script // run pre-request script
let scriptResult; let scriptResult;
@@ -412,7 +414,8 @@ const registerNetworkIpc = (mainWindow) => {
collectionPath, collectionPath,
onConsoleLog, onConsoleLog,
processEnvVars, processEnvVars,
scriptingConfig scriptingConfig,
runRequestByItemPathname
); );
mainWindow.webContents.send('main:script-environment-update', { mainWindow.webContents.send('main:script-environment-update', {
@@ -462,7 +465,8 @@ const registerNetworkIpc = (mainWindow) => {
collectionUid, collectionUid,
runtimeVariables, runtimeVariables,
processEnvVars, processEnvVars,
scriptingConfig scriptingConfig,
runRequestByItemPathname
) => { ) => {
// run post-response vars // run post-response vars
const postResponseVars = get(request, 'vars.res', []); const postResponseVars = get(request, 'vars.res', []);
@@ -510,7 +514,8 @@ const registerNetworkIpc = (mainWindow) => {
collectionPath, collectionPath,
onConsoleLog, onConsoleLog,
processEnvVars, processEnvVars,
scriptingConfig scriptingConfig,
runRequestByItemPathname
); );
mainWindow.webContents.send('main:script-environment-update', { mainWindow.webContents.send('main:script-environment-update', {
@@ -527,14 +532,28 @@ const registerNetworkIpc = (mainWindow) => {
return scriptResult; return scriptResult;
}; };
// handler for sending http request const runRequest = async ({ item, collection, environment, runtimeVariables, runInBackground = false }) => {
ipcMain.handle('send-http-request', async (event, item, collection, environment, runtimeVariables) => {
const collectionUid = collection.uid; const collectionUid = collection.uid;
const collectionPath = collection.pathname; const collectionPath = collection.pathname;
const cancelTokenUid = uuid(); const cancelTokenUid = uuid();
const requestUid = uuid(); const requestUid = uuid();
mainWindow.webContents.send('main:run-request-event', { const runRequestByItemPathname = async (relativeItemPathname) => {
return new Promise(async (resolve, reject) => {
let itemPathname = path.join(collection?.pathname, relativeItemPathname);
if (itemPathname && !itemPathname?.endsWith('.bru')) {
itemPathname = `${itemPathname}.bru`;
}
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', type: 'request-queued',
requestUid, requestUid,
collectionUid, collectionUid,
@@ -565,7 +584,8 @@ const registerNetworkIpc = (mainWindow) => {
collectionUid, collectionUid,
runtimeVariables, runtimeVariables,
processEnvVars, processEnvVars,
scriptingConfig scriptingConfig,
runRequestByItemPathname
); );
const axiosInstance = await configureRequest( const axiosInstance = await configureRequest(
@@ -577,7 +597,7 @@ const registerNetworkIpc = (mainWindow) => {
collectionPath collectionPath
); );
mainWindow.webContents.send('main:run-request-event', { !runInBackground && mainWindow.webContents.send('main:run-request-event', {
type: 'request-sent', type: 'request-sent',
requestSent: { requestSent: {
url: request.url, url: request.url,
@@ -649,7 +669,8 @@ const registerNetworkIpc = (mainWindow) => {
collectionUid, collectionUid,
runtimeVariables, runtimeVariables,
processEnvVars, processEnvVars,
scriptingConfig scriptingConfig,
runRequestByItemPathname
); );
// run assertions // run assertions
@@ -665,7 +686,7 @@ const registerNetworkIpc = (mainWindow) => {
processEnvVars processEnvVars
); );
mainWindow.webContents.send('main:run-request-event', { !runInBackground && mainWindow.webContents.send('main:run-request-event', {
type: 'assertion-results', type: 'assertion-results',
results: results, results: results,
itemUid: item.uid, itemUid: item.uid,
@@ -686,10 +707,11 @@ const registerNetworkIpc = (mainWindow) => {
collectionPath, collectionPath,
onConsoleLog, onConsoleLog,
processEnvVars, processEnvVars,
scriptingConfig scriptingConfig,
runRequestByItemPathname
); );
mainWindow.webContents.send('main:run-request-event', { !runInBackground && mainWindow.webContents.send('main:run-request-event', {
type: 'test-results', type: 'test-results',
results: testResults.results, results: testResults.results,
itemUid: item.uid, itemUid: item.uid,
@@ -723,6 +745,11 @@ const registerNetworkIpc = (mainWindow) => {
return Promise.reject(error); 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) => { ipcMain.handle('send-collection-oauth2-request', async (event, collection, environment, runtimeVariables) => {
@@ -914,10 +941,26 @@ const registerNetworkIpc = (mainWindow) => {
const scriptingConfig = get(brunoConfig, 'scripts', {}); const scriptingConfig = get(brunoConfig, 'scripts', {});
scriptingConfig.runtime = getJsSandboxRuntime(collection); scriptingConfig.runtime = getJsSandboxRuntime(collection);
const collectionRoot = get(collection, 'root', {}); const collectionRoot = get(collection, 'root', {});
let stopRunnerExecution = false;
const abortController = new AbortController(); const abortController = new AbortController();
saveCancelToken(cancelTokenUid, abortController); saveCancelToken(cancelTokenUid, abortController);
const runRequestByItemPathname = async (relativeItemPathname) => {
return new Promise(async (resolve, reject) => {
let itemPathname = path.join(collection?.pathname, relativeItemPathname);
if (itemPathname && !itemPathname?.endsWith('.bru')) {
itemPathname = `${itemPathname}.bru`;
}
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}`);
});
}
if (!folder) { if (!folder) {
folder = collection; folder = collection;
} }
@@ -960,6 +1003,8 @@ const registerNetworkIpc = (mainWindow) => {
throw error; throw error;
} }
stopRunnerExecution = false;
const item = folderRequests[currentRequestIndex]; const item = folderRequests[currentRequestIndex];
let nextRequestName; let nextRequestName;
const itemUid = item.uid; const itemUid = item.uid;
@@ -993,13 +1038,33 @@ const registerNetworkIpc = (mainWindow) => {
collectionUid, collectionUid,
runtimeVariables, runtimeVariables,
processEnvVars, processEnvVars,
scriptingConfig scriptingConfig,
runRequestByItemPathname
); );
if (preRequestScriptResult?.nextRequestName !== undefined) { if (preRequestScriptResult?.nextRequestName !== undefined) {
nextRequestName = preRequestScriptResult.nextRequestName; nextRequestName = preRequestScriptResult.nextRequestName;
} }
if (preRequestScriptResult?.stopExecution) {
stopRunnerExecution = true;
}
if (preRequestScriptResult?.skipRequest) {
mainWindow.webContents.send('main:run-folder-event', {
type: 'runner-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: // todo:
// i have no clue why electron can't send the request object // i have no clue why electron can't send the request object
// without safeParseJSON(safeStringifyJSON(request.data)) // without safeParseJSON(safeStringifyJSON(request.data))
@@ -1111,13 +1176,18 @@ const registerNetworkIpc = (mainWindow) => {
collectionUid, collectionUid,
runtimeVariables, runtimeVariables,
processEnvVars, processEnvVars,
scriptingConfig scriptingConfig,
runRequestByItemPathname
); );
if (postRequestScriptResult?.nextRequestName !== undefined) { if (postRequestScriptResult?.nextRequestName !== undefined) {
nextRequestName = postRequestScriptResult.nextRequestName; nextRequestName = postRequestScriptResult.nextRequestName;
} }
if (postRequestScriptResult?.stopExecution) {
stopRunnerExecution = true;
}
// run assertions // run assertions
const assertions = get(item, 'request.assertions'); const assertions = get(item, 'request.assertions');
if (assertions) { if (assertions) {
@@ -1151,7 +1221,8 @@ const registerNetworkIpc = (mainWindow) => {
collectionPath, collectionPath,
onConsoleLog, onConsoleLog,
processEnvVars, processEnvVars,
scriptingConfig scriptingConfig,
runRequestByItemPathname
); );
if (testResults?.nextRequestName !== undefined) { if (testResults?.nextRequestName !== undefined) {
@@ -1182,6 +1253,18 @@ const registerNetworkIpc = (mainWindow) => {
...eventData ...eventData
}); });
} }
if (stopRunnerExecution) {
deleteCancelToken(cancelTokenUid);
mainWindow.webContents.send('main:run-folder-event', {
type: 'testrun-ended',
collectionUid,
folderUid,
statusText: 'collection run was terminated!'
});
break;
}
if (nextRequestName !== undefined) { if (nextRequestName !== undefined) {
nJumps++; nJumps++;
if (nJumps > 10000) { if (nJumps > 10000) {

View File

@@ -203,9 +203,31 @@ const getTreePathFromCollectionToItem = (collection, _item) => {
return path; 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 = { module.exports = {
mergeHeaders, mergeHeaders,
mergeVars, mergeVars,
mergeScripts, mergeScripts,
getTreePathFromCollectionToItem getTreePathFromCollectionToItem,
slash,
findItemByPathname,
findItemInCollectionByPathname
} }

View File

@@ -13,6 +13,17 @@ class Bru {
this.requestVariables = requestVariables || {}; this.requestVariables = requestVariables || {};
this.globalEnvironmentVariables = globalEnvironmentVariables || {}; this.globalEnvironmentVariables = globalEnvironmentVariables || {};
this.collectionPath = collectionPath; this.collectionPath = collectionPath;
this.runner = {
skipRequest: () => {
this.skipRequest = true;
},
stopExecution: () => {
this.stopExecution = true;
},
setNextRequest: (nextRequest) => {
this.nextRequest = nextRequest;
}
};
} }
_interpolate = (str) => { _interpolate = (str) => {

View File

@@ -408,6 +408,8 @@ class AssertRuntime {
} }
} }
request.assertionResults = assertionResults;
return assertionResults; return assertionResults;
} }
} }

View File

@@ -45,7 +45,8 @@ class ScriptRuntime {
collectionPath, collectionPath,
onConsoleLog, onConsoleLog,
processEnvVars, processEnvVars,
scriptingConfig scriptingConfig,
runRequestByItemPathname
) { ) {
const globalEnvironmentVariables = request?.globalEnvironmentVariables || {}; const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
const collectionVariables = request?.collectionVariables || {}; const collectionVariables = request?.collectionVariables || {};
@@ -92,6 +93,10 @@ class ScriptRuntime {
}; };
} }
if(runRequestByItemPathname) {
context.bru.runRequest = runRequestByItemPathname;
}
if (this.runtime === 'quickjs') { if (this.runtime === 'quickjs') {
await executeQuickJsVmAsync({ await executeQuickJsVmAsync({
script: script, script: script,
@@ -104,7 +109,9 @@ class ScriptRuntime {
envVariables: cleanJson(envVariables), envVariables: cleanJson(envVariables),
runtimeVariables: cleanJson(runtimeVariables), runtimeVariables: cleanJson(runtimeVariables),
globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
nextRequestName: bru.nextRequest nextRequestName: bru.nextRequest,
skipRequest: bru.skipRequest,
stopExecution: bru.stopExecution
}; };
} }
@@ -152,7 +159,9 @@ class ScriptRuntime {
envVariables: cleanJson(envVariables), envVariables: cleanJson(envVariables),
runtimeVariables: cleanJson(runtimeVariables), runtimeVariables: cleanJson(runtimeVariables),
globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
nextRequestName: bru.nextRequest nextRequestName: bru.nextRequest,
skipRequest: bru.skipRequest,
stopExecution: bru.stopExecution
}; };
} }
@@ -165,7 +174,8 @@ class ScriptRuntime {
collectionPath, collectionPath,
onConsoleLog, onConsoleLog,
processEnvVars, processEnvVars,
scriptingConfig scriptingConfig,
runRequestByItemPathname
) { ) {
const globalEnvironmentVariables = request?.globalEnvironmentVariables || {}; const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
const collectionVariables = request?.collectionVariables || {}; const collectionVariables = request?.collectionVariables || {};
@@ -209,6 +219,10 @@ class ScriptRuntime {
}; };
} }
if(runRequestByItemPathname) {
context.bru.runRequest = runRequestByItemPathname;
}
if (this.runtime === 'quickjs') { if (this.runtime === 'quickjs') {
await executeQuickJsVmAsync({ await executeQuickJsVmAsync({
script: script, script: script,
@@ -221,7 +235,9 @@ class ScriptRuntime {
envVariables: cleanJson(envVariables), envVariables: cleanJson(envVariables),
runtimeVariables: cleanJson(runtimeVariables), runtimeVariables: cleanJson(runtimeVariables),
globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
nextRequestName: bru.nextRequest nextRequestName: bru.nextRequest,
skipRequest: bru.skipRequest,
stopExecution: bru.stopExecution
}; };
} }
@@ -269,7 +285,9 @@ class ScriptRuntime {
envVariables: cleanJson(envVariables), envVariables: cleanJson(envVariables),
runtimeVariables: cleanJson(runtimeVariables), runtimeVariables: cleanJson(runtimeVariables),
globalEnvironmentVariables: cleanJson(globalEnvironmentVariables), globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
nextRequestName: bru.nextRequest nextRequestName: bru.nextRequest,
skipRequest: bru.skipRequest,
stopExecution: bru.stopExecution
}; };
} }
} }

View File

@@ -32,6 +32,25 @@ const CryptoJS = require('crypto-js');
const NodeVault = require('node-vault'); const NodeVault = require('node-vault');
const { executeQuickJsVmAsync } = require('../sandbox/quickjs'); const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
const getResultsSummary = (results) => {
const summary = {
total: results.length,
passed: 0,
failed: 0,
skipped: 0,
};
results.forEach((r) => {
const passed = r.status === "pass";
if (passed) summary.passed += 1;
else if (r.status === "fail") summary.failed += 1;
else summary.skipped += 1;
});
return summary;
}
class TestRuntime { class TestRuntime {
constructor(props) { constructor(props) {
this.runtime = props?.runtime || 'vm2'; this.runtime = props?.runtime || 'vm2';
@@ -46,12 +65,14 @@ class TestRuntime {
collectionPath, collectionPath,
onConsoleLog, onConsoleLog,
processEnvVars, processEnvVars,
scriptingConfig scriptingConfig,
runRequestByItemPathname
) { ) {
const globalEnvironmentVariables = request?.globalEnvironmentVariables || {}; const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
const collectionVariables = request?.collectionVariables || {}; const collectionVariables = request?.collectionVariables || {};
const folderVariables = request?.folderVariables || {}; const folderVariables = request?.folderVariables || {};
const requestVariables = request?.requestVariables || {}; const requestVariables = request?.requestVariables || {};
const assertionResults = request?.assertionResults || [];
const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables); const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables);
const req = new BrunoRequest(request); const req = new BrunoRequest(request);
const res = new BrunoResponse(response); const res = new BrunoResponse(response);
@@ -75,6 +96,7 @@ class TestRuntime {
} }
const __brunoTestResults = new TestResults(); const __brunoTestResults = new TestResults();
const test = Test(__brunoTestResults, chai); const test = Test(__brunoTestResults, chai);
if (!testsFile || !testsFile.length) { if (!testsFile || !testsFile.length) {
@@ -88,6 +110,36 @@ class TestRuntime {
}; };
} }
bru.getTestResults = async () => {
let results = await __brunoTestResults.getResults();
const summary = getResultsSummary(results);
return {
summary,
results: results?.map?.(r => ({
status: r?.status,
description: r?.description,
expected: r?.expected,
actual: r?.actual,
error: r?.error
}))
};
}
bru.getAssertionResults = async () => {
let results = assertionResults;
const summary = getResultsSummary(results);
return {
summary,
results: results?.map?.(r => ({
status: r?.status,
lhsExpr: r?.lhsExpr,
rhsExpr: r?.rhsExpr,
operator: r?.operator,
rhsOperand: r?.rhsOperand,
error: r?.error
}))
};
}
const context = { const context = {
test, test,
bru, bru,
@@ -113,6 +165,10 @@ class TestRuntime {
}; };
} }
if(runRequestByItemPathname) {
context.bru.runRequest = runRequestByItemPathname;
}
if (this.runtime === 'quickjs') { if (this.runtime === 'quickjs') {
await executeQuickJsVmAsync({ await executeQuickJsVmAsync({
script: testsFile, script: testsFile,

View File

@@ -1,7 +1,9 @@
const { cleanJson } = require('../../../utils');
const { marshallToVm } = require('../utils'); const { marshallToVm } = require('../utils');
const addBruShimToContext = (vm, bru) => { const addBruShimToContext = (vm, bru) => {
const bruObject = vm.newObject(); const bruObject = vm.newObject();
const bruRunnerObject = vm.newObject();
let cwd = vm.newFunction('cwd', function () { let cwd = vm.newFunction('cwd', function () {
return marshallToVm(bru.cwd(), vm); return marshallToVm(bru.cwd(), vm);
@@ -93,6 +95,24 @@ const addBruShimToContext = (vm, bru) => {
vm.setProp(bruObject, 'setNextRequest', setNextRequest); vm.setProp(bruObject, 'setNextRequest', setNextRequest);
setNextRequest.dispose(); setNextRequest.dispose();
let runnerSkipRequest = vm.newFunction('skipRequest', function () {
bru?.runner?.skipRequest();
});
vm.setProp(bruRunnerObject, 'skipRequest', runnerSkipRequest);
runnerSkipRequest.dispose();
let runnerStopExecution = vm.newFunction('stopExecution', function () {
bru?.runner?.stopExecution();
});
vm.setProp(bruRunnerObject, 'stopExecution', runnerStopExecution);
runnerStopExecution.dispose();
let runnerSetNextRequest = vm.newFunction('setNextRequest', function (nextRequest) {
bru?.runner?.setNextRequest(vm.dump(nextRequest));
});
vm.setProp(bruRunnerObject, 'setNextRequest', runnerSetNextRequest);
runnerSetNextRequest.dispose();
let visualize = vm.newFunction('visualize', function (htmlString) { let visualize = vm.newFunction('visualize', function (htmlString) {
bru.visualize(vm.dump(htmlString)); bru.visualize(vm.dump(htmlString));
}); });
@@ -123,6 +143,70 @@ const addBruShimToContext = (vm, bru) => {
vm.setProp(bruObject, 'getCollectionVar', getCollectionVar); vm.setProp(bruObject, 'getCollectionVar', getCollectionVar);
getCollectionVar.dispose(); 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 sleep = vm.newFunction('sleep', (timer) => {
const t = vm.getString(timer); const t = vm.getString(timer);
const promise = vm.newPromise(); const promise = vm.newPromise();
@@ -134,6 +218,7 @@ const addBruShimToContext = (vm, bru) => {
}); });
sleep.consume((handle) => vm.setProp(bruObject, 'sleep', handle)); sleep.consume((handle) => vm.setProp(bruObject, 'sleep', handle));
vm.setProp(bruObject, 'runner', bruRunnerObject);
vm.setProp(vm.global, 'bru', bruObject); vm.setProp(vm.global, 'bru', bruObject);
bruObject.dispose(); bruObject.dispose();
}; };

View File

@@ -12,5 +12,8 @@
}, },
"peerDependencies": { "peerDependencies": {
"yup": "^0.32.11" "yup": "^0.32.11"
},
"dependencies": {
"nanoid": "3.3.8"
} }
} }