AI Unified Process plugin for the Vaadin/jOOQ stack
97
93%
Does it follow best practices?
Impact
98%
1.30xAverage score across 10 eval scenarios
Passed
No known issues
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 to configure this optional server.
@UseCase AnnotationBrowserless 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).
Test classes must be named after the use case using the pattern
UC<id><PascalCaseUseCaseName>Test — for example UC001RegisterPersonTest for use case UC-001
"Register Person". This makes the link between spec and test obvious and is the convention the AIUP
IntelliJ Navigator plugin relies on.
@UseCase annotationEvery test method must be annotated with @UseCase(id = "UC-XXX", ...) so the
AIUP IntelliJ Navigator 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/<group>/<artifact>/usecase/UseCase.java. The annotation
must have exactly this shape:
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 {};
}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 | {} |
@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() { ... }<dependency>
<groupId>com.vaadin</groupId>
<artifactId>browserless-test-junit6</artifactId>
<scope>test</scope>
</dependency>@Transactional annotation (transaction boundaries must stay intact)LocatorJ, _get, _find, _click, GridKt, NotificationsKt — those are the legacy Karibu API. Use the Browserless $() query and test() wrapper insteadtest(...) — use the component's Java API directly (e.g. textField.getValue(), button.isEnabled())$() for overlay components (Context Menu, Menu Bar) — use the dedicated tester's clickItem() / find() methodsCreate 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 |
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.
@SpringBootTest
class PersonViewTest extends SpringBrowserlessTest {
// ...
}For non-Spring projects, extend com.vaadin.testbench.unit.BrowserlessTest instead.
Use references/UC001ManagePersonsTest.java as the test
class structure. It demonstrates the UC<id><Name>Test 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.
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();$() Query// Single result
TextField name = $(TextField.class).single();
Button save = $(Button.class).withText("Save").single();
TextField nameField = $(TextField.class).withCaption("Name").single();
ComboBox<Country> 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<Button> buttons = $(Button.class).all();
// Existence check (no exception)
if ($(Notification.class).exists()) { /* ... */ }| Filter | Purpose |
|---|---|
withText(String) | Exact text match |
withTextContaining(String) | Substring text match |
withCaption(String) | Exact caption (label) match |
withCaptionContaining(String) | Substring caption match |
withId(String) | Component ID |
withClassName(String...) | Has all given CSS class names |
withAttribute(String[, String]) | Has attribute (optionally with value) |
withValue(V) | For HasValue components |
withPropertyValue(getter, value) | Custom getter match |
withCondition(Predicate) | Custom predicate |
| Operator | Purpose |
|---|---|
single() | Expect exactly one match |
last() | Last match |
atIndex(int) | Match at position |
all() | Return List |
id(String) | Match by ID |
exists() | Boolean check, no exception |
withResultsSize(int) / withResultsSize(min, max) | Assert count |
test(...) WrapperUse test(component) for actions (click, setValue, selectItem). Read state from the component's Java API.
// Form interactions
test($(TextField.class).withCaption("Name").single()).setValue("John");
test($(ComboBox.class).withCaption("Country").single()).selectItem("Switzerland");
test($(DatePicker.class).withCaption("Birth Date").single())
.setValue(LocalDate.of(1990, 1, 1));
test($(Checkbox.class).withCaption("Active").single()).click();
// Buttons
test($(Button.class).withText("Save").single()).click();
// Reading state — use the component API, not the tester
String value = $(TextField.class).withCaption("Name").single().getValue();| Component | Key Tester Methods |
|---|---|
| TextField | setValue(String), clear() |
| NumberField | setValue(double) |
| Checkbox | click() (toggles) |
| Button | click(), rightClick(), middleClick() |
| Select | selectItem(String), selectItem(int) |
| ComboBox | selectItem(String), getSuggestionItems() |
| DatePicker | setValue(LocalDate) |
| Grid | getRow(int), size(), getCellText(row, column) |
| Notification | getText() |
| Dialog | open(), close() |
| ConfirmDialog | open(), confirm(), cancel(), reject() |
| Upload | upload(File), uploadAll(File...) |
| ContextMenu | clickItem(String...), clickItem(int...), isItemChecked() |
| MenuBar | clickItem(String...) |
Grid<PersonRecord> grid = $(Grid.class).single();
// Size
assertThat(test(grid).size()).isEqualTo(100);
// Selected items (via Java API)
Set<PersonRecord> selected = grid.getSelectedItems();
// Cell value as text
String name = test(grid).getCellText(0, 1);
// Underlying row data
PersonRecord row = test(grid).getRow(0);
// Component column action — get the renderer's component and click it
test(grid).getRow(0); // ensure row is materialized
$(Button.class, grid).withCondition(b -> /* ... */).first().click();// Notification is open?
assertThat($(Notification.class).exists()).isTrue();
// Read its text
String message = test($(Notification.class).single()).getText();
assertThat(message).isEqualTo("Record saved successfully");
// No notification
assertThat($(Notification.class).exists()).isFalse();ConfirmDialog dialog = $(ConfirmDialog.class).single();
test(dialog).confirm(); // click confirm
test(dialog).cancel(); // click cancel
test(dialog).reject(); // click reject (3-button dialogs)fireShortcut(Key.ENTER);
fireShortcut(Key.KEY_S, KeyModifier.CONTROL);For components without a stable label/text, set a test ID on the server side and look up by ID:
// Server-side
submitButton.setTestId("submit-button");
// In the test
Button submit = $(Button.class).id("submit-button");Use AssertJ for assertions; read state from component APIs, not from test(...).
| Assertion Type | Example |
|---|---|
| Grid size | assertThat(test(grid).size()).isEqualTo(10) |
| Component visible | assertThat(button.isVisible()).isTrue() |
| Component enabled | assertThat(button.isEnabled()).isTrue() |
| Field value | assertThat(textField.getValue()).isEqualTo("x") |
| Field invalid | assertThat(textField.isInvalid()).isTrue() |
| Collection size | assertThat(items).hasSize(5) |
| Notification text | assertThat(test(notif).getText()).isEqualTo("Saved") |
| Component open | assertThat($(Dialog.class).exists()).isTrue() |
docs/use-cases/UC-XXX-*.md) to identify the main success
scenario, alternative flows (A1, A2, …), and referenced business rules (BR-XXX)UseCase annotation type already exists in the project. If not, create
UseCase.java with the canonical shape shown aboveUC<id><PascalCaseUseCaseName>Test, extending
SpringBrowserlessTest and annotated @SpringBootTest@UseCase(id = "UC-XXX", scenario = "…", businessRules = {"BR-…"})
mirroring the spec headingsnavigate(...)$() / $view()test(component)$()...exists() to verify the component is in the treegetCurrentView() to confirm navigation succeededContextMenuTester, MenuBarTester) — $() won't see them@UseCase annotation contract): https://github.com/AI-Unified-Process/intellij-pluginhttps://mcp.vaadin.com/docs)