From 55892d1e49b2181caaa784195a4aa367a345c065 Mon Sep 17 00:00:00 2001 From: "totally-not-ai[bot]" <290682512+totally-not-ai[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 07:12:13 +0000 Subject: [PATCH 01/16] docs: correct browserless tester docs for the 25.3 behavior changes The "Constraint Enforcement" section stated the opposite of the shipped behavior: value testers commit a value that only breaks a validation constraint and leave the field invalid, and text testers refuse only input the browser physically prevents. The Grid Context Menu example used ContextMenuTester, which test(gridContextMenu) no longer returns, and the context menu section claimed that a menu need not be opened first, while clickItem() and friends now throw on a closed menu. Rewrite those sections, add the 25.3 tester additions to the Common Testers table (TreeGrid, GridContextMenu, SplitLayout, Card, AvatarGroup, Accordion, Grid deselection, getCellComponent/renderCellComponent, dialog dismissal, step buttons, clear()/clickClearButton(), getItemTexts(), upload constraints, slot-scoped finders) and document the behavior changes in the migration guide. --- .../browserless/component-testers.adoc | 117 +++++++++++++++--- .../flow/testing/browserless/migration.adoc | 62 ++++++++++ .../browserless/overlay-components.adoc | 84 ++++++++++++- 3 files changed, 242 insertions(+), 21 deletions(-) diff --git a/articles/flow/testing/browserless/component-testers.adoc b/articles/flow/testing/browserless/component-testers.adoc index ddcc4e5ffc..129f5e2dc4 100644 --- a/articles/flow/testing/browserless/component-testers.adoc +++ b/articles/flow/testing/browserless/component-testers.adoc @@ -57,37 +57,37 @@ The following table shows frequently used testers and their key methods: | Component | Key Tester Methods | [classname]`TextField` -| [methodname]`setValue(String)`, [methodname]`clear()` +| [methodname]`setValue(String)`, [methodname]`clear()`, [since:com.vaadin:vaadin@V25.3]#[methodname]`clickClearButton()`# | [classname]`Button` | [methodname]`click()` | [classname]`Checkbox` -| [methodname]`click()` (toggles checked state) +| [methodname]`click()` (toggles checked state), [since:com.vaadin:vaadin@V25.3]#[methodname]`check()`, [methodname]`uncheck()`, [methodname]`isChecked()`# | [classname]`NumberField` -| [methodname]`setValue(double)` +| [methodname]`setValue(double)`, [since:com.vaadin:vaadin@V25.3]#[methodname]`clear()`, [methodname]`clickClearButton()`, [methodname]`stepUp()`, [methodname]`stepDown()`, [methodname]`isValid()`# | [classname]`Select` | [methodname]`selectItem(String)`, [methodname]`selectItem(int)` | [classname]`ComboBox` -| [methodname]`selectItem(String)`, [methodname]`getSuggestionItems()` +| [methodname]`selectItem(String)`, [methodname]`getSuggestionItems()`, [since:com.vaadin:vaadin@V25.3]#[methodname]`clickClearButton()`# | [classname]`Grid` -| [methodname]`getRow(index)`, [methodname]`size()`, [methodname]`getCellText(row, column)` +| [methodname]`getRow(index)`, [methodname]`size()`, [methodname]`getCellText(row, column)`, [methodname]`getCellComponent(row, column)`, [methodname]`select(row)`, [methodname]`selectAll()`, [since:com.vaadin:vaadin@V25.3]#[methodname]`deselect(row)`, [methodname]`deselectAll()`, [methodname]`renderCellComponent(row, column)`, [methodname]`contextMenu(row)`# | [classname]`Notification` | [methodname]`getText()` | [classname]`Dialog` -| [methodname]`open()`, [methodname]`close()` +| [methodname]`open()`, [methodname]`close()`, [since:com.vaadin:vaadin@V25.3]#[methodname]`pressEscape()`, [methodname]`clickOutside()`, [methodname]`isOpen()`, [methodname]`findInHeader(Class)`, [methodname]`findInFooter(Class)`# | [classname]`ConfirmDialog` | [methodname]`open()`, [methodname]`confirm()`, [methodname]`cancel()`, [methodname]`reject()` | [classname]`Upload` -| [methodname]`upload(File)`, [methodname]`uploadAll(File...)` +| [methodname]`upload(File)`, [methodname]`uploadAll(File...)`, [since:com.vaadin:vaadin@V25.3]#[methodname]`removeFile(File)`, [methodname]`getLastUploadStatus()`, [methodname]`ensureUploaded()`# | [since:com.vaadin:vaadin@V25.2]#[classname]`Breadcrumbs`# | [classname]`BreadcrumbsTester`: [methodname]`getItemTexts()`, [methodname]`clickItem(String)`, [methodname]`clickItem(int)` @@ -112,6 +112,33 @@ The following table shows frequently used testers and their key methods: | [since:com.vaadin:vaadin@V25.3]#[classname]`GridPro`# | [classname]`GridProTester`: [methodname]`setValue(row, column, value)`, plus everything [classname]`GridTester` offers + +| [since:com.vaadin:vaadin@V25.3]#[classname]`TreeGrid`# +| [classname]`TreeGridTester`: [methodname]`expand(row)`, [methodname]`collapse(row)`, [methodname]`isExpanded(row)`, [methodname]`hasChildren(row)`, plus everything [classname]`GridTester` offers + +| [since:com.vaadin:vaadin@V25.3]#[classname]`GridContextMenu`# +| [classname]`GridContextMenuTester`: [methodname]`open(row)`, [methodname]`clickItem(String...)`, [methodname]`getItemTexts()`. See <> + +| [classname]`Accordion` +| [classname]`AccordionTester`: [methodname]`openDetails(summary)`, [methodname]`isOpen(summary)`, [methodname]`getPanel(summary)`, [since:com.vaadin:vaadin@V25.3]#[methodname]`closeDetails()`, [methodname]`closeDetails(summary)`, [methodname]`toggleDetails(summary)`# + +| [since:com.vaadin:vaadin@V25.3]#[classname]`SplitLayout`# +| [classname]`SplitLayoutTester`: [methodname]`dragSplitterTo(position)`, [methodname]`getSplitterPosition()`, [methodname]`getPrimaryComponent()`, [methodname]`getSecondaryComponent()`, [methodname]`findInPrimary(Class)`, [methodname]`findInSecondary(Class)` + +| [since:com.vaadin:vaadin@V25.3]#[classname]`Card`# +| [classname]`CardTester`: [methodname]`getTitleAsText()`, [methodname]`getSubtitleAsText()`, [methodname]`getHeader()`, [methodname]`getMedia()`, [methodname]`getFooterComponents()`, [methodname]`findInHeader(Class)`, [methodname]`findInFooter(Class)` + +| [since:com.vaadin:vaadin@V25.3]#[classname]`AvatarGroup`# +| [classname]`AvatarGroupTester`: [methodname]`getItems()`, [methodname]`getVisibleItems()`, [methodname]`getOverflowItems()`, [methodname]`getOverflowAbbreviation()`, [methodname]`getActiveUsersLabel()` + +| [classname]`LoginOverlay` +| [methodname]`openOverlay()`, [methodname]`isOpen()`, [since:com.vaadin:vaadin@V25.3]#[methodname]`findInFooter(Class)`, [methodname]`findInCustomFormArea(Class)`# + +| [classname]`DatePicker`, [classname]`TimePicker` +| [methodname]`setValue(value)`, [since:com.vaadin:vaadin@V25.3]#[methodname]`clear()`, [methodname]`clickClearButton()`, [methodname]`isValid()`# + +| [classname]`DateTimePicker` +| [methodname]`setValue(value)`, [since:com.vaadin:vaadin@V25.3]#[methodname]`clear()`, [methodname]`isValid()`# |=== @@ -133,31 +160,89 @@ test(categorySelect).selectItem("Meals & Entertainment"); ==== +[role="since:com.vaadin:vaadin@V25.3"] +=== Slot-Scoped Finders + +Components that place content in named slots expose finders scoped to a single slot, so that a query doesn't have to sift through everything the component holds: + +[source,java] +---- +Button save = test(dialog).findInFooter(Button.class).withText("Save").single(); +Span caption = test(card).findInHeader(Span.class).single(); +Button next = test(splitLayout).findInPrimary(Button.class).single(); +---- + +[classname]`DialogTester` and [classname]`CardTester` have [methodname]`findInHeader()` and [methodname]`findInFooter()`, [classname]`SplitLayoutTester` has [methodname]`findInPrimary()` and [methodname]`findInSecondary()`, and [classname]`LoginOverlayTester` has [methodname]`findInFooter()` and [methodname]`findInCustomFormArea()`. These are tester methods; the generated locators don't have them. For any other slot, filter a query with [methodname]`withinSlot()` -- see <>. + + === Constraint Enforcement -Testers enforce the same constraints that apply to user input in a browser. If a component has constraints like minimum or maximum values, required fields, or step intervals, the tester rejects values that violate them. This ensures your tests reflect actual user behavior. +A tester does what the user can do in the browser, which means it separates input the browser makes impossible from input the browser accepts and then marks invalid. + + +[role="since:com.vaadin:vaadin@V25.3"] +==== Values That Only Break Validation + +A value that breaks a validation constraint is committed, because the browser commits it too and leaves the field invalid. This covers `min`, `max`, and `step` on [classname]`NumberField` and [classname]`IntegerField`, `min` and `max` on the date and time pickers, `minLength` and `pattern` on the text fields, and the empty value of a required field. -For example, a [classname]`NumberField` with a minimum of `0` rejects negative values through the tester: +Assert the outcome with [methodname]`isValid()` on the tester instead of expecting [methodname]`setValue()` to throw: [source,java] ---- NumberField amount = new NumberField("Amount"); amount.setMin(0); -amount.setStep(0.01); -// This throws an exception — the value violates the minimum constraint +// Committed, exactly as when the user types it test(amount).setValue(-5.0); + +Assertions.assertEquals(-5.0, amount.getValue()); +Assertions.assertFalse(test(amount).isValid()); +---- + +[methodname]`isValid()` runs the component's own default validator, and it also reports the field as invalid when something outside the component -- a [classname]`Binder` or a custom validator -- has marked it invalid. That makes it the method to assert on in a form test. + +[NOTE] +Earlier versions refused these values with an exception. See <> for updating existing tests. + + +[role="since:com.vaadin:vaadin@V25.3"] +==== Values the User Cannot Type + +Input the browser physically prevents is refused with an [classname]`IllegalArgumentException`. A text input truncates what is over `maxLength` and filters out the keystrokes `allowedCharPattern` doesn't match, so a value that breaks either rule is one no user could have produced: + +[source,java] +---- +TextField code = new TextField("Code"); +code.setMaxLength(4); + +// Throws IllegalArgumentException -- the field would never hold five characters +test(code).setValue("12345"); ---- -To test that your application handles invalid input correctly, set values directly through the component's Java API instead of the tester: +Passing `null` is refused the same way when the field's empty value isn't `null`, since a text input has no null state. Use [methodname]`clear()` to empty a field. + + +[role="since:com.vaadin:vaadin@V25.3"] +==== Emptying a Field + +[methodname]`clear()` models selecting the contents and deleting them, which the user can always do, so it needs no clear button -- and a required field may legitimately end up invalid as a result. [methodname]`clickClearButton()` clicks the clear button instead, and fails when that button isn't visible, because a hidden clear button isn't something the user can click. + + +[role="since:com.vaadin:vaadin@V25.3"] +==== Upload Constraints + +[classname]`UploadTester` puts files through the same gate the browser applies before it sends anything to the server: [methodname]`setMaxFiles()`, [methodname]`setMaxFileSize()`, and the accepted file types, in that order. A file that fails one of them never reaches the upload handler or receiver, and a [classname]`FileRejectedEvent` is fired, as it is in the browser. + +A refused file is no more of an error in the tester than it is in the browser, so the upload doesn't throw. Check what became of each file with [methodname]`getLastUploadStatus()`, or call [methodname]`ensureUploaded()` to fail the test unless every file of the last upload went through: [source,java] ---- -// Bypass tester constraints to test validation logic -amount.setValue(-5.0); -// Now verify that your application reacts appropriately +test(upload).upload(new File("report.pdf")); +test(upload).ensureUploaded(); ---- +`maxFiles` is checked against an emulated file list, so files stay in it between calls, the same way the entries the browser shows do. An [classname]`Upload` with a plain [interfacename]`Receiver` implicitly allows one file, which makes a second upload fail; [methodname]`removeFile()` makes room for it the way the user does. + === Base Methods @@ -183,6 +268,8 @@ All testers inherit from [classname]`ComponentTester`, which provides methods us | Creates a component query scoped to the children of the wrapped component. |=== +[since:com.vaadin:vaadin@V25.3]#Value changes and property updates that a tester performs are reported as coming from the client, so [methodname]`isFromClient()` returns `true` for the events they fire. A listener that reacts only to user input therefore runs in the test as it does in the browser.# + [role="since:com.vaadin:vaadin@V25.2"] == Testing a Component Without a View diff --git a/articles/flow/testing/browserless/migration.adoc b/articles/flow/testing/browserless/migration.adoc index a214090e67..b314aae174 100644 --- a/articles/flow/testing/browserless/migration.adoc +++ b/articles/flow/testing/browserless/migration.adoc @@ -94,6 +94,68 @@ If you're migrating from JUnit 5 (`vaadin-testbench-unit-junit5`), no other code If you're migrating from JUnit 4 (`vaadin-testbench-unit`), you also need to update JUnit annotations and imports (e.g., `@Before` → `@BeforeEach`, `Assert` → `Assertions`). See the https://docs.junit.org/current/user-guide/migrating-from-junit4.html[JUnit 5 migration guide] for details. +[role="since:com.vaadin:vaadin@V25.3"] +== Behavior Changes in 25.3 + +The testers model user interaction more strictly in 25.3. The changes below can make an existing test fail, and the fix is the same in each case: assert on the state the component ends up in, the way a user reads it in the browser. + + +[[constraint-violating-values-are-committed]] +=== Constraint-Violating Values Are Committed + +[methodname]`setValue()` on [classname]`NumberFieldTester`, [classname]`DatePickerTester`, [classname]`TimePickerTester`, and [classname]`DateTimePickerTester` no longer throws for a value outside `min` and `max` or off the `step` scale. The value is committed, as the browser commits it, and the field is left invalid. + +Replace an expected exception with an assertion on [methodname]`isValid()`: + +.Before +[source,java] +---- +Assertions.assertThrows(IllegalArgumentException.class, + () -> test(amount).setValue(-5.0)); +---- + +.After +[source,java] +---- +test(amount).setValue(-5.0); +Assertions.assertFalse(test(amount).isValid()); +---- + +A test that used to set such a value through the component's own API to work around the tester can now go through the tester. + + +[[text-values-the-user-cannot-type-are-refused]] +=== Text Values the User Cannot Type Are Refused + +[classname]`TextFieldTester` and [classname]`TextAreaTester` now refuse a value the browser physically prevents -- one longer than `maxLength`, or one containing characters `allowedCharPattern` filters out -- with an [classname]`IllegalArgumentException`. A value that only breaks `minLength` or `pattern` is committed, as before. Set the value through the component's API for a test that deliberately puts impossible content in a field. + + +[[getcellcomponent-returns-the-rendered-component]] +=== getCellComponent Returns the Rendered Component + +[methodname]`GridTester.getCellComponent()` returns the component the grid actually rendered for the cell, which is the instance the browser shows: the same cell gives the same instance twice, and interacting with it affects the row on screen. It previously rendered a throw-away copy on every call. + +Tests written against the old behavior -- and cells the grid doesn't render at all, such as those of a hidden column, which now throw -- can use [methodname]`renderCellComponent()` instead, which keeps rendering a copy per call. + + +[[test-returns-a-more-specific-tester]] +=== test() Returns a More Specific Tester + +[methodname]`test(treeGrid)` returns a [classname]`TreeGridTester` and [methodname]`test(gridContextMenu)` returns a [classname]`GridContextMenuTester`, rather than the [classname]`GridTester` and [classname]`ContextMenuTester` they returned before. Code that assigned the result to an explicitly typed variable no longer compiles and needs the new type -- or `var`. + + +[[menu-testers-require-an-open-menu]] +=== Menu Testers Require an Open Menu + +[classname]`ContextMenuTester` and [classname]`GridContextMenuTester` interact with the items of an open menu only, since a closed menu isn't attached to the UI in the browser either. Add [methodname]`open()` before [methodname]`clickItem()`, [methodname]`isItemChecked()`, or [methodname]`getItemTooltipText()`. [methodname]`find()` on the tester still works while the menu is closed. + + +[[grid-context-menus-have-their-own-tester]] +=== Grid Context Menus Are Opened on a Row + +A [classname]`GridContextMenu` is about a row, so [classname]`GridContextMenuTester` is obtained with [methodname]`test(grid).contextMenu(row)` and opened with [methodname]`open()`, or opened directly with [methodname]`open(row)`. Clicking an item of a grid context menu without opening it on a row no longer works. See <>. + + == TestBench End-to-End Coexistence Browserless tests and TestBench end-to-end tests can coexist in the same project. The two modules use separate package namespaces, so having both `browserless-test-junit6` and `vaadin-testbench` on the test classpath doesn't cause conflicts. diff --git a/articles/flow/testing/browserless/overlay-components.adoc b/articles/flow/testing/browserless/overlay-components.adoc index 47fff1064c..655ef5932f 100644 --- a/articles/flow/testing/browserless/overlay-components.adoc +++ b/articles/flow/testing/browserless/overlay-components.adoc @@ -17,14 +17,36 @@ Some Vaadin components render their content in overlays, which means their child [classname]`ContextMenu` renders its items in an overlay outside the main component tree. Use [classname]`ContextMenuTester` to interact with menu items and any custom components inside the menu. +=== Opening and Closing the Menu + +A closed context menu isn't attached to the UI, so, exactly as in the browser, its items can't be interacted with. Open the menu before clicking an item or reading its state; [methodname]`clickItem()`, [methodname]`isItemChecked()`, [methodname]`getItemTexts()`, and [methodname]`getItemTooltipText()` throw an [classname]`IllegalStateException` on a closed menu. + +[source,java] +---- +ContextMenuTester menu_ = test(view.contextMenu); + +// Simulates the user asking for the menu, for example with a right click +menu_.open(); +menu_.clickItem("Edit"); +menu_.close(); +---- + +[methodname]`open()` renders no client-side overlay; it simulates the server-side state changes that opening the menu produces in the browser. [since:com.vaadin:vaadin@V25.3]#Both [methodname]`open()` and [methodname]`close()` are reported as user actions, so the resulting [classname]`OpenedChangeEvent` has [methodname]`isFromClient()` returning `true`. Opening a menu that is already open throws.# + +.Behavior Changed in 25.3 +[NOTE] +Earlier versions let a test click the items of a closed menu. See <> for updating existing tests. + + === Clicking Menu Items -Use [methodname]`clickItem()` to simulate clicking a menu item by its text or zero-based position. You don't need to open the menu first -- the tester operates directly on server-side state. +Use [methodname]`clickItem()` to simulate clicking a menu item by its text or zero-based position. [source,java] ---- ContextMenu menu = view.contextMenu; ContextMenuTester menu_ = test(menu); +menu_.open(); // Click a top-level item by text menu_.clickItem("Edit"); @@ -38,12 +60,32 @@ menu_.clickItem(1, 0); // first item in second item's submenu ---- +[role="since:com.vaadin:vaadin@V25.3"] +=== Reading Menu Item Texts + +[methodname]`getItemTexts()` returns the texts of the items the browser shows, in display order and without the hidden ones, which makes it the way to assert on the contents of a menu that is built dynamically. Pass a path to read the items of a sub menu: + +[source,java] +---- +menu_.open(); + +// ["Preview", "Share"] -- a hidden item is left out +Assertions.assertEquals(List.of("Preview", "Share"), menu_.getItemTexts()); + +// The items of the "Share" sub menu +Assertions.assertEquals(List.of("Copy link", "Email"), + menu_.getItemTexts("Share")); +---- + + === Checkable Menu Items Use [methodname]`isItemChecked()` to check whether a checkable item is checked. Clicking a checkable item toggles its checked state automatically. [source,java] ---- +menu_.open(); + menu_.clickItem("Bold"); Assertions.assertTrue(menu_.isItemChecked("Bold")); @@ -54,7 +96,7 @@ Assertions.assertFalse(menu_.isItemChecked("Bold")); === Finding Components in the Menu -If you've added custom components (not just text items) to the menu, use the tester's [methodname]`find()` method on the wrapped component (e.g., [methodname]`test(menu).find(...)`) instead of the top-level [methodname]`find()` query. Call [methodname]`open()` first if you need the components to be in an attached state. +If you've added custom components (not only text items) to the menu, use the tester's [methodname]`find()` method on the wrapped component ([methodname]`test(menu).find(...)`) instead of the top-level [methodname]`find()` query. [methodname]`find()` is the one tester method that works on a closed menu, since it queries the menu contents rather than the UI; the components it returns are detached until the menu is opened. Open the menu first to interact with them. [source,java] ---- @@ -68,18 +110,40 @@ Assertions.assertTrue(div.isAttached()); ---- +[[grid-context-menu]] +[role="since:com.vaadin:vaadin@V25.3"] === Grid Context Menu -[classname]`GridContextMenu` extends [classname]`ContextMenu`, so the same [classname]`ContextMenuTester` API works for grid context menus. +A [classname]`GridContextMenu` is always about a row, so it has a tester of its own, [classname]`GridContextMenuTester`, which addresses rows. Reach it through the grid's tester with [methodname]`contextMenu(row)`, which targets a row without opening the menu, and open it there: [source,java] ---- -GridContextMenu gridMenu = view.gridContextMenu; -ContextMenuTester> gridMenu_ = test(gridMenu); +var menu_ = test(grid).contextMenu(0); -gridMenu_.clickItem("Delete"); +menu_.open(); +menu_.clickItem("Delete"); ---- +Opening the menu fires a [classname]`GridContextMenuOpenedEvent` reporting the target row, so a handler that builds the items from the row runs as it does in the browser. [methodname]`open(row)` opens the menu on a row directly, which is shorter when a test opens it on several rows in turn: + +[source,java] +---- +GridContextMenuTester, Person> menu_ = + test(view.gridContextMenu); + +menu_.open(0); +Assertions.assertEquals(List.of("Edit", "Delete"), menu_.getItemTexts()); +menu_.close(); + +menu_.open(1); +---- + +A tester obtained with [methodname]`test(gridContextMenu)` targets no row, so it has to be opened with [methodname]`open(row)`. Everything else -- [methodname]`clickItem()`, [methodname]`isItemChecked()`, [methodname]`getItemTexts()`, and [methodname]`find()` -- behaves as it does on [classname]`ContextMenuTester`. + +.Behavior Changed in 25.3 +[NOTE] +[methodname]`test(gridContextMenu)` previously returned a [classname]`ContextMenuTester`. See <> for updating existing tests. + === Common Pitfalls @@ -110,7 +174,15 @@ menuBar_.clickItem("File"); // Click nested item menuBar_.clickItem("File", "Save As"); + +// The visible top-level items, in display order +List topLevel = menuBar_.getItemTexts(); + +// The items of the "File" sub menu +List fileItems = menuBar_.getItemTexts("File"); ---- +A [classname]`MenuBar` is always visible, so its items need no opening. [since:com.vaadin:vaadin@V25.3]#[methodname]`getItemTexts()`# leaves out hidden items, which are also skipped when an item is looked up by text or position. + [discussion-id]`E3F7D8A2-9B14-4C6E-A1D0-8F5E2C3B7A91` From 3659c36e512df8ecdb7b0358bf7ea812b75f88d7 Mon Sep 17 00:00:00 2001 From: "totally-not-ai[bot]" <290682512+totally-not-ai[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 07:12:19 +0000 Subject: [PATCH 02/16] docs: document withinSlot and what browserless queries can see Add the withinSlot() filter to the component query and locator filter tables, and a section on what find() reaches: components a grid renders per item are reached through getCellComponent()/renderCellComponent(), overlay content only while the overlay is open, and column header, footer and editor components by a plain find(). --- .../testing/browserless/component-query.adoc | 49 +++++++++++++++++++ .../flow/testing/browserless/locators.adoc | 3 ++ 2 files changed, 52 insertions(+) diff --git a/articles/flow/testing/browserless/component-query.adoc b/articles/flow/testing/browserless/component-query.adoc index 7ad1d61b4b..4b9bffb0f2 100644 --- a/articles/flow/testing/browserless/component-query.adoc +++ b/articles/flow/testing/browserless/component-query.adoc @@ -128,6 +128,11 @@ The following table lists all available filter methods, grouped by category: | [methodname]`withoutAttribute(String)` | Excludes components that have the given attribute. +2+h| Slots + +| [since:com.vaadin:vaadin@V25.3]#[methodname]`withinSlot(String)`# +| Matches components that sit in the given named slot of the component that hosts them. See <>. + 2+h| Value & Properties | [methodname]`withValue(V)` @@ -192,6 +197,50 @@ Button submitButton = find(Button.class).testId("submit-button"); To combine a test ID with other filter conditions, use the [methodname]`withTestId()` filter method instead and finish the chain with a regular terminal operator. Since test IDs are expected to be unique, both forms fail if more than one component matches. See <> for more on test IDs. +[[slots]] +[role="since:com.vaadin:vaadin@V25.3"] +=== Filtering by Slot + +Components such as [classname]`Card`, [classname]`Dialog`, and [classname]`SplitLayout` place content in named slots. An unfiltered query returns matches from all of them, and [methodname]`withinSlot()` narrows the result to one slot: + +[source,java] +---- +// Only the buttons the card put in its footer +List