fix(tests): flaky bound action e2e tests (#8071)

This commit is contained in:
shubh-bruno
2026-06-22 18:55:05 +05:30
committed by GitHub
parent d2de2d590e
commit 46498e8423
14 changed files with 2096 additions and 2038 deletions

View File

@@ -127,6 +127,12 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
return false;
}, { enabled: isKeyboardFocused, deps: [isKeyboardFocused] });
useKeybinding('newRequest', () => {
if (!isFolder) return false;
setNewRequestModalOpen(true);
return false;
}, { enabled: isKeyboardFocused && isFolder, deps: [isKeyboardFocused, isFolder] });
const [dropType, setDropType] = useState(null); // 'adjacent' or 'inside'
const [{ isDragging }, drag, dragPreview] = useDrag({

View File

@@ -228,6 +228,11 @@ const Collection = ({ collection, searchText }) => {
return false;
}, { enabled: isKeyboardFocused, deps: [isKeyboardFocused] });
useKeybinding('newRequest', () => {
setShowNewRequestModal(true);
return false;
}, { enabled: isKeyboardFocused, deps: [isKeyboardFocused] });
const handleFocus = () => {
setIsKeyboardFocused(true);
dispatch(setFocusedSidebarPath(collection.pathname));

View File

@@ -88,26 +88,6 @@ export const HotkeysProvider = (props) => {
};
}, [activeTabUid, tabs, collections, dispatch, userKeyBindings, keybindingsEnabled]);
// new request
useEffect(() => {
bindAction('newRequest', (e) => {
const activeTab = find(tabs, (t) => t.uid === activeTabUid);
if (activeTab) {
const collection = findCollectionByUid(collections, activeTab.collectionUid);
if (collection) {
setShowNewRequestModal(true);
}
}
return false; // this stops the event bubbling
});
return () => {
unbindAction('newRequest');
};
}, [activeTabUid, tabs, collections, setShowNewRequestModal, userKeyBindings, keybindingsEnabled]);
// global search
useEffect(() => {
bindAction('globalSearch', (e) => {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,173 @@
import { expect, test } from '../../playwright';
import { closeAllCollections } from '../utils/page';
import {
closePreferencesTab,
collectionName,
modifier,
openKeybindingsTab,
openRequest,
pressShortcut,
resetKeybindings,
setupBoundActionsData
} from './helpers';
test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
test.beforeEach(async ({ pageWithUserData: page, createTmpDir }) => {
await page.locator('[data-app-state="loaded"]').waitFor(); ;
await setupBoundActionsData(page, createTmpDir);
});
test.afterEach(async ({ pageWithUserData: page }) => {
await resetKeybindings(page);
await closeAllCollections(page);
});
test.describe('COLLECTIONS & ENVIRONMENTS', () => {
test.describe('SHORTCUT: Import Collection', () => {
test('default Cmd/Ctrl+O open import collection modal', async ({ pageWithUserData: page }) => {
await pressShortcut(page, modifier, 'KeyO');
await expect(page.getByTestId('import-collection-modal')).toBeVisible();
// Close the modal to leave a clean state for subsequent tests
await page.getByTestId('modal-close-button').click();
await expect(page.getByTestId('import-collection-modal')).not.toBeVisible();
});
test('customized Alt+O open import collection modal', async ({ pageWithUserData: page }) => {
await pressShortcut(page, modifier, 'KeyW');
// Remap importCollection to Alt+O
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-importCollection');
await row.hover();
await page.getByTestId('keybinding-edit-importCollection').click();
await expect(page.getByTestId('keybinding-input-importCollection')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'KeyO');
await closePreferencesTab(page);
// Trigger the remapped shortcut
await pressShortcut(page, 'Alt', 'KeyO');
await expect(page.getByTestId('import-collection-modal')).toBeVisible();
// Close the modal to leave a clean state for subsequent tests
await page.getByTestId('modal-close-button').click();
await expect(page.getByTestId('import-collection-modal')).not.toBeVisible();
});
});
test.describe('SHORTCUT: Edit Environment (Cmd/Ctrl+E)', () => {
test('open environment tab of collection Cmd/Ctrl+E', async ({ pageWithUserData: page }) => {
await openRequest(page, 'kb-collection', 'req-7', { persist: true });
await pressShortcut(page, modifier, 'KeyE');
await expect(page.locator('.request-tab').filter({ has: page.getByText('Environments', { exact: true }) })).toBeVisible();
});
test('open environment tab of collection customized Alt+E', async ({ pageWithUserData: page }) => {
// Remap editEnvironment to Alt+E
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-editEnvironment');
await row.hover();
await page.getByTestId('keybinding-edit-editEnvironment').click();
await expect(page.getByTestId('keybinding-input-editEnvironment')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'KeyE');
await openRequest(page, 'kb-collection', 'req-7', { persist: true });
await pressShortcut(page, 'Alt', 'KeyE');
await expect(page.locator('.request-tab').filter({ has: page.getByText('Environments', { exact: true }) })).toBeVisible();
});
});
test.describe('SHORTCUT: New Request', () => {
test('default Cmd/Ctrl+N opens new request modal for collection', async ({ pageWithUserData: page }) => {
// Focus the collection so the keybinding is active
await page.getByTestId('sidebar-collection-row').filter({ has: page.getByText(collectionName, { exact: true }) }).click();
await pressShortcut(page, modifier, 'KeyN');
await page.getByTestId('request-name').fill('nr-collection-cenv');
await page.getByTestId('new-request-url').locator('.CodeMirror').click();
await page.keyboard.type('https://echo.usebruno.com');
await page.getByTestId('create-new-request-button').click();
await expect(page.locator('.request-tab').filter({ has: page.getByText('nr-collection-cenv', { exact: true }) })).toBeVisible();
});
test('default Cmd/Ctrl+N opens new request modal for folder', async ({ pageWithUserData: page }) => {
// Focus the folder so the keybinding is active
await page.locator('.collection-item-name').filter({ has: page.getByText('kb-folder', { exact: true }) }).click();
await pressShortcut(page, modifier, 'KeyN');
await page.getByTestId('request-name').fill('nr-folder-cenv');
await page.getByTestId('new-request-url').locator('.CodeMirror').click();
await page.keyboard.type('https://echo.usebruno.com');
await page.getByTestId('create-new-request-button').click();
await expect(page.locator('.request-tab').filter({ has: page.getByText('nr-folder-cenv', { exact: true }) })).toBeVisible();
});
test('customized Alt+N opens new request modal for collection', async ({ pageWithUserData: page }) => {
// Remap newRequest to Alt+N
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-newRequest');
await row.hover();
await page.getByTestId('keybinding-edit-newRequest').click();
await expect(page.getByTestId('keybinding-input-newRequest')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'KeyN');
// Focus the collection so the keybinding is active
await page.getByTestId('sidebar-collection-row').filter({ has: page.getByText(collectionName, { exact: true }) }).click();
await pressShortcut(page, 'Alt', 'KeyN');
await page.getByTestId('request-name').fill('nr-collection-cenv-altn');
await page.getByTestId('new-request-url').locator('.CodeMirror').click();
await page.keyboard.type('https://echo.usebruno.com');
await page.getByTestId('create-new-request-button').click();
await expect(page.locator('.request-tab').filter({ has: page.getByText('nr-collection-cenv-altn', { exact: true }) })).toBeVisible();
});
test('customized Alt+N opens new request modal for folder', async ({ pageWithUserData: page }) => {
// Remap newRequest to Alt+N
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-newRequest');
await row.hover();
await page.getByTestId('keybinding-edit-newRequest').click();
await expect(page.getByTestId('keybinding-input-newRequest')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'KeyN');
// Focus the folder so the keybinding is active
await page.locator('.collection-item-name').filter({ has: page.getByText('kb-folder', { exact: true }) }).click();
await pressShortcut(page, 'Alt', 'KeyN');
await page.getByTestId('request-name').fill('nr-folder-cenv-altn');
await page.getByTestId('new-request-url').locator('.CodeMirror').click();
await page.keyboard.type('https://echo.usebruno.com');
await page.getByTestId('create-new-request-button').click();
await expect(page.locator('.request-tab').filter({ has: page.getByText('nr-folder-cenv-altn', { exact: true }) })).toBeVisible();
});
});
});
});

View File

@@ -0,0 +1,95 @@
import { expect, test } from '../../playwright';
import { closeAllCollections } from '../utils/page';
import {
modifier,
openKeybindingsTab,
pressShortcut,
resetKeybindings,
setupBoundActionsData
} from './helpers';
test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
test.beforeEach(async ({ pageWithUserData: page, createTmpDir }) => {
await page.locator('[data-app-state="loaded"]').waitFor(); ;
await setupBoundActionsData(page, createTmpDir);
});
test.afterEach(async ({ pageWithUserData: page }) => {
await resetKeybindings(page);
await closeAllCollections(page);
});
test.describe('DEVELOPER TOOLS', () => {
test.describe('SHORTCUT: Open Terminal (Cmd/Ctrl+T)', () => {
test('default Cmd/Ctrl+T opens terminal', async ({ pageWithUserData: page }) => {
// Open Collection-Settings tab (double-click collection name)
await page.getByTestId('sidebar-collection-row').filter({ has: page.getByText('kb-collection', { exact: true }) }).click();
await expect(page.locator('.request-tab').filter({ has: page.getByText('Collection', { exact: true }) })).toBeVisible();
// Press Cmd/Ctrl+T to open terminal at workspace level
await pressShortcut(page, modifier, 'KeyT');
// Verify terminal session is visible using data-testid
const collectionTerminalSession = page.getByTestId('session-list-0');
await expect(collectionTerminalSession).toBeVisible();
const collectionSession = collectionTerminalSession;
await expect(collectionSession).toContainText('kb-collection');
await page.getByTitle('Close console').click();
// Open Folder-Settings tab (create folder + double-click)
// Open folder settings
await page.locator('.collection-item-name').filter({ has: page.getByText('kb-terminal-folder', { exact: true }) }).dblclick();
await expect(page.locator('.request-tab').filter({ has: page.getByText('kb-terminal-folder', { exact: true }) })).toBeVisible();
await pressShortcut(page, modifier, 'KeyT');
const folderTerminalSession = page.getByTestId('session-list-1');
await expect(folderTerminalSession).toBeVisible();
// Verify the terminal session name is the workspace name (default_workspace)
const folderSessionName = folderTerminalSession;
await expect(folderSessionName).toContainText('kb-terminal-folder');
});
});
test.describe('SHORTCUT: Open Terminal (customized Alt+T)', () => {
test('customized Alt+T opens terminal', async ({ pageWithUserData: page }) => {
// Remap openTerminal to Alt+T
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-openTerminal');
await row.hover();
await page.getByTestId('keybinding-edit-openTerminal').click();
await expect(page.getByTestId('keybinding-input-openTerminal')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'KeyT');
await page.getByTestId('sidebar-collection-row').filter({ has: page.getByText('kb-collection', { exact: true }) }).click();
await expect(page.locator('.request-tab').filter({ has: page.getByText('Collection', { exact: true }) })).toBeVisible();
// Press Cmd/Ctrl+T to open terminal at workspace level
await pressShortcut(page, 'Alt', 'KeyT');
await page.waitForTimeout(500);
// Verify terminal session is visible using data-testid
const collectionTerminalSession = page.getByTestId('session-list-0');
await expect(collectionTerminalSession).toBeVisible();
const collectionSession = collectionTerminalSession;
await expect(collectionSession).toContainText('kb-collection');
// Open folder settings
await page.locator('.collection-item-name').filter({ has: page.getByText('kb-terminal-folder', { exact: true }) }).dblclick();
await pressShortcut(page, 'Alt', 'KeyT');
const folderTerminalSession = page.getByTestId('session-list-1');
await expect(folderTerminalSession).toBeVisible();
// Verify the terminal session name is the workspace name (default_workspace)
const folderSessionName = folderTerminalSession;
await expect(folderSessionName).toContainText('kb-terminal-folder');
});
});
});
});

