--- name: browserless-test description: > Creates Vaadin Browserless server-side unit tests for Vaadin views covering navigation, component interactions, form validation, grid operations, and notifications. Use when the user asks to "write Browserless tests", "write Vaadin UI unit tests", "unit test a Vaadin view without a browser", "create view tests with the official Vaadin testing framework", or mentions Browserless testing, SpringBrowserlessTest, browserless-test-junit6, UI Unit Testing, or server-side Vaadin testing. --- # Browserless Test ## Instructions Create Vaadin Browserless unit tests for Vaadin views based on the use case $ARGUMENTS. Browserless Testing executes the UI directly inside the JVM — no browser, no WebDriver, no servlet container. Browserless Testing is the **official, recommended** server-side testing framework for Vaadin. It has been free and open source under Apache 2.0 since **Vaadin 25.1** (previously the commercial UI Unit Testing add-on). It supersedes the community Karibu Testing library — prefer this skill over `/karibu-test` for any new test code. If the Vaadin MCP server (`https://mcp.vaadin.com/docs`) is configured, use it for documentation lookups; otherwise rely on your own knowledge and the documentation links below. See [the MCP setup rule](../../rules/mcp-servers.md) to configure this optional server. ## If Tests for This Use Case Already Exist A diff of the specification change may follow the file path in the arguments. When it is there, it is the definitive list of what changed — work through it change by change. A removed line means the scenario it described was dropped: delete the tests that exist only for it instead of keeping them as passing extras. Before writing new tests, look for an existing test class for this use case — search for `UC*Test` and for methods annotated `@UseCase(id = "UC-XXX")`. If one exists, **update it to match the current specification instead of creating a second test class**: - Add test methods for scenarios and business rules the spec has gained since the tests were written - Update existing test methods whose expected values, labels, component captions, or flows the spec has changed - Delete tests for scenarios the spec no longer contains - Leave passing tests the spec still requires untouched - Update the test data (Flyway test migrations) when the spec's data requirements changed - Run the whole test class afterwards, not only the methods you added ## Test Class Naming and `@UseCase` Annotation Browserless tests are **use case tests**. Each test class verifies the behavior of exactly one use case from the use case specification (`docs/use-cases/UC-XXX-*.md`). ### Class naming Test classes must be named after the use case using the pattern `UCTest` — for example `UC001RegisterPersonTest` for use case UC-001 "Register Person". This makes the link between spec and test obvious and is the convention the AI Unified Process IntelliJ Navigator plugin relies on. ### `@UseCase` annotation Every test method must be annotated with `@UseCase(id = "UC-XXX", ...)` so the [AI Unified Process IntelliJ Navigator plugin](https://github.com/AI-Unified-Process/intellij-plugin) can wire up gutter icons and Find Usages between the Markdown spec and the Java tests. **Bootstrap step.** Before writing any tests, check whether the project already contains an annotation type named `UseCase` (search the project for `@interface UseCase`). If it does not, create it. The package does not matter — the plugin resolves the annotation by short name — but a conventional location is `src/main/java///usecase/UseCase.java`. The annotation must have exactly this shape: ```java package com.example.app.usecase; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface UseCase { String id(); String scenario() default "Main Success Scenario"; String[] businessRules() default {}; } ``` ### Usage on test methods Annotate each test method with the use case ID and (when applicable) the scenario and business rules it covers. The values must match headings in the corresponding `UC-XXX-*.md` spec: | Attribute | Maps to spec heading | Default | |-----------------|--------------------------------------------|--------------------------| | `id` | `**Use Case ID:** UC-XXX` | (required) | | `scenario` | `## Main Success Scenario` or `### A1: …` | `"Main Success Scenario"` | | `businessRules` | `### BR-XXX` headings inside the same UC | `{}` | ```java @Test @UseCase(id = "UC-001") void register_person_with_valid_data() { ... } @Test @UseCase(id = "UC-001", scenario = "A1: Email Already Exists") void registration_fails_when_email_already_exists() { ... } @Test @UseCase(id = "UC-001", scenario = "A2: Invalid Postal Code", businessRules = {"BR-003"}) void registration_fails_when_postal_code_invalid() { ... } ``` ## Maven Dependency ```xml com.vaadin browserless-test-junit6 test ``` ## DO NOT - Use Mockito for mocking - Use `@Transactional` annotation (transaction boundaries must stay intact) - Use services, repositories, or DSLContext to create test data - Delete all data in cleanup (only remove data created during the test) - Use browser-based testing patterns (this is server-side testing) - Use Karibu's `LocatorJ`, `_get`, `_find`, `_click`, `GridKt`, `NotificationsKt` — those are the legacy Karibu API. Use the Browserless `$()` query and `test()` wrapper instead - Read component state through `test(...)` — use the component's Java API directly (e.g. `textField.getValue()`, `button.isEnabled()`) - Use `$()` for overlay components (Context Menu, Menu Bar) — use the dedicated tester's `clickItem()` / `find()` methods ## Test Data Strategy Create test data using Flyway migrations in `src/test/resources/db/migration`. | Approach | Location | Purpose | |------------------|----------------------------------------|--------------------------| | Flyway migration | src/test/resources/db/migration/V*.sql | Populate test data | | Manual cleanup | @AfterEach method | Remove test-created data | ## Base Test Class Extend `com.vaadin.testbench.unit.SpringBrowserlessTest` and annotate the class with `@SpringBootTest`. The base class creates the Vaadin session, UI, and component tree inside the JUnit JVM. ```java @SpringBootTest class PersonViewTest extends SpringBrowserlessTest { // ... } ``` For non-Spring projects, extend `com.vaadin.testbench.unit.BrowserlessTest` instead. ## Template Use [references/UC001ManagePersonsTest.java](references/UC001ManagePersonsTest.java) as the test class structure. It demonstrates the `UCTest` class naming, the `@UseCase` annotation on every test method, and how to map alternative flows (`scenario = "A1: …"`) and business rules (`businessRules = {"BR-…"}`) onto the spec headings. ## Common Patterns ### Navigate to View ```java navigate(PersonView.class); // by class navigate("person", PersonView.class); // by route navigate(PersonDetailView.class, "42"); // with URL parameter navigate(PersonTemplateView.class, Map.of("id", "42")); // with URL template HasElement currentView = getCurrentView(); ``` ### Find Components — `$()` Query ```java // Single result TextField name = $(TextField.class).single(); Button save = $(Button.class).withText("Save").single(); TextField nameField = $(TextField.class).withCaption("Name").single(); ComboBox country = $(ComboBox.class).withId("country").single(); // Scope to current view TextField name = $view(TextField.class).single(); // Scope to a parent component TextField name = $(TextField.class, view.formLayout).single(); // All matching List