Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 204 additions & 0 deletions packages/bruno-api-docs/e2e/tests/playground/script-execution.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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: [] }));

Expand Down
Loading