From 75b40f15e283eb70069bca71f2228660e51db5a2 Mon Sep 17 00:00:00 2001 From: jcgueriaud1 Date: Tue, 7 Jul 2026 15:40:48 +0300 Subject: [PATCH 1/3] Broaden vaadin-playwright-test skill trigger to indirect requests The skill matcher keys off signals present in the conversation. When a requirement lives inside a fetched issue/PR/spec body (e.g. "Implement #85.") rather than the user's own words, none of the old trigger keywords appear in the prompt and the skill never fires. Widen the description to trigger on indirect requests, on in-context code signals (SpringPlaywrightIT, @PlaywrightElement, IT file paths), and any Vaadin-project task involving a Playwright/IT test. Co-Authored-By: Claude Opus 4.8 (1M context) --- skills/vaadin-playwright-test/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/vaadin-playwright-test/SKILL.md b/skills/vaadin-playwright-test/SKILL.md index a2bbb31..2b93af2 100644 --- a/skills/vaadin-playwright-test/SKILL.md +++ b/skills/vaadin-playwright-test/SKILL.md @@ -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) From 7d1173f1b9f42c55b2e32b1ab747b244715b191e Mon Sep 17 00:00:00 2001 From: jcgueriaud1 Date: Tue, 7 Jul 2026 16:14:16 +0300 Subject: [PATCH 2/3] Fix skill docs: hallucinated APIs, mangled examples, autonomous-mode steps - element-mapping.md: verify every method in the Key Methods column against the auto-generated api-reference.md; replace non-existent APIs (selectByText, clickRow, sortByColumn, assertUnchecked, sendMessage, uploadFile, wrong get(page) factories) with the real ones - SKILL.md: fix wrong wrapper calls in the raw-locator guidance, restore the formatter-mangled Java templates, document MainView/Main root route mapping, add a version-drift caveat for api-reference.md, and make Steps 4-5 write/run without confirmation during autonomous execution - setup.md: clean up malformed XML snippets - TESTING.md: describe page objects by library package instead of a source path that only exists in the DramaFinder repo Co-Authored-By: Claude Fable 5 --- skills/vaadin-playwright-test/SKILL.md | 86 ++++++++-------- skills/vaadin-playwright-test/TESTING.md | 2 +- .../vaadin-playwright-test/element-mapping.md | 97 ++++++++++--------- skills/vaadin-playwright-test/setup.md | 10 +- 4 files changed, 97 insertions(+), 98 deletions(-) diff --git a/skills/vaadin-playwright-test/SKILL.md b/skills/vaadin-playwright-test/SKILL.md index 2b93af2..7d5c425 100644 --- a/skills/vaadin-playwright-test/SKILL.md +++ b/skills/vaadin-playwright-test/SKILL.md @@ -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. @@ -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 ``, 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 @@ -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 - -; // mirror src/test/java structure +package ; // mirror src/test/java structure import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @@ -142,21 +145,17 @@ import .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 IT extends -SpringPlaywrightIT { // or AbstractBasePlaywrightIT +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) // omit if not Spring Boot +public class IT extends SpringPlaywrightIT { // or AbstractBasePlaywrightIT @Override - public String getView () { + public String getView() { return "/"; } @Test - public void testTitle () { + public void testTitle() { assertThat(page).hasTitle(""); } @@ -172,24 +171,14 @@ otherwise. **Smoke test (one per component):** ```java - @Test -public void test(){ -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() { + TextFieldElement field = TextFieldElement.getByLabel(page, "My Label"); + field.assertVisible(); + field.assertLabel("My Label"); + field.assertValue(""); + field.setValue("test value"); + field.assertValue("test value"); } ``` @@ -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//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//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=IT`? +Run with `mvn verify -Dit.test=IT`. -**Warn the user**: the first Vaadin frontend build takes 3–5 minutes. Subsequent -runs are ~25 seconds. \ No newline at end of file +- **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. \ No newline at end of file diff --git a/skills/vaadin-playwright-test/TESTING.md b/skills/vaadin-playwright-test/TESTING.md index 8ad59d3..449235e 100644 --- a/skills/vaadin-playwright-test/TESTING.md +++ b/skills/vaadin-playwright-test/TESTING.md @@ -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. diff --git a/skills/vaadin-playwright-test/element-mapping.md b/skills/vaadin-playwright-test/element-mapping.md index 576829e..29d02e8 100644 --- a/skills/vaadin-playwright-test/element-mapping.md +++ b/skills/vaadin-playwright-test/element-mapping.md @@ -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 @@ -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(); diff --git a/skills/vaadin-playwright-test/setup.md b/skills/vaadin-playwright-test/setup.md index 333bbe0..fc30b9b 100644 --- a/skills/vaadin-playwright-test/setup.md +++ b/skills/vaadin-playwright-test/setup.md @@ -35,7 +35,6 @@ Two edits, both in `pom.xml`: Inside ``, add: ```xml - RESOLVED_VERSION ``` @@ -47,7 +46,6 @@ Replace `RESOLVED_VERSION` with the value from Step 1. If a Inside ``, add (skip whichever is already present): ```xml - com.microsoft.playwright playwright @@ -55,10 +53,10 @@ Inside ``, add (skip whichever is already present): -org.vaadin.addons -dramafinder -${dramafinder.version} -test + org.vaadin.addons + dramafinder + ${dramafinder.version} + test ``` From f8f4aae1b57c363af9761fbe5d326e17f2e5369e Mon Sep 17 00:00:00 2001 From: jcgueriaud1 Date: Tue, 7 Jul 2026 17:17:21 +0300 Subject: [PATCH 3/3] Make the skill trigger on dependency detection, not just prompt wording Skill matching is a heuristic over context: a task that never mentions Vaadin/Playwright/tests gives the model no signal to load the skill. Add two deterministic signal sources: - SessionStart plugin hook that greps pom.xml for the dramafinder artifact and, on a hit, injects a standing reminder to use the skill for any IT test work - setup.md step that appends an equivalent pointer to the consuming project's CLAUDE.md, covering sessions where the plugin hook isn't active Bump plugin version to 0.3.0 (plugin.json + tile.json). Co-Authored-By: Claude Fable 5 --- .../.claude-plugin/plugin.json | 2 +- .../hooks/detect-dramafinder.sh | 16 ++++++++++++++++ .../vaadin-playwright-test/hooks/hooks.json | 14 ++++++++++++++ skills/vaadin-playwright-test/setup.md | 19 ++++++++++++++++++- tile.json | 2 +- 5 files changed, 50 insertions(+), 3 deletions(-) create mode 100755 skills/vaadin-playwright-test/hooks/detect-dramafinder.sh create mode 100644 skills/vaadin-playwright-test/hooks/hooks.json diff --git a/skills/vaadin-playwright-test/.claude-plugin/plugin.json b/skills/vaadin-playwright-test/.claude-plugin/plugin.json index 8427706..63cccef 100644 --- a/skills/vaadin-playwright-test/.claude-plugin/plugin.json +++ b/skills/vaadin-playwright-test/.claude-plugin/plugin.json @@ -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" }, diff --git a/skills/vaadin-playwright-test/hooks/detect-dramafinder.sh b/skills/vaadin-playwright-test/hooks/detect-dramafinder.sh new file mode 100755 index 0000000..e7d842e --- /dev/null +++ b/skills/vaadin-playwright-test/hooks/detect-dramafinder.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# SessionStart hook installed with the vaadin-playwright-test plugin. +# +# Skill matching is a heuristic over whatever is in context: if a task (e.g. +# a GitHub issue body) never mentions Vaadin, Playwright, or tests, nothing +# signals the model to load the skill. This hook makes the signal come from +# the environment instead — when the project declares a DramaFinder +# dependency, it injects a standing reminder into the session context. +if grep -qs 'dramafinder' pom.xml */pom.xml 2>/dev/null; then + cat <<'EOF' +This project depends on the DramaFinder Playwright testing library. For any +integration/IT test work (writing, editing, or running tests for Vaadin +views), load the vaadin-playwright-test skill before writing the test. +EOF +fi +exit 0 diff --git a/skills/vaadin-playwright-test/hooks/hooks.json b/skills/vaadin-playwright-test/hooks/hooks.json new file mode 100644 index 0000000..618f1cc --- /dev/null +++ b/skills/vaadin-playwright-test/hooks/hooks.json @@ -0,0 +1,14 @@ +{ + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/detect-dramafinder.sh\"" + } + ] + } + ] + } +} diff --git a/skills/vaadin-playwright-test/setup.md b/skills/vaadin-playwright-test/setup.md index fc30b9b..02507c2 100644 --- a/skills/vaadin-playwright-test/setup.md +++ b/skills/vaadin-playwright-test/setup.md @@ -81,7 +81,24 @@ Otherwise: 5. **Write** the result to `src/test/java//it/support/SpringPlaywrightIT.java`. -## Step 4 — Confirm dependencies resolve +## Step 4 — Add a CLAUDE.md pointer + +Skill matching is heuristic: a future task that never mentions Vaadin, +Playwright, or tests won't trigger the skill on its own. A standing pointer in +the project's `CLAUDE.md` fixes that (it is loaded into context every session). + +Skip this step if `CLAUDE.md` already mentions `vaadin-playwright-test`. +Otherwise append to `CLAUDE.md` (create the file if it doesn't exist): + +```markdown +## Integration testing + +This project uses DramaFinder for Playwright integration tests. For any +integration/IT test work (writing, editing, or running tests for Vaadin +views), use the `vaadin-playwright-test` skill. +``` + +## Step 5 — Confirm dependencies resolve Optional but recommended: run `mvn -q dependency:resolve` to surface any pom syntax errors before generating tests. Skip if the user wants to proceed without diff --git a/tile.json b/tile.json index 6297ad9..9daa31d 100644 --- a/tile.json +++ b/tile.json @@ -1,6 +1,6 @@ { "name": "dramafinder/vaadin-playwright-test", - "version": "0.2.0", + "version": "0.3.0", "private": false, "summary": "Generate Playwright integration tests for Vaadin 25 views using the DramaFinder library. Use when the user wants to write IT tests for a Vaadin view, mentions DramaFinder, or asks about Playwright testing in a Vaadin project.", "skills": {