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
2 changes: 1 addition & 1 deletion skills/vaadin-playwright-test/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "vaadin-playwright-test",
"description": "Generate Playwright integration tests for Vaadin views using the DramaFinder library — element interaction, form validation, grid assertions, and navigation checks.",
"version": "0.2.0",
"version": "0.3.0",
"author": {
"name": "jcgueriaud1"
},
Expand Down
88 changes: 41 additions & 47 deletions skills/vaadin-playwright-test/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: vaadin-playwright-test
description: Generate Playwright integration tests for Vaadin 25 views using the DramaFinder library, including element interaction, form validation, grid assertions, and navigation checks. Use when the user wants to write IT tests for a Vaadin view, mentions DramaFinder, or asks about Playwright testing in a Vaadin project.
description: Generate Playwright integration tests for Vaadin 25 views using the DramaFinder library, including element interaction, form validation, grid assertions, and navigation checks. Use whenever you are about to write, edit, or run an integration/IT test for a Vaadin view — including when the requirement comes from a GitHub issue, PR, spec, or ticket rather than the user's direct words (e.g. "implement #85", "fix the failing test", "add coverage for this view"). Also use when the user mentions DramaFinder, Playwright, `SpringPlaywrightIT`, `@PlaywrightElement`, files under `src/test/**/tests/it/`, or asks about Playwright testing in a Vaadin project. If a task in a Vaadin project involves any Playwright/IT test, load this skill before writing the test.
---

# Vaadin Playwright Test Generator (DramaFinder)
Expand Down Expand Up @@ -69,8 +69,9 @@ If no existing IT tests exist, use the default structure in Step 3.

Read the target view source provided by the user. Extract:

- `@Route("value")` → URL path (default: class name lowercased, stripped of
`View` suffix, e.g. `PersonView` → `/person`).
- `@Route("value")` → URL path (default when no value: class name lowercased,
stripped of `View` suffix, e.g. `PersonView` → `/person`; `MainView` and
`Main` map to `/`).
- `@PageTitle("...")` → expected page title
- Every interactive component → its DramaFinder wrapper (see table below).
- Form fields → label text used as locator.
Expand All @@ -80,7 +81,10 @@ Read the target view source provided by the user. Extract:
See [element-mapping.md](element-mapping.md) for the full component → element
class table, and [api-reference.md](api-reference.md) for the **complete public
API** (every element, its methods, signatures and one-line descriptions) of the
version you have installed.
DramaFinder version bundled with this skill (see the version in its header). If
the project pins an older `<dramafinder.version>`, a method documented there may
not exist yet — if a call fails to compile, check the project's version before
looking for alternatives.

> **Never download or unzip the DramaFinder jar/sources to discover its API.**
> The complete, always-current signature reference is bundled beside this skill
Expand Down Expand Up @@ -120,18 +124,17 @@ leaderboardGrid.assertCellContent(0, "Score", "100");
leaderboardGrid.assertRowCount(10);
```

The same rule applies to every wrapped component: `ComboBoxElement.selectByText()`
not `combo.locator("vaadin-combo-box-item")`; `MenuBarElement.clickItem()` not
a raw `vaadin-menu-bar-button` locator; and so on.
The same rule applies to every wrapped component:
`ComboBoxElement.selectItem()` not `combo.locator("vaadin-combo-box-item")`;
`MenuBarElement.getMenuItemElement("File").click()` not a raw
`vaadin-menu-bar-button` locator; and so on.

## Step 3 — Generate the test class

### Default structure (no existing tests to mirror)

```java
package

<same.package.as.view>; // mirror src/test/java structure
package <same.package.as.view>; // mirror src/test/java structure

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
Expand All @@ -142,21 +145,17 @@ import <basePackage>.it.support.SpringPlaywrightIT; // Spring projects: actual l
// import org.vaadin.addons.dramafinder.AbstractBasePlaywrightIT; // non-Spring projects

import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
// omit if not Spring Boot
public class <ViewName>IT extends

