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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions articles/flow/testing/browserless/extensions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,19 @@ Both extensions support a builder-style API for configuration, used as an altern

| [methodname]`withComponentTesterPackages(String...)`
| Adds packages to scan for custom [classname]`ComponentTester` implementations. Equivalent to [annotationname]`@ComponentTesterPackages`.

| [since:com.vaadin:vaadin@V25.3]#[methodname]`withApplicationProperty(String, String)`#
| Sets a Vaadin application property for the environment the extension creates. [methodname]`withApplicationProperties(Map)` sets several at once.

| [since:com.vaadin:vaadin@V25.3]#[methodname]`withFeatureFlags(String...)`#
| Enables the given feature flags. [methodname]`withFeatureFlag(String, boolean)` enables or disables a single flag.

| [since:com.vaadin:vaadin@V25.3]#[methodname]`withConfiguration(BrowserlessConfiguration)`#
| Applies a configuration built elsewhere, so that several test classes can share it.
|===

Application properties, feature flags, and [classname]`Lookup` services can also be declared with the [annotationname]`@BrowserlessTestConfig` annotation, on a test class or on a single test method. See <<test-configuration#, Test Configuration>>.

The [annotationname]`@ViewPackages` annotation still works when placed on the test class; programmatic configuration adds to what the annotation declares.

.Extension with Programmatic Configuration
Expand Down
11 changes: 11 additions & 0 deletions articles/flow/testing/browserless/multi-user.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,19 @@ For advanced setups, [classname]`BrowserlessApplicationContext.Builder` exposes

| [methodname]`withCloseHook(Runnable)`
| Registers a callback to run after the context tears down -- intended for releasing framework-specific state.

| [since:com.vaadin:vaadin@V25.3]#[methodname]`withApplicationProperty(String, String)` / [methodname]`withApplicationProperties(String...)`#
| Sets Vaadin application properties for the environment the context creates.

| [since:com.vaadin:vaadin@V25.3]#[methodname]`withFeatureFlags(String...)` / [methodname]`withFeatureFlags(Feature...)` / [methodname]`withFeatureFlag(String, boolean)` / [methodname]`withFeatureFlag(Feature, boolean)`#
| Enables or disables feature flags for the environment the context creates.

| [since:com.vaadin:vaadin@V25.3]#[methodname]`withConfiguration(BrowserlessConfiguration)`#
| Applies a configuration built elsewhere as the baseline that the other methods add to -- the one resolved for the current test, for example.
|===

The property, feature flag, and configuration methods are the programmatic form of [annotationname]`@BrowserlessTestConfig`; see <<test-configuration#configuring-without-annotations, Configuring Without Annotations>>. [classname]`SecuredBrowserlessApplicationContext.Builder` exposes the same methods.

For one-off tweaks without holding on to a builder reference, [methodname]`BrowserlessApplicationContext.create(UnaryOperator<Builder>)` and the corresponding [methodname]`createSecured(Function<Builder, SecuredBuilder<C>>)` accept a configurer:

[source,java]
Expand Down
121 changes: 121 additions & 0 deletions articles/flow/testing/browserless/test-configuration.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
---
title: Test Configuration
page-title: How to configure the Vaadin environment of a browserless test
description: Set Vaadin application properties, feature flags, and Lookup services for a single test class or test method.
meta-description: Configure Vaadin application properties, feature flags, and Lookup services for a single browserless test with the BrowserlessTestConfig annotation.
order: 46
---


= [since:com.vaadin:vaadin@V25.3]#Test Configuration#

Some tests need a Vaadin environment configured differently from the rest of the suite: a view behind a feature flag, or a setting such as `devmode.sessionSerialization.enabled`. Annotate the test class, or a single test method, with [annotationname]`@BrowserlessTestConfig`:

[source,java]
----
@ViewPackages(classes = CartView.class)
@BrowserlessTestConfig(
applicationProperties = "devmode.sessionSerialization.enabled=true",
featureFlags = "myExperimentalFeature")
class CartViewTest extends BrowserlessTest {

@Test
void experimentalCheckoutIsShown() {
// The feature flag is enabled for this test
}

@Test
@BrowserlessTestConfig(featureFlags = "myExperimentalFeature=false")
void fallbackCheckoutIsShown() {
// The same class, with the flag off for this method only
}
}
----

Each setting applies to the Vaadin environment created for the annotated test, so there is nothing to reset afterwards, and nothing leaks into the next test.


== Settings

[cols="1,2"]
|===
| Attribute | Description

| [propertyname]`applicationProperties`
| `name=value` pairs applied to the Vaadin deployment configuration, such as `"devmode.sessionSerialization.enabled=true"`. The value is everything after the first `=`, so a value can itself contain `=`. The properties are set before the servlet starts, so code that runs at startup, such as a [interfacename]`VaadinServiceInitListener`, already sees them.

| [propertyname]`featureFlags`
| Either a feature identifier, to enable the feature, or an `id=true\|false` pair. These flags override the [filename]`vaadin-featureflags.properties` file and the `vaadin.experimental.*` system properties. Toggling a flag this way needs no development mode and writes nothing into the project folder. An unknown identifier fails with an error that lists the available flags.

| [propertyname]`lookupServices`
| Implementation classes registered with the Vaadin [classname]`Lookup`, such as an [interfacename]`InstantiatorFactory` or a [interfacename]`ResourceProvider`.
|===

The `browserless` application property itself stays enforced and cannot be overridden.


== Merging Class and Method Configuration

Every annotation a test inherits contributes to the configuration, rather than being shadowed by the nearest one. The closer a declaration is to the test method, the higher it ranks: the method first, then the test class, then superclasses from the nearest up, and then, for a [annotationname]`@Nested` test, enclosing classes from the innermost out.

