diff --git a/packages/bruno-api-docs/e2e/tests/playground/script-execution.spec.ts b/packages/bruno-api-docs/e2e/tests/playground/script-execution.spec.ts index a05d209d..cdd4d31b 100644 --- a/packages/bruno-api-docs/e2e/tests/playground/script-execution.spec.ts +++ b/packages/bruno-api-docs/e2e/tests/playground/script-execution.spec.ts @@ -24,6 +24,97 @@ test('tv4 validates against a schema', function () { }); `; +const LIBRARY_FUNCTIONS_SCRIPT = ` +const chai = require('chai'); +const path = require('path'); +const Ajv = require('ajv'); +const addFormats = require('ajv-formats'); + +test('chai.expect and chai.assert', function () { + chai.expect(2 + 3).to.equal(5); + if (typeof chai.assert === 'function') { + chai.assert.equal(4, 4); + } +}); + +test('Buffer btoa atob globals', function () { + expect(Buffer.from('hello').toString('base64')).to.equal('aGVsbG8='); + expect(Buffer.from('6272756e6f', 'hex').toString('utf8')).to.equal('bruno'); + expect(btoa('hello')).to.equal('aGVsbG8='); + expect(atob('aGVsbG8=')).to.equal('hello'); +}); + +test('path.resolve join and basename', function () { + expect(path.resolve('/a/b', '../c')).to.equal('/a/c'); + expect(path.join('a', 'b')).to.include('b'); + expect(path.basename('foo.txt')).to.equal('foo.txt'); +}); + +test('ajv with ajv-formats', function () { + const ajv = new Ajv(); + addFormats(ajv); + const validate = ajv.compile({ type: 'string', format: 'email' }); + expect(validate('qa@usebruno.com')).to.equal(true); + expect(validate('not-an-email')).to.equal(false); +}); +`; + +const AXIOS_GET_SCRIPT = ` +const axios = require('axios'); +const url = bru.interpolate('{{host}}/api/users'); +const echo = await axios.get(url); + +test('axios.get via the sandbox shim', function () { + expect(echo.status).to.equal(200); + expect(echo.data.users[0].name).to.equal('Ada'); +}); +`; + +const JWT_UNSUPPORTED_SCRIPT = ` +test('jsonwebtoken is not supported in the playground', function () { + var message = ''; + try { + require('jsonwebtoken'); + } catch (e) { + message = String(e && e.message ? e.message : e); + } + expect(message).to.contain('not currently supported in the docs playground'); +}); +`; + +const PRE_REQUEST_MOMENT_SCRIPT = ` +const moment = require('moment'); +const { v4 } = require('uuid'); +const stamp = moment.utc('2026-09-03T12:00:00Z').format('YYYY-MM-DD'); +req.setHeader('X-Moment-Date', stamp); +bru.setVar('preRequestMoment', stamp); +bru.setVar('preRequestUuid', v4()); +`; + +const PRE_REQUEST_MOMENT_TESTS = ` +test('pre-request moment and uuid ran before send', function () { + expect(bru.getVar('preRequestMoment')).to.equal('2026-09-03'); + expect(bru.getVar('preRequestUuid')).to.match( + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i + ); +}); +`; + +const POST_RESPONSE_CRYPTO_SCRIPT = ` +const CryptoJS = require('crypto-js'); +const body = typeof res.body === 'string' ? res.body : JSON.stringify(res.body); +bru.setVar('responseSha256', CryptoJS.SHA256(body).toString()); +`; + +const POST_RESPONSE_CRYPTO_TESTS = ` +test('post-response crypto-js hashed the body', function () { + const digest = bru.getVar('responseSha256'); + expect(digest).to.be.a('string'); + expect(digest).to.have.lengthOf(64); + expect(digest).to.match(/^[0-9a-f]{64}$/); +}); +`; + const REQUIRE_FS_TESTS_SCRIPT = ` test('ran before the throw', function () { expect(1).to.equal(1); }); require('fs'); @@ -70,6 +161,119 @@ test.describe('playground script execution', () => { await expect(page.getByText(/Failed: [1-9]/)).toHaveCount(0); }); + test('runs chai Buffer path and ajv library functions on Send', async ({ page, playground, responsePane }) => { + await page.route('**/api/users**', (route) => + route.fulfill({ + status: 200, + headers: { 'content-type': 'application/json', 'access-control-allow-origin': '*' }, + body: JSON.stringify({ users: [{ id: 1, name: 'Ada' }] }) + }) + ); + + await page.goto('/#/?pg=1&dock=bottom'); + await playground.openSidebarItem('get users'); + await playground.selectTab('tests'); + await setEditorScript(page, playground.testsEditor, LIBRARY_FUNCTIONS_SCRIPT); + + await responsePane.send(); + await responsePane.switchToTab('tests'); + + await expect(page.getByText(/Passed: [1-9]\d*, Failed: 0/).first()).toBeVisible(); + await expect(page.getByText(/Failed: [1-9]/)).toHaveCount(0); + }); + + test('runs axios.get through the sandbox shim on Send', async ({ page, playground, responsePane }) => { + await page.route('**/api/users**', (route) => + route.fulfill({ + status: 200, + headers: { 'content-type': 'application/json', 'access-control-allow-origin': '*' }, + body: JSON.stringify({ users: [{ id: 1, name: 'Ada' }] }) + }) + ); + + await page.goto('/#/?pg=1&dock=bottom'); + await playground.openSidebarItem('get users'); + await playground.selectTab('tests'); + await setEditorScript(page, playground.testsEditor, AXIOS_GET_SCRIPT); + + await responsePane.send(); + await responsePane.switchToTab('tests'); + + await expect(page.getByText(/Passed: [1-9]\d*, Failed: 0/).first()).toBeVisible(); + await expect(page.getByText(/Failed: [1-9]/)).toHaveCount(0); + }); + + test('jsonwebtoken require is rejected with a playground-specific error', async ({ page, playground, responsePane }) => { + await page.route('**/api/users**', (route) => + route.fulfill({ + status: 200, + headers: { 'content-type': 'application/json', 'access-control-allow-origin': '*' }, + body: JSON.stringify({ users: [{ id: 1, name: 'Ada' }] }) + }) + ); + + await page.goto('/#/?pg=1&dock=bottom'); + await playground.openSidebarItem('get users'); + await playground.selectTab('tests'); + await setEditorScript(page, playground.testsEditor, JWT_UNSUPPORTED_SCRIPT); + + await responsePane.send(); + await responsePane.switchToTab('tests'); + + await expect(page.getByText(/Passed: [1-9]\d*, Failed: 0/).first()).toBeVisible(); + await expect(page.getByText(/Failed: [1-9]/)).toHaveCount(0); + }); + + test('uses moment and uuid in a pre-request script on Send', async ({ page, playground, responsePane }) => { + let momentHeader = ''; + await page.route('**/api/users**', (route) => { + momentHeader = route.request().headers()['x-moment-date'] || ''; + return route.fulfill({ + status: 200, + headers: { 'content-type': 'application/json', 'access-control-allow-origin': '*' }, + body: JSON.stringify({ users: [{ id: 1, name: 'Ada' }] }) + }); + }); + + await page.goto('/#/?pg=1&dock=bottom'); + await playground.openSidebarItem('get users'); + await playground.selectTab('scripts'); + await setEditorScript(page, playground.preRequestScriptEditor, PRE_REQUEST_MOMENT_SCRIPT); + await playground.selectTab('tests'); + await setEditorScript(page, playground.testsEditor, PRE_REQUEST_MOMENT_TESTS); + + await responsePane.send(); + await responsePane.switchToTab('tests'); + + await expect(page.getByText(/Passed: [1-9]\d*, Failed: 0/).first()).toBeVisible(); + await expect(page.getByText(/Failed: [1-9]/)).toHaveCount(0); + expect(momentHeader).toBe('2026-09-03'); + }); + + test('runs crypto-js in a post-response script on Send', async ({ page, playground, responsePane }) => { + await page.route('**/api/users**', (route) => + route.fulfill({ + status: 200, + headers: { 'content-type': 'application/json', 'access-control-allow-origin': '*' }, + body: JSON.stringify({ users: [{ id: 1, name: 'Ada' }] }) + }) + ); + + await page.goto('/#/?pg=1&dock=bottom'); + await playground.openSidebarItem('get users'); + await playground.selectTab('scripts'); + await page.getByTestId('scripts-tabs-tab-post-response').click(); + await setEditorScript(page, playground.postResponseScriptEditor, POST_RESPONSE_CRYPTO_SCRIPT); + await playground.selectTab('tests'); + await setEditorScript(page, playground.testsEditor, POST_RESPONSE_CRYPTO_TESTS); + + await responsePane.send(); + await responsePane.switchToTab('tests'); + + await expect(page.getByText(/Passed: [1-9]\d*, Failed: 0/).first()).toBeVisible(); + await expect(page.getByText(/Failed: [1-9]/)).toHaveCount(0); + }); + test('a tests script that throws shows a dismissable Test Script Error card and keeps the tests that ran', async ({ page, playground, responsePane }) => { await responsePane.mockUsersResponse(JSON.stringify({ users: [] }));