feat: js api supports get path params (#5235) (#6762)

This commit is contained in:
Sid
2026-01-21 18:41:47 +05:30
committed by GitHub
parent 1f571267b0
commit 27b7fa81f2
15 changed files with 347 additions and 3 deletions

View File

@@ -13,6 +13,9 @@ const STATIC_API_HINTS = {
'req.timeout',
'req.getUrl()',
'req.setUrl(url)',
'req.getHost()',
'req.getPath()',
'req.getQueryString()',
'req.getMethod()',
'req.getAuthMode()',
'req.setMethod(method)',
@@ -27,6 +30,7 @@ const STATIC_API_HINTS = {
'req.setTimeout(timeout)',
'req.getExecutionMode()',
'req.getName()',
'req.getPathParams()',
'req.getTags()',
'req.disableParsingResponseJson()',
'req.onFail(function(err) {})'

View File

@@ -38,10 +38,18 @@ const replacements = {
// Supported Postman request translations:
// - pm.request.url / request.url -> req.getUrl()
// - pm.request.url.getHost() -> req.getHost()
// - pm.request.url.getPath() -> req.getPath()
// - pm.request.url.getQueryString() -> req.getQueryString()
// - pm.request.url.variables -> req.getPathParams()
// - pm.request.method / request.method -> req.getMethod()
// - pm.request.headers / request.headers -> req.getHeaders()
// - pm.request.body / request.body -> req.getBody()
// - pm.info.requestName / request.name -> req.getName()
'pm\\.request\\.url\\.getHost\\(\\)': 'req.getHost()',
'pm\\.request\\.url\\.getPath\\(\\)': 'req.getPath()',
'pm\\.request\\.url\\.getQueryString\\(\\)': 'req.getQueryString()',
'pm\\.request\\.url\\.variables': 'req.getPathParams()',
'pm\\.request\\.url': 'req.getUrl()',
'pm\\.request\\.method': 'req.getMethod()',
'pm\\.request\\.headers': 'req.getHeaders()',

View File

@@ -38,6 +38,10 @@ const simpleTranslations = {
'pm.info.requestName': 'req.getName()',
// Request properties (pm.request.*)
'pm.request.url.getHost': 'req.getHost',
'pm.request.url.getPath': 'req.getPath',
'pm.request.url.getQueryString': 'req.getQueryString',
'pm.request.url.variables': 'req.getPathParams()',
'pm.request.url': 'req.getUrl()',
'pm.request.method': 'req.getMethod()',
'pm.request.headers': 'req.getHeaders()',

View File

@@ -42,4 +42,20 @@ describe('postmanTranslations - request commands', () => {
`;
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
});
test('should handle pm.request.url helper methods', () => {
const inputScript = `
const host = pm.request.url.getHost();
const path = pm.request.url.getPath();
const queryString = pm.request.url.getQueryString();
const pathVariables = pm.request.url.variables;
`;
const expectedOutput = `
const host = req.getHost();
const path = req.getPath();
const queryString = req.getQueryString();
const pathVariables = req.getPathParams();
`;
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
});
});

View File

@@ -332,9 +332,6 @@ const configureRequest = async (
request.url = urlObj.toString();
}
// Remove pathParams, already in URL (Issue #2439)
delete request.pathParams;
// Remove apiKeyAuthValueForQueryParams, already interpolated and added to URL
delete request.apiKeyAuthValueForQueryParams;

View File

@@ -18,6 +18,7 @@ class BrunoRequest {
this.headers = req.headers;
this.timeout = req.timeout;
this.name = req.name;
this.pathParams = req.pathParams;
this.tags = req.tags || [];
/**
* We automatically parse the JSON body if the content type is JSON
@@ -41,6 +42,53 @@ class BrunoRequest {
this.req.url = url;
}
getHost() {
try {
const url = new URL(this.req.url);
return url.host;
} catch (e) {
return '';
}
}
getPath() {
try {
const url = new URL(this.req.url);
let pathname = url.pathname;
// If path params exist, interpolate them into the pathname
if (this.req.pathParams && Array.isArray(this.req.pathParams)) {
pathname = pathname
.split('/')
.map((segment) => {
if (segment.startsWith(':')) {
const paramName = segment.slice(1);
const pathParam = this.req.pathParams.find((param) => param.name === paramName);
if (pathParam && pathParam.value) {
return pathParam.value;
}
}
return segment;
})
.join('/');
}
return pathname;
} catch (e) {
return '';
}
}
getQueryString() {
try {
const url = new URL(this.req.url);
// Return query string without the leading '?'
return url.search ? url.search.substring(1) : '';
} catch (e) {
return '';
}
}
getMethod() {
return this.req.method;
}
@@ -191,6 +239,16 @@ class BrunoRequest {
return this.req.name;
}
getPathParams() {
const params = Array.isArray(this.req.pathParams) ? this.req.pathParams : [];
return params.map((param) => ({
name: param.name,
value: param.value,
type: param.type
}));
}
/**
* Get the tags associated with this request
* @returns {Array<string>} Array of tag strings

View File

@@ -9,6 +9,7 @@ const addBrunoRequestShimToContext = (vm, req) => {
const body = marshallToVm(req.getBody(), vm);
const timeout = marshallToVm(req.getTimeout(), vm);
const name = marshallToVm(req.getName(), vm);
const pathParams = marshallToVm(req.getPathParams(), vm);
const tags = marshallToVm(req.getTags(), vm);
vm.setProp(reqObject, 'url', url);
@@ -17,6 +18,7 @@ const addBrunoRequestShimToContext = (vm, req) => {
vm.setProp(reqObject, 'body', body);
vm.setProp(reqObject, 'timeout', timeout);
vm.setProp(reqObject, 'name', name);
vm.setProp(reqObject, 'pathParams', pathParams);
vm.setProp(reqObject, 'tags', tags);
url.dispose();
@@ -25,6 +27,7 @@ const addBrunoRequestShimToContext = (vm, req) => {
body.dispose();
timeout.dispose();
name.dispose();
pathParams.dispose();
tags.dispose();
let getUrl = vm.newFunction('getUrl', function () {
@@ -39,6 +42,24 @@ const addBrunoRequestShimToContext = (vm, req) => {
vm.setProp(reqObject, 'setUrl', setUrl);
setUrl.dispose();
let getHost = vm.newFunction('getHost', function () {
return marshallToVm(req.getHost(), vm);
});
vm.setProp(reqObject, 'getHost', getHost);
getHost.dispose();
let getPath = vm.newFunction('getPath', function () {
return marshallToVm(req.getPath(), vm);
});
vm.setProp(reqObject, 'getPath', getPath);
getPath.dispose();
let getQueryString = vm.newFunction('getQueryString', function () {
return marshallToVm(req.getQueryString(), vm);
});
vm.setProp(reqObject, 'getQueryString', getQueryString);
getQueryString.dispose();
let getMethod = vm.newFunction('getMethod', function () {
return marshallToVm(req.getMethod(), vm);
});
@@ -57,6 +78,12 @@ const addBrunoRequestShimToContext = (vm, req) => {
vm.setProp(reqObject, 'getName', getName);
getName.dispose();
let getPathParams = vm.newFunction('getPathParams', function () {
return marshallToVm(req.getPathParams(), vm);
});
vm.setProp(reqObject, 'getPathParams', getPathParams);
getPathParams.dispose();
let setMethod = vm.newFunction('setMethod', function (method) {
req.setMethod(vm.dump(method));
});

View File

@@ -0,0 +1,23 @@
meta {
name: getHost
type: http
seq: 1
}
get {
url: {{host}}/ping
body: none
auth: none
}
assert {
res.status: eq 200
res.body: eq pong
}
tests {
test("req.getHost()", function() {
const host = req.getHost();
expect(host).to.equal("testbench-sanity.usebruno.com");
});
}

View File

@@ -0,0 +1,18 @@
meta {
name: getPath
type: http
seq: 1
}
get {
url: {{host}}/api/users/123
body: none
auth: none
}
tests {
test("req.getPath()", function() {
const path = req.getPath();
expect(path).to.equal("/api/users/123");
});
}

View File

@@ -0,0 +1,23 @@
meta {
name: getPathParam
type: http
seq: 1
}
get {
url: {{host}}/:pathParam
body: none
auth: none
}
params:path {
pathParam: ping
}
tests {
test("req.getPathParams()", function() {
const pathParams = req.getPathParams();
expect(pathParams[0].name).to.equal('pathParam');
expect(pathParams[0].value).to.equal('ping');
});
}

View File

@@ -0,0 +1,29 @@
meta {
name: getQueryString
type: http
seq: 1
}
get {
url: {{host}}/ping?page=1&limit=10&sort=desc
body: none
auth: none
}
params:query {
page: 1
limit: 10
sort: desc
}
assert {
res.status: eq 200
res.body: eq pong
}
tests {
test("req.getQueryString()", function() {
const queryString = req.getQueryString();
expect(queryString).to.equal("page=1&limit=10&sort=desc");
});
}

View File

@@ -0,0 +1,9 @@
{
"version": "1",
"name": "url_helpers_test",
"type": "collection",
"ignore": [
"node_modules",
".git"
]
}

View File

@@ -0,0 +1,84 @@
meta {
name: url-helpers-test
type: http
seq: 1
}
get {
url: https://echo.usebruno.com/api/users/:userId?name=john&age=30
body: none
auth: none
}
params:path {
userId: 123
}
script:pre-request {
// Test URL helper methods in pre-request script
const host = req.getHost();
const path = req.getPath();
const queryString = req.getQueryString();
const pathParams = req.getPathParams();
// Store values for verification in tests
bru.setVar('preReqHost', host);
bru.setVar('preReqPath', path);
bru.setVar('preReqQueryString', queryString);
bru.setVar('preReqPathParams', JSON.stringify(pathParams));
}
script:post-response {
// Test URL helper methods in post-response script
const host = req.getHost();
const path = req.getPath();
const queryString = req.getQueryString();
const pathParams = req.getPathParams();
// Store values for verification in tests
bru.setVar('postResHost', host);
bru.setVar('postResPath', path);
bru.setVar('postResQueryString', queryString);
bru.setVar('postResPathParams', JSON.stringify(pathParams));
}
tests {
test("getHost() returns correct host", function() {
const preReqHost = bru.getVar('preReqHost');
const postResHost = bru.getVar('postResHost');
expect(preReqHost).to.equal('echo.usebruno.com');
expect(postResHost).to.equal('echo.usebruno.com');
});
test("getPath() returns correct path", function() {
const preReqPath = bru.getVar('preReqPath');
const postResPath = bru.getVar('postResPath');
expect(preReqPath).to.equal('/api/users/123');
expect(postResPath).to.equal('/api/users/123');
});
test("getQueryString() returns correct query string", function() {
const preReqQueryString = bru.getVar('preReqQueryString');
const postResQueryString = bru.getVar('postResQueryString');
expect(preReqQueryString).to.equal('name=john&age=30');
expect(postResQueryString).to.equal('name=john&age=30');
});
test("getPathParams() returns correct path parameters", function() {
const preReqPathParams = JSON.parse(bru.getVar('preReqPathParams'));
const postResPathParams = JSON.parse(bru.getVar('postResPathParams'));
expect(preReqPathParams).to.be.an('array');
expect(preReqPathParams).to.have.lengthOf(1);
expect(preReqPathParams[0].name).to.equal('userId');
expect(preReqPathParams[0].value).to.equal('123');
expect(postResPathParams).to.be.an('array');
expect(postResPathParams).to.have.lengthOf(1);
expect(postResPathParams[0].name).to.equal('userId');
expect(postResPathParams[0].value).to.equal('123');
});
}

View File

@@ -0,0 +1,6 @@
{
"maximized": false,
"lastOpenedCollections": [
"{{projectRoot}}/tests/scripting/url-helpers/fixtures/collections/url_helpers_test"
]
}

View File

@@ -0,0 +1,38 @@
import { test } from '../../../playwright';
import { setSandboxMode, runCollection, validateRunnerResults } from '../../utils/page';
test.describe.serial('URL helper methods', () => {
test.describe('req.getHost(), req.getPath(), req.getQueryString(), req.getPathParams()', () => {
test('should work in developer mode', async ({ pageWithUserData: page }) => {
// Set up developer mode
await setSandboxMode(page, 'url_helpers_test', 'developer');
// Run the collection
await runCollection(page, 'url_helpers_test');
// Validate test results - 1 request should pass (with 4 assertions inside)
await validateRunnerResults(page, {
totalRequests: 1,
passed: 1,
failed: 0,
skipped: 0
});
});
test('should work in safe mode', async ({ pageWithUserData: page }) => {
// Set up safe mode
await setSandboxMode(page, 'url_helpers_test', 'safe');
// Run the collection
await runCollection(page, 'url_helpers_test');
// Validate test results - 1 request should pass in safe mode too (with 4 assertions inside)
await validateRunnerResults(page, {
totalRequests: 1,
passed: 1,
failed: 0,
skipped: 0
});
});
});
});