SpringPlaywrightIT { // or AbstractBasePlaywrightIT
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) // omit if not Spring Boot
public class <ViewName>IT extends SpringPlaywrightIT { // or AbstractBasePlaywrightIT

@Override
public String getView () {
public String getView() {
return "/<route-path>";
}

@Test
public void testTitle () {
public void testTitle() {
assertThat(page).hasTitle("<PageTitle value>");
}

Expand All @@ -172,24 +171,14 @@ otherwise.
**Smoke test (one per component):**

```java
@Test
public void test<ComponentLabel>(){
TextFieldElement field = TextFieldElement.getByLabel(page, "My Label");
field.

assertVisible();
field.

assertLabel("My Label");
field.

assertValue("");
field.

setValue("test value");
field.

assertValue("test value");
@Test
public void test<ComponentLabel>() {
TextFieldElement field = TextFieldElement.getByLabel(page, "My Label");
field.assertVisible();
field.assertLabel("My Label");
field.assertValue("");
field.setValue("test value");
field.assertValue("test value");
}
```

Expand Down Expand Up @@ -229,20 +218,25 @@ public void testGridLoadsData() {
}
```

## Step 4 — Show generated test, then confirm before writing

Display the full generated test class in a code block. Then ask:
## Step 4 — Write the test

> Shall I write this to `src/test/java/<package>/<ViewName>IT.java`?
Place the test in `src/test/java` mirroring the view's package under
`src/main/java`.

Only write the file after explicit confirmation. Place it in `src/test/java`
mirroring the view's package under `src/main/java`.
- **Interactive session** (the user asked for a test in conversation): display
the full generated test class in a code block first, then ask:
> Shall I write this to `src/test/java/<package>/<ViewName>IT.java`?

## Step 5 — Offer to run the test
Only write the file after confirmation.
- **Autonomous execution** (implementing an issue/PR/spec, or running
unattended): write the file directly without asking.

After writing, ask:
## Step 5 — Run the test

> Do you want me to run this test now with `mvn verify -Dit.test=<ViewName>IT`?
Run with `mvn verify -Dit.test=<ViewName>IT`.

**Warn the user**: the first Vaadin frontend build takes 3–5 minutes. Subsequent
runs are ~25 seconds.
- **Interactive session**: offer to run it first — the first Vaadin frontend
build takes 3–5 minutes (subsequent runs are ~25 seconds), so let the user
decide.
- **Autonomous execution**: run it directly and fix any failures before
finishing.
2 changes: 1 addition & 1 deletion skills/vaadin-playwright-test/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Best practices for writing Playwright integration tests in this pro

## Guiding Principles

- **Page Object Model (POM):** All tests should use the Page Object Model. Interactions with the UI should be encapsulated in page objects, and tests should only call methods on these page objects. This isolates the tests from changes in the UI. The page objects are the drama finder elements in `src/main/java/org/vaadin/addons/dramafinder/element`.
- **Page Object Model (POM):** All tests should use the Page Object Model. Interactions with the UI should be encapsulated in page objects, and tests should only call methods on these page objects. This isolates the tests from changes in the UI. The page objects are the DramaFinder element wrappers from the library (package `org.vaadin.addons.dramafinder.element`), plus any custom `*Element` classes in the project's own sources.

- **One Test, One Assert:** Each test method should test a single, specific piece of functionality. This makes tests easier to understand and debug.

Expand Down
97 changes: 52 additions & 45 deletions skills/vaadin-playwright-test/element-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,54 @@ 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.
> The `Key Methods` column below is verified against the bundled
> [api-reference.md](api-reference.md) but abbreviated — it lists the most
> common factories and methods, not everything. The auto-generated **Element
> index** at the top of [api-reference.md](api-reference.md) (derived directly
> from source, verified in CI) is the authoritative list of factories, and each
> element's section there is the authoritative list of methods. Never use a
> method that isn't in api-reference.md.

| 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()` |
| `TextArea` | `TextAreaElement` | `getByLabel(page, label)`, `setValue()`, `assertValue()`, `assertValid()`, `assertInvalid()` |
| `EmailField` | `EmailFieldElement` | `getByLabel(page, label)`, `setValue()`, `assertValue()`, `assertValid()`, `assertInvalid()` |
| `PasswordField` | `PasswordFieldElement` | `getByLabel(page, label)`, `setValue()`, `assertValue()` |
| `NumberField` | `NumberFieldElement` | `getByLabel(page, label)`, `setValue()`, `assertValue()` |
| `IntegerField` | `IntegerFieldElement` | `getByLabel(page, label)`, `setValue()`, `assertValue()` |
| `TextField` | `TextFieldElement` | `getByLabel(page, label)`, `setValue()`, `assertValue()`, `assertValid()`, `assertInvalid()`, `assertErrorMessage()`, `assertLabel()`, `assertPlaceholder()`, `assertHelperHasText()`, `assertMinLength()`, `assertMaxLength()`, `assertPattern()`, `clickClearButton()` |
| `TextArea` | `TextAreaElement` | `getByLabel(page, label)`, `setValue()`, `assertValue()` — extends `TextFieldElement` |
| `EmailField` | `EmailFieldElement` | `getByLabel(page, label)`, `setValue()`, `assertValue()` — extends `TextFieldElement` |
| `PasswordField` | `PasswordFieldElement` | `getByLabel(page, label)`, `setValue()`, `assertValue()` — extends `TextFieldElement` |
| `NumberField` | `NumberFieldElement` | `getByLabel(page, label)`, `setValue()`, `assertValue()`, `assertMin()`, `assertMax()`, `assertStep()`, `clickIncreaseButton()`, `clickDecreaseButton()` |
| `IntegerField` | `IntegerFieldElement` | `getByLabel(page, label)`, `setValue()`, `assertValue()`, `assertMin()`, `assertMax()`, `assertStep()` |
| `BigDecimalField` | `BigDecimalFieldElement` | `getByLabel(page, label)`, `setValue()`, `assertValue()` |
| `Button` | `ButtonElement` | `getByText(page, text)`, `get(page)`, `click()`, `assertVisible()`, `assertEnabled()`, `assertDisabled()` |
| `Checkbox` | `CheckboxElement` | `getByLabel(page, label)`, `check()`, `uncheck()`, `assertChecked()`, `assertUnchecked()` |
| `RadioButtonGroup` | `RadioButtonGroupElement` | `getByLabel(page, label)`, `selectByText()`, `assertSelectedValue()` |
| `ComboBox` | `ComboBoxElement` | `getByLabel(page, label)`, `selectByText()`, `assertValue()`, `openDropdown()` |
| `MultiSelectComboBox` | `MultiSelectComboBoxElement` | `getByLabel(page, label)`, `selectByText()`, `assertSelectedValues()` |
| `Select` | `SelectElement` | `getByLabel(page, label)`, `selectByText()`, `assertValue()` |
| `ListBox` | `ListBoxElement` | `get(page)`, `selectByText()`, `assertSelectedValue()` |
| `DatePicker` | `DatePickerElement` | `getByLabel(page, label)`, `setValue()`, `assertValue()`, `assertValid()`, `assertInvalid()` |
| `TimePicker` | `TimePickerElement` | `getByLabel(page, label)`, `setValue()`, `assertValue()` |
| `DateTimePicker` | `DateTimePickerElement` | `getByLabel(page, label)`, `setValue()`, `assertValue()` |
| `Grid` | `GridElement` | `get(page)`, `assertRowCount()`, `assertCellContent(row, col, text)`, `clickRow()`, `sortByColumn()`, `selectRow()`, `assertRowSelected()` |
| `TreeGrid` | `TreeGridElement` | `get(page)`, `expandRow()`, `collapseRow()`, `assertRowCount()`, `assertCellContent()` |
| `VirtualList` | `VirtualListElement` | `get(page)`, `assertItemCount()` |
| `Dialog` | `DialogElement` | `get(page)`, `assertOpen()`, `assertClosed()`, `getLocator()` |
| `Notification` | `NotificationElement` | `get(page)`, `assertVisible()`, `assertText()` |
| `Tabs` / `Tab` | `TabElement` | `getByLabel(page, label)`, `click()`, `assertSelected()` |
| `TabSheet` | `TabSheetElement` | `get(page)`, `selectTabByLabel()`, `assertTabSelected()` |
| `Accordion` | `AccordionElement` | `get(page)`, `openPanel()`, `closePanel()`, `assertPanelOpen()` |
| `AccordionPanel` | `AccordionPanelElement` | `get(page)`, `assertOpen()`, `assertClosed()` |
| `Details` | `DetailsElement` | `get(page)`, `open()`, `close()`, `assertOpen()`, `assertClosed()` |
| `MenuBar` | `MenuBarElement` | `get(page)`, `clickItem()` |
| `ContextMenu` | `ContextMenuElement` | `get(page)`, `open()`, `clickItem()` |
| `SplitLayout` | `SplitLayoutElement` | `get(page)`, `assertOrientation()` |
| `Upload` | `UploadElement` | `get(page)`, `uploadFile()`, `assertFileUploaded()` |
| `ProgressBar` | `ProgressBarElement` | `get(page)`, `assertValue()`, `assertIndeterminate()` |
| `Avatar` | `AvatarElement` | `get(page)`, `assertName()`, `assertAbbreviation()` |
| `MessageInput` | `MessageInputElement` | `get(page)`, `sendMessage()` |
| `MessageList` | `MessageListElement` | `get(page)`, `assertMessageCount()`, `assertMessageText()` |
| `Popover` | `PopoverElement` | `get(page)`, `assertOpen()`, `assertClosed()` |
| `SideNavigation` | `SideNavigationElement` | `get(page)`, `clickItem()`, `assertItemSelected()` |
| `Card` | `CardElement` | `get(page)`, `assertVisible()`, `getText()` |
| `Button` | `ButtonElement` | `getByText(page, text)`, `click()`, `assertVisible()`, `assertEnabled()`, `assertDisabled()` |
| `Checkbox` | `CheckboxElement` | `getByLabel(page, label)`, `check()`, `uncheck()`, `assertChecked()`, `assertNotChecked()`, `assertIndeterminate()` |
| `RadioButtonGroup` | `RadioButtonGroupElement` | `getByLabel(page, label)`, `selectByLabel()`, `selectByValue()`, `assertValue()` |
| `ComboBox` | `ComboBoxElement` | `getByLabel(page, label)`, `selectItem()`, `filterAndSelectItem()`, `assertValue()`, `open()`, `assertItemCount()` |
| `MultiSelectComboBox` | `MultiSelectComboBoxElement` | `getByLabel(page, label)`, `selectItem()`, `selectItems()`, `deselectItem()`, `assertSelectedItems()`, `assertSelectedCount()` |
| `Select` | `SelectElement` | `getByLabel(page, label)`, `selectItem()`, `assertValue()` |
| `ListBox` | `ListBoxElement` | `getByLabel(page, label)`, `selectItem()`, `assertSelectedValue()`, `assertItemEnabled()`, `assertItemDisabled()` |
| `DatePicker` | `DatePickerElement` | `getByLabel(page, label)`, `setValue(LocalDate)`, `assertValue(LocalDate)`, `assertValid()`, `assertInvalid()` |
| `TimePicker` | `TimePickerElement` | `getByLabel(page, label)`, `setValue(LocalTime)`, `assertValue(LocalTime)` |
| `DateTimePicker` | `DateTimePickerElement` | `getByLabel(page, label)`, `setValue(LocalDateTime)`, `assertValue(LocalDateTime)`, `setDate()`, `setTime()` |
| `Grid` | `GridElement` | `get(page)`, `getById(page, id)`, `assertRowCount()`, `assertCellContent(row, col, text)`, `assertHeaderCellContents()`, `select(rowIndex)`, `assertRowSelected()`, `scrollToRow()`, `findRow()`, `findCell()`; sorting via `findHeaderCellByText(text)` → `HeaderCellElement.clickSort()` / `assertSortAscending()` |
| `TreeGrid` | `TreeGridElement` | `get(page)`, `expandRow()`, `collapseRow()`, `assertRowCount()`, `assertCellContent()` — extends `GridElement` |
| `VirtualList` | `VirtualListElement` | `get(page)`, `assertRowCount()`, `assertItemRendered()`, `scrollToRow()` |
| `Dialog` | `DialogElement` | `getByHeaderText(page, text)` or `new DialogElement(page)`, `assertOpen()`, `assertClosed()`, `assertHeaderText()`, `closeWithEscape()`, `getContentLocator()` |
| `Notification` | `NotificationElement` | `getByText(page, text)`, `assertOpen()`, `assertClosed()`, `assertContent()` |
| `Tabs` / `Tab` | `TabElement` | `getTabByText(tabsLocator, text)`, `getSelectedTab(tabsLocator)`, `select()`, `assertSelected()` |
| `TabSheet` | `TabSheetElement` | `get(page)`, `selectTab(label)`, `getSelectedTab()`, `assertTabsCount()` |
| `Accordion` | `AccordionElement` | `new AccordionElement(locator)`, `openPanel(summary)`, `closePanel(summary)`, `assertPanelOpened()`, `assertPanelClosed()` |
| `AccordionPanel` | `AccordionPanelElement` | `getAccordionPanelBySummary(locator, summary)`, `assertOpened()`, `assertClosed()` |
| `Details` | `DetailsElement` | `getBySummaryText(page, summary)`, `setOpen(boolean)`, `assertOpened()`, `assertClosed()` |
| `MenuBar` | `MenuBarElement` | `getByLabel(page, label)` or `new MenuBarElement(page)`, `getMenuItemElement(name)`, `openSubMenu(name)` |
| `ContextMenu` | `ContextMenuElement` | `ContextMenuElement.openOn(target)` then `new ContextMenuElement(page)`, `selectItem()`, `assertOpen()`, `assertClosed()` |
| `SplitLayout` | `SplitLayoutElement` | `get(page)`, `assertHorizontal()`, `assertVertical()`, `dragSplitterBy()` |
| `Upload` | `UploadElement` | `getByButtonText(page, text)`, `uploadFiles(Path...)`, `assertHasFile()`, `assertFileComplete()` |
| `ProgressBar` | `ProgressBarElement` | `new ProgressBarElement(locator)`, `assertValue()`, `assertIndeterminate()` |
| `Avatar` | `AvatarElement` | `get(page)`, `getByName(page, name)`, `assertName()`, `assertAbbreviation()` |
| `MessageInput` | `MessageInputElement` | `get(page)`, `typeAndSubmit()`, `submit()`, `assertValue()` |
| `MessageList` | `MessageListElement` | `get(page)`, `assertMessageCount()`, `assertMessageText(index, text)`, `assertMessageUserName(index, name)` |
| `Popover` | `PopoverElement` | `getByLabel(page, label)` or `new PopoverElement(page)`, `assertOpen()`, `assertClosed()` |
| `SideNavigation` | `SideNavigationElement` | `getByLabel(page, label)`, `clickItem(label)`, `getItem(label)`, `assertCollapsed()`, `assertExpanded()` |
| `Card` | `CardElement` | `getByTitle(page, title)`, `assertTitle()`, `assertSubtitle()` |

## Factory method conventions

Expand All @@ -74,13 +75,19 @@ ButtonElement save = ButtonElement.getByText(page, "Save");
NotificationElement toast = NotificationElement.getByText(page, "Saved");
DialogElement dialog = DialogElement.getByHeaderText(page, "Confirm");

// From a Locator — always available via the constructor
// (useful for elements inside dialogs, grid cells, etc.)
// A few elements have no static factory at all (AccordionElement,
// ProgressBarElement, SideNavigationItemElement, ...) — construct them
// from a Locator, which is always available on every element:
// (also useful for elements inside dialogs, grid cells, etc.)
TextFieldElement field = new TextFieldElement(dialog.getLocator().locator("vaadin-text-field"));
```

## Common shared assertions (available on most elements)

Provided by `VaadinElement` and the shared mixins — an element only has a
mixin's methods if it implements that mixin (see each element's **Implements**
line in [api-reference.md](api-reference.md)).

```java
element.assertVisible();
element.assertHidden();
Expand Down
Loading
Loading