Repository: vaadin/flow-components
Pull request #7338: feat: Improve MenuBarElement
Author: login: TatuLund
Labels: name: prerelease version for Vaadin 24.8.0
name: Released with Vaadin 24.8.0.alpha8
name: Released with Vaadin 25.0.0-alpha1
Created: 2025-04-23T12:16:41Z | Merged: 2025-05-14T06:24:33Z

Fixes https://github.com/vaadin/flow-components/issues/7362
Fixes https://github.com/vaadin/flow-components/issues/7363


Diff:
diff --git a/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarClassNamesIT.java b/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarClassNamesIT.java
index 13097e89672..34f5e9f92b7 100644
--- a/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarClassNamesIT.java
+++ b/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarClassNamesIT.java
@@ -22,9 +22,10 @@
 import org.junit.Test;
 import org.openqa.selenium.By;
 
+import com.vaadin.flow.component.menubar.testbench.MenuBarButtonElement;
 import com.vaadin.flow.component.menubar.testbench.MenuBarElement;
+import com.vaadin.flow.component.menubar.testbench.MenuBarItemElement;
 import com.vaadin.flow.testutil.TestPath;
-import com.vaadin.testbench.TestBenchElement;
 import com.vaadin.tests.AbstractComponentIT;
 
 @TestPath("vaadin-menu-bar/menu-bar-class-names")
