From vaadin-development
Guides writing end-to-end browser tests with Vaadin TestBench for Vaadin 25 apps, covering Element API, ElementQuery, page objects, and TestBenchTestCase for real-browser, cross-browser, and visual regression testing.
npx claudepluginhub marcushellberg/vaadin-development-plugin --plugin vaadin-developmentThis skill uses the workspace's default tool permissions.
Use the Vaadin MCP tools (`search_vaadin_docs`) to look up the latest documentation whenever uncertain about a specific API detail. Always set `vaadin_version` to `"25"` and `ui_language` to `"java"`.
Creates Playwright browser-based integration tests for Vaadin views using Drama Finder library for type-safe, accessibility-first element wrappers. Triggers on requests for Playwright tests, E2E tests, or Vaadin UI testing.
Tests local web applications using Playwright: verifies frontend functionality, debugs UI behavior, captures screenshots, views logs. Mandatory before declaring implementation complete.
Tests web apps end-to-end using openbrowser-ai to simulate user interactions: navigate pages, fill forms, click elements, assert content and state. For QA, user flows, deployments.
Share bugs, ideas, or general feedback.
Use the Vaadin MCP tools (search_vaadin_docs) to look up the latest documentation whenever uncertain about a specific API detail. Always set vaadin_version to "25" and ui_language to "java".
Note: TestBench requires a commercial Vaadin subscription.
TestBench runs your Vaadin application in a real browser (Chrome, Firefox, etc.) and lets you write Java tests that interact with it like a user would. It's built on Selenium but provides a high-level API specifically designed for Vaadin components.
Use end-to-end TestBench tests for:
For everything else, prefer UI unit tests (see the ui-unit-testing skill) — they're faster and less flaky.
An Element class represents a DOM element — either a built-in HTML element (<div>, <span>) or a Vaadin web component (<vaadin-button>, <vaadin-grid>). Elements provide component-specific methods for interaction.
Every Vaadin component has a corresponding Element class: ButtonElement, TextFieldElement, GridElement, ComboBoxElement, etc.
ElementQuery finds elements on the page. Use the $() method:
// Find by type
ButtonElement button = $(ButtonElement.class).first();
// Find by ID
TextFieldElement name = $(TextFieldElement.class).id("name");
// Find all buttons
List<ButtonElement> buttons = $(ButtonElement.class).all();
// Wait for element to appear
ButtonElement btn = $(ButtonElement.class).waitForFirst();
// Find by attribute
$(DivElement.class).attribute("class", "active").first();
// Nested query — find inside another element
VerticalLayoutElement layout = $(VerticalLayoutElement.class).id("content");
ButtonElement innerBtn = layout.$(ButtonElement.class).first();
Key methods on ElementQuery:
id("id") — find by id (returns single element)first() — first matchlast() — last matchget(n) — nth matchall() — all matches as listexists() — boolean checkwaitForFirst() — waits until a match appearsattribute("name", "value") — filter by attributepublic class LoginTest extends BrowserTestBase {
@BrowserTest
public void loginWithValidCredentials() {
$(TextFieldElement.class).id("username").setValue("admin");
$(PasswordFieldElement.class).id("password").setValue("secret");
$(ButtonElement.class).id("login").click();
// Verify navigation to dashboard
assertTrue($(DivElement.class).id("dashboard").exists());
}
}
public class LoginTest extends TestBenchTestCase {
@Before
public void setup() throws Exception {
setDriver(new ChromeDriver());
getDriver().get("http://localhost:8080");
}
@Test
public void loginWithValidCredentials() {
$(TextFieldElement.class).id("username").setValue("admin");
$(PasswordFieldElement.class).id("password").setValue("secret");
$(ButtonElement.class).id("login").click();
assertTrue($(DivElement.class).id("dashboard").exists());
}
@After
public void teardown() {
getDriver().quit();
}
}
Page objects encapsulate interaction with a specific view or component, keeping test methods clean and maintainable. If the UI changes, only the page object needs updating — not every test.
A page object extends TestBenchElement and uses @Element("tag-name"):
@Element("div")
@Attribute(name = "class", contains = "login-view")
public class LoginViewElement extends TestBenchElement {
public void login(String username, String password) {
$(TextFieldElement.class).id("username").setValue(username);
$(PasswordFieldElement.class).id("password").setValue(password);
$(ButtonElement.class).id("login").click();
}
public boolean isLoginFailed() {
return $(DivElement.class).attribute("class", "error").exists();
}
}
@BrowserTest
public void loginSuccess() {
LoginViewElement loginView = $(LoginViewElement.class).waitForFirst();
loginView.login("admin", "secret");
assertTrue($(DashboardViewElement.class).exists());
}
@BrowserTest
public void loginFailure() {
LoginViewElement loginView = $(LoginViewElement.class).waitForFirst();
loginView.login("admin", "wrong");
assertTrue(loginView.isLoginFailed());
}
Use @Attribute to match page objects to DOM elements:
// Match by class attribute (contains for multi-value attributes)
@Element("div")
@Attribute(name = "class", contains = "my-view")
// Auto-match by simple class name (removes Element/PageObject suffix)
@Element("div")
@Attribute(name = "class", contains = Attribute.SIMPLE_CLASS_NAME)
GridElement grid = $(GridElement.class).first();
// Get row count
int rowCount = grid.getRowCount();
// Get cell content
String name = grid.getCell(0, 0).getText();
// Click a row
grid.getRow(0).click();
// Scroll to a row
grid.scrollToRow(50);
ComboBoxElement combo = $(ComboBoxElement.class).id("country");
combo.openPopup();
combo.selectByText("Finland");
// Dialogs overlay the main content
DialogElement dialog = $(DialogElement.class).waitForFirst();
dialog.$(ButtonElement.class).id("confirm").click();
waitForFirst() instead of first() — when elements might not be immediately present (after navigation, async loading).component.setId("login-button") makes them easy to find in tests. Prefer IDs over positional queries.waitForFirst() or waitUntil() instead of Thread.sleep(). Explicit waits are more reliable and faster.