From 8552b47eada722e81a5b767cf6f3d5c952e9c828 Mon Sep 17 00:00:00 2001 From: Sid Date: Thu, 7 May 2026 22:17:11 +0530 Subject: [PATCH] feat: request restore (#7948) --- .../RequestTabs/RequestTab/index.js | 16 ++- tests/snapshots/basic.spec.ts | 91 +++++++++++++++ .../request-pane-interactivity.spec.ts | 108 ++++++++++++++++++ 3 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 tests/snapshots/request-pane-interactivity.spec.ts diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js index 3ab080c27..21aeaf46c 100644 --- a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js @@ -1,6 +1,6 @@ import React, { useCallback, useState, useRef, Fragment, useMemo, useEffect } from 'react'; import get from 'lodash/get'; -import { makeTabPermanent } from 'providers/ReduxStore/slices/tabs'; +import { makeTabPermanent, syncTabUid } from 'providers/ReduxStore/slices/tabs'; import { saveRequest, saveCollectionRoot, saveFolderRoot, saveEnvironment, saveCollectionSettings, closeTabs } from 'providers/ReduxStore/slices/collections/actions'; import useKeybinding from 'hooks/useKeybinding'; import { deleteRequestDraft, deleteCollectionDraft, deleteFolderDraft, clearEnvironmentsDraft } from 'providers/ReduxStore/slices/collections'; @@ -46,6 +46,20 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi item = findItemInCollectionByPathname(collection, tab.pathname); } + useEffect(() => { + const isRequestType = tab.type === 'request' + || tab.type === 'http-request' + || tab.type === 'graphql-request' + || tab.type === 'grpc-request' + || tab.type === 'ws-request'; + + if (!isRequestType || !tab.pathname || !item?.uid || tab.uid === item.uid) { + return; + } + + dispatch(syncTabUid({ oldUid: tab.uid, newUid: item.uid })); + }, [dispatch, item?.uid, tab.pathname, tab.type, tab.uid]); + const method = useMemo(() => { if (!item) return; switch (item.type) { diff --git a/tests/snapshots/basic.spec.ts b/tests/snapshots/basic.spec.ts index b84a9cc92..7f971f254 100644 --- a/tests/snapshots/basic.spec.ts +++ b/tests/snapshots/basic.spec.ts @@ -799,3 +799,94 @@ test.describe('Snapshot: File Structure', () => { }); }); }); + +test.describe('Snapshot: Basic Request Movement', () => { + test('requests interactivity is also restored', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('snap-structure'); + const colPath = await createTmpDir('col'); + + const app = await launchElectronApp({ userDataPath }); + const page = await app.firstWindow(); + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + await test.step('Create collection and open a request', async () => { + await createCollection(page, 'TestCol', colPath); + await createRequest(page, 'Req1', 'TestCol', { url: 'https://echo.usebruno.com', method: 'GET' }); + await openRequest(page, 'TestCol', 'Req1', { persist: true }); + await selectRequestPaneTab(page, 'Headers'); + }); + + await test.step('Close app and inspect snapshot file', async () => { + await page.waitForTimeout(2000); + await closeElectronApp(app); + }); + + await test.step('Verify request pane tabs remain interactive after restore', async () => { + const app2 = await launchElectronApp({ userDataPath }); + const page2 = await app2.firstWindow(); + await page2.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + const locators = buildCommonLocators(page2); + await expect(locators.tabs.requestTab('Req1')).toBeVisible({ timeout: 15000 }); + await locators.tabs.requestTab('Req1').click({ force: true }); + + await selectRequestPaneTab(page2, 'Headers'); + await selectRequestPaneTab(page2, 'Auth'); + await selectRequestPaneTab(page2, 'Vars'); + await selectRequestPaneTab(page2, 'Tests'); + await selectRequestPaneTab(page2, 'Params'); + + await closeElectronApp(app2); + }); + }); + + test('graphql request pane tab interactivity is restored after restart', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('snap-graphql-interactivity'); + const colPath = await createTmpDir('col'); + + const app = await launchElectronApp({ userDataPath }); + const page = await app.firstWindow(); + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + await test.step('Create collection and GraphQL request', async () => { + await createCollection(page, 'TestCol', colPath); + + const locators = buildCommonLocators(page); + await locators.sidebar.collection('TestCol').hover(); + await locators.actions.collectionActions('TestCol').click(); + await locators.dropdown.item('New Request').click(); + + await page.getByTestId('graphql-request').click(); + await page.getByTestId('request-name').fill('ReqGraph'); + await page.getByTestId('new-request-url').locator('.CodeMirror').click(); + await page.keyboard.type('https://echo.usebruno.com/graphql'); + await locators.modal.button('Create').click(); + + await openRequest(page, 'TestCol', 'ReqGraph', { persist: true }); + await selectRequestPaneTab(page, 'Headers'); + }); + + await test.step('Close and restart app', async () => { + await page.waitForTimeout(2000); + await closeElectronApp(app); + }); + + await test.step('Verify GraphQL pane tabs remain interactive', async () => { + const app2 = await launchElectronApp({ userDataPath }); + const page2 = await app2.firstWindow(); + await page2.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + const locators = buildCommonLocators(page2); + await expect(locators.tabs.requestTab('ReqGraph')).toBeVisible({ timeout: 15000 }); + await locators.tabs.requestTab('ReqGraph').click({ force: true }); + + await selectRequestPaneTab(page2, 'Headers'); + await selectRequestPaneTab(page2, 'Auth'); + await selectRequestPaneTab(page2, 'Vars'); + await selectRequestPaneTab(page2, 'Tests'); + await selectRequestPaneTab(page2, 'Query'); + + await closeElectronApp(app2); + }); + }); +}); diff --git a/tests/snapshots/request-pane-interactivity.spec.ts b/tests/snapshots/request-pane-interactivity.spec.ts new file mode 100644 index 000000000..0a41f02a9 --- /dev/null +++ b/tests/snapshots/request-pane-interactivity.spec.ts @@ -0,0 +1,108 @@ +import { test, expect, closeElectronApp } from '../../playwright'; +import { + createCollection, + openRequest, + selectRequestPaneTab +} from '../utils/page'; +import { buildCommonLocators } from '../utils/page/locators'; + +test.describe('Snapshot: Request Pane Interactivity', () => { + test('grpc request pane tab interactivity is restored after restart', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('snap-grpc-interactivity'); + const colPath = await createTmpDir('col'); + + const app = await launchElectronApp({ userDataPath }); + const page = await app.firstWindow(); + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + await test.step('Create collection and gRPC request', async () => { + await createCollection(page, 'TestCol', colPath); + + const locators = buildCommonLocators(page); + await locators.sidebar.collection('TestCol').hover(); + await locators.actions.collectionActions('TestCol').click(); + await locators.dropdown.item('New Request').click(); + + await page.getByTestId('grpc-request').click(); + await page.getByTestId('request-name').fill('ReqGrpc'); + await page.getByTestId('new-request-url').locator('.CodeMirror').click(); + await page.keyboard.type('grpc://localhost:50051'); + await locators.modal.button('Create').click(); + + await openRequest(page, 'TestCol', 'ReqGrpc', { persist: true }); + await selectRequestPaneTab(page, 'Metadata'); + }); + + await test.step('Close and restart app', async () => { + await page.waitForTimeout(2000); + await closeElectronApp(app); + }); + + await test.step('Verify gRPC pane tabs remain interactive', async () => { + const app2 = await launchElectronApp({ userDataPath }); + const page2 = await app2.firstWindow(); + await page2.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + const locators = buildCommonLocators(page2); + await expect(locators.tabs.requestTab('ReqGrpc')).toBeVisible({ timeout: 15000 }); + await locators.tabs.requestTab('ReqGrpc').click({ force: true }); + + await selectRequestPaneTab(page2, 'Metadata'); + await selectRequestPaneTab(page2, 'Auth'); + await selectRequestPaneTab(page2, 'Docs'); + await selectRequestPaneTab(page2, 'Message'); + + await closeElectronApp(app2); + }); + }); + + test('websocket request pane tab interactivity is restored after restart', async ({ launchElectronApp, createTmpDir }) => { + const userDataPath = await createTmpDir('snap-ws-interactivity'); + const colPath = await createTmpDir('col'); + + const app = await launchElectronApp({ userDataPath }); + const page = await app.firstWindow(); + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + await test.step('Create collection and WebSocket request', async () => { + await createCollection(page, 'TestCol', colPath); + + const locators = buildCommonLocators(page); + await locators.sidebar.collection('TestCol').hover(); + await locators.actions.collectionActions('TestCol').click(); + await locators.dropdown.item('New Request').click(); + + await page.getByTestId('ws-request').click(); + await page.getByTestId('request-name').fill('ReqWs'); + await page.getByTestId('new-request-url').locator('.CodeMirror').click(); + await page.keyboard.type('ws://localhost:8080'); + await locators.modal.button('Create').click(); + + await openRequest(page, 'TestCol', 'ReqWs', { persist: true }); + await selectRequestPaneTab(page, 'Headers'); + }); + + await test.step('Close and restart app', async () => { + await page.waitForTimeout(2000); + await closeElectronApp(app); + }); + + await test.step('Verify WebSocket pane tabs remain interactive', async () => { + const app2 = await launchElectronApp({ userDataPath }); + const page2 = await app2.firstWindow(); + await page2.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + const locators = buildCommonLocators(page2); + await expect(locators.tabs.requestTab('ReqWs')).toBeVisible({ timeout: 15000 }); + await locators.tabs.requestTab('ReqWs').click({ force: true }); + + await selectRequestPaneTab(page2, 'Headers'); + await selectRequestPaneTab(page2, 'Auth'); + await selectRequestPaneTab(page2, 'Settings'); + await selectRequestPaneTab(page2, 'Docs'); + await selectRequestPaneTab(page2, 'Message'); + + await closeElectronApp(app2); + }); + }); +});