@@ -42,7 +43,7 @@ public void init() {
 
     @Test
     public void toggleMenuItemClassName_classNameIsToggled() {
-        TestBenchElement menuButton1 = menuBar.getButtons().get(0);
+        MenuBarButtonElement menuButton1 = menuBar.getButtons().get(0);
         Assert.assertFalse(menuButton1.hasAttribute("class"));
         click("toggle-item1-class-name");
         menuButton1 = menuBar.getButtons().get(0);
@@ -56,7 +57,7 @@ public void toggleMenuItemClassName_classNameIsToggled() {
 
     @Test
     public void setMenuItemClassName_classNameIsSet() {
-        TestBenchElement menuButton1 = menuBar.getButtons().get(0);
+        MenuBarButtonElement menuButton1 = menuBar.getButtons().get(0);
         Assert.assertFalse(menuButton1.hasAttribute("class"));
         click("toggle-item1-class-name");
         menuButton1 = menuBar.getButtons().get(0);
@@ -72,7 +73,7 @@ public void setMenuItemClassName_classNameIsSet() {
 
     @Test
     public void toggleMenuItemClassNameWithSetClassName_classNameIsToggled() {
-        TestBenchElement menuButton1 = menuBar.getButtons().get(0);
+        MenuBarButtonElement menuButton1 = menuBar.getButtons().get(0);
         Assert.assertFalse(menuButton1.hasAttribute("class"));
         click("set-unset-item1-class-name");
         menuButton1 = menuBar.getButtons().get(0);
@@ -86,7 +87,7 @@ public void toggleMenuItemClassNameWithSetClassName_classNameIsToggled() {
 
     @Test
     public void toggleMultipleItemClassName_classNamesAreToggled() {
-        TestBenchElement menuButton1 = menuBar.getButtons().get(0);
+        MenuBarButtonElement menuButton1 = menuBar.getButtons().get(0);
         Assert.assertFalse(menuButton1.hasAttribute("class"));
         click("add-remove-multiple-classes");
         menuButton1 = menuBar.getButtons().get(0);
@@ -178,7 +179,7 @@ public void menuItemWithClassNameInOverflow_changeClassName_classNameIsChanged()
         menuBar.getOverflowButton().click();
         click("change-item2-class-name");
         menuBar.getOverflowButton().click();
-        TestBenchElement menuItem = menuBar.getSubMenuItems().get(0);
+        MenuBarItemElement menuItem = menuBar.getSubMenuItems().get(0);
         Assert.assertEquals(
                 Set.of(MenuBarClassNamesPage.MENU_ITEM_SECOND_CLASS_NAME),
                 menuItem.getClassNames());
@@ -192,7 +193,7 @@ public void menuItemWithClassNameInOverflow_removeClassName_classNameIsRemoved()
         menuBar.getOverflowButton().click();
         click("remove-item2-class-name");
         menuBar.getOverflowButton().click();
-        TestBenchElement menuItem = menuBar.getSubMenuItems().get(0);
+        MenuBarItemElement menuItem = menuBar.getSubMenuItems().get(0);
         Assert.assertEquals(Set.of(), menuItem.getClassNames());
     }
 
@@ -205,7 +206,7 @@ public void menuItemWithClassNameInOverflow_menuItemLeavesOverflow_classNameCanB
         click("reset-width");
         waitForResizeObserver();
         click("change-item2-class-name");
-        TestBenchElement menuItem = menuBar.getButtons().get(1);
+        MenuBarButtonElement menuItem = menuBar.getButtons().get(1);
         Assert.assertEquals(
                 Set.of(MenuBarClassNamesPage.MENU_ITEM_SECOND_CLASS_NAME),
                 menuItem.getClassNames());
@@ -246,7 +247,7 @@ private void waitForResizeObserver() {
     private void verifySubMenuItemClassNames(boolean containsClassNames,
             String... classNames) {
         openSubMenu();
-        TestBenchElement subMenuItem = menuBar.getSubMenuItems().get(2);
+        MenuBarItemElement subMenuItem = menuBar.getSubMenuItems().get(2);
         var subMenuItemClassNames = subMenuItem.getClassNames();
         for (String className : classNames) {
             if (containsClassNames) {
diff --git a/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarDetachReattachIT.java b/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarDetachReattachIT.java
index e64834eaf98..24083a22350 100644
--- a/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarDetachReattachIT.java
+++ b/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarDetachReattachIT.java
@@ -20,9 +20,9 @@
 import org.junit.Test;
 import org.openqa.selenium.By;
 
+import com.vaadin.flow.component.menubar.testbench.MenuBarButtonElement;
 import com.vaadin.flow.component.menubar.testbench.MenuBarElement;
 import com.vaadin.flow.testutil.TestPath;
-import com.vaadin.testbench.TestBenchElement;
 import com.vaadin.tests.AbstractComponentIT;
 
 @TestPath("vaadin-menu-bar/detach-reattach")
@@ -55,7 +55,7 @@ public void setI18n_i18nIsUpdated() {
 
         waitForResizeObserver();
 
-        TestBenchElement overflowButton = menuBar.getOverflowButton();
+        MenuBarButtonElement overflowButton = menuBar.getOverflowButton();
 
         Assert.assertEquals("More options",
                 overflowButton.getDomAttribute("aria-label"));
@@ -74,7 +74,7 @@ public void setI18n_detach_attach_i18nIsPersisted() {
 
         click("set-i18n");
 
-        TestBenchElement overflowButton = menuBar.getOverflowButton();
+        MenuBarButtonElement overflowButton = menuBar.getOverflowButton();
 
         Assert.assertEquals("more-options",
                 overflowButton.getDomAttribute("aria-label"));
diff --git a/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarPageIT.java b/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarPageIT.java
index 1d7d1d1e1c5..3f3586207c2 100644
--- a/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarPageIT.java
+++ b/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarPageIT.java
@@ -23,7 +23,9 @@
 import org.junit.Test;
 import org.openqa.selenium.By;
 
+import com.vaadin.flow.component.menubar.testbench.MenuBarButtonElement;
 import com.vaadin.flow.component.menubar.testbench.MenuBarElement;
+import com.vaadin.flow.component.menubar.testbench.MenuBarItemElement;
 import com.vaadin.flow.testutil.TestPath;
 import com.vaadin.testbench.TestBenchElement;
 import com.vaadin.tests.AbstractComponentIT;
@@ -99,9 +101,9 @@ public void openSubSubMenu_clickCheckableItem_checkableStateChanges() {
         hoverOn(menuBar.getSubMenuItems().get(1));
 
         waitUntil(driver -> menuBar.getAllSubMenus().size() == 2);
-        TestBenchElement checkableItem = menuBar
+        MenuBarItemElement checkableItem = menuBar
                 .getSubMenuItems(menuBar.getAllSubMenus().get(1)).get(1);
-        Assert.assertTrue(checkableItem.hasAttribute("menu-item-checked"));
+        Assert.assertTrue(checkableItem.isChecked());
 
         checkableItem.click();
         verifyClosed();
@@ -110,16 +112,16 @@ public void openSubSubMenu_clickCheckableItem_checkableStateChanges() {
         openSubSubMenu();
         checkableItem = menuBar.getSubMenuItems(menuBar.getAllSubMenus().get(1))
                 .get(1);
-        Assert.assertFalse(checkableItem.hasAttribute("menu-item-checked"));
+        Assert.assertFalse(checkableItem.isChecked());
     }
 
     @Test
     public void setCheckedExternally_openSubMenu_itemChecked() {
         click("toggle-checked");
         openSubSubMenu();
-        TestBenchElement checkableItem = menuBar
+        MenuBarItemElement checkableItem = menuBar
                 .getSubMenuItems(menuBar.getAllSubMenus().get(1)).get(1);
-        Assert.assertTrue(checkableItem.hasAttribute("menu-item-checked"));
+        Assert.assertTrue(checkableItem.isChecked());
     }
 
     @Test
@@ -160,7 +162,7 @@ public void buttonsOverflow_itemsMovedToOverflowSubMenu() {
         click("set-width");
         waitForResizeObserver();
         click("add-root-item");
-        TestBenchElement overflowButton = menuBar.getOverflowButton();
+        MenuBarButtonElement overflowButton = menuBar.getOverflowButton();
         Assert.assertNotNull("Expected the overflow button to be rendered",
                 overflowButton);
         assertButtonContents("item 1");
@@ -200,8 +202,10 @@ public void changeItems_clickRootItemWithClickListener_clickListenerCalledOnce()
     public void buttonWithClickListenerOverflows_clickListenerWorksInSubMenu() {
         click("set-width");
         waitForResizeObserver();
-        menuBar.getOverflowButton().click();
-        menuBar.getSubMenuItems().get(0).click();
+        var overflowButton = menuBar.getOverflowButton();
+        overflowButton.click();
+        Assert.assertNotNull(menuBar.getSubMenu());
+        menuBar.getSubMenuItems(overflowButton.getSubMenu()).get(0).click();
         assertMessage("clicked item 2");
     }
 
@@ -243,7 +247,7 @@ public void buttonsReflectDisabledStateOfMenuItems() {
     @Test
     public void disableButton_removeDisabledAttribute_click_listenerNotCalled() {
         click("toggle-disable");
-        TestBenchElement button2 = menuBar.getButtons().get(1);
+        MenuBarButtonElement button2 = menuBar.getButtons().get(1);
         executeScript("arguments[0].disabled=false;"
                 + "arguments[0].querySelector('vaadin-menu-bar-item').disabled=false;",
                 button2);
@@ -417,7 +421,7 @@ private String[] getOverlayMenuItemContents(TestBenchElement overlay) {
     }
 
     private String[] getOverlayMenuItemContents(
-            List<TestBenchElement> menuItems) {
+            List<MenuBarItemElement> menuItems) {
         return menuItems.stream().map(item -> item.getDomProperty("innerHTML"))
                 .toArray(String[]::new);
     }
diff --git a/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarThemeIT.java b/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarThemeIT.java
index fc3ae6348ee..9b10c46fc72 100644
--- a/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarThemeIT.java
+++ b/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarThemeIT.java
@@ -20,9 +20,9 @@
 import org.junit.Test;
 import org.openqa.selenium.By;
 
+import com.vaadin.flow.component.menubar.testbench.MenuBarButtonElement;
 import com.vaadin.flow.component.menubar.testbench.MenuBarElement;
 import com.vaadin.flow.testutil.TestPath;
-import com.vaadin.testbench.TestBenchElement;
 import com.vaadin.tests.AbstractComponentIT;
 
 @TestPath("vaadin-menu-bar/menu-bar-theme")
@@ -50,7 +50,7 @@ public void toggleMenuBarTheme_themeIsToggled() {
 
     @Test
     public void toggleMenuItemTheme_themeIsToggled() {
-        TestBenchElement menuButton1 = menuBar.getButtons().get(0);
+        MenuBarButtonElement menuButton1 = menuBar.getButtons().get(0);
         Assert.assertFalse(menuButton1.hasAttribute("theme"));
         click("toggle-item-1-theme");
         menuButton1 = menuBar.getButtons().get(0);
@@ -66,7 +66,7 @@ public void setMenuItemTheme_toggleVisibility_themeIsPreserved() {
         click("toggle-item-1-theme");
         click("toggle-item-1-visibility");
         click("toggle-item-1-visibility");
-        TestBenchElement menuButton1 = menuBar.getButtons().get(0);
+        MenuBarButtonElement menuButton1 = menuBar.getButtons().get(0);
         Assert.assertEquals(menuButton1.getDomAttribute("theme"),
                 MenuBarThemePage.MENU_ITEM_THEME);
     }
@@ -77,7 +77,7 @@ public void setMenuItemTheme_hide_resetTheme_show_themeIsUnset() {
         click("toggle-item-1-visibility");
         click("toggle-item-1-theme");
         click("toggle-item-1-visibility");
-        TestBenchElement menuButton1 = menuBar.getButtons().get(0);
+        MenuBarButtonElement menuButton1 = menuBar.getButtons().get(0);
         Assert.assertFalse(menuButton1.hasAttribute("theme"));
     }
 
@@ -112,7 +112,7 @@ public void toggleMenuBarTheme_toggleMenuItemTheme_themeIsOverridden() {
         click("toggle-theme");
         click("toggle-item-1-theme");
 
-        TestBenchElement menuButton1 = menuBar.getButtons().get(0);
+        MenuBarButtonElement menuButton1 = menuBar.getButtons().get(0);
         Assert.assertEquals(MenuBarThemePage.MENU_ITEM_THEME,
                 menuButton1.getDomAttribute("theme"));
     }
diff --git a/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarVisibilityIT.java b/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarVisibilityIT.java
index 6d86887c96b..b910022ac54 100644
--- a/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarVisibilityIT.java
+++ b/vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow-integration-tests/src/test/java/com/vaadin/flow/component/menubar/tests/MenuBarVisibilityIT.java
@@ -19,6 +19,7 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import com.vaadin.flow.component.menubar.testbench.MenuBarButtonElement;
 import com.vaadin.flow.component.menubar.testbench.MenuBarElement;
 import com.vaadin.flow.testutil.TestPath;
 import com.vaadin.testbench.TestBenchElement;
@@ -58,7 +59,7 @@ public void hide_disableMenuItem_show_buttonIsDisabled() {
         Assert.assertTrue(menuBar.isDisplayed());
 
         // Check that the menu item is disabled.
-        TestBenchElement button = menuBar.getButtons().get(0);
+        MenuBarButtonElement button = menuBar.getButtons().get(0);
         Assert.assertTrue(button.getPropertyBoolean("disabled"));
     }
 
diff --git a/vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarButtonElement.java b/vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarButtonElement.java
new file mode 100644
index 00000000000..85892952a75
--- /dev/null
+++ b/vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarButtonElement.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.flow.component.menubar.testbench;
+
+import java.util.List;
+
+import org.openqa.selenium.support.ui.ExpectedConditions;
+
+import com.vaadin.testbench.TestBenchElement;
+import com.vaadin.testbench.elementsbase.Element;
+
+/**
+ * A TestBench element representing a {@code <vaadin-menu-bar-button>} element.
+ */
+@Element("vaadin-menu-bar-button")
+public class MenuBarButtonElement extends TestBenchElement {
+
+    /**
+     * Get the sub menu overlay element linked to this menu button.
+     *
+     * @return TestBenchElement for the open sub menu.
+     */
+    public TestBenchElement getSubMenu() {
+        waitForSubMenu();
+        return getPropertyElement("__overlay");
+    }
+
+    /**
+     * Get TestBenchElements representing sub menu items under this button.
+     *
+     * @return List of MenuBarItemElement representing sub menu items.
+     */
+    public List<MenuBarItemElement> getSubMenuItems() {
+        return getSubMenu().$(MenuBarItemElement.class).all();
+    }
+
+    /**
+     * Check if the button has open sub menu.
+     *
+     * @return True if there is sub menu open
+     */
+    public boolean isExpanded() {
+        return hasAttribute("expanded");
+    }
+
+    private void waitForSubMenu() {
+        waitUntil(ExpectedConditions.attributeToBe(this, "aria-expanded",
+                "true"));
+    }
+
+}
diff --git a/vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarElement.java b/vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarElement.java
index 3f1b399f0cd..b92010f9766 100644
--- a/vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarElement.java
+++ b/vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarElement.java
@@ -17,7 +17,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
 import java.util.stream.Collectors;
 
 import org.openqa.selenium.By;
@@ -27,8 +26,7 @@
 import com.vaadin.testbench.elementsbase.Element;
 
 /**
- * A TestBench element representing a <code>&lt;vaadin-menu-bar&gt;</code>
- * element.
+ * A TestBench element representing a {@code <vaadin-menu-bar>} element.
  */
 @Element("vaadin-menu-bar")
 public class MenuBarElement extends TestBenchElement {
@@ -42,8 +40,8 @@ public class MenuBarElement extends TestBenchElement {
      *
      * @return the button elements in the menu bar
      */
-    public List<TestBenchElement> getButtons() {
-        return $("vaadin-menu-bar-button").all().stream().filter(
+    public List<MenuBarButtonElement> getButtons() {
+        return $(MenuBarButtonElement.class).all().stream().filter(
                 element -> !isOverflowButton(element) && isVisible(element))
                 .collect(Collectors.toList());
     }
@@ -54,8 +52,9 @@ public List<TestBenchElement> getButtons() {
      *
      * @return the button which opens the sub menu of overflowing items
      */
-    public TestBenchElement getOverflowButton() {
-        TestBenchElement overflowButton = $("[slot='overflow']").first();
+    public MenuBarButtonElement getOverflowButton() {
+        MenuBarButtonElement overflowButton = $(MenuBarButtonElement.class)
+                .withAttribute("slot", "overflow").first();
         if (overflowButton == null || overflowButton.hasAttribute("hidden")) {
             return null;
         }
@@ -63,7 +62,7 @@ public TestBenchElement getOverflowButton() {
     }
 
     private boolean isOverflowButton(TestBenchElement element) {
-        return Objects.equals("overflow", element.getDomAttribute("slot"));
+        return "overflow".equals(element.getAttribute("slot"));
     }
 
     private boolean isVisible(TestBenchElement element) {
@@ -75,9 +74,9 @@ private boolean isVisible(TestBenchElement element) {
      * Get TestBenchElements representing sub menu items under the first sub
      * menu.
      *
-     * @return List of TestBenchElements representing sub menu items.
+     * @return List of MenuBarItemElement representing sub menu items.
      */
-    public List<TestBenchElement> getSubMenuItems() {
+    public List<MenuBarItemElement> getSubMenuItems() {
         return getSubMenuItems(getSubMenu());
     }
 
@@ -87,21 +86,21 @@ public List<TestBenchElement> getSubMenuItems() {
      *
      * @param overlay
      *            The sub menu overlay from which items are being collected.
-     * @return List of TestBenchElements representing sub menu items.
+     * @return List of MenuBarItemElement representing sub menu items.
      */
-    public List<TestBenchElement> getSubMenuItems(TestBenchElement overlay) {
-        return overlay.$("vaadin-menu-bar-item").all();
+    public List<MenuBarItemElement> getSubMenuItems(TestBenchElement overlay) {
+        return overlay.$(MenuBarItemElement.class).all();
     }
 
     /**
      * Get the sub menu overlay element.
      *
-     * @return TestBenchElement for the first open sub menu.
+     * @return TestBenchElement for the first open sub menu in this menu bar
      */
     public TestBenchElement getSubMenu() {
-        waitForSubMenu();
-        return (TestBenchElement) getDriver()
-                .findElement(By.tagName(OVERLAY_TAG));
+        var button = $(MenuBarButtonElement.class).withAttribute("expanded")
+                .withCondition(this::isVisible).first();
+        return button != null ? button.getSubMenu() : null;
     }
 
     /**
diff --git a/vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarItemElement.java b/vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarItemElement.java
new file mode 100644
index 00000000000..9f6b58db6a2
--- /dev/null
+++ b/vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarItemElement.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.flow.component.menubar.testbench;
+
+import java.util.List;
+
+import org.openqa.selenium.support.ui.ExpectedConditions;
+
+import com.vaadin.testbench.TestBenchElement;
+import com.vaadin.testbench.elementsbase.Element;
+
+/**
+ * A TestBench element representing a {@code <vaadin-menu-bar-item>} element.
+ */
+@Element("vaadin-menu-bar-item")
+public class MenuBarItemElement extends TestBenchElement {
+
+    /**
+     * Get the sub menu overlay element linked to this menu item.
+     *
+     * @return TestBenchElement for the open sub menu.
+     */
+    public TestBenchElement getSubMenu() {
+        waitForSubMenu();
+        return getPropertyElement("__overlay");
+    }
+
+    /**
+     * Get TestBenchElements representing sub menu items under this item.
+     *
+     * @return List of MenuBarItemElement representing sub menu items.
+     */
+    public List<MenuBarItemElement> getSubMenuItems() {
+        return getSubMenu().$(MenuBarItemElement.class).all();
+    }
+
+    /**
+     * Check if the item has open sub menu.
+     *
+     * @return True if there is sub menu open
+     */
+    public boolean isExpanded() {
+        return hasAttribute("expanded");
+    }
+
+    /**
+     * Check if the item is checked.
+     *
+     * @return True if there is checkmark
+     */
+    public boolean isChecked() {
+        return hasAttribute("menu-item-checked");
+    }
+
+    private void waitForSubMenu() {
+        waitUntil(ExpectedConditions.attributeToBe(this, "aria-expanded",
+                "true"));
+    }
+
+}


Reviews:
user: login: vursen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T10:06:48Z
user: login: vursen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T06:41:13Z
user: login: web-padawan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T09:04:44Z
user: login: TatuLund
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T09:45:55Z
user: login: vursen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T04:11:09Z
user: login: vursen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T04:12:05Z
user: login: vursen
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T04:13:43Z

Review comments:
diff_hunk: @@ -89,19 +89,25 @@ public List<TestBenchElement> getSubMenuItems() {
      *            The sub menu overlay from which items are being collected.
      * @return List of TestBenchElements representing sub menu items.
      */
-    public List<TestBenchElement> getSubMenuItems(TestBenchElement overlay) {
-        return overlay.$("vaadin-menu-bar-item").all();
+    public List<MenuBarItemElement> getSubMenuItems(TestBenchElement overlay) {
+        return overlay.$(MenuBarItemElement.class).all();
     }
 
     /**
      * Get the sub menu overlay element.
      *
-     * @return TestBenchElement for the first open sub menu.
+     * @return TestBenchElement for the first open sub menu in this menu bar
      */
     public TestBenchElement getSubMenu() {
-        waitForSubMenu();
-        return (TestBenchElement) getDriver()
-                .findElement(By.tagName(OVERLAY_TAG));
+        var buttons = $(MenuBarButtonElement.class).all().stream()
+                .filter(element -> isVisible(element))
+                .collect(Collectors.toList());
+        var openedButton = buttons.stream()
+                .filter(button -> button.hasAttribute("expanded")).findFirst();
+        if (!openedButton.isPresent()) {
+            return null;
+        }
+        return openedButton.get().getSubMenu();
path: vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarElement.java
user: login: vursen
body: Could it be simplified?

```suggestion
        var button = $(MenuBarButtonElement.class).withAttribute("expanded").withCondition(this::isVisible).first();
        if (button == null) {
          return;
        }
        return button.getSubMenu();
```
created_at: 2025-04-30T09:57:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,64 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.flow.component.menubar.testbench;
+
+import org.openqa.selenium.support.ui.ExpectedConditions;
+
+import com.vaadin.testbench.TestBenchElement;
+import com.vaadin.testbench.elementsbase.Element;
+
+/**
+ * A TestBench element representing a <code>&lt;vaadin-menu-bar-item&gt;</code>
+ * element.
+ */
+@Element("vaadin-menu-bar-item")
+public class MenuBarItemElement extends TestBenchElement {
+
+    /**
+     * Get the sub menu overlay element linked to this menu item.
+     *
+     * @return TestBenchElement for the open sub menu.
+     */
+    public TestBenchElement getSubMenu() {
+        waitForSubMenu();
+        return (TestBenchElement) executeScript(
+                "return arguments[0].__overlay;", this);
+    }
+
+    /**
+     * Check if the item has open sub menu.
+     *
+     * @return True if there is sub menu open
+     */
+    public boolean isExpended() {
path: vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarItemElement.java
user: login: vursen
body: ```suggestion
    public boolean isExpanded() {
```
created_at: 2025-04-30T09:58:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,64 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.flow.component.menubar.testbench;
+
+import org.openqa.selenium.support.ui.ExpectedConditions;
+
+import com.vaadin.testbench.TestBenchElement;
+import com.vaadin.testbench.elementsbase.Element;
+
+/**
+ * A TestBench element representing a <code>&lt;vaadin-menu-bar-item&gt;</code>
+ * element.
+ */
+@Element("vaadin-menu-bar-item")
+public class MenuBarItemElement extends TestBenchElement {
+
+    /**
+     * Get the sub menu overlay element linked to this menu item.
+     *
+     * @return TestBenchElement for the open sub menu.
+     */
+    public TestBenchElement getSubMenu() {
+        waitForSubMenu();
+        return (TestBenchElement) executeScript(
+                "return arguments[0].__overlay;", this);
path: vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarItemElement.java
user: login: vursen
body: ```suggestion
        return getPropertyElement("__overlay");
```
created_at: 2025-04-30T09:59:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,53 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.flow.component.menubar.testbench;
+
+import org.openqa.selenium.support.ui.ExpectedConditions;
+
+import com.vaadin.testbench.TestBenchElement;
+import com.vaadin.testbench.elementsbase.Element;
+
+/**
+ * A TestBench element representing a {@code <vaadin-menu-bar-button>} element.
+ */
+@Element("vaadin-menu-bar-button")
+public class MenuBarButtonElement extends TestBenchElement {
+
+    /**
+     * Get the sub menu overlay element linked to this menu button.
+     *
+     * @return TestBenchElement for the open sub menu.
+     */
+    public TestBenchElement getSubMenu() {
path: vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarButtonElement.java
user: login: vursen
body: Would it make sense to add `getSubMenuItems` to `MenuBarButtonElement` and `MenuBarItemElement` for consistency with `MenuBarElement`?
created_at: 2025-05-01T06:41:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,71 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.flow.component.menubar.testbench;
+
+import org.openqa.selenium.support.ui.ExpectedConditions;
+
+import com.vaadin.testbench.TestBenchElement;
+import com.vaadin.testbench.elementsbase.Element;
+
+/**
+ * A TestBench element representing a {@code <vaadin-menu-bar-item>} element.
+ */
+@Element("vaadin-menu-bar-item")
+public class MenuBarItemElement extends TestBenchElement {
+
+    /**
+     * Get the sub menu overlay element linked to this menu item.
+     *
+     * @return TestBenchElement for the open sub menu.
+     */
+    public TestBenchElement getSubMenu() {
+        waitForSubMenu();
+        return getPropertyElement("__overlay");
path: vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarItemElement.java
user: login: web-padawan
body: This is using private API that is actually part of `OverlayPositionMixin`. It would be better if we could get a reference to the correct `vaadin-menu-bar-submenu` element from the DOM
created_at: 2025-05-05T09:04:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,71 @@
+/*
+ * Copyright 2000-2025 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.flow.component.menubar.testbench;
+
+import org.openqa.selenium.support.ui.ExpectedConditions;
+
+import com.vaadin.testbench.TestBenchElement;
+import com.vaadin.testbench.elementsbase.Element;
+
+/**
+ * A TestBench element representing a {@code <vaadin-menu-bar-item>} element.
+ */
+@Element("vaadin-menu-bar-item")
+public class MenuBarItemElement extends TestBenchElement {
+
+    /**
+     * Get the sub menu overlay element linked to this menu item.
+     *
+     * @return TestBenchElement for the open sub menu.
+     */
+    public TestBenchElement getSubMenu() {
+        waitForSubMenu();
+        return getPropertyElement("__overlay");
path: vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarItemElement.java
user: login: TatuLund
body: There is no better way currently. There is a proposal to create additional TypeScript test API in Vaadin 25, which would eventually fix this. So we need to go with this for time being.
created_at: 2025-05-05T09:45:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -54,16 +52,17 @@ public List<TestBenchElement> getButtons() {
      *
      * @return the button which opens the sub menu of overflowing items
      */
-    public TestBenchElement getOverflowButton() {
-        TestBenchElement overflowButton = $("[slot='overflow']").first();
+    public MenuBarButtonElement getOverflowButton() {
+        MenuBarButtonElement overflowButton = $(MenuBarButtonElement.class)
+                .withAttribute("slot", "overflow").first();
         if (overflowButton == null || overflowButton.hasAttribute("hidden")) {
             return null;
         }
         return overflowButton;
     }
 
     private boolean isOverflowButton(TestBenchElement element) {
-        return Objects.equals("overflow", element.getDomAttribute("slot"));
+        return element.getAttribute("slot").contains("overflow");
path: vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarElement.java
user: login: vursen
body: ```suggestion
        return "overflow".equals(element.getAttribute("slot"));
```
created_at: 2025-05-06T04:11:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -87,21 +86,24 @@ public List<TestBenchElement> getSubMenuItems() {
      *
      * @param overlay
      *            The sub menu overlay from which items are being collected.
-     * @return List of TestBenchElements representing sub menu items.
+     * @return List of MenuBarItemElement representing sub menu items.
      */
-    public List<TestBenchElement> getSubMenuItems(TestBenchElement overlay) {
-        return overlay.$("vaadin-menu-bar-item").all();
+    public List<MenuBarItemElement> getSubMenuItems(TestBenchElement overlay) {
+        return overlay.$(MenuBarItemElement.class).all();
     }
 
     /**
      * Get the sub menu overlay element.
      *
-     * @return TestBenchElement for the first open sub menu.
+     * @return TestBenchElement for the first open sub menu in this menu bar
      */
     public TestBenchElement getSubMenu() {
-        waitForSubMenu();
-        return (TestBenchElement) getDriver()
-                .findElement(By.tagName(OVERLAY_TAG));
+        var button = $(MenuBarButtonElement.class).withAttribute("expanded")
+                .withCondition(this::isVisible).first();
+        if (button == null) {
+            return null;
+        }
+        return button.getSubMenu();
path: vaadin-menu-bar-flow-parent/vaadin-menu-bar-testbench/src/main/java/com/vaadin/flow/component/menubar/testbench/MenuBarElement.java
user: login: vursen
body: ```suggestion
        return button != null ? button.getSubMenu() : null;
```
created_at: 2025-05-06T04:12:05Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-05-14T06:01:17Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=vaadin_flow-components&pullRequest=7338) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1 New issue](https://sonarcloud.io/project/issues?id=vaadin_flow-components&pullRequest=7338&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=vaadin_flow-components&pullRequest=7338&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=vaadin_flow-components&pullRequest=7338&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=vaadin_flow-components&pullRequest=7338&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=vaadin_flow-components&pullRequest=7338&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=vaadin_flow-components&pullRequest=7338)


author_association: NONE
user: login: vaadin-bot
created_at: 2025-05-26T17:37:22Z
body: This ticket/PR has been released with Vaadin 24.8.0.alpha8 and is also targeting the upcoming stable 24.8.0 version.
author_association: COLLABORATOR
user: login: vaadin-bot
created_at: 2025-07-01T12:13:10Z
body: This ticket/PR has been released with Vaadin 25.0.0-alpha1.
author_association: COLLABORATOR