Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,28 @@

## Project Structure & Module Organization

- Root `pom.xml` — Core Playwright helpers for Vaadin (`src/main/java/...`),
resources under `src/main/resources`.
- Single-module Maven project. Root `pom.xml` builds the library — core
Playwright helpers for Vaadin live in
`src/main/java/org/vaadin/addons/dramafinder/`.
- The demo views used to exercise the library are test-only, under
`src/test/java/.../tests/testuis/`; there is no standalone runnable demo app.
Views are served by the Spring Boot IT harness (`@SpringBootTest`) when the
`*IT.java` tests run.

## Build, Test, and Development Commands

- 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`
- Build + unit tests: `./mvnw clean install`
- Run unit tests only: `./mvnw test`
- Run integration tests (Failsafe, `**/*IT.java`, requires the `it` profile):
`./mvnw -Pit verify`
- Run a single IT: `./mvnw -Dit.test=MyViewIT -Pit verify`
- Debug an IT with a visible browser: `./mvnw -Pdebug-ui -Dit.test=MyViewIT verify`
(or `-Dheadless=false`)

## Coding Style & Naming Conventions

- Java 21; 4-space indent; organize imports; no trailing whitespace.
- Packages: lowercase (`org.vaadin.dramafinder`); classes: `PascalCase`;
- Packages: lowercase (`org.vaadin.addons.dramafinder`); classes: `PascalCase`;
methods/fields: `camelCase`; constants: `UPPER_SNAKE_CASE`.
- Public API in `dramafinder` should be small, cohesive, and documented with
Javadoc.
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ The demo is only here to run the test

### Javadoc

Public APIs in the `dramafinder` module are documented with concise Javadoc:
Public APIs in the `dramafinder` library are documented with concise Javadoc:

- Element classes include a short summary referencing the underlying Vaadin
tag (e.g., `vaadin-text-field`) and any noteworthy behaviors.
Expand All @@ -198,8 +198,7 @@ mvn test
## Integration tests

The integration tests are built using Spring Boot, Playwright, and Axe-core.
The tests are located in files ending with `IT.java` in the
`sortable-layout-demo` module.
The tests are located in files ending with `IT.java` under `src/test/java`.
The tests are run with the `maven-failsafe-plugin` when the `it` profile is
activated.

Expand Down
2 changes: 1 addition & 1 deletion skills/vaadin-playwright-test/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Before writing any raw locator, confirm there is genuinely no wrapper: check
`*Element.java` files (custom extensions not in the table). Only if neither
covers the component may you use a plain Playwright locator. For recurring
needs, create your own element class extending `VaadinElement`,
or [open an issue](https://github.com/vaadin/dramafinder/issues) in the
or [open an issue](https://github.com/parttio/dramafinder/issues) in the
DramaFinder repository to request one.

### Do NOT drop to raw locators for wrapped components
Expand Down
49 changes: 49 additions & 0 deletions skills/vaadin-playwright-test/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,55 @@ Complete public API of every DramaFinder element wrapper. Each element lists the

**Do not download or unzip the DramaFinder jar to discover its API — it is all here.**

## Element index

Wrapped web-component tag, its wrapper class, and the wrapper's actual static factory methods. **Factory methods are not uniform** — an element only has the factories listed here (e.g. most fields use `getByLabel`, containers use `get`; `getById` exists on only a few). Do not assume a factory that isn't listed. Every wrapper is also constructible via `new <Name>(Locator)`.

| Web-component tag | Element wrapper | Static factory methods |
|---|---|---|
| `<vaadin-accordion>` | [AccordionElement](#accordionelement) | *constructor only* |
| `<vaadin-accordion-panel>` | [AccordionPanelElement](#accordionpanelelement) | `getAccordionPanelBySummary(Locator, String)`, `getOpenedAccordionPanel(Locator)` |
| `<vaadin-avatar>` | [AvatarElement](#avatarelement) | `get(Locator)`, `get(Page)`, `getByName(Locator, String)`, `getByName(Page, String)` |
| `<vaadin-big-decimal-field>` | [BigDecimalFieldElement](#bigdecimalfieldelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` |
| `<vaadin-button>` | [ButtonElement](#buttonelement) | `getByLabel(Page, String)`, `getByText(Locator, Locator.GetByRoleOptions)`, `getByText(Locator, String)`, `getByText(Page, Page.GetByRoleOptions)`, `getByText(Page, String)` |
| `<vaadin-card>` | [CardElement](#cardelement) | `getByTitle(Locator, String)`, `getByTitle(Page, String)` |
| `<vaadin-checkbox>` | [CheckboxElement](#checkboxelement) | `getByLabel(Page, String)` |
| `<vaadin-combo-box>` | [ComboBoxElement](#comboboxelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` |
| `<vaadin-date-picker>` | [DatePickerElement](#datepickerelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` |
| `<vaadin-date-time-picker>` | [DateTimePickerElement](#datetimepickerelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` |
| `<vaadin-details>` | [DetailsElement](#detailselement) | `getBySummaryText(Page, String)` |
| `<vaadin-email-field>` | [EmailFieldElement](#emailfieldelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` |
| `<vaadin-grid>` | [GridElement](#gridelement) | `get(Locator)`, `get(Page)`, `getById(Page, String)` |
| `<vaadin-integer-field>` | [IntegerFieldElement](#integerfieldelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` |
| `<vaadin-list-box>` | [ListBoxElement](#listboxelement) | `getByLabel(Page, String)` |
| `<vaadin-message-input>` | [MessageInputElement](#messageinputelement) | `get(Locator)`, `get(Page)` |
| `<vaadin-message-list>` | [MessageListElement](#messagelistelement) | `get(Locator)`, `get(Page)` |
| `<vaadin-multi-select-combo-box>` | [MultiSelectComboBoxElement](#multiselectcomboboxelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` |
| `<vaadin-number-field>` | [NumberFieldElement](#numberfieldelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` |
| `<vaadin-password-field>` | [PasswordFieldElement](#passwordfieldelement) | `getByLabel(Page, String)` |
| `<vaadin-progress-bar>` | [ProgressBarElement](#progressbarelement) | *constructor only* |
| `<vaadin-radio-group>` | [RadioButtonGroupElement](#radiobuttongroupelement) | `getByLabel(Page, String)` |
| `<vaadin-select>` | [SelectElement](#selectelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` |
| `<vaadin-side-nav>` | [SideNavigationElement](#sidenavigationelement) | `getByLabel(Page, String)` |
| `<vaadin-side-nav-item>` | [SideNavigationItemElement](#sidenavigationitemelement) | *constructor only* |
| `<vaadin-split-layout>` | [SplitLayoutElement](#splitlayoutelement) | `get(Page)` |
| `<vaadin-tab>` | [TabElement](#tabelement) | `getSelectedTab(Locator)`, `getTabByText(Locator, String)` |
| `<vaadin-tabsheet>` | [TabSheetElement](#tabsheetelement) | `get(Page)` |
| `<vaadin-text-area>` | [TextAreaElement](#textareaelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` |
| `<vaadin-text-field>` | [TextFieldElement](#textfieldelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` |
| `<vaadin-time-picker>` | [TimePickerElement](#timepickerelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` |
| `<vaadin-upload>` | [UploadElement](#uploadelement) | `getByButtonText(Page, String)` |
| `<vaadin-virtual-list>` | [VirtualListElement](#virtuallistelement) | `get(Page)` |
| — | [AbstractNumberFieldElement](#abstractnumberfieldelement) | *constructor only* |
| — | [ContextMenuElement](#contextmenuelement) | *constructor only* |
| — | [DialogElement](#dialogelement) | `getByHeaderText(Page, String)` |
| — | [MenuBarElement](#menubarelement) | `getByLabel(Page, String)` |
| — | [MenuElement](#menuelement) | `getByLabel(Page, String)` |
| — | [MenuItemElement](#menuitemelement) | `getByLabel(Locator, String)` |
| — | [NotificationElement](#notificationelement) | `getByText(Page, String)` |
| — | [PopoverElement](#popoverelement) | `getByLabel(Page, String)` |
| — | [TreeGridElement](#treegridelement) | `get(Locator)`, `get(Page)`, `getById(Page, String)` |

## Elements

[AbstractNumberFieldElement](#abstractnumberfieldelement) · [AccordionElement](#accordionelement) · [AccordionPanelElement](#accordionpanelelement) · [AvatarElement](#avatarelement) · [BigDecimalFieldElement](#bigdecimalfieldelement) · [ButtonElement](#buttonelement) · [CardElement](#cardelement) · [CheckboxElement](#checkboxelement) · [ComboBoxElement](#comboboxelement) · [ContextMenuElement](#contextmenuelement) · [DatePickerElement](#datepickerelement) · [DateTimePickerElement](#datetimepickerelement) · [DetailsElement](#detailselement) · [DialogElement](#dialogelement) · [EmailFieldElement](#emailfieldelement) · [GridElement](#gridelement) · [IntegerFieldElement](#integerfieldelement) · [ListBoxElement](#listboxelement) · [MenuBarElement](#menubarelement) · [MenuElement](#menuelement) · [MenuItemElement](#menuitemelement) · [MessageInputElement](#messageinputelement) · [MessageListElement](#messagelistelement) · [MultiSelectComboBoxElement](#multiselectcomboboxelement) · [NotificationElement](#notificationelement) · [NumberFieldElement](#numberfieldelement) · [PasswordFieldElement](#passwordfieldelement) · [PopoverElement](#popoverelement) · [ProgressBarElement](#progressbarelement) · [RadioButtonGroupElement](#radiobuttongroupelement) · [SelectElement](#selectelement) · [SideNavigationElement](#sidenavigationelement) · [SideNavigationItemElement](#sidenavigationitemelement) · [SplitLayoutElement](#splitlayoutelement) · [TabElement](#tabelement) · [TabSheetElement](#tabsheetelement) · [TextAreaElement](#textareaelement) · [TextFieldElement](#textfieldelement) · [TimePickerElement](#timepickerelement) · [TreeGridElement](#treegridelement) · [UploadElement](#uploadelement) · [VirtualListElement](#virtuallistelement)
Expand Down
32 changes: 24 additions & 8 deletions skills/vaadin-playwright-test/element-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ Use this table to map Vaadin component class names found in view source to the c

Also scan `src/main/java` for any `*Element.java` files not listed here (custom extensions).

> **The `Key Methods` column is illustrative, not authoritative.** Factory
> methods are **not** uniform across elements — the factory name shown here may
> be out of date. For the exact, always-current factory signatures of each
> element, use the auto-generated **Element index** at the top of
> [api-reference.md](api-reference.md) (derived directly from source, verified in
> CI). Never assume a factory that isn't listed there.

| Vaadin Component | DramaFinder Element Class | Key Methods |
|-----------------|--------------------------|-------------|
| `TextField` | `TextFieldElement` | `getByLabel(page, label)`, `setValue()`, `assertValue()`, `assertValid()`, `assertInvalid()`, `assertErrorMessage()`, `assertLabel()`, `assertPlaceholder()`, `assertHelperHasText()`, `assertPrefixHasText()`, `assertSuffixHasText()`, `assertClearButtonVisible()`, `clickClearButton()`, `assertTheme()`, `assertAllowedCharPattern()`, `assertMinLength()`, `assertMaxLength()`, `assertPattern()`, `assertTooltipHasText()`, `assertAriaLabel()`, `assertIsFocused()`, `assertEnabled()`, `assertDisabled()` |
Expand Down Expand Up @@ -47,19 +54,28 @@ Also scan `src/main/java` for any `*Element.java` files not listed here (custom

## Factory method conventions

Every element has these standard factory methods:
Factory methods are **not uniform** across elements — do not assume every
element has `get`, `getByLabel`, and `getById`. The **Element index** in
[api-reference.md](api-reference.md) lists the exact factories each element
provides. The common shapes are:

```java
// From a Page (finds the first matching element on the page)
TextFieldElement field = TextFieldElement.get(page);

// By aria-label or visible label text
// Fields (TextField, ComboBox, DatePicker, ...) — by aria-label or visible label
TextFieldElement field = TextFieldElement.getByLabel(page, "My Label");

// By DOM id
TextFieldElement field = TextFieldElement.getById(page, "my-field-id");
// Containers (Grid, VirtualList, TabSheet, ...) — first matching element on the page
GridElement grid = GridElement.get(page);

// By DOM id — only on a few elements (e.g. GridElement, TreeGridElement)
GridElement grid = GridElement.getById(page, "my-grid-id");

// Some elements have a bespoke factory (check the index), e.g.
ButtonElement save = ButtonElement.getByText(page, "Save");
NotificationElement toast = NotificationElement.getByText(page, "Saved");
DialogElement dialog = DialogElement.getByHeaderText(page, "Confirm");

// From a Locator (useful for elements inside dialogs, grid cells, etc.)
// From a Locator — always available via the constructor
// (useful for elements inside dialogs, grid cells, etc.)
TextFieldElement field = new TextFieldElement(dialog.getLocator().locator("vaadin-text-field"));
```

Expand Down
2 changes: 1 addition & 1 deletion skills/vaadin-playwright-test/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ the steps.

## Constants

- `KNOWN_LATEST = 1.1.1` — fallback version if Maven Central lookup fails. Bump
- `KNOWN_LATEST = 1.1.2` — fallback version if Maven Central lookup fails. Bump
when the library releases.

## Step 1 — Resolve the latest version
Expand Down
57 changes: 57 additions & 0 deletions tools/generate-api-reference.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ public static void main(String[] args) throws IOException {
md.append("Method one-liners come from Javadoc.\n\n");
md.append("**Do not download or unzip the DramaFinder jar to discover its API — it is all here.**\n\n");

// Component tag → wrapper → factories index. Machine-derived from the
// @PlaywrightElement annotation and each wrapper's static factory
// methods, so it can never drift: factory methods are NOT uniform
// across elements, and this table is the authoritative list.
renderIndex(md, elements);

// Table of contents for the elements.
md.append("## Elements\n\n");
md.append(elements.keySet().stream()
Expand Down Expand Up @@ -108,6 +114,57 @@ public static void main(String[] args) throws IOException {
+ mixins.size() + " mixins)");
}

/** Render the tag → wrapper → factories index table. */
static void renderIndex(StringBuilder md, Map<String, TypeDeclaration<?>> elements) {
md.append("## Element index\n\n");
md.append("Wrapped web-component tag, its wrapper class, and the wrapper's actual ");
md.append("static factory methods. **Factory methods are not uniform** — an element ");
md.append("only has the factories listed here (e.g. most fields use `getByLabel`, ");
md.append("containers use `get`; `getById` exists on only a few). Do not assume a ");
md.append("factory that isn't listed. Every wrapper is also constructible via ");
md.append("`new <Name>(Locator)`.\n\n");
md.append("| Web-component tag | Element wrapper | Static factory methods |\n");
md.append("|---|---|---|\n");
// Sort by tag (untagged composites last), then by name.
elements.entrySet().stream()
.sorted(Comparator
.<Map.Entry<String, TypeDeclaration<?>>, String>comparing(
e -> { String t = tagOf(e.getValue()); return t == null ? "￿" : t; })
.thenComparing(Map.Entry::getKey))
.forEach(e -> {
TypeDeclaration<?> t = e.getValue();
String name = e.getKey();
String tag = tagOf(t);
List<String> factories = factorySigs(t);
md.append("| ").append(tag != null ? "`<" + tag + ">`" : "—")
.append(" | [").append(name).append("](#").append(anchor(name)).append(")")
.append(" | ")
.append(factories.isEmpty()
? "*constructor only*"
: factories.stream().map(s -> "`" + s + "`").collect(Collectors.joining(", ")))
.append(" |\n");
});
md.append("\n");
}

/** Public static methods that return the element's own type — i.e. its factory methods. */
static List<String> factorySigs(TypeDeclaration<?> t) {
String name = t.getNameAsString();
return t.getMethods().stream()
.filter(m -> m.isPublic() && m.isStatic())
.filter(m -> m.getType().asString().equals(name))
.map(m -> m.getNameAsString() + "(" + paramTypes(m.getParameters()) + ")")
.distinct()
.sorted()
.collect(Collectors.toList());
}

static String paramTypes(List<Parameter> ps) {
return ps.stream()
.map(p -> p.getType().asString() + (p.isVarArgs() ? "..." : ""))
.collect(Collectors.joining(", "));
}

/** Render one type: heading, tag, javadoc, hierarchy, constants, constructors, methods, nested types. */
static void renderType(StringBuilder md, TypeDeclaration<?> t, int level, boolean isElement) {
String name = t.getNameAsString();
Expand Down
Loading