diff --git a/tests/shortcuts/bound-actions-developer-tools.spec.ts b/tests/shortcuts/bound-actions-developer-tools.spec.ts new file mode 100644 index 00000000000..f6793c453ea --- /dev/null +++ b/tests/shortcuts/bound-actions-developer-tools.spec.ts @@ -0,0 +1,243 @@ +import { test, expect, Page } from '../../playwright'; +import { + createCollection, + createRequest, + openRequest as openRequestBase, + closeAllCollections, + createFolder, + openCollection, + selectRequestPaneTab +} from '../utils/page'; + +const modifier = process.platform === 'darwin' ? 'Meta' : 'Control'; +const collectionName = 'kb-collection'; +const baseRequests = ['req-1', 'req-2', 'req-3', 'req-4', 'req-5', 'req-6', 'req-7', 'req-8', 'req-9']; + +const setupBoundActionsData = async (page: Page, createTmpDir: (prefix: string) => Promise) => { + 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({ hasText: requestName }).first(); + return (await request.count()) > 0; +}; + +const openRequest = async (...args: Parameters) => { + const [page, targetCollectionName, requestName] = args; + if ( + targetCollectionName === collectionName + && baseRequests.includes(requestName) + && !(await checkIfRequestExists(page, requestName)) + ) { + await createRequest(page, requestName, targetCollectionName); + } + + return openRequestBase(...args); +}; + +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({ hasText: 'Keybindings' })).toBeVisible(); +}; + +/** + * Close the Preferences tab by clicking its close button. + * Using the close button avoids depending on any keyboard shortcut that may + * have just been reconfigured. + */ +const closePreferencesTab = async (page: Page) => { + const prefTab = page.locator('.request-tab').filter({ hasText: 'Preferences' }); + await prefTab.hover(); + await prefTab.getByTestId('request-tab-close-icon').click({ force: true }); + await expect(prefTab).not.toBeVisible({ timeout: 8000 }); +}; + +const closeTabByName = async (page: any, name: string | RegExp) => { + const tab = page.locator('.request-tab').filter({ hasText: name }); + await tab.click(); + await tab.hover(); + await tab.getByTestId('request-tab-close-icon').click({ force: true }); + await expect(tab).not.toBeVisible({ timeout: 2000 }); +}; + +const openFolderSettingsTab = async (page: Page, folderName: string) => { + await openCollection(page, collectionName); + const folderRow = page.locator('.collection-item-name').filter({ hasText: folderName }).first(); + await expect(folderRow).toBeVisible({ timeout: 5000 }); + await folderRow.dblclick(); + await expect(page.locator('.request-tab').filter({ hasText: folderName })).toBeVisible({ timeout: 3000 }); +}; + +const reopenClosedTab = async (page: Page, shortcut: () => Promise, expectedTabName: string | RegExp) => { + for (let attempt = 0; attempt < 3; attempt++) { + await page.locator('.request-tab').first().click(); + await page.waitForTimeout(150); + await shortcut(); + const reopenedTab = page.locator('.request-tab').filter({ hasText: expectedTabName }); + if ((await reopenedTab.count()) > 0) { + await expect(reopenedTab).toBeVisible({ timeout: 3000 }); + return; + } + await page.waitForTimeout(200); + } + + await expect(page.locator('.request-tab').filter({ hasText: expectedTabName })).toBeVisible({ timeout: 5000 }); +}; + +const remapKeybinding = async ( + page: Page, + action: string, + pressShortcut: () => Promise +) => { + await openKeybindingsTab(page); + const row = page.getByTestId(`keybinding-row-${action}`); + await expect(row).toBeVisible({ timeout: 5000 }); + 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({ timeout: 5000 }); + + await page.keyboard.press('Backspace'); + await pressShortcut(); + await closePreferencesTab(page); +}; + +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; +}; + +// ─── Tests ──── + +test.describe('Shortcut Keys - BOUND_ACTIONS', () => { + test.beforeEach(async ({ page, createTmpDir }) => { + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 5000 }); + await setupBoundActionsData(page, createTmpDir); + }); + + test.afterAll(async ({ page }) => { + await closeAllCollections(page); + }); + + test.describe('DEVELOPER TOOLS', () => { + test.describe('SHORTCUT: Open Terminal', () => { + test('default Cmd/Ctrl+T opens terminal', async ({ page, createTmpDir }) => { + // Open Collection-Settings tab (double-click collection name) + await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).click(); + await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 }); + + // Press Cmd/Ctrl+T to open terminal at workspace level + await page.keyboard.press(`${modifier}+KeyT`); + + // Verify terminal session is visible using data-testid + const collectionTerminalSession = page.getByTestId('session-list-0'); + await expect(collectionTerminalSession).toBeVisible({ timeout: 2000 }); + + 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({ hasText: 'kb-terminal-folder' }).dblclick(); + await expect(page.locator('.request-tab').filter({ hasText: 'kb-terminal-folder' })).toBeVisible({ timeout: 2000 }); + + await page.keyboard.press(`${modifier}+KeyT`); + const folderTerminalSession = page.getByTestId('session-list-1'); + await expect(folderTerminalSession).toBeVisible({ timeout: 2000 }); + + // Verify the terminal session name is the workspace name (default_workspace) + const folderSessionName = folderTerminalSession; + await expect(folderSessionName).toContainText('kb-terminal-folder'); + + // Close all sessions with terminal tab + await page.getByTestId('session-close-1').click(); + await page.waitForTimeout(1000); + await page.getByTestId('session-close-0').click(); + await expect(page.getByTestId('session-close-0')).not.toBeVisible({ timeout: 3000 }); + await page.getByTitle('Close console').click(); + }); + + test('customized Alt+T opens terminal', async ({ page, createTmpDir }) => { + // 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({ timeout: 2000 }); + + await page.keyboard.down('Backspace'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyT'); + await page.keyboard.up('KeyT'); + await page.keyboard.up('Alt'); + + await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).click(); + await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 }); + + // Press Cmd/Ctrl+T to open terminal at workspace level + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyT'); + await page.keyboard.up('KeyT'); + await page.keyboard.up('Alt'); + await page.waitForTimeout(500); + + // Verify terminal session is visible using data-testid + const collectionTerminalSession = page.getByTestId('session-list-0'); + await expect(collectionTerminalSession).toBeVisible({ timeout: 2000 }); + + const collectionSession = collectionTerminalSession; + await expect(collectionSession).toContainText('kb-collection'); + + // Open folder settings + await page.locator('.collection-item-name').filter({ hasText: 'kb-terminal-folder' }).dblclick(); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyT'); + await page.keyboard.up('KeyT'); + await page.keyboard.up('Alt'); + const folderTerminalSession = page.getByTestId('session-list-1'); + await expect(folderTerminalSession).toBeVisible({ timeout: 2000 }); + + // Verify the terminal session name is the workspace name (default_workspace) + const folderSessionName = folderTerminalSession; + await expect(folderSessionName).toContainText('kb-terminal-folder'); + + // Close all sessions with terminal tab + await page.getByTestId('session-close-1').click(); + await page.waitForTimeout(1000); + await page.getByTestId('session-close-0').click(); + await expect(page.getByTestId('session-close-0')).not.toBeVisible({ timeout: 3000 }); + await page.getByTitle('Close console').click(); + }); + }); + }); +}); diff --git a/tests/shortcuts/bound-actions-layout.spec.ts b/tests/shortcuts/bound-actions-layout.spec.ts new file mode 100644 index 00000000000..e67da022136 --- /dev/null +++ b/tests/shortcuts/bound-actions-layout.spec.ts @@ -0,0 +1,283 @@ +import { test, expect, Page } from '../../playwright'; +import { + createCollection, + createRequest, + openRequest as openRequestBase, + closeAllCollections, + createFolder, + openCollection, + selectRequestPaneTab +} from '../utils/page'; + +const modifier = process.platform === 'darwin' ? 'Meta' : 'Control'; +const collectionName = 'kb-collection'; +const baseRequests = ['req-1', 'req-2', 'req-3', 'req-4', 'req-5', 'req-6', 'req-7', 'req-8', 'req-9']; + +const setupBoundActionsData = async (page: Page, createTmpDir: (prefix: string) => Promise) => { + 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({ hasText: requestName }).first(); + return (await request.count()) > 0; +}; + +const openRequest = async (...args: Parameters) => { + const [page, targetCollectionName, requestName] = args; + if ( + targetCollectionName === collectionName + && baseRequests.includes(requestName) + && !(await checkIfRequestExists(page, requestName)) + ) { + await createRequest(page, requestName, targetCollectionName); + } + + return openRequestBase(...args); +}; + +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({ hasText: 'Keybindings' })).toBeVisible(); +}; + +/** + * Close the Preferences tab by clicking its close button. + * Using the close button avoids depending on any keyboard shortcut that may + * have just been reconfigured. + */ +const closePreferencesTab = async (page: Page) => { + const prefTab = page.locator('.request-tab').filter({ hasText: 'Preferences' }); + await prefTab.hover(); + await prefTab.getByTestId('request-tab-close-icon').click({ force: true }); + await expect(prefTab).not.toBeVisible({ timeout: 8000 }); +}; + +const closeTabByName = async (page: any, name: string | RegExp) => { + const tab = page.locator('.request-tab').filter({ hasText: name }); + await tab.click(); + await tab.hover(); + await tab.getByTestId('request-tab-close-icon').click({ force: true }); + await expect(tab).not.toBeVisible({ timeout: 2000 }); +}; + +const openFolderSettingsTab = async (page: Page, folderName: string) => { + await openCollection(page, collectionName); + const folderRow = page.locator('.collection-item-name').filter({ hasText: folderName }).first(); + await expect(folderRow).toBeVisible({ timeout: 5000 }); + await folderRow.dblclick(); + await expect(page.locator('.request-tab').filter({ hasText: folderName })).toBeVisible({ timeout: 3000 }); +}; + +const reopenClosedTab = async (page: Page, shortcut: () => Promise, expectedTabName: string | RegExp) => { + for (let attempt = 0; attempt < 3; attempt++) { + await page.locator('.request-tab').first().click(); + await page.waitForTimeout(150); + await shortcut(); + const reopenedTab = page.locator('.request-tab').filter({ hasText: expectedTabName }); + if ((await reopenedTab.count()) > 0) { + await expect(reopenedTab).toBeVisible({ timeout: 3000 }); + return; + } + await page.waitForTimeout(200); + } + + await expect(page.locator('.request-tab').filter({ hasText: expectedTabName })).toBeVisible({ timeout: 5000 }); +}; + +const remapKeybinding = async ( + page: Page, + action: string, + pressShortcut: () => Promise +) => { + await openKeybindingsTab(page); + const row = page.getByTestId(`keybinding-row-${action}`); + await expect(row).toBeVisible({ timeout: 5000 }); + 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({ timeout: 5000 }); + + await page.keyboard.press('Backspace'); + await pressShortcut(); + await closePreferencesTab(page); +}; + +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; +}; + +// ─── Tests ──── + +test.describe('Shortcut Keys - BOUND_ACTIONS', () => { + test.beforeEach(async ({ page, createTmpDir }) => { + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 5000 }); + await setupBoundActionsData(page, createTmpDir); + }); + + test.afterAll(async ({ page }) => { + await closeAllCollections(page); + }); + + test.describe('LAYOUT', () => { + test.describe('SHORTCUT: Change Layout', () => { + test('default Cmd/Ctrl+J change layout orientation', async ({ page, createTmpDir }) => { + await openRequest(page, 'kb-collection', 'req-5', { persist: true }); + + // Press Cmd/Ctrl+J to change layout + await page.keyboard.press(`${modifier}+KeyJ`); + + await expect( + page.getByTestId('response-layout-toggle-btn') + ).toHaveAttribute('title', 'Switch to horizontal layout', { timeout: 2000 }); + + // Press Cmd/Ctrl+J to change layout + await page.keyboard.press(`${modifier}+KeyJ`); + + await expect( + page.getByTestId('response-layout-toggle-btn') + ).toHaveAttribute('title', 'Switch to vertical layout', { timeout: 2000 }); + + // Press Cmd/Ctrl+J to change layout + await page.keyboard.press(`${modifier}+KeyJ`); + + await expect( + page.getByTestId('response-layout-toggle-btn') + ).toHaveAttribute('title', 'Switch to horizontal layout', { timeout: 2000 }); + }); + + test('customized Alt+Shift+Y change layout orientation', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + // Remap changeLayout to Alt+D + 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({ timeout: 2000 }); + + await page.keyboard.down('Backspace'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Shift'); + await page.keyboard.up('Alt'); + + await openRequest(page, 'kb-collection', 'req-5', { persist: true }); + + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Shift'); + await page.keyboard.up('Alt'); + + await expect( + page.getByTestId('response-layout-toggle-btn') + ).toHaveAttribute('title', 'Switch to vertical layout', { timeout: 2000 }); + + // Press Cmd/Ctrl+J to change layout + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Shift'); + await page.keyboard.up('Alt'); + + await expect( + page.getByTestId('response-layout-toggle-btn') + ).toHaveAttribute('title', 'Switch to horizontal layout', { timeout: 2000 }); + + // Press Cmd/Ctrl+J to change layout + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Shift'); + await page.keyboard.up('Alt'); + + await expect( + page.getByTestId('response-layout-toggle-btn') + ).toHaveAttribute('title', 'Switch to vertical layout', { timeout: 2000 }); + }); + }); + + test.describe('SHORTCUT: Open Preferences', () => { + test('default Cmd/Ctrl+, open preferences', async ({ page }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + // Press Cmd/Ctrl+J to change layout + await page.keyboard.down(modifier); + await page.keyboard.down('Comma'); + await page.keyboard.up('Comma'); + await page.keyboard.up(modifier); + + await expect(page.locator('.request-tab').filter({ hasText: 'Preferences' })).toBeVisible({ timeout: 3000 }); + }); + + test('customized Cmd/Ctrl+P open preferences', async ({ 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({ timeout: 2000 }); + + await page.keyboard.down('Backspace'); + + await page.keyboard.down(modifier); + await page.keyboard.down('KeyP'); + await page.keyboard.up('KeyP'); + await page.keyboard.up(modifier); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + // Press Cmd/Ctrl+J to change layout + await page.keyboard.down(modifier); + await page.keyboard.down('KeyP'); + await page.keyboard.up('KeyP'); + await page.keyboard.up(modifier); + + await expect(page.locator('.request-tab').filter({ hasText: 'Preferences' })).toBeVisible({ timeout: 3000 }); + }); + }); + }); +}); diff --git a/tests/shortcuts/bound-actions-requests.spec.ts b/tests/shortcuts/bound-actions-requests.spec.ts new file mode 100644 index 00000000000..5bb982bc76b --- /dev/null +++ b/tests/shortcuts/bound-actions-requests.spec.ts @@ -0,0 +1,419 @@ +import { test, expect, Page } from '../../playwright'; +import { + createCollection, + createRequest, + openRequest as openRequestBase, + closeAllCollections, + createFolder, + openCollection, + selectRequestPaneTab +} from '../utils/page'; + +const modifier = process.platform === 'darwin' ? 'Meta' : 'Control'; +const collectionName = 'kb-collection'; +const baseRequests = ['req-1', 'req-2', 'req-3', 'req-4', 'req-5', 'req-6', 'req-7', 'req-8', 'req-9']; + +const setupBoundActionsData = async (page: Page, createTmpDir: (prefix: string) => Promise) => { + 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({ hasText: requestName }).first(); + return (await request.count()) > 0; +}; + +const openRequest = async (...args: Parameters) => { + const [page, targetCollectionName, requestName] = args; + if ( + targetCollectionName === collectionName + && baseRequests.includes(requestName) + && !(await checkIfRequestExists(page, requestName)) + ) { + await createRequest(page, requestName, targetCollectionName); + } + + return openRequestBase(...args); +}; + +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({ hasText: 'Keybindings' })).toBeVisible(); +}; + +/** + * Close the Preferences tab by clicking its close button. + * Using the close button avoids depending on any keyboard shortcut that may + * have just been reconfigured. + */ +const closePreferencesTab = async (page: Page) => { + const prefTab = page.locator('.request-tab').filter({ hasText: 'Preferences' }); + await prefTab.hover(); + await prefTab.getByTestId('request-tab-close-icon').click({ force: true }); + await expect(prefTab).not.toBeVisible({ timeout: 8000 }); +}; + +const closeTabByName = async (page: any, name: string | RegExp) => { + const tab = page.locator('.request-tab').filter({ hasText: name }); + await tab.click(); + await tab.hover(); + await tab.getByTestId('request-tab-close-icon').click({ force: true }); + await expect(tab).not.toBeVisible({ timeout: 2000 }); +}; + +const openFolderSettingsTab = async (page: Page, folderName: string) => { + await openCollection(page, collectionName); + const folderRow = page.locator('.collection-item-name').filter({ hasText: folderName }).first(); + await expect(folderRow).toBeVisible({ timeout: 5000 }); + await folderRow.dblclick(); + await expect(page.locator('.request-tab').filter({ hasText: folderName })).toBeVisible({ timeout: 3000 }); +}; + +const reopenClosedTab = async (page: Page, shortcut: () => Promise, expectedTabName: string | RegExp) => { + for (let attempt = 0; attempt < 3; attempt++) { + await page.locator('.request-tab').first().click(); + await page.waitForTimeout(150); + await shortcut(); + const reopenedTab = page.locator('.request-tab').filter({ hasText: expectedTabName }); + if ((await reopenedTab.count()) > 0) { + await expect(reopenedTab).toBeVisible({ timeout: 3000 }); + return; + } + await page.waitForTimeout(200); + } + + await expect(page.locator('.request-tab').filter({ hasText: expectedTabName })).toBeVisible({ timeout: 5000 }); +}; + +const remapKeybinding = async ( + page: Page, + action: string, + pressShortcut: () => Promise +) => { + await openKeybindingsTab(page); + const row = page.getByTestId(`keybinding-row-${action}`); + await expect(row).toBeVisible({ timeout: 5000 }); + 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({ timeout: 5000 }); + + await page.keyboard.press('Backspace'); + await pressShortcut(); + await closePreferencesTab(page); +}; + +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; +}; + +// ─── Tests ──── + +test.describe('Shortcut Keys - BOUND_ACTIONS', () => { + test.beforeEach(async ({ page, createTmpDir }) => { + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 5000 }); + await setupBoundActionsData(page, createTmpDir); + }); + + test.afterAll(async ({ page }) => { + await closeAllCollections(page); + }); + + test.describe('REQUESTS', () => { + test.describe('SHORTCUT: Send Request from CodeEditor (Cmd/Ctrl+Enter)', () => { + test('sends request when cursor is in JSON body editor', async ({ page }) => { + // Close existing tabs + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + // 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"', { timeout: 5000 }); + + // Cursor is still in the body CodeMirror — press Cmd/Ctrl+Enter to send + await page.keyboard.press(`${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 ({ page }) => { + // Close existing tabs + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + 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"', { timeout: 5000 }); + + // First send to populate response + await page.keyboard.press(`${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 page.keyboard.press(`${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 ({ page }) => { + // Close existing tabs + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + 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 page.keyboard.press(`${modifier}+Enter`); + + // Verify a 200 response came back + await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 15000 }); + }); + }); + + test.describe('SHORTCUT: Send Request from CodeEditor (customized Shift+Enter)', () => { + test('customized Shift+Enter sends request when cursor is in JSON body editor', async ({ page }) => { + // Close existing tabs + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + // 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({ timeout: 2000 }); + + await page.keyboard.down('Backspace'); + + await page.keyboard.down('Shift'); + await page.keyboard.down('Enter'); + await page.keyboard.up('Enter'); + await page.keyboard.up('Shift'); + + // await closePreferencesTab(page); + // 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"', { timeout: 5000 }); + + // Cursor is still in the body CodeMirror — press Shift+Enter (customized) to send + await page.keyboard.press('Shift+Enter'); + + await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 15000 }); + + // Reset Default + await openKeybindingsTab(page); + await page.getByTestId('reset-all-keybindings-btn').click({ timeout: 2000 }); + }); + + test('customized Shift+Enter sends request when cursor is in response body editor', async ({ page }) => { + // Close existing tabs + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + // 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({ timeout: 2000 }); + + await page.keyboard.down('Backspace'); + + await page.keyboard.down('Shift'); + await page.keyboard.down('Enter'); + await page.keyboard.up('Enter'); + await page.keyboard.up('Shift'); + + // await closePreferencesTab(page); + 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"', { timeout: 5000 }); + + // First send with Shift+Enter to populate response + await page.keyboard.press('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 page.keyboard.press('Shift+Enter'); + + await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 15000 }); + + // Reset Default + await openKeybindingsTab(page); + await page.getByTestId('reset-all-keybindings-btn').click({ timeout: 2000 }); + }); + + test('customized Shift+Enter sends request when cursor is in pre-request Vars value editor', async ({ page }) => { + // Close existing tabs + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + // 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({ timeout: 2000 }); + + await page.keyboard.down('Backspace'); + + await page.keyboard.down('Shift'); + await page.keyboard.down('Enter'); + await page.keyboard.up('Enter'); + await page.keyboard.up('Shift'); + + // await closePreferencesTab(page); + 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 page.keyboard.press('Shift+Enter'); + + await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 15000 }); + + // Reset Default + await openKeybindingsTab(page); + await page.getByTestId('reset-all-keybindings-btn').click({ timeout: 2000 }); + }); + }); + }); +}); diff --git a/tests/shortcuts/bound-actions-search.spec.ts b/tests/shortcuts/bound-actions-search.spec.ts new file mode 100644 index 00000000000..cb5360b29c6 --- /dev/null +++ b/tests/shortcuts/bound-actions-search.spec.ts @@ -0,0 +1,196 @@ +import { test, expect, Page } from '../../playwright'; +import { + createCollection, + createRequest, + openRequest as openRequestBase, + closeAllCollections, + createFolder, + openCollection, + selectRequestPaneTab +} from '../utils/page'; + +const modifier = process.platform === 'darwin' ? 'Meta' : 'Control'; +const collectionName = 'kb-collection'; +const baseRequests = ['req-1', 'req-2', 'req-3', 'req-4', 'req-5', 'req-6', 'req-7', 'req-8', 'req-9']; + +const setupBoundActionsData = async (page: Page, createTmpDir: (prefix: string) => Promise) => { + 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({ hasText: requestName }).first(); + return (await request.count()) > 0; +}; + +const openRequest = async (...args: Parameters) => { + const [page, targetCollectionName, requestName] = args; + if ( + targetCollectionName === collectionName + && baseRequests.includes(requestName) + && !(await checkIfRequestExists(page, requestName)) + ) { + await createRequest(page, requestName, targetCollectionName); + } + + return openRequestBase(...args); +}; + +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({ hasText: 'Keybindings' })).toBeVisible(); +}; + +/** + * Close the Preferences tab by clicking its close button. + * Using the close button avoids depending on any keyboard shortcut that may + * have just been reconfigured. + */ +const closePreferencesTab = async (page: Page) => { + const prefTab = page.locator('.request-tab').filter({ hasText: 'Preferences' }); + await prefTab.hover(); + await prefTab.getByTestId('request-tab-close-icon').click({ force: true }); + await expect(prefTab).not.toBeVisible({ timeout: 8000 }); +}; + +const closeTabByName = async (page: any, name: string | RegExp) => { + const tab = page.locator('.request-tab').filter({ hasText: name }); + await tab.click(); + await tab.hover(); + await tab.getByTestId('request-tab-close-icon').click({ force: true }); + await expect(tab).not.toBeVisible({ timeout: 2000 }); +}; + +const openFolderSettingsTab = async (page: Page, folderName: string) => { + await openCollection(page, collectionName); + const folderRow = page.locator('.collection-item-name').filter({ hasText: folderName }).first(); + await expect(folderRow).toBeVisible({ timeout: 5000 }); + await folderRow.dblclick(); + await expect(page.locator('.request-tab').filter({ hasText: folderName })).toBeVisible({ timeout: 3000 }); +}; + +const reopenClosedTab = async (page: Page, shortcut: () => Promise, expectedTabName: string | RegExp) => { + for (let attempt = 0; attempt < 3; attempt++) { + await page.locator('.request-tab').first().click(); + await page.waitForTimeout(150); + await shortcut(); + const reopenedTab = page.locator('.request-tab').filter({ hasText: expectedTabName }); + if ((await reopenedTab.count()) > 0) { + await expect(reopenedTab).toBeVisible({ timeout: 3000 }); + return; + } + await page.waitForTimeout(200); + } + + await expect(page.locator('.request-tab').filter({ hasText: expectedTabName })).toBeVisible({ timeout: 5000 }); +}; + +const remapKeybinding = async ( + page: Page, + action: string, + pressShortcut: () => Promise +) => { + await openKeybindingsTab(page); + const row = page.getByTestId(`keybinding-row-${action}`); + await expect(row).toBeVisible({ timeout: 5000 }); + 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({ timeout: 5000 }); + + await page.keyboard.press('Backspace'); + await pressShortcut(); + await closePreferencesTab(page); +}; + +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; +}; + +// ─── Tests ──── + +test.describe('Shortcut Keys - BOUND_ACTIONS', () => { + test.beforeEach(async ({ page, createTmpDir }) => { + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 5000 }); + await setupBoundActionsData(page, createTmpDir); + }); + + test.afterAll(async ({ page }) => { + await closeAllCollections(page); + }); + + test.describe('SEARCH', () => { + test.describe('SHORTCUT: Global Search', () => { + test('default Cmd/Ctrl+K Global Search Modal', async ({ page, createTmpDir }) => { + // Press Cmd/Ctrl+K to global search modal + await page.keyboard.press(`${modifier}+KeyK`); + + await page.keyboard.down(modifier); + await page.keyboard.down('KeyK'); + await page.keyboard.up('KeyK'); + await page.keyboard.up(modifier); + + await page.getByTestId('global-search-input').click(); + await expect(page.getByTestId('global-search-input')).toBeVisible({ timeout: 2000 }); + + // await page.waitForTimeout(500); + await page.keyboard.down('Escape'); + await page.keyboard.up('Escape'); + }); + + test('customized Alt+K Global Search Modal', async ({ page, createTmpDir }) => { + // Remap globalSearch to Alt+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({ timeout: 2000 }); + + await page.keyboard.down('Backspace'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyK'); + await page.keyboard.up('KeyK'); + await page.keyboard.up('Alt'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyK'); + await page.keyboard.up('KeyK'); + await page.keyboard.up('Alt'); + + await page.getByTestId('global-search-input').click(); + await expect(page.getByTestId('global-search-input')).toBeVisible({ timeout: 2000 }); + + await page.keyboard.down('Escape'); + await page.keyboard.up('Escape'); + }); + }); + }); +}); diff --git a/tests/shortcuts/bound-actions-sidebar.spec.ts b/tests/shortcuts/bound-actions-sidebar.spec.ts new file mode 100644 index 00000000000..5618e835637 --- /dev/null +++ b/tests/shortcuts/bound-actions-sidebar.spec.ts @@ -0,0 +1,713 @@ +import { test, expect, Page } from '../../playwright'; +import { + createCollection, + createRequest, + openRequest as openRequestBase, + closeAllCollections, + createFolder, + openCollection, + selectRequestPaneTab +} from '../utils/page'; + +const modifier = process.platform === 'darwin' ? 'Meta' : 'Control'; +const collectionName = 'kb-collection'; +const baseRequests = ['req-1', 'req-2', 'req-3', 'req-4', 'req-5', 'req-6', 'req-7', 'req-8', 'req-9']; + +const setupBoundActionsData = async (page: Page, createTmpDir: (prefix: string) => Promise) => { + 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({ hasText: requestName }).first(); + return (await request.count()) > 0; +}; + +const openRequest = async (...args: Parameters) => { + const [page, targetCollectionName, requestName] = args; + if ( + targetCollectionName === collectionName + && baseRequests.includes(requestName) + && !(await checkIfRequestExists(page, requestName)) + ) { + await createRequest(page, requestName, targetCollectionName); + } + + return openRequestBase(...args); +}; + +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({ hasText: 'Keybindings' })).toBeVisible(); +}; + +/** + * Close the Preferences tab by clicking its close button. + * Using the close button avoids depending on any keyboard shortcut that may + * have just been reconfigured. + */ +const closePreferencesTab = async (page: Page) => { + const prefTab = page.locator('.request-tab').filter({ hasText: 'Preferences' }); + await prefTab.hover(); + await prefTab.getByTestId('request-tab-close-icon').click({ force: true }); + await expect(prefTab).not.toBeVisible({ timeout: 8000 }); +}; + +const closeTabByName = async (page: any, name: string | RegExp) => { + const tab = page.locator('.request-tab').filter({ hasText: name }); + await tab.click(); + await tab.hover(); + await tab.getByTestId('request-tab-close-icon').click({ force: true }); + await expect(tab).not.toBeVisible({ timeout: 2000 }); +}; + +const openFolderSettingsTab = async (page: Page, folderName: string) => { + await openCollection(page, collectionName); + const folderRow = page.locator('.collection-item-name').filter({ hasText: folderName }).first(); + await expect(folderRow).toBeVisible({ timeout: 5000 }); + await folderRow.dblclick(); + await expect(page.locator('.request-tab').filter({ hasText: folderName })).toBeVisible({ timeout: 3000 }); +}; + +const reopenClosedTab = async (page: Page, shortcut: () => Promise, expectedTabName: string | RegExp) => { + for (let attempt = 0; attempt < 3; attempt++) { + await page.locator('.request-tab').first().click(); + await page.waitForTimeout(150); + await shortcut(); + const reopenedTab = page.locator('.request-tab').filter({ hasText: expectedTabName }); + if ((await reopenedTab.count()) > 0) { + await expect(reopenedTab).toBeVisible({ timeout: 3000 }); + return; + } + await page.waitForTimeout(200); + } + + await expect(page.locator('.request-tab').filter({ hasText: expectedTabName })).toBeVisible({ timeout: 5000 }); +}; + +const remapKeybinding = async ( + page: Page, + action: string, + pressShortcut: () => Promise +) => { + await openKeybindingsTab(page); + const row = page.getByTestId(`keybinding-row-${action}`); + await expect(row).toBeVisible({ timeout: 5000 }); + 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({ timeout: 5000 }); + + await page.keyboard.press('Backspace'); + await pressShortcut(); + await closePreferencesTab(page); +}; + +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; +}; + +// ─── Tests ──── + +test.describe('Shortcut Keys - BOUND_ACTIONS', () => { + test.beforeEach(async ({ page, createTmpDir }) => { + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 5000 }); + await setupBoundActionsData(page, createTmpDir); + }); + + test.afterAll(async ({ page }) => { + await closeAllCollections(page); + }); + + test.describe('SIDEBAR', () => { + test.describe('SHORTCUT: Sidebar search', () => { + test('default Cmd/Ctrl+F open sidebar search', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + await page.keyboard.press(`${modifier}+KeyF`); + + // await expect(page.getByPlaceholder('Search requests...')).toBeVisible({ timeout: 3000 }); + await expect(page.getByTestId('sidebar-search-input')).toBeVisible({ timeout: 3000 }); + await page.getByTitle('Search requests').click(); + }); + + test('customized Alt+F opens sidebar search', async ({ 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({ timeout: 2000 }); + + await page.keyboard.down('Backspace'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyF'); + await page.keyboard.up('KeyF'); + await page.keyboard.up('Alt'); + + // Press Cmd/Ctrl+T to open sidebar search + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyF'); + await page.keyboard.up('KeyF'); + await page.keyboard.up('Alt'); + + await expect(page.getByTestId('sidebar-search-input')).toBeVisible({ timeout: 2000 }); + await page.getByTitle('Search requests').click(); + }); + }); + + test.describe('SHORTCUT: New request', () => { + test('default Cmd/Ctrl+N open new request modal', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + await page.locator('.collection-item-name').filter({ hasText: 'kb-folder' }).click(); + + await page.keyboard.press(`${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({ hasText: 'nr-folder' })).toBeVisible({ timeout: 2000 }); + }); + + test('customized Alt+N open new request modal', async ({ page, createTmpDir }) => { + // 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({ timeout: 2000 }); + + await page.keyboard.down('Backspace'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyN'); + await page.keyboard.up('KeyN'); + await page.keyboard.up('Alt'); + + await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).click(); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyN'); + await page.keyboard.up('KeyN'); + await page.keyboard.up('Alt'); + + 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({ hasText: 'nr-collection' })).toBeVisible({ timeout: 2000 }); + }); + }); + + test.describe('SHORTCUT: Rename Item', () => { + test('default Cmd/Ctrl+R open rename item modal for request', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).dblclick(); + await openRequest(page, 'kb-collection', 'req-1', { persist: true }); + await page.keyboard.press(`${modifier}+KeyR`); + + // Verify rename modal opens + const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename request/i }); + await expect(renameModal).toBeVisible({ timeout: 3000 }); + + // 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({ hasText: 'req-1' })).toBeVisible({ timeout: 2000 }); + await expect(page.locator('.collection-item-name').filter({ hasText: 'req-1-rename' })).toBeVisible({ timeout: 2000 }); + }); + + test('default Cmd/Ctrl+R open rename item modal for folder', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + await page.locator('.collection-item-name').filter({ hasText: 'kb-folder' }).dblclick(); + await page.keyboard.press(`${modifier}+KeyR`); + + // Verify rename modal opens + const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename folder/i }); + await expect(renameModal).toBeVisible({ timeout: 3000 }); + + // 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({ hasText: 'kb-folder-renamed' })).toBeVisible({ timeout: 2000 }); + }); + + test('default Cmd/Ctrl+R open rename item modal for collection', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).click(); + await page.keyboard.press(`${modifier}+KeyR`); + + // Verify rename modal opens + const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename collection/i }); + await expect(renameModal).toBeVisible({ timeout: 3000 }); + + // 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.locator('.collection-name').filter({ hasText: 'kb-collection-renamed' })).toBeVisible({ timeout: 3000 }); + }); + + test('customized Alt+X open rename item modal for request', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + // 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({ timeout: 2000 }); + + await page.keyboard.down('Backspace'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyX'); + await page.keyboard.up('KeyX'); + await page.keyboard.up('Alt'); + + await openRequest(page, collectionName, 'req-1', { persist: true }); + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyX'); + await page.keyboard.up('KeyX'); + await page.keyboard.up('Alt'); + + // Verify rename modal opens + const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename request/i }); + await expect(renameModal).toBeVisible({ timeout: 3000 }); + + // 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({ hasText: 'req-1-renamed-altx' })).toBeVisible({ timeout: 2000 }); + }); + + test('customized Alt+R open rename item modal for folder', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + await remapKeybinding(page, 'renameItem', async () => { + await page.keyboard.press('Alt+KeyX'); + }); + + await createFolder(page, 'kb-folder-rename-src', collectionName, true); + await openFolderSettingsTab(page, 'kb-folder-rename-src'); + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyX'); + await page.keyboard.up('KeyX'); + await page.keyboard.up('Alt'); + + // Verify rename modal opens + const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename folder/i }); + await expect(renameModal).toBeVisible({ timeout: 3000 }); + + // 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({ hasText: 'kb-folder-renamed-altx-src' })).toBeVisible({ timeout: 2000 }); + }); + + test('customized Alt+R open rename item modal for collection', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + await remapKeybinding(page, 'renameItem', async () => { + await page.keyboard.press('Alt+KeyX'); + }); + + await page.locator('.collection-name').filter({ hasText: collectionName }).click(); + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyX'); + await page.keyboard.up('KeyX'); + await page.keyboard.up('Alt'); + + // Verify rename modal opens + const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename collection/i }); + await expect(renameModal).toBeVisible({ timeout: 3000 }); + + // 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.locator('.collection-name').filter({ hasText: 'kb-collection-renamed-altx' })).toBeVisible({ timeout: 2000 }); + }); + }); + + test.describe('SHORTCUT: Clone Item', () => { + test('default Cmd/Ctrl+D open clone item modal for request', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + await openRequest(page, 'kb-collection', 'req-1', { persist: true }); + await page.keyboard.press(`${modifier}+KeyD`); + + // Verify clone modal opens + const cloneModal = page.locator('.bruno-modal-card').filter({ hasText: /clone request/i }); + await expect(cloneModal).toBeVisible({ timeout: 3000 }); + + // 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({ hasText: 'req-1 clone 1' })).toBeVisible({ timeout: 2000 }); + }); + + test('default Cmd/Ctrl+D open clone item modal for folder', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + await page.locator('.collection-item-name').filter({ hasText: 'kb-folder' }).dblclick(); + await page.keyboard.press(`${modifier}+KeyD`); + + // Verify clone modal opens + const cloneModal = page.locator('.bruno-modal-card').filter({ hasText: /clone folder/i }); + await expect(cloneModal).toBeVisible({ timeout: 3000 }); + + // 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({ hasText: 'kb-folder clone 1' })).toBeVisible({ timeout: 2000 }); + }); + + test('customized Alt+D open clone item modal for request', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + // 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({ timeout: 2000 }); + + await page.keyboard.down('Backspace'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyD'); + await page.keyboard.up('KeyD'); + await page.keyboard.up('Alt'); + + await openRequest(page, 'kb-collection', 'req-2', { persist: true }); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyD'); + await page.keyboard.up('KeyD'); + await page.keyboard.up('Alt'); + + // Verify clone modal opens + const cloneModal = page.locator('.bruno-modal-card').filter({ hasText: /clone request/i }); + await expect(cloneModal).toBeVisible({ timeout: 3000 }); + + // 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({ hasText: 'req-2 clone 1' })).toBeVisible({ timeout: 2000 }); + }); + + test('customized Alt+D open clone item modal for folder', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + await createFolder(page, 'kb-folder-clone-src', collectionName, true); + await openCollection(page, collectionName); + await page.locator('.collection-item-name').filter({ hasText: 'kb-folder-clone-src' }).first().click(); + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyD'); + await page.keyboard.up('KeyD'); + await page.keyboard.up('Alt'); + + // Verify clone modal opens + const cloneModal = page.locator('.bruno-modal-card').filter({ hasText: /clone folder/i }); + await expect(cloneModal).toBeVisible({ timeout: 3000 }); + + // 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({ hasText: 'kb-folder-clone-src copy 1' })).toBeVisible({ timeout: 2000 }); + }); + }); + + test.describe('SHORTCUT: Copy Paste Item', () => { + test('default Cmd/Ctrl+C/V copy paste item for request', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + await openRequest(page, 'kb-collection', 'req-3', { persist: true }); + await page.keyboard.press(`${modifier}+KeyC`); + await page.keyboard.press(`${modifier}+KeyV`); + + // Verify cloned request appears in sidebar + await expect(page.locator('.collection-item-name').filter({ hasText: 'req-3 (1)' })).toBeVisible({ timeout: 2000 }); + }); + + test('default Cmd/Ctrl+C/V copy paste item for folder', async ({ page }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + await openRequest(page, collectionName, 'kb-folder', { persist: true }); + await page.keyboard.press(`${modifier}+KeyC`); + await page.keyboard.press(`${modifier}+KeyV`); + + // Verify copied item appears in sidebar as child of folder + await expect(page.locator('.collection-item-name').filter({ hasText: 'kb-folder' })).toHaveCount(2); + }); + + test('customized Alt+C/V copy paste item for request', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + // 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({ timeout: 2000 }); + + await page.keyboard.down('Backspace'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyC'); + await page.keyboard.up('KeyC'); + await page.keyboard.up('Alt'); + + // 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({ timeout: 2000 }); + + await page.keyboard.down('Backspace'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyV'); + await page.keyboard.up('KeyV'); + await page.keyboard.up('Alt'); + + await openRequest(page, 'kb-collection', 'req-4', { persist: true }); + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyC'); + await page.keyboard.up('KeyC'); + await page.keyboard.up('Alt'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyV'); + await page.keyboard.up('KeyV'); + await page.keyboard.up('Alt'); + + // Verify cloned request appears in sidebar + await expect(page.locator('.collection-item-name').filter({ hasText: 'req-4 (1)' })).toBeVisible({ timeout: 2000 }); + }); + + test('customized Alt+C/V copy paste item for folder', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + await remapKeybinding(page, 'copyItem', async () => { + await page.keyboard.press('Alt+KeyC'); + }); + await remapKeybinding(page, 'pasteItem', async () => { + await page.keyboard.press('Alt+KeyV'); + }); + + await createFolder(page, 'kb-folder-copy-src', collectionName, true); + await openFolderSettingsTab(page, 'kb-folder-copy-src'); + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyC'); + await page.keyboard.up('KeyC'); + await page.keyboard.up('Alt'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyV'); + await page.keyboard.up('KeyV'); + await page.keyboard.up('Alt'); + + // Verify copied item appears in sidebar as child of folder + await expect(page.locator('.collection-item-name').filter({ hasText: 'kb-folder-copy-src' })).toHaveCount(2); + }); + }); + + test.describe('SHORTCUT: Collapse Sidebar', () => { + test('default collapse sidebar using default Cmd/Ctrl+\\', async ({ page, createTmpDir }) => { + await expect(page.getByTestId('collections')).toBeVisible(); + await page.locator('body').click({ position: { x: 1, y: 1 } }); + + // Press Cmd/Ctrl+\ to collapse sidebar + await page.keyboard.press(`${modifier}+Backslash`); + + await expect.poll( + () => page.locator('aside.sidebar').evaluate((el) => parseFloat(getComputedStyle(el).width)), + { timeout: 5000 } + ).toBeLessThan(5); + + // Press Cmd/Ctrl+\ to collapse expanded sidebar + await page.keyboard.press(`${modifier}+Backslash`); + + await expect.poll( + () => page.locator('aside.sidebar').evaluate((el) => parseFloat(getComputedStyle(el).width)), + { timeout: 5000 } + ).toBeGreaterThan(200); + }); + + test('should expand -> collapse -> expand the sidebar using customized Shift+G', async ({ page, createTmpDir }) => { + // 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({ timeout: 2000 }); + + await page.keyboard.down('Backspace'); + + await page.keyboard.down('Shift'); + await page.keyboard.down('KeyG'); + await page.keyboard.up('KeyG'); + await page.keyboard.up('Shift'); + + await closePreferencesTab(page); + + // Trigger the remapped shortcut to collapse sidebar + await page.keyboard.down('Shift'); + await page.keyboard.down('KeyG'); + await page.keyboard.up('KeyG'); + await page.keyboard.up('Shift'); + + // Verify sidebar collapsed to 0px + await expect.poll( + () => page.locator('aside.sidebar').evaluate((el) => getComputedStyle(el).width), + { timeout: 5000 } + ).toBe('0px'); + + // Trigger the remapped shortcut to expand sidebar + await page.keyboard.down('Shift'); + await page.keyboard.down('KeyG'); + await page.keyboard.up('KeyG'); + await page.keyboard.up('Shift'); + + await expect.poll( + () => page.locator('aside.sidebar').evaluate((el) => getComputedStyle(el).width), + { timeout: 5000 } + ).toBe('250px'); + }); + }); + }); +}); diff --git a/tests/shortcuts/bound-actions-tabs.spec.ts b/tests/shortcuts/bound-actions-tabs.spec.ts new file mode 100644 index 00000000000..6ee1a41f7db --- /dev/null +++ b/tests/shortcuts/bound-actions-tabs.spec.ts @@ -0,0 +1,899 @@ +import { test, expect, Page } from '../../playwright'; +import { + createCollection, + createRequest, + openRequest as openRequestBase, + closeAllCollections, + createFolder, + openCollection, + selectRequestPaneTab +} from '../utils/page'; + +const modifier = process.platform === 'darwin' ? 'Meta' : 'Control'; +const collectionName = 'kb-collection'; +const baseRequests = ['req-1', 'req-2', 'req-3', 'req-4', 'req-5', 'req-6', 'req-7', 'req-8', 'req-9']; + +const setupBoundActionsData = async (page: Page, createTmpDir: (prefix: string) => Promise) => { + 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({ hasText: requestName }).first(); + return (await request.count()) > 0; +}; + +const openRequest = async (...args: Parameters) => { + const [page, targetCollectionName, requestName] = args; + if ( + targetCollectionName === collectionName + && baseRequests.includes(requestName) + && !(await checkIfRequestExists(page, requestName)) + ) { + await createRequest(page, requestName, targetCollectionName); + } + + return openRequestBase(...args); +}; + +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({ hasText: 'Keybindings' })).toBeVisible(); +}; + +/** + * Close the Preferences tab by clicking its close button. + * Using the close button avoids depending on any keyboard shortcut that may + * have just been reconfigured. + */ +const closePreferencesTab = async (page: Page) => { + const prefTab = page.locator('.request-tab').filter({ hasText: 'Preferences' }); + await prefTab.hover(); + await prefTab.getByTestId('request-tab-close-icon').click({ force: true }); + await expect(prefTab).not.toBeVisible({ timeout: 8000 }); +}; + +const closeTabByName = async (page: any, name: string | RegExp) => { + const tab = page.locator('.request-tab').filter({ hasText: name }); + await tab.click(); + await tab.hover(); + await tab.getByTestId('request-tab-close-icon').click({ force: true }); + await expect(tab).not.toBeVisible({ timeout: 2000 }); +}; + +const openFolderSettingsTab = async (page: Page, folderName: string) => { + await openCollection(page, collectionName); + const folderRow = page.locator('.collection-item-name').filter({ hasText: folderName }).first(); + await expect(folderRow).toBeVisible({ timeout: 5000 }); + await folderRow.dblclick(); + await expect(page.locator('.request-tab').filter({ hasText: folderName })).toBeVisible({ timeout: 3000 }); +}; + +const reopenClosedTab = async (page: Page, shortcut: () => Promise, expectedTabName: string | RegExp) => { + for (let attempt = 0; attempt < 3; attempt++) { + await page.locator('.request-tab').first().click(); + await page.waitForTimeout(150); + await shortcut(); + const reopenedTab = page.locator('.request-tab').filter({ hasText: expectedTabName }); + if ((await reopenedTab.count()) > 0) { + await expect(reopenedTab).toBeVisible({ timeout: 3000 }); + return; + } + await page.waitForTimeout(200); + } + + await expect(page.locator('.request-tab').filter({ hasText: expectedTabName })).toBeVisible({ timeout: 5000 }); +}; + +const remapKeybinding = async ( + page: Page, + action: string, + pressShortcut: () => Promise +) => { + await openKeybindingsTab(page); + const row = page.getByTestId(`keybinding-row-${action}`); + await expect(row).toBeVisible({ timeout: 5000 }); + 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({ timeout: 5000 }); + + await page.keyboard.press('Backspace'); + await pressShortcut(); + await closePreferencesTab(page); +}; + +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; +}; + +// ─── Tests ──── + +test.describe('Shortcut Keys - BOUND_ACTIONS', () => { + test.beforeEach(async ({ page, createTmpDir }) => { + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 5000 }); + await setupBoundActionsData(page, createTmpDir); + }); + + test.afterAll(async ({ page }) => { + await closeAllCollections(page); + }); + + test.describe('TABS', () => { + test.describe('SHORTCUT: Close Tab', () => { + test('default Cmd/Ctrl+W closes the active tab', async ({ page, createTmpDir }) => { + await openRequest(page, collectionName, 'req-1', { persist: true }); + await expect(page.locator('.request-tab').filter({ hasText: 'req-1' })).toBeVisible({ timeout: 2000 }); + + await page.keyboard.press(`${modifier}+KeyW`); + await expect(page.locator('.request-tab')).toHaveCount(2, { timeout: 3000 }); + }); + + test('customized Cmd/Ctrl+Shift+X closes the active tab', async ({ page, createTmpDir }) => { + // 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.down('Backspace'); + + await page.keyboard.down('Shift'); + await page.keyboard.down('KeyX'); + await page.keyboard.up('KeyX'); + await page.keyboard.up('Shift'); + + await closePreferencesTab(page); + + await openRequest(page, collectionName, 'req-1', { persist: true }); + await expect(page.locator('.request-tab').filter({ hasText: 'req-1' })).toBeVisible({ timeout: 2000 }); + + await page.keyboard.down('Shift'); + await page.keyboard.down('KeyX'); + await page.keyboard.up('KeyX'); + await page.keyboard.up('Shift'); + await expect(page.locator('.request-tab')).toHaveCount(2, { timeout: 3000 }); + }); + }); + + test.describe('SHORTCUT: Close All Tabs', () => { + test('default Cmd/Ctrl+Shift+W closes all tabs', async ({ 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({ hasText: 'req-1' })).toBeVisible({ timeout: 2000 }); + await expect(page.locator('.request-tab').filter({ hasText: 'req-2' })).toBeVisible({ timeout: 2000 }); + await expect(page.locator('.request-tab').filter({ hasText: 'req-3' })).toBeVisible({ timeout: 2000 }); + + await page.keyboard.down(modifier); + await page.keyboard.down('Shift'); + await page.keyboard.down('KeyW'); + await page.keyboard.up('KeyW'); + await page.keyboard.up('Shift'); + await page.keyboard.up(modifier); + await expect(page.locator('.request-tab')).toHaveCount(2, { timeout: 3000 }); + }); + + test('customized Alt+Y closes all tabs', async ({ 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.down('Backspace'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + 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({ hasText: 'req-1' })).toBeVisible({ timeout: 2000 }); + await expect(page.locator('.request-tab').filter({ hasText: 'req-2' })).toBeVisible({ timeout: 2000 }); + await expect(page.locator('.request-tab').filter({ hasText: 'req-3' })).toBeVisible({ timeout: 2000 }); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + await expect(page.locator('.request-tab')).toHaveCount(2, { timeout: 3000 }); + }); + }); + + test.describe('SHORTCUT: Save', () => { + test('default Cmd/Ctrl+S save tab', async ({ page, createTmpDir }) => { + await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).dblclick(); + await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 }); + + // Verify initially there is NO draft indicator (close icon is present) + const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) }); + 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 page.keyboard.down(modifier); + await page.keyboard.down('KeyS'); + await page.keyboard.up('KeyS'); + await page.keyboard.up(modifier); + + // Verify draft indicator is gone after saving + await expect(collectionTab.locator('.close-icon')).toBeVisible(); + await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible(); + }); + + test('customized Alt+S save tab', async ({ page, createTmpDir }) => { + // 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.down('Backspace'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyS'); + await page.keyboard.up('KeyS'); + await page.keyboard.up('Alt'); + + await closePreferencesTab(page); + + await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).dblclick(); + await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 }); + + // Verify initially there is NO draft indicator (close icon is present) + const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) }); + 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 page.keyboard.down('Alt'); + await page.keyboard.down('KeyS'); + await page.keyboard.up('KeyS'); + await page.keyboard.up('Alt'); + + // 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('default Cmd/Ctrl+Shift+S save all tabs', async ({ page }) => { + await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).dblclick(); + await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 }); + + // Verify initially there is NO draft indicator (close icon is present) + const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) }); + 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({ hasText: 'kb-draft-folder' }).dblclick(); + + // Verify folder settings tab is open + const folderTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'kb-draft-folder' }) }); + 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 page.keyboard.down(modifier); + await page.keyboard.down('Shift'); + await page.keyboard.down('KeyS'); + await page.keyboard.up('KeyS'); + await page.keyboard.up('Shift'); + await page.keyboard.up(modifier); + + // 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('customized Alt+Shift+S save all tabs', async ({ 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.down('Backspace'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await page.keyboard.down('KeyS'); + await page.keyboard.up('KeyS'); + await page.keyboard.up('Shift'); + await page.keyboard.up('Alt'); + + await closePreferencesTab(page); + + await page.locator('.collection-name').filter({ hasText: collectionName }).dblclick(); + await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 }); + + // Verify initially there is NO draft indicator (close icon is present) + const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) }); + 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({ hasText: 'kb-draft-folder' }).dblclick(); + + // Verify folder settings tab is open + const folderTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'kb-draft-folder' }) }); + 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 page.keyboard.down('Alt'); + await page.keyboard.down('Shift'); + await page.keyboard.down('KeyS'); + await page.keyboard.up('KeyS'); + await page.keyboard.up('Shift'); + await page.keyboard.up('Alt'); + + // 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(); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + }); + }); + + test.describe('SHORTCUT: Switch to Previous Tab', () => { + test('default Cmd/Ctrl+Shift+[ switches to previous tab', async ({ 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({ hasText: 'req-6' })).toBeVisible({ timeout: 2000 }); + + // req-6 is active (last opened) — press previous → req-5 + await page.keyboard.press(`${modifier}+Shift+BracketLeft`); + await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 }); + + // Press again → req-4 + await page.keyboard.press(`${modifier}+Shift+BracketLeft`); + await expect(page.locator('li.request-tab.active')).toHaveText(/req-4/, { timeout: 3000 }); + }); + + test('customized Shift+P switches to previous tab', async ({ 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.down('Backspace'); + + await page.keyboard.down('Shift'); + await page.keyboard.down('KeyP'); + await page.keyboard.up('KeyP'); + await page.keyboard.up('Shift'); + + 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({ hasText: 'req-6' })).toBeVisible({ timeout: 2000 }); + + // req-6 is active — press Shift+P → req-5 + await page.keyboard.down('Shift'); + await page.keyboard.down('KeyP'); + await page.keyboard.up('KeyP'); + await page.keyboard.up('Shift'); + await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 }); + }); + }); + + test.describe('SHORTCUT: Switch to Next Tab', () => { + test('default Cmd/Ctrl+Shift+] switches to next tab', async ({ 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 page.keyboard.press(`${modifier}+Shift+BracketRight`); + await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 }); + + // Press again → req-6 + await page.keyboard.press(`${modifier}+Shift+BracketRight`); + await expect(page.locator('li.request-tab.active')).toHaveText(/req-6/, { timeout: 3000 }); + }); + + test('customized Shift+N switches to next tab', async ({ 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.down('Backspace'); + + await page.keyboard.down('Shift'); + await page.keyboard.down('KeyN'); + await page.keyboard.up('KeyN'); + await page.keyboard.up('Shift'); + + 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 page.keyboard.down('Shift'); + await page.keyboard.down('KeyN'); + await page.keyboard.up('KeyN'); + await page.keyboard.up('Shift'); + await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 }); + }); + }); + + test.describe('SHORTCUT: Move Tab Left', () => { + test('default Cmd/Ctrl+[ moves active tab left', async ({ 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 page.keyboard.press(`${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 page.keyboard.press(`${modifier}+BracketLeft`); + await expect(tabs.nth(totalTabs - 3)).toHaveText(/req-9/, { timeout: 3000 }); + }); + + test('customized Alt+L moves active tab left', async ({ 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.down('Backspace'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyL'); + await page.keyboard.up('KeyL'); + await page.keyboard.up('Alt'); + + 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 page.keyboard.down('Alt'); + await page.keyboard.down('KeyL'); + await page.keyboard.up('KeyL'); + await page.keyboard.up('Alt'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyL'); + await page.keyboard.up('KeyL'); + await page.keyboard.up('Alt'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyL'); + await page.keyboard.up('KeyL'); + await page.keyboard.up('Alt'); + await expect(tabs.nth(0)).toHaveText(/req-9/); + }); + }); + + test.describe('SHORTCUT: Move Tab Right', () => { + test('default Cmd/Ctrl+] moves active tab right', async ({ 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 page.keyboard.press(`${modifier}+BracketLeft`); + await page.keyboard.press(`${modifier}+BracketLeft`); + await page.keyboard.press(`${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 page.keyboard.press(`${modifier}+BracketRight`); + const indexAfterOneMove = await getTabIndex(page, 'req-9'); + expect(indexAfterOneMove).toBeGreaterThanOrEqual(startIndex); + + await page.keyboard.press(`${modifier}+BracketRight`); + const indexAfterTwoMoves = await getTabIndex(page, 'req-9'); + expect(indexAfterTwoMoves).toBeGreaterThanOrEqual(indexAfterOneMove); + + await page.keyboard.press(`${modifier}+BracketRight`); + const indexAfterThreeMoves = await getTabIndex(page, 'req-9'); + expect(indexAfterThreeMoves).toBeGreaterThanOrEqual(indexAfterTwoMoves); + }); + + test('customized Alt+R moves active tab right', async ({ 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.down('Backspace'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyR'); + await page.keyboard.up('KeyR'); + await page.keyboard.up('Alt'); + + 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({ hasText: 'req-7' }).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 page.keyboard.down('Alt'); + await page.keyboard.down('KeyR'); + await page.keyboard.up('KeyR'); + await page.keyboard.up('Alt'); + + const indexAfterOneMove = await getTabIndex(page, 'req-7'); + expect(indexAfterOneMove).toBeGreaterThan(startIndex); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyR'); + await page.keyboard.up('KeyR'); + await page.keyboard.up('Alt'); + + const indexAfterTwoMoves = await getTabIndex(page, 'req-7'); + expect(indexAfterTwoMoves).toBeGreaterThanOrEqual(indexAfterOneMove); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyR'); + await page.keyboard.up('KeyR'); + await page.keyboard.up('Alt'); + + const indexAfterThreeMoves = await getTabIndex(page, 'req-7'); + expect(indexAfterThreeMoves).toBeGreaterThanOrEqual(indexAfterTwoMoves); + + // Close all tabs + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + }); + }); + + test.describe('SHORTCUT: Switch to Tab at Position', () => { + test('default Cmd/Ctrl+1-8 open tab from 1-8', async ({ page }) => { + await openRequest(page, 'kb-collection', 'req-1', { persist: true }); + await openRequest(page, 'kb-collection', 'req-2', { persist: true }); + await openRequest(page, 'kb-collection', 'req-3', { persist: true }); + await openRequest(page, 'kb-collection', 'req-4', { persist: true }); + await openRequest(page, 'kb-collection', 'req-5', { persist: true }); + await openRequest(page, 'kb-collection', 'req-6', { persist: true }); + 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 }); + + await expect(page.locator('.request-tab')).toHaveCount(9, { timeout: 2000 }); + const tabs = page.locator('.request-tab'); + + await expect(tabs.nth(0)).toHaveText(/req-1/, { timeout: 2000 }); + await page.keyboard.press(`${modifier}+1`); + await expect(page.locator('li.request-tab.active')).toHaveText(/req-1/, { timeout: 3000 }); + await page.keyboard.press(`${modifier}+2`); + await expect(page.locator('li.request-tab.active')).toHaveText(/req-2/, { timeout: 3000 }); + await page.keyboard.press(`${modifier}+3`); + await expect(page.locator('li.request-tab.active')).toHaveText(/req-3/, { timeout: 3000 }); + await page.keyboard.press(`${modifier}+4`); + await expect(page.locator('li.request-tab.active')).toHaveText(/req-4/, { timeout: 3000 }); + await page.keyboard.press(`${modifier}+5`); + await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 }); + await page.keyboard.press(`${modifier}+6`); + await expect(page.locator('li.request-tab.active')).toHaveText(/req-6/, { timeout: 3000 }); + await page.keyboard.press(`${modifier}+7`); + await expect(page.locator('li.request-tab.active')).toHaveText(/req-7/, { timeout: 3000 }); + await page.keyboard.press(`${modifier}+8`); + await expect(page.locator('li.request-tab.active')).toHaveText(/req-8/, { timeout: 3000 }); + }); + }); + + test.describe('SHORTCUT: Reopen Last Closed Tab', () => { + test('default Cmd/Ctrl+Shift+T reopens last closed request tab', async ({ page }) => { + await openRequest(page, collectionName, 'req-2', { persist: true }); + await openRequest(page, collectionName, 'req-1', { persist: true }); + const req1Tab = page.locator('.request-tab').filter({ hasText: 'req-1' }).first(); + await req1Tab.click(); + await expect(req1Tab).toHaveClass(/active/); + await closeTabByName(page, 'req-1'); + + await reopenClosedTab(page, async () => page.keyboard.press(`${modifier}+Shift+t`), 'req-1'); + }); + + test('default Cmd/Ctrl+Shift+T reopens multiple tab types in LIFO order', async ({ page }) => { + await openRequest(page, collectionName, 'req-2', { persist: true }); + + // Open Collection-Settings tab (double-click collection name) + await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).dblclick(); + await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 }); + + // Open Runner tab + await page.getByTestId('runner').click(); + await expect(page.locator('.request-tab').filter({ hasText: 'Runner' })).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({ hasText: 'Variables' })).toBeVisible({ timeout: 2000 }); + + // Open Folder-Settings tab (create folder + double-click) + await page.locator('.collection-item-name').filter({ hasText: 'kb-folder' }).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 () => page.keyboard.press(`${modifier}+Shift+t`), 'Runner'); + await reopenClosedTab(page, async () => page.keyboard.press(`${modifier}+Shift+t`), /variables/i); + await reopenClosedTab(page, async () => page.keyboard.press(`${modifier}+Shift+t`), 'Collection'); + await reopenClosedTab(page, async () => page.keyboard.press(`${modifier}+Shift+t`), 'kb-folder'); + }); + + test('customized Alt+Z reopens last closed tab', async ({ page }) => { + await remapKeybinding(page, 'reopenLastClosedTab', async () => { + await page.keyboard.press('Alt+z'); + }); + + await openRequest(page, collectionName, 'req-2', { persist: true }); + await openRequest(page, collectionName, 'req-1', { persist: true }); + const req1Tab = page.locator('.request-tab').filter({ hasText: 'req-1' }).first(); + await req1Tab.click(); + await expect(req1Tab).toHaveClass(/active/); + await closeTabByName(page, 'req-1'); + + await reopenClosedTab(page, async () => { + await page.keyboard.press('Alt+z'); + }, 'req-1'); + }); + }); + }); + + test.describe('SHORTCUT: Edit Environment', () => { + test('open environment tab of collection Cmd/Ctrl+E', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + await openRequest(page, 'kb-collection', 'req-7', { persist: true }); + + await page.keyboard.down(modifier); + await page.keyboard.down('KeyE'); + await page.keyboard.up('KeyE'); + await page.keyboard.up(modifier); + + await expect(page.locator('.request-tab').filter({ hasText: 'Environments' })).toBeVisible({ timeout: 2000 }); + }); + + test('open environment tab of collection customized Alt+E', async ({ page, createTmpDir }) => { + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyY'); + await page.keyboard.up('KeyY'); + await page.keyboard.up('Alt'); + + // 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({ timeout: 2000 }); + + await page.keyboard.down('Backspace'); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyE'); + await page.keyboard.up('KeyE'); + await page.keyboard.up('Alt'); + + await openRequest(page, 'kb-collection', 'req-7', { persist: true }); + + await page.keyboard.down('Alt'); + await page.keyboard.down('KeyE'); + await page.keyboard.up('KeyE'); + await page.keyboard.up('Alt'); + + await expect(page.locator('.request-tab').filter({ hasText: 'Environments' })).toBeVisible({ timeout: 2000 }); + + // Rest Default - just in case to not fail shortcuts in other places + await openKeybindingsTab(page); + await page.getByTestId('reset-all-keybindings-btn').click({ timeout: 2000 }); + }); + }); +}); diff --git a/tests/shortcuts/bound-actions.spec.ts b/tests/shortcuts/bound-actions.spec.ts deleted file mode 100644 index 3f4aba44a0a..00000000000 --- a/tests/shortcuts/bound-actions.spec.ts +++ /dev/null @@ -1,2013 +0,0 @@ -import { test, expect, Page } from '../../playwright'; -import { - createCollection, - createRequest, - openRequest as openRequestBase, - closeAllCollections, - createFolder, - openCollection, - selectRequestPaneTab -} from '../utils/page'; - -const modifier = process.platform === 'darwin' ? 'Meta' : 'Control'; -const collectionName = 'kb-collection'; -const baseRequests = ['req-1', 'req-2', 'req-3', 'req-4', 'req-5', 'req-6', 'req-7', 'req-8', 'req-9']; - -const setupBoundActionsData = async (page: Page, createTmpDir: (prefix: string) => Promise) => { - 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({ hasText: requestName }).first(); - return (await request.count()) > 0; -}; - -const openRequest = async (...args: Parameters) => { - const [page, targetCollectionName, requestName] = args; - if ( - targetCollectionName === collectionName - && baseRequests.includes(requestName) - && !(await checkIfRequestExists(page, requestName)) - ) { - await createRequest(page, requestName, targetCollectionName); - } - - return openRequestBase(...args); -}; - -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({ hasText: 'Keybindings' })).toBeVisible(); -}; - -/** - * Close the Preferences tab by clicking its close button. - * Using the close button avoids depending on any keyboard shortcut that may - * have just been reconfigured. - */ -const closePreferencesTab = async (page: Page) => { - const prefTab = page.locator('.request-tab').filter({ hasText: 'Preferences' }); - await prefTab.hover(); - await prefTab.getByTestId('request-tab-close-icon').click({ force: true }); - await expect(prefTab).not.toBeVisible({ timeout: 8000 }); -}; - -const closeTabByName = async (page: any, name: string | RegExp) => { - const tab = page.locator('.request-tab').filter({ hasText: name }); - await tab.click(); - await tab.hover(); - await tab.getByTestId('request-tab-close-icon').click({ force: true }); - await expect(tab).not.toBeVisible({ timeout: 2000 }); -}; - -const openFolderSettingsTab = async (page: Page, folderName: string) => { - await openCollection(page, collectionName); - const folderRow = page.locator('.collection-item-name').filter({ hasText: folderName }).first(); - await expect(folderRow).toBeVisible({ timeout: 5000 }); - await folderRow.dblclick(); - await expect(page.locator('.request-tab').filter({ hasText: folderName })).toBeVisible({ timeout: 3000 }); -}; - -const reopenClosedTab = async (page: Page, shortcut: () => Promise, expectedTabName: string | RegExp) => { - for (let attempt = 0; attempt < 3; attempt++) { - await page.locator('.request-tab').first().click(); - await page.waitForTimeout(150); - await shortcut(); - const reopenedTab = page.locator('.request-tab').filter({ hasText: expectedTabName }); - if ((await reopenedTab.count()) > 0) { - await expect(reopenedTab).toBeVisible({ timeout: 3000 }); - return; - } - await page.waitForTimeout(200); - } - - await expect(page.locator('.request-tab').filter({ hasText: expectedTabName })).toBeVisible({ timeout: 5000 }); -}; - -const remapKeybinding = async ( - page: Page, - action: string, - pressShortcut: () => Promise -) => { - await openKeybindingsTab(page); - const row = page.getByTestId(`keybinding-row-${action}`); - await expect(row).toBeVisible({ timeout: 5000 }); - 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({ timeout: 5000 }); - - await page.keyboard.press('Backspace'); - await pressShortcut(); - await closePreferencesTab(page); -}; - -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; -}; - -// ─── Tests ──── - -test.describe('Shortcut Keys - BOUND_ACTIONS', () => { - test.beforeEach(async ({ page, createTmpDir }) => { - await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 5000 }); - await setupBoundActionsData(page, createTmpDir); - }); - - test.afterAll(async ({ page }) => { - await closeAllCollections(page); - }); - - test.describe('TABS', () => { - test.describe('SHORTCUT: Close Tab', () => { - test('default Cmd/Ctrl+W closes the active tab', async ({ page, createTmpDir }) => { - await openRequest(page, collectionName, 'req-1', { persist: true }); - await expect(page.locator('.request-tab').filter({ hasText: 'req-1' })).toBeVisible({ timeout: 2000 }); - - await page.keyboard.press(`${modifier}+KeyW`); - await expect(page.locator('.request-tab')).toHaveCount(2, { timeout: 3000 }); - }); - - test('customized Cmd/Ctrl+Shift+X closes the active tab', async ({ page, createTmpDir }) => { - // 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.down('Backspace'); - - await page.keyboard.down('Shift'); - await page.keyboard.down('KeyX'); - await page.keyboard.up('KeyX'); - await page.keyboard.up('Shift'); - - await closePreferencesTab(page); - - await openRequest(page, collectionName, 'req-1', { persist: true }); - await expect(page.locator('.request-tab').filter({ hasText: 'req-1' })).toBeVisible({ timeout: 2000 }); - - await page.keyboard.down('Shift'); - await page.keyboard.down('KeyX'); - await page.keyboard.up('KeyX'); - await page.keyboard.up('Shift'); - await expect(page.locator('.request-tab')).toHaveCount(2, { timeout: 3000 }); - }); - }); - - test.describe('SHORTCUT: Close All Tabs', () => { - test('default Cmd/Ctrl+Shift+W closes all tabs', async ({ 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({ hasText: 'req-1' })).toBeVisible({ timeout: 2000 }); - await expect(page.locator('.request-tab').filter({ hasText: 'req-2' })).toBeVisible({ timeout: 2000 }); - await expect(page.locator('.request-tab').filter({ hasText: 'req-3' })).toBeVisible({ timeout: 2000 }); - - await page.keyboard.down(modifier); - await page.keyboard.down('Shift'); - await page.keyboard.down('KeyW'); - await page.keyboard.up('KeyW'); - await page.keyboard.up('Shift'); - await page.keyboard.up(modifier); - await expect(page.locator('.request-tab')).toHaveCount(2, { timeout: 3000 }); - }); - - test('customized Alt+Y closes all tabs', async ({ 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.down('Backspace'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - 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({ hasText: 'req-1' })).toBeVisible({ timeout: 2000 }); - await expect(page.locator('.request-tab').filter({ hasText: 'req-2' })).toBeVisible({ timeout: 2000 }); - await expect(page.locator('.request-tab').filter({ hasText: 'req-3' })).toBeVisible({ timeout: 2000 }); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - await expect(page.locator('.request-tab')).toHaveCount(2, { timeout: 3000 }); - }); - }); - - test.describe('SHORTCUT: Save', () => { - test('default Cmd/Ctrl+S save tab', async ({ page, createTmpDir }) => { - await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).dblclick(); - await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 }); - - // Verify initially there is NO draft indicator (close icon is present) - const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) }); - 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 page.keyboard.down(modifier); - await page.keyboard.down('KeyS'); - await page.keyboard.up('KeyS'); - await page.keyboard.up(modifier); - - // Verify draft indicator is gone after saving - await expect(collectionTab.locator('.close-icon')).toBeVisible(); - await expect(collectionTab.locator('.has-changes-icon')).not.toBeVisible(); - }); - - test('customized Alt+S save tab', async ({ page, createTmpDir }) => { - // 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.down('Backspace'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyS'); - await page.keyboard.up('KeyS'); - await page.keyboard.up('Alt'); - - await closePreferencesTab(page); - - await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).dblclick(); - await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 }); - - // Verify initially there is NO draft indicator (close icon is present) - const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) }); - 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 page.keyboard.down('Alt'); - await page.keyboard.down('KeyS'); - await page.keyboard.up('KeyS'); - await page.keyboard.up('Alt'); - - // 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('default Cmd/Ctrl+Shift+S save all tabs', async ({ page }) => { - await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).dblclick(); - await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 }); - - // Verify initially there is NO draft indicator (close icon is present) - const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) }); - 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({ hasText: 'kb-draft-folder' }).dblclick(); - - // Verify folder settings tab is open - const folderTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'kb-draft-folder' }) }); - 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 page.keyboard.down(modifier); - await page.keyboard.down('Shift'); - await page.keyboard.down('KeyS'); - await page.keyboard.up('KeyS'); - await page.keyboard.up('Shift'); - await page.keyboard.up(modifier); - - // 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('customized Alt+Shift+S save all tabs', async ({ 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.down('Backspace'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); - await page.keyboard.down('KeyS'); - await page.keyboard.up('KeyS'); - await page.keyboard.up('Shift'); - await page.keyboard.up('Alt'); - - await closePreferencesTab(page); - - await page.locator('.collection-name').filter({ hasText: collectionName }).dblclick(); - await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 }); - - // Verify initially there is NO draft indicator (close icon is present) - const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) }); - 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({ hasText: 'kb-draft-folder' }).dblclick(); - - // Verify folder settings tab is open - const folderTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'kb-draft-folder' }) }); - 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 page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); - await page.keyboard.down('KeyS'); - await page.keyboard.up('KeyS'); - await page.keyboard.up('Shift'); - await page.keyboard.up('Alt'); - - // 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(); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - }); - }); - - test.describe('SHORTCUT: Switch to Previous Tab', () => { - test('default Cmd/Ctrl+Shift+[ switches to previous tab', async ({ 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({ hasText: 'req-6' })).toBeVisible({ timeout: 2000 }); - - // req-6 is active (last opened) — press previous → req-5 - await page.keyboard.press(`${modifier}+Shift+BracketLeft`); - await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 }); - - // Press again → req-4 - await page.keyboard.press(`${modifier}+Shift+BracketLeft`); - await expect(page.locator('li.request-tab.active')).toHaveText(/req-4/, { timeout: 3000 }); - }); - - test('customized Shift+P switches to previous tab', async ({ 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.down('Backspace'); - - await page.keyboard.down('Shift'); - await page.keyboard.down('KeyP'); - await page.keyboard.up('KeyP'); - await page.keyboard.up('Shift'); - - 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({ hasText: 'req-6' })).toBeVisible({ timeout: 2000 }); - - // req-6 is active — press Shift+P → req-5 - await page.keyboard.down('Shift'); - await page.keyboard.down('KeyP'); - await page.keyboard.up('KeyP'); - await page.keyboard.up('Shift'); - await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 }); - }); - }); - - test.describe('SHORTCUT: Switch to Next Tab', () => { - test('default Cmd/Ctrl+Shift+] switches to next tab', async ({ 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 page.keyboard.press(`${modifier}+Shift+BracketRight`); - await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 }); - - // Press again → req-6 - await page.keyboard.press(`${modifier}+Shift+BracketRight`); - await expect(page.locator('li.request-tab.active')).toHaveText(/req-6/, { timeout: 3000 }); - }); - - test('customized Shift+N switches to next tab', async ({ 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.down('Backspace'); - - await page.keyboard.down('Shift'); - await page.keyboard.down('KeyN'); - await page.keyboard.up('KeyN'); - await page.keyboard.up('Shift'); - - 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 page.keyboard.down('Shift'); - await page.keyboard.down('KeyN'); - await page.keyboard.up('KeyN'); - await page.keyboard.up('Shift'); - await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 }); - }); - }); - - test.describe('SHORTCUT: Move Tab Left', () => { - test('default Cmd/Ctrl+[ moves active tab left', async ({ 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 page.keyboard.press(`${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 page.keyboard.press(`${modifier}+BracketLeft`); - await expect(tabs.nth(totalTabs - 3)).toHaveText(/req-9/, { timeout: 3000 }); - }); - - test('customized Alt+L moves active tab left', async ({ 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.down('Backspace'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyL'); - await page.keyboard.up('KeyL'); - await page.keyboard.up('Alt'); - - 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 page.keyboard.down('Alt'); - await page.keyboard.down('KeyL'); - await page.keyboard.up('KeyL'); - await page.keyboard.up('Alt'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyL'); - await page.keyboard.up('KeyL'); - await page.keyboard.up('Alt'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyL'); - await page.keyboard.up('KeyL'); - await page.keyboard.up('Alt'); - await expect(tabs.nth(0)).toHaveText(/req-9/); - }); - }); - - test.describe('SHORTCUT: Move Tab Right', () => { - test('default Cmd/Ctrl+] moves active tab right', async ({ 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 page.keyboard.press(`${modifier}+BracketLeft`); - await page.keyboard.press(`${modifier}+BracketLeft`); - await page.keyboard.press(`${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 page.keyboard.press(`${modifier}+BracketRight`); - const indexAfterOneMove = await getTabIndex(page, 'req-9'); - expect(indexAfterOneMove).toBeGreaterThanOrEqual(startIndex); - - await page.keyboard.press(`${modifier}+BracketRight`); - const indexAfterTwoMoves = await getTabIndex(page, 'req-9'); - expect(indexAfterTwoMoves).toBeGreaterThanOrEqual(indexAfterOneMove); - - await page.keyboard.press(`${modifier}+BracketRight`); - const indexAfterThreeMoves = await getTabIndex(page, 'req-9'); - expect(indexAfterThreeMoves).toBeGreaterThanOrEqual(indexAfterTwoMoves); - }); - - test('customized Alt+R moves active tab right', async ({ 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.down('Backspace'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyR'); - await page.keyboard.up('KeyR'); - await page.keyboard.up('Alt'); - - 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({ hasText: 'req-7' }).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 page.keyboard.down('Alt'); - await page.keyboard.down('KeyR'); - await page.keyboard.up('KeyR'); - await page.keyboard.up('Alt'); - - const indexAfterOneMove = await getTabIndex(page, 'req-7'); - expect(indexAfterOneMove).toBeGreaterThan(startIndex); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyR'); - await page.keyboard.up('KeyR'); - await page.keyboard.up('Alt'); - - const indexAfterTwoMoves = await getTabIndex(page, 'req-7'); - expect(indexAfterTwoMoves).toBeGreaterThanOrEqual(indexAfterOneMove); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyR'); - await page.keyboard.up('KeyR'); - await page.keyboard.up('Alt'); - - const indexAfterThreeMoves = await getTabIndex(page, 'req-7'); - expect(indexAfterThreeMoves).toBeGreaterThanOrEqual(indexAfterTwoMoves); - - // Close all tabs - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - }); - }); - - test.describe('SHORTCUT: Switch to Tab at Position', () => { - test('default Cmd/Ctrl+1-8 open tab from 1-8', async ({ page }) => { - await openRequest(page, 'kb-collection', 'req-1', { persist: true }); - await openRequest(page, 'kb-collection', 'req-2', { persist: true }); - await openRequest(page, 'kb-collection', 'req-3', { persist: true }); - await openRequest(page, 'kb-collection', 'req-4', { persist: true }); - await openRequest(page, 'kb-collection', 'req-5', { persist: true }); - await openRequest(page, 'kb-collection', 'req-6', { persist: true }); - 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 }); - - await expect(page.locator('.request-tab')).toHaveCount(9, { timeout: 2000 }); - const tabs = page.locator('.request-tab'); - - await expect(tabs.nth(0)).toHaveText(/req-1/, { timeout: 2000 }); - await page.keyboard.press(`${modifier}+1`); - await expect(page.locator('li.request-tab.active')).toHaveText(/req-1/, { timeout: 3000 }); - await page.keyboard.press(`${modifier}+2`); - await expect(page.locator('li.request-tab.active')).toHaveText(/req-2/, { timeout: 3000 }); - await page.keyboard.press(`${modifier}+3`); - await expect(page.locator('li.request-tab.active')).toHaveText(/req-3/, { timeout: 3000 }); - await page.keyboard.press(`${modifier}+4`); - await expect(page.locator('li.request-tab.active')).toHaveText(/req-4/, { timeout: 3000 }); - await page.keyboard.press(`${modifier}+5`); - await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 }); - await page.keyboard.press(`${modifier}+6`); - await expect(page.locator('li.request-tab.active')).toHaveText(/req-6/, { timeout: 3000 }); - await page.keyboard.press(`${modifier}+7`); - await expect(page.locator('li.request-tab.active')).toHaveText(/req-7/, { timeout: 3000 }); - await page.keyboard.press(`${modifier}+8`); - await expect(page.locator('li.request-tab.active')).toHaveText(/req-8/, { timeout: 3000 }); - }); - }); - - test.describe('SHORTCUT: Reopen Last Closed Tab', () => { - test('default Cmd/Ctrl+Shift+T reopens last closed request tab', async ({ page }) => { - await openRequest(page, collectionName, 'req-2', { persist: true }); - await openRequest(page, collectionName, 'req-1', { persist: true }); - const req1Tab = page.locator('.request-tab').filter({ hasText: 'req-1' }).first(); - await req1Tab.click(); - await expect(req1Tab).toHaveClass(/active/); - await closeTabByName(page, 'req-1'); - - await reopenClosedTab(page, async () => page.keyboard.press(`${modifier}+Shift+t`), 'req-1'); - }); - - test('default Cmd/Ctrl+Shift+T reopens multiple tab types in LIFO order', async ({ page }) => { - await openRequest(page, collectionName, 'req-2', { persist: true }); - - // Open Collection-Settings tab (double-click collection name) - await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).dblclick(); - await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 }); - - // Open Runner tab - await page.getByTestId('runner').click(); - await expect(page.locator('.request-tab').filter({ hasText: 'Runner' })).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({ hasText: 'Variables' })).toBeVisible({ timeout: 2000 }); - - // Open Folder-Settings tab (create folder + double-click) - await page.locator('.collection-item-name').filter({ hasText: 'kb-folder' }).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 () => page.keyboard.press(`${modifier}+Shift+t`), 'Runner'); - await reopenClosedTab(page, async () => page.keyboard.press(`${modifier}+Shift+t`), /variables/i); - await reopenClosedTab(page, async () => page.keyboard.press(`${modifier}+Shift+t`), 'Collection'); - await reopenClosedTab(page, async () => page.keyboard.press(`${modifier}+Shift+t`), 'kb-folder'); - }); - - test('customized Alt+Z reopens last closed tab', async ({ page }) => { - await remapKeybinding(page, 'reopenLastClosedTab', async () => { - await page.keyboard.press('Alt+z'); - }); - - await openRequest(page, collectionName, 'req-2', { persist: true }); - await openRequest(page, collectionName, 'req-1', { persist: true }); - const req1Tab = page.locator('.request-tab').filter({ hasText: 'req-1' }).first(); - await req1Tab.click(); - await expect(req1Tab).toHaveClass(/active/); - await closeTabByName(page, 'req-1'); - - await reopenClosedTab(page, async () => { - await page.keyboard.press('Alt+z'); - }, 'req-1'); - }); - }); - }); - - test.describe('SIDEBAR', () => { - test.describe('SHORTCUT: Sidebar search', () => { - test('default Cmd/Ctrl+F open sidebar search', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - await page.keyboard.press(`${modifier}+KeyF`); - - // await expect(page.getByPlaceholder('Search requests...')).toBeVisible({ timeout: 3000 }); - await expect(page.getByTestId('sidebar-search-input')).toBeVisible({ timeout: 3000 }); - await page.getByTitle('Search requests').click(); - }); - - test('customized Alt+F opens sidebar search', async ({ 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({ timeout: 2000 }); - - await page.keyboard.down('Backspace'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyF'); - await page.keyboard.up('KeyF'); - await page.keyboard.up('Alt'); - - // Press Cmd/Ctrl+T to open sidebar search - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyF'); - await page.keyboard.up('KeyF'); - await page.keyboard.up('Alt'); - - await expect(page.getByTestId('sidebar-search-input')).toBeVisible({ timeout: 2000 }); - await page.getByTitle('Search requests').click(); - }); - }); - - test.describe('SHORTCUT: New request', () => { - test('default Cmd/Ctrl+N open new request modal', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - await page.locator('.collection-item-name').filter({ hasText: 'kb-folder' }).click(); - - await page.keyboard.press(`${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({ hasText: 'nr-folder' })).toBeVisible({ timeout: 2000 }); - }); - - test('customized Alt+N open new request modal', async ({ page, createTmpDir }) => { - // 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({ timeout: 2000 }); - - await page.keyboard.down('Backspace'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyN'); - await page.keyboard.up('KeyN'); - await page.keyboard.up('Alt'); - - await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).click(); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyN'); - await page.keyboard.up('KeyN'); - await page.keyboard.up('Alt'); - - 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({ hasText: 'nr-collection' })).toBeVisible({ timeout: 2000 }); - }); - }); - - test.describe('SHORTCUT: Rename Item', () => { - test('default Cmd/Ctrl+R open rename item modal for request', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).dblclick(); - await openRequest(page, 'kb-collection', 'req-1', { persist: true }); - await page.keyboard.press(`${modifier}+KeyR`); - - // Verify rename modal opens - const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename request/i }); - await expect(renameModal).toBeVisible({ timeout: 3000 }); - - // 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({ hasText: 'req-1' })).toBeVisible({ timeout: 2000 }); - await expect(page.locator('.collection-item-name').filter({ hasText: 'req-1-rename' })).toBeVisible({ timeout: 2000 }); - }); - - test('default Cmd/Ctrl+R open rename item modal for folder', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - await page.locator('.collection-item-name').filter({ hasText: 'kb-folder' }).dblclick(); - await page.keyboard.press(`${modifier}+KeyR`); - - // Verify rename modal opens - const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename folder/i }); - await expect(renameModal).toBeVisible({ timeout: 3000 }); - - // 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({ hasText: 'kb-folder-renamed' })).toBeVisible({ timeout: 2000 }); - }); - - test('default Cmd/Ctrl+R open rename item modal for collection', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).click(); - await page.keyboard.press(`${modifier}+KeyR`); - - // Verify rename modal opens - const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename collection/i }); - await expect(renameModal).toBeVisible({ timeout: 3000 }); - - // 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.locator('.collection-name').filter({ hasText: 'kb-collection-renamed' })).toBeVisible({ timeout: 3000 }); - }); - - test('customized Alt+X open rename item modal for request', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - // 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({ timeout: 2000 }); - - await page.keyboard.down('Backspace'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyX'); - await page.keyboard.up('KeyX'); - await page.keyboard.up('Alt'); - - await openRequest(page, collectionName, 'req-1', { persist: true }); - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyX'); - await page.keyboard.up('KeyX'); - await page.keyboard.up('Alt'); - - // Verify rename modal opens - const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename request/i }); - await expect(renameModal).toBeVisible({ timeout: 3000 }); - - // 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({ hasText: 'req-1-renamed-altx' })).toBeVisible({ timeout: 2000 }); - }); - - test('customized Alt+R open rename item modal for folder', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - await remapKeybinding(page, 'renameItem', async () => { - await page.keyboard.press('Alt+KeyX'); - }); - - await createFolder(page, 'kb-folder-rename-src', collectionName, true); - await openFolderSettingsTab(page, 'kb-folder-rename-src'); - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyX'); - await page.keyboard.up('KeyX'); - await page.keyboard.up('Alt'); - - // Verify rename modal opens - const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename folder/i }); - await expect(renameModal).toBeVisible({ timeout: 3000 }); - - // 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({ hasText: 'kb-folder-renamed-altx-src' })).toBeVisible({ timeout: 2000 }); - }); - - test('customized Alt+R open rename item modal for collection', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - await remapKeybinding(page, 'renameItem', async () => { - await page.keyboard.press('Alt+KeyX'); - }); - - await page.locator('.collection-name').filter({ hasText: collectionName }).click(); - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyX'); - await page.keyboard.up('KeyX'); - await page.keyboard.up('Alt'); - - // Verify rename modal opens - const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename collection/i }); - await expect(renameModal).toBeVisible({ timeout: 3000 }); - - // 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.locator('.collection-name').filter({ hasText: 'kb-collection-renamed-altx' })).toBeVisible({ timeout: 2000 }); - }); - }); - - test.describe('SHORTCUT: Clone Item', () => { - test('default Cmd/Ctrl+D open clone item modal for request', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - await openRequest(page, 'kb-collection', 'req-1', { persist: true }); - await page.keyboard.press(`${modifier}+KeyD`); - - // Verify clone modal opens - const cloneModal = page.locator('.bruno-modal-card').filter({ hasText: /clone request/i }); - await expect(cloneModal).toBeVisible({ timeout: 3000 }); - - // 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({ hasText: 'req-1 clone 1' })).toBeVisible({ timeout: 2000 }); - }); - - test('default Cmd/Ctrl+D open clone item modal for folder', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - await page.locator('.collection-item-name').filter({ hasText: 'kb-folder' }).dblclick(); - await page.keyboard.press(`${modifier}+KeyD`); - - // Verify clone modal opens - const cloneModal = page.locator('.bruno-modal-card').filter({ hasText: /clone folder/i }); - await expect(cloneModal).toBeVisible({ timeout: 3000 }); - - // 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({ hasText: 'kb-folder clone 1' })).toBeVisible({ timeout: 2000 }); - }); - - test('customized Alt+D open clone item modal for request', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - // 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({ timeout: 2000 }); - - await page.keyboard.down('Backspace'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyD'); - await page.keyboard.up('KeyD'); - await page.keyboard.up('Alt'); - - await openRequest(page, 'kb-collection', 'req-2', { persist: true }); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyD'); - await page.keyboard.up('KeyD'); - await page.keyboard.up('Alt'); - - // Verify clone modal opens - const cloneModal = page.locator('.bruno-modal-card').filter({ hasText: /clone request/i }); - await expect(cloneModal).toBeVisible({ timeout: 3000 }); - - // 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({ hasText: 'req-2 clone 1' })).toBeVisible({ timeout: 2000 }); - }); - - test('customized Alt+D open clone item modal for folder', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - await createFolder(page, 'kb-folder-clone-src', collectionName, true); - await openCollection(page, collectionName); - await page.locator('.collection-item-name').filter({ hasText: 'kb-folder-clone-src' }).first().click(); - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyD'); - await page.keyboard.up('KeyD'); - await page.keyboard.up('Alt'); - - // Verify clone modal opens - const cloneModal = page.locator('.bruno-modal-card').filter({ hasText: /clone folder/i }); - await expect(cloneModal).toBeVisible({ timeout: 3000 }); - - // 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({ hasText: 'kb-folder-clone-src copy 1' })).toBeVisible({ timeout: 2000 }); - }); - }); - - test.describe('SHORTCUT: Copy Paste Item', () => { - test('default Cmd/Ctrl+C/V copy paste item for request', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - await openRequest(page, 'kb-collection', 'req-3', { persist: true }); - await page.keyboard.press(`${modifier}+KeyC`); - await page.keyboard.press(`${modifier}+KeyV`); - - // Verify cloned request appears in sidebar - await expect(page.locator('.collection-item-name').filter({ hasText: 'req-3 (1)' })).toBeVisible({ timeout: 2000 }); - }); - - test('default Cmd/Ctrl+C/V copy paste item for folder', async ({ page }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - await openRequest(page, collectionName, 'kb-folder', { persist: true }); - await page.keyboard.press(`${modifier}+KeyC`); - await page.keyboard.press(`${modifier}+KeyV`); - - // Verify copied item appears in sidebar as child of folder - await expect(page.locator('.collection-item-name').filter({ hasText: 'kb-folder' })).toHaveCount(2); - }); - - test('customized Alt+C/V copy paste item for request', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - // 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({ timeout: 2000 }); - - await page.keyboard.down('Backspace'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyC'); - await page.keyboard.up('KeyC'); - await page.keyboard.up('Alt'); - - // 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({ timeout: 2000 }); - - await page.keyboard.down('Backspace'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyV'); - await page.keyboard.up('KeyV'); - await page.keyboard.up('Alt'); - - await openRequest(page, 'kb-collection', 'req-4', { persist: true }); - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyC'); - await page.keyboard.up('KeyC'); - await page.keyboard.up('Alt'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyV'); - await page.keyboard.up('KeyV'); - await page.keyboard.up('Alt'); - - // Verify cloned request appears in sidebar - await expect(page.locator('.collection-item-name').filter({ hasText: 'req-4 (1)' })).toBeVisible({ timeout: 2000 }); - }); - - test('customized Alt+C/V copy paste item for folder', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - await remapKeybinding(page, 'copyItem', async () => { - await page.keyboard.press('Alt+KeyC'); - }); - await remapKeybinding(page, 'pasteItem', async () => { - await page.keyboard.press('Alt+KeyV'); - }); - - await createFolder(page, 'kb-folder-copy-src', collectionName, true); - await openFolderSettingsTab(page, 'kb-folder-copy-src'); - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyC'); - await page.keyboard.up('KeyC'); - await page.keyboard.up('Alt'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyV'); - await page.keyboard.up('KeyV'); - await page.keyboard.up('Alt'); - - // Verify copied item appears in sidebar as child of folder - await expect(page.locator('.collection-item-name').filter({ hasText: 'kb-folder-copy-src' })).toHaveCount(2); - }); - }); - - test.describe('SHORTCUT: Collapse Sidebar', () => { - test('default collapse sidebar using default Cmd/Ctrl+\\', async ({ page, createTmpDir }) => { - await expect(page.getByTestId('collections')).toBeVisible(); - await page.locator('body').click({ position: { x: 1, y: 1 } }); - - // Press Cmd/Ctrl+\ to collapse sidebar - await page.keyboard.press(`${modifier}+Backslash`); - - await expect.poll( - () => page.locator('aside.sidebar').evaluate((el) => parseFloat(getComputedStyle(el).width)), - { timeout: 5000 } - ).toBeLessThan(5); - - // Press Cmd/Ctrl+\ to collapse expanded sidebar - await page.keyboard.press(`${modifier}+Backslash`); - - await expect.poll( - () => page.locator('aside.sidebar').evaluate((el) => parseFloat(getComputedStyle(el).width)), - { timeout: 5000 } - ).toBeGreaterThan(200); - }); - - test('should expand -> collapse -> expand the sidebar using customized Shift+G', async ({ page, createTmpDir }) => { - // 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({ timeout: 2000 }); - - await page.keyboard.down('Backspace'); - - await page.keyboard.down('Shift'); - await page.keyboard.down('KeyG'); - await page.keyboard.up('KeyG'); - await page.keyboard.up('Shift'); - - await closePreferencesTab(page); - - // Trigger the remapped shortcut to collapse sidebar - await page.keyboard.down('Shift'); - await page.keyboard.down('KeyG'); - await page.keyboard.up('KeyG'); - await page.keyboard.up('Shift'); - - // Verify sidebar collapsed to 0px - await expect.poll( - () => page.locator('aside.sidebar').evaluate((el) => getComputedStyle(el).width), - { timeout: 5000 } - ).toBe('0px'); - - // Trigger the remapped shortcut to expand sidebar - await page.keyboard.down('Shift'); - await page.keyboard.down('KeyG'); - await page.keyboard.up('KeyG'); - await page.keyboard.up('Shift'); - - await expect.poll( - () => page.locator('aside.sidebar').evaluate((el) => getComputedStyle(el).width), - { timeout: 5000 } - ).toBe('250px'); - }); - }); - }); - - test.describe('DEVELOPER TOOLS', () => { - test.describe('SHORTCUT: Open Terminal', () => { - test('default Cmd/Ctrl+T opens terminal', async ({ page, createTmpDir }) => { - // Open Collection-Settings tab (double-click collection name) - await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).click(); - await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 }); - - // Press Cmd/Ctrl+T to open terminal at workspace level - await page.keyboard.press(`${modifier}+KeyT`); - - // Verify terminal session is visible using data-testid - const collectionTerminalSession = page.getByTestId('session-list-0'); - await expect(collectionTerminalSession).toBeVisible({ timeout: 2000 }); - - 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({ hasText: 'kb-terminal-folder' }).dblclick(); - await expect(page.locator('.request-tab').filter({ hasText: 'kb-terminal-folder' })).toBeVisible({ timeout: 2000 }); - - await page.keyboard.press(`${modifier}+KeyT`); - const folderTerminalSession = page.getByTestId('session-list-1'); - await expect(folderTerminalSession).toBeVisible({ timeout: 2000 }); - - // Verify the terminal session name is the workspace name (default_workspace) - const folderSessionName = folderTerminalSession; - await expect(folderSessionName).toContainText('kb-terminal-folder'); - - // Close all sessions with terminal tab - await page.getByTestId('session-close-1').click(); - await page.waitForTimeout(1000); - await page.getByTestId('session-close-0').click(); - await expect(page.getByTestId('session-close-0')).not.toBeVisible({ timeout: 3000 }); - await page.getByTitle('Close console').click(); - }); - - test('customized Alt+T opens terminal', async ({ page, createTmpDir }) => { - // 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({ timeout: 2000 }); - - await page.keyboard.down('Backspace'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyT'); - await page.keyboard.up('KeyT'); - await page.keyboard.up('Alt'); - - await page.locator('.collection-name').filter({ hasText: 'kb-collection' }).click(); - await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 }); - - // Press Cmd/Ctrl+T to open terminal at workspace level - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyT'); - await page.keyboard.up('KeyT'); - await page.keyboard.up('Alt'); - await page.waitForTimeout(500); - - // Verify terminal session is visible using data-testid - const collectionTerminalSession = page.getByTestId('session-list-0'); - await expect(collectionTerminalSession).toBeVisible({ timeout: 2000 }); - - const collectionSession = collectionTerminalSession; - await expect(collectionSession).toContainText('kb-collection'); - - // Open folder settings - await page.locator('.collection-item-name').filter({ hasText: 'kb-terminal-folder' }).dblclick(); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyT'); - await page.keyboard.up('KeyT'); - await page.keyboard.up('Alt'); - const folderTerminalSession = page.getByTestId('session-list-1'); - await expect(folderTerminalSession).toBeVisible({ timeout: 2000 }); - - // Verify the terminal session name is the workspace name (default_workspace) - const folderSessionName = folderTerminalSession; - await expect(folderSessionName).toContainText('kb-terminal-folder'); - - // Close all sessions with terminal tab - await page.getByTestId('session-close-1').click(); - await page.waitForTimeout(1000); - await page.getByTestId('session-close-0').click(); - await expect(page.getByTestId('session-close-0')).not.toBeVisible({ timeout: 3000 }); - await page.getByTitle('Close console').click(); - }); - }); - }); - - test.describe('LAYOUT', () => { - test.describe('SHORTCUT: Change Layout', () => { - test('default Cmd/Ctrl+J change layout orientation', async ({ page, createTmpDir }) => { - await openRequest(page, 'kb-collection', 'req-5', { persist: true }); - - // Press Cmd/Ctrl+J to change layout - await page.keyboard.press(`${modifier}+KeyJ`); - - await expect( - page.getByTestId('response-layout-toggle-btn') - ).toHaveAttribute('title', 'Switch to horizontal layout', { timeout: 2000 }); - - // Press Cmd/Ctrl+J to change layout - await page.keyboard.press(`${modifier}+KeyJ`); - - await expect( - page.getByTestId('response-layout-toggle-btn') - ).toHaveAttribute('title', 'Switch to vertical layout', { timeout: 2000 }); - - // Press Cmd/Ctrl+J to change layout - await page.keyboard.press(`${modifier}+KeyJ`); - - await expect( - page.getByTestId('response-layout-toggle-btn') - ).toHaveAttribute('title', 'Switch to horizontal layout', { timeout: 2000 }); - }); - - test('customized Alt+Shift+Y change layout orientation', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - // Remap changeLayout to Alt+D - 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({ timeout: 2000 }); - - await page.keyboard.down('Backspace'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Shift'); - await page.keyboard.up('Alt'); - - await openRequest(page, 'kb-collection', 'req-5', { persist: true }); - - await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Shift'); - await page.keyboard.up('Alt'); - - await expect( - page.getByTestId('response-layout-toggle-btn') - ).toHaveAttribute('title', 'Switch to vertical layout', { timeout: 2000 }); - - // Press Cmd/Ctrl+J to change layout - await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Shift'); - await page.keyboard.up('Alt'); - - await expect( - page.getByTestId('response-layout-toggle-btn') - ).toHaveAttribute('title', 'Switch to horizontal layout', { timeout: 2000 }); - - // Press Cmd/Ctrl+J to change layout - await page.keyboard.down('Alt'); - await page.keyboard.down('Shift'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Shift'); - await page.keyboard.up('Alt'); - - await expect( - page.getByTestId('response-layout-toggle-btn') - ).toHaveAttribute('title', 'Switch to vertical layout', { timeout: 2000 }); - }); - }); - - test.describe('SHORTCUT: Open Preferences', () => { - test('default Cmd/Ctrl+, open preferences', async ({ page }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - // Press Cmd/Ctrl+J to change layout - await page.keyboard.down(modifier); - await page.keyboard.down('Comma'); - await page.keyboard.up('Comma'); - await page.keyboard.up(modifier); - - await expect(page.locator('.request-tab').filter({ hasText: 'Preferences' })).toBeVisible({ timeout: 3000 }); - }); - - test('customized Cmd/Ctrl+P open preferences', async ({ 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({ timeout: 2000 }); - - await page.keyboard.down('Backspace'); - - await page.keyboard.down(modifier); - await page.keyboard.down('KeyP'); - await page.keyboard.up('KeyP'); - await page.keyboard.up(modifier); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - // Press Cmd/Ctrl+J to change layout - await page.keyboard.down(modifier); - await page.keyboard.down('KeyP'); - await page.keyboard.up('KeyP'); - await page.keyboard.up(modifier); - - await expect(page.locator('.request-tab').filter({ hasText: 'Preferences' })).toBeVisible({ timeout: 3000 }); - }); - }); - }); - - test.describe('SEARCH', () => { - test.describe('SHORTCUT: Global Search', () => { - test('default Cmd/Ctrl+K Global Search Modal', async ({ page, createTmpDir }) => { - // Press Cmd/Ctrl+K to global search modal - await page.keyboard.press(`${modifier}+KeyK`); - - await page.keyboard.down(modifier); - await page.keyboard.down('KeyK'); - await page.keyboard.up('KeyK'); - await page.keyboard.up(modifier); - - await page.getByTestId('global-search-input').click(); - await expect(page.getByTestId('global-search-input')).toBeVisible({ timeout: 2000 }); - - // await page.waitForTimeout(500); - await page.keyboard.down('Escape'); - await page.keyboard.up('Escape'); - }); - - test('customized Alt+K Global Search Modal', async ({ page, createTmpDir }) => { - // Remap globalSearch to Alt+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({ timeout: 2000 }); - - await page.keyboard.down('Backspace'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyK'); - await page.keyboard.up('KeyK'); - await page.keyboard.up('Alt'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyK'); - await page.keyboard.up('KeyK'); - await page.keyboard.up('Alt'); - - await page.getByTestId('global-search-input').click(); - await expect(page.getByTestId('global-search-input')).toBeVisible({ timeout: 2000 }); - - await page.keyboard.down('Escape'); - await page.keyboard.up('Escape'); - }); - }); - }); - - test.describe('SHORTCUT: Edit Environment', () => { - test('open environment tab of collection Cmd/Ctrl+E', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - await openRequest(page, 'kb-collection', 'req-7', { persist: true }); - - await page.keyboard.down(modifier); - await page.keyboard.down('KeyE'); - await page.keyboard.up('KeyE'); - await page.keyboard.up(modifier); - - await expect(page.locator('.request-tab').filter({ hasText: 'Environments' })).toBeVisible({ timeout: 2000 }); - }); - - test('open environment tab of collection customized Alt+E', async ({ page, createTmpDir }) => { - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - // 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({ timeout: 2000 }); - - await page.keyboard.down('Backspace'); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyE'); - await page.keyboard.up('KeyE'); - await page.keyboard.up('Alt'); - - await openRequest(page, 'kb-collection', 'req-7', { persist: true }); - - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyE'); - await page.keyboard.up('KeyE'); - await page.keyboard.up('Alt'); - - await expect(page.locator('.request-tab').filter({ hasText: 'Environments' })).toBeVisible({ timeout: 2000 }); - - // Rest Default - just in case to not fail shortcuts in other places - await openKeybindingsTab(page); - await page.getByTestId('reset-all-keybindings-btn').click({ timeout: 2000 }); - }); - }); - - test.describe('REQUESTS', () => { - test.describe('SHORTCUT: Send Request from CodeEditor (Cmd/Ctrl+Enter)', () => { - test('sends request when cursor is in JSON body editor', async ({ page }) => { - // Close existing tabs - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - // 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"', { timeout: 5000 }); - - // Cursor is still in the body CodeMirror — press Cmd/Ctrl+Enter to send - await page.keyboard.press(`${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 ({ page }) => { - // Close existing tabs - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - 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"', { timeout: 5000 }); - - // First send to populate response - await page.keyboard.press(`${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 page.keyboard.press(`${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 ({ page }) => { - // Close existing tabs - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - 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 page.keyboard.press(`${modifier}+Enter`); - - // Verify a 200 response came back - await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 15000 }); - }); - }); - - test.describe('SHORTCUT: Send Request from CodeEditor (customized Shift+Enter)', () => { - test('customized Shift+Enter sends request when cursor is in JSON body editor', async ({ page }) => { - // Close existing tabs - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - // 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({ timeout: 2000 }); - - await page.keyboard.down('Backspace'); - - await page.keyboard.down('Shift'); - await page.keyboard.down('Enter'); - await page.keyboard.up('Enter'); - await page.keyboard.up('Shift'); - - // await closePreferencesTab(page); - // 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"', { timeout: 5000 }); - - // Cursor is still in the body CodeMirror — press Shift+Enter (customized) to send - await page.keyboard.press('Shift+Enter'); - - await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 15000 }); - - // Reset Default - await openKeybindingsTab(page); - await page.getByTestId('reset-all-keybindings-btn').click({ timeout: 2000 }); - }); - - test('customized Shift+Enter sends request when cursor is in response body editor', async ({ page }) => { - // Close existing tabs - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - // 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({ timeout: 2000 }); - - await page.keyboard.down('Backspace'); - - await page.keyboard.down('Shift'); - await page.keyboard.down('Enter'); - await page.keyboard.up('Enter'); - await page.keyboard.up('Shift'); - - // await closePreferencesTab(page); - 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"', { timeout: 5000 }); - - // First send with Shift+Enter to populate response - await page.keyboard.press('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 page.keyboard.press('Shift+Enter'); - - await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 15000 }); - - // Reset Default - await openKeybindingsTab(page); - await page.getByTestId('reset-all-keybindings-btn').click({ timeout: 2000 }); - }); - - test('customized Shift+Enter sends request when cursor is in pre-request Vars value editor', async ({ page }) => { - // Close existing tabs - await page.keyboard.down('Alt'); - await page.keyboard.down('KeyY'); - await page.keyboard.up('KeyY'); - await page.keyboard.up('Alt'); - - // 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({ timeout: 2000 }); - - await page.keyboard.down('Backspace'); - - await page.keyboard.down('Shift'); - await page.keyboard.down('Enter'); - await page.keyboard.up('Enter'); - await page.keyboard.up('Shift'); - - // await closePreferencesTab(page); - 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 page.keyboard.press('Shift+Enter'); - - await expect(page.getByTestId('response-status-code')).toContainText('200', { timeout: 15000 }); - - // Reset Default - await openKeybindingsTab(page); - await page.getByTestId('reset-all-keybindings-btn').click({ timeout: 2000 }); - }); - }); - }); -});