155
tests/shortcuts/helpers.ts Normal file
View File

@@ -0,0 +1,155 @@
import { expect, Page } from '../../playwright';
import {
closeAllCollections,
createCollection,
createFolder,
createRequest,
openCollection,
openRequest as openRequestBase
} from '../utils/page';
export const modifier = process.platform === 'darwin' ? 'Meta' : 'Control';
export const collectionName = 'kb-collection';
export const baseRequests = ['req-1', 'req-2', 'req-3', 'req-4', 'req-5', 'req-6', 'req-7', 'req-8', 'req-9'];
export const pressShortcut = async (page: Page, ...keys: string[]) => {
for (const key of keys) {
await page.keyboard.down(key);
}
for (const key of [...keys].reverse()) {
await page.keyboard.up(key);
}
};
export const setupBoundActionsData = async (page: Page, createTmpDir: (prefix: string) => Promise<string>) => {
await closeAllCollections(page);
const path = await createTmpDir('kb-collection-path');
await createCollection(page, collectionName, path);
await createFolder(page, 'kb-folder', collectionName, true);
await createFolder(page, 'kb-draft-folder', collectionName, true);
await createFolder(page, 'kb-terminal-folder', collectionName, true);
};
const checkIfRequestExists = async (page: Page, requestName: string) => {
await openCollection(page, collectionName);
const request = page.getByTestId('collections').locator('.collection-item-name').filter({ has: page.getByText(requestName, { exact: true }) });
return (await request.count()) > 0;
};
export const openRequest = async (...args: Parameters<typeof openRequestBase>) => {
const [page, targetCollectionName, requestName] = args;
if (
targetCollectionName === collectionName
&& baseRequests.includes(requestName)
&& !(await checkIfRequestExists(page, requestName))
) {
await createRequest(page, requestName, targetCollectionName);
}
return openRequestBase(...args);
};
export const openKeybindingsTab = async (page: Page) => {
await page.getByRole('button', { name: 'Open Preferences' }).click();
await page.getByRole('tab', { name: 'Keybindings' }).click();
await expect(page.locator('.section-header').filter({ has: page.getByText('Keybindings', { exact: true }) })).toBeVisible();
};
export const closePreferencesTab = async (page: Page) => {
const prefTab = page.locator('.request-tab').filter({ has: page.getByText('Preferences', { exact: true }) });
// Nothing to do if it's already closed.
if (!(await prefTab.isVisible().catch(() => false))) return;
await expect(async () => {
await prefTab.hover();
await prefTab.getByTestId('request-tab-close-icon').click({ force: true });
await expect(prefTab).not.toBeVisible();
}).toPass({ timeout: 10000 });
};
export const resetKeybindings = async (page: Page) => {
await openKeybindingsTab(page);
const resetBtn = page.getByTestId('reset-all-keybindings-btn');
if (await resetBtn.isEnabled().catch(() => false)) {
await resetBtn.click({ timeout: 2000 });
}
await closePreferencesTab(page);
};
export const closeTabByName = async (page: any, name: string | RegExp) => {
const tab = page.locator('.request-tab').filter({ has: page.getByText(name, { exact: true }) });
await tab.dblclick();
await tab.hover();
await tab.getByTestId('request-tab-close-icon').click({ force: true });
await expect(tab).not.toBeVisible();
};
export const openFolderSettingsTab = async (page: Page, folderName: string) => {
await openCollection(page, collectionName);
const folderRow = page.locator('.collection-item-name').filter({ has: page.getByText(folderName, { exact: true }) });
await expect(folderRow).toBeVisible();
await folderRow.dblclick();
await expect(page.locator('.request-tab').filter({ has: page.getByText(folderName, { exact: true }) })).toBeVisible();
};
export const reopenClosedTab = async (page: Page, shortcut: () => Promise<void>, expectedTabName: string | RegExp) => {
for (let attempt = 0; attempt < 3; attempt++) {
await page.locator('.request-tab').first().click();
// Wait for a tab to become active (focus settled) before firing the shortcut.
await expect(page.locator('.request-tab.active')).toBeVisible();
await shortcut();
const reopenedTab = page.locator('.request-tab').filter({ has: page.getByText(expectedTabName, { exact: true }) });
if ((await reopenedTab.count()) > 0) {
await expect(reopenedTab).toBeVisible();
return;
}
// Event-driven backoff: give the reopened tab a chance to appear before retrying.
await reopenedTab.waitFor({ state: 'visible', timeout: 1000 }).catch(() => { });
}
await expect(page.locator('.request-tab').filter({ has: page.getByText(expectedTabName, { exact: true }) })).toBeVisible();
};
export const remapKeybinding = async (
page: Page,
action: string,
...keys: string[]
) => {
await openKeybindingsTab(page);
const row = page.getByTestId(`keybinding-row-${action}`);
await expect(row).toBeVisible();
await row.scrollIntoViewIfNeeded();
await row.hover();
const editButton = row.getByTestId(`keybinding-edit-${action}`);
const keybindingInput = page.getByTestId(`keybinding-input-${action}`);
if (await editButton.isVisible().catch(() => false)) {
await editButton.click({ force: true });
} else {
await row.click({ force: true });
if (await editButton.isVisible().catch(() => false)) {
await editButton.click({ force: true });
}
}
await expect(keybindingInput).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, ...keys);
await closePreferencesTab(page);
};
export const getTabIndex = async (page: Page, name: string) => {
const tabs = page.locator('.request-tab .tab-label');
const count = await tabs.count();
for (let i = 0; i < count; i++) {
const text = (await tabs.nth(i).innerText()).trim();
if (text.includes(name)) {
return i;
}
}
return -1;
};

View File

@@ -0,0 +1,8 @@
{
"preferences": {
"onboarding": {
"hasLaunchedBefore": true,
"hasSeenWelcomeModal": true
}
}
}

View File

@@ -0,0 +1,48 @@
import { expect, test } from '../../playwright';
import { closeAllCollections } from '../utils/page';
import {
modifier,
openKeybindingsTab,
pressShortcut,
resetKeybindings,
setupBoundActionsData
} from './helpers';
test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
test.beforeEach(async ({ pageWithUserData: page, createTmpDir }) => {
await page.locator('[data-app-state="loaded"]').waitFor(); ;
await setupBoundActionsData(page, createTmpDir);
});
test.afterEach(async ({ pageWithUserData: page }) => {
await resetKeybindings(page);
await closeAllCollections(page);
});
test.describe('OTHERS', () => {
test.describe('SHORTCUT: Open Preferences', () => {
test('default Cmd/Ctrl+, open preferences', async ({ pageWithUserData: page }) => {
await pressShortcut(page, modifier, 'Comma');
await expect(page.locator('.request-tab').filter({ has: page.getByText('Preferences', { exact: true }) })).toBeVisible();
});
});
test.describe('SHORTCUT: Open Preferences (customized Cmd/Ctrl+P)', () => {
test('customized Cmd/Ctrl+P open preferences', async ({ pageWithUserData: page }) => {
// Remap openPreferences to Ctrl+P
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-openPreferences');
await row.hover();
await page.getByTestId('keybinding-edit-openPreferences').click();
await expect(page.getByTestId('keybinding-input-openPreferences')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, modifier, 'KeyP');
await expect(page.locator('.request-tab').filter({ has: page.getByText('Preferences', { exact: true }) })).toBeVisible();
});
});
});
});

View File

