Files
bruno/tests/snapshots/basic.spec.ts
2026-05-14 17:38:55 +05:30

863 lines
36 KiB
TypeScript

import path from 'path';
import fs from 'fs';
import { test, expect, closeElectronApp, type Page } from '../../playwright';
import {
createCollection,
createRequest,
openRequest,
openCollection,
switchWorkspace,
selectRequestPaneTab,
waitForReadyPage
} from '../utils/page';
import { buildCommonLocators } from '../utils/page/locators';
/**
* Helper: read the snapshot JSON from the user data directory.
* electron-store saves it as `ui-state-snapshot.json`.
*/
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 clickCollectionsSortAction = async (page: Page, times: number = 1) => {
for (let i = 0; i < times; i += 1) {
await page.getByTestId('collections-header-actions-menu').click();
await page.getByTestId('collections-header-actions-menu-sort').click();
}
};
const expectSidebarCollectionOrder = async (page: Page, expectedNames: string[]) => {
const rows = page.getByTestId('sidebar-collection-row');
await expect.poll(async () => {
const actualNames: string[] = [];
const count = await rows.count();
for (let i = 0; i < count; i += 1) {
const name = await rows.nth(i).locator('#sidebar-collection-name').textContent();
if (name) {
actualNames.push(name.trim());
}
}
return actualNames;
}).toEqual(expectedNames);
};
const expectSnapshotWorkspaceSortings = async (userDataPath: string, expectedSortings: string[]) => {
await expect.poll(() => {
const snapshot = readSnapshot(userDataPath);
const sortings = Array.isArray(snapshot?.workspaces)
? snapshot.workspaces.map((workspace) => workspace?.sorting).filter(Boolean)
: [];
return sortings.sort();
}).toEqual([...expectedSortings].sort());
};
// ─── Tab Persistence ────────────────────────────────────────────────────────
test.describe('Snapshot: Tab Persistence', () => {
test('open tabs are restored after app restart in the same order', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-tabs-order');
const colPath = await createTmpDir('col');
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
await test.step('Create collection with two requests and open both', async () => {
await createCollection(page, 'TestCol', colPath);
await createRequest(page, 'ReqAlpha', 'TestCol', { url: 'https://echo.usebruno.com', method: 'GET' });
await createRequest(page, 'ReqBeta', 'TestCol', { url: 'https://echo.usebruno.com', method: 'GET' });
await openRequest(page, 'TestCol', 'ReqAlpha', { persist: true });
await openRequest(page, 'TestCol', 'ReqBeta', { persist: true });
});
await test.step('Close and restart app', async () => {
// Wait for debounced snapshot save to flush
await page.waitForTimeout(2000);
await closeElectronApp(app);
});
await test.step('Verify tabs restored in order', async () => {
const app2 = await launchElectronApp({ userDataPath });
const page2 = await waitForReadyPage(app2);
const locators = buildCommonLocators(page2);
// Wait for snapshot hydration to restore tabs
await expect(locators.tabs.requestTab('ReqAlpha')).toBeVisible({ timeout: 15000 });
await expect(locators.tabs.requestTab('ReqBeta')).toBeVisible({ timeout: 10000 });
// Verify order: ReqAlpha before ReqBeta
const tabs = page2.locator('.request-tab .tab-label');
const tabTexts = await tabs.allTextContents();
const alphaIndex = tabTexts.findIndex((t) => t.includes('ReqAlpha'));
const betaIndex = tabTexts.findIndex((t) => t.includes('ReqBeta'));
expect(alphaIndex).toBeGreaterThanOrEqual(0);
expect(betaIndex).toBeGreaterThan(alphaIndex);
await closeElectronApp(app2);
});
});
test('active tab is remembered after restart', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-active-tab');
const colPath = await createTmpDir('col');
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
await test.step('Create two requests and focus ReqAlpha', async () => {
await createCollection(page, 'TestCol', colPath);
await createRequest(page, 'ReqAlpha', 'TestCol', { url: 'https://echo.usebruno.com', method: 'GET' });
await createRequest(page, 'ReqBeta', 'TestCol', { url: 'https://echo.usebruno.com', method: 'GET' });
await openRequest(page, 'TestCol', 'ReqBeta', { persist: true });
await openRequest(page, 'TestCol', 'ReqAlpha', { persist: true });
const locators = buildCommonLocators(page);
await expect(locators.tabs.activeRequestTab()).toContainText('ReqAlpha');
});
await test.step('Close and restart app', async () => {
await page.waitForTimeout(2000);
await closeElectronApp(app);
});
await test.step('Verify ReqAlpha is the active tab', async () => {
const app2 = await launchElectronApp({ userDataPath });
const page2 = await waitForReadyPage(app2);
const locators = buildCommonLocators(page2);
await expect(locators.tabs.activeRequestTab()).toContainText('ReqAlpha', { timeout: 10000 });
await closeElectronApp(app2);
});
});
test('closed tab stays closed after restart', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-closed-tab');
const colPath = await createTmpDir('col');
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
await test.step('Create two requests, open both, close one', async () => {
await createCollection(page, 'TestCol', colPath);
await createRequest(page, 'ReqKeep', 'TestCol', { url: 'https://echo.usebruno.com', method: 'GET' });
await createRequest(page, 'ReqClose', 'TestCol', { url: 'https://echo.usebruno.com', method: 'GET' });
await openRequest(page, 'TestCol', 'ReqKeep', { persist: true });
await openRequest(page, 'TestCol', 'ReqClose', { persist: true });
const locators = buildCommonLocators(page);
await locators.tabs.closeTab('ReqClose').click({ force: true });
await expect(locators.tabs.requestTab('ReqClose')).not.toBeVisible();
});
await test.step('Close and restart app', async () => {
await page.waitForTimeout(2000);
await closeElectronApp(app);
});
await test.step('Verify ReqClose is not restored', async () => {
const app2 = await launchElectronApp({ userDataPath });
const page2 = await waitForReadyPage(app2);
const locators = buildCommonLocators(page2);
await expect(locators.tabs.requestTab('ReqKeep')).toBeVisible({ timeout: 10000 });
await expect(locators.tabs.requestTab('ReqClose')).not.toBeVisible();
await closeElectronApp(app2);
});
});
test('request pane tab selection persists after restart', async ({ launchElectronApp, createTmpDir }) => {
test.setTimeout(60000);
const userDataPath = await createTmpDir('snap-pane-tab');
const colPath = await createTmpDir('col');
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
await test.step('Create request and switch to Headers tab', 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 and restart app', async () => {
await page.waitForTimeout(2000);
await closeElectronApp(app);
});
await test.step('Verify Headers tab is still selected', async () => {
const app2 = await launchElectronApp({ userDataPath });
const page2 = await waitForReadyPage(app2);
const locators = buildCommonLocators(page2);
// The active collection's tabs should be auto-restored by switchWorkspace
await expect(locators.tabs.requestTab('Req1')).toBeVisible({ timeout: 15000 });
// Click the request tab to make it active and show its pane
await locators.tabs.requestTab('Req1').click({ force: true });
// The active request pane tab should be Headers
const requestPane = page2.locator('.request-pane > .px-4');
await expect(requestPane).toBeVisible({ timeout: 5000 });
const headersTab = requestPane.locator('.tabs').getByRole('tab', { name: 'Headers' });
await expect(headersTab).toHaveClass(/active/, { timeout: 5000 });
await closeElectronApp(app2);
});
});
});
// ─── Workspace State ────────────────────────────────────────────────────────
test.describe('Snapshot: Workspace State', () => {
test('active workspace is remembered after restart', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-active-ws');
const workspaceBPath = await createTmpDir('workspace-b');
const WORKSPACE_YML = [
'opencollection: 1.0.0',
'info:',
' name: WorkspaceB',
' type: workspace',
'collections:',
'specs: []',
'docs: \'\'',
''
].join('\n');
fs.writeFileSync(path.join(workspaceBPath, 'workspace.yml'), WORKSPACE_YML);
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
await test.step('Open WorkspaceB and switch to it', async () => {
await app.evaluate(
({ dialog }, targetPath: string) => {
(dialog as any).showOpenDialog = () =>
Promise.resolve({ canceled: false, filePaths: [targetPath] });
},
workspaceBPath
);
await page.getByTestId('workspace-menu').click();
await page.locator('.dropdown-item').filter({ hasText: 'Open workspace' }).click();
await expect(page.getByTestId('workspace-name')).toHaveText('WorkspaceB', { timeout: 10000 });
});
await test.step('Close and restart app', async () => {
// Wait for debounced snapshot save to flush
await page.waitForTimeout(2000);
await closeElectronApp(app);
});
await test.step('Verify WorkspaceB is still active', async () => {
const app2 = await launchElectronApp({ userDataPath });
const page2 = await waitForReadyPage(app2);
await expect(page2.getByTestId('workspace-name')).toHaveText('WorkspaceB', { timeout: 10000 });
await closeElectronApp(app2);
});
});
test('workspace collection sorting persists across workspace switches and restart', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-ws-collection-sorting');
const defaultColZPath = await createTmpDir('default-col-zulu');
const defaultColAPath = await createTmpDir('default-col-alpha');
const secondWorkspacePath = await createTmpDir('workspace-sorting-b');
const secondColMPath = await createTmpDir('ws2-col-middle');
const secondColAPath = await createTmpDir('ws2-col-alpha');
const WORKSPACE_YML = [
'opencollection: 1.0.0',
'info:',
' name: WorkspaceB',
' type: workspace',
'collections:',
'specs: []',
'docs: \'\'',
''
].join('\n');
fs.writeFileSync(path.join(secondWorkspacePath, 'workspace.yml'), WORKSPACE_YML);
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
await test.step('Create collections in default workspace and set A-Z sort', async () => {
await createCollection(page, 'Zulu', defaultColZPath);
await createCollection(page, 'Alpha', defaultColAPath);
await expectSidebarCollectionOrder(page, ['Zulu', 'Alpha']);
await clickCollectionsSortAction(page, 1);
await expectSidebarCollectionOrder(page, ['Alpha', 'Zulu']);
await expectSnapshotWorkspaceSortings(userDataPath, ['alphabetical']);
});
await test.step('Open second workspace and set Z-A sort', async () => {
await app.evaluate(
({ dialog }, targetPath: string) => {
(dialog as any).showOpenDialog = () =>
Promise.resolve({ canceled: false, filePaths: [targetPath] });
},
secondWorkspacePath
);
await page.getByTestId('workspace-menu').click();
await page.locator('.dropdown-item').filter({ hasText: 'Open workspace' }).click();
await expect(page.getByTestId('workspace-name')).toHaveText('WorkspaceB', { timeout: 10000 });
await createCollection(page, 'Middle', secondColMPath);
await createCollection(page, 'AlphaWS2', secondColAPath);
await expectSidebarCollectionOrder(page, ['Middle', 'AlphaWS2']);
await clickCollectionsSortAction(page, 1);
await expectSidebarCollectionOrder(page, ['AlphaWS2', 'Middle']);
await clickCollectionsSortAction(page, 1);
await expectSidebarCollectionOrder(page, ['Middle', 'AlphaWS2']);
await expectSnapshotWorkspaceSortings(userDataPath, ['alphabetical', 'reverseAlphabetical']);
});
await test.step('Switch back and forth, verify per-workspace sort restore', async () => {
await switchWorkspace(page, 'My Workspace');
await expectSidebarCollectionOrder(page, ['Alpha', 'Zulu']);
await switchWorkspace(page, 'WorkspaceB');
await expectSidebarCollectionOrder(page, ['Middle', 'AlphaWS2']);
});
await test.step('Restart app and verify per-workspace sort still persists', async () => {
await page.waitForTimeout(2000);
await closeElectronApp(app);
const app2 = await launchElectronApp({ userDataPath });
const page2 = await waitForReadyPage(app2);
await expect(page2.getByTestId('workspace-name')).toHaveText('WorkspaceB', { timeout: 10000 });
await expectSidebarCollectionOrder(page2, ['Middle', 'AlphaWS2']);
await switchWorkspace(page2, 'My Workspace');
await expectSidebarCollectionOrder(page2, ['Alpha', 'Zulu']);
await closeElectronApp(app2);
});
});
test('each workspace remembers its own active collection', async ({ launchElectronApp, createTmpDir }) => {
test.setTimeout(60000);
const userDataPath = await createTmpDir('snap-ws-active-col');
const colAPath = await createTmpDir('col-a');
const colBPath = await createTmpDir('col-b');
const workspaceBPath = await createTmpDir('workspace-b');
const WORKSPACE_YML = [
'opencollection: 1.0.0',
'info:',
' name: WorkspaceB',
' type: workspace',
'collections:',
'specs: []',
'docs: \'\'',
''
].join('\n');
fs.writeFileSync(path.join(workspaceBPath, 'workspace.yml'), WORKSPACE_YML);
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
await test.step('Create ColA with request in default workspace', async () => {
await createCollection(page, 'ColA', colAPath);
await createRequest(page, 'ReqA', 'ColA', { url: 'https://echo.usebruno.com', method: 'GET' });
await openRequest(page, 'ColA', 'ReqA', { persist: true });
});
await test.step('Open WorkspaceB', async () => {
await app.evaluate(
({ dialog }, targetPath: string) => {
(dialog as any).showOpenDialog = () =>
Promise.resolve({ canceled: false, filePaths: [targetPath] });
},
workspaceBPath
);
await page.getByTestId('workspace-menu').click();
await page.locator('.dropdown-item').filter({ hasText: 'Open workspace' }).click();
await expect(page.getByTestId('workspace-name')).toHaveText('WorkspaceB', { timeout: 10000 });
});
await test.step('Create ColB with request in WorkspaceB', async () => {
await createCollection(page, 'ColB', colBPath);
await createRequest(page, 'ReqB', 'ColB', { url: 'https://echo.usebruno.com', method: 'GET' });
await openRequest(page, 'ColB', 'ReqB', { persist: true });
});
await test.step('Switch back to default workspace and verify ColA tabs restored', async () => {
// Wait for snapshot to save before switching
await page.waitForTimeout(2000);
await switchWorkspace(page, 'My Workspace');
// Wait for collections to load, then click collection to mount and restore tabs
const locators = buildCommonLocators(page);
await expect(locators.sidebar.collection('ColA')).toBeVisible({ timeout: 10000 });
await openCollection(page, 'ColA');
await expect(locators.tabs.requestTab('ReqA')).toBeVisible({ timeout: 15000 });
});
await test.step('Switch to WorkspaceB and verify ColB tabs restored', async () => {
await page.waitForTimeout(2000);
await switchWorkspace(page, 'WorkspaceB');
const locators = buildCommonLocators(page);
await expect(locators.sidebar.collection('ColB')).toBeVisible({ timeout: 10000 });
await openCollection(page, 'ColB');
await expect(locators.tabs.requestTab('ReqB')).toBeVisible({ timeout: 15000 });
});
await closeElectronApp(app);
});
});
// ─── Collection State ───────────────────────────────────────────────────────
test.describe('Snapshot: Collection State', () => {
test('collection expanded state persists after restart', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-col-expanded');
const colPath = await createTmpDir('col');
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
await test.step('Create collection and open a request (expands it)', 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 });
// Verify collection is expanded (request is visible in sidebar)
const locators = buildCommonLocators(page);
await expect(locators.sidebar.request('Req1')).toBeVisible();
});
await test.step('Close and restart app', async () => {
await page.waitForTimeout(2000);
await closeElectronApp(app);
});
await test.step('Verify collection is still expanded', async () => {
const app2 = await launchElectronApp({ userDataPath });
const page2 = await waitForReadyPage(app2);
const locators = buildCommonLocators(page2);
// The active collection should be expanded, showing items in sidebar
await expect(locators.sidebar.request('Req1')).toBeVisible({ timeout: 10000 });
await closeElectronApp(app2);
});
});
});
// ─── Multi-Workspace Tab Isolation ──────────────────────────────────────────
test.describe('Snapshot: Multi-Workspace Tab Isolation', () => {
test('tabs from workspace A do not leak into workspace B after restart', async ({ launchElectronApp, createTmpDir }) => {
test.setTimeout(60000);
const userDataPath = await createTmpDir('snap-tab-isolation');
const colAPath = await createTmpDir('col-a');
const colBPath = await createTmpDir('col-b');
const workspaceBPath = await createTmpDir('workspace-b');
const WORKSPACE_YML = [
'opencollection: 1.0.0',
'info:',
' name: WorkspaceB',
' type: workspace',
'collections:',
'specs: []',
'docs: \'\'',
''
].join('\n');
fs.writeFileSync(path.join(workspaceBPath, 'workspace.yml'), WORKSPACE_YML);
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
await test.step('Create ReqA in default workspace', async () => {
await createCollection(page, 'ColA', colAPath);
await createRequest(page, 'ReqA', 'ColA', { url: 'https://echo.usebruno.com', method: 'GET' });
await openRequest(page, 'ColA', 'ReqA', { persist: true });
});
await test.step('Switch to WorkspaceB and create ReqB', async () => {
await app.evaluate(
({ dialog }, targetPath: string) => {
(dialog as any).showOpenDialog = () =>
Promise.resolve({ canceled: false, filePaths: [targetPath] });
},
workspaceBPath
);
await page.getByTestId('workspace-menu').click();
await page.locator('.dropdown-item').filter({ hasText: 'Open workspace' }).click();
await expect(page.getByTestId('workspace-name')).toHaveText('WorkspaceB', { timeout: 10000 });
await createCollection(page, 'ColB', colBPath);
await createRequest(page, 'ReqB', 'ColB', { url: 'https://echo.usebruno.com', method: 'GET' });
await openRequest(page, 'ColB', 'ReqB', { persist: true });
});
await test.step('Close and restart app', async () => {
// Wait for debounced snapshot save to flush
await page.waitForTimeout(2000);
await closeElectronApp(app);
});
await test.step('Verify WorkspaceB tabs do not show ReqA', async () => {
const app2 = await launchElectronApp({ userDataPath });
const page2 = await waitForReadyPage(app2);
// App should restore to WorkspaceB (last active)
await expect(page2.getByTestId('workspace-name')).toHaveText('WorkspaceB', { timeout: 10000 });
const locators = buildCommonLocators(page2);
// Wait for collection to load, then click to bring its tabs into view
await expect(locators.sidebar.collection('ColB')).toBeVisible({ timeout: 10000 });
await openCollection(page2, 'ColB');
await expect(locators.tabs.requestTab('ReqB')).toBeVisible({ timeout: 10000 });
await expect(locators.tabs.requestTab('ReqA')).not.toBeVisible();
await test.step('Switch to default workspace and verify ReqA, not ReqB', async () => {
await switchWorkspace(page2, 'My Workspace');
// Wait for collections to load in the workspace
await expect(locators.sidebar.collection('ColA')).toBeVisible({ timeout: 10000 });
await openCollection(page2, 'ColA');
await expect(locators.tabs.requestTab('ReqA')).toBeVisible({ timeout: 15000 });
await expect(locators.tabs.requestTab('ReqB')).not.toBeVisible();
});
await closeElectronApp(app2);
});
});
test('same collection in two workspaces keeps tabs isolated after restart', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-tab-isolation-shared-col');
const sharedColPath = await createTmpDir('shared-col');
const workspaceBPath = await createTmpDir('workspace-b-shared-col');
const WORKSPACE_YML = [
'opencollection: 1.0.0',
'info:',
' name: WorkspaceB',
' type: workspace',
'collections:',
'specs: []',
'docs: \'\'',
''
].join('\n');
fs.writeFileSync(path.join(workspaceBPath, 'workspace.yml'), WORKSPACE_YML);
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
await test.step('Create shared collection in default workspace and open ReqA', async () => {
await createCollection(page, 'SharedCol', sharedColPath);
await createRequest(page, 'ReqA', 'SharedCol', { url: 'https://echo.usebruno.com', method: 'GET' });
await createRequest(page, 'ReqB', 'SharedCol', { url: 'https://echo.usebruno.com', method: 'GET' });
await openRequest(page, 'SharedCol', 'ReqA', { persist: true });
});
const sharedCollectionPath = path.join(sharedColPath, 'SharedCol');
await test.step('Open WorkspaceB and add the same collection path', async () => {
await app.evaluate(
({ dialog }, targetPath: string) => {
(dialog as any).showOpenDialog = () =>
Promise.resolve({ canceled: false, filePaths: [targetPath] });
},
workspaceBPath
);
await page.getByTestId('workspace-menu').click();
await page.locator('.dropdown-item').filter({ hasText: 'Open workspace' }).click();
await expect(page.getByTestId('workspace-name')).toHaveText('WorkspaceB', { timeout: 10000 });
await app.evaluate(
({ dialog }, targetPath: string) => {
(dialog as any).showOpenDialog = () =>
Promise.resolve({ canceled: false, filePaths: [targetPath] });
},
sharedCollectionPath
);
await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Open collection' }).click();
const locators = buildCommonLocators(page);
await expect(locators.sidebar.collection('SharedCol')).toBeVisible({ timeout: 10000 });
});
await test.step('In WorkspaceB open ReqB', async () => {
await openCollection(page, 'SharedCol');
await openRequest(page, 'SharedCol', 'ReqB', { persist: true });
});
await test.step('Close and restart app', async () => {
// snapshot saving is done in the background at 1000ms debounce
await page.waitForTimeout(2000);
await closeElectronApp(app);
});
await test.step('Verify tab isolation for same collection across workspaces', async () => {
const app2 = await launchElectronApp({ userDataPath });
const page2 = await waitForReadyPage(app2);
await expect(page2.getByTestId('workspace-name')).toHaveText('WorkspaceB', { timeout: 10000 });
const locators = buildCommonLocators(page2);
await expect(locators.sidebar.collection('SharedCol')).toBeVisible({ timeout: 10000 });
await openCollection(page2, 'SharedCol');
await expect(locators.tabs.requestTab('ReqB')).toBeVisible({ timeout: 10000 });
await expect(locators.tabs.requestTab('ReqA')).not.toBeVisible();
await switchWorkspace(page2, 'My Workspace');
await expect(locators.sidebar.collection('SharedCol')).toBeVisible({ timeout: 10000 });
await openCollection(page2, 'SharedCol');
await expect(locators.tabs.requestTab('ReqA')).toBeVisible({ timeout: 15000 });
await expect(locators.tabs.requestTab('ReqB')).not.toBeVisible();
await closeElectronApp(app2);
});
});
});
// ─── DevTools State ─────────────────────────────────────────────────────────
test.describe('Snapshot: DevTools State', () => {
test('devtools open state and active tab persist after restart', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-devtools');
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
await test.step('Open devtools and switch to Performance tab', async () => {
const devToolsButton = page.locator('button[data-trigger="dev-tools"]');
await devToolsButton.click();
await expect(page.locator('.console-header')).toBeVisible();
const performanceTab = page.locator('.console-tab').filter({ hasText: 'Performance' });
await performanceTab.click();
await expect(performanceTab).toHaveClass(/active/);
});
await test.step('Close and restart app', async () => {
// Wait for debounced snapshot save to flush
await page.waitForTimeout(2000);
await closeElectronApp(app);
});
await test.step('Verify devtools is open with Performance tab active', async () => {
const app2 = await launchElectronApp({ userDataPath });
const page2 = await waitForReadyPage(app2);
// DevTools should be open
await expect(page2.locator('.console-header')).toBeVisible({ timeout: 10000 });
// Performance tab should be active
const performanceTab = page2.locator('.console-tab').filter({ hasText: 'Performance' });
await expect(performanceTab).toHaveClass(/active/, { timeout: 5000 });
await closeElectronApp(app2);
});
});
});
// ─── Edge Cases ─────────────────────────────────────────────────────────────
test.describe('Snapshot: Edge Cases', () => {
test('fresh app launch with no snapshot file loads cleanly', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-fresh');
// Ensure no snapshot file exists
const snapshotPath = path.join(userDataPath, 'ui-state-snapshot.json');
if (fs.existsSync(snapshotPath)) {
fs.unlinkSync(snapshotPath);
}
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
// App should load the default workspace without errors
await expect(page.getByTestId('workspace-name')).toBeVisible({ timeout: 10000 });
await closeElectronApp(app);
});
test('corrupt snapshot file results in graceful recovery', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-corrupt');
// Write invalid JSON to snapshot file
const snapshotPath = path.join(userDataPath, 'ui-state-snapshot.json');
fs.writeFileSync(snapshotPath, '{ invalid json !!!', 'utf-8');
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
// App should recover and show default workspace
await expect(page.getByTestId('workspace-name')).toBeVisible({ timeout: 10000 });
await closeElectronApp(app);
});
});
// ─── Snapshot File Structure ────────────────────────────────────────────────
test.describe('Snapshot: File Structure', () => {
test('snapshot file uses array-based structure with correct keys', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-structure');
const colPath = await createTmpDir('col');
const app = await launchElectronApp({ userDataPath });
const page = await waitForReadyPage(app);
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 });
});
// Give the debounced save time to flush
const snapshotPath = path.join(userDataPath, 'ui-state-snapshot.json');
await expect.poll(() => fs.existsSync(snapshotPath)).toBe(true);
await test.step('Close app and inspect snapshot file', async () => {
await closeElectronApp(app);
const snapshot = readSnapshot(userDataPath);
expect(snapshot).not.toBeNull();
// Top-level keys should exist
expect(snapshot).toHaveProperty('activeWorkspacePath');
expect(snapshot).toHaveProperty('extras');
expect(snapshot).toHaveProperty('extras.devTools');
expect(snapshot).toHaveProperty('workspaces');
expect(snapshot).toHaveProperty('collections');
// workspaces and collections should be arrays
expect(Array.isArray(snapshot.workspaces)).toBe(true);
expect(Array.isArray(snapshot.collections)).toBe(true);
// There should be at least one workspace
expect(snapshot.workspaces.length).toBeGreaterThanOrEqual(1);
// Workspace should have expected shape
const firstWorkspace = snapshot.workspaces[0];
expect(firstWorkspace).toHaveProperty('pathname');
expect(firstWorkspace).toHaveProperty('lastActiveCollectionPathname');
expect(firstWorkspace).toHaveProperty('sorting');
expect(firstWorkspace).toHaveProperty('collections');
expect(Array.isArray(firstWorkspace.collections)).toBe(true);
// There should be at least one collection
expect(snapshot.collections.length).toBeGreaterThanOrEqual(1);
// Collection should have expected shape
const firstCollection = snapshot.collections[0];
expect(firstCollection).toHaveProperty('pathname');
expect(firstCollection).toHaveProperty('workspacePathname');
expect(firstCollection).toHaveProperty('environment');
expect(firstCollection).toHaveProperty('environmentPath');
expect(firstCollection).toHaveProperty('selectedEnvironment');
expect(firstCollection).toHaveProperty('isOpen');
expect(firstCollection).toHaveProperty('isMounted');
expect(firstCollection).toHaveProperty('tabs');
expect(Array.isArray(firstCollection.tabs)).toBe(true);
expect(firstCollection).toHaveProperty('activeTab');
});
});
});
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 waitForReadyPage(app);
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 waitForReadyPage(app2);
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 waitForReadyPage(app);
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 waitForReadyPage(app2);
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);
});
});
});