- Root
pom.xml— Core Playwright helpers for Vaadin (src/main/java/...), resources undersrc/main/resources.
- Build all modules + unit tests:
./mvnw clean install - Run demo locally:
./mvnw -pl dramafinder-demo spring-boot:run(serves at http://localhost:8080) - Run integration tests (Failsafe, includes
**/*IT.java):./mvnw -B verify --file pom.xml - Run a single test:
- IT:
./mvnw -Dit.test=MyViewIT -Pit verify
- IT:
- Java 21; 4-space indent; organize imports; no trailing whitespace.
- Packages: lowercase (
org.vaadin.dramafinder); classes:PascalCase; methods/fields:camelCase; constants:UPPER_SNAKE_CASE. - Public API in
dramafindershould be small, cohesive, and documented with Javadoc. - Prefer aria role for internal locators
- Add a class-level Javadoc for each element class and shared mixin:
- Identify the Vaadin tag using inline code (e.g.,
vaadin-text-field). - One–two sentence summary of responsibilities and notable behaviors.
- For factory helpers (e.g.,
getByLabel), mention the ARIA role used.
- Identify the Vaadin tag using inline code (e.g.,
- For public methods, document parameters, return values, and null semantics
(especially for assertion helpers where
nullimplies absence). - Use
{@inheritDoc}on simple overrides (e.g., locator accessors) to avoid duplication. - Keep Javadoc concise and consistent; prefer present tense and active voice.
- Frameworks: JUnit 5, Playwright (Java), Axe-core checks in demo.
- Unit tests live with their module; integration tests go in
src/test/java/**/*IT.java. - Tests should be deterministic; avoid timing sleeps—prefer Playwright waits and assertions.
- Commits: short, imperative subject; optional scope prefix (e.g.,
core:,demo:). Example:core: add TestFieldElement to test a Vaadin Textfield. - PRs: describe intent and approach, link issues, list test coverage and manual steps; include screenshots/gifs for UI changes.
- Keep changes module-scoped; update README/AGENTS.md when commands or workflows change.
- Requires Java 21. Vaadin demo uses Node tooling; first runs may download frontend and Playwright browser binaries.
- Do not commit generated/build output:
target/,frontend/generated/,vite.generated.ts. - Prefer configuration via
application.propertiesin each module’ssrc/main/resources.
- Pitfall: Using overly broad locators that match multiple elements.
- Solution: Always use
.first()when expecting a single element, or filter withLocator.FilterOptionsto narrow results.
// Bad: May match multiple elements
page.locator("vaadin-button");
// Good: Specific by accessible name
ButtonElement.getByText(page, "Save");- Pitfall: Using XPath or CSS selectors that don't pierce shadow DOM.
- Solution: Playwright auto-pierces shadow DOM with CSS selectors. Use
xpath=./*prefix only for direct children to avoid piercing.
// Pierces shadow DOM (default)
getLocator().locator("[slot='input']");
// Does NOT pierce shadow DOM (explicit xpath)
getLocator().locator("xpath=./*[not(@slot)][1]");- Pitfall: Calling methods on the wrong locator (component root vs input).
- Solution: Use
getInputLocator()for value/focus operations,getLocator()for component-level attributes.
// Component-level attribute
getLocator().getAttribute("opened");
// Input-level attribute
getInputLocator().getAttribute("value");- Pitfall: Asserting state immediately after an action without waiting.
- Solution: Use Playwright assertions which auto-retry. Avoid raw
getAttribute()checks in assertions.
// Bad: No retry, may flake
assertTrue(getLocator().getAttribute("opened") != null);
// Good: Auto-retries until timeout
assertThat(getLocator()).hasAttribute("opened", "");- Pitfall: Confusing HTML attributes with DOM properties.
- Solution: Use
getAttribute()for HTML attributes,evaluate()orhasJSProperty()for DOM properties.
// HTML attribute
getInputLocator().getAttribute("maxlength");
// DOM property (set via JavaScript)
getLocator().evaluate("(el, v) => el.maxLength = v", max);
assertThat(getLocator()).hasJSProperty("value", expectedValue);- Pitfall: Not handling
nullvalues in assertion helpers. - Solution: Always check for
nulland assert absence of attribute.
public void assertMinLength(Integer min) {
if (min != null) {
assertThat(getInputLocator()).hasAttribute("minLength", min + "");
} else {
// Assert attribute is absent
assertThat(getInputLocator()).not().hasAttribute("minLength", Pattern.compile(".*"));
}
}- Pitfall: Using wrong ARIA role in
getByRole()lookups. - Solution: Check the actual role of the internal element:
- Text inputs:
TEXTBOX - Number inputs:
SPINBUTTON - Date/time pickers:
COMBOBOX - Buttons:
BUTTON - Checkboxes:
CHECKBOX - Radio buttons:
RADIO
- Text inputs:
All element classes provide static factory methods for lookup by accessible name:
public static TextFieldElement getByLabel(Page page, String label) {
return new TextFieldElement(
page.locator(FIELD_TAG_NAME)
.filter(new Locator.FilterOptions()
.setHas(page.getByRole(AriaRole.TEXTBOX,
new Page.GetByRoleOptions().setName(label)))
).first());
}Shared behavior is extracted into interfaces with default implementations:
public interface HasClearButtonElement extends HasLocatorElement {
default void clickClearButton() {
getLocator().locator("[part~='clear-button']").click();
}
}Elements delegate to specific internal locators for different operations:
@Override
public Locator getFocusLocator() {
return getInputLocator(); // Focus goes to input, not wrapper
}
@Override
public Locator getEnabledLocator() {
return getInputLocator(); // Disabled state is on input
}Complex components compose simpler elements internally:
public class DateTimePickerElement extends VaadinElement {
private final DatePickerElement datePickerElement;
private final TimePickerElement timePickerElement;
public DateTimePickerElement(Locator locator) {
super(locator);
datePickerElement = new DatePickerElement(locator.locator(DatePickerElement.FIELD_TAG_NAME));
timePickerElement = new TimePickerElement(locator.locator(TimePickerElement.FIELD_TAG_NAME));
}
}For each state getter, provide matching assert methods:
public boolean isOpen() { ... }
public void assertOpen() { ... }
public void assertClosed() { ... }
public boolean isChecked() { ... }
public void assertChecked() { ... }
public void assertNotChecked() { ... }Factory methods support both page-level and scoped lookups:
// Page-level lookup
public static ButtonElement getByText(Page page, String text) { ... }
// Scoped lookup (within a container)
public static ButtonElement getByText(Locator locator, String text) { ... }- Prefer ARIA roles over tag names for element lookup when possible.
- Use
partselectors for internal component parts (e.g.,[part~='input']). - Chain filters rather than complex XPath for readability.
- Document null semantics in assertion method Javadocs.
- Dispatch events after programmatic value changes if needed:
getLocator().dispatchEvent("change");
- Use constants for tag names and attribute names to avoid typos.
- Inherit behavior by extending base elements (e.g.,
EmailFieldElement extends TextFieldElement).
- Follow this file's conventions for any edits. Keep patches minimal and focused.
- Use
*IT.javaonly for end-to-end tests executed by Failsafe. - Refer to
docs/specifications/for detailed element API documentation.