@@ -0,0 +1,296 @@
import { expect, test } from '../../playwright';
import { closeAllCollections, createRequest, selectRequestPaneTab } from '../utils/page';
import {
modifier,
openKeybindingsTab,
openRequest,
pressShortcut,
resetKeybindings,
setupBoundActionsData
} from './helpers';
test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
test.beforeEach(async ({ pageWithUserData: page, createTmpDir }) => {
await page.locator('[data-app-state="loaded"]').waitFor(); ;
await setupBoundActionsData(page, createTmpDir);
});
test.afterEach(async ({ pageWithUserData: page }) => {
await resetKeybindings(page);
await closeAllCollections(page);
});
test.describe('REQUESTS', () => {
test.describe('SHORTCUT: Send Request (Cmd/Ctrl+Enter)', () => {
test('sends request when cursor is in JSON body editor', async ({ pageWithUserData: page }) => {
// Create a POST request in the shared collection pointing to the echo server
await createRequest(page, 'cmd-enter-req-body', 'kb-collection', {
url: 'https://echo.usebruno.com',
method: 'POST'
});
await openRequest(page, 'kb-collection', 'cmd-enter-req-body', { persist: true });
// Open Body tab and select JSON mode
await selectRequestPaneTab(page, 'Body');
await page.getByTestId('request-body-mode-selector').click();
await page.locator('.dropdown-item').filter({ hasText: /^JSON$/ }).click();
// Focus the body code editor and type JSON
const bodyEditor = page.getByTestId('request-body-editor').locator('.CodeMirror');
await bodyEditor.click();
await page.keyboard.type('{"name": "Bruno", "version": 2, "tags": ["api", "client", "http"], "active": true, "meta": {"author": "user", "created": "2025-01-01", "updated": "2025-06-01"}, "counts": {"requests": 42, "collections": 7}}');
await expect(page.getByTestId('request-body-editor')).toContainText('"name": "Bruno"');
// Cursor is still in the body CodeMirror - press Cmd/Ctrl+Enter to send
await pressShortcut(page, modifier, 'Enter');
// Verify a 200 response came back
await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 15000 });
});
test('sends request when cursor is in response body editor', async ({ pageWithUserData: page }) => {
await createRequest(page, 'cmd-enter-req-resp', 'kb-collection', {
url: 'https://echo.usebruno.com',
method: 'POST'
});
await openRequest(page, 'kb-collection', 'cmd-enter-req-resp', { persist: true });
await selectRequestPaneTab(page, 'Body');
await page.getByTestId('request-body-mode-selector').click();
await page.locator('.dropdown-item').filter({ hasText: /^JSON$/ }).click();
const bodyEditor = page.getByTestId('request-body-editor').locator('.CodeMirror');
await bodyEditor.click();
await page.keyboard.type('{"name": "Bruno", "version": 2, "tags": ["api", "client", "http"], "active": true, "meta": {"author": "user", "created": "2025-01-01", "updated": "2025-06-01"}, "counts": {"requests": 42, "collections": 7}}');
await expect(page.getByTestId('request-body-editor')).toContainText('"name": "Bruno"');
// First send to populate response
await pressShortcut(page, modifier, 'Enter');
await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 15000 });
// Focus cursor inside the response body CodeMirror
const responseEditor = page.getByTestId('response-preview-container').locator('.CodeMirror').first();
await responseEditor.waitFor({ state: 'visible', timeout: 5000 });
await responseEditor.click();
// Press Cmd/Ctrl+Enter again - should re-send the request
await pressShortcut(page, modifier, 'Enter');
// Verify a 200 response came back (no error, status stays/refreshes to 200)
await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 15000 });
});
test('sends request when cursor is in pre-request Vars value editor', async ({ pageWithUserData: page }) => {
await createRequest(page, 'cmd-enter-req-vars', 'kb-collection', {
url: 'https://echo.usebruno.com',
method: 'POST'
});
await openRequest(page, 'kb-collection', 'cmd-enter-req-vars', { persist: true });
// Open Vars tab - request Vars has a Pre Request section as the first table
await selectRequestPaneTab(page, 'Vars');
// Fill the first var row: name=var-1
const varsTable = page.getByTestId('request-pane').locator('table').first();
const firstRow = varsTable.locator('tbody tr').first();
const nameInput = firstRow.locator('input[type="text"]').first();
await nameInput.click();
await nameInput.fill('var-1');
// Click the value CodeMirror editor and type a multi-line value
const valueEditor = firstRow.locator('.CodeMirror').first();
await valueEditor.click();
await page.keyboard.type('val-1');
await page.keyboard.press('Enter'); // insert newline in value editor
await page.keyboard.type('val-2');
// Cursor is still in the value CodeMirror - press Cmd/Ctrl+Enter to send
// (should NOT insert a newline; should fire sendRequest)
await pressShortcut(page, modifier, 'Enter');
// Verify a 200 response came back
await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 15000 });
});
});
test.describe('SHORTCUT: Send Request (customized Shift+Enter)', () => {
test('sends request when cursor is in JSON body editor', async ({ pageWithUserData: page }) => {
// Remap sendRequest to Shift+Enter
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-sendRequest');
await row.hover();
await page.getByTestId('keybinding-edit-sendRequest').click();
await expect(page.getByTestId('keybinding-input-sendRequest')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Shift', 'Enter');
// Create a POST request in the shared collection pointing to the echo server
await createRequest(page, 'shift-enter-req-body', 'kb-collection', {
url: 'https://echo.usebruno.com',
method: 'POST'
});
await openRequest(page, 'kb-collection', 'shift-enter-req-body', { persist: true });
// Open Body tab and select JSON mode
await selectRequestPaneTab(page, 'Body');
await page.getByTestId('request-body-mode-selector').click();
await page.locator('.dropdown-item').filter({ hasText: /^JSON$/ }).click();
const bodyEditor = page.getByTestId('request-body-editor').locator('.CodeMirror');
await bodyEditor.click();
await page.keyboard.type('{"name": "Bruno", "version": 2, "tags": ["api", "client", "http"], "active": true, "meta": {"author": "user", "created": "2025-01-01", "updated": "2025-06-01"}, "counts": {"requests": 42, "collections": 7}}');
await expect(page.getByTestId('request-body-editor')).toContainText('"name": "Bruno"');
// Cursor is still in the body CodeMirror - press Shift+Enter (customized) to send
await pressShortcut(page, 'Shift', 'Enter');
await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 15000 });
});
test('sends request when cursor is in response body editor', async ({ pageWithUserData: page }) => {
// Remap sendRequest to Shift+Enter
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-sendRequest');
await row.hover();
await page.getByTestId('keybinding-edit-sendRequest').click();
await expect(page.getByTestId('keybinding-input-sendRequest')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Shift', 'Enter');
await createRequest(page, 'shift-enter-req-resp', 'kb-collection', {
url: 'https://echo.usebruno.com',
method: 'POST'
});
await openRequest(page, 'kb-collection', 'shift-enter-req-resp', { persist: true });
await selectRequestPaneTab(page, 'Body');
await page.getByTestId('request-body-mode-selector').click();
await page.locator('.dropdown-item').filter({ hasText: /^JSON$/ }).click();
const bodyEditor = page.getByTestId('request-body-editor').locator('.CodeMirror');
await bodyEditor.click();
await page.keyboard.type('{"name": "Bruno", "version": 2, "tags": ["api", "client", "http"], "active": true, "meta": {"author": "user", "created": "2025-01-01", "updated": "2025-06-01"}, "counts": {"requests": 42, "collections": 7}}');
await expect(page.getByTestId('request-body-editor')).toContainText('"name": "Bruno"');
// First send with Shift+Enter to populate response
await pressShortcut(page, 'Shift', 'Enter');
await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 15000 });
// Focus cursor inside the response body CodeMirror
const responseEditor = page.getByTestId('response-preview-container').locator('.CodeMirror').first();
await responseEditor.waitFor({ state: 'visible', timeout: 5000 });
await responseEditor.click();
// Press Shift+Enter again - should re-send the request
await pressShortcut(page, 'Shift', 'Enter');
await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 15000 });
});
test('sends request when cursor is in pre-request Vars value editor', async ({ pageWithUserData: page }) => {
// Remap sendRequest to Shift+Enter
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-sendRequest');
await row.hover();
await page.getByTestId('keybinding-edit-sendRequest').click();
await expect(page.getByTestId('keybinding-input-sendRequest')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Shift', 'Enter');
await createRequest(page, 'shift-enter-req-vars', 'kb-collection', {
url: 'https://echo.usebruno.com',
method: 'POST'
});
await openRequest(page, 'kb-collection', 'shift-enter-req-vars', { persist: true });
await selectRequestPaneTab(page, 'Vars');
const varsTable = page.getByTestId('request-pane').locator('table').first();
const firstRow = varsTable.locator('tbody tr').first();
const nameInput = firstRow.locator('input[type="text"]').first();
await nameInput.click();
await nameInput.fill('var-1');
const valueEditor = firstRow.locator('.CodeMirror').first();
await valueEditor.click();
await page.keyboard.type('val-1');
await page.keyboard.press('Enter'); // insert newline in value editor
await page.keyboard.type('val-2');
// Cursor is still in the value CodeMirror - press Shift+Enter (customized) to send
await pressShortcut(page, 'Shift', 'Enter');
await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 15000 });
});
});
test.describe('SHORTCUT: Change Orientation (Cmd/Ctrl+J)', () => {
test('default Cmd/Ctrl+J change layout orientation', async ({ pageWithUserData: page }) => {
await openRequest(page, 'kb-collection', 'req-5', { persist: true });
// Press Cmd/Ctrl+J to change layout
await pressShortcut(page, modifier, 'KeyJ');
await expect(
page.getByTestId('response-layout-toggle-btn')
).toHaveAttribute('title', 'Switch to horizontal layout');
// Press Cmd/Ctrl+J to change layout
await pressShortcut(page, modifier, 'KeyJ');
await expect(
page.getByTestId('response-layout-toggle-btn')
).toHaveAttribute('title', 'Switch to vertical layout');
// Press Cmd/Ctrl+J to change layout
await pressShortcut(page, modifier, 'KeyJ');
await expect(
page.getByTestId('response-layout-toggle-btn')
).toHaveAttribute('title', 'Switch to horizontal layout');
});
});
test.describe('SHORTCUT: Change Orientation (customized Alt+Shift+Y)', () => {
test('customized Alt+Shift+Y change layout orientation', async ({ pageWithUserData: page }) => {
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-changeLayout');
await row.hover();
await page.getByTestId('keybinding-edit-changeLayout').click();
await expect(page.getByTestId('keybinding-input-changeLayout')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'Shift', 'KeyY');
await openRequest(page, 'kb-collection', 'req-5', { persist: true });
await pressShortcut(page, 'Alt', 'Shift', 'KeyY');
await expect(
page.getByTestId('response-layout-toggle-btn')
).toHaveAttribute('title', 'Switch to horizontal layout');
// Press Alt+Shift+Y to change layout
await pressShortcut(page, 'Alt', 'Shift', 'KeyY');
await expect(
page.getByTestId('response-layout-toggle-btn')
).toHaveAttribute('title', 'Switch to vertical layout');
// Press Alt+Shift+Y to change layout
await pressShortcut(page, 'Alt', 'Shift', 'KeyY');
await expect(
page.getByTestId('response-layout-toggle-btn')
).toHaveAttribute('title', 'Switch to horizontal layout');
});
});
});
});

View File

@@ -0,0 +1,57 @@
import { expect, test } from '../../playwright';
import { closeAllCollections } from '../utils/page';
import {
closePreferencesTab,
modifier,
openKeybindingsTab,
pressShortcut,
resetKeybindings,
setupBoundActionsData
} from './helpers';
test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
test.beforeEach(async ({ pageWithUserData: page, createTmpDir }) => {
await page.locator('[data-app-state="loaded"]').waitFor(); ;
await setupBoundActionsData(page, createTmpDir);
});
test.afterEach(async ({ pageWithUserData: page }) => {
await resetKeybindings(page);
await closeAllCollections(page);
});
test.describe('SEARCH', () => {
test.describe('SHORTCUT: Global Search (Cmd/Ctrl+K)', () => {
test('default Cmd/Ctrl+K Global Search Modal', async ({ pageWithUserData: page }) => {
await pressShortcut(page, modifier, 'KeyK');
await expect(page.getByTestId('global-search-input')).toBeVisible();
await page.getByTestId('global-search-input').click();
await pressShortcut(page, 'Escape');
});
test('customized Shift+K Global Search Modal', async ({ pageWithUserData: page }) => {
// Remap globalSearch to Shift+K
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-globalSearch');
await row.hover();
await page.getByTestId('keybinding-edit-globalSearch').click();
await expect(page.getByTestId('keybinding-input-globalSearch')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Shift', 'KeyK');
await closePreferencesTab(page);
await pressShortcut(page, 'Shift', 'KeyK');
await expect(page.getByTestId('global-search-input')).toBeVisible();
await page.getByTestId('global-search-input').click();
await pressShortcut(page, 'Escape');
});
});
});
});

