Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
55892d1
docs: correct browserless tester docs for the 25.3 behavior changes
totally-not-ai[bot] Sep 22, 2026
3659c36
docs: document withinSlot and what browserless queries can see
totally-not-ai[bot] Sep 22, 2026
e0ae67c
docs: document per-test Vaadin configuration for browserless tests
totally-not-ai[bot] Sep 22, 2026
f73edf2
docs: show navigating to a location with a query string in browserles…
totally-not-ai[bot] Sep 22, 2026
15be9cc
docs: add 25.3 upgrade notes for the client engine, npm pinning and 401
totally-not-ai[bot] Sep 22, 2026
a5cecbe
docs: explain what happens to updates the client doesn't receive
totally-not-ai[bot] Sep 22, 2026
0e7f0c5
docs: show navigating to a location that carries a query string
totally-not-ai[bot] Sep 22, 2026
113d920
docs: document rejection of signal values the signal cannot hold
totally-not-ai[bot] Sep 22, 2026
e123067
docs: use the full GridContextMenuTester clickItem signature
totally-not-ai[bot] Sep 22, 2026
d68b7af
docs: tighten the 25.3 browserless and client engine wording
totally-not-ai[bot] Sep 22, 2026
1d3d072
docs: drop what the open browserless PRs already cover
totally-not-ai[bot] Sep 22, 2026
8badaac
docs: place the upload constraints after the general constraint rules
totally-not-ai[bot] Sep 22, 2026
a113898
docs: leave the tester table rows to the tester API documentation
totally-not-ai[bot] Sep 22, 2026
1742df0
docs: leave the versions-file format to the add-on packaging change
totally-not-ai[bot] Sep 22, 2026
7aaa20e
docs: don't promise a file format this page doesn't describe
totally-not-ai[bot] Sep 22, 2026
a8bbd54
docs: leave the undelivered invocation guidance on the JavaScript page
totally-not-ai[bot] Sep 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions articles/building-apps/views/pass-data/query-parameters.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ public class OrdersView extends Main implements BeforeEnterObserver {
[IMPORTANT]
You _cannot_ set query parameters to `null`. To clear a query parameter, exclude it from the [classname]`QueryParameters` object.

[since:com.vaadin:vaadin@V25.3]#When you navigate by location string, the query string can also be part of the string itself, which is handy for a link that's already assembled -- one that comes from a mail template or an external system, for example. The query string is parsed into the [classname]`QueryParameters` of the resulting location, so the view reads it the same way either way:#

[source,java]
----
UI.getCurrentOrThrow().navigate("orders?filter=late&sort=due");
----

The string form and a separate [classname]`QueryParameters` object are alternatives, not a combination: [methodname]`navigate(String, QueryParameters)` throws an [classname]`IllegalArgumentException` if the location carries a query string or a fragment of its own while the parameters aren't empty, since the ones in the string would be lost. A location that consists only of a fragment, such as `"#totals"`, identifies no route and is passed on to the client router as it is.


== Binding a Field to a Query Parameter

Expand Down
5 changes: 3 additions & 2 deletions articles/flow/advanced/application-lifecycle.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ See <<{articles}/flow/advanced/session-and-ui-init-listener#, Session and UI Lis

((("UI", "loading")))
When a browser first accesses a URL mapped to the servlet of a particular UI class, the Vaadin servlet generates a loader page.
The page loads the client-side engine (widget set), which in turn loads the UI in a separate request to the Vaadin servlet.
The page loads the client-side engine, which in turn loads the UI in a separate request to the Vaadin servlet.
[since:com.vaadin:vaadin@V25.3]#The engine is written in TypeScript and is bundled with the rest of the frontend by Vite. Earlier versions loaded an engine compiled from Java with Google Web Toolkit (GWT) from `VAADIN/static/client/`.#

A [classname]`UI` instance is created when the client-side engine makes its first request.

Expand All @@ -99,7 +100,7 @@ To do this, you need to extend the [classname]`VaadinServlet` and add a [interfa
You can then add the bootstrap listener to a user session with the
[methodname]`addIndexHtmlRequestListener()` method when the session is initialized.

Loading the widget set is handled in the loader page with functions defined in a separate [filename]`BootstrapHandler.js` script, whose content is included inline in the page.
Loading the client-side engine is handled in the loader page with functions defined in a separate [filename]`BootstrapHandler.js` script, whose content is included inline in the page.

[[application.lifecycle.ui-expiration]]
== UI Expiration
Expand Down
2 changes: 2 additions & 0 deletions articles/flow/routing/navigation.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ editButton.addClickListener(e ->

In addition to handling <<route-parameters#, Route Parameters>> as in the above example, the [methodname]`UI.navigate()` method has other overloads that allow you to pass <<additional-guides/query-parameters#, Query Parameters>> or <<additional-guides/route-templates#, Route Templates Parameters>> to the target view.

[since:com.vaadin:vaadin@V25.3]#A location string may carry its query string and fragment itself, as in [methodname]`ui.navigate("user/123?tab=orders#totals")`. The query string is parsed into the query parameters of the resulting location. Passing a [classname]`QueryParameters` object alongside such a location is an error -- the parameters in the string would be lost -- and throws an [classname]`IllegalArgumentException`.#

== Using the RouterLink Component

[classname]`RouterLink` is a special component based on the <a> tag to create links pointing to route targets in your application.
Expand Down
4 changes: 2 additions & 2 deletions articles/flow/security/advanced-topics/frequent-issues.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ From time to time, Vaadin users perform security tests on the framework and repo

== Content-Security-Policy (CSP) Set to Unsafe Values

The settings `script-src 'unsafe-inline' 'unsafe-eval'` and `style-src 'unsafe-inline'` are required during Vaadin application start, that is, the bootstrap process. The bootstrap process that starts the application loads the widget set which is the client-side engine part of the application. This consists of precompiled JavaScript logic, for example, for the communication protocol, DOM control, Buttons, Layouts, etc., but not the application code. The widget set is a static resource. After it's loaded, the client-side engine needs to be started using [methodname]`JavaScript.eval()`.
The settings `script-src 'unsafe-inline' 'unsafe-eval'` and `style-src 'unsafe-inline'` are required during Vaadin application start, that is, the bootstrap process. The bootstrap process that starts the application loads the client-side engine, which is the part of the framework that runs in the browser. It consists of JavaScript logic for the communication protocol and DOM control, for example, but not of the application code. The engine is a static resource, and once it's loaded, it's started by a script that the bootstrap page includes inline. The engine then evaluates the JavaScript expressions that the server sends -- those of [methodname]`executeJs()` and of the event data expressions -- by constructing functions from them in the browser, which is what `unsafe-eval` permits.

Hence, these settings are architectural limitations in Vaadin, so that the framework can start its client-side engine in the browser.

Expand Down Expand Up @@ -76,7 +76,7 @@ This happens when Vaadin sends user events to the server and receives JSON data

== Open Redirection &ndash; DOM-Based

This issue is reported because `vaadinBootstrap.js` indeed opens a new HTTP request. This is done to fetch the initial application state. On first request to an application URL, Vaadin replies with the bootstrap file, which, in turn loads the theme, widget set, and application state.
This issue is reported because `vaadinBootstrap.js` indeed opens a new HTTP request. This is done to fetch the initial application state. On first request to an application URL, Vaadin replies with the bootstrap file, which, in turn loads the theme, client-side engine, and application state.

The way this request is done can't be used by an attacker to modify the application state. Hence, this is a false positive.

Expand Down
4 changes: 4 additions & 0 deletions articles/flow/security/enabling-security.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ Configure form login with [methodname]`loginView()` rather than by calling Sprin
A hand-written [methodname]`formLogin()` configuration does none of that. The internal request that the log-in view itself makes to the server is then treated as an unauthenticated request and redirected back to the log-in view, which produces an endless redirect loop -- typically visible as an `ERR_TOO_MANY_REDIRECTS` page or a "Connection lost" notification instead of a working log-in form.
====

.Blocked Sub-Resources Answer With 401
[NOTE]
[since:com.vaadin:vaadin@V25.3]#With a log-in view configured, an unauthorized request that the browser makes for a sub-resource -- a stylesheet, script, image, font, or web app manifest -- is answered with `401 Unauthorized` rather than redirected to the log-in view, which such a request can't render. The browser then reports the resource that was blocked, instead of failing in a redirect loop. Permit the path in the security configuration to serve the resource; see <<{articles}/upgrading#spring-security-and-stylesheet, Spring Security and StyleSheet>>.#

.Never Use Hard-Coded Credentials in Production
[WARNING]
The implementation of the [methodname]`userDetailsService()` method is just an in-memory implementation for the sake of brevity in this documentation. In a normal application, you can change the Spring Security configuration to use an authentication provider for Lightweight Directory Access Protocol (LDAP), JAAS, and other real-world sources. See https://dzone.com/articles/flow/spring-security-authentication[Spring Security authentication providers] to read more about them.
Expand Down
25 changes: 25 additions & 0 deletions articles/flow/testing/browserless/component-query.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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 <<slots, Filtering by Slot>>.

2+h| Value & Properties

| [methodname]`withValue(V)`
Expand Down Expand Up @@ -192,6 +197,26 @@ 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 <<getting-started#test-ids, Getting Started>> 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<Button> footerButtons = test(card).find(Button.class)
.withinSlot("footer").all();
----

A component is in slot `name` when it carries `slot="name"` itself, or when an element that carries it is one of its ancestors. Everything below a slot belongs to that slot, however deeply nested, and when slots nest, the outermost one -- the one closest to the search context -- decides. This makes [methodname]`withinSlot()` different from [methodname]`withAttribute("slot", name)`, which matches only a component that is itself the slot root.

Slot names are the ones the component uses in the browser, and they're component specific: [classname]`Card` and [classname]`ConfirmDialog` name their header slot `header`, while [classname]`Dialog` names it `header-content`. A name that no component is slotted under produces no results. Printing the component tree with [methodname]`toPrettyTree()` shows the slots as `@slot='...'`.

For the slots of a component that has a tester, the tester's own finders are usually shorter; see <<component-testers#slot-scoped-finders, Slot-Scoped Finders>>.


=== Checking Existence

Use [methodname]`exists()` to check whether a query has results without throwing an exception when none are found:
Expand Down
31 changes: 31 additions & 0 deletions articles/flow/testing/browserless/component-testers.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,21 @@ 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 <<component-query#slots, Filtering by Slot>>.


=== 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.
Expand All @@ -159,6 +174,22 @@ amount.setValue(-5.0);
----


[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.

An upload is different from a value: 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]
----
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

All testers inherit from [classname]`ComponentTester`, which provides methods useful across component types:
Expand Down
3 changes: 3 additions & 0 deletions articles/flow/testing/browserless/locators.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ Locators share their filter vocabulary with [classname]`ComponentQuery`:
| [methodname]`withAttribute(String)` / [methodname]`withAttribute(String, String)` / [methodname]`withoutAttribute(...)`
| Matches by attribute presence and value.

| [since:com.vaadin:vaadin@V25.3]#[methodname]`withinSlot(String)`#
| Matches components sitting in the given named slot of the component that hosts them -- for example `footer` on a [classname]`Card`. Everything below a slot belongs to it. See <<component-query#slots, Filtering by Slot>>.

| [methodname]`withValue(V)`
| Matches [interfacename]`HasValue` components whose value equals the given value.

Expand Down
Loading
Loading