Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 243 additions & 0 deletions tests/shortcuts/bound-actions-developer-tools.spec.ts
Original file line number Diff line number Diff line change
@@ -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<string>) => {
await closeAllCollections(page);
const path = await createTmpDir('kb-collection-path');
await createCollection(page, collectionName, path);

await createFolder(page, 'kb-folder', collectionName, true);
await createFolder(page, 'kb-draft-folder', collectionName, true);
await createFolder(page, 'kb-terminal-folder', collectionName, true);
};

const checkIfRequestExists = async (page: Page, requestName: string) => {
await openCollection(page, collectionName);
const request = page.getByTestId('collections').locator('.collection-item-name').filter({ hasText: requestName }).first();
return (await request.count()) > 0;
};

const openRequest = async (...args: Parameters<typeof openRequestBase>) => {
const [page, targetCollectionName, requestName] = args;
if (
targetCollectionName === collectionName
&& baseRequests.includes(requestName)
&& !(await checkIfRequestExists(page, requestName))
) {
await createRequest(page, requestName, targetCollectionName);
}

return openRequestBase(...args);
};

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<void>, 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<void>
) => {
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 });
Comment on lines +180 to +184
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace these terminal-cleanup sleeps with locator-based waits.

Both waitForTimeout(1000) calls reintroduce the timing dependency this PR is trying to remove. Wait for the closed session row/button to disappear before moving to the next click.

As per coding guidelines "Try to reduce usage of page.waitForTimeout(); in code unless absolutely necessary and the locator cannot be found using existing expect() playwright calls" and "Ensure tests are deterministic and reproducible. No randomness, timing dependencies, or environment-specific assumptions without explicit control."

Also applies to: 235-238

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/shortcuts/bound-actions-developer-tools.spec.ts` around lines 180 -
184, Replace the hard sleep calls with locator-based waits: after clicking the
close button (e.g., page.getByTestId('session-close-1') or 'session-close-0'),
wait for the corresponding session row/button locator to become hidden/detached
using Playwright's expect(...).not.toBeVisible or locator.waitFor({ state:
'hidden' | 'detached' ) instead of page.waitForTimeout(1000); do this for the
occurrences around the session-close clicks (the block using
page.getByTestId('session-close-1') and the later block at the other occurrence)
so the test proceeds only after the UI element is actually removed.

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();
});
});
});
});
Loading
Loading