View File

@@ -0,0 +1,473 @@
import { expect, test } from '../../playwright';
import { closeAllCollections, createFolder, openCollection } from '../utils/page';
import {
closePreferencesTab,
collectionName,
modifier,
openFolderSettingsTab,
openKeybindingsTab,
openRequest,
pressShortcut,
remapKeybinding,
resetKeybindings,
setupBoundActionsData
} from './helpers';
test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
test.beforeEach(async ({ pageWithUserData: page, createTmpDir }) => {
await page.locator('[data-app-state="loaded"]').waitFor();
await setupBoundActionsData(page, createTmpDir);
});
test.afterEach(async ({ pageWithUserData: page }) => {
await resetKeybindings(page);
await closeAllCollections(page);
});
test.describe('SIDEBAR', () => {
test.describe('SHORTCUT: Sidebar search (Cmd/Ctrl+F)', () => {
test('Sidebar search default (Cmd/Ctrl+F)', async ({ pageWithUserData: page }) => {
await pressShortcut(page, modifier, 'KeyF');
await expect(page.getByTestId('sidebar-search-input')).toBeVisible();
await page.getByTitle('Search requests').click();
});
test('Sidebar search customized (Alt+F)', async ({ pageWithUserData: page }) => {
// Remap sidebarSearch to Alt+F
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-sidebarSearch');
await row.hover();
await page.getByTestId('keybinding-edit-sidebarSearch').click();
await expect(page.getByTestId('keybinding-input-sidebarSearch')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'KeyF');
await closePreferencesTab(page);
await pressShortcut(page, 'Alt', 'KeyF');
await expect(page.getByTestId('sidebar-search-input')).toBeVisible();
await page.getByTitle('Search requests').click();
});
});
test.describe('SHORTCUT: New request (Cmd/Ctrl+N)', () => {
test('New request default (Cmd/Ctrl+N)', async ({ pageWithUserData: page }) => {
await page.locator('.collection-item-name').filter({ has: page.getByText('kb-folder', { exact: true }) }).click();
await pressShortcut(page, modifier, 'KeyN');
await page.getByTestId('request-name').fill('nr-folder');
await page.getByTestId('new-request-url').locator('.CodeMirror').click();
await page.keyboard.type('https://echo.usebruno.com');
await page.getByTestId('create-new-request-button').click();
await expect(page.locator('.request-tab').filter({ has: page.getByText('nr-folder', { exact: true }) })).toBeVisible();
});
test('New request customized (Alt+N)', async ({ pageWithUserData: page }) => {
// Remap newRequest to Alt+N
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-newRequest');
await row.hover();
await page.getByTestId('keybinding-edit-newRequest').click();
await expect(page.getByTestId('keybinding-input-newRequest')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'KeyN');
await page.getByTestId('sidebar-collection-row').filter({ has: page.getByText('kb-collection', { exact: true }) }).click();
await pressShortcut(page, 'Alt', 'KeyN');
await page.getByTestId('request-name').fill('nr-collection');
await page.getByTestId('new-request-url').locator('.CodeMirror').click();
await page.keyboard.type('https://echo.usebruno.com');
await page.getByTestId('create-new-request-button').click();
await expect(page.locator('.request-tab').filter({ has: page.getByText('nr-collection', { exact: true }) })).toBeVisible();
});
});
test.describe('SHORTCUT: Rename Item', () => {
test.describe('SHORTCUT: Rename Item for request (Cmd/Ctrl+R)', () => {
test('default Cmd/Ctrl+R open rename item modal for request', async ({ pageWithUserData: page }) => {
await page.getByTestId('sidebar-collection-row').filter({ has: page.getByText('kb-collection', { exact: true }) }).dblclick();
await openRequest(page, 'kb-collection', 'req-1', { persist: true });
await pressShortcut(page, modifier, 'KeyR');
// Verify rename modal opens
const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename request/i });
await expect(renameModal).toBeVisible();
// Fill in the rename req name
const requestNameInput = page.locator('#collection-item-name');
await requestNameInput.fill('req-1-renamed');
// Click the rename button
await page.getByTestId('rename-item-button').click();
// Verify renamed request appears in sidebar
await expect(page.locator('.collection-item-name').filter({ has: page.getByText('req-1-renamed', { exact: true }) })).toBeVisible();
});
});
test.describe('SHORTCUT: Rename Item for folder (Cmd/Ctrl+R)', () => {
test('default Cmd/Ctrl+R open rename item modal for folder', async ({ pageWithUserData: page }) => {
await page.locator('.collection-item-name').filter({ has: page.getByText('kb-folder', { exact: true }) }).dblclick();
await pressShortcut(page, modifier, 'KeyR');
// Verify rename modal opens
const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename folder/i });
await expect(renameModal).toBeVisible();
// Fill in the rename req name
const folderNameInput = page.locator('#collection-item-name');
await folderNameInput.fill('kb-folder-renamed');
// Click the rename button
await page.getByTestId('rename-item-button').click();
// Verify renamed request appears in sidebar
await expect(page.locator('.collection-item-name').filter({ has: page.getByText('kb-folder-renamed', { exact: true }) })).toBeVisible();
});
});
test.describe('SHORTCUT: Rename Item for collection (Cmd/Ctrl+R)', () => {
test('default Cmd/Ctrl+R open rename item modal for collection', async ({ pageWithUserData: page }) => {
await page.getByTestId('sidebar-collection-row').filter({ has: page.getByText('kb-collection', { exact: true }) }).click();
await pressShortcut(page, modifier, 'KeyR');
// Verify rename modal opens
const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename collection/i });
await expect(renameModal).toBeVisible();
// Fill in the rename req name
const collectionInput = page.locator('#collection-name');
await collectionInput.fill('kb-collection-renamed');
// Click the rename button
await page.locator('.submit').click();
// Verify renamed request appears in sidebar
await expect(page.getByTestId('sidebar-collection-row').filter({ has: page.getByText('kb-collection-renamed', { exact: true }) })).toBeVisible();
});
});
test.describe('SHORTCUT: Rename Item for request (customized Alt+X)', () => {
test('customized Alt+X open rename item modal for request', async ({ pageWithUserData: page }) => {
// Remap renameItem to Alt+R
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-renameItem');
await row.hover();
await page.getByTestId('keybinding-edit-renameItem').click();
await expect(page.getByTestId('keybinding-input-renameItem')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'KeyX');
await openRequest(page, collectionName, 'req-1', { persist: true });
await pressShortcut(page, 'Alt', 'KeyX');
// Verify rename modal opens
const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename request/i });
await expect(renameModal).toBeVisible();
// Fill in the rename req name
const requestNameInput = page.locator('#collection-item-name');
await requestNameInput.fill('req-1-renamed-altx');
// Click the rename button
await page.getByTestId('rename-item-button').click();
// Verify renamed request appears in sidebar
await expect(page.locator('.collection-item-name').filter({ has: page.getByText('req-1-renamed-altx', { exact: true }) })).toBeVisible();
});
});
test.describe('SHORTCUT: Rename Item for folder (customized Alt+X)', () => {
test('customized Alt+R open rename item modal for folder', async ({ pageWithUserData: page }) => {
await remapKeybinding(page, 'renameItem', 'Alt', 'KeyX');
await createFolder(page, 'kb-folder-rename-src', collectionName, true);
await openFolderSettingsTab(page, 'kb-folder-rename-src');
await pressShortcut(page, 'Alt', 'KeyX');
// Verify rename modal opens
const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename folder/i });
await expect(renameModal).toBeVisible();
// Fill in the rename req name
const folderNameInput = page.locator('#collection-item-name');
await folderNameInput.fill('kb-folder-renamed-altx-src');
// Click the rename button
await page.getByTestId('rename-item-button').click();
// Verify renamed request appears in sidebar
await expect(page.locator('.collection-item-name').filter({ has: page.getByText('kb-folder-renamed-altx-src', { exact: true }) })).toBeVisible();
});
});
test.describe('SHORTCUT: Rename Item for collection (customized Alt+X)', () => {
test('customized Alt+R open rename item modal for collection', async ({ pageWithUserData: page }) => {
await remapKeybinding(page, 'renameItem', 'Alt', 'KeyX');
await page.getByTestId('sidebar-collection-row').filter({ has: page.getByText(collectionName, { exact: true }) }).click();
await pressShortcut(page, 'Alt', 'KeyX');
// Verify rename modal opens
const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename collection/i });
await expect(renameModal).toBeVisible();
// Fill in the rename req name
const collectionInput = page.locator('#collection-name');
await collectionInput.fill('kb-collection-renamed-altx');
// Click the rename button
await page.locator('.submit').click();
// Verify renamed request appears in sidebar
await expect(page.getByTestId('sidebar-collection-row').filter({ has: page.getByText('kb-collection-renamed-altx', { exact: true }) })).toBeVisible();
});
});
});
test.describe('SHORTCUT: Clone Item', () => {
test.describe('SHORTCUT: Clone Item for request (Cmd/Ctrl+D)', () => {
test('default Cmd/Ctrl+D open clone item modal for request', async ({ pageWithUserData: page }) => {
await openRequest(page, 'kb-collection', 'req-1', { persist: true });
await pressShortcut(page, modifier, 'KeyD');
// Verify clone modal opens
const cloneModal = page.locator('.bruno-modal-card').filter({ hasText: /clone request/i });
await expect(cloneModal).toBeVisible();
// Fill in the clone req name
const requestNameInput = page.locator('#collection-item-name');
await requestNameInput.fill('req-1 clone 1');
// Click the clone button
await page.getByTestId('clone-item-button').click();
// Verify cloned request appears in sidebar
await expect(page.locator('.collection-item-name').filter({ has: page.getByText('req-1 clone 1', { exact: true }) })).toBeVisible();
});
});
test.describe('SHORTCUT: Clone Item for folder (Cmd/Ctrl+D)', () => {
test('default Cmd/Ctrl+D open clone item modal for folder', async ({ pageWithUserData: page }) => {
await page.locator('.collection-item-name').filter({ has: page.getByText('kb-folder', { exact: true }) }).dblclick();
await pressShortcut(page, modifier, 'KeyD');
// Verify clone modal opens
const cloneModal = page.locator('.bruno-modal-card').filter({ hasText: /clone folder/i });
await expect(cloneModal).toBeVisible();
// Fill in the clone kb-folder name
const folderNameInput = page.locator('#collection-item-name');
await folderNameInput.fill('kb-folder clone 1');
// Click the clone button
await page.getByTestId('clone-item-button').click();
// Verify cloned request appears in sidebar
await expect(page.locator('.collection-item-name').filter({ has: page.getByText('kb-folder clone 1', { exact: true }) })).toBeVisible();
});
});
test.describe('SHORTCUT: Clone Item for request (customized Alt+D)', () => {
test('customized Alt+D open clone item modal for request', async ({ pageWithUserData: page }) => {
// Remap cloneItem to Alt+D
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-cloneItem');
await row.hover();
await page.getByTestId('keybinding-edit-cloneItem').click();
await expect(page.getByTestId('keybinding-input-cloneItem')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'KeyD');
await openRequest(page, 'kb-collection', 'req-2', { persist: true });
await pressShortcut(page, 'Alt', 'KeyD');
// Verify clone modal opens
const cloneModal = page.locator('.bruno-modal-card').filter({ hasText: /clone request/i });
await expect(cloneModal).toBeVisible();
// Fill in the clone req name
const requestNameInput = page.locator('#collection-item-name');
await requestNameInput.fill('req-2 clone 1');
// Click the clone button
await page.getByTestId('clone-item-button').click();
// Verify renamed request appears in sidebar
await expect(page.locator('.collection-item-name').filter({ has: page.getByText('req-2 clone 1', { exact: true }) })).toBeVisible();
});
});
test.describe('SHORTCUT: Clone Item for folder (customized Alt+D)', () => {
test('customized Alt+D open clone item modal for folder', async ({ pageWithUserData: page }) => {
// Remap cloneItem to Alt+D (keybindings are reset after each test, so re-bind here).
await remapKeybinding(page, 'cloneItem', 'Alt', 'KeyD');
await closePreferencesTab(page);
await createFolder(page, 'kb-folder-clone-src', collectionName, true);
await openCollection(page, collectionName);
await page.locator('.collection-item-name').filter({ has: page.getByText('kb-folder-clone-src', { exact: true }) }).first().click();
await pressShortcut(page, 'Alt', 'KeyD');
// Verify clone modal opens
const cloneModal = page.locator('.bruno-modal-card').filter({ hasText: /clone folder/i });
await expect(cloneModal).toBeVisible();
// Fill in the clone req name
const folderNameInput = page.locator('#collection-item-name');
await folderNameInput.fill('kb-folder-clone-src copy 1');
// Click the clone button
await page.getByTestId('clone-item-button').click();
// Verify renamed request appears in sidebar
await expect(page.locator('.collection-item-name').filter({ has: page.getByText('kb-folder-clone-src copy 1', { exact: true }) })).toBeVisible();
});
});
});
test.describe('SHORTCUT: Copy Paste Item', () => {
test.describe('SHORTCUT: Copy Paste Item for request (Cmd/Ctrl+C/V)', () => {
test('default Cmd/Ctrl+C/V copy paste item for request', async ({ pageWithUserData: page }) => {
await openRequest(page, 'kb-collection', 'req-3', { persist: true });
await pressShortcut(page, modifier, 'KeyC');
await pressShortcut(page, modifier, 'KeyV');
// Verify cloned request appears in sidebar
await expect(page.locator('.collection-item-name').filter({ has: page.getByText('req-3 (1)', { exact: true }) })).toBeVisible();
});
});
test.describe('SHORTCUT: Copy Paste Item for folder (Cmd/Ctrl+C/V)', () => {
test('default Cmd/Ctrl+C/V copy paste item for folder', async ({ pageWithUserData: page }) => {
await openRequest(page, collectionName, 'kb-folder', { persist: true });
await pressShortcut(page, modifier, 'KeyC');
await pressShortcut(page, modifier, 'KeyV');
// Verify copied item appears in sidebar as child of folder
await expect(page.locator('.collection-item-name').filter({ has: page.getByText('kb-folder', { exact: true }) })).toHaveCount(2);
});
});
test.describe('SHORTCUT: Copy Paste Item for request (customized Alt+C/V)', () => {
test('customized Alt+C/V copy paste item for request', async ({ pageWithUserData: page }) => {
// Remap copyItem to Alt+D
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-copyItem');
await row.hover();
await page.getByTestId('keybinding-edit-copyItem').click();
await expect(page.getByTestId('keybinding-input-copyItem')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'KeyC');
// Remap pasteItem to Alt+V
await openKeybindingsTab(page);
const row2 = page.getByTestId('keybinding-row-pasteItem');
await row2.hover();
await page.getByTestId('keybinding-edit-pasteItem').click();
await expect(page.getByTestId('keybinding-input-pasteItem')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'KeyV');
await openRequest(page, 'kb-collection', 'req-4', { persist: true });
await pressShortcut(page, 'Alt', 'KeyC');
await pressShortcut(page, 'Alt', 'KeyV');
// Verify cloned request appears in sidebar
await expect(page.locator('.collection-item-name').filter({ has: page.getByText('req-4 (1)', { exact: true }) })).toBeVisible();
});
});
test.describe('SHORTCUT: Copy Paste Item for folder (Cmd/Ctrl+C/V)', () => {
test('customized Alt+C/V copy paste item for folder', async ({ pageWithUserData: page }) => {
await remapKeybinding(page, 'copyItem', 'Alt', 'KeyC');
await remapKeybinding(page, 'pasteItem', 'Alt', 'KeyV');
await createFolder(page, 'kb-folder-copy-src', collectionName, true);
await openFolderSettingsTab(page, 'kb-folder-copy-src');
await pressShortcut(page, 'Alt', 'KeyC');
await pressShortcut(page, 'Alt', 'KeyV');
// Verify copied item appears in sidebar as child of folder
await expect(page.locator('.collection-item-name').filter({ has: page.getByText('kb-folder-copy-src', { exact: true }) })).toHaveCount(2);
});
});
});
test.describe('SHORTCUT: Collapse Sidebar', () => {
test('Collapse sidebar & Expand using default Cmd/Ctrl+\\', async ({ pageWithUserData: page }) => {
await expect(page.getByTestId('collections')).toBeVisible();
await page.locator('body').click({ position: { x: 1, y: 1 } });
// Press Cmd/Ctrl+\ to collapse sidebar
await pressShortcut(page, modifier, 'Backslash');
// Verify sidebar collapsed to 0px
await expect.poll(
() => page.locator('aside.sidebar').evaluate((el) => getComputedStyle(el).width)
).toBe('0px');
// Press Cmd/Ctrl+\ to expand sidebar
await pressShortcut(page, modifier, 'Backslash');
// Verify sidebar expanded to 250px
await expect.poll(
() => page.locator('aside.sidebar').evaluate((el) => getComputedStyle(el).width)
).toBe('250px');
});
test('Collapse sidebar & Expand using customized (Shift+G)', async ({ pageWithUserData: page }) => {
// Remap collapseSidebar to Shift+G
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-collapseSidebar');
await row.hover();
await page.getByTestId('keybinding-edit-collapseSidebar').click();
await expect(page.getByTestId('keybinding-input-collapseSidebar')).toBeVisible();
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Shift', 'KeyG');
await closePreferencesTab(page);
// Trigger the remapped shortcut to collapse sidebar
await pressShortcut(page, 'Shift', 'KeyG');
// Verify sidebar collapsed to 0px
await expect.poll(
() => page.locator('aside.sidebar').evaluate((el) => getComputedStyle(el).width)
).toBe('0px');
// Trigger the remapped shortcut to expand sidebar
await pressShortcut(page, 'Shift', 'KeyG');
// Verify sidebar expanded to 250px
await expect.poll(
() => page.locator('aside.sidebar').evaluate((el) => getComputedStyle(el).width)
).toBe('250px');
});
});
});
});

