testbench-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseEnd-to-End Browser Testing with Vaadin TestBench
使用Vaadin TestBench进行端到端浏览器测试
Use the Vaadin MCP tools () to look up the latest documentation whenever uncertain about a specific API detail. Always set to and to .
search_vaadin_docsvaadin_version"25"ui_language"java"Note: TestBench requires a commercial Vaadin subscription.
当不确定特定API细节时,请使用Vaadin MCP工具()查阅最新文档。请始终将设置为,设置为。
search_vaadin_docsvaadin_version"25"ui_language"java"注意: TestBench需要Vaadin商业订阅。
What TestBench Is
TestBench是什么
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.
TestBench会在真实浏览器(Chrome、Firefox等)中运行你的Vaadin应用,并允许你编写Java测试,模拟用户操作与应用交互。它基于Selenium构建,但提供了专为Vaadin组件设计的高级API。
When to Use TestBench (vs. UI Unit Tests)
何时使用TestBench(对比UI单元测试)
Use end-to-end TestBench tests for:
- Critical user paths — login, checkout, payment flows
- Client-side behavior — JavaScript functionality, custom web components
- Visual regression — screenshot comparison to catch unintended UI changes
- Cross-browser testing — verify behavior across Chrome, Firefox, Safari
- Integration with external systems — SSO, OAuth flows
For everything else, prefer UI unit tests (see the skill) — they're faster and less flaky.
ui-unit-testing以下场景适合使用TestBench端到端测试:
- 关键用户路径 — 登录、结账、支付流程
- 客户端行为 — JavaScript功能、自定义Web组件
- 可视化回归 — 通过截图对比捕获意外的UI变更
- 跨浏览器测试 — 在Chrome、Firefox、Safari等浏览器中验证行为一致性
- 与外部系统集成 — SSO、OAuth流程
其他场景优先选择UI单元测试(参考技能)——这类测试速度更快且更稳定。
ui-unit-testingCore Concepts
核心概念
Elements
Elements(元素)
An class represents a DOM element — either a built-in HTML element (, ) or a Vaadin web component (, ). Elements provide component-specific methods for interaction.
Element<div><span><vaadin-button><vaadin-grid>Every Vaadin component has a corresponding Element class: , , , , etc.
ButtonElementTextFieldElementGridElementComboBoxElementElement<div><span><vaadin-button><vaadin-grid>每个Vaadin组件都有对应的Element类:、、、等。
ButtonElementTextFieldElementGridElementComboBoxElementElementQuery
ElementQuery(元素查询)
ElementQuery$()java
// 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:
- — find by id (returns single element)
id("id") - — first match
first() - — last match
last() - — nth match
get(n) - — all matches as list
all() - — boolean check
exists() - — waits until a match appears
waitForFirst() - — filter by attribute
attribute("name", "value")
ElementQuery$()java
// 按类型查找
ButtonElement button = $(ButtonElement.class).first();
// 按ID查找
TextFieldElement name = $(TextFieldElement.class).id("name");
// 查找所有按钮
List<ButtonElement> buttons = $(ButtonElement.class).all();
// 等待元素出现
ButtonElement btn = $(ButtonElement.class).waitForFirst();
// 按属性查找
$(DivElement.class).attribute("class", "active").first();
// 嵌套查询——在另一个元素内查找
VerticalLayoutElement layout = $(VerticalLayoutElement.class).id("content");
ButtonElement innerBtn = layout.$(ButtonElement.class).first();ElementQuery的核心方法:
- — 按ID查找(返回单个元素)
id("id") - — 返回第一个匹配元素
first() - — 返回最后一个匹配元素
last() - — 返回第n个匹配元素
get(n) - — 返回所有匹配元素的列表
all() - — 检查元素是否存在(返回布尔值)
exists() - — 等待直到匹配元素出现
waitForFirst() - — 按属性过滤元素
attribute("name", "value")
Writing Tests
编写测试用例
Basic test (JUnit 5)
基础测试(JUnit 5)
java
public 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());
}
}java
public 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();
// 验证是否导航到仪表盘
assertTrue($(DivElement.class).id("dashboard").exists());
}
}JUnit 4 test
JUnit 4测试用例
java
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();
}
}java
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
Page Object(页面对象)
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.
页面对象封装了与特定视图或组件的交互逻辑,让测试方法更简洁且易于维护。当UI发生变更时,只需更新页面对象,无需修改所有测试用例。
Creating a page object
创建页面对象
A page object extends and uses :
TestBenchElement@Element("tag-name")java
@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();
}
}页面对象需继承并使用注解:
TestBenchElement@Element("tag-name")java
@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();
}
}Using the page object in tests
在测试中使用页面对象
java
@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());
}java
@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());
}Matching strategies
匹配策略
Use to match page objects to DOM elements:
@Attributejava
// 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)使用注解将页面对象与DOM元素匹配:
@Attributejava
// 按class属性匹配(contains适用于多值属性)
@Element("div")
@Attribute(name = "class", contains = "my-view")
// 按简单类名自动匹配(移除Element/PageObject后缀)
@Element("div")
@Attribute(name = "class", contains = Attribute.SIMPLE_CLASS_NAME)Working with Complex Components
处理复杂组件
Grid
Grid(表格)
java
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);java
GridElement grid = $(GridElement.class).first();
// 获取行数
int rowCount = grid.getRowCount();
// 获取单元格内容
String name = grid.getCell(0, 0).getText();
// 点击某一行
grid.getRow(0).click();
// 滚动到指定行
grid.scrollToRow(50);ComboBox
ComboBox(下拉框)
java
ComboBoxElement combo = $(ComboBoxElement.class).id("country");
combo.openPopup();
combo.selectByText("Finland");java
ComboBoxElement combo = $(ComboBoxElement.class).id("country");
combo.openPopup();
combo.selectByText("Finland");Dialog
Dialog(对话框)
java
// Dialogs overlay the main content
DialogElement dialog = $(DialogElement.class).waitForFirst();
dialog.$(ButtonElement.class).id("confirm").click();java
// 对话框会覆盖主内容
DialogElement dialog = $(DialogElement.class).waitForFirst();
dialog.$(ButtonElement.class).id("confirm").click();Best Practices
最佳实践
- Use Page Objects for all but the simplest tests — they make tests readable and maintainable. Your test methods should read like a user story.
- Use instead of
waitForFirst()— when elements might not be immediately present (after navigation, async loading).first() - Assign IDs to key components — makes them easy to find in tests. Prefer IDs over positional queries.
component.setId("login-button") - Keep end-to-end tests focused on critical paths — use UI unit tests for comprehensive coverage. End-to-end tests should verify the journey, not every edge case.
- Run tests in CI with headless browsers — Chrome headless is the most reliable option.
- Don't sleep, wait — use or
waitForFirst()instead ofwaitUntil(). Explicit waits are more reliable and faster.Thread.sleep() - Test one user journey per test method — end-to-end tests are expensive. Make each test cover a meaningful scenario, but keep them independent.
- 除最简单的测试外,均使用Page Object — 它们让测试用例更易读且易于维护。测试方法的逻辑应像用户故事一样清晰。
- 使用替代
waitForFirst()— 当元素可能不会立即出现时(如导航后、异步加载时)。first() - 为关键组件分配ID — 能让测试更易定位元素。优先使用ID而非位置查询。
component.setId("login-button") - 端到端测试聚焦关键路径 — 全面测试请使用UI单元测试。端到端测试应验证完整用户流程,而非每个边缘场景。
- 在CI中使用无头浏览器运行测试 — Chrome无头模式是最可靠的选择。
- 不要用sleep,改用等待方法 — 使用或
waitForFirst()替代waitUntil()。显式等待更可靠且效率更高。Thread.sleep() - 每个测试方法验证一个用户流程 — 端到端测试成本较高。每个测试应覆盖一个有意义的场景,且保持测试之间的独立性。