diff --git a/package-lock.json b/package-lock.json index f0c612916..ff174d7e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -1475,7 +1474,6 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", - "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -1506,7 +1504,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1524,7 +1521,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/@babel/generator": { @@ -1804,7 +1800,6 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.25.9", @@ -7789,7 +7784,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "dev": true, "license": "MIT" }, "node_modules/@types/lodash": { @@ -7812,7 +7806,6 @@ "version": "12.2.3", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/linkify-it": "*", @@ -7823,7 +7816,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "dev": true, "license": "MIT" }, "node_modules/@types/ms": { @@ -11068,7 +11060,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -12679,7 +12670,6 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -13647,7 +13637,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -24207,7 +24196,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -26304,18 +26293,19 @@ "name": "@usebruno/common", "version": "0.1.0", "license": "MIT", - "dependencies": { - "@faker-js/faker": "^9.7.0" - }, "devDependencies": { "@babel/preset-env": "^7.26.9", "@babel/preset-typescript": "^7.27.0", + "@faker-js/faker": "^9.7.0", "@jest/globals": "^29.7.0", "@rollup/plugin-commonjs": "^23.0.2", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-typescript": "^12.1.2", + "@types/jest": "^29.5.14", "babel-jest": "^29.7.0", + "cheerio": "^1.0.0", "moment": "^2.29.4", + "playwright": "^1.52.0", "rollup": "3.29.5", "rollup-plugin-dts": "^5.0.0", "rollup-plugin-peer-deps-external": "^2.2.4", @@ -26708,6 +26698,7 @@ "version": "9.7.0", "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.7.0.tgz", "integrity": "sha512-aozo5vqjCmDoXLNUJarFZx2IN/GgGaogY4TMJ6so/WLZOWpSV7fvj2dmrV6sEAnUm1O7aCrhTibjpzeDFgNqbg==", + "dev": true, "funding": [ { "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": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -26831,6 +26837,38 @@ "dev": true, "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": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", diff --git a/package.json b/package.json index 074e7aa9b..b8690d75c 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "build:electron:deb": "./scripts/build-electron.sh deb", "build:electron:rpm": "./scripts/build-electron.sh rpm", "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:e2e": "npx playwright test", "test:report": "npx playwright show-report", diff --git a/packages/bruno-cli/src/commands/run.js b/packages/bruno-cli/src/commands/run.js index ba1e671dc..57e334e86 100644 --- a/packages/bruno-cli/src/commands/run.js +++ b/packages/bruno-cli/src/commands/run.js @@ -2,6 +2,7 @@ const fs = require('fs'); const chalk = require('chalk'); const path = require('path'); const { forOwn, cloneDeep } = require('lodash'); +const { getRunnerSummary } = require('@usebruno/common/runner'); const { exists, isFile, isDirectory } = require('../utils/filesystem'); const { runSingleRequest } = require('../runner/run-single-request'); const { bruToEnvJson, getEnvVars } = require('../utils/bru'); @@ -16,50 +17,18 @@ const command = 'run [filename]'; const desc = 'Run a request'; const printRunSummary = (results) => { - let totalRequests = 0; - let passedRequests = 0; - let failedRequests = 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) { - 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 { + totalRequests, + passedRequests, + failedRequests, + skippedRequests, + totalAssertions, + passedAssertions, + failedAssertions, + totalTests, + passedTests, + failedTests + } = getRunnerSummary(results); const maxLength = 12; @@ -99,7 +68,7 @@ const printRunSummary = (results) => { totalTests, passedTests, failedTests - }; + } }; const createCollectionFromPath = (collectionPath) => { diff --git a/packages/bruno-cli/tests/commands/run.spec.js b/packages/bruno-cli/tests/commands/run.spec.js deleted file mode 100644 index 10cdf42b4..000000000 --- a/packages/bruno-cli/tests/commands/run.spec.js +++ /dev/null @@ -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); - }); -}); diff --git a/packages/bruno-cli/tests/reporters/html.spec.js b/packages/bruno-cli/tests/reporters/html.spec.js deleted file mode 100644 index b45e57f41..000000000 --- a/packages/bruno-cli/tests/reporters/html.spec.js +++ /dev/null @@ -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)); - }); -}); diff --git a/packages/bruno-common/package.json b/packages/bruno-common/package.json index 2664d1a84..a29f586ec 100644 --- a/packages/bruno-common/package.json +++ b/packages/bruno-common/package.json @@ -7,8 +7,14 @@ "types": "dist/index.d.ts", "exports": { ".": { + "require": "./dist/cjs/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": [ @@ -21,19 +27,19 @@ "test": "jest", "test:watch": "jest --watch", "prebuild": "npm run clean", - "build": "rollup -c", + "build": "rollup -c rollup.config.js", + "watch": "rollup -c -w", "prepack": "npm run test && npm run build" }, - "dependencies": { - "@faker-js/faker": "^9.7.0" - }, "devDependencies": { "@babel/preset-env": "^7.26.9", "@babel/preset-typescript": "^7.27.0", + "@faker-js/faker": "^9.7.0", "@jest/globals": "^29.7.0", "@rollup/plugin-commonjs": "^23.0.2", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-typescript": "^12.1.2", + "@types/jest": "^29.5.14", "babel-jest": "^29.7.0", "moment": "^2.29.4", "rollup": "3.29.5", diff --git a/packages/bruno-common/rollup.config.js b/packages/bruno-common/rollup.config.js index 51fc15a4e..8eec5127f 100644 --- a/packages/bruno-common/rollup.config.js +++ b/packages/bruno-common/rollup.config.js @@ -7,29 +7,53 @@ const peerDepsExternal = require('rollup-plugin-peer-deps-external'); const packageJson = require('./package.json'); -module.exports = [ - { - input: 'src/index.ts', - output: [ - { - file: packageJson.main, - format: 'cjs', - sourcemap: true - }, - { - file: packageJson.module, - format: 'esm', - sourcemap: true +function createBuildConfig({ inputDir, input, cjsOutput, esmOutput }) { + return [ + { + input, + output: [ + { + file: cjsOutput, + format: 'cjs', + sourcemap: true + }, + { + file: esmOutput, + format: 'esm', + sourcemap: true + } + ], + plugins: [ + peerDepsExternal(), + nodeResolve(), + commonjs(), + typescript({ + tsconfig: './tsconfig.json', + include: [inputDir] + }), + terser() + ], + treeshake: { + moduleSideEffects: false } - ], - plugins: [ - peerDepsExternal(), - nodeResolve({ - extensions: ['.css'] - }), - commonjs(), - typescript({ tsconfig: './tsconfig.json' }), - terser() - ] - } + } + ]; +} + +// todo: configure declarations +module.exports = [ + // Main package build + ...createBuildConfig({ + 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' + }) ]; diff --git a/packages/bruno-common/src/runner/index.ts b/packages/bruno-common/src/runner/index.ts new file mode 100644 index 000000000..3e3c842b0 --- /dev/null +++ b/packages/bruno-common/src/runner/index.ts @@ -0,0 +1,4 @@ +import { generateHtmlReport } from "./reports/html/generate-report"; +import { getRunnerSummary } from "./runner-summary"; + +export { generateHtmlReport, getRunnerSummary }; \ No newline at end of file diff --git a/packages/bruno-common/src/runner/reports/html/generate-report.ts b/packages/bruno-common/src/runner/reports/html/generate-report.ts new file mode 100644 index 000000000..378a75197 --- /dev/null +++ b/packages/bruno-common/src/runner/reports/html/generate-report.ts @@ -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 } \ No newline at end of file diff --git a/packages/bruno-common/src/runner/reports/html/template.ts b/packages/bruno-common/src/runner/reports/html/template.ts new file mode 100644 index 000000000..d19e2077f --- /dev/null +++ b/packages/bruno-common/src/runner/reports/html/template.ts @@ -0,0 +1,654 @@ +export const htmlTemplateString = (resutsJsonString: string) =>` + +
+ + + + + +