mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-22 04:05:42 +00:00
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:
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
135
tests/snapshots/collection.spec.ts
Normal file
135
tests/snapshots/collection.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './actions';
|
||||
export * from './runner';
|
||||
export * from './locators';
|
||||
export * from '../snapshot';
|
||||
|
||||
@@ -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
43
tests/utils/snapshot.ts
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user