diff --git a/README.md b/README.md index 6ec2540..3cbc287 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,13 @@ Here are the core functions provided by the package: - `removeResponseHeaderUrl(name)`: Removes a single response header. - `removeResponseHeadersUrl(headerNames)`: Removes multiple response headers from an array of names. +### Query Parameters + +- `modifyQueryParamUrl(paramName, paramValue, params)`: Add or modify query parameters in URLs. +- `modifyQueryParamsUrl(params)`: Add/modify multiple query parameters using an object. +- `removeQueryParamUrl(paramName, params)`: Remove query parameters from URLs. +- `removeQueryParamsUrl(params)`: Remove multiple query parameters using an array. + ### Importing Rules - `importRules(apiKey)`: Import all rules from your Requestly account using API key. @@ -83,6 +90,10 @@ const { addResponseHeadersUrl, removeResponseHeaderUrl, removeResponseHeadersUrl, + modifyQueryParamUrl, + modifyQueryParamsUrl, + removeQueryParamUrl, + removeQueryParamsUrl, importRules, closeWelcomePage, } = require("@requestly/rq-automation"); @@ -129,6 +140,26 @@ async function seleniumExample() { removeResponseHeadersUrl(["X-Response-Header", "X-Another-Response-Header"]) ); + // Add query parameters for tracking/analytics + await driver.get(modifyQueryParamUrl("debug", "true")); + + // Add multiple query parameters for A/B testing + await driver.get( + modifyQueryParamsUrl({ + variant: "A", + utm_source: "test", + user_segment: "premium" + }) + ); + + // Remove sensitive query parameters + await driver.get(removeQueryParamUrl("api_key")); + + // Remove multiple tracking parameters for privacy testing + await driver.get( + removeQueryParamsUrl(["utm_source", "utm_medium", "fbclid", "gclid"]) + ); + // Import a shared list of rules await driver.get(importRules("YOUR_API_KEY")); @@ -178,6 +209,8 @@ const { getExtension } = require("@requestly/rq-automation"); const path = require("path"); const { addRequestHeaderUrl, + modifyQueryParamsUrl, + removeQueryParamsUrl, // ... import other functions importRules, closeWelcomePage, @@ -199,7 +232,19 @@ async function playwrightExample() { const page = await browser.newPage(); // Add a request header - await page.goto(addRequestHeaderUrl("X-Request-By", "Puppeteer")); + await page.goto(addRequestHeaderUrl("X-Request-By", "Playwright")); + + // Add query parameters for feature testing + await page.goto( + modifyQueryParamsUrl({ + feature_flag: "new_ui", + test_variant: "B", + debug: "true" + }) + ); + + // Remove tracking parameters for privacy testing + await page.goto(removeQueryParamsUrl(["utm_source", "fbclid"])); // Import a shared list of rules await page.goto(importRules("YOUR_API_KEY")); @@ -246,6 +291,8 @@ const puppeteer = require("puppeteer"); const { getExtension } = require("@requestly/rq-automation"); const { addRequestHeaderUrl, + modifyQueryParamUrl, + removeQueryParamsUrl, // ... import other functions importRules, closeWelcomePage, @@ -266,6 +313,12 @@ async function puppeteerExample() { const page = await browser.newPage(); await page.goto(addRequestHeaderUrl("X-Request-By", "Puppeteer")); + // Add query parameter for API testing + await page.goto(modifyQueryParamUrl("api_version", "v2")); + + // Remove multiple sensitive parameters + await page.goto(removeQueryParamsUrl(["session_token", "api_key"])); + // Import a shared list of rules await page.goto(importRules("YOUR_API_KEY")); @@ -285,4 +338,4 @@ puppeteerExample(); ## Keywords -`requestly` `selenium` `playwright` `puppeteer` `webdriver` `chrome` `modify` `headers` `request headers` `response headers` `redirect` `delay` `throttle` `automation` `testing` +`requestly` `selenium` `playwright` `puppeteer` `webdriver` `chrome` `modify` `headers` `request headers` `response headers` `query parameters` `url parameters` `redirect` `delay` `throttle` `automation` `testing` `a/b testing` `feature flags` diff --git a/index.js b/index.js index 4299e55..87e516c 100644 --- a/index.js +++ b/index.js @@ -231,6 +231,98 @@ function removeResponseHeadersUrl(headers) { return removeResponseHeaderUrl(null, headers); } +/** + * Add or modify query parameters in URLs. + * @param {string} paramName - The name of the query parameter to add/modify. + * @param {string} paramValue - The value of the query parameter. + * @param {Object.} [params] - Object containing multiple parameters to add/modify. + * @returns {string} The automation URL for modifying query parameters. + * @throws {Error} If neither paramName/paramValue nor params are provided. + */ +function modifyQueryParamUrl(paramName, paramValue, params) { + let query = ""; + if (params && typeof params === "object") { + // Validate all parameters + Object.entries(params).forEach(([name, value]) => { + if (!name || typeof name !== "string") { + throw new Error("Parameter name must be a non-empty string"); + } + if (typeof value !== "string") { + throw new Error("Parameter value must be a string"); + } + }); + query = buildQueryString(params); + } else if (paramName !== null && paramName !== undefined && typeof paramValue !== "undefined") { + if (!paramName || typeof paramName !== "string") { + throw new Error("Parameter name must be a non-empty string"); + } + if (typeof paramValue !== "string") { + throw new Error("Parameter value must be a string"); + } + query = `${encodeURIComponent(paramName)}=${encodeURIComponent(paramValue)}`; + } else { + throw new Error("Provide either params object or paramName and paramValue"); + } + return `https://app.requestly.io/automation/modify-query-param?${query}`; +} + +/** + * Shortcut: Add/modify multiple query parameters using an object. + * @param {Object.} params - Key-value pairs of query parameters to add/modify. + * @returns {string} The automation URL for modifying multiple query parameters. + * @throws {Error} If params is not provided or not an object. + */ +function modifyQueryParamsUrl(params) { + if (!params || typeof params !== "object" || Array.isArray(params)) { + throw new Error("params parameter is required and must be an object"); + } + return modifyQueryParamUrl(null, null, params); +} + +/** + * Remove query parameters from URLs. + * @param {string} paramName - The name of the query parameter to remove. + * @param {string[]} [params] - Array of parameter names to remove. + * @returns {string} The automation URL for removing query parameters. + * @throws {Error} If neither paramName nor params are provided. + */ +function removeQueryParamUrl(paramName, params) { + let query = ""; + if (params && Array.isArray(params)) { + if (params.length === 0) { + throw new Error("params array cannot be empty"); + } + // Validate all parameter names + params.forEach(name => { + if (!name || typeof name !== "string") { + throw new Error("Parameter name must be a non-empty string"); + } + }); + query = params.map(name => `param=${encodeURIComponent(name)}`).join("&"); + } else if (paramName !== null && paramName !== undefined) { + if (typeof paramName !== "string" || !paramName) { + throw new Error("Parameter name must be a non-empty string"); + } + query = `param=${encodeURIComponent(paramName)}`; + } else { + throw new Error("Provide either paramName or params array"); + } + return `https://app.requestly.io/automation/remove-query-param?${query}`; +} + +/** + * Shortcut: Remove multiple query parameters using an array. + * @param {string[]} params - Array of parameter names to remove. + * @returns {string} The automation URL for removing multiple query parameters. + * @throws {Error} If params is not provided or not an array. + */ +function removeQueryParamsUrl(params) { + if (!Array.isArray(params)) { + throw new Error("params parameter must be an array"); + } + return removeQueryParamUrl(null, params); +} + /** * Get a URL to import rules using an API key. * @param {string} api_key - The API key for importing rules. @@ -253,6 +345,10 @@ module.exports = { addResponseHeadersUrl, removeResponseHeaderUrl, removeResponseHeadersUrl, + modifyQueryParamUrl, + modifyQueryParamsUrl, + removeQueryParamUrl, + removeQueryParamsUrl, importRules, getExtension, closeWelcomePage, diff --git a/package.json b/package.json index ecd8329..4b1ea57 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,17 @@ "headers", "request headers", "response headers", + "query parameters", + "url parameters", "redirect", "redirect network requests", "delay", "delay network requests", "throttle", - "throttle network requests" + "throttle network requests", + "automation testing", + "web testing", + "api testing" ], "peerDependencies": { "selenium-webdriver": "^4.0.0", diff --git a/tests/queryParams.test.js b/tests/queryParams.test.js new file mode 100644 index 0000000..2610e65 --- /dev/null +++ b/tests/queryParams.test.js @@ -0,0 +1,261 @@ +const { + modifyQueryParamUrl, + modifyQueryParamsUrl, + removeQueryParamUrl, + removeQueryParamsUrl, +} = require('../index'); + +describe('Query Parameter Manipulation', () => { + describe('modifyQueryParamUrl', () => { + test('should create URL with single query parameter', () => { + const result = modifyQueryParamUrl('testParam', 'testValue'); + expect(result).toBe('https://app.requestly.io/automation/modify-query-param?testParam=testValue'); + }); + + test('should handle special characters in parameter name and value', () => { + const result = modifyQueryParamUrl('test¶m', 'test=value&more'); + expect(result).toBe('https://app.requestly.io/automation/modify-query-param?test%26param=test%3Dvalue%26more'); + }); + + test('should handle Unicode characters', () => { + const result = modifyQueryParamUrl('测试', '值'); + expect(result).toBe('https://app.requestly.io/automation/modify-query-param?%E6%B5%8B%E8%AF%95=%E5%80%BC'); + }); + + test('should create URL with multiple query parameters using params object', () => { + const params = { + param1: 'value1', + param2: 'value2', + param3: 'value3' + }; + const result = modifyQueryParamUrl(null, null, params); + expect(result).toBe('https://app.requestly.io/automation/modify-query-param?param1=value1¶m2=value2¶m3=value3'); + }); + + test('should handle empty string values', () => { + const result = modifyQueryParamUrl('emptyParam', ''); + expect(result).toBe('https://app.requestly.io/automation/modify-query-param?emptyParam='); + }); + + test('should handle numeric values as strings', () => { + const result = modifyQueryParamUrl('numericParam', '123'); + expect(result).toBe('https://app.requestly.io/automation/modify-query-param?numericParam=123'); + }); + + // Error cases + test('should throw error when no parameters are provided', () => { + expect(() => modifyQueryParamUrl()).toThrow('Provide either params object or paramName and paramValue'); + }); + + test('should throw error when paramName is empty string', () => { + expect(() => modifyQueryParamUrl('', 'value')).toThrow('Parameter name must be a non-empty string'); + }); + + test('should throw error when paramName is not a string', () => { + expect(() => modifyQueryParamUrl(123, 'value')).toThrow('Parameter name must be a non-empty string'); + }); + + test('should throw error when paramValue is not a string', () => { + expect(() => modifyQueryParamUrl('param', 123)).toThrow('Parameter value must be a string'); + }); + + test('should throw error when params object contains invalid parameter name', () => { + const params = { + '': 'value1', + param2: 'value2' + }; + expect(() => modifyQueryParamUrl(null, null, params)).toThrow('Parameter name must be a non-empty string'); + }); + + test('should throw error when params object contains invalid parameter value', () => { + const params = { + param1: 'value1', + param2: 123 + }; + expect(() => modifyQueryParamUrl(null, null, params)).toThrow('Parameter value must be a string'); + }); + }); + + describe('modifyQueryParamsUrl', () => { + test('should create URL with multiple query parameters', () => { + const params = { + userId: '12345', + sessionId: 'abc-def-ghi', + debug: 'true' + }; + const result = modifyQueryParamsUrl(params); + expect(result).toBe('https://app.requestly.io/automation/modify-query-param?userId=12345&sessionId=abc-def-ghi&debug=true'); + }); + + test('should handle single parameter object', () => { + const params = { singleParam: 'singleValue' }; + const result = modifyQueryParamsUrl(params); + expect(result).toBe('https://app.requestly.io/automation/modify-query-param?singleParam=singleValue'); + }); + + test('should handle parameters with special characters', () => { + const params = { + 'param with spaces': 'value with spaces', + 'param&special': 'value=special' + }; + const result = modifyQueryParamsUrl(params); + expect(result).toBe('https://app.requestly.io/automation/modify-query-param?param%20with%20spaces=value%20with%20spaces¶m%26special=value%3Dspecial'); + }); + + // Error cases + test('should throw error when params is null', () => { + expect(() => modifyQueryParamsUrl(null)).toThrow('params parameter is required and must be an object'); + }); + + test('should throw error when params is undefined', () => { + expect(() => modifyQueryParamsUrl(undefined)).toThrow('params parameter is required and must be an object'); + }); + + test('should throw error when params is not an object', () => { + expect(() => modifyQueryParamsUrl('not an object')).toThrow('params parameter is required and must be an object'); + }); + + test('should throw error when params is an array', () => { + expect(() => modifyQueryParamsUrl(['param1', 'param2'])).toThrow('params parameter is required and must be an object'); + }); + }); + + describe('removeQueryParamUrl', () => { + test('should create URL to remove single query parameter', () => { + const result = removeQueryParamUrl('paramToRemove'); + expect(result).toBe('https://app.requestly.io/automation/remove-query-param?param=paramToRemove'); + }); + + test('should handle parameter name with special characters', () => { + const result = removeQueryParamUrl('param&special'); + expect(result).toBe('https://app.requestly.io/automation/remove-query-param?param=param%26special'); + }); + + test('should create URL to remove multiple query parameters using array', () => { + const params = ['param1', 'param2', 'param3']; + const result = removeQueryParamUrl(null, params); + expect(result).toBe('https://app.requestly.io/automation/remove-query-param?param=param1¶m=param2¶m=param3'); + }); + + test('should handle Unicode parameter names', () => { + const result = removeQueryParamUrl('测试参数'); + expect(result).toBe('https://app.requestly.io/automation/remove-query-param?param=%E6%B5%8B%E8%AF%95%E5%8F%82%E6%95%B0'); + }); + + // Error cases + test('should throw error when no parameters are provided', () => { + expect(() => removeQueryParamUrl()).toThrow('Provide either paramName or params array'); + }); + + test('should throw error when paramName is empty string', () => { + expect(() => removeQueryParamUrl('')).toThrow('Parameter name must be a non-empty string'); + }); + + test('should throw error when paramName is not a string', () => { + expect(() => removeQueryParamUrl(123)).toThrow('Parameter name must be a non-empty string'); + }); + + test('should throw error when params array is empty', () => { + expect(() => removeQueryParamUrl(null, [])).toThrow('params array cannot be empty'); + }); + + test('should throw error when params array contains invalid parameter name', () => { + const params = ['param1', '', 'param3']; + expect(() => removeQueryParamUrl(null, params)).toThrow('Parameter name must be a non-empty string'); + }); + + test('should throw error when params array contains non-string parameter name', () => { + const params = ['param1', 123, 'param3']; + expect(() => removeQueryParamUrl(null, params)).toThrow('Parameter name must be a non-empty string'); + }); + }); + + describe('removeQueryParamsUrl', () => { + test('should create URL to remove multiple query parameters', () => { + const params = ['sessionId', 'debug', 'timestamp']; + const result = removeQueryParamsUrl(params); + expect(result).toBe('https://app.requestly.io/automation/remove-query-param?param=sessionId¶m=debug¶m=timestamp'); + }); + + test('should handle single parameter in array', () => { + const params = ['singleParam']; + const result = removeQueryParamsUrl(params); + expect(result).toBe('https://app.requestly.io/automation/remove-query-param?param=singleParam'); + }); + + test('should handle parameters with special characters', () => { + const params = ['param with spaces', 'param&special']; + const result = removeQueryParamsUrl(params); + expect(result).toBe('https://app.requestly.io/automation/remove-query-param?param=param%20with%20spaces¶m=param%26special'); + }); + + // Error cases + test('should throw error when params is not an array', () => { + expect(() => removeQueryParamsUrl('not an array')).toThrow('params parameter must be an array'); + }); + + test('should throw error when params is null', () => { + expect(() => removeQueryParamsUrl(null)).toThrow('params parameter must be an array'); + }); + + test('should throw error when params is undefined', () => { + expect(() => removeQueryParamsUrl(undefined)).toThrow('params parameter must be an array'); + }); + + test('should throw error when params is an object', () => { + expect(() => removeQueryParamsUrl({ param1: 'value1' })).toThrow('params parameter must be an array'); + }); + }); + + describe('Integration scenarios', () => { + test('should handle common e-commerce tracking parameters', () => { + const trackingParams = { + utm_source: 'google', + utm_medium: 'cpc', + utm_campaign: 'summer_sale', + gclid: 'CjwKCAjw...', + fbclid: 'IwAR0...' + }; + const result = modifyQueryParamsUrl(trackingParams); + expect(result).toContain('utm_source=google'); + expect(result).toContain('utm_medium=cpc'); + expect(result).toContain('utm_campaign=summer_sale'); + }); + + test('should handle API pagination parameters', () => { + const paginationParams = { + page: '2', + limit: '50', + sort: 'created_at', + order: 'desc' + }; + const result = modifyQueryParamsUrl(paginationParams); + expect(result).toContain('page=2'); + expect(result).toContain('limit=50'); + expect(result).toContain('sort=created_at'); + expect(result).toContain('order=desc'); + }); + + test('should handle removing sensitive parameters', () => { + const sensitiveParams = ['api_key', 'session_token', 'user_secret']; + const result = removeQueryParamsUrl(sensitiveParams); + expect(result).toContain('param=api_key'); + expect(result).toContain('param=session_token'); + expect(result).toContain('param=user_secret'); + }); + + test('should handle search and filter parameters', () => { + const searchParams = { + q: 'javascript automation', + category: 'programming', + price_min: '10', + price_max: '100', + in_stock: 'true' + }; + const result = modifyQueryParamsUrl(searchParams); + expect(result).toContain('q=javascript%20automation'); + expect(result).toContain('category=programming'); + expect(result).toContain('price_min=10'); + }); + }); +}); \ No newline at end of file