From 96b266788a6a5a56e41df996bd7b67c882094d3f Mon Sep 17 00:00:00 2001 From: biocodeit Date: Thu, 27 Nov 2025 15:07:33 +0530 Subject: [PATCH 01/19] Add save test script to collection database the project often requires the script to be saved to the collection. --- src/components/RequestBuilder.tsx | 6 ++++++ src/types/index.ts | 1 + 2 files changed, 7 insertions(+) diff --git a/src/components/RequestBuilder.tsx b/src/components/RequestBuilder.tsx index 8b10815..6d07d7d 100644 --- a/src/components/RequestBuilder.tsx +++ b/src/components/RequestBuilder.tsx @@ -255,6 +255,11 @@ export default function RequestBuilder({ selectedHistoryItem, selectedRequest }: if (selectedRequest.body) { setBody(selectedRequest.body); } + + if (selectedRequest.postScript) { + setTestScript(selectedRequest.postScript) + } + } }, [selectedRequest]); @@ -483,6 +488,7 @@ export default function RequestBuilder({ selectedHistoryItem, selectedRequest }: method, url: url.trim(), headers: headersObject, + postScript: METHODS_WITH_BODY.includes(method) ? testScript : undefined, params: {}, body: METHODS_WITH_BODY.includes(method) ? body : undefined, createdAt: new Date(), diff --git a/src/types/index.ts b/src/types/index.ts index f2aa620..377be9c 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -6,6 +6,7 @@ export interface Request { headers: Record; params: Record; body?: string; + postScript?: string; createdAt: Date; updatedAt: Date; } From 317df941f0d483b43dddc5b65f7765a992da6ae7 Mon Sep 17 00:00:00 2001 From: biocodeit Date: Thu, 27 Nov 2025 16:27:56 +0530 Subject: [PATCH 02/19] rename collection fixture --- e2e/fixtures/collection.fixure.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 e2e/fixtures/collection.fixure.ts diff --git a/e2e/fixtures/collection.fixure.ts b/e2e/fixtures/collection.fixure.ts new file mode 100644 index 0000000..42f7e89 --- /dev/null +++ b/e2e/fixtures/collection.fixure.ts @@ -0,0 +1,15 @@ +import {test as base} from '@playwright/test' +import { CollectionModel } from '../object-models/collection-model' +import { APImodel } from '../object-models/api-model' + +export const test = base.extend<{collection: CollectionModel, apiReq: APImodel}> +({ + collection : async({page}, use) => { + await use(new CollectionModel(page)) + }, + apiReq : async ({page},use) => { + await use(new APImodel(page)) + } +}) + +export { expect} from '@playwright/test' \ No newline at end of file From 4a486a8c7ae8041a340d7a83e65981863c7d6961 Mon Sep 17 00:00:00 2001 From: biocodeit Date: Fri, 28 Nov 2025 11:10:21 +0530 Subject: [PATCH 03/19] rename collection fixture --- e2e/collection-test.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/collection-test.spec.ts b/e2e/collection-test.spec.ts index b7f3c7c..932205a 100644 --- a/e2e/collection-test.spec.ts +++ b/e2e/collection-test.spec.ts @@ -1,4 +1,4 @@ -import {test, expect} from './fixtures/collection' +import {test, expect} from './fixtures/collection.fixure' test('check creation of new collection', async ({collection, apiReq ,page}) => { const collectionName = 'New Test Collection' From 754058b8020ae9396235d14ca91bc5fae25b63db Mon Sep 17 00:00:00 2001 From: biocodeit Date: Fri, 28 Nov 2025 11:12:30 +0530 Subject: [PATCH 04/19] add environment model to collection fixture --- e2e/fixtures/collection.fixure.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/e2e/fixtures/collection.fixure.ts b/e2e/fixtures/collection.fixure.ts index 42f7e89..cc26f10 100644 --- a/e2e/fixtures/collection.fixure.ts +++ b/e2e/fixtures/collection.fixure.ts @@ -1,14 +1,22 @@ import {test as base} from '@playwright/test' import { CollectionModel } from '../object-models/collection-model' import { APImodel } from '../object-models/api-model' +import { EnvSettings } from '../object-models/environemt-setting.model' -export const test = base.extend<{collection: CollectionModel, apiReq: APImodel}> +export const test = base.extend<{ + collection: CollectionModel, + apiReq: APImodel, + envSetup : EnvSettings +}> ({ collection : async({page}, use) => { await use(new CollectionModel(page)) }, apiReq : async ({page},use) => { await use(new APImodel(page)) + }, + envSetup: async({page},use) => { + use(new EnvSettings(page)) } }) From 0df5736f0c26d7d35071ad7cad1c70eb91ad0564 Mon Sep 17 00:00:00 2001 From: biocodeit Date: Fri, 28 Nov 2025 11:14:37 +0530 Subject: [PATCH 05/19] redundant remove collection.ts fixture due to renaming --- e2e/fixtures/collection.ts | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 e2e/fixtures/collection.ts diff --git a/e2e/fixtures/collection.ts b/e2e/fixtures/collection.ts deleted file mode 100644 index 42f7e89..0000000 --- a/e2e/fixtures/collection.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {test as base} from '@playwright/test' -import { CollectionModel } from '../object-models/collection-model' -import { APImodel } from '../object-models/api-model' - -export const test = base.extend<{collection: CollectionModel, apiReq: APImodel}> -({ - collection : async({page}, use) => { - await use(new CollectionModel(page)) - }, - apiReq : async ({page},use) => { - await use(new APImodel(page)) - } -}) - -export { expect} from '@playwright/test' \ No newline at end of file From 29ebdb0dce32d4fa3e2a9842cedd86e03147cb84 Mon Sep 17 00:00:00 2001 From: biocodeit Date: Fri, 28 Nov 2025 11:19:12 +0530 Subject: [PATCH 06/19] add test script to api model made all locator search in request build section as parent added test script locators added test script writing feature seperate getrequestResult from apiwrap combined getreResult with send button added fill header for request --- e2e/object-models/api-model.ts | 75 ++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/e2e/object-models/api-model.ts b/e2e/object-models/api-model.ts index 8501b6c..d59eb3e 100644 --- a/e2e/object-models/api-model.ts +++ b/e2e/object-models/api-model.ts @@ -6,58 +6,71 @@ export class APImodel extends BasePage { constructor(page:Page) {super(page) } - - fillUrl : Locator = this.page.getByPlaceholder('https://api.example.com/endpoint') - sendBtn : Locator = this.page.getByRole('button', {name:'Send'}) - responseBody : Locator = this.page.locator('div').filter({hasText:'Body'}) - .filter({hasText:'Compare'}).last() - .getByRole('presentation') - reqType : Locator = this.page.getByRole('combobox') - reqBody : Locator = this.page.locator('div',{hasText:'Request Body (JSON)', - has: this.page.getByRole('presentation')}) - .last() - .getByRole('textbox') + reqBuilderMain : Locator = this.page.locator('#_R_5klrlb_') + fillUrl : Locator = this.reqBuilderMain.getByPlaceholder('https://api.example.com/endpoint') + sendBtn : Locator = this.reqBuilderMain.getByRole('button', {name:'Send'}) + responseBody : Locator = this.reqBuilderMain.getByRole('presentation') + reqType : Locator = this.reqBuilderMain.getByRole('combobox') + reqBodySection : Locator = this.reqBuilderMain.locator('div',{hasText:'Headers'}).getByRole('button',{name: 'Body'}) + reqBody : Locator = this.reqBuilderMain.locator('div').filter({has: this.page.getByRole('presentation')}) + .filter({hasText:'Request Body (JSON)'}).last() + .getByRole('textbox') + reqHeaderSection: Locator = this.reqBuilderMain.getByRole('button',{name:'Headers'}) + reqAddNewHeader : Locator = this.reqBuilderMain.getByRole('button',{name:'+ Add Header'}) + reqHeaderName : Locator = this.reqBuilderMain.getByPlaceholder('Header name').last() + reqHeaderValue : Locator = this.reqBuilderMain.getByPlaceholder('Header value').last() + reqTestSec : Locator = this.reqBuilderMain.getByRole('button',{name:'Tests'}) + reqTestTextBox : Locator = this.page.locator('div') + .filter({has:this.page.getByRole('presentation')}) + .filter({hasText:'Tests (Post-response Script)'}).last() + .getByRole('textbox') async get(url:string):Promise { await this.fillUrl.fill(url) await this.reqType.selectOption('GET') - await this.sendBtn.click() - return await this.getResponseResult() } async post(url: string, data : string) - { - await this.fillUrl.fill(url) - await this.reqType.selectOption('POST') - await this.fillRequestBody(data) - await this.sendBtn.click() - return await this.getResponseResult() - } + {await this.apiwrap('POST', url, data)} async patch(url: string, data: string) - { - await this.fillUrl.fill(url) - await this.reqType.selectOption('PATCH') - await this.fillRequestBody(data) - await this.sendBtn.click() - return await this.getResponseResult() - } + {await this.apiwrap('PATCH', url, data)} - private async fillRequestBody (data: string) - { + private async fillRequestBody (data: string) { await this.clearall(this.reqBody) await this.reqBody.fill(data) } - private async getResponseResult() - { + async getResponseResult() { + await this.sendBtn.click() await this.page.waitForLoadState('domcontentloaded') await this.responseBody.locator('.view-line').last().textContent() let result = await this.responseBody.textContent() result = result.replace(/\u00A0/g, ' ') return result } + + private async apiwrap(method:string, url:string, data: string) {{ + await this.fillUrl.fill(url) + await this.reqType.selectOption(method) + await this.reqBodySection.click() + await this.fillRequestBody(data) + } + } + + async fillHeader(hName:string, hValue: string) { + await this.reqHeaderSection.click() + await this.reqAddNewHeader.click() + await this.reqHeaderName.fill(hName) + await this.reqHeaderValue.fill(hValue) + } + + async writeTest(testData:string) { + await this.reqTestSec.click() + await this.clearall(this.reqTestTextBox) + await this.reqTestTextBox.fill(testData) + } } \ No newline at end of file From f72d51988f19e8e323c9b8092e549539ab2454d1 Mon Sep 17 00:00:00 2001 From: biocodeit Date: Fri, 28 Nov 2025 11:23:46 +0530 Subject: [PATCH 07/19] add env-setting model --- e2e/object-models/environemt-setting.model.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 e2e/object-models/environemt-setting.model.ts diff --git a/e2e/object-models/environemt-setting.model.ts b/e2e/object-models/environemt-setting.model.ts new file mode 100644 index 0000000..6198e8e --- /dev/null +++ b/e2e/object-models/environemt-setting.model.ts @@ -0,0 +1,41 @@ +import { BasePage} from "./BasePage"; +import { type Page, Locator } from "@playwright/test"; + +export class EnvSettings extends BasePage { + constructor(page:Page) { + super(page) + } + envSec : Locator = this.page.getByRole('button',{name:'Environment'}) + envDiv : Locator = this.page.locator('//div') + .filter({has: this.envSec}) + .filter({has: this.page + .getByRole('heading',{name:'Environment'})}).last() + createNewEnvBtn : Locator = this.envDiv.getByRole('button', {name:'+ New'}) + saveNewEnvBtn : Locator = this.envDiv.getByRole('button',{name: 'Create'}) + envNameInput : Locator = this.envDiv.getByPlaceholder('Environment name') + addEnvVarBtn : Locator = this.envDiv.getByRole('button', {name:'+ Add'}) + envVarNameInput : Locator = this.envDiv.getByPlaceholder('Variable name', {exact: true}) + envVarValueInput : Locator = this.envDiv.getByPlaceholder('Value') + envVarSaveBtn : Locator = this.envDiv.getByTitle('Add variable') + envVarSaveConfirm : Locator = this.envDiv.getByRole('button', {name: 'Save'}) + + async createNewEnv(envName:string) { + await this.envSec.click() + await this.createNewEnvBtn.click() + await this.envNameInput.fill(envName) + await this.saveNewEnvBtn.click() + } + + async addEnvVariable(envName:string , varName:string, varValue: string) { + await this.envDiv.locator('div', {hasText: envName}) + .filter({hasText: 'Edit'}).last() + .getByRole('button', {name: 'Edit'}).click() + await this.addEnvVarBtn.click() + await this.envVarNameInput.fill(varName) + await this.envVarValueInput.fill(varValue) + await this.envVarSaveBtn.click() + await this.envVarSaveConfirm.click() + } + + +} \ No newline at end of file From 3adbccef86928f8b700eee039faa2ef21494e2db Mon Sep 17 00:00:00 2001 From: biocodeit Date: Fri, 28 Nov 2025 11:24:16 +0530 Subject: [PATCH 08/19] add a complete user project includes: set auth variables to request login token request set token to env var user login --- e2e/mini-project.spec.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 e2e/mini-project.spec.ts diff --git a/e2e/mini-project.spec.ts b/e2e/mini-project.spec.ts new file mode 100644 index 0000000..d817b35 --- /dev/null +++ b/e2e/mini-project.spec.ts @@ -0,0 +1,34 @@ +import {test, expect} from './fixtures/collection.fixure' + + +const postData1: string = '{ "email": "eve.holt@reqres.in", "password": "cityslicka" }' +const postData2: string = '{ "name": "morpheus", "job": "leader"}' +const testData1 : string = `const varb = pm.response.json() +pm.variables.set('toki',varb.token) +console.log(pm.variables.get('toki'))` +const testData2 : string =`const varb = pm.response.json() +pm.variables.set('userId', varb.id) +console.log(pm.variables.get('userId'))` + + +test('testing miniproject', async({collection, apiReq, envSetup, page}) => { + await page.goto('/') + + await apiReq.fillHeader('x-api-key', 'reqres-free-v1') + + await envSetup.createNewEnv('QA') + await envSetup.addEnvVariable('QA', 'baseURL', 'reqres.in') + await apiReq.post('https://{{baseURL}}/api/login', postData1 ) + await expect(page).toHaveURL('/') + await apiReq.writeTest(testData1) + await collection.createCollection('User Collection') + await collection.saveToCollection('login token', 'User Collection') + await apiReq.getResponseResult() + + await apiReq.fillHeader('Authorization', '{{toki}}') + await apiReq.post('https://{{baseURL}}/api/users', postData2) + await apiReq.writeTest(testData2) + await collection.saveToCollection('login user', 'User Collection') + await apiReq.getResponseResult() + +}) \ No newline at end of file From 27fdc8057923c180b95118e03648bce54b16650d Mon Sep 17 00:00:00 2001 From: biocodeit Date: Fri, 28 Nov 2025 12:05:51 +0530 Subject: [PATCH 09/19] resolve bugs after previous env model changes dependent models needed restructuring --- e2e/basic-workflow.spec.ts | 21 +++++++++++---------- e2e/object-models/api-model.ts | 3 ++- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/e2e/basic-workflow.spec.ts b/e2e/basic-workflow.spec.ts index 92367cd..58df0a2 100644 --- a/e2e/basic-workflow.spec.ts +++ b/e2e/basic-workflow.spec.ts @@ -1,30 +1,31 @@ -import { test, expect } from './fixtures/api.fixture' +import { test, expect } from './fixtures/collection.fixure' -test('checking for basic workflow functionality', async ({api, page}) => { +test('checking for basic workflow functionality', async ({apiReq, page}) => { await page.goto('/') - let result = await api.get('https://jsonplaceholder.typicode.com/todos/1') + await apiReq.get('https://jsonplaceholder.typicode.com/todos/1') + let result = await apiReq.getResponseResult() expect(result).toContain('"userId": 1') expect(result).toContain('"id": 1') expect(result).toContain('"title"') }) -test('post checking for postt request', async ({api,page}) => { +test('post checking for postt request', async ({apiReq,page}) => { await page.goto('/') - const result = await api.post('https://jsonplaceholder.typicode.com/posts', postData) + await apiReq.post('https://jsonplaceholder.typicode.com/posts', postData) + const result = await apiReq.getResponseResult() await expect(result).toContain('"title": "foo"') await expect(result).toContain('"body": "bar"') }) -test('checking for patch request', async ({api, page}) =>{ +test('checking for patch request', async ({apiReq, page}) =>{ await page.goto('/') - const result = await api.patch('https://jsonplaceholder.typicode.com/posts/1', patchData) + await apiReq.patch('https://jsonplaceholder.typicode.com/posts/1', patchData) + const result = await apiReq.getResponseResult() await expect(result).toContain('"title": "foo"') await expect(result).toContain('"id": 1') }) -const postData = `{ "title": "foo", "body": "bar", "userId" : 101}` - - +const postData = `{ "title": "foo", "body": "bar", "userId" : 101}` const patchData = `{ "id": 1, "title": "foo"}` \ No newline at end of file diff --git a/e2e/object-models/api-model.ts b/e2e/object-models/api-model.ts index d59eb3e..49468f6 100644 --- a/e2e/object-models/api-model.ts +++ b/e2e/object-models/api-model.ts @@ -9,7 +9,8 @@ export class APImodel extends BasePage { reqBuilderMain : Locator = this.page.locator('#_R_5klrlb_') fillUrl : Locator = this.reqBuilderMain.getByPlaceholder('https://api.example.com/endpoint') sendBtn : Locator = this.reqBuilderMain.getByRole('button', {name:'Send'}) - responseBody : Locator = this.reqBuilderMain.getByRole('presentation') + responseSec : Locator = this.page.locator('#_R_9klrlb_') + responseBody : Locator = this.responseSec.getByRole('presentation') reqType : Locator = this.reqBuilderMain.getByRole('combobox') reqBodySection : Locator = this.reqBuilderMain.locator('div',{hasText:'Headers'}).getByRole('button',{name: 'Body'}) reqBody : Locator = this.reqBuilderMain.locator('div').filter({has: this.page.getByRole('presentation')}) From 04f89881601ecca1151f96d3eb4f2fb70322fafc Mon Sep 17 00:00:00 2001 From: biocodeit Date: Fri, 28 Nov 2025 12:09:39 +0530 Subject: [PATCH 10/19] renamed the mini-project to complete-user-login --- e2e/{mini-project.spec.ts => complete-user-login.spec.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename e2e/{mini-project.spec.ts => complete-user-login.spec.ts} (100%) diff --git a/e2e/mini-project.spec.ts b/e2e/complete-user-login.spec.ts similarity index 100% rename from e2e/mini-project.spec.ts rename to e2e/complete-user-login.spec.ts From ee64242138c415e824a50b6414eed58d92169cd2 Mon Sep 17 00:00:00 2001 From: Daniel Bitengo Date: Thu, 27 Nov 2025 20:28:48 +0300 Subject: [PATCH 11/19] fix: prevent crash when entering invalid URLs in request builder, fixes #32 (#36) Added comprehensive URL validation to prevent TypeError when users type partial or invalid URLs in the request input field. ## Changes ### Core Fix - Added URL validation helper functions in src/lib/url-validator.ts - Updated RequestBuilder to safely handle invalid URLs in tab name generation - Prevents crash when typing partial URLs like "h", "ht", "https:", etc. ### User Experience Improvements - Added visual feedback: red border when URL is invalid - Added inline error message: "Please enter a valid HTTP(S) URL" - Smart validation: allows template variables like {{baseUrl}} for environment variables - Non-blocking: users can still attempt to send requests with variables ### Testing - Added comprehensive test suite with 30+ test cases - Tests cover: valid/invalid URLs, template variables, edge cases, partial URLs - All tests verify the fix handles the crash scenario ## Technical Details The crash occurred in a useEffect hook that tried to construct a URL object without validation. The URL constructor throws TypeError for invalid input, causing the app to crash on every keystroke as users typed. The fix uses a three-pronged approach: 1. Validate URLs before constructing URL objects 2. Allow template variables for environment variable substitution 3. Gracefully fallback to displaying raw URL string for invalid URLs Fixes #32 --- src/components/RequestBuilder.tsx | 77 ++++++++++------ src/lib/__tests__/url-validator.test.ts | 116 ++++++++++++++++++++++++ src/lib/url-validator.ts | 44 +++++++++ 3 files changed, 208 insertions(+), 29 deletions(-) create mode 100644 src/lib/__tests__/url-validator.test.ts create mode 100644 src/lib/url-validator.ts diff --git a/src/components/RequestBuilder.tsx b/src/components/RequestBuilder.tsx index 6d07d7d..b82105a 100644 --- a/src/components/RequestBuilder.tsx +++ b/src/components/RequestBuilder.tsx @@ -16,6 +16,7 @@ import CodeGenerationModal from './CodeGenerationModal'; import VariablesPanel from './VariablesPanel'; import WebSocketPanel from './WebSocketPanel'; import { scriptingService, ScriptResult } from '@/lib/scripting-service'; +import { isValidUrl, shouldShowUrlError } from '@/lib/url-validator'; const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'] as const; const METHODS_WITH_BODY = ['POST', 'PUT', 'PATCH']; @@ -72,6 +73,7 @@ export default function RequestBuilder({ selectedHistoryItem, selectedRequest }: // Derived state - must be before hooks that use it const hasBody = METHODS_WITH_BODY.includes(method); + const showUrlValidationError = shouldShowUrlError(url); // Load saved WebSocket panel height from localStorage useEffect(() => { @@ -211,7 +213,11 @@ export default function RequestBuilder({ selectedHistoryItem, selectedRequest }: url, headers, body, - name: url ? `${method} ${new URL(url).pathname}` : 'New Request', + name: url && isValidUrl(url) + ? `${method} ${new URL(url).pathname}` + : url + ? `${method} ${url}` + : 'New Request', }); } } @@ -507,34 +513,46 @@ export default function RequestBuilder({ selectedHistoryItem, selectedRequest }: Request Builder -
- - - setUrl(e.target.value)} - placeholder="https://api.example.com/endpoint" - className="flex-1 min-w-[300px] px-3 py-2 border border-gray-300 dark:border-gray-700 rounded bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100" - onKeyDown={(e) => { - if (e.key === 'Enter' && !e.shiftKey) { - handleSend(); - } - }} - /> - - +
{/* Save to Collection Dialog */} diff --git a/src/lib/__tests__/url-validator.test.ts b/src/lib/__tests__/url-validator.test.ts new file mode 100644 index 0000000..b62cc93 --- /dev/null +++ b/src/lib/__tests__/url-validator.test.ts @@ -0,0 +1,116 @@ +import { describe, it, expect } from 'vitest'; +import { hasTemplateVariables, isValidUrl, shouldShowUrlError } from '../url-validator'; + +describe('URL Validator', () => { + describe('hasTemplateVariables', () => { + it('should return true for strings with template variables', () => { + expect(hasTemplateVariables('{{baseUrl}}')).toBe(true); + expect(hasTemplateVariables('https://{{host}}/api')).toBe(true); + expect(hasTemplateVariables('https://api.example.com/{{endpoint}}')).toBe(true); + expect(hasTemplateVariables('{{protocol}}://{{host}}:{{port}}')).toBe(true); + }); + + it('should return false for strings without template variables', () => { + expect(hasTemplateVariables('https://api.example.com')).toBe(false); + expect(hasTemplateVariables('http://localhost:3000')).toBe(false); + expect(hasTemplateVariables('plain text')).toBe(false); + expect(hasTemplateVariables('')).toBe(false); + }); + + it('should return false for malformed template syntax', () => { + expect(hasTemplateVariables('{baseUrl}')).toBe(false); + expect(hasTemplateVariables('{{baseUrl')).toBe(false); + expect(hasTemplateVariables('baseUrl}}')).toBe(false); + expect(hasTemplateVariables('{{}}')).toBe(false); + }); + }); + + describe('isValidUrl', () => { + it('should return true for valid HTTP URLs', () => { + expect(isValidUrl('http://example.com')).toBe(true); + expect(isValidUrl('http://api.example.com/v1/users')).toBe(true); + expect(isValidUrl('http://localhost:3000')).toBe(true); + expect(isValidUrl('http://192.168.1.1:8080/api')).toBe(true); + }); + + it('should return true for valid HTTPS URLs', () => { + expect(isValidUrl('https://example.com')).toBe(true); + expect(isValidUrl('https://api.example.com/v1/users')).toBe(true); + expect(isValidUrl('https://jsonplaceholder.typicode.com/posts')).toBe(true); + expect(isValidUrl('https://example.com:443/path?query=value')).toBe(true); + }); + + it('should return true for URLs with template variables', () => { + expect(isValidUrl('https://{{host}}/api')).toBe(true); + expect(isValidUrl('{{baseUrl}}/users')).toBe(true); + expect(isValidUrl('https://api.example.com/{{endpoint}}')).toBe(true); + }); + + it('should return false for invalid URLs', () => { + expect(isValidUrl('not a url')).toBe(false); + expect(isValidUrl('ftp://example.com')).toBe(false); // FTP not allowed + expect(isValidUrl('example.com')).toBe(false); // Missing protocol + expect(isValidUrl('htt')).toBe(false); + expect(isValidUrl('https:')).toBe(false); + expect(isValidUrl('https:/')).toBe(false); + expect(isValidUrl('https://')).toBe(false); + }); + + it('should return false for empty or very short strings', () => { + expect(isValidUrl('')).toBe(false); + expect(isValidUrl(' ')).toBe(false); + expect(isValidUrl('http')).toBe(false); + expect(isValidUrl('h')).toBe(false); + }); + + it('should return false for partial URLs being typed', () => { + expect(isValidUrl('h')).toBe(false); + expect(isValidUrl('ht')).toBe(false); + expect(isValidUrl('htt')).toBe(false); + expect(isValidUrl('http')).toBe(false); + expect(isValidUrl('https')).toBe(false); + expect(isValidUrl('https:')).toBe(false); + expect(isValidUrl('https:/')).toBe(false); + expect(isValidUrl('https://')).toBe(false); + expect(isValidUrl('https://e')).toBe(false); + }); + + it('should handle URLs with query parameters and fragments', () => { + expect(isValidUrl('https://example.com?foo=bar')).toBe(true); + expect(isValidUrl('https://example.com#section')).toBe(true); + expect(isValidUrl('https://example.com?foo=bar&baz=qux#section')).toBe(true); + }); + }); + + describe('shouldShowUrlError', () => { + it('should return true for invalid URLs without template variables', () => { + expect(shouldShowUrlError('not a url')).toBe(true); + expect(shouldShowUrlError('example.com')).toBe(true); + expect(shouldShowUrlError('h')).toBe(true); + expect(shouldShowUrlError('https:')).toBe(true); + expect(shouldShowUrlError('ftp://example.com')).toBe(true); + }); + + it('should return false for valid URLs', () => { + expect(shouldShowUrlError('https://example.com')).toBe(false); + expect(shouldShowUrlError('http://localhost:3000')).toBe(false); + expect(shouldShowUrlError('https://api.example.com/v1/users')).toBe(false); + }); + + it('should return false for URLs with template variables', () => { + expect(shouldShowUrlError('{{baseUrl}}/api')).toBe(false); + expect(shouldShowUrlError('https://{{host}}/users')).toBe(false); + expect(shouldShowUrlError('https://api.example.com/{{endpoint}}')).toBe(false); + }); + + it('should return false for empty strings', () => { + expect(shouldShowUrlError('')).toBe(false); + expect(shouldShowUrlError(' ')).toBe(false); + }); + + it('should handle whitespace correctly', () => { + expect(shouldShowUrlError(' https://example.com ')).toBe(false); + expect(shouldShowUrlError(' invalid url ')).toBe(true); + }); + }); +}); diff --git a/src/lib/url-validator.ts b/src/lib/url-validator.ts new file mode 100644 index 0000000..196bc4e --- /dev/null +++ b/src/lib/url-validator.ts @@ -0,0 +1,44 @@ +/** + * URL Validation Utilities + * Provides functions for validating and checking URLs in the RestBolt app + */ + +/** + * Checks if a string contains template variables (e.g., {{baseUrl}}, {{token}}) + * @param str - The string to check + * @returns true if the string contains template variables + */ +export const hasTemplateVariables = (str: string): boolean => { + return /\{\{.+?\}\}/.test(str); +}; + +/** + * Validates if a string is a valid HTTP(S) URL + * Also returns true if the URL contains template variables + * @param urlString - The URL string to validate + * @returns true if the URL is valid, uses http/https protocol, or contains template variables + */ +export const isValidUrl = (urlString: string): boolean => { + if (!urlString || urlString.trim().length < 8) return false; + + // Allow URLs with template variables + if (hasTemplateVariables(urlString)) return true; + + try { + const url = new URL(urlString); + return url.protocol === 'http:' || url.protocol === 'https:'; + } catch { + return false; + } +}; + +/** + * Checks if URL should show validation error + * Only show error if URL is non-empty, has no template variables, and is invalid + * @param urlString - The URL string to check + * @returns true if should show validation error + */ +export const shouldShowUrlError = (urlString: string): boolean => { + const trimmed = urlString.trim(); + return trimmed.length > 0 && !hasTemplateVariables(trimmed) && !isValidUrl(trimmed); +}; From efad928fe3d434a38c600b6ffe2b3486899ef1dc Mon Sep 17 00:00:00 2001 From: biocodeit Date: Fri, 28 Nov 2025 16:11:08 +0530 Subject: [PATCH 12/19] refactor mini project --- e2e/complete-user-login.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/e2e/complete-user-login.spec.ts b/e2e/complete-user-login.spec.ts index d817b35..3f07a2d 100644 --- a/e2e/complete-user-login.spec.ts +++ b/e2e/complete-user-login.spec.ts @@ -1,6 +1,5 @@ import {test, expect} from './fixtures/collection.fixure' - const postData1: string = '{ "email": "eve.holt@reqres.in", "password": "cityslicka" }' const postData2: string = '{ "name": "morpheus", "job": "leader"}' const testData1 : string = `const varb = pm.response.json() From 61d68e3cfbaf0e22dd82fa1b375fbf894581cce5 Mon Sep 17 00:00:00 2001 From: biocodeit Date: Fri, 28 Nov 2025 20:11:27 +0530 Subject: [PATCH 13/19] add cpu throttle to check for flaky test WHY: - test sometime fail in CI - so throttling cpu will help - configured throttling only for local --- e2e/object-models/BasePage.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/e2e/object-models/BasePage.ts b/e2e/object-models/BasePage.ts index c364113..878e412 100644 --- a/e2e/object-models/BasePage.ts +++ b/e2e/object-models/BasePage.ts @@ -1,9 +1,17 @@ import {type Page, Locator} from '@playwright/test' - - export class BasePage { - constructor(protected page:Page) {} + constructor(protected page:Page) { + + if (process.env.THROTLE) { + (async () => { + const context = this.page.context(); + const cdpSession = await context.newCDPSession(this.page); + await cdpSession.send('Emulation.setCPUThrottlingRate', { rate: 6 }); + + })() + } + } protected button(btnName: string): Locator { From 976116413105f91f2cd1485c9ad451830df6e2d5 Mon Sep 17 00:00:00 2001 From: biocodeit Date: Sat, 29 Nov 2025 09:03:18 +0530 Subject: [PATCH 14/19] repair locator syntax --- e2e/collection-test.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/collection-test.spec.ts b/e2e/collection-test.spec.ts index 932205a..46a03be 100644 --- a/e2e/collection-test.spec.ts +++ b/e2e/collection-test.spec.ts @@ -18,6 +18,6 @@ test('check creation of new collection', async ({collection, apiReq ,page}) => { //4. Check if the request got saved in the collection await page.locator('div') .filter({has: page.getByTitle('Delete collection')}) - .getByRole('button').filter({hasText:collectionName}).click() + .getByRole('button',{name:collectionName}).click() await expect(page.getByText('Post 1')).toBeVisible() }) \ No newline at end of file From 523309bf541a38c874a1f4ede37275da3527c7bf Mon Sep 17 00:00:00 2001 From: biocodeit Date: Sat, 29 Nov 2025 09:03:49 +0530 Subject: [PATCH 15/19] add 'detached' instead of 'hidden' for save collection div to avoid flaky test and occurance of multiple instance need to wait till one of the button instance is closed. --- e2e/object-models/collection-model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/object-models/collection-model.ts b/e2e/object-models/collection-model.ts index bcc1f01..bcbc09e 100644 --- a/e2e/object-models/collection-model.ts +++ b/e2e/object-models/collection-model.ts @@ -26,6 +26,6 @@ export class CollectionModel extends BasePage { .filter({ hasText: 'Select Collection' }).last() await collDiv.getByRole('textbox').fill(postNam) await collDiv.getByRole('button').filter({ hasText: collNam }).click() - await collDiv.getByLabel('Request Name (optional)').waitFor({ state: 'hidden' }) + await collDiv.waitFor({ state: 'detached' }) } } From 1255d479123815d80c67c965d4ea715742308b9a Mon Sep 17 00:00:00 2001 From: biocodeit Date: Sat, 29 Nov 2025 10:25:16 +0530 Subject: [PATCH 16/19] add network idle before parsing api response --- e2e/object-models/api-model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/object-models/api-model.ts b/e2e/object-models/api-model.ts index 49468f6..081a743 100644 --- a/e2e/object-models/api-model.ts +++ b/e2e/object-models/api-model.ts @@ -46,7 +46,7 @@ export class APImodel extends BasePage { async getResponseResult() { await this.sendBtn.click() - await this.page.waitForLoadState('domcontentloaded') + await this.page.waitForLoadState('networkidle') await this.responseBody.locator('.view-line').last().textContent() let result = await this.responseBody.textContent() result = result.replace(/\u00A0/g, ' ') From c038307d38519a1c3ca15721dcb21d9959085842 Mon Sep 17 00:00:00 2001 From: biocodeit Date: Sun, 30 Nov 2025 21:16:24 +0530 Subject: [PATCH 17/19] rename files , spell mistake --- e2e/fixtures/{collection.fixure.ts => collection.fixture.ts} | 0 .../{environemt-setting.model.ts => environment-setting.model.ts} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename e2e/fixtures/{collection.fixure.ts => collection.fixture.ts} (100%) rename e2e/object-models/{environemt-setting.model.ts => environment-setting.model.ts} (100%) diff --git a/e2e/fixtures/collection.fixure.ts b/e2e/fixtures/collection.fixture.ts similarity index 100% rename from e2e/fixtures/collection.fixure.ts rename to e2e/fixtures/collection.fixture.ts diff --git a/e2e/object-models/environemt-setting.model.ts b/e2e/object-models/environment-setting.model.ts similarity index 100% rename from e2e/object-models/environemt-setting.model.ts rename to e2e/object-models/environment-setting.model.ts From 23d2fc1bb6bcc31537195d9f16fe277e9618045c Mon Sep 17 00:00:00 2001 From: biocodeit Date: Mon, 1 Dec 2025 10:40:42 +0530 Subject: [PATCH 18/19] refactor after renaming fixture files --- e2e/basic-workflow.spec.ts | 2 +- e2e/collection-test.spec.ts | 2 +- e2e/complete-user-login.spec.ts | 2 +- e2e/fixtures/collection.fixture.ts | 2 +- e2e/fixtures/collection.fixure.ts | 23 +++++++++++++++++++++++ 5 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 e2e/fixtures/collection.fixure.ts diff --git a/e2e/basic-workflow.spec.ts b/e2e/basic-workflow.spec.ts index 58df0a2..9e8e007 100644 --- a/e2e/basic-workflow.spec.ts +++ b/e2e/basic-workflow.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from './fixtures/collection.fixure' +import { test, expect } from './fixtures/collection.fixture' test('checking for basic workflow functionality', async ({apiReq, page}) => { diff --git a/e2e/collection-test.spec.ts b/e2e/collection-test.spec.ts index 46a03be..bb713c1 100644 --- a/e2e/collection-test.spec.ts +++ b/e2e/collection-test.spec.ts @@ -1,4 +1,4 @@ -import {test, expect} from './fixtures/collection.fixure' +import {test, expect} from './fixtures/collection.fixture' test('check creation of new collection', async ({collection, apiReq ,page}) => { const collectionName = 'New Test Collection' diff --git a/e2e/complete-user-login.spec.ts b/e2e/complete-user-login.spec.ts index 3f07a2d..fdc8357 100644 --- a/e2e/complete-user-login.spec.ts +++ b/e2e/complete-user-login.spec.ts @@ -1,4 +1,4 @@ -import {test, expect} from './fixtures/collection.fixure' +import {test, expect} from './fixtures/collection.fixture' const postData1: string = '{ "email": "eve.holt@reqres.in", "password": "cityslicka" }' const postData2: string = '{ "name": "morpheus", "job": "leader"}' diff --git a/e2e/fixtures/collection.fixture.ts b/e2e/fixtures/collection.fixture.ts index cc26f10..43a6d89 100644 --- a/e2e/fixtures/collection.fixture.ts +++ b/e2e/fixtures/collection.fixture.ts @@ -1,7 +1,7 @@ import {test as base} from '@playwright/test' import { CollectionModel } from '../object-models/collection-model' import { APImodel } from '../object-models/api-model' -import { EnvSettings } from '../object-models/environemt-setting.model' +import { EnvSettings } from '../object-models/environment-setting.model' export const test = base.extend<{ collection: CollectionModel, diff --git a/e2e/fixtures/collection.fixure.ts b/e2e/fixtures/collection.fixure.ts new file mode 100644 index 0000000..bb29f66 --- /dev/null +++ b/e2e/fixtures/collection.fixure.ts @@ -0,0 +1,23 @@ +import {test as base} from '@playwright/test' +import { CollectionModel } from '../object-models/collection-model' +import { APImodel } from '../object-models/api-model' +import { EnvSettings } from '../object-models/environment-setting.model' + +export const test = base.extend<{ + collection: CollectionModel, + apiReq: APImodel, + envSetup : EnvSettings +}> +({ + collection : async({page}, use) => { + await use(new CollectionModel(page)) + }, + apiReq : async ({page},use) => { + await use(new APImodel(page)) + }, + envSetup: async({page},use) => { + use(new EnvSettings(page)) + } +}) + +export { expect} from '@playwright/test' \ No newline at end of file From f80a8213c9931863dd983ffb6942e8fe5b3bee88 Mon Sep 17 00:00:00 2001 From: biocodeit Date: Mon, 1 Dec 2025 10:46:52 +0530 Subject: [PATCH 19/19] deleted redundant collection fixture file --- e2e/fixtures/collection.fixure.ts | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 e2e/fixtures/collection.fixure.ts diff --git a/e2e/fixtures/collection.fixure.ts b/e2e/fixtures/collection.fixure.ts deleted file mode 100644 index bb29f66..0000000 --- a/e2e/fixtures/collection.fixure.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {test as base} from '@playwright/test' -import { CollectionModel } from '../object-models/collection-model' -import { APImodel } from '../object-models/api-model' -import { EnvSettings } from '../object-models/environment-setting.model' - -export const test = base.extend<{ - collection: CollectionModel, - apiReq: APImodel, - envSetup : EnvSettings -}> -({ - collection : async({page}, use) => { - await use(new CollectionModel(page)) - }, - apiReq : async ({page},use) => { - await use(new APImodel(page)) - }, - envSetup: async({page},use) => { - use(new EnvSettings(page)) - } -}) - -export { expect} from '@playwright/test' \ No newline at end of file