View File

@@ -0,0 +1,760 @@
import { expect, test } from '../../playwright';
import { closeAllCollections } from '../utils/page';
import {
closePreferencesTab,
closeTabByName,
collectionName,
getTabIndex,
modifier,
openKeybindingsTab,
openRequest,
pressShortcut,
remapKeybinding,
reopenClosedTab,
resetKeybindings,
setupBoundActionsData
} from './helpers';
test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
test.beforeEach(async ({ pageWithUserData: page, createTmpDir }) => {
await page.locator('[data-app-state="loaded"]').waitFor(); ;
await setupBoundActionsData(page, createTmpDir);
});
test.afterEach(async ({ pageWithUserData: page }) => {
await resetKeybindings(page);
await closeAllCollections(page);
});
test.describe('TABS', () => {
test.describe('SHORTCUT: Close Tab', () => {
test('Close active tab default (Cmd/Ctrl+W)', async ({ pageWithUserData: page }) => {
await openRequest(page, collectionName, 'req-1', { persist: true });
const reqTab = page.locator('.request-tab').filter({ has: page.getByText('req-1', { exact: true }) });
// Click the tab to guarantee it's the focused/active tab before firing the shortcut.
await reqTab.click();
await expect(reqTab).toHaveClass(/active/, { timeout: 2000 });
await pressShortcut(page, modifier, 'KeyW');
await expect(reqTab).not.toBeVisible({ timeout: 3000 });
});
test('Close active tab customized (Shift+X)', async ({ pageWithUserData: page }) => {
// Remap closeTab to Cmd/Ctrl+Shift+X
await openKeybindingsTab(page);
const row = page.getByTestId(`keybinding-row-closeTab`);
await row.hover();
await page.getByTestId(`keybinding-edit-closeTab`).click();
// Wait for input to enter recording mode
await expect(page.getByTestId(`keybinding-input-closeTab`)).toBeVisible({ timeout: 2000 });
// Remove the old keybindings
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Shift', 'KeyX');
await closePreferencesTab(page);
await openRequest(page, collectionName, 'req-1', { persist: true });
await expect(page.locator('.request-tab').filter({ has: page.getByText('req-1', { exact: true }) })).toBeVisible({ timeout: 2000 });
await pressShortcut(page, 'Shift', 'KeyX');
await expect(page.locator('.request-tab').filter({ has: page.getByText('req-1', { exact: true }) })).not.toBeVisible({ timeout: 3000 });
});
});
// Closing a tab that has unsaved changes must surface the entity-specific
// "Unsaved changes" confirmation modal (request / folder / collection).
test.describe('SHORTCUT: Close Tab (Unsaved changes modal)', () => {
// These tests trigger close with the customized Shift+X binding; set it up
// once for the group. The top-level afterEach resets it after each test.
test.beforeEach(async ({ pageWithUserData: page }) => {
await remapKeybinding(page, 'closeTab', 'Shift', 'KeyX');
await closePreferencesTab(page);
});
test('Close active tab customized (Shift+X) shows unsaved changes modal for request', async ({ pageWithUserData: page }) => {
// Open a request and make an unsaved change (edit the URL → draft)
await openRequest(page, collectionName, 'req-1', { persist: true });
const requestTab = page.locator('.request-tab').filter({ has: page.getByText('req-1', { exact: true }) });
await expect(requestTab).toBeVisible({ timeout: 2000 });
await page.locator('#request-url .CodeMirror').click();
await page.keyboard.type('/users');
await expect(requestTab.locator('.has-changes-icon')).toBeVisible();
await pressShortcut(page, 'Shift', 'KeyX');
// The request "Unsaved changes" modal should appear
const modal = page.locator('.bruno-modal-card').filter({ has: page.getByText('Unsaved changes', { exact: true }) });
await expect(modal).toBeVisible({ timeout: 3000 });
await expect(modal).toContainText('You have unsaved changes in request');
await expect(modal).toContainText('req-1');
await expect(modal.getByRole('button', { name: 'Don\'t Save' })).toBeVisible();
await expect(modal.getByRole('button', { name: 'Cancel' })).toBeVisible();
await expect(modal.getByRole('button', { name: 'Save', exact: true })).toBeVisible();
// Cancel keeps the tab open with its draft intact
await modal.getByRole('button', { name: 'Cancel' }).click();
await expect(modal).not.toBeVisible();
await expect(requestTab.locator('.has-changes-icon')).toBeVisible();
// Trigger again and discard → the tab closes
await pressShortcut(page, 'Escape');
await pressShortcut(page, 'Shift', 'KeyX');
await expect(modal).toBeVisible({ timeout: 3000 });
await modal.getByRole('button', { name: 'Don\'t Save' }).click();
await expect(requestTab).not.toBeVisible({ timeout: 3000 });
});
test('Close active tab customized (Shift+X) shows unsaved changes modal for folder', async ({ pageWithUserData: page }) => {
// Open folder settings and make an unsaved change (add a header → draft)
await page.locator('.collection-item-name').filter({ has: page.getByText('kb-draft-folder', { exact: true }) }).dblclick();
const folderTab = page.locator('.request-tab').filter({ has: page.getByText('kb-draft-folder', { exact: true }) });
await expect(folderTab).toBeVisible({ timeout: 3000 });
const headerRow = page.locator('table').first().locator('tbody tr').first();
await headerRow.locator('.CodeMirror').first().click();
await page.keyboard.type('X-Folder-Header');
await headerRow.locator('.CodeMirror').nth(1).click();
await page.keyboard.type('folder-value');
await expect(folderTab.locator('.has-changes-icon')).toBeVisible();
await pressShortcut(page, 'Shift', 'KeyX');
// The folder "Unsaved changes" modal should appear
const modal = page.locator('.bruno-modal-card').filter({ has: page.getByText('Unsaved changes', { exact: true }) });
await expect(modal).toBeVisible({ timeout: 3000 });
await expect(modal).toContainText('folder settings');
await expect(modal).toContainText('kb-draft-folder');
await expect(modal.getByRole('button', { name: 'Don\'t Save' })).toBeVisible();
await expect(modal.getByRole('button', { name: 'Cancel' })).toBeVisible();
await expect(modal.getByRole('button', { name: 'Save', exact: true })).toBeVisible();
// Cancel keeps the tab open with its draft intact
await modal.getByRole('button', { name: 'Cancel' }).click();
await expect(modal).not.toBeVisible();
await expect(folderTab.locator('.has-changes-icon')).toBeVisible();
// Trigger again and discard → the tab closes
await pressShortcut(page, 'Escape');
await pressShortcut(page, 'Shift', 'KeyX');
await expect(modal).toBeVisible({ timeout: 3000 });
await modal.getByRole('button', { name: 'Don\'t Save' }).click();
await expect(folderTab).not.toBeVisible({ timeout: 3000 });
});
test('Close active tab customized (Shift+X) shows unsaved changes modal for collection', async ({ pageWithUserData: page }) => {
// Open collection settings and make an unsaved change (add a header → draft)
await page.getByTestId('sidebar-collection-row').filter({ has: page.getByText('kb-collection', { exact: true }) }).dblclick();
const collectionTab = page.locator('.request-tab').filter({ has: page.getByText('Collection', { exact: true }) });
await expect(collectionTab).toBeVisible({ timeout: 3000 });
await page.locator('.tab.headers').click();
const headerRow = page.locator('table').first().locator('tbody tr').first();
await headerRow.locator('.CodeMirror').first().click();
await page.keyboard.type('X-Custom-Header');
await headerRow.locator('.CodeMirror').nth(1).click();
await page.keyboard.type('custom-value');
await expect(collectionTab.locator('.has-changes-icon')).toBeVisible();
await pressShortcut(page, 'Shift', 'KeyX');
// The collection "Unsaved changes" modal should appear
const modal = page.locator('.bruno-modal-card').filter({ has: page.getByText('Unsaved changes', { exact: true }) });
await expect(modal).toBeVisible({ timeout: 3000 });
await expect(modal).toContainText('collection settings');
await expect(modal).toContainText(collectionName);
await expect(modal.getByRole('button', { name: 'Don\'t Save' })).toBeVisible();
await expect(modal.getByRole('button', { name: 'Cancel' })).toBeVisible();
await expect(modal.getByRole('button', { name: 'Save', exact: true })).toBeVisible();
// Cancel keeps the tab open with its draft intact
await modal.getByRole('button', { name: 'Cancel' }).click();
await expect(modal).not.toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).toBeVisible();
// Trigger again and discard → the tab closes
await pressShortcut(page, 'Escape');
await pressShortcut(page, 'Shift', 'KeyX');
await expect(modal).toBeVisible({ timeout: 3000 });
await modal.getByRole('button', { name: 'Don\'t Save' }).click();
await expect(collectionTab).not.toBeVisible({ timeout: 3000 });
});
});
test.describe('SHORTCUT: Close All Tabs', () => {
test('Close all tabs default (Cmd/Ctrl+Shift+W)', async ({ pageWithUserData: page }) => {
await openRequest(page, collectionName, 'req-1', { persist: true });
await openRequest(page, collectionName, 'req-2', { persist: true });
await openRequest(page, collectionName, 'req-3', { persist: true });
await page.getByTestId('runner').click();
await expect(page.locator('.request-tab').filter({ has: page.getByText('req-1', { exact: true }) })).toBeVisible({ timeout: 2000 });
await expect(page.locator('.request-tab').filter({ has: page.getByText('req-2', { exact: true }) })).toBeVisible({ timeout: 2000 });
await expect(page.locator('.request-tab').filter({ has: page.getByText('req-3', { exact: true }) })).toBeVisible({ timeout: 2000 });
await pressShortcut(page, modifier, 'Shift', 'KeyW');
await expect(page.locator('.request-tab').filter({ has: page.getByText('req-1', { exact: true }) })).not.toBeVisible({ timeout: 3000 });
await expect(page.locator('.request-tab').filter({ has: page.getByText('req-2', { exact: true }) })).not.toBeVisible({ timeout: 3000 });
await expect(page.locator('.request-tab').filter({ has: page.getByText('req-3', { exact: true }) })).not.toBeVisible({ timeout: 3000 });
});
test('Close all tabs customized (Alt+Y)', async ({ pageWithUserData: page }) => {
// Remap closeAllTabs to Alt+Y
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-closeAllTabs');
await row.hover();
await page.getByTestId('keybinding-row-closeAllTabs').click();
await expect(page.getByTestId('keybinding-input-closeAllTabs')).toBeVisible({ timeout: 2000 });
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'KeyY');
await closePreferencesTab(page);
await openRequest(page, collectionName, 'req-1', { persist: true });
await openRequest(page, collectionName, 'req-2', { persist: true });
await openRequest(page, collectionName, 'req-3', { persist: true });
await expect(page.locator('.request-tab').filter({ has: page.getByText('req-1', { exact: true }) })).toBeVisible({ timeout: 2000 });
await expect(page.locator('.request-tab').filter({ has: page.getByText('req-2', { exact: true }) })).toBeVisible({ timeout: 2000 });
await expect(page.locator('.request-tab').filter({ has: page.getByText('req-3', { exact: true }) })).toBeVisible({ timeout: 2000 });
await pressShortcut(page, 'Alt', 'KeyY');
await expect(page.locator('.request-tab').filter({ has: page.getByText('req-1', { exact: true }) })).not.toBeVisible({ timeout: 3000 });
await expect(page.locator('.request-tab').filter({ has: page.getByText('req-2', { exact: true }) })).not.toBeVisible({ timeout: 3000 });
await expect(page.locator('.request-tab').filter({ has: page.getByText('req-3', { exact: true }) })).not.toBeVisible({ timeout: 3000 });
});
});
test.describe('SHORTCUT: Save', () => {
test('Save tab customized (Cmd/Ctrl+S)', async ({ pageWithUserData: page }) => {
await page.getByTestId('sidebar-collection-row').filter({ has: page.getByText('kb-collection', { exact: true }) }).dblclick();
await expect(page.locator('.request-tab').filter({ has: page.getByText('Collection', { exact: true }) })).toBeVisible({ timeout: 2000 });
// Verify initially there is NO draft indicator (close icon is present)
const collectionTab = page.locator('.request-tab').filter({ has: page.getByText('Collection', { exact: true }) });
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
await page.locator('.tab.headers').click();
const headerTable = page.locator('table').first();
const headerRow = headerTable.locator('tbody tr').first();
const nameEditor = headerRow.locator('.CodeMirror').first();
await nameEditor.click();
await page.keyboard.type('X-Custom-Header');
const valueEditor = headerRow.locator('.CodeMirror').nth(1);
await valueEditor.click();
await page.keyboard.type('custom-value');
// Verify draft indicator appears in the tab
await expect(collectionTab.locator('.has-changes-icon')).toBeVisible();
await expect(collectionTab.locator('.close-icon')).not.toBeVisible();
// Save the changes
await pressShortcut(page, modifier, 'KeyS');
// Verify draft indicator is gone after saving
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
});
test('Save tab customized (Alt+S)', async ({ pageWithUserData: page }) => {
// Remap save to Alt+S
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-save');
await row.hover();
await page.getByTestId('keybinding-edit-save').click();
await expect(page.getByTestId('keybinding-input-save')).toBeVisible({ timeout: 2000 });
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'KeyS');
await closePreferencesTab(page);
await page.getByTestId('sidebar-collection-row').filter({ has: page.getByText('kb-collection', { exact: true }) }).dblclick();
await expect(page.locator('.request-tab').filter({ has: page.getByText('Collection', { exact: true }) })).toBeVisible({ timeout: 2000 });
// Verify initially there is NO draft indicator (close icon is present)
const collectionTab = page.locator('.request-tab').filter({ has: page.getByText('Collection', { exact: true }) });
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
await page.locator('.tab.headers').click();
const headerTable = page.locator('table').first();
const headerRow = headerTable.locator('tbody tr').first();
const nameEditor = headerRow.locator('.CodeMirror').first();
await nameEditor.click();
await page.keyboard.type('X-Custom-Header');
const valueEditor = headerRow.locator('.CodeMirror').nth(1);
await valueEditor.click();
await page.keyboard.type('custom-value');
// Verify draft indicator appears in the tab
await expect(collectionTab.locator('.has-changes-icon')).toBeVisible();
await expect(collectionTab.locator('.close-icon')).not.toBeVisible();
await page.locator('body').click({ position: { x: 1, y: 1 } });
// Save the changes
await pressShortcut(page, 'Alt', 'KeyS');
// Verify draft indicator is gone after saving
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
});
});
test.describe('SHORTCUT: Save All Tabs', () => {
test('Save all tabs default (Cmd/Ctrl+Shift+S)', async ({ pageWithUserData: page }) => {
await page.getByTestId('sidebar-collection-row').filter({ has: page.getByText('kb-collection', { exact: true }) }).dblclick();
await expect(page.locator('.request-tab').filter({ has: page.getByText('Collection', { exact: true }) })).toBeVisible({ timeout: 2000 });
// Verify initially there is NO draft indicator (close icon is present)
const collectionTab = page.locator('.request-tab').filter({ has: page.getByText('Collection', { exact: true }) });
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
await page.locator('.tab.headers').click();
const headerTable = page.locator('table').first();
const headerRow = headerTable.locator('tbody tr').first();
const nameEditor = headerRow.locator('.CodeMirror').first();
await nameEditor.click();
await page.keyboard.type('X-Custom-Header');
const valueEditor = headerRow.locator('.CodeMirror').nth(1);
await valueEditor.click();
await page.keyboard.type('custom-value');
// Verify draft indicator appears in the tab
await expect(collectionTab.locator('.has-changes-icon')).toBeVisible();
await expect(collectionTab.locator('.close-icon')).not.toBeVisible();
// Open Folder-Settings tab (create folder + double-click)
await page.locator('.collection-item-name').filter({ has: page.getByText('kb-draft-folder', { exact: true }) }).dblclick();
// Verify folder settings tab is open
const folderTab = page.locator('.request-tab').filter({ has: page.getByText('kb-draft-folder', { exact: true }) });
await expect(folderTab).toBeVisible();
await expect(folderTab.locator('.close-icon')).toBeVisible();
await expect(folderTab.locator('.has-changes-icon')).not.toBeVisible();
const folderHeaderTable = page.locator('table').first();
const folderHeaderRow = folderHeaderTable.locator('tbody tr').first();
const folderNameEditor = folderHeaderRow.locator('.CodeMirror').first();
await folderNameEditor.click();
await page.keyboard.type('X-Folder-Header');
const folderValueEditor = folderHeaderRow.locator('.CodeMirror').nth(1);
await folderValueEditor.click();
await page.keyboard.type('folder-value');
// Verify draft indicator appears in the folder tab
await expect(folderTab.locator('.has-changes-icon')).toBeVisible();
await expect(folderTab.locator('.close-icon')).not.toBeVisible();
// Save the changes
await pressShortcut(page, modifier, 'Shift', 'KeyS');
// Verify draft indicator is gone after saving
await expect(folderTab.locator('.close-icon')).toBeVisible();
await expect(folderTab.locator('.has-changes-icon')).not.toBeVisible();
// Verify draft indicator is gone after saving
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
});
test('Save all tabs customized (Alt+Shift+S)', async ({ pageWithUserData: page }) => {
// Remap saveAllTabs to Alt+Shift+S
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-saveAllTabs');
await row.hover();
await page.getByTestId('keybinding-edit-saveAllTabs').click();
await expect(page.getByTestId('keybinding-input-saveAllTabs')).toBeVisible({ timeout: 2000 });
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'Shift', 'KeyS');
await closePreferencesTab(page);
await page.getByTestId('sidebar-collection-row').filter({ has: page.getByText(collectionName, { exact: true }) }).dblclick();
await expect(page.locator('.request-tab').filter({ has: page.getByText('Collection', { exact: true }) })).toBeVisible({ timeout: 2000 });
// Verify initially there is NO draft indicator (close icon is present)
const collectionTab = page.locator('.request-tab').filter({ has: page.getByText('Collection', { exact: true }) });
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
await page.locator('.tab.headers').click();
const headerTable = page.locator('table').first();
const headerRow = headerTable.locator('tbody tr').first();
const nameEditor = headerRow.locator('.CodeMirror').first();
await nameEditor.click();
await page.keyboard.type('X-Custom-Header');
const valueEditor = headerRow.locator('.CodeMirror').nth(1);
await valueEditor.click();
await page.keyboard.type('custom-value');
// Verify draft indicator appears in the tab
await expect(collectionTab.locator('.has-changes-icon')).toBeVisible();
await expect(collectionTab.locator('.close-icon')).not.toBeVisible();
// Open Folder-Settings tab (create folder + double-click)
await page.locator('.collection-item-name').filter({ has: page.getByText('kb-draft-folder', { exact: true }) }).dblclick();
// Verify folder settings tab is open
const folderTab = page.locator('.request-tab').filter({ has: page.getByText('kb-draft-folder', { exact: true }) });
await expect(folderTab).toBeVisible();
await expect(folderTab.locator('.close-icon')).toBeVisible();
await expect(folderTab.locator('.has-changes-icon')).not.toBeVisible();
const folderHeaderTable = page.locator('table').first();
const folderHeaderRow = folderHeaderTable.locator('tbody tr').first();
const folderNameEditor = folderHeaderRow.locator('.CodeMirror').first();
await folderNameEditor.click();
await page.keyboard.type('X-Folder-Header');
const folderValueEditor = folderHeaderRow.locator('.CodeMirror').nth(1);
await folderValueEditor.click();
await page.keyboard.type('folder-value');
// Verify draft indicator appears in the folder tab
await expect(folderTab.locator('.has-changes-icon')).toBeVisible();
await expect(folderTab.locator('.close-icon')).not.toBeVisible();
// Save the changes
await pressShortcut(page, 'Alt', 'Shift', 'KeyS');
// Verify draft indicator is gone after saving
await expect(folderTab.locator('.close-icon')).toBeVisible();
await expect(folderTab.locator('.has-changes-icon')).not.toBeVisible();
// Verify draft indicator is gone after saving
await expect(collectionTab.locator('.close-icon')).toBeVisible();
await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible();
});
});
test.describe('SHORTCUT: Switch to Previous Tab', () => {
test('Switch to Previous Tab default (Cmd/Ctrl+Shift+[)', async ({ pageWithUserData: page }) => {
await openRequest(page, collectionName, 'req-4', { persist: true });
await openRequest(page, collectionName, 'req-5', { persist: true });
await openRequest(page, collectionName, 'req-6', { persist: true });
await expect(page.locator('.request-tab').filter({ has: page.getByText('req-6', { exact: true }) })).toBeVisible({ timeout: 2000 });
// req-6 is active (last opened) - press previous → req-5
await pressShortcut(page, modifier, 'Shift', 'BracketLeft');
await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 });
// Press again → req-4
await pressShortcut(page, modifier, 'Shift', 'BracketLeft');
await expect(page.locator('li.request-tab.active')).toHaveText(/req-4/, { timeout: 3000 });
});
test('Switch to Previous Tab customized (Shift+P)', async ({ pageWithUserData: page }) => {
// Remap switchToPreviousTab to Shift+P
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-switchToPreviousTab');
await row.hover();
await page.getByTestId('keybinding-edit-switchToPreviousTab').click();
await expect(page.getByTestId('keybinding-input-switchToPreviousTab')).toBeVisible({ timeout: 2000 });
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Shift', 'KeyP');
await closePreferencesTab(page);
// Reuse the same requests opened in the default test
await openRequest(page, collectionName, 'req-4', { persist: true });
await openRequest(page, collectionName, 'req-5', { persist: true });
await openRequest(page, collectionName, 'req-6', { persist: true });
await expect(page.locator('.request-tab').filter({ has: page.getByText('req-6', { exact: true }) })).toBeVisible({ timeout: 2000 });
// req-6 is active - press Shift+P → req-5
await pressShortcut(page, 'Shift', 'KeyP');
await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 });
});
});
test.describe('SHORTCUT: Switch to Next Tab', () => {
test('Switch to Next Tab default (Cmd/Ctrl+Shift+])', async ({ pageWithUserData: page }) => {
await openRequest(page, collectionName, 'req-4', { persist: true });
await openRequest(page, collectionName, 'req-5', { persist: true });
await openRequest(page, collectionName, 'req-6', { persist: true });
// Go back to req-4 to start from the left
await openRequest(page, 'kb-collection', 'req-4', { persist: true });
await expect(page.locator('li.request-tab.active')).toHaveText(/req-4/);
// req-4 is active - press next → req-5
await pressShortcut(page, modifier, 'Shift', 'BracketRight');
await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 });
// Press again → req-6
await pressShortcut(page, modifier, 'Shift', 'BracketRight');
await expect(page.locator('li.request-tab.active')).toHaveText(/req-6/, { timeout: 3000 });
});
test('Switch to Next Tab customized (Shift+N)', async ({ pageWithUserData: page }) => {
// Remap switchToNextTab to Shift+N
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-switchToNextTab');
await row.hover();
await page.getByTestId('keybinding-edit-switchToNextTab').click();
await expect(page.getByTestId('keybinding-input-switchToNextTab')).toBeVisible({ timeout: 2000 });
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Shift', 'KeyN');
await closePreferencesTab(page);
await openRequest(page, collectionName, 'req-4', { persist: true });
await openRequest(page, collectionName, 'req-5', { persist: true });
await openRequest(page, collectionName, 'req-6', { persist: true });
// Go back to req-4
await openRequest(page, 'kb-collection', 'req-4', { persist: true });
await expect(page.locator('li.request-tab.active')).toHaveText(/req-4/);
// req-4 is active - press Shift+N → req-5
await pressShortcut(page, 'Shift', 'KeyN');
await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 });
});
});
test.describe('SHORTCUT: Move Tab Left', () => {
test('Move Tab Left default (Cmd/Ctrl+[)', async ({ pageWithUserData: page }) => {
await openRequest(page, collectionName, 'req-7', { persist: true });
await openRequest(page, collectionName, 'req-8', { persist: true });
await openRequest(page, collectionName, 'req-9', { persist: true });
// req-9 is active and last
const tabs = page.locator('.request-tab');
const totalTabs = await tabs.count();
await expect(tabs.nth(totalTabs - 1)).toHaveText(/req-9/);
// Press Cmd/Ctrl+[ → req-9 moves left, req-8 becomes last
await pressShortcut(page, modifier, 'BracketLeft');
await expect(tabs.nth(totalTabs - 1)).toHaveText(/req-8/, { timeout: 3000 });
await expect(tabs.nth(totalTabs - 2)).toHaveText(/req-9/);
// Press again → req-9 moves one more position left
await pressShortcut(page, modifier, 'BracketLeft');
await expect(tabs.nth(totalTabs - 3)).toHaveText(/req-9/, { timeout: 3000 });
});
test('Move Tab Left customized (Alt+L)', async ({ pageWithUserData: page }) => {
// Remap moveTabLeft to Alt+L
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-moveTabLeft');
await row.hover();
await page.getByTestId('keybinding-edit-moveTabLeft').click();
await expect(page.getByTestId('keybinding-input-moveTabLeft')).toBeVisible({ timeout: 2000 });
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'KeyL');
await closePreferencesTab(page);
await openRequest(page, 'kb-collection', 'req-7', { persist: true });
await openRequest(page, 'kb-collection', 'req-8', { persist: true });
await openRequest(page, 'kb-collection', 'req-9', { persist: true });
// req-9 is active
const tabs = page.locator('.request-tab');
// Press Alt+L → req-9 moves left, req-8 becomes last
await pressShortcut(page, 'Alt', 'KeyL');
await pressShortcut(page, 'Alt', 'KeyL');
await pressShortcut(page, 'Alt', 'KeyL');
await expect(tabs.nth(0)).toHaveText(/req-9/);
});
});
test.describe('SHORTCUT: Move Tab Right', () => {
test('Move Tab Right default (Cmd/Ctrl+])', async ({ pageWithUserData: page }) => {
await openRequest(page, collectionName, 'req-6', { persist: true });
await openRequest(page, collectionName, 'req-7', { persist: true });
await openRequest(page, collectionName, 'req-8', { persist: true });
await openRequest(page, collectionName, 'req-9', { persist: true });
// Move req-9 to first position first
await pressShortcut(page, modifier, 'BracketLeft');
await pressShortcut(page, modifier, 'BracketLeft');
await pressShortcut(page, modifier, 'BracketLeft');
await expect(page.locator('li.request-tab.active')).toHaveText(/req-9/);
const startIndex = await getTabIndex(page, 'req-9');
expect(startIndex).toBeGreaterThanOrEqual(0);
await pressShortcut(page, modifier, 'BracketRight');
const indexAfterOneMove = await getTabIndex(page, 'req-9');
expect(indexAfterOneMove).toBeGreaterThanOrEqual(startIndex);
await pressShortcut(page, modifier, 'BracketRight');
const indexAfterTwoMoves = await getTabIndex(page, 'req-9');
expect(indexAfterTwoMoves).toBeGreaterThanOrEqual(indexAfterOneMove);
await pressShortcut(page, modifier, 'BracketRight');
const indexAfterThreeMoves = await getTabIndex(page, 'req-9');
expect(indexAfterThreeMoves).toBeGreaterThanOrEqual(indexAfterTwoMoves);
});
test('Move Tab Right customized (Alt+R)', async ({ pageWithUserData: page }) => {
// Remap moveTabRight to Alt+R
await openKeybindingsTab(page);
const row = page.getByTestId('keybinding-row-moveTabRight');
await row.hover();
await page.getByTestId('keybinding-edit-moveTabRight').click();
await expect(page.getByTestId('keybinding-input-moveTabRight')).toBeVisible({ timeout: 2000 });
await page.keyboard.press('Backspace');
await pressShortcut(page, 'Alt', 'KeyR');
await closePreferencesTab(page);
await openRequest(page, collectionName, 'req-6', { persist: true });
await openRequest(page, collectionName, 'req-7', { persist: true });
await openRequest(page, collectionName, 'req-8', { persist: true });
await openRequest(page, collectionName, 'req-9', { persist: true });
const req7Tab = page.locator('.request-tab').filter({ has: page.getByText('req-7', { exact: true }) }).first();
await req7Tab.click();
await expect(req7Tab).toHaveClass(/active/);
const startIndex = await getTabIndex(page, 'req-7');
expect(startIndex).toBeGreaterThanOrEqual(0);
// Press Alt+L → req-9 moves right, req-8 becomes last
await pressShortcut(page, 'Alt', 'KeyR');
const indexAfterOneMove = await getTabIndex(page, 'req-7');
expect(indexAfterOneMove).toBeGreaterThan(startIndex);
await pressShortcut(page, 'Alt', 'KeyR');
const indexAfterTwoMoves = await getTabIndex(page, 'req-7');
expect(indexAfterTwoMoves).toBeGreaterThanOrEqual(indexAfterOneMove);
await pressShortcut(page, 'Alt', 'KeyR');
const indexAfterThreeMoves = await getTabIndex(page, 'req-7');
expect(indexAfterThreeMoves).toBeGreaterThanOrEqual(indexAfterTwoMoves);
});
});
test.describe('SHORTCUT: Switch to Tab at Position', () => {
test('Switch to Tab at Position default (Cmd/Ctrl+1-8)', async ({ pageWithUserData: page }) => {
// Open req-1..req-9 as persisted tabs (persist: true keeps each tab open).
for (let i = 1; i <= 9; i++) {
await openRequest(page, 'kb-collection', `req-${i}`, { persist: true });
}
// Cmd/Ctrl+<n> activates the tab at position n.
for (let pos = 1; pos <= 8; pos++) {
await pressShortcut(page, modifier, `${pos}`);
await expect(page.locator('li.request-tab.active')).toHaveText(new RegExp(`req-${pos}`), { timeout: 3000 });
}
});
});
test.describe('SHORTCUT: Reopen Last Closed Tab', () => {
test('Reopen Last Closed Tab default (Cmd/Ctrl+Shift+T)', async ({ pageWithUserData: page }) => {
await openRequest(page, collectionName, 'req-2', { persist: true });
await openRequest(page, collectionName, 'req-1', { persist: true });
const req1Tab = page.locator('.request-tab').filter({ has: page.getByText('req-1', { exact: true }) }).first();
await req1Tab.click();
await expect(req1Tab).toHaveClass(/active/);
await closeTabByName(page, 'req-1');
await reopenClosedTab(page, async () => {
await pressShortcut(page, modifier, 'Shift', 'KeyT');
}, 'req-1');
});
test('Reopen Last Closed Tab customized (Cmd/Ctrl+Shift+T)', async ({ pageWithUserData: page }) => {
await openRequest(page, collectionName, 'req-2', { persist: true });
// Open Collection-Settings tab (double-click collection name)
await page.getByTestId('sidebar-collection-row').filter({ has: page.getByText('kb-collection', { exact: true }) }).dblclick();
await expect(page.locator('.request-tab').filter({ has: page.getByText('Collection', { exact: true }) })).toBeVisible({ timeout: 2000 });
// Open Runner tab
await page.getByTestId('runner').click();
await expect(page.locator('.request-tab').filter({ has: page.getByText('Runner', { exact: true }) })).toBeVisible({ timeout: 2000 });
// Open Variables tab
await page.getByTestId('more-actions').click();
await page.getByTestId('more-actions-variables').click();
await expect(page.locator('.request-tab').filter({ has: page.getByText('Variables', { exact: true }) })).toBeVisible({ timeout: 2000 });
// Open Folder-Settings tab (create folder + double-click)
await page.locator('.collection-item-name').filter({ has: page.getByText('kb-folder', { exact: true }) }).dblclick();
// Close in order: kb-folder (first closed) → Collection → Variables → Runner (last closed)
await closeTabByName(page, 'kb-folder');
await closeTabByName(page, 'Collection');
await closeTabByName(page, 'Variables');
await closeTabByName(page, 'Runner');
// Reopen LIFO: Runner was closed last → reopens first
await reopenClosedTab(page, async () => {
await pressShortcut(page, modifier, 'Shift', 'KeyT');
}, 'Runner');
await reopenClosedTab(page, async () => {
await pressShortcut(page, modifier, 'Shift', 'KeyT');
}, /variables/i);
await reopenClosedTab(page, async () => {
await pressShortcut(page, modifier, 'Shift', 'KeyT');
}, 'Collection');
await reopenClosedTab(page, async () => {
await pressShortcut(page, modifier, 'Shift', 'KeyT');
}, 'kb-folder');
});
test('Reopen Last Closed Tab customized (Alt+Z)', async ({ pageWithUserData: page }) => {
await remapKeybinding(page, 'reopenLastClosedTab', 'Alt', 'KeyZ');
await openRequest(page, collectionName, 'req-2', { persist: true });
await openRequest(page, collectionName, 'req-1', { persist: true });
const req1Tab = page.locator('.request-tab').filter({ has: page.getByText('req-1', { exact: true }) }).first();
await req1Tab.click();
await expect(req1Tab).toHaveClass(/active/);
await closeTabByName(page, 'req-1');
await reopenClosedTab(page, async () => {
await pressShortcut(page, 'Alt', 'KeyZ');
}, 'req-1');
});
});
});
});

