--- name: add-feature-test description: > Given a plain-language description of an EXISTING Chamilo feature (a tool, a form, an admin page — anything reachable in the running app), locate its current implementation (legacy PHP or Vue SPA), explore the live rendered UI to confirm real selectors/behavior, and author a complete, non-duplicating Playwright/Gherkin test suite for it in tests/playwright/features/. Use when the user describes a feature and asks for test coverage, wants to "add tests for X", asks to confirm a feature "keeps working", or runs /add-feature-test. Companion to the Playwright section of CLAUDE.md. --- # Add Feature Test Turn a plain-language feature description into a Playwright/Gherkin test suite that actually reflects the live application — not assumptions from reading source code alone. Static code reading gets selectors, field names, dialog types, and even which page is actually live wrong often enough that every one of those claims must be confirmed against a running instance before it goes into a test. Scope, per the user's own framing: **wide-ranging, not exhaustive.** Cover the feature's real create/read/update/delete flows and role-based access variants that a normal user would actually hit. Do not chase combinatorial platform- setting states or contrived edge cases nobody would realistically trigger. --- ## Step 0 — Get the feature description straight If the description could point at more than one page (a feature that exists both as a legacy tool and a parallel/newer Vue equivalent, or a name that's ambiguous inside Chamilo's domain), ask which one before doing any real work. Otherwise proceed — most descriptions ("the Agenda tool", "creating a session", "the document manager") are unambiguous enough to just go find. ## Step 1 — Locate the current implementation Search BOTH possible homes — this codebase is mid-migration and a feature can live in either, or have moved without every caller being updated: - Legacy PHP: `public/main//*.php`, `public/main/inc/lib/*.lib.php` for the form-building logic. - Vue SPA: `assets/vue/views//*.vue`, `assets/vue/router/.js`. If both exist, determine which is actually **reachable** in the live app — course-tool links and admin-panel entries have been silently repointed to a new Vue route while the old URL still technically loads (`toolAnnouncement`'s announcement tool is exactly this: direct URL still hits the legacy page, but the course-tool link now goes to `/resources/announcement/:id`). Don't assume; check `src/CoreBundle/Tool/*.php` / grep the link's actual `href`/route target. If the legacy page is now a dead stub (`header('Location: ...'); exit;` or similar), this is a **fresh-scenario** case, not a port — write scenarios against the current Vue page's real intent, not the old page's old flow. `class.feature`'s rewrite of the dead `usergroups.php` → `UsergroupList.vue` and `toolDocument.feature`'s full rewrite against the Vue Document tool are the reference examples for this. ## Step 2 — Check for existing coverage (don't duplicate) 1. `tests/playwright/features/*.feature` — if a file for this feature **already exists**, this is an EXTEND task: read it fully, understand what it already covers, and only add what's missing (new scenarios, new roles, edge cases) rather than starting over or duplicating scenarios. 2. The deleted Behat suite — the files are in git history (`git show 98c77757ea6:tests/behat/features/.feature`; `git ls-tree -r --name-only 98c77757ea6 tests/behat` for the list, 84 files at that commit). If a same-topic file existed there, it's a **hint** of intended scenarios (what create/edit/delete flows the original author thought mattered) — never a source of truth for selectors. Every field name, button label, and dialog type it assumes must still be verified live in Step 4; those files rotted constantly (name→title renames, dead pages, changed widgets, typo'd step phrases that never even matched in the original suite). 3. If genuinely new (nothing in either place — e.g. a feature added since then), design scenarios from CLAUDE.md's own mandatory rule (it applies here too): cover create/read/update/delete at minimum; run the full scenario set once per role if the page is reachable by more than one role; add an explicit access-denied scenario for role-restricted pages. ## Step 3 — Get a safe place to test Prefer a disposable, dedicated fresh install where creating/deleting data freely is fine — ask the user if one exists for this repo (one may already be set up as a separate vhost/DB pointed at this exact worktree). Confirm: - It's actually serving **this worktree's** code, not a shared box serving a different branch. A vhost `DocumentRoot` edit alone is not enough — the web server process must also be reloaded to pick it up. Verify with a plain marker file (`echo x > public/marker.txt && curl .../marker.txt`) before trusting ANY test result against it — a stale-server false-positive/negative wastes the rest of the session's work. - The one-time seed sequence has run if the feature needs course context (`package.json`'s `test:playwright:seed` → `:seed-course` → `:seed-private-course` → `:seed-settings`, in that order — creates the test users, the `TEMP`/`TEMPPRIVATE` courses most course-tool features assume exist, and the settings some scenarios assume are enabled). If no such environment is available and only a shared/production-like box exists, get explicit confirmation before creating or deleting ANY data there, and prefer read-only verification (log in, look, don't submit forms) until that's granted. ## Step 4 — Explore the live UI (this is the core of the skill) Log in as the relevant role(s) and physically use the feature. For anything non-trivial (a form with more than plain text inputs, any delete/confirm action, any grid), don't just eyeball it in a browser — write a short, disposable Node script using the already-installed `playwright` package to log in, navigate, and dump the actual DOM: field `id`/`name` attributes, button structure and `title`/`aria-label`, dialog markup, icon classes. This is dramatically faster and more reliable than reading PHP/Vue source and guessing what renders — this migration's own history is full of cases where the source *looked* like it would produce one thing and the live DOM showed another (QuickForm's `id` prefixing by form name while `name` stays unprefixed; a "Save" button that's actually a disabled TinyMCE toolbar button sharing the same accessible name; an icon-only button whose only identifier is a `title` attribute; a jqGrid "select all" checkbox that visually toggles but does nothing if data rows haven't loaded yet). Specifically confirm, don't assume: - **Form fields**: plain input, or one of the richer widgets below? - **Multi-value fields**: a plain ``s, left = available, right = `_to` = actually submitted, empty until JS moves an option across)? - **Rich text fields**: legacy pages use TinyMCE via `window.setContentFromEditor(id, content)`; Vue pages use `BaseTinyEditor` (`@tinymce/tinymce-vue`) which needs a `change` event fired after `setContent()` to reach `v-model`. Different steps in `tests/playwright/steps/common.steps.ts` handle each — check which applies. - **Date/time pickers**: PrimeVue's ``/`` (`BaseCalendar.vue`) is genuinely `readonly` — no keyboard entry works at all, and its time picker is increment/decrement buttons only, impractical to drive to an exact value. If the field already defaults to a sane current value when opened, prefer relying on that default over trying to set an exact one. - **Delete/destructive confirmation** — there are at least four distinct mechanisms in this codebase; identify which one before writing the step: native browser `confirm()`, SweetAlert2, a PrimeVue `ConfirmDialog` (labels vary — "Yes"/"No", "Confirm"/plain button text — read the actual rendered buttons), or jqGrid's own built-in del dialog (its buttons are ``, not real `