fix: example-request tab collision (#7989)

* fix: prevent response-example tabs from hijacking request sidebar selection

* fix: add selectors for examples with index

* test: better locator

* fix: duplicate name collision

* fix: refactor sidebar example handling functions for better clarity and reusability

* chore: cr comments
This commit is contained in:
Sid
2026-05-13 21:47:39 +05:30
committed by lohit
parent c12fe6cc12
commit 54567bbd69
12 changed files with 682 additions and 23 deletions

View File

@@ -1,7 +1,9 @@
import { test, expect, closeElectronApp, type Page } from '../../playwright';
import {
createCollection,
createExampleFromSidebar,
createRequest,
openExampleFromSidebar,
openRequest
} from '../utils/page';
import { buildCommonLocators } from '../utils/page/locators';
@@ -77,4 +79,117 @@ test.describe('Snapshot: Sidebar-Tab Restoration', () => {
await closeElectronApp(app2);
});
});
test('when request and example are open, last active request restores as active after restart', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-sidebar-request-active-over-example');
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 request, create example, then make request last active', async () => {
await createCollection(page, 'TestCol', colPath);
await createRequest(page, 'ReqAlpha', 'TestCol', { url: 'https://echo.usebruno.com', method: 'GET' });
await openRequest(page, 'TestCol', 'ReqAlpha', { persist: true });
await createExampleFromSidebar(page, 'ReqAlpha', 'Example One');
await expect(page.getByTestId('response-example-title')).toHaveText('ReqAlpha / Example One');
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 request restores as active and request click does not focus example', 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.activeRequestTab()).toContainText('ReqAlpha', { timeout: 15000 });
await openRequest(page2, 'TestCol', 'ReqAlpha', { persist: true });
await expect(locators.tabs.requestTab('ReqAlpha')).toHaveCount(1);
await expect(page2.getByTestId('response-example-title')).not.toBeVisible();
await closeElectronApp(app2);
});
});
test('when last active tab is an example, it restores as active after restart', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-sidebar-example-active-restore');
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 request and example, then make example active', async () => {
await createCollection(page, 'TestCol', colPath);
await createRequest(page, 'ReqAlpha', 'TestCol', { url: 'https://echo.usebruno.com', method: 'GET' });
await openRequest(page, 'TestCol', 'ReqAlpha', { persist: true });
await createExampleFromSidebar(page, 'ReqAlpha', 'Example One');
await openExampleFromSidebar(page, 'ReqAlpha', 'Example One');
await expect(page.getByTestId('response-example-title')).toHaveText('ReqAlpha / Example One');
});
await test.step('Close and restart app', async () => {
await page.waitForTimeout(2000);
await closeElectronApp(app);
});
await test.step('Verify example restores as active', async () => {
const app2 = await launchElectronApp({ userDataPath });
const page2 = await app2.firstWindow();
await page2.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
await expect(page2.getByTestId('response-example-title')).toHaveText('ReqAlpha / Example One', { timeout: 15000 });
await closeElectronApp(app2);
});
});
test('when duplicate example names exist, snapshot restores the same active example by index', async ({ launchElectronApp, createTmpDir }) => {
const userDataPath = await createTmpDir('snap-sidebar-duplicate-example-restore');
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 request and two examples with duplicate names, then activate second', async () => {
await createCollection(page, 'TestCol', colPath);
await createRequest(page, 'ReqAlpha', 'TestCol', { url: 'https://echo.usebruno.com', method: 'GET' });
await openRequest(page, 'TestCol', 'ReqAlpha', { persist: true });
await createExampleFromSidebar(page, 'ReqAlpha', 'DupExample', 'first-desc');
await createExampleFromSidebar(page, 'ReqAlpha', 'DupExample', 'second-desc');
await openExampleFromSidebar(page, 'ReqAlpha', 'DupExample', 1);
await expect(page.getByTestId('response-example-description')).toHaveText('second-desc');
});
await test.step('Close and restart app', async () => {
await page.waitForTimeout(2000);
await closeElectronApp(app);
});
await test.step('Verify second duplicate example restores as active', async () => {
const app2 = await launchElectronApp({ userDataPath });
const page2 = await app2.firstWindow();
await page2.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
await expect(page2.getByTestId('response-example-title')).toHaveText('ReqAlpha / DupExample', { timeout: 15000 });
await expect(page2.getByTestId('response-example-description')).toHaveText('second-desc');
await closeElectronApp(app2);
});
});
});

View File

@@ -1198,6 +1198,41 @@ const sendAndWaitForResponse = async (page: Page) => {
});
};
const createExampleFromSidebar = async (page: Page, requestName: string, exampleName: string, description: string = '') => {
const requestRow = page.locator('.collection-item-name').filter({ hasText: requestName }).first();
await requestRow.hover();
await requestRow.locator('..').locator('.menu-icon').click({ force: true });
await page.locator('.dropdown-item').filter({ hasText: 'Create Example' }).click();
const exampleInput = page.getByTestId('create-example-name-input');
await expect(exampleInput).toBeVisible();
await exampleInput.clear();
await exampleInput.fill(exampleName);
const descriptionInput = page.getByTestId('create-example-description-input');
await descriptionInput.clear();
await descriptionInput.fill(description);
await page.getByRole('button', { name: 'Create Example' }).click();
await expect(page.locator('text=Create Response Example')).not.toBeAttached();
};
const openExampleFromSidebar = async (page: Page, requestName: string, exampleName: string, index: number = 0) => {
const requestRow = page.locator('.collection-item-name').filter({ hasText: requestName }).first();
const requestBranch = requestRow.locator('..');
const exampleRow = requestBranch
.locator('.collection-item-name')
.filter({ has: page.locator('.example-icon') })
.getByText(exampleName, { exact: true })
.nth(index);
if (!(await exampleRow.isVisible())) {
await requestRow.getByTestId('request-item-chevron').click();
}
await expect(exampleRow).toBeVisible();
await exampleRow.click();
};
export {
closeAllCollections,
openCollection,
@@ -1243,7 +1278,9 @@ export {
addPostResponseScript,
addTestScript,
sendAndWaitForErrorCard,
sendAndWaitForResponse
sendAndWaitForResponse,
createExampleFromSidebar,
openExampleFromSidebar
};
export type { SandboxMode, EnvironmentType, EnvironmentVariable, ImportCollectionOptions, CreateRequestOptions, CreateUntitledRequestOptions, CreateTransientRequestOptions, AssertionInput };