From 1dcacc56b03163f9988344f3aede6c84b1df3dac Mon Sep 17 00:00:00 2001 From: jcgueriaud1 Date: Mon, 6 Jul 2026 12:15:00 +0300 Subject: [PATCH] Fix agent-doc inconsistencies and CI-verify the factory index The hand-written agent docs contradicted the actual API, causing agents to emit non-compiling code or run non-existent commands. Fix the source of the drift and the individual mistakes: - generate-api-reference.java: emit an Element index (tag -> wrapper -> factory methods) derived from the @PlaywrightElement annotation and each wrapper's real static factories. Factory methods are NOT uniform, and this table is now the authoritative, CI-verified list (api-reference.yml already fails stale PRs), preventing element-mapping.md drift. - element-mapping.md: drop the false "every element has get/getByLabel/getById" claim; mark the Key Methods column illustrative and point to the index. - AGENTS.md: fix wrong package (org.vaadin.dramafinder -> org.vaadin.addons.dramafinder) and replace bogus multi-module/spring-boot:run commands with the real single-module -Pit verify flow. - README.md: fix stale sortable-layout-demo module reference. - SKILL.md: correct issue URL vaadin -> parttio. - setup.md: bump KNOWN_LATEST fallback 1.1.1 -> 1.1.2. Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 25 ++++---- README.md | 5 +- skills/vaadin-playwright-test/SKILL.md | 2 +- .../vaadin-playwright-test/api-reference.md | 49 ++++++++++++++++ .../vaadin-playwright-test/element-mapping.md | 32 ++++++++--- skills/vaadin-playwright-test/setup.md | 2 +- tools/generate-api-reference.java | 57 +++++++++++++++++++ 7 files changed, 149 insertions(+), 23 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 593b4cf..de55816 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/README.md b/README.md index 2b44fcc..8ed82d2 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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. diff --git a/skills/vaadin-playwright-test/SKILL.md b/skills/vaadin-playwright-test/SKILL.md index 5593080..a2bbb31 100644 --- a/skills/vaadin-playwright-test/SKILL.md +++ b/skills/vaadin-playwright-test/SKILL.md @@ -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 diff --git a/skills/vaadin-playwright-test/api-reference.md b/skills/vaadin-playwright-test/api-reference.md index e4ff833..c6709a8 100644 --- a/skills/vaadin-playwright-test/api-reference.md +++ b/skills/vaadin-playwright-test/api-reference.md @@ -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 (Locator)`. + +| Web-component tag | Element wrapper | Static factory methods | +|---|---|---| +| `` | [AccordionElement](#accordionelement) | *constructor only* | +| `` | [AccordionPanelElement](#accordionpanelelement) | `getAccordionPanelBySummary(Locator, String)`, `getOpenedAccordionPanel(Locator)` | +| `` | [AvatarElement](#avatarelement) | `get(Locator)`, `get(Page)`, `getByName(Locator, String)`, `getByName(Page, String)` | +| `` | [BigDecimalFieldElement](#bigdecimalfieldelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` | +| `` | [ButtonElement](#buttonelement) | `getByLabel(Page, String)`, `getByText(Locator, Locator.GetByRoleOptions)`, `getByText(Locator, String)`, `getByText(Page, Page.GetByRoleOptions)`, `getByText(Page, String)` | +| `` | [CardElement](#cardelement) | `getByTitle(Locator, String)`, `getByTitle(Page, String)` | +| `` | [CheckboxElement](#checkboxelement) | `getByLabel(Page, String)` | +| `` | [ComboBoxElement](#comboboxelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` | +| `` | [DatePickerElement](#datepickerelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` | +| `` | [DateTimePickerElement](#datetimepickerelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` | +| `` | [DetailsElement](#detailselement) | `getBySummaryText(Page, String)` | +| `` | [EmailFieldElement](#emailfieldelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` | +| `` | [GridElement](#gridelement) | `get(Locator)`, `get(Page)`, `getById(Page, String)` | +| `` | [IntegerFieldElement](#integerfieldelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` | +| `` | [ListBoxElement](#listboxelement) | `getByLabel(Page, String)` | +| `` | [MessageInputElement](#messageinputelement) | `get(Locator)`, `get(Page)` | +| `` | [MessageListElement](#messagelistelement) | `get(Locator)`, `get(Page)` | +| `` | [MultiSelectComboBoxElement](#multiselectcomboboxelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` | +| `` | [NumberFieldElement](#numberfieldelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` | +| `` | [PasswordFieldElement](#passwordfieldelement) | `getByLabel(Page, String)` | +| `` | [ProgressBarElement](#progressbarelement) | *constructor only* | +| `` | [RadioButtonGroupElement](#radiobuttongroupelement) | `getByLabel(Page, String)` | +| `` | [SelectElement](#selectelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` | +| `` | [SideNavigationElement](#sidenavigationelement) | `getByLabel(Page, String)` | +| `` | [SideNavigationItemElement](#sidenavigationitemelement) | *constructor only* | +| `` | [SplitLayoutElement](#splitlayoutelement) | `get(Page)` | +| `` | [TabElement](#tabelement) | `getSelectedTab(Locator)`, `getTabByText(Locator, String)` | +| `` | [TabSheetElement](#tabsheetelement) | `get(Page)` | +| `` | [TextAreaElement](#textareaelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` | +| `` | [TextFieldElement](#textfieldelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` | +| `` | [TimePickerElement](#timepickerelement) | `getByLabel(Locator, String)`, `getByLabel(Page, String)` | +| `` | [UploadElement](#uploadelement) | `getByButtonText(Page, String)` | +| `` | [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) diff --git a/skills/vaadin-playwright-test/element-mapping.md b/skills/vaadin-playwright-test/element-mapping.md index 7d403d7..576829e 100644 --- a/skills/vaadin-playwright-test/element-mapping.md +++ b/skills/vaadin-playwright-test/element-mapping.md @@ -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()` | @@ -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")); ``` diff --git a/skills/vaadin-playwright-test/setup.md b/skills/vaadin-playwright-test/setup.md index f63d7ea..333bbe0 100644 --- a/skills/vaadin-playwright-test/setup.md +++ b/skills/vaadin-playwright-test/setup.md @@ -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 diff --git a/tools/generate-api-reference.java b/tools/generate-api-reference.java index 87f17fa..1d82fb6 100644 --- a/tools/generate-api-reference.java +++ b/tools/generate-api-reference.java @@ -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() @@ -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> 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 (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 + .>, 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 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 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 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();