[source,java]
----
@BrowserlessTestConfig(applicationProperties = "base.property=fromBase")
abstract class AbstractViewTest extends BrowserlessTest {
}

@BrowserlessTestConfig(featureFlags = "myExperimentalFeature")
class CartViewTest extends AbstractViewTest {
// Both base.property and myExperimentalFeature apply
}
----

Lookup services are the exception: they accumulate instead of replacing each other, so a test method can add a service, but cannot remove one that its test class declares. The services that the Spring and Quarkus integrations need are always registered, and the test configuration never affects them.

A method-level annotation needs an environment built for each test method. When one environment is shared by the whole class, as with [classname]`BrowserlessClassExtension`, the annotation is rejected with an error naming the methods that carry it. Move it to the test class in that case.


== Configuring Without Annotations

Check warning on line 78 in articles/flow/testing/browserless/test-configuration.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.HeadingCase] 'Configuring Without Annotations' should be in title case. Raw Output: {"message":"[Vaadin.HeadingCase] 'Configuring Without Annotations' should be in title case.","location":{"path":"articles/flow/testing/browserless/test-configuration.adoc","range":{"start":{"line":78,"column":4},"end":{"line":78,"column":35}}},"severity":"WARNING","code":{"value":"Vaadin.HeadingCase","url":"https://vaadin.com/docs/contributing/docs/styleguide#headings"}}

The same settings can be built in code. On a JUnit 6 extension:

[source,java]
----
@RegisterExtension
BrowserlessExtension extension = new BrowserlessExtension()
.withApplicationProperty("devmode.sessionSerialization.enabled", "true")
.withFeatureFlags("myExperimentalFeature");
----

On the application context builder of a multi-user test:

[source,java]
----
try (var app = BrowserlessApplicationContext.create(builder -> builder
.withViewPackages(CartView.class)
.withFeatureFlags("myExperimentalFeature"))) {
// ...
}
----

Or by overriding [methodname]`testConfiguration()` on a test that extends a base class:

[source,java]
----
@Override
protected BrowserlessConfiguration testConfiguration() {
return BrowserlessConfiguration.builder()
.withConfiguration(super.testConfiguration())
.withFeatureFlags("myExperimentalFeature")
.build();
}
----

A configuration built on an extension or on the application context builder wins over the class-level annotation, and loses against the method-level one. A [methodname]`testConfiguration()` override ranks differently: [methodname]`super.testConfiguration()` returns the configuration already resolved from the annotations, so whatever the override adds on top of it wins over all of them, the method-level annotation included. Build on [methodname]`super.testConfiguration()` to refine the declared configuration, and leave out the values that a test method needs to override.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A configuration built on an extension or on the application context builder wins over the class-level annotation, and loses against the method-level one

This is true only for the JUnit extension. BrowserlessApplicationContext only uses the configuration built with its own builder; it does take into account any annotation.
However, the annotation configuration can be provided explicitly by using BrowserlessConfiguration.from(...) factory method.

BrowserlessApplicationContext.create(b -> b.withViewPackages(MyView.class)
        .withConfiguration(BrowserlessConfiguration.from(getClass())));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good catch, thanks — fixed. The precedence sentence now scopes the "wins over the class-level annotation" rule to the JUnit extensions, and the section says explicitly that a BrowserlessApplicationContext never looks at @BrowserlessTestConfig, with your BrowserlessConfiguration.from(getClass()) snippet as the way to opt in. Also noted that from(...) resolves a single annotation rather than merging the hierarchy the way an extension does. The builder table on the multi-user page got the same correction.


.Spring Properties Win
[NOTE]
With Spring, a Vaadin property defined in the Spring environment, such as `vaadin.devmode.sessionSerialization.enabled` in [filename]`application.properties`, is applied by [classname]`SpringServlet` on top of the test configuration, and therefore wins over [annotationname]`@BrowserlessTestConfig`. Use [annotationname]`@TestPropertySource` to override such a property for a test. Properties that are not Vaadin init parameters are unaffected.


[discussion-id]`CD70CAA5-6506-4810-BED5-0F54DCE2F0FA`
4 changes: 4 additions & 0 deletions articles/upgrading/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,10 @@ Because of the change, the [classname]`com.vaadin.flow.component.html.testbench.

The [methodname]`getPropertyString`, [methodname]`getPropertyBoolean`, [methodname]`getPropertyDouble` and [methodname]`getPropertyInteger` methods of the [classname]`TestBenchElement` class have been changed to not convert property values to the respective result types anymore. For example, calling [methodname]`getPropertyString` on a property that contains a number value will now throw an exception instead of returning the string representation of the number.

=== Browserless Testing: Lookup Services of the Spring and Quarkus Integrations

The services that the Spring and Quarkus integrations need have moved from [methodname]`lookupServices()` to [methodname]`frameworkLookupServices()`, and are registered in every case. An override of [methodname]`lookupServices()` therefore adds to them instead of replacing them, which is what keeps those integrations working, and the resulting [classname]`Lookup` holds more services than before. [methodname]`lookupServices()` itself is deprecated in favor of the test configuration, which declares the same services on a test class or a test method. See <<{articles}/flow/testing/browserless/test-configuration#,Test Configuration>>.

== Binder
[methodname]`Binder.validate()` implementation has been changed to behave as its Javadoc states. In other words, [methodname]`Binder.validate()` no longer fails when bean level validators have been configured but no bean is currently set (i.e. [classname]`Binder` is used in buffered mode).

Expand Down
Loading