fix(snapshot): folder nested script tab interactivity and tests (#8225)

* fix(snapshot): folder script interactivity

* fix: add tests for collection scripts
This commit is contained in:
Sid
2026-06-12 19:13:34 +05:30
committed by lohit
parent a09ddedf90
commit f05bb9c49d
9 changed files with 343 additions and 34 deletions

View File

@@ -22,7 +22,9 @@ const Script = ({ collection, folder }) => {
const responseScript = folder.draft ? get(folder, 'draft.request.script.res', '') : get(folder, 'root.request.script.res', '');
const tabs = useSelector((state) => state.tabs.tabs);
const focusedTab = find(tabs, (t) => t.uid === folder.uid);
const focusedTab = find(tabs, (tab) => tab.type === 'folder-settings' && (tab.uid === folder.uid || tab.folderUid === folder.uid))
|| find(tabs, (tab) => tab.type === 'folder-settings' && tab.pathname === folder.pathname);
const tabUid = focusedTab?.uid || folder.uid;
const scriptPaneTab = focusedTab?.scriptPaneTab;
// Default to post-response if pre-request script is empty (only when scriptPaneTab is null/undefined)
@@ -34,7 +36,7 @@ const Script = ({ collection, folder }) => {
const activeTab = scriptPaneTab || getDefaultTab();
const setActiveTab = (tab) => {
dispatch(updateScriptPaneTab({ uid: folder.uid, scriptPaneTab: tab }));
dispatch(updateScriptPaneTab({ uid: tabUid, scriptPaneTab: tab }));
};
const { displayedTheme } = useTheme();

View File

@@ -52,8 +52,9 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
|| tab.type === 'graphql-request'
|| tab.type === 'grpc-request'
|| tab.type === 'ws-request';
const shouldSyncUid = isRequestType || tab.type === 'folder-settings';
if (!isRequestType || !tab.pathname || !item?.uid || tab.uid === item.uid) {
if (!shouldSyncUid || !tab.pathname || !item?.uid || tab.uid === item.uid) {
return;
}

View File

@@ -446,6 +446,9 @@ export const tabsSlice = createSlice({
const tab = find(state.tabs, (t) => t.uid === oldUid);
if (tab) {
tab.uid = newUid;
if (tab.type === 'folder-settings') {
tab.folderUid = newUid;
}
if (state.activeTabUid === oldUid) {
state.activeTabUid = newUid;
}

View File

@@ -0,0 +1,135 @@
import { test, expect, closeElectronApp } from '../../playwright';
import {
createCollection,
createWorkspace,
focusCollectionSettingsTab,
openCollectionSettings,
readSnapshot,
findSnapshotCollectionTab,
selectCollectionPaneTab,
selectCollectionScriptPaneTab,
switchWorkspace,
waitForReadyPage,
waitForSnapshotFile
} from '../utils/page';
test.describe('Snapshot: collection Pane Interactivity', () => {
test('collection pane tab interactivity is preserved after workspace switch', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-collection-workspace-switch');
const colPath = await createTmpDir('col');
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
await test.step('Create collection and open collection settings', async () => {
await createCollection(page, 'TestCol', colPath);
await openCollectionSettings(page, 'TestCol', { persist: true });
await selectCollectionPaneTab(page, 'auth');
});
await test.step('Switch to a new workspace', async () => {
// Background flushing takes about 2 seconds to complete
await page.waitForTimeout(2000);
await createWorkspace(page, 'SecondWorkspace');
await expect(page.getByTestId('workspace-name')).toHaveText('SecondWorkspace', { timeout: 5000 });
});
await test.step('Switch back to original workspace and verify collection pane interactivity', async () => {
await switchWorkspace(page, 'My Workspace');
await openCollectionSettings(page, 'TestCol', { persist: true });
await focusCollectionSettingsTab(page);
await selectCollectionPaneTab(page, 'auth');
await selectCollectionPaneTab(page, 'headers');
await selectCollectionPaneTab(page, 'overview');
await selectCollectionPaneTab(page, 'script');
await selectCollectionPaneTab(page, 'vars');
});
await closeElectronApp(app);
});
test('collection pane tab interactivity is preserved after app restart', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-collection-restart');
const colPath = await createTmpDir('col');
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
await test.step('Create collection and open collection settings on auth tab', async () => {
await createCollection(page, 'TestCol', colPath);
await openCollectionSettings(page, 'TestCol', { persist: true });
await selectCollectionPaneTab(page, 'auth');
});
await test.step('Close app and verify snapshot stores collection-settings tab', async () => {
await page.waitForTimeout(2000);
await closeElectronApp(app);
await waitForSnapshotFile(userDataPath);
const snapshot = readSnapshot(userDataPath);
const tab = findSnapshotCollectionTab(snapshot, colPath);
expect(tab).toBeTruthy();
expect(tab.type).toBe('collection-settings');
expect(tab.permanent).toBe(true);
});
await test.step('Restart app and verify collection pane interactivity is restored', async () => {
const app2 = await launchElectronApp({ userDataPath });
const page2 = await waitForReadyPage(app2);
await focusCollectionSettingsTab(page2, { timeout: 15000 });
await selectCollectionPaneTab(page2, 'auth');
await selectCollectionPaneTab(page2, 'headers');
await selectCollectionPaneTab(page2, 'overview');
await selectCollectionPaneTab(page2, 'script');
await selectCollectionPaneTab(page2, 'vars');
await closeElectronApp(app2);
});
});
test('collection script\'s tabs need to be interactive after app restart', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-collection-restart');
const colPath = await createTmpDir('col');
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
await test.step('Create collection and open collection settings on script tab', async () => {
await createCollection(page, 'TestCol', colPath);
await openCollectionSettings(page, 'TestCol', { persist: true });
await selectCollectionPaneTab(page, 'script');
});
await test.step('Close app and verify snapshot stores collection-settings tab', async () => {
await page.waitForTimeout(2000);
await closeElectronApp(app);
await waitForSnapshotFile(userDataPath);
const snapshot = readSnapshot(userDataPath);
const tab = findSnapshotCollectionTab(snapshot, colPath);
expect(tab).toBeTruthy();
expect(tab.type).toBe('collection-settings');
expect(tab.permanent).toBe(true);
});
await test.step('Restart app and verify collection script pane is interactive', async () => {
const app2 = await launchElectronApp({ userDataPath });
const page2 = await waitForReadyPage(app2);
await focusCollectionSettingsTab(page2, { timeout: 15000 });
await selectCollectionPaneTab(page2, 'script');
await selectCollectionScriptPaneTab(page2, 'pre-request');
await selectCollectionScriptPaneTab(page2, 'post-response');
await closeElectronApp(app2);
});
});
});

View File

@@ -1,34 +1,18 @@
import path from 'path';
import fs from 'fs';
import { test, expect, closeElectronApp } from '../../playwright';
import {
createCollection,
createFolder,
createWorkspace,
focusFolderSettingsTab,
openfolder,
readSnapshot,
findSnapshotFolderTab,
selectfolderPaneTab,
selectFolderScriptPaneTab,
switchWorkspace,
waitForReadyPage
waitForReadyPage,
waitForSnapshotFile
} from '../utils/page';
import { buildCommonLocators } from '../utils/page/locators';
const readSnapshot = (userDataPath: string) => {
const snapshotPath = path.join(userDataPath, 'ui-state-snapshot.json');
if (!fs.existsSync(snapshotPath)) return null;
return JSON.parse(fs.readFileSync(snapshotPath, 'utf-8'));
};
const findSnapshotFolderTab = (snapshot: any, folderName: string) => {
if (!snapshot || !Array.isArray(snapshot.collections)) return null;
for (const collection of snapshot.collections) {
if (!Array.isArray(collection?.tabs)) continue;
const tab = collection.tabs.find(
(t: any) => t?.type === 'folder-settings' && typeof t?.pathname === 'string' && t.pathname.includes(folderName)
);
if (tab) return tab;
}
return null;
};
test.describe('Snapshot: folder Pane Interactivity', () => {
test('folder pane tab interactivity is preserved after workspace switch', async ({ launchElectronApp, createTmpDir }) => {
@@ -55,10 +39,7 @@ test.describe('Snapshot: folder Pane Interactivity', () => {
await switchWorkspace(page, 'My Workspace');
await openfolder(page, 'TestCol', 'TestFolder', { persist: true });
const locators = buildCommonLocators(page);
await expect(locators.tabs.folderTab('TestFolder')).toBeVisible({ timeout: 10000 });
await locators.tabs.folderTab('TestFolder').click({ force: true });
await focusFolderSettingsTab(page, 'TestFolder');
await selectfolderPaneTab(page, 'auth');
await selectfolderPaneTab(page, 'headers');
@@ -88,8 +69,7 @@ test.describe('Snapshot: folder Pane Interactivity', () => {
await page.waitForTimeout(2000);
await closeElectronApp(app);
const snapshotPath = path.join(userDataPath, 'ui-state-snapshot.json');
await expect.poll(() => fs.existsSync(snapshotPath)).toBe(true);
await waitForSnapshotFile(userDataPath);
const snapshot = readSnapshot(userDataPath);
const tab = findSnapshotFolderTab(snapshot, 'TestFolder');
@@ -102,9 +82,7 @@ test.describe('Snapshot: folder Pane Interactivity', () => {
const app2 = await launchElectronApp({ userDataPath });
const page2 = await waitForReadyPage(app2);
const locators = buildCommonLocators(page2);
await expect(locators.tabs.folderTab('TestFolder')).toBeVisible({ timeout: 15000 });
await locators.tabs.folderTab('TestFolder').click({ force: true });
await focusFolderSettingsTab(page2, 'TestFolder', { timeout: 15000 });
await selectfolderPaneTab(page2, 'auth');
await selectfolderPaneTab(page2, 'headers');
@@ -115,4 +93,46 @@ test.describe('Snapshot: folder Pane Interactivity', () => {
await closeElectronApp(app2);
});
});
test('folder script\'s tabs need to be interactive after app restart', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-folder-restart');
const colPath = await createTmpDir('col');
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
await test.step('Create collection and folder, open folder settings on auth tab', async () => {
await createCollection(page, 'TestCol', colPath);
await createFolder(page, 'TestFolder', 'TestCol');
await openfolder(page, 'TestCol', 'TestFolder', { persist: true });
await selectfolderPaneTab(page, 'script');
});
await test.step('Close app and verify snapshot stores folder-settings tab', async () => {
await page.waitForTimeout(2000);
await closeElectronApp(app);
await waitForSnapshotFile(userDataPath);
const snapshot = readSnapshot(userDataPath);
const tab = findSnapshotFolderTab(snapshot, 'TestFolder');
expect(tab).toBeTruthy();
expect(tab.type).toBe('folder-settings');
expect(tab.permanent).toBe(true);
});
await test.step('Restart app and verify folder script pane is interactive', async () => {
const app2 = await launchElectronApp({ userDataPath });
const page2 = await waitForReadyPage(app2);
await focusFolderSettingsTab(page2, 'TestFolder', { timeout: 15000 });
await selectfolderPaneTab(page2, 'script');
await selectFolderScriptPaneTab(page2, 'pre-request');
await selectFolderScriptPaneTab(page2, 'post-response');
await closeElectronApp(app2);
});
});
});

View File

@@ -857,6 +857,101 @@ const selectfolderPaneTab = async (page: Page, tabName: string) => {
});
};
/**
* Select a sub-tab in the folder script pane (Pre Request or Post Response)
* @param page - The page object
* @param tabName - 'pre-request' or 'post-response'
* @returns void
*/
const selectFolderScriptPaneTab = async (page: Page, tabName: 'pre-request' | 'post-response') => {
await test.step(`Select folder script pane tab "${tabName}"`, async () => {
const locators = buildCommonLocators(page);
const tab = locators.paneTabs.folderScriptTab(tabName);
await tab.click();
await expect(tab).toContainClass('active');
});
};
/**
* Open a collection's settings tab by clicking on it in the sidebar
* @param page - The page object
* @param collectionName - The name of the collection
* @param options - Optional settings (persist: double-click to make tab permanent)
* @returns void
*/
const openCollectionSettings = async (page: Page, collectionName: string, { persist = false } = {}) => {
await test.step(`Open collection settings for "${collectionName}"`, async () => {
const locators = buildCommonLocators(page);
const collection = locators.sidebar.collection(collectionName);
if (!persist) {
await collection.click();
} else {
await collection.dblclick();
}
});
};
/**
* Select a tab in the collection settings pane
* @param page - The page object
* @param tabName - The tab name key (e.g. 'auth', 'headers', 'overview', 'script', 'vars')
* @returns void
*/
const selectCollectionPaneTab = async (page: Page, tabName: string) => {
await test.step(`Select collection pane tab "${tabName}"`, async () => {
const locators = buildCommonLocators(page);
const tab = locators.paneTabs.collectionSettingsTab(tabName.toLowerCase());
await tab.click();
await expect(tab).toContainClass('active');
});
};
/**
* Select a sub-tab in the collection script pane (Pre Request or Post Response)
* @param page - The page object
* @param tabName - 'pre-request' or 'post-response'
* @returns void
*/
const selectCollectionScriptPaneTab = async (page: Page, tabName: 'pre-request' | 'post-response') => {
await test.step(`Select collection script pane tab "${tabName}"`, async () => {
const locators = buildCommonLocators(page);
const tab = locators.paneTabs.tabTrigger(tabName);
await tab.click();
await expect(tab).toContainClass('active');
});
};
/**
* Focus the folder settings tab in the tab bar after restore
* @param page - The page object
* @param folderName - The name of the folder
* @param options - Optional timeout in milliseconds
* @returns void
*/
const focusFolderSettingsTab = async (page: Page, folderName: string, { timeout = 10000 } = {}) => {
await test.step(`Focus folder settings tab "${folderName}"`, async () => {
const locators = buildCommonLocators(page);
const tab = locators.tabs.folderTab(folderName);
await expect(tab).toBeVisible({ timeout });
await tab.click({ force: true });
});
};
/**
* Focus the collection settings tab in the tab bar after restore
* @param page - The page object
* @param options - Optional timeout in milliseconds
* @returns void
*/
const focusCollectionSettingsTab = async (page: Page, { timeout = 10000 } = {}) => {
await test.step('Focus collection settings tab', async () => {
const locators = buildCommonLocators(page);
const tab = locators.tabs.collectionSettingsTab();
await expect(tab).toBeVisible({ timeout });
await tab.click({ force: true });
});
};
/**
* Open a request within a folder
* @param page - The page object
@@ -1667,6 +1762,12 @@ export {
openfolder,
openFolderRequest,
selectfolderPaneTab,
selectFolderScriptPaneTab,
openCollectionSettings,
selectCollectionPaneTab,
selectCollectionScriptPaneTab,
focusFolderSettingsTab,
focusCollectionSettingsTab,
getResponseBody,
expectResponseContains,
selectRequestPaneTab,

View File

@@ -1,3 +1,4 @@
export * from './actions';
export * from './runner';
export * from './locators';
export * from '../snapshot';

View File

@@ -38,6 +38,8 @@ export const buildCommonLocators = (page: Page) => ({
tabs: {
requestTab: (requestName: string) => page.locator('.request-tab .tab-label').filter({ hasText: requestName }),
folderTab: (folderName: string) => page.locator('.request-tab .tab-label').filter({ hasText: folderName }),
collectionSettingsTab: () =>
page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) }),
activeRequestTab: () => page.locator('.request-tab.active'),
closeTab: (requestName: string) => page.locator('.request-tab').filter({ hasText: requestName }).getByTestId('request-tab-close-icon'),
draftIndicator: () => page.locator('.request-tab.active .has-changes-icon')
@@ -46,6 +48,7 @@ export const buildCommonLocators = (page: Page) => ({
responsiveTab: (key: string) => page.getByTestId(`responsive-tab-${key}`),
collectionSettingsTab: (key: string) => page.getByTestId(`collection-settings-tab-${key}`),
folderSettingsTab: (key: string) => page.getByTestId(`folder-settings-tab-${key}`),
folderScriptTab: (key: 'pre-request' | 'post-response') => page.getByTestId(`tab-trigger-${key}`),
tabTrigger: (key: string) => page.getByTestId(`tab-trigger-${key}`)
},
folder: {

43
tests/utils/snapshot.ts Normal file
View File

@@ -0,0 +1,43 @@
import path from 'path';
import fs from 'fs';
import { expect } from '../../playwright';
export const getSnapshotPath = (userDataPath: string) =>
path.join(userDataPath, 'ui-state-snapshot.json');
/**
* Read the snapshot JSON from the user data directory.
* electron-store saves it as `ui-state-snapshot.json`.
*/
export const readSnapshot = (userDataPath: string) => {
const snapshotPath = getSnapshotPath(userDataPath);
if (!fs.existsSync(snapshotPath)) return null;
return JSON.parse(fs.readFileSync(snapshotPath, 'utf-8'));
};
export const waitForSnapshotFile = async (userDataPath: string) => {
await expect.poll(() => fs.existsSync(getSnapshotPath(userDataPath))).toBe(true);
};
export const findSnapshotFolderTab = (snapshot: any, folderName: string) => {
if (!snapshot || !Array.isArray(snapshot.collections)) return null;
for (const collection of snapshot.collections) {
if (!Array.isArray(collection?.tabs)) continue;
const tab = collection.tabs.find(
(t: any) => t?.type === 'folder-settings' && typeof t?.pathname === 'string' && t.pathname.includes(folderName)
);
if (tab) return tab;
}
return null;
};
export const findSnapshotCollectionTab = (snapshot: any, collectionPath: string) => {
if (!snapshot || !Array.isArray(snapshot.collections)) return null;
for (const collection of snapshot.collections) {
if (collectionPath && collection?.pathname && !collection.pathname.includes(collectionPath)) continue;
if (!Array.isArray(collection?.tabs)) continue;
const tab = collection.tabs.find((t: any) => t?.type === 'collection-settings');
if (tab) return tab;
}
return null;
};