mirror of
https://github.com/usebruno/bruno.git
synced 2025-05-05 15:32:58 +00:00
feat: Moved logic related to html report generation to bruno-common package (#4536)
--------- Co-authored-by: lohit jiddimani <lohitjiddimani@lohits-MacBook-Air.local>
This commit is contained in:
parent
e3c28fd0ec
commit
220da6b58e
68
package-lock.json
generated
68
package-lock.json
generated
@ -54,7 +54,6 @@
|
|||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||||
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
@ -1475,7 +1474,6 @@
|
|||||||
"version": "7.26.0",
|
"version": "7.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz",
|
||||||
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
|
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
@ -1506,7 +1504,6 @@
|
|||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@ -1524,7 +1521,6 @@
|
|||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@babel/generator": {
|
"node_modules/@babel/generator": {
|
||||||
@ -1804,7 +1800,6 @@
|
|||||||
"version": "7.26.0",
|
"version": "7.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz",
|
||||||
"integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
|
"integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/template": "^7.25.9",
|
"@babel/template": "^7.25.9",
|
||||||
@ -7789,7 +7784,6 @@
|
|||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||||
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
|
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/lodash": {
|
"node_modules/@types/lodash": {
|
||||||
@ -7812,7 +7806,6 @@
|
|||||||
"version": "12.2.3",
|
"version": "12.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
|
||||||
"integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==",
|
"integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/linkify-it": "*",
|
"@types/linkify-it": "*",
|
||||||
@ -7823,7 +7816,6 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
|
||||||
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/ms": {
|
"node_modules/@types/ms": {
|
||||||
@ -11068,7 +11060,6 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/cookie": {
|
"node_modules/cookie": {
|
||||||
@ -12679,7 +12670,6 @@
|
|||||||
"version": "0.1.13",
|
"version": "0.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -13647,7 +13637,6 @@
|
|||||||
"version": "1.0.0-beta.2",
|
"version": "1.0.0-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||||
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@ -24207,7 +24196,7 @@
|
|||||||
"version": "4.9.5",
|
"version": "4.9.5",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@ -26304,18 +26293,19 @@
|
|||||||
"name": "@usebruno/common",
|
"name": "@usebruno/common",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"@faker-js/faker": "^9.7.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/preset-env": "^7.26.9",
|
"@babel/preset-env": "^7.26.9",
|
||||||
"@babel/preset-typescript": "^7.27.0",
|
"@babel/preset-typescript": "^7.27.0",
|
||||||
|
"@faker-js/faker": "^9.7.0",
|
||||||
"@jest/globals": "^29.7.0",
|
"@jest/globals": "^29.7.0",
|
||||||
"@rollup/plugin-commonjs": "^23.0.2",
|
"@rollup/plugin-commonjs": "^23.0.2",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
"@rollup/plugin-typescript": "^12.1.2",
|
"@rollup/plugin-typescript": "^12.1.2",
|
||||||
|
"@types/jest": "^29.5.14",
|
||||||
"babel-jest": "^29.7.0",
|
"babel-jest": "^29.7.0",
|
||||||
|
"cheerio": "^1.0.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
|
"playwright": "^1.52.0",
|
||||||
"rollup": "3.29.5",
|
"rollup": "3.29.5",
|
||||||
"rollup-plugin-dts": "^5.0.0",
|
"rollup-plugin-dts": "^5.0.0",
|
||||||
"rollup-plugin-peer-deps-external": "^2.2.4",
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||||
@ -26708,6 +26698,7 @@
|
|||||||
"version": "9.7.0",
|
"version": "9.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.7.0.tgz",
|
||||||
"integrity": "sha512-aozo5vqjCmDoXLNUJarFZx2IN/GgGaogY4TMJ6so/WLZOWpSV7fvj2dmrV6sEAnUm1O7aCrhTibjpzeDFgNqbg==",
|
"integrity": "sha512-aozo5vqjCmDoXLNUJarFZx2IN/GgGaogY4TMJ6so/WLZOWpSV7fvj2dmrV6sEAnUm1O7aCrhTibjpzeDFgNqbg==",
|
||||||
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@ -26824,6 +26815,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/bruno-common/node_modules/fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/bruno-common/node_modules/ms": {
|
"packages/bruno-common/node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@ -26831,6 +26837,38 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"packages/bruno-common/node_modules/playwright": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright-core": "1.52.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"packages/bruno-common/node_modules/playwright-core": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"playwright-core": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/bruno-common/node_modules/typescript": {
|
"packages/bruno-common/node_modules/typescript": {
|
||||||
"version": "5.8.3",
|
"version": "5.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||||
|
@ -53,6 +53,7 @@
|
|||||||
"build:electron:deb": "./scripts/build-electron.sh deb",
|
"build:electron:deb": "./scripts/build-electron.sh deb",
|
||||||
"build:electron:rpm": "./scripts/build-electron.sh rpm",
|
"build:electron:rpm": "./scripts/build-electron.sh rpm",
|
||||||
"build:electron:snap": "./scripts/build-electron.sh snap",
|
"build:electron:snap": "./scripts/build-electron.sh snap",
|
||||||
|
"watch:common": "npm run watch --workspace=packages/bruno-common",
|
||||||
"test:codegen": "npm run dev:web & node ./scripts/playwright-codegen.js",
|
"test:codegen": "npm run dev:web & node ./scripts/playwright-codegen.js",
|
||||||
"test:e2e": "npx playwright test",
|
"test:e2e": "npx playwright test",
|
||||||
"test:report": "npx playwright show-report",
|
"test:report": "npx playwright show-report",
|
||||||
|
@ -2,6 +2,7 @@ const fs = require('fs');
|
|||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { forOwn, cloneDeep } = require('lodash');
|
const { forOwn, cloneDeep } = require('lodash');
|
||||||
|
const { getRunnerSummary } = require('@usebruno/common/runner');
|
||||||
const { exists, isFile, isDirectory } = require('../utils/filesystem');
|
const { exists, isFile, isDirectory } = require('../utils/filesystem');
|
||||||
const { runSingleRequest } = require('../runner/run-single-request');
|
const { runSingleRequest } = require('../runner/run-single-request');
|
||||||
const { bruToEnvJson, getEnvVars } = require('../utils/bru');
|
const { bruToEnvJson, getEnvVars } = require('../utils/bru');
|
||||||
@ -16,50 +17,18 @@ const command = 'run [filename]';
|
|||||||
const desc = 'Run a request';
|
const desc = 'Run a request';
|
||||||
|
|
||||||
const printRunSummary = (results) => {
|
const printRunSummary = (results) => {
|
||||||
let totalRequests = 0;
|
const {
|
||||||
let passedRequests = 0;
|
totalRequests,
|
||||||
let failedRequests = 0;
|
passedRequests,
|
||||||
let skippedRequests = 0;
|
failedRequests,
|
||||||
let totalAssertions = 0;
|
skippedRequests,
|
||||||
let passedAssertions = 0;
|
totalAssertions,
|
||||||
let failedAssertions = 0;
|
passedAssertions,
|
||||||
let totalTests = 0;
|
failedAssertions,
|
||||||
let passedTests = 0;
|
totalTests,
|
||||||
let failedTests = 0;
|
passedTests,
|
||||||
|
failedTests
|
||||||
for (const result of results) {
|
} = getRunnerSummary(results);
|
||||||
totalRequests += 1;
|
|
||||||
totalTests += result.testResults.length;
|
|
||||||
totalAssertions += result.assertionResults.length;
|
|
||||||
let anyFailed = false;
|
|
||||||
let hasAnyTestsOrAssertions = false;
|
|
||||||
for (const testResult of result.testResults) {
|
|
||||||
hasAnyTestsOrAssertions = true;
|
|
||||||
if (testResult.status === 'pass') {
|
|
||||||
passedTests += 1;
|
|
||||||
} else {
|
|
||||||
anyFailed = true;
|
|
||||||
failedTests += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const assertionResult of result.assertionResults) {
|
|
||||||
hasAnyTestsOrAssertions = true;
|
|
||||||
if (assertionResult.status === 'pass') {
|
|
||||||
passedAssertions += 1;
|
|
||||||
} else {
|
|
||||||
anyFailed = true;
|
|
||||||
failedAssertions += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasAnyTestsOrAssertions && result.skipped) {
|
|
||||||
skippedRequests += 1;
|
|
||||||
}
|
|
||||||
else if (!hasAnyTestsOrAssertions && result.error) {
|
|
||||||
failedRequests += 1;
|
|
||||||
} else {
|
|
||||||
passedRequests += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxLength = 12;
|
const maxLength = 12;
|
||||||
|
|
||||||
@ -99,7 +68,7 @@ const printRunSummary = (results) => {
|
|||||||
totalTests,
|
totalTests,
|
||||||
passedTests,
|
passedTests,
|
||||||
failedTests
|
failedTests
|
||||||
};
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createCollectionFromPath = (collectionPath) => {
|
const createCollectionFromPath = (collectionPath) => {
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
const { describe, it, expect } = require('@jest/globals');
|
|
||||||
|
|
||||||
const { printRunSummary } = require('../../src/commands/run');
|
|
||||||
|
|
||||||
describe('printRunSummary', () => {
|
|
||||||
// Suppress console.log output
|
|
||||||
jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
||||||
|
|
||||||
it('should produce the correct summary for a successful run', () => {
|
|
||||||
const results = [
|
|
||||||
{
|
|
||||||
testResults: [{ status: 'pass' }, { status: 'pass' }, { status: 'pass' }],
|
|
||||||
assertionResults: [{ status: 'pass' }, { status: 'pass' }],
|
|
||||||
error: null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testResults: [{ status: 'pass' }, { status: 'pass' }],
|
|
||||||
assertionResults: [{ status: 'pass' }, { status: 'pass' }, { status: 'pass' }],
|
|
||||||
error: null
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const summary = printRunSummary(results);
|
|
||||||
|
|
||||||
expect(summary.totalRequests).toBe(2);
|
|
||||||
expect(summary.passedRequests).toBe(2);
|
|
||||||
expect(summary.failedRequests).toBe(0);
|
|
||||||
expect(summary.totalAssertions).toBe(5);
|
|
||||||
expect(summary.passedAssertions).toBe(5);
|
|
||||||
expect(summary.failedAssertions).toBe(0);
|
|
||||||
expect(summary.totalTests).toBe(5);
|
|
||||||
expect(summary.passedTests).toBe(5);
|
|
||||||
expect(summary.failedTests).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should produce the correct summary for a failed run', () => {
|
|
||||||
const results = [
|
|
||||||
{
|
|
||||||
testResults: [{ status: 'fail' }, { status: 'pass' }, { status: 'pass' }],
|
|
||||||
assertionResults: [{ status: 'pass' }, { status: 'fail' }],
|
|
||||||
error: null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testResults: [{ status: 'pass' }, { status: 'fail' }],
|
|
||||||
assertionResults: [{ status: 'pass' }, { status: 'fail' }, { status: 'fail' }],
|
|
||||||
error: null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testResults: [],
|
|
||||||
assertionResults: [],
|
|
||||||
error: new Error('Request failed')
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const summary = printRunSummary(results);
|
|
||||||
|
|
||||||
expect(summary.totalRequests).toBe(3);
|
|
||||||
expect(summary.passedRequests).toBe(2);
|
|
||||||
expect(summary.failedRequests).toBe(1);
|
|
||||||
expect(summary.totalAssertions).toBe(5);
|
|
||||||
expect(summary.passedAssertions).toBe(2);
|
|
||||||
expect(summary.failedAssertions).toBe(3);
|
|
||||||
expect(summary.totalTests).toBe(5);
|
|
||||||
expect(summary.passedTests).toBe(3);
|
|
||||||
expect(summary.failedTests).toBe(2);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,81 +0,0 @@
|
|||||||
const { describe, it, expect } = require('@jest/globals');
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
const makeHtmlOutput = require('../../src/reporters/html');
|
|
||||||
|
|
||||||
describe('makeHtmlOutput', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.restoreAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should produce an html report', () => {
|
|
||||||
const outputJson = {
|
|
||||||
summary: {
|
|
||||||
totalRequests: 1,
|
|
||||||
passedRequests: 1,
|
|
||||||
failedRequests: 1,
|
|
||||||
totalAssertions: 1,
|
|
||||||
passedAssertions: 1,
|
|
||||||
failedAssertions: 1,
|
|
||||||
totalTests: 1,
|
|
||||||
passedTests: 1,
|
|
||||||
failedTests: 1
|
|
||||||
},
|
|
||||||
results: [
|
|
||||||
{
|
|
||||||
description: 'description provided',
|
|
||||||
suitename: 'Tests/Suite A',
|
|
||||||
request: {
|
|
||||||
method: 'GET',
|
|
||||||
url: 'https://ima.test'
|
|
||||||
},
|
|
||||||
assertionResults: [
|
|
||||||
{
|
|
||||||
lhsExpr: 'res.status',
|
|
||||||
rhsExpr: 'eq 200',
|
|
||||||
status: 'pass'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
lhsExpr: 'res.status',
|
|
||||||
rhsExpr: 'neq 200',
|
|
||||||
status: 'fail',
|
|
||||||
error: 'expected 200 to not equal 200'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
runtime: 1.2345678
|
|
||||||
},
|
|
||||||
{
|
|
||||||
request: {
|
|
||||||
method: 'GET',
|
|
||||||
url: 'https://imanother.test'
|
|
||||||
},
|
|
||||||
suitename: 'Tests/Suite B',
|
|
||||||
testResults: [
|
|
||||||
{
|
|
||||||
lhsExpr: 'res.status',
|
|
||||||
rhsExpr: 'eq 200',
|
|
||||||
description: 'A test that passes',
|
|
||||||
status: 'pass'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: 'A test that fails',
|
|
||||||
status: 'fail',
|
|
||||||
error: 'expected 200 to not equal 200',
|
|
||||||
status: 'fail'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
runtime: 2.3456789
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
makeHtmlOutput(outputJson, '/tmp/testfile.html');
|
|
||||||
|
|
||||||
const htmlReport = fs.writeFileSync.mock.calls[0][1];
|
|
||||||
expect(htmlReport).toContain(JSON.stringify(outputJson, null, 2));
|
|
||||||
});
|
|
||||||
});
|
|
@ -7,8 +7,14 @@
|
|||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
|
"require": "./dist/cjs/index.js",
|
||||||
"import": "./dist/esm/index.js",
|
"import": "./dist/esm/index.js",
|
||||||
"require": "./dist/cjs/index.js"
|
"types": "./dist/index.d.ts"
|
||||||
|
},
|
||||||
|
"./runner": {
|
||||||
|
"require": "./dist/runner/cjs/index.js",
|
||||||
|
"import": "./dist/runner/esm/index.js",
|
||||||
|
"types": "./dist/runner/index.d.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
@ -21,19 +27,19 @@
|
|||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"prebuild": "npm run clean",
|
"prebuild": "npm run clean",
|
||||||
"build": "rollup -c",
|
"build": "rollup -c rollup.config.js",
|
||||||
|
"watch": "rollup -c -w",
|
||||||
"prepack": "npm run test && npm run build"
|
"prepack": "npm run test && npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
|
||||||
"@faker-js/faker": "^9.7.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/preset-env": "^7.26.9",
|
"@babel/preset-env": "^7.26.9",
|
||||||
"@babel/preset-typescript": "^7.27.0",
|
"@babel/preset-typescript": "^7.27.0",
|
||||||
|
"@faker-js/faker": "^9.7.0",
|
||||||
"@jest/globals": "^29.7.0",
|
"@jest/globals": "^29.7.0",
|
||||||
"@rollup/plugin-commonjs": "^23.0.2",
|
"@rollup/plugin-commonjs": "^23.0.2",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
"@rollup/plugin-typescript": "^12.1.2",
|
"@rollup/plugin-typescript": "^12.1.2",
|
||||||
|
"@types/jest": "^29.5.14",
|
||||||
"babel-jest": "^29.7.0",
|
"babel-jest": "^29.7.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"rollup": "3.29.5",
|
"rollup": "3.29.5",
|
||||||
|
@ -7,29 +7,53 @@ const peerDepsExternal = require('rollup-plugin-peer-deps-external');
|
|||||||
|
|
||||||
const packageJson = require('./package.json');
|
const packageJson = require('./package.json');
|
||||||
|
|
||||||
module.exports = [
|
function createBuildConfig({ inputDir, input, cjsOutput, esmOutput }) {
|
||||||
{
|
return [
|
||||||
input: 'src/index.ts',
|
{
|
||||||
output: [
|
input,
|
||||||
{
|
output: [
|
||||||
file: packageJson.main,
|
{
|
||||||
format: 'cjs',
|
file: cjsOutput,
|
||||||
sourcemap: true
|
format: 'cjs',
|
||||||
},
|
sourcemap: true
|
||||||
{
|
},
|
||||||
file: packageJson.module,
|
{
|
||||||
format: 'esm',
|
file: esmOutput,
|
||||||
sourcemap: true
|
format: 'esm',
|
||||||
|
sourcemap: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
peerDepsExternal(),
|
||||||
|
nodeResolve(),
|
||||||
|
commonjs(),
|
||||||
|
typescript({
|
||||||
|
tsconfig: './tsconfig.json',
|
||||||
|
include: [inputDir]
|
||||||
|
}),
|
||||||
|
terser()
|
||||||
|
],
|
||||||
|
treeshake: {
|
||||||
|
moduleSideEffects: false
|
||||||
}
|
}
|
||||||
],
|
}
|
||||||
plugins: [
|
];
|
||||||
peerDepsExternal(),
|
}
|
||||||
nodeResolve({
|
|
||||||
extensions: ['.css']
|
// todo: configure declarations
|
||||||
}),
|
module.exports = [
|
||||||
commonjs(),
|
// Main package build
|
||||||
typescript({ tsconfig: './tsconfig.json' }),
|
...createBuildConfig({
|
||||||
terser()
|
inputDir: 'src/**/*',
|
||||||
]
|
input: 'src/index.ts',
|
||||||
}
|
cjsOutput: packageJson.main,
|
||||||
|
esmOutput: packageJson.module
|
||||||
|
}),
|
||||||
|
// reports/html
|
||||||
|
...createBuildConfig({
|
||||||
|
inputDir: 'src/runner/**/*',
|
||||||
|
input: 'src/runner/index.ts',
|
||||||
|
cjsOutput: 'dist/runner/cjs/index.js',
|
||||||
|
esmOutput: 'dist/runner/esm/index.js'
|
||||||
|
})
|
||||||
];
|
];
|
||||||
|
4
packages/bruno-common/src/runner/index.ts
Normal file
4
packages/bruno-common/src/runner/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { generateHtmlReport } from "./reports/html/generate-report";
|
||||||
|
import { getRunnerSummary } from "./runner-summary";
|
||||||
|
|
||||||
|
export { generateHtmlReport, getRunnerSummary };
|
@ -0,0 +1,39 @@
|
|||||||
|
import { T_RunnerResults } from "../../types";
|
||||||
|
import { isHtmlContentType, getContentType, redactImageData, encodeBase64 } from "../../utils";
|
||||||
|
import { getRunnerSummary } from "../../runner-summary";
|
||||||
|
import htmlTemplateString from "./template";
|
||||||
|
|
||||||
|
const generateHtmlReport = ({
|
||||||
|
runnerResults
|
||||||
|
}: {
|
||||||
|
runnerResults: T_RunnerResults[]
|
||||||
|
}): string => {
|
||||||
|
const resultsWithSummaryAndCleanData = runnerResults.map(({ iterationIndex, results }) => {
|
||||||
|
return {
|
||||||
|
iterationIndex,
|
||||||
|
results: results.map((result) => {
|
||||||
|
const { request, response } = result || {};
|
||||||
|
const requestContentType = request?.headers ? getContentType(request?.headers) : '';
|
||||||
|
const responseContentType = response?.headers ? getContentType(response?.headers) : '';
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
request: {
|
||||||
|
...result.request,
|
||||||
|
data: request?.data ? redactImageData(request?.data, requestContentType) : request?.data,
|
||||||
|
isHtml: isHtmlContentType(requestContentType)
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
...result.response,
|
||||||
|
data: response?.data ? redactImageData(response?.data, responseContentType) : response?.data,
|
||||||
|
isHtml: isHtmlContentType(responseContentType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
summary: getRunnerSummary(results)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const htmlString = htmlTemplateString(encodeBase64(JSON.stringify(resultsWithSummaryAndCleanData)));
|
||||||
|
return htmlString;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { generateHtmlReport }
|
654
packages/bruno-common/src/runner/reports/html/template.ts
Normal file
654
packages/bruno-common/src/runner/reports/html/template.ts
Normal file
@ -0,0 +1,654 @@
|
|||||||
|
export const htmlTemplateString = (resutsJsonString: string) =>`<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||||
|
<!-- Would use latest version, you'd better specify a version -->
|
||||||
|
<script src="https://unpkg.com/naive-ui"></script>
|
||||||
|
|
||||||
|
<title>Bruno</title>
|
||||||
|
<style>
|
||||||
|
.error > .status {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.success > .status {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-collapse-item.success > .n-collapse-item__header {
|
||||||
|
background-color: rgba(237, 247, 242, 1);
|
||||||
|
}
|
||||||
|
.n-collapse-item.error > .n-collapse-item__header {
|
||||||
|
background-color: rgba(251, 238, 241, 1);
|
||||||
|
}
|
||||||
|
.skipped > .status {
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.min-width-150 {
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<n-config-provider :theme="theme">
|
||||||
|
<n-layout embedded position="absolute" content-style="padding: 24px;">
|
||||||
|
<n-card>
|
||||||
|
<n-flex>
|
||||||
|
<n-page-header title="Bruno run dashboard">
|
||||||
|
<template #avatar>
|
||||||
|
<n-avatar size="large" style="background-color: transparent">
|
||||||
|
<svg id="emoji" width="34" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="color">
|
||||||
|
<path
|
||||||
|
fill="#F4AA41"
|
||||||
|
stroke="none"
|
||||||
|
d="M23.5,14.5855l-4.5,1.75l-7.25,8.5l-4.5,10.75l2,5.25c1.2554,3.7911,3.5231,7.1832,7.25,10l2.5-3.3333 c0,0,3.8218,7.7098,10.7384,8.9598c0,0,10.2616,1.936,15.5949-0.8765c3.4203-1.8037,4.4167-4.4167,4.4167-4.4167l3.4167-3.4167 l1.5833,2.3333l2.0833-0.0833l5.4167-7.25L64,37.3355l-0.1667-4.5l-2.3333-5.5l-4.8333-7.4167c0,0-2.6667-4.9167-8.1667-3.9167 c0,0-6.5-4.8333-11.8333-4.0833S32.0833,10.6688,23.5,14.5855z"
|
||||||
|
></path>
|
||||||
|
<polygon
|
||||||
|
fill="#EA5A47"
|
||||||
|
stroke="none"
|
||||||
|
points="36,47.2521 32.9167,49.6688 30.4167,49.6688 30.3333,53.5021 31.0833,57.0021 32.1667,58.9188 35,60.4188 39.5833,59.8355 41.1667,58.0855 42.1667,53.8355 41.9167,49.8355 39.9167,50.0855"
|
||||||
|
></polygon>
|
||||||
|
<polygon
|
||||||
|
fill="#3F3F3F"
|
||||||
|
stroke="none"
|
||||||
|
points="32.5,36.9188 30.9167,40.6688 33.0833,41.9188 34.3333,42.4188 38.6667,42.5855 41.5833,40.3355 39.8333,37.0855"
|
||||||
|
></polygon>
|
||||||
|
</g>
|
||||||
|
<g id="hair"></g>
|
||||||
|
<g id="skin"></g>
|
||||||
|
<g id="skin-shadow"></g>
|
||||||
|
<g id="line">
|
||||||
|
<path
|
||||||
|
fill="#000000"
|
||||||
|
stroke="none"
|
||||||
|
d="M29.5059,30.1088c0,0-1.8051,1.2424-2.7484,0.6679c-0.9434-0.5745-1.2424-1.8051-0.6679-2.7484 s1.805-1.2424,2.7484-0.6679S29.5059,30.1088,29.5059,30.1088z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-miterlimit="10"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M33.1089,37.006h6.1457c0.4011,0,0.7634,0.2397,0.9203,0.6089l1.1579,2.7245l-2.1792,1.1456 c-0.6156,0.3236-1.3654-0.0645-1.4567-0.754"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-miterlimit="10"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M34.7606,40.763c-0.1132,0.6268-0.7757,0.9895-1.3647,0.7471l-2.3132-0.952l1.0899-2.9035 c0.1465-0.3901,0.5195-0.6486,0.9362-0.6486"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-miterlimit="10"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M30.4364,50.0268c0,0-0.7187,8.7934,3.0072,9.9375c2.6459,0.8125,5.1497,0.5324,6.0625-0.25 c0.875-0.75,2.6323-4.4741,1.8267-9.6875"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="#000000"
|
||||||
|
stroke="none"
|
||||||
|
d="M44.2636,30.1088c0,0,1.805,1.2424,2.7484,0.6679c0.9434-0.5745,1.2424-1.8051,0.6679-2.7484 c-0.5745-0.9434-1.805-1.2424-2.7484-0.6679C43.9881,27.9349,44.2636,30.1088,44.2636,30.1088z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-miterlimit="10"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M25.6245,42.8393c-0.475,3.6024,2.2343,5.7505,4.2847,6.8414c1.1968,0.6367,2.6508,0.5182,3.7176-0.3181l2.581-2.0233l2.581,2.0233 c1.0669,0.8363,2.5209,0.9548,3.7176,0.3181c2.0504-1.0909,4.7597-3.239,4.2847-6.8414"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-miterlimit="10"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M19.9509,28.3572c-2.3166,5.1597-0.5084,13.0249,0.119,15.3759c0.122,0.4571,0.0755,0.9355-0.1271,1.3631l-1.9874,4.1937 c-0.623,1.3146-2.3934,1.5533-3.331,0.4409c-3.1921-3.7871-8.5584-11.3899-6.5486-16.686 c7.0625-18.6104,15.8677-18.1429,15.8677-18.1429c2.8453-1.9336,13.1042-6.9375,24.8125,0.875c0,0,8.6323-1.7175,14.9375,16.9375 c1.8036,5.3362-3.4297,12.8668-6.5506,16.6442c-0.9312,1.127-2.7162,0.8939-3.3423-0.4272l-1.9741-4.1656 c-0.2026-0.4275-0.2491-0.906-0.1271-1.3631c0.6275-2.3509,2.4356-10.2161,0.119-15.3759"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-miterlimit="10"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M52.6309,46.4628c0,0-3.0781,6.7216-7.8049,8.2712"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-miterlimit="10"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M19.437,46.969c0,0,3.0781,6.0823,7.8049,7.632"
|
||||||
|
></path>
|
||||||
|
<line
|
||||||
|
x1="36.2078"
|
||||||
|
x2="36.2078"
|
||||||
|
y1="47.3393"
|
||||||
|
y2="44.3093"
|
||||||
|
fill="none"
|
||||||
|
stroke="#000000"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-miterlimit="10"
|
||||||
|
stroke-width="2"
|
||||||
|
></line>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</n-avatar>
|
||||||
|
</template>
|
||||||
|
<template #extra>
|
||||||
|
<n-flex justify="end">
|
||||||
|
<n-switch v-model:value="darkMode" :rail-style="darkModeRailStyle">
|
||||||
|
<template #checked> Dark </template>
|
||||||
|
<template #unchecked> Light </template>
|
||||||
|
</n-switch>
|
||||||
|
</n-flex>
|
||||||
|
</template>
|
||||||
|
</n-page-header>
|
||||||
|
<n-tabs type="segment" animated v-model:value="currentTab">
|
||||||
|
<n-tab-pane name="summary" tab="Summary">
|
||||||
|
<n-flex justify="center" vertical>
|
||||||
|
<x-summary v-for="(result, index) in res" :res="result" :key="index"></x-summary>
|
||||||
|
</n-flex>
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="requests" tab="Requests">
|
||||||
|
<n-flex justify="center" vertical>
|
||||||
|
<x-requests v-for="(result, index) in res" :res="result" :key="index"></x-requests>
|
||||||
|
</n-flex>
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
</n-flex>
|
||||||
|
</n-card>
|
||||||
|
</n-layout>
|
||||||
|
</n-config-provider>
|
||||||
|
</div>
|
||||||
|
<script type="text/x-template" id="summary-component">
|
||||||
|
<n-flex vertical style="margin-bottom: 50px;">
|
||||||
|
<n-card>
|
||||||
|
<template #header>
|
||||||
|
<span style="font-size: 24px;">{{ iterationTitle }}</span>
|
||||||
|
</template>
|
||||||
|
<n-flex justify="center">
|
||||||
|
<n-flex justify="center">
|
||||||
|
<n-alert type="success">
|
||||||
|
<n-statistic
|
||||||
|
label="Total requests"
|
||||||
|
:value="summaryTotalRequests"
|
||||||
|
>
|
||||||
|
</n-statistic>
|
||||||
|
</n-alert>
|
||||||
|
<n-alert :type="summaryErrors ? 'error' : 'success'">
|
||||||
|
<n-statistic label="Total errors" :value="summaryErrors">
|
||||||
|
</n-statistic>
|
||||||
|
</n-alert>
|
||||||
|
<n-alert type="success">
|
||||||
|
<n-statistic
|
||||||
|
label="Total Controls"
|
||||||
|
:value="summaryTotalControls"
|
||||||
|
>
|
||||||
|
</n-statistic>
|
||||||
|
</n-alert>
|
||||||
|
<n-alert :type="summaryFailedControls ? 'error' : 'success'">
|
||||||
|
<n-statistic
|
||||||
|
label="Total Failed Controls"
|
||||||
|
:value="summaryFailedControls"
|
||||||
|
>
|
||||||
|
</n-statistic>
|
||||||
|
</n-alert>
|
||||||
|
<n-alert type="warning" v-if="summarySkippedRequests">
|
||||||
|
<n-statistic label="Skipped requests" :value="summarySkippedRequests">
|
||||||
|
</n-statistic>
|
||||||
|
</n-alert>
|
||||||
|
<n-statistic
|
||||||
|
label="Total run duration"
|
||||||
|
:value="Math.round(totalRunDuration*1000)/1000"
|
||||||
|
>
|
||||||
|
<template #suffix>s</template>
|
||||||
|
</n-statistic>
|
||||||
|
</n-flex>
|
||||||
|
</n-flex>
|
||||||
|
</n-card>
|
||||||
|
<n-data-table :columns="summaryColumns" :data="summaryData" />
|
||||||
|
</n-flex>
|
||||||
|
</script>
|
||||||
|
<script type="text/x-template" id="requests-component">
|
||||||
|
<n-card>
|
||||||
|
<template #header>
|
||||||
|
<span style="font-size: 24px;">{{ iterationTitle }}</span>
|
||||||
|
</template>
|
||||||
|
<n-flex vertical style="margin-bottom: 50px">
|
||||||
|
<n-switch
|
||||||
|
v-model:value="onlyFailed"
|
||||||
|
:rail-style="railStyle"
|
||||||
|
>
|
||||||
|
<template #checked> Only Failed </template>
|
||||||
|
<template #unchecked> Show All </template>
|
||||||
|
</n-switch>
|
||||||
|
|
||||||
|
<n-collapse>
|
||||||
|
<x-result v-for="(result, index) in results" :result="result" :key="results.length"></x-result>
|
||||||
|
</n-collapse>
|
||||||
|
</n-flex>
|
||||||
|
</n-card>
|
||||||
|
</script>
|
||||||
|
<script type="text/x-template" id="result-component">
|
||||||
|
<n-collapse-item
|
||||||
|
:name="resultTitle"
|
||||||
|
arrow-placement="right"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<n-alert
|
||||||
|
:type="getAlertType"
|
||||||
|
:bordered="false"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
{{result.path}} - {{result.response.status === 'skipped' ? 'Request Skipped' : (totalPassed + '/' + total + ' Passed')}} {{hasError ? " - (request failed)" : "" }}
|
||||||
|
</template>
|
||||||
|
</n-alert>
|
||||||
|
</template>
|
||||||
|
<n-flex vertical>
|
||||||
|
<n-grid x-gap="12" :cols="2">
|
||||||
|
<n-gi>
|
||||||
|
<n-card title="REQUEST INFORMATION">
|
||||||
|
<n-list>
|
||||||
|
<n-list-item>
|
||||||
|
<n-thing
|
||||||
|
title="File"
|
||||||
|
:description="result.path"
|
||||||
|
/>
|
||||||
|
</n-list-item>
|
||||||
|
<n-list-item>
|
||||||
|
<n-thing
|
||||||
|
title="Request Method"
|
||||||
|
:description="result.request.method"
|
||||||
|
/>
|
||||||
|
</n-list-item>
|
||||||
|
<n-list-item>
|
||||||
|
<n-thing
|
||||||
|
title="Request URL"
|
||||||
|
:description="result.request.url"
|
||||||
|
/>
|
||||||
|
</n-list-item>
|
||||||
|
</n-list>
|
||||||
|
</n-card>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-card title="RESPONSE INFORMATION">
|
||||||
|
<n-list>
|
||||||
|
<n-list-item>
|
||||||
|
<n-thing
|
||||||
|
title="Response Code"
|
||||||
|
:description="'' + result.response.status"
|
||||||
|
/>
|
||||||
|
</n-list-item>
|
||||||
|
<n-list-item>
|
||||||
|
<n-thing
|
||||||
|
title="Response time"
|
||||||
|
:description="result.response.responseTime + ' ms'"
|
||||||
|
/>
|
||||||
|
</n-list-item>
|
||||||
|
<n-list-item>
|
||||||
|
<n-thing
|
||||||
|
title="Test duration"
|
||||||
|
:description="testDuration"
|
||||||
|
/>
|
||||||
|
</n-list-item>
|
||||||
|
</n-list>
|
||||||
|
</n-card>
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
|
<n-alert v-if="hasError" title="Error" type="error">
|
||||||
|
{{result.error}}
|
||||||
|
</n-alert>
|
||||||
|
<n-card title="REQUEST HEADERS">
|
||||||
|
<n-data-table
|
||||||
|
:columns="headerColumns"
|
||||||
|
:data="headerDataRequest"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
|
<n-card
|
||||||
|
v-if="result.request.data"
|
||||||
|
title="REQUEST BODY"
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
v-if="result.request.isHtml"
|
||||||
|
:srcdoc="result.request.data"
|
||||||
|
style="width: 100%; height: 400px; border: none;"
|
||||||
|
></iframe>
|
||||||
|
|
||||||
|
<pre v-else>{{ result.request.data }}</pre>
|
||||||
|
</n-card>
|
||||||
|
<n-card title="RESPONSE HEADERS">
|
||||||
|
<n-data-table
|
||||||
|
:columns="headerColumns"
|
||||||
|
:data="headerDataResponse"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
|
<n-card
|
||||||
|
v-if="result.response.data"
|
||||||
|
title="RESPONSE BODY"
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
v-if="result.response.isHtml"
|
||||||
|
:srcdoc="result.response.data"
|
||||||
|
style="width: 100%; height: 400px; border: none;"
|
||||||
|
></iframe>
|
||||||
|
|
||||||
|
<pre v-else>{{ result.response.data }}</pre> </n-card>
|
||||||
|
<n-card title="ASSERTIONS INFORMATION">
|
||||||
|
<n-data-table
|
||||||
|
:columns="assertionsColumns"
|
||||||
|
:data="result.assertionResults"
|
||||||
|
:row-class-name="assertionsRowClassName"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
|
<n-card title="TESTS INFORMATION">
|
||||||
|
<n-data-table
|
||||||
|
:columns="testsColumns"
|
||||||
|
:data="result.testResults"
|
||||||
|
:row-class-name="testsRowClassName"
|
||||||
|
/>
|
||||||
|
</n-card>
|
||||||
|
</n-flex>
|
||||||
|
</n-collapse-item>
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
const { createApp, ref, computed, onMounted } = Vue;
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
function decodeBase64(base64) {
|
||||||
|
const binary = atob(base64);
|
||||||
|
const bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
|
||||||
|
return new TextDecoder().decode(bytes);
|
||||||
|
}
|
||||||
|
const res = JSON.parse(decodeBase64('${resutsJsonString}'));
|
||||||
|
|
||||||
|
const currentTab = ref('summary');
|
||||||
|
|
||||||
|
const getTabFromQueryParam = () => {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const tab = urlParams.get('tab');
|
||||||
|
return tab && ['summary', 'requests'].includes(tab) ? tab : 'summary';
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
currentTab.value = getTabFromQueryParam();
|
||||||
|
});
|
||||||
|
|
||||||
|
const darkMode = ref(false);
|
||||||
|
const theme = computed(() => {
|
||||||
|
return darkMode.value ? naive.darkTheme : null;
|
||||||
|
});
|
||||||
|
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||||
|
darkMode.value = true;
|
||||||
|
}
|
||||||
|
// To watch for os theme changes
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (event) => {
|
||||||
|
darkMode.value = event.matches;
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
res,
|
||||||
|
theme,
|
||||||
|
darkMode,
|
||||||
|
darkModeRailStyle: () => ({ background: 'var(--n-rail-color)' }),
|
||||||
|
currentTab
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const app = Vue.createApp(App);
|
||||||
|
|
||||||
|
app.component('x-summary', {
|
||||||
|
template: '#summary-component',
|
||||||
|
props: ['res'],
|
||||||
|
setup(props) {
|
||||||
|
const summaryColumns = [
|
||||||
|
{
|
||||||
|
title: 'SUMMARY ITEM',
|
||||||
|
key: 'title'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'TOTAL',
|
||||||
|
key: 'total'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'PASSED',
|
||||||
|
key: 'passed'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'FAILED',
|
||||||
|
key: 'failed'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'SKIPPED',
|
||||||
|
key: 'skipped'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'ERROR',
|
||||||
|
key: 'error'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const summaryData = computed(() => [
|
||||||
|
{
|
||||||
|
title: 'Requests',
|
||||||
|
total: props.res.summary.totalRequests,
|
||||||
|
passed: props.res.summary.passedRequests,
|
||||||
|
failed: props.res.summary.failedRequests,
|
||||||
|
skipped: props.res.summary.skippedRequests,
|
||||||
|
error: props.res.summary.errorRequests
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Assertions',
|
||||||
|
total: props.res.summary.totalAssertions,
|
||||||
|
passed: props.res.summary.passedAssertions,
|
||||||
|
failed: props.res.summary.failedAssertions,
|
||||||
|
skipped: '-',
|
||||||
|
error: '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Tests',
|
||||||
|
total: props.res.summary.totalTests,
|
||||||
|
passed: props.res.summary.passedTests,
|
||||||
|
failed: props.res.summary.failedTests,
|
||||||
|
skipped: '-',
|
||||||
|
error: '-'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
const summaryTotalRequests = computed(() => {
|
||||||
|
return props.res.summary.totalRequests;
|
||||||
|
});
|
||||||
|
const summaryTotalControls = computed(() => {
|
||||||
|
return props.res.summary.totalTests + props.res.summary.totalAssertions;
|
||||||
|
});
|
||||||
|
const summaryFailedControls = computed(
|
||||||
|
() => props?.res?.summary?.failedTests + props?.res?.summary?.failedAssertions
|
||||||
|
);
|
||||||
|
const summarySkippedRequests = computed(() => props?.res?.summary?.skippedRequests || 0);
|
||||||
|
const summaryErrors = computed(() => props?.res?.results?.filter((r) => r.error || r.status === 'error').length) || 0;
|
||||||
|
const totalRunDuration = computed(() => props.res?.results?.reduce((total, result) => result.runDuration + total, 0));
|
||||||
|
const iterationIndex = Number(props.res.iterationIndex) + 1;
|
||||||
|
return {
|
||||||
|
summaryColumns,
|
||||||
|
summaryData,
|
||||||
|
summaryTotalControls,
|
||||||
|
summaryTotalRequests,
|
||||||
|
summaryFailedControls,
|
||||||
|
summarySkippedRequests,
|
||||||
|
summaryErrors,
|
||||||
|
totalRunDuration,
|
||||||
|
iterationTitle: 'Iteration ' + iterationIndex
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.component('x-requests', {
|
||||||
|
template: '#requests-component',
|
||||||
|
props: ['res'],
|
||||||
|
setup(props) {
|
||||||
|
const onlyFailed = ref(false);
|
||||||
|
const filteredResults = computed(() => {
|
||||||
|
if (onlyFailed.value) {
|
||||||
|
return props?.res?.results?.filter(
|
||||||
|
(r) =>
|
||||||
|
r.status === 'error' ||
|
||||||
|
!!r?.testResults?.find((t) => t.status !== 'pass') ||
|
||||||
|
!!r?.assertionResults?.find((t) => t.status !== 'pass')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return props.res.results;
|
||||||
|
});
|
||||||
|
const iterationIndex = Number(props.res.iterationIndex) + 1;
|
||||||
|
return {
|
||||||
|
onlyFailed,
|
||||||
|
results: filteredResults,
|
||||||
|
railStyle: ({ checked }) => {
|
||||||
|
const style = {};
|
||||||
|
if (checked) {
|
||||||
|
style.background = '#d03050';
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
},
|
||||||
|
iterationTitle: 'Iteration ' + iterationIndex
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.component('x-result', {
|
||||||
|
template: '#result-component',
|
||||||
|
props: ['result'],
|
||||||
|
setup(props) {
|
||||||
|
const headerColumns = [
|
||||||
|
{
|
||||||
|
title: 'Header Name',
|
||||||
|
key: 'name',
|
||||||
|
className: 'min-width-150'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Header Value',
|
||||||
|
key: 'value'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const assertionsColumns = [
|
||||||
|
{
|
||||||
|
title: 'Expression',
|
||||||
|
key: 'lhsExpr'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Operator',
|
||||||
|
key: 'operator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Operand',
|
||||||
|
key: 'rhsOperand'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Status',
|
||||||
|
key: 'status',
|
||||||
|
className: 'status'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Error',
|
||||||
|
key: 'error'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const assertionsRowClassName = (row) => {
|
||||||
|
return row.status === 'fail' ? 'error' : 'success';
|
||||||
|
};
|
||||||
|
const testsRowClassName = (row) => {
|
||||||
|
if (row.status === 'skipped') return 'skipped';
|
||||||
|
return row.status === 'fail' ? 'error' : 'success';
|
||||||
|
};
|
||||||
|
const testsColumns = [
|
||||||
|
{
|
||||||
|
title: 'Description',
|
||||||
|
key: 'description'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Status',
|
||||||
|
key: 'status',
|
||||||
|
className: 'status'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Error',
|
||||||
|
key: 'error'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
function mapHeaderToTableData(headers) {
|
||||||
|
if (!headers) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return Object.keys(headers).map((name) => ({
|
||||||
|
name,
|
||||||
|
value: headers[name]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const headerDataRequest = computed(() => {
|
||||||
|
return mapHeaderToTableData(props?.result?.request?.headers);
|
||||||
|
});
|
||||||
|
const headerDataResponse = computed(() => {
|
||||||
|
return mapHeaderToTableData(props?.result?.response?.headers);
|
||||||
|
});
|
||||||
|
const totalPassed = computed(() => {
|
||||||
|
return (
|
||||||
|
(props?.result?.testResults?.filter((t) => t.status === 'pass').length || 0) +
|
||||||
|
(props?.result?.assertionResults?.filter((t) => t.status === 'pass').length || 0)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const total = computed(() => {
|
||||||
|
return (props?.result?.testResults?.length || 0) + (props?.result?.assertionResults?.length || 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasError = computed(() => !!props?.result?.error || props?.result?.status === 'error');
|
||||||
|
const hasFailure = computed(() => total.value !== totalPassed.value);
|
||||||
|
const testDuration = computed(() => Math.round(props?.result?.runDuration * 1000) + ' ms');
|
||||||
|
const resultTitle = computed(() => props?.result?.path + ' ' + props?.result?.response?.status + ' ' + props?.result?.response?.statusText);
|
||||||
|
const getAlertType = computed(() => {
|
||||||
|
if (props.result.response.status === 'skipped') {
|
||||||
|
return 'warning';
|
||||||
|
}
|
||||||
|
return hasError.value || hasFailure.value ? 'error' : 'success';
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
headerColumns,
|
||||||
|
headerDataRequest,
|
||||||
|
headerDataResponse,
|
||||||
|
assertionsColumns,
|
||||||
|
assertionsRowClassName,
|
||||||
|
testsRowClassName,
|
||||||
|
totalPassed,
|
||||||
|
total,
|
||||||
|
hasFailure,
|
||||||
|
hasError,
|
||||||
|
testsColumns,
|
||||||
|
result: props.result,
|
||||||
|
testDuration,
|
||||||
|
resultTitle,
|
||||||
|
getAlertType,
|
||||||
|
iterationIndex: props?.result?.iterationIndex
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use(naive);
|
||||||
|
app.mount('#app');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default htmlTemplateString;
|
68
packages/bruno-common/src/runner/runner-summary.ts
Normal file
68
packages/bruno-common/src/runner/runner-summary.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { T_RunnerRequestExecutionResult, T_RunSummary } from "./types";
|
||||||
|
|
||||||
|
// todo: this is generic, not specific to html, can be moved out of the report/html sub-package
|
||||||
|
export const getRunnerSummary = (results: T_RunnerRequestExecutionResult[]): T_RunSummary => {
|
||||||
|
let totalRequests = 0;
|
||||||
|
let passedRequests = 0;
|
||||||
|
let failedRequests = 0;
|
||||||
|
let errorRequests = 0;
|
||||||
|
let skippedRequests = 0;
|
||||||
|
let totalAssertions = 0;
|
||||||
|
let passedAssertions = 0;
|
||||||
|
let failedAssertions = 0;
|
||||||
|
let totalTests = 0;
|
||||||
|
let passedTests = 0;
|
||||||
|
let failedTests = 0;
|
||||||
|
|
||||||
|
for (const result of results || []) {
|
||||||
|
const { status, testResults, assertionResults } = result;
|
||||||
|
totalRequests += 1;
|
||||||
|
totalTests += Number(testResults?.length) || 0;
|
||||||
|
totalAssertions += Number(assertionResults?.length) || 0;
|
||||||
|
|
||||||
|
if (status === 'skipped') {
|
||||||
|
skippedRequests += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let anyFailed = false;
|
||||||
|
for (const testResult of testResults || []) {
|
||||||
|
if (testResult.status === "pass") {
|
||||||
|
passedTests += 1;
|
||||||
|
} else {
|
||||||
|
anyFailed = true;
|
||||||
|
failedTests += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const assertionResult of assertionResults || []) {
|
||||||
|
if (assertionResult.status === "pass") {
|
||||||
|
passedAssertions += 1;
|
||||||
|
} else {
|
||||||
|
anyFailed = true;
|
||||||
|
failedAssertions += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!anyFailed && status !== "error") {
|
||||||
|
passedRequests += 1;
|
||||||
|
} else if (anyFailed) {
|
||||||
|
failedRequests += 1;
|
||||||
|
} else {
|
||||||
|
errorRequests += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalRequests,
|
||||||
|
passedRequests,
|
||||||
|
failedRequests,
|
||||||
|
errorRequests,
|
||||||
|
skippedRequests,
|
||||||
|
totalAssertions,
|
||||||
|
passedAssertions,
|
||||||
|
failedAssertions,
|
||||||
|
totalTests,
|
||||||
|
passedTests,
|
||||||
|
failedTests,
|
||||||
|
};
|
||||||
|
};
|
114
packages/bruno-common/src/runner/types/index.ts
Normal file
114
packages/bruno-common/src/runner/types/index.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
// assertion results types
|
||||||
|
type T_AssertionPassResult = {
|
||||||
|
lhsExpr: string;
|
||||||
|
rhsExpr: string;
|
||||||
|
rhsOperand: string;
|
||||||
|
operator: string;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type T_AssertionFailResult = {
|
||||||
|
lhsExpr: string;
|
||||||
|
rhsExpr: string;
|
||||||
|
rhsOperand: string;
|
||||||
|
operator: string;
|
||||||
|
status: string;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type T_AssertionResult = T_AssertionPassResult | T_AssertionFailResult;
|
||||||
|
|
||||||
|
// test results types
|
||||||
|
type T_TestPassResult = {
|
||||||
|
status: string;
|
||||||
|
description: string;
|
||||||
|
uid?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type T_TestFailResult = {
|
||||||
|
status: string;
|
||||||
|
description: string;
|
||||||
|
error: string;
|
||||||
|
uid?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type T_TestResult = T_TestPassResult | T_TestFailResult;
|
||||||
|
|
||||||
|
type T_EmptyRequest = {
|
||||||
|
method?: null | undefined;
|
||||||
|
url?: null | undefined;
|
||||||
|
headers?: null | undefined;
|
||||||
|
data?: null | undefined;
|
||||||
|
isHtml?: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// request types
|
||||||
|
type T_Request = {
|
||||||
|
method: string;
|
||||||
|
url: string;
|
||||||
|
headers: Record<string, string | number | undefined>;
|
||||||
|
data: string | object | null | boolean | number;
|
||||||
|
isHtml?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type T_EmptyResponse = {
|
||||||
|
status?: null | undefined;
|
||||||
|
statusText?: null | undefined;
|
||||||
|
headers?: null | undefined;
|
||||||
|
data?: null | undefined;
|
||||||
|
responseTime?: number | undefined;
|
||||||
|
isHtml?: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
type T_SkippedResponse = {
|
||||||
|
status?: string | null | undefined;
|
||||||
|
statusText?: string | null | undefined;
|
||||||
|
headers?: null | undefined;
|
||||||
|
data?: null | undefined;
|
||||||
|
responseTime?: number | undefined;
|
||||||
|
isHtml?: boolean | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// response types
|
||||||
|
type T_Response = {
|
||||||
|
status: number | string;
|
||||||
|
statusText: string;
|
||||||
|
headers: Record<string, string | number | undefined>;
|
||||||
|
data: string | object | null | boolean | number;
|
||||||
|
isHtml?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
// result type
|
||||||
|
export type T_RunnerRequestExecutionResult = {
|
||||||
|
iterationIndex: number;
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
request: T_EmptyRequest | T_Request;
|
||||||
|
response: T_EmptyResponse | T_Response | T_SkippedResponse;
|
||||||
|
status: null | undefined | string;
|
||||||
|
error: null | undefined | string;
|
||||||
|
assertionResults?: T_AssertionResult[];
|
||||||
|
testResults?: T_TestResult[];
|
||||||
|
runDuration: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type T_RunnerResults = {
|
||||||
|
iterationIndex: number;
|
||||||
|
iterationData?: any; // todo - csv/json row data
|
||||||
|
results: T_RunnerRequestExecutionResult[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// run summary type
|
||||||
|
export type T_RunSummary = {
|
||||||
|
totalRequests: number;
|
||||||
|
passedRequests: number;
|
||||||
|
failedRequests: number;
|
||||||
|
errorRequests: number;
|
||||||
|
skippedRequests: number;
|
||||||
|
totalAssertions: number;
|
||||||
|
passedAssertions: number;
|
||||||
|
failedAssertions: number;
|
||||||
|
totalTests: number;
|
||||||
|
passedTests: number;
|
||||||
|
failedTests: number;
|
||||||
|
}
|
31
packages/bruno-common/src/runner/utils/index.ts
Normal file
31
packages/bruno-common/src/runner/utils/index.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
export const encodeBase64 = (str: string) => {
|
||||||
|
const bytes = new TextEncoder().encode(str);
|
||||||
|
const binary = bytes.reduce((acc, byte) => acc + String.fromCharCode(byte), '');
|
||||||
|
return btoa(binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const decodeBase64 = (base64: string) => {
|
||||||
|
const binary = atob(base64);
|
||||||
|
const bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
|
||||||
|
return new TextDecoder().decode(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getContentType = (headers: Record<string, string | number | undefined>): string => {
|
||||||
|
if (!headers || typeof headers !== 'object') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const contentType = Object.entries(headers)
|
||||||
|
.find(([key]) => key.toLowerCase() === 'content-type')?.[1];
|
||||||
|
return typeof contentType === 'string' ? contentType : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isHtmlContentType = (contentType: string) => {
|
||||||
|
return contentType?.includes("html");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const redactImageData = (data: string | object | number | boolean, contentType: string) => {
|
||||||
|
if (contentType?.includes("image")) {
|
||||||
|
return "Response content redacted (image data)";
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
@ -1003,7 +1003,9 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
responseReceived: {
|
responseReceived: {
|
||||||
status: 'skipped',
|
status: 'skipped',
|
||||||
statusText: 'request skipped via pre-request script',
|
statusText: 'request skipped via pre-request script',
|
||||||
data: null
|
data: null,
|
||||||
|
responseTime: 0,
|
||||||
|
headers: null
|
||||||
},
|
},
|
||||||
...eventData
|
...eventData
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user