From vaadin-claude
Guides writing end-to-end browser tests with Vaadin TestBench for Vaadin 25, covering page objects, ElementQuery, and visual regression testing.
npx claudepluginhub vaadin/claude-plugin --plugin vaadin-claudeThis 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"`.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
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.