diff --git a/packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js b/packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js
index b625d59be..1b22ea215 100644
--- a/packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js
+++ b/packages/bruno-app/src/components/AppTitleBar/StyledWrapper.js
@@ -29,7 +29,6 @@ const Wrapper = styled.div`
.workspace-name-container,
.dropdown-item,
.home-button,
- .env-selector-trigger,
.dropdown,
button {
-webkit-app-region: no-drag;
@@ -49,25 +48,6 @@ const Wrapper = styled.div`
margin-left: 0px;
}
- /* Home button */
- .home-button {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 28px;
- height: 28px;
- border: none;
- background: transparent;
- border-radius: 6px;
- cursor: pointer;
- color: ${(props) => props.theme.sidebar.color};
- transition: background 0.15s ease;
-
- &:hover {
- background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
- }
- }
-
/* Workspace Name Dropdown Trigger */
.workspace-name-container {
display: flex;
@@ -112,7 +92,7 @@ const Wrapper = styled.div`
.bruno-text {
font-size: 13px;
font-weight: 600;
- color: ${(props) => props.theme.sidebar.muted};
+ color: ${(props) => props.theme.text};
letter-spacing: 0.5px;
}
}
@@ -125,36 +105,6 @@ const Wrapper = styled.div`
flex-shrink: 0;
}
- /* Action buttons in right section */
- .titlebar-action-button {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 28px;
- height: 28px;
- border: none;
- background: transparent;
- border-radius: 6px;
- cursor: pointer;
- color: ${(props) => props.theme.sidebar.color};
- transition: background 0.15s ease;
-
- &:hover {
- background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
- }
-
- svg {
- color: ${(props) => props.theme.sidebar.color};
- }
- }
-
- /* Draggable region */
- .drag-region {
- flex: 1;
- height: 100%;
- -webkit-app-region: drag;
- }
-
/* Workspace Dropdown Styles */
.workspace-item {
display: flex;
diff --git a/packages/bruno-app/src/components/AppTitleBar/index.js b/packages/bruno-app/src/components/AppTitleBar/index.js
index 5f3dce40c..d573ed656 100644
--- a/packages/bruno-app/src/components/AppTitleBar/index.js
+++ b/packages/bruno-app/src/components/AppTitleBar/index.js
@@ -1,5 +1,6 @@
+import React from 'react';
import { IconCheck, IconChevronDown, IconFolder, IconHome, IconLayoutColumns, IconLayoutRows, IconPin, IconPinned, IconPlus } from '@tabler/icons';
-import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
import toast from 'react-hot-toast';
import { useDispatch, useSelector } from 'react-redux';
@@ -9,7 +10,8 @@ import { openWorkspaceDialog, switchWorkspace } from 'providers/ReduxStore/slice
import { sortWorkspaces, toggleWorkspacePin } from 'utils/workspaces';
import Bruno from 'components/Bruno';
-import Dropdown from 'components/Dropdown';
+import MenuDropdown from 'ui/MenuDropdown';
+import ActionIcon from 'ui/ActionIcon';
import IconSidebarToggle from 'components/Icons/IconSidebarToggle';
import CreateWorkspace from 'components/WorkspaceSidebar/CreateWorkspace';
@@ -53,13 +55,10 @@ const AppTitleBar = () => {
}, [workspaces, preferences]);
const [createWorkspaceModalOpen, setCreateWorkspaceModalOpen] = useState(false);
- const [showWorkspaceDropdown, setShowWorkspaceDropdown] = useState(false);
- const workspaceDropdownTippyRef = useRef();
- const onWorkspaceDropdownCreate = (ref) => (workspaceDropdownTippyRef.current = ref);
const WorkspaceName = forwardRef((props, ref) => {
return (
-
setShowWorkspaceDropdown(!showWorkspaceDropdown)}>
+
{toTitleCase(activeWorkspace?.name) || 'Default Workspace'}
@@ -72,12 +71,10 @@ const AppTitleBar = () => {
const handleWorkspaceSwitch = (workspaceUid) => {
dispatch(switchWorkspace(workspaceUid));
- setShowWorkspaceDropdown(false);
toast.success(`Switched to ${workspaces.find((w) => w.uid === workspaceUid)?.name}`);
};
const handleOpenWorkspace = async () => {
- setShowWorkspaceDropdown(false);
try {
await dispatch(openWorkspaceDialog());
toast.success('Workspace opened successfully');
@@ -87,7 +84,6 @@ const AppTitleBar = () => {
};
const handleCreateWorkspace = () => {
- setShowWorkspaceDropdown(false);
setCreateWorkspaceModalOpen(true);
};
@@ -124,6 +120,59 @@ const AppTitleBar = () => {
dispatch(savePreferences(updatedPreferences));
};
+ // Build workspace menu items
+ const workspaceMenuItems = useMemo(() => {
+ const items = sortedWorkspaces.map((workspace) => {
+ const isActive = workspace.uid === activeWorkspaceUid;
+ const isPinned = preferences?.workspaces?.pinnedWorkspaceUids?.includes(workspace.uid);
+
+ return {
+ id: workspace.uid,
+ label: toTitleCase(workspace.name),
+ onClick: () => handleWorkspaceSwitch(workspace.uid),
+ className: `workspace-item ${isActive ? 'active' : ''}`,
+ rightSection: (
+
+ {workspace.type !== 'default' && (
+
handlePinWorkspace(workspace.uid, e)}
+ label={isPinned ? 'Unpin workspace' : 'Pin workspace'}
+ size="sm"
+ >
+ {isPinned ? (
+
+ ) : (
+
+ )}
+
+ )}
+ {isActive &&
}
+
+ )
+ };
+ });
+
+ // Add label and action items
+ items.push(
+ { type: 'label', label: 'Workspaces' },
+ {
+ id: 'create-workspace',
+ leftSection: IconPlus,
+ label: 'Create workspace',
+ onClick: handleCreateWorkspace
+ },
+ {
+ id: 'open-workspace',
+ leftSection: IconFolder,
+ label: 'Open workspace',
+ onClick: handleOpenWorkspace
+ }
+ );
+
+ return items;
+ }, [sortedWorkspaces, activeWorkspaceUid, preferences, handlePinWorkspace]);
+
return (
{createWorkspaceModalOpen && (
@@ -133,61 +182,24 @@ const AppTitleBar = () => {
{/* Left section: Home + Workspace */}
-
+
{/* Workspace Dropdown */}
-
}
+
setShowWorkspaceDropdown(false)}
+ selectedItemId={activeWorkspaceUid}
>
- {sortedWorkspaces.map((workspace) => {
- const isActive = workspace.uid === activeWorkspaceUid;
- const isPinned = preferences?.workspaces?.pinnedWorkspaceUids?.includes(workspace.uid);
-
- return (
- handleWorkspaceSwitch(workspace.uid)}
- >
-
{toTitleCase(workspace.name)}
-
- {workspace.type !== 'default' && (
-
- )}
- {isActive && }
-
-
- );
- })}
-
- Workspaces
-
-
-
- Create workspace
-
-
-
- Open workspace
-
-
+
+
{/* Center section: Bruno logo + text */}
@@ -199,33 +211,30 @@ const AppTitleBar = () => {
{/* Right section: Action buttons */}
{/* Toggle sidebar */}
-
+
{/* Toggle devtools */}
-
+
{/* Toggle vertical layout */}
-
-
+
diff --git a/packages/bruno-app/src/components/Dropdown/StyledWrapper.js b/packages/bruno-app/src/components/Dropdown/StyledWrapper.js
index c9a44bd9f..7452a4f75 100644
--- a/packages/bruno-app/src/components/Dropdown/StyledWrapper.js
+++ b/packages/bruno-app/src/components/Dropdown/StyledWrapper.js
@@ -25,6 +25,16 @@ const Wrapper = styled.div`
padding-top: 0;
padding-bottom: 0;
+ [role="menu"] {
+ outline: none;
+ &:focus {
+ outline: none;
+ }
+ &:focus-visible {
+ outline: none;
+ }
+ }
+
.label-item {
display: flex;
align-items: center;
@@ -59,6 +69,10 @@ const Wrapper = styled.div`
}
}
+ .dropdown-label {
+ flex: 1;
+ }
+
.dropdown-icon {
flex-shrink: 0;
width: 16px;
@@ -70,10 +84,31 @@ const Wrapper = styled.div`
opacity: 0.8;
}
+ .dropdown-right-section {
+ margin-left: auto;
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
&:hover:not(:disabled) {
background-color: ${(props) => props.theme.dropdown.hoverBg};
}
+ &.selected-focused:not(:disabled) {
+ background-color: ${(props) => props.theme.dropdown.hoverBg};
+ }
+
+ &:focus-visible:not(:disabled) {
+ outline: none;
+ background-color: ${(props) => props.theme.dropdown.hoverBg};
+ }
+
+ &:focus:not(:focus-visible) {
+ outline: none;
+ }
+
&:disabled {
cursor: not-allowed;
opacity: 0.5;
diff --git a/packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
index 4c20e241b..0285c8b0b 100644
--- a/packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
+++ b/packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js
@@ -1,7 +1,7 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
- padding: 8px 4px 6px 10px;
+
.sidebar-header {
display: flex;
@@ -9,6 +9,7 @@ const StyledWrapper = styled.div`
justify-content: space-between;
gap: 8px;
border-bottom: 1px solid ${(props) => props.theme.sidebar.collection.item.hoverBg};
+ padding: 6px 4px 6px 10px;
}
/* Section Title (single view mode) */
diff --git a/packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js b/packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js
index 397a3f056..b53aff940 100644
--- a/packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js
+++ b/packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js
@@ -13,7 +13,7 @@ import {
IconSquareX,
IconTrash
} from '@tabler/icons';
-import { useRef, useState } from 'react';
+import { useState } from 'react';
import toast from 'react-hot-toast';
import { useDispatch, useSelector } from 'react-redux';
@@ -22,7 +22,8 @@ import { sortCollections } from 'providers/ReduxStore/slices/collections/index';
import { importCollectionInWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import { openApiSpec } from 'providers/ReduxStore/slices/apiSpec';
-import Dropdown from 'components/Dropdown';
+import MenuDropdown from 'ui/MenuDropdown';
+import ActionIcon from 'ui/ActionIcon';
import ImportCollection from 'components/Sidebar/ImportCollection';
import ImportCollectionLocation from 'components/Sidebar/ImportCollectionLocation';
import CreateApiSpec from 'components/Sidebar/ApiSpecs/CreateApiSpec';
@@ -75,12 +76,6 @@ const SidebarHeader = ({ setShowSearch }) => {
});
};
- const addDropdownTippyRef = useRef();
- const onAddDropdownCreate = (ref) => (addDropdownTippyRef.current = ref);
-
- const actionsDropdownTippyRef = useRef();
- const onActionsDropdownCreate = (ref) => (actionsDropdownTippyRef.current = ref);
-
const handleToggleSearch = () => {
if (setShowSearch) {
setShowSearch((prev) => !prev);
@@ -183,123 +178,112 @@ const SidebarHeader = ({ setShowSearch }) => {
>
);
+ // Configuration for Add/Create dropdown items
+ const addDropdownItems = [
+ {
+ id: 'create',
+ leftSection: IconPlus,
+ label: 'Create collection',
+ onClick: () => {
+ setCreateCollectionModalOpen(true);
+ }
+ },
+ {
+ id: 'import',
+ leftSection: IconDownload,
+ label: 'Import collection',
+ onClick: () => {
+ setImportCollectionModalOpen(true);
+ }
+ },
+ {
+ id: 'open',
+ leftSection: IconFolder,
+ label: 'Open collection',
+ onClick: () => {
+ handleOpenCollection();
+ }
+ },
+ {
+ type: 'label',
+ label: 'API Specs'
+ },
+ {
+ id: 'create-api-spec',
+ leftSection: IconPlus,
+ label: 'Create API Spec',
+ onClick: () => {
+ setCreateApiSpecModalOpen(true);
+ }
+ },
+ {
+ id: 'open-api-spec',
+ leftSection: IconFileCode,
+ label: 'Open API Spec',
+ onClick: () => {
+ handleOpenApiSpec();
+ }
+ }
+ ];
+
+ // Configuration for Actions dropdown items
+ const actionsDropdownItems = [
+ {
+ id: 'sort',
+ leftSection: getSortIcon(),
+ label: getSortLabel(),
+ onClick: () => {
+ handleSortCollections();
+ }
+ },
+ {
+ id: 'close-all',
+ leftSection: IconSquareX,
+ label: 'Close all',
+ onClick: () => {
+ selectAllCollectionsToClose();
+ }
+ }
+ ];
+
// Render Collections-specific actions
const renderCollectionsActions = () => (
<>
-
- {/* Add/Create dropdown */}
-
-
-
- )}
- placement="bottom-end"
- style="new"
- >
- Collections
- {
- setCreateCollectionModalOpen(true);
- addDropdownTippyRef.current?.hide();
- }}
- >
-
- Create collection
-
- {
- addDropdownTippyRef.current?.hide();
- setImportCollectionModalOpen(true);
- }}
- >
-
- Import collection
-
- {
- handleOpenCollection();
- addDropdownTippyRef.current?.hide();
- }}
- >
-
- Open collection
-
+
+
- API Specs
- {
- setCreateApiSpecModalOpen(true);
- addDropdownTippyRef.current?.hide();
- }}
- >
-
- Create API Spec
-
- {
- handleOpenApiSpec();
- addDropdownTippyRef.current?.hide();
- }}
- >
-
- Open API Spec
-
-
-
- {/* Actions dropdown (sort, close all, etc.) */}
-
-
-
- )}
+ {/* Add Collection dropdown */}
+
- {
- handleSortCollections();
- actionsDropdownTippyRef.current?.hide();
- }}
- aria-label="Sort collections"
- title="Sort collections"
- data-testid="sort-collections-button"
+
- {(() => {
- const SortIcon = getSortIcon();
- return ;
- })()}
- {getSortLabel()}
-
- {
- selectAllCollectionsToClose();
- actionsDropdownTippyRef.current?.hide();
- }}
- aria-label="Close all collections"
- title="Close all collections"
- data-testid="close-all-collections-button"
+
+
+
+
+ {/* More Actions dropdown (sort, close all, etc.) */}
+
+
-
- Close all
-
-
+
+
+
{collectionsToClose.length > 0 && (
diff --git a/packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js b/packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js
new file mode 100644
index 000000000..55c6e9044
--- /dev/null
+++ b/packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js
@@ -0,0 +1,53 @@
+import styled, { css } from 'styled-components';
+
+const sizeMap = {
+ xs: 20,
+ sm: 22,
+ md: 24,
+ lg: 28,
+ xl: 32
+};
+
+const variants = {
+ subtle: css`
+ color: ${(props) => props.theme.colors.text.muted};
+ background: transparent;
+ &:hover:not(:disabled) {
+ color: ${(props) => props.theme.text};
+ background: ${(props) => props.theme.dropdown.hoverBg};
+ }
+ `
+};
+
+const StyledWrapper = styled.button`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: all 0.15s ease;
+ padding: 0;
+
+ width: ${(props) => sizeMap[props.$size] || props.$size}px;
+ height: ${(props) => sizeMap[props.$size] || props.$size}px;
+
+ ${(props) => variants[props.$variant] || variants.subtle}
+
+ svg {
+ stroke: currentColor;
+ }
+
+ &:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+
+ ${(props) => props.$colorOnHover && css`
+ &:hover:not(:disabled) {
+ color: ${props.$colorOnHover};
+ }
+ `}
+`;
+
+export default StyledWrapper;
diff --git a/packages/bruno-app/src/ui/ActionIcon/index.js b/packages/bruno-app/src/ui/ActionIcon/index.js
new file mode 100644
index 000000000..fa4ec9761
--- /dev/null
+++ b/packages/bruno-app/src/ui/ActionIcon/index.js
@@ -0,0 +1,52 @@
+import React from 'react';
+import StyledWrapper from './StyledWrapper';
+
+/**
+ * ActionIcon - A reusable icon button component
+ *
+ * @param {Object} props
+ * @param {ReactNode} props.children - The icon component to render
+ * @param {string} props.variant - Visual variant: 'subtle' (default), 'filled', 'outline', etc.
+ * @param {string} props.size - Size of the button: 'sm', 'md', 'lg', etc. (default: 'md')
+ * @param {boolean} props.disabled - Whether the button is disabled
+ * @param {string} props.className - Additional CSS class names
+ * @param {string} props.component - Polymorphic component (default: 'button')
+ * @param {string} props.label - Label for both title and aria-label (preferred)
+ * @param {string} props.title - Title attribute (falls back to label or aria-label)
+ * @param {string} [props.ariaLabel] - Accessibility label (falls back to label or title)
+ * @param {string} props.colorOnHover - Color to apply to icon on hover/focus (e.g., 'red', '#ef4444', 'var(--color-danger)')
+ * @param {Object} props...rest - Other props passed to the underlying element
+ */
+const ActionIcon = ({
+ children,
+ variant = 'subtle',
+ size = 'md',
+ disabled = false,
+ className = '',
+ component: Component = 'button',
+ label,
+ 'aria-label': ariaLabel,
+ colorOnHover,
+ ...rest
+}) => {
+ // Build className array and filter out empty strings
+ const classNames = ['action-icon', className].filter(Boolean).join(' ');
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default ActionIcon;
diff --git a/packages/bruno-app/src/ui/MenuDropdown/index.js b/packages/bruno-app/src/ui/MenuDropdown/index.js
new file mode 100644
index 000000000..e4ef53fc2
--- /dev/null
+++ b/packages/bruno-app/src/ui/MenuDropdown/index.js
@@ -0,0 +1,279 @@
+import React from 'react';
+import { useRef, useCallback, useState } from 'react';
+import Dropdown from 'components/Dropdown';
+
+// Constants
+const NAVIGATION_KEYS = ['ArrowDown', 'ArrowUp', 'Home', 'End', 'Escape'];
+const ACTION_KEYS = ['Enter', ' '];
+
+// Calculate next index for keyboard navigation
+const getNextIndex = (currentIndex, total, key, noFocus) => {
+ if (key === 'Home') return 0;
+ if (key === 'End') return total - 1;
+ if (key === 'ArrowDown') return noFocus ? 0 : (currentIndex + 1) % total;
+ if (key === 'ArrowUp') return noFocus ? total - 1 : (currentIndex - 1 + total) % total;
+ return currentIndex;
+};
+
+/**
+ * MenuDropdown - A reusable dropdown menu component with keyboard navigation
+ *
+ * @param {Object} props
+ * @param {Array} props.items - Array of menu items with structure:
+ * - id: string (unique identifier)
+ * - type: 'item' | 'label' | 'divider' (default: 'item')
+ * - leftSection: React component or React element (rendered on the left side, for items only)
+ * - rightSection: React component or React element (rendered on the right side, for items only)
+ * - label: string (display text for items, or label text for labels; also used for aria-label and title if not provided)
+ * - ariaLabel: string (accessibility label, falls back to label or title if not provided)
+ * - onClick: function (handler when item is clicked, for items only)
+ * - title: string (tooltip text, falls back to label or ariaLabel if not provided)
+ * - testId: string (optional, for testing, for items only)
+ * - disabled: boolean (optional, for items only)
+ * - className: string (optional, additional CSS classes for the item)
+ * @param {ReactNode} props.children - The trigger element (button, etc.)
+ * @param {string} props.placement - Tippy placement (default: 'bottom-end')
+ * @param {string} props.className - Optional className for the dropdown
+ * @param {string} props.selectedItemId - Optional ID of the selected/active item to focus on open
+ * @param {Object} props.dropdownProps - Other props passed to underlying Dropdown component
+ */
+const MenuDropdown = ({
+ items = [],
+ children,
+ placement = 'bottom-end',
+ className,
+ selectedItemId,
+ 'data-testid': testId = 'menu-dropdown',
+ ...dropdownProps
+}) => {
+ const tippyRef = useRef();
+ const [isOpen, setIsOpen] = useState(false);
+
+ // Get all focusable menu items from the menu dropdown
+ const getMenuItems = useCallback(() => {
+ const popper = tippyRef.current?.popper;
+ if (!popper) return [];
+
+ const menuContainer = popper.querySelector('[role="menu"]');
+ if (!menuContainer) return [];
+
+ return Array.from(
+ menuContainer.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])')
+ );
+ }, []);
+
+ // Handle item click and close dropdown
+ const handleItemClick = useCallback((item) => {
+ if (item.disabled) return;
+ item.onClick?.();
+ setIsOpen(false);
+ }, []);
+
+ // Focus a menu item
+ const focusMenuItem = (item, addSelectedClass = false) => {
+ if (item) {
+ // Remove selected class from all items first
+ const menuContainer = item.closest('[role="menu"]');
+ if (menuContainer) {
+ menuContainer.querySelectorAll('.selected-focused').forEach((el) => {
+ el.classList.remove('selected-focused');
+ });
+ }
+
+ if (addSelectedClass) {
+ item.classList.add('selected-focused');
+ }
+ item.focus();
+ item.scrollIntoView({ block: 'nearest' });
+ }
+ };
+
+ // Keyboard navigation handler (handles all keyboard events at menu level)
+ const handleMenuKeyDown = useCallback((e) => {
+ const itemsToNavigate = getMenuItems();
+ if (itemsToNavigate.length === 0) return;
+
+ const currentIndex = itemsToNavigate.findIndex((el) => el === document.activeElement);
+ const isNoMenuItemFocused = currentIndex === -1;
+
+ // Handle Escape
+ if (e.key === 'Escape') {
+ e.preventDefault();
+ e.stopPropagation();
+ setIsOpen(false);
+ return;
+ }
+
+ // Handle action keys (Enter, Space)
+ if (ACTION_KEYS.includes(e.key) && !isNoMenuItemFocused) {
+ e.preventDefault();
+ e.stopPropagation();
+ const currentItem = itemsToNavigate[currentIndex];
+ const itemId = currentItem?.getAttribute('data-item-id');
+ const item = items.find((i) => i.id === itemId);
+ if (item && !item.disabled) {
+ handleItemClick(item);
+ }
+ return;
+ }
+
+ // Handle navigation keys
+ if (NAVIGATION_KEYS.includes(e.key)) {
+ e.preventDefault();
+ e.stopPropagation();
+ const nextIndex = getNextIndex(currentIndex, itemsToNavigate.length, e.key, isNoMenuItemFocused);
+ focusMenuItem(itemsToNavigate[nextIndex], false);
+ }
+ }, [getMenuItems, items, handleItemClick]);
+
+ // Toggle dropdown visibility
+ const handleTriggerClick = useCallback(() => {
+ setIsOpen((prev) => !prev);
+ }, []);
+
+ // Close dropdown when clicking outside
+ const handleClickOutside = useCallback(() => {
+ setIsOpen(false);
+ }, []);
+
+ // Setup Tippy instance
+ const onDropdownCreate = useCallback((ref) => {
+ tippyRef.current = ref;
+ if (ref) {
+ ref.setProps({
+ onShow: () => {
+ // Focus selected item if available, otherwise focus menu container
+ setTimeout(() => {
+ const menuContainer = ref.popper?.querySelector('[role="menu"]');
+ if (!menuContainer) return;
+
+ // If selectedItemId is provided, find and focus that item
+ if (selectedItemId) {
+ const menuItems = Array.from(
+ menuContainer.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])')
+ );
+
+ const selectedItem = menuItems.find(
+ (item) => item.getAttribute('data-item-id') === selectedItemId
+ );
+
+ if (selectedItem) {
+ focusMenuItem(selectedItem, true);
+ return;
+ }
+ }
+
+ // Fallback: focus menu container
+ menuContainer.focus();
+ }, 0);
+ }
+ });
+ }
+ }, [selectedItemId]);
+
+ // Render section (left or right)
+ const renderSection = (section) => {
+ if (!section) return null;
+
+ // If it's a React component (function), render it with default icon props
+ if (typeof section === 'function') {
+ const SectionComponent = section;
+ return
;
+ }
+
+ // If it's already a React element, render it as-is
+ return section;
+ };
+
+ // Render menu item
+ const renderMenuItem = (item) => {
+ return (
+
!item.disabled && handleItemClick(item)}
+ tabIndex={item.disabled ? -1 : 0}
+ aria-label={item.ariaLabel}
+ aria-disabled={item.disabled}
+ title={item.title}
+ data-testid={`${testId}-${item.id.toLowerCase()}`}
+ >
+ {renderSection(item.leftSection)}
+
{item.label}
+ {item.rightSection && (
+
{
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ >
+ {renderSection(item.rightSection)}
+
+ )}
+
+ );
+ };
+
+ // Render label item
+ const renderLabel = (item) => (
+
+ {item.label}
+
+ );
+
+ // Render divider item
+ const renderDivider = (item, index) => (
+
+ );
+
+ // Render menu content
+ const renderMenuContent = () => {
+ let dividerIndex = 0;
+
+ return items.map((item) => {
+ const itemType = item.type || 'item';
+
+ if (itemType === 'label') {
+ return renderLabel(item);
+ }
+
+ if (itemType === 'divider') {
+ return renderDivider(item, dividerIndex++);
+ }
+
+ return renderMenuItem(item);
+ });
+ };
+
+ // Clone children to attach click handler
+ const triggerElement = React.isValidElement(children)
+ ? React.cloneElement(children, {
+ 'onClick': (e) => {
+ children.props.onClick?.(e);
+ handleTriggerClick();
+ },
+ 'data-testid': testId
+ })
+ :
{children}
;
+
+ return (
+
+
+ {renderMenuContent()}
+
+
+ );
+};
+
+export default MenuDropdown;
diff --git a/tests/collection/close-all-collections/close-all-collections.spec.ts b/tests/collection/close-all-collections/close-all-collections.spec.ts
index c43d06d6d..86a1bca9b 100644
--- a/tests/collection/close-all-collections/close-all-collections.spec.ts
+++ b/tests/collection/close-all-collections/close-all-collections.spec.ts
@@ -17,7 +17,7 @@ const restartAppAndGetLocators = async (restartApp: (options?: { initUserDataPat
};
// TODO: These tests need to be updated for the new workspace UI
-// The CollectionsHeader component (with close-all-collections-button) is not rendered in workspace mode
+// The CollectionsHeader component (with collections-header-actions-menu-close-all) is not rendered in workspace mode
// The "Remove from workspace" flow is different from the old "Close collection" flow
test.describe.skip('Close All Collections', () => {
test.afterAll(async () => {
diff --git a/tests/collection/create/create-collection.spec.ts b/tests/collection/create/create-collection.spec.ts
index 147983a72..5620e8af9 100644
--- a/tests/collection/create/create-collection.spec.ts
+++ b/tests/collection/create/create-collection.spec.ts
@@ -8,7 +8,7 @@ test.describe('Create collection', () => {
});
test('Create collection and add a simple HTTP request', async ({ page, createTmpDir }) => {
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }).click();
await page.getByLabel('Name').click();
await page.getByLabel('Name').fill('test-collection');
diff --git a/tests/collection/moving-requests/tag-persistence.spec.ts b/tests/collection/moving-requests/tag-persistence.spec.ts
index 7ae0020ba..920e20cc0 100644
--- a/tests/collection/moving-requests/tag-persistence.spec.ts
+++ b/tests/collection/moving-requests/tag-persistence.spec.ts
@@ -9,7 +9,7 @@ test.describe('Tag persistence', () => {
test('Verify tag persistence while moving requests within a collection', async ({ page, createTmpDir }) => {
// Create first collection - click plus icon button to open dropdown
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }).click();
await page.getByLabel('Name').fill('test-collection');
const locationInput = page.locator('.bruno-modal').getByLabel('Location');
@@ -79,7 +79,7 @@ test.describe('Tag persistence', () => {
test('verify tag persistence while moving requests between folders', async ({ page, createTmpDir }) => {
// Create first collection - click plus icon button to open dropdown
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }).click();
await page.getByLabel('Name').fill('test-collection');
const locationInput = page.locator('.bruno-modal').getByLabel('Location');
diff --git a/tests/collection/moving-tabs/move-tabs.spec.ts b/tests/collection/moving-tabs/move-tabs.spec.ts
index 6700ff05b..0f46caa20 100644
--- a/tests/collection/moving-tabs/move-tabs.spec.ts
+++ b/tests/collection/moving-tabs/move-tabs.spec.ts
@@ -9,7 +9,7 @@ test.describe('Move tabs', () => {
test('Verify tab move by drag and drop', async ({ page, createTmpDir }) => {
// Create a collection
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }).click();
await page.getByLabel('Name').fill('source-collection-drag-drop');
const locationInput = page.locator('.bruno-modal').getByLabel('Location');
@@ -100,7 +100,7 @@ test.describe('Move tabs', () => {
test('Verify tab move by keyboard shortcut', async ({ page, createTmpDir }) => {
// Create a collection
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }).click();
await page.getByLabel('Name').fill('source-collection-keyboard-shortcut');
const locationInput2 = page.locator('.bruno-modal').getByLabel('Location');
diff --git a/tests/collection/open/open-multiple-collections.spec.ts b/tests/collection/open/open-multiple-collections.spec.ts
index 835ffbe5f..73756417e 100644
--- a/tests/collection/open/open-multiple-collections.spec.ts
+++ b/tests/collection/open/open-multiple-collections.spec.ts
@@ -57,7 +57,7 @@ test.describe('Open Multiple Collections', () => {
await expect(page.locator('#sidebar-collection-name').getByText('Test Collection 1')).not.toBeVisible();
// Click on plus icon button and then "Open collection" in the dropdown
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Open collection' }).click();
// Wait for both collections to appear in the sidebar
@@ -92,7 +92,7 @@ test.describe('Open Multiple Collections', () => {
},
{ collection1Dir, collection2Dir });
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Open collection' }).click();
// Wait for error toasts to appear
diff --git a/tests/environments/import-environment/collection-env-import.spec.ts b/tests/environments/import-environment/collection-env-import.spec.ts
index fa6ac1c8c..84ac2bd63 100644
--- a/tests/environments/import-environment/collection-env-import.spec.ts
+++ b/tests/environments/import-environment/collection-env-import.spec.ts
@@ -12,7 +12,7 @@ test.describe('Collection Environment Import Tests', () => {
const envFile = path.join(__dirname, 'fixtures', 'collection-env.json');
// Import test collection
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
const importModal = page.locator('[data-testid="import-collection-modal"]');
diff --git a/tests/environments/import-environment/global-env-import.spec.ts b/tests/environments/import-environment/global-env-import.spec.ts
index 917675ef9..9b05d1bd6 100644
--- a/tests/environments/import-environment/global-env-import.spec.ts
+++ b/tests/environments/import-environment/global-env-import.spec.ts
@@ -8,7 +8,7 @@ test.describe('Global Environment Import Tests', () => {
const globalEnvFile = path.join(__dirname, 'fixtures', 'global-env.json');
// Import test collection
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
const importModal = page.locator('[data-testid="import-collection-modal"]');
diff --git a/tests/import/bruno/import-bruno-corrupted-fails.spec.ts b/tests/import/bruno/import-bruno-corrupted-fails.spec.ts
index f10f1fa67..090722ab7 100644
--- a/tests/import/bruno/import-bruno-corrupted-fails.spec.ts
+++ b/tests/import/bruno/import-bruno-corrupted-fails.spec.ts
@@ -5,7 +5,7 @@ test.describe('Import Corrupted Bruno Collection - Should Fail', () => {
test('Import Bruno collection with invalid JSON structure should fail', async ({ page }) => {
const brunoFile = path.resolve(__dirname, 'fixtures', 'bruno-malformed.json');
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
diff --git a/tests/import/bruno/import-bruno-missing-required-schema.spec.ts b/tests/import/bruno/import-bruno-missing-required-schema.spec.ts
index a41cf7ba9..6a5d9cf6b 100644
--- a/tests/import/bruno/import-bruno-missing-required-schema.spec.ts
+++ b/tests/import/bruno/import-bruno-missing-required-schema.spec.ts
@@ -5,7 +5,7 @@ test.describe('Import Bruno Collection - Missing Required Schema Fields', () =>
test('Import Bruno collection missing required version field should fail', async ({ page }) => {
const brunoFile = path.resolve(__dirname, 'fixtures', 'bruno-missing-required-fields.json');
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
diff --git a/tests/import/bruno/import-bruno-with-examples.spec.ts b/tests/import/bruno/import-bruno-with-examples.spec.ts
index fade75046..036d21b43 100644
--- a/tests/import/bruno/import-bruno-with-examples.spec.ts
+++ b/tests/import/bruno/import-bruno-with-examples.spec.ts
@@ -11,7 +11,7 @@ test.describe('Import Bruno Collection with Examples', () => {
const brunoFile = path.resolve(__dirname, 'fixtures', 'bruno-with-examples.json');
await test.step('Open import collection modal', async () => {
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
});
diff --git a/tests/import/file-types/file-input-acceptance.spec.ts b/tests/import/file-types/file-input-acceptance.spec.ts
index 481440ede..d386f8d78 100644
--- a/tests/import/file-types/file-input-acceptance.spec.ts
+++ b/tests/import/file-types/file-input-acceptance.spec.ts
@@ -2,7 +2,7 @@ import { test, expect } from '../../../playwright';
test.describe('File Input Acceptance', () => {
test('File input accepts expected file types', async ({ page }) => {
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Check that file input exists (even if hidden)
diff --git a/tests/import/file-types/invalid-file-handling.spec.ts b/tests/import/file-types/invalid-file-handling.spec.ts
index bccf0c81f..b2eb6162f 100644
--- a/tests/import/file-types/invalid-file-handling.spec.ts
+++ b/tests/import/file-types/invalid-file-handling.spec.ts
@@ -5,7 +5,7 @@ test.describe('Invalid File Handling', () => {
test('Handle invalid file without crashing', async ({ page }) => {
const invalidFile = path.resolve(__dirname, 'fixtures', 'invalid.txt');
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
diff --git a/tests/import/insomnia/import-insomnia-v4-environments.spec.ts b/tests/import/insomnia/import-insomnia-v4-environments.spec.ts
index ac6df41d7..4eaabdd69 100644
--- a/tests/import/insomnia/import-insomnia-v4-environments.spec.ts
+++ b/tests/import/insomnia/import-insomnia-v4-environments.spec.ts
@@ -23,7 +23,7 @@ test.describe('Import Insomnia v4 Collection - Environment Import', () => {
const insomniaFile = path.resolve(__dirname, 'fixtures', 'insomnia-v4-with-envs.json');
await test.step('Import Insomnia v4 collection with environments', async () => {
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
const importModal = page.getByTestId('import-collection-modal');
diff --git a/tests/import/insomnia/import-insomnia-v5-environments.spec.ts b/tests/import/insomnia/import-insomnia-v5-environments.spec.ts
index 745ab753a..25555e2a8 100644
--- a/tests/import/insomnia/import-insomnia-v5-environments.spec.ts
+++ b/tests/import/insomnia/import-insomnia-v5-environments.spec.ts
@@ -23,7 +23,7 @@ test.describe('Import Insomnia v5 Collection - Environment Import', () => {
const insomniaFile = path.resolve(__dirname, 'fixtures', 'insomnia-v5-with-envs.yaml');
await test.step('Import Insomnia v5 collection with environments', async () => {
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
const importModal = page.getByTestId('import-collection-modal');
diff --git a/tests/import/insomnia/invalid-missing-collection.spec.ts b/tests/import/insomnia/invalid-missing-collection.spec.ts
index 1a20e6d72..4acb7338d 100644
--- a/tests/import/insomnia/invalid-missing-collection.spec.ts
+++ b/tests/import/insomnia/invalid-missing-collection.spec.ts
@@ -5,7 +5,7 @@ test.describe('Invalid Insomnia Collection - Missing Collection Array', () => {
test('Handle Insomnia v5 collection missing collection array', async ({ page }) => {
const insomniaFile = path.resolve(__dirname, 'fixtures', 'insomnia-v5-invalid-missing-collection.yaml');
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
diff --git a/tests/import/insomnia/malformed-structure.spec.ts b/tests/import/insomnia/malformed-structure.spec.ts
index 08286a403..55731b4e7 100644
--- a/tests/import/insomnia/malformed-structure.spec.ts
+++ b/tests/import/insomnia/malformed-structure.spec.ts
@@ -5,7 +5,7 @@ test.describe('Invalid Insomnia Collection - Malformed Structure', () => {
test('Handle malformed Insomnia collection structure', async ({ page }) => {
const insomniaFile = path.resolve(__dirname, 'fixtures', 'insomnia-malformed.json');
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
diff --git a/tests/import/openapi/duplicate-operation-names-fix.spec.ts b/tests/import/openapi/duplicate-operation-names-fix.spec.ts
index 64f2e8d84..ffd82480a 100644
--- a/tests/import/openapi/duplicate-operation-names-fix.spec.ts
+++ b/tests/import/openapi/duplicate-operation-names-fix.spec.ts
@@ -12,7 +12,7 @@ test.describe('OpenAPI Duplicate Names Handling', () => {
const openApiFile = path.resolve(__dirname, 'fixtures', 'openapi-duplicate-operation-name.yaml');
// start the import process
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// wait for the import collection modal to appear
diff --git a/tests/import/openapi/import-openapi-with-examples.spec.ts b/tests/import/openapi/import-openapi-with-examples.spec.ts
index ad64b371c..08048c55b 100644
--- a/tests/import/openapi/import-openapi-with-examples.spec.ts
+++ b/tests/import/openapi/import-openapi-with-examples.spec.ts
@@ -35,7 +35,7 @@ test.describe('Import OpenAPI Collection with Examples', () => {
}, { importDir });
await test.step('Open import collection modal', async () => {
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
});
@@ -152,7 +152,7 @@ test.describe('Import OpenAPI Collection with Examples', () => {
}, { importDir });
await test.step('Open import collection modal', async () => {
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
});
diff --git a/tests/import/openapi/malformed-yaml.spec.ts b/tests/import/openapi/malformed-yaml.spec.ts
index 0a43cbaf5..9323f8260 100644
--- a/tests/import/openapi/malformed-yaml.spec.ts
+++ b/tests/import/openapi/malformed-yaml.spec.ts
@@ -5,7 +5,7 @@ test.describe('Invalid OpenAPI - Malformed YAML', () => {
test('Handle malformed OpenAPI YAML structure', async ({ page }) => {
const openApiFile = path.resolve(__dirname, 'fixtures', 'openapi-malformed.yaml');
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
diff --git a/tests/import/openapi/missing-info.spec.ts b/tests/import/openapi/missing-info.spec.ts
index 5dddba729..f019a844e 100644
--- a/tests/import/openapi/missing-info.spec.ts
+++ b/tests/import/openapi/missing-info.spec.ts
@@ -5,7 +5,7 @@ test.describe('Invalid OpenAPI - Missing Info Section', () => {
test('Handle OpenAPI specification missing required info section', async ({ page }) => {
const openApiFile = path.resolve(__dirname, 'fixtures', 'openapi-missing-info.yaml');
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
diff --git a/tests/import/openapi/operation-name-with-newlines-fix.spec.ts b/tests/import/openapi/operation-name-with-newlines-fix.spec.ts
index 72d621be9..9351574e0 100644
--- a/tests/import/openapi/operation-name-with-newlines-fix.spec.ts
+++ b/tests/import/openapi/operation-name-with-newlines-fix.spec.ts
@@ -12,7 +12,7 @@ test.describe('OpenAPI Newline Handling', () => {
const openApiFile = path.resolve(__dirname, 'fixtures', 'openapi-newline-in-operation-name.yaml');
// start the import process
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// wait for the import collection modal to appear
diff --git a/tests/import/openapi/path-based-grouping.spec.ts b/tests/import/openapi/path-based-grouping.spec.ts
index 9a2494d63..f77def01e 100644
--- a/tests/import/openapi/path-based-grouping.spec.ts
+++ b/tests/import/openapi/path-based-grouping.spec.ts
@@ -12,7 +12,7 @@ test.describe('OpenAPI Path-Based Grouping', () => {
const openApiFile = path.resolve(__dirname, 'fixtures', 'openapi-path-grouping.json');
// Start the import process
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
diff --git a/tests/import/postman/import-postman-with-examples.spec.ts b/tests/import/postman/import-postman-with-examples.spec.ts
index 8181c58ea..98dccbfaa 100644
--- a/tests/import/postman/import-postman-with-examples.spec.ts
+++ b/tests/import/postman/import-postman-with-examples.spec.ts
@@ -35,7 +35,7 @@ test.describe('Import Postman Collection with Examples', () => {
}, { importDir });
await test.step('Open import collection modal', async () => {
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
});
diff --git a/tests/import/postman/invalid-json.spec.ts b/tests/import/postman/invalid-json.spec.ts
index f47d1d2f1..7bfe8682a 100644
--- a/tests/import/postman/invalid-json.spec.ts
+++ b/tests/import/postman/invalid-json.spec.ts
@@ -5,7 +5,7 @@ test.describe('Invalid Postman Collection - Invalid JSON', () => {
test('Handle invalid JSON syntax', async ({ page }) => {
const postmanFile = path.resolve(__dirname, 'fixtures', 'postman-invalid-schema.json');
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
diff --git a/tests/import/postman/invalid-missing-info.spec.ts b/tests/import/postman/invalid-missing-info.spec.ts
index a71ac4388..0952c573e 100644
--- a/tests/import/postman/invalid-missing-info.spec.ts
+++ b/tests/import/postman/invalid-missing-info.spec.ts
@@ -5,7 +5,7 @@ test.describe('Invalid Postman Collection - Missing Info', () => {
test('Handle Postman collection missing required info field', async ({ page }) => {
const postmanFile = path.resolve(__dirname, 'fixtures', 'postman-invalid-missing-info.json');
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
diff --git a/tests/import/postman/invalid-schema.spec.ts b/tests/import/postman/invalid-schema.spec.ts
index bd8d26eb1..1961e5650 100644
--- a/tests/import/postman/invalid-schema.spec.ts
+++ b/tests/import/postman/invalid-schema.spec.ts
@@ -5,7 +5,7 @@ test.describe('Invalid Postman Collection - Invalid Schema', () => {
test('Handle Postman collection with invalid schema version', async ({ page }) => {
const postmanFile = path.resolve(__dirname, 'fixtures', 'postman-invalid-schema.json');
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
diff --git a/tests/import/postman/malformed-structure.spec.ts b/tests/import/postman/malformed-structure.spec.ts
index 846bf9c2a..fb600ed7e 100644
--- a/tests/import/postman/malformed-structure.spec.ts
+++ b/tests/import/postman/malformed-structure.spec.ts
@@ -5,7 +5,7 @@ test.describe('Invalid Postman Collection - Malformed Structure', () => {
test('Handle malformed Postman collection structure', async ({ page }) => {
const postmanFile = path.resolve(__dirname, 'fixtures', 'postman-malformed.json');
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
diff --git a/tests/import/wsdl/import-wsdl.spec.ts b/tests/import/wsdl/import-wsdl.spec.ts
index 71dd220fb..b19452235 100644
--- a/tests/import/wsdl/import-wsdl.spec.ts
+++ b/tests/import/wsdl/import-wsdl.spec.ts
@@ -13,7 +13,7 @@ test.describe('Import WSDL Collection', () => {
const wsdlFile = path.join(testDataDir, 'wsdl.xml');
await test.step('Open import collection modal', async () => {
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
@@ -71,7 +71,7 @@ test.describe('Import WSDL Collection', () => {
const wsdlFile = path.join(testDataDir, 'wsdl-bruno.json');
await test.step('Open import collection modal', async () => {
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import collection modal to be ready
diff --git a/tests/preferences/default-collection-location/default-collection-location.spec.js b/tests/preferences/default-collection-location/default-collection-location.spec.js
index edb751bd9..f1076483a 100644
--- a/tests/preferences/default-collection-location/default-collection-location.spec.js
+++ b/tests/preferences/default-collection-location/default-collection-location.spec.js
@@ -56,7 +56,7 @@ test.describe('Default Collection Location Feature', () => {
test('Should use default location in Create Collection modal', async ({ pageWithUserData: page }) => {
// test Create Collection modal
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }).click();
// verify the default location is pre-filled (if location input is visible)
diff --git a/tests/request/encoding/curl-encoding.spec.ts b/tests/request/encoding/curl-encoding.spec.ts
index c6a7b7f78..b7b05f906 100644
--- a/tests/request/encoding/curl-encoding.spec.ts
+++ b/tests/request/encoding/curl-encoding.spec.ts
@@ -19,7 +19,7 @@ test.describe('Code Generation URL Encoding', () => {
createTmpDir
}) => {
// Use plus icon button in new workspace UI
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }).click();
await page.getByLabel('Name').fill('unencoded-test-collection');
const locationInput = page.getByLabel('Location');
@@ -65,7 +65,7 @@ test.describe('Code Generation URL Encoding', () => {
createTmpDir
}) => {
// Use plus icon button in new workspace UI
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }).click();
await page.getByLabel('Name').fill('encoded-test-collection');
const locationInput = page.getByLabel('Location');
diff --git a/tests/request/newlines/newlines-persistence.spec.ts b/tests/request/newlines/newlines-persistence.spec.ts
index 30e497975..4184234c6 100644
--- a/tests/request/newlines/newlines-persistence.spec.ts
+++ b/tests/request/newlines/newlines-persistence.spec.ts
@@ -10,7 +10,7 @@ test('should persist request with newlines across app restarts', async ({ create
const app1 = await launchElectronApp({ userDataPath });
const page = await app1.firstWindow();
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }).click();
await page.locator('.bruno-modal').getByLabel('Name').fill('newlines-persistence');
await page.locator('.bruno-modal').getByLabel('Location').fill(collectionPath);
diff --git a/tests/request/save/save.spec.ts b/tests/request/save/save.spec.ts
index a40d3161f..aee7aa9c9 100644
--- a/tests/request/save/save.spec.ts
+++ b/tests/request/save/save.spec.ts
@@ -9,7 +9,7 @@ const isRequestSaved = async (saveButton: Locator) => {
};
const setup = async (page: Page, createTmpDir: (tag?: string | undefined) => Promise
) => {
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }).click();
await page.getByLabel('Name').fill('source-collection');
const locationInput = page.getByLabel('Location');
diff --git a/tests/utils/page/actions.ts b/tests/utils/page/actions.ts
index 813030f6c..720905bd1 100644
--- a/tests/utils/page/actions.ts
+++ b/tests/utils/page/actions.ts
@@ -64,7 +64,7 @@ type CreateCollectionOptions = {
*/
const createCollection = async (page, collectionName: string, collectionLocation: string, options: CreateCollectionOptions = {}) => {
await test.step(`Create collection "${collectionName}"`, async () => {
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }).click();
const createCollectionModal = page.locator('.bruno-modal-card').filter({ hasText: 'Create Collection' });
@@ -184,7 +184,7 @@ const importCollection = async (
await test.step(`Import collection from "${filePath}"`, async () => {
const locators = buildCommonLocators(page);
- await page.locator('.plus-icon-button').click();
+ await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' }).click();
// Wait for import modal
diff --git a/tests/utils/page/locators.ts b/tests/utils/page/locators.ts
index a1a72c942..68823c2bc 100644
--- a/tests/utils/page/locators.ts
+++ b/tests/utils/page/locators.ts
@@ -17,7 +17,7 @@ export const buildCommonLocators = (page: Page) => ({
const folderWrapper = page.locator('.collection-item-name').filter({ hasText: folderName }).locator('..');
return folderWrapper.locator('.collection-item-name').filter({ hasText: requestName });
},
- closeAllCollectionsButton: () => page.getByTestId('close-all-collections-button'),
+ closeAllCollectionsButton: () => page.getByTestId('collections-header-actions-menu-close-all'),
collectionRow: (name: string) => page.locator('.collection-name').filter({
has: page.locator('#sidebar-collection-name', { hasText: name })
})
@@ -81,7 +81,7 @@ export const buildCommonLocators = (page: Page) => ({
copyButton: () => page.locator('button[title="Copy response to clipboard"]')
},
plusMenu: {
- button: () => page.locator('.plus-icon-button'),
+ button: () => page.getByTestId('collections-header-add-menu'),
createCollection: () => page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }),
importCollection: () => page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Import collection' })
},