View File

@@ -48,9 +48,15 @@ const closeAllCollections = async (page) => {
for (let i = 0; i < numberOfCollections; i++) {
const firstCollection = page.locator('[data-testid="collections"] .collection-name').first();
await firstCollection.hover();
await firstCollection.locator('.collection-actions .icon').click();
await page.locator('.dropdown-item').getByText('Remove').click();
await firstCollection.scrollIntoViewIfNeeded();
const removeMenuItem = page.locator('.dropdown-item').getByText('Remove');
await expect(async () => {
await firstCollection.hover();
await firstCollection.locator('.collection-actions .icon').click({ force: true });
await expect(removeMenuItem).toBeVisible({ timeout: 2000 });
}).toPass({ timeout: 15000 });
await removeMenuItem.click();
// Wait for modal to appear - could be either regular remove or drafts confirmation
const removeModal = page.locator('.bruno-modal').filter({ hasText: 'Remove Collection' });
@@ -150,10 +156,18 @@ const createCollection = async (
await expect(nameInput).toHaveValue(collectionName, { timeout: 2000 });
if (format) {
await createCollectionModal.locator('.advanced-options .btn-advanced').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Show File Format' }).click();
const advancedBtn = createCollectionModal.locator('.advanced-options .btn-advanced');
const showFileFormatToggle = page.getByTestId('show-file-format-toggle');
const formatSelect = createCollectionModal.locator('#format');
await formatSelect.waitFor({ state: 'visible', timeout: 5000 });
await expect(async () => {
if (!(await formatSelect.isVisible())) {
await advancedBtn.click();
await showFileFormatToggle.click({ timeout: 2000 });
}
await expect(formatSelect).toBeVisible({ timeout: 2000 });
}).toPass({ timeout: 15000 });
await formatSelect.selectOption(format);
}