diff --git a/.claude/commands/playwright.md b/.claude/commands/playwright.md new file mode 100644 index 0000000..ce5a551 --- /dev/null +++ b/.claude/commands/playwright.md @@ -0,0 +1,984 @@ +# /playwright — Execute Playwright Test Sweeps + +## Arguments + +`$ARGUMENTS` + +Parse the arguments above: + +- **Empty** → run all sweeps A through L (Chromium) +- **One or more letters** (e.g. `A`, `A B C`, `C D`) → run only those sweeps (Chromium) +- **`init`** → perform session initialization only, no sweeps +- **`--browser=firefox`** or **`--browser=webkit`** (optionally followed by sweep letters) → cross-browser run; see **Cross-Browser Sessions** section + +## Workflow + +1. **Check dev server** — navigate to `http://localhost:5173`. If it does not load, stop and tell the user to run `cd playground && npm run dev` in a separate terminal. +2. **Session initialization** — follow the 4-step block in the "Session Initialization" section below exactly. For cross-browser runs substitute the tool namespace as described in "Cross-Browser Sessions". +3. If argument is `init`, stop after initialization. +4. **Execute each requested sweep** — for every sweep letter (or all A–L if no argument, or the CB subset if `--browser` was specified with no letters): + a. Print `## Sweep : ( cases) [Browser]` + b. Run the Sweep Setup block for that sweep + c. For each test row: perform the steps, compare actual vs Expected, record PASS / FAIL / SKIP with a one-line note. If the ID is in the Known Issues table, prepend `[KNOWN BUG]` — a FAIL is expected. +5. **Update this file** — write results back into the Result column for every executed row. Append a new row to the Test Run Log (include Browser column). Never overwrite past log entries. +6. **Print summary table** — PASS / FAIL / SKIP per sweep plus totals. Flag non-known FAILs as regressions. +7. **Create/update `TEST_REPORT.md`** — write (or overwrite) `TEST_REPORT.md` in the project root with the following sections: + - **Run metadata**: date, branch, browser, total PASS / FAIL / SKIP counts + - **Per-sweep summary table**: sweep letter, name, pass, fail, skip + - **Failures**: one row per FAIL with ID, sweep, description, and observed vs expected + - **Known Issues**: copy the current Known Issues table (OPEN rows only) + - **Open Follow-up Items**: copy the current Open Follow-up Items table + - **Regressions**: list any non-known FAILs explicitly as regressions + Use the same data written to the Test Run Log — no re-running tests. + +--- + +# Playwright Test Playbook — `calendar-simple` Library + +## About This Playbook + +This is the **canonical execution file** for every Playwright MCP test run against the `calendar-simple` library. Load this file as context at the start of each test session, then execute sweeps A–L in order. + +- **Target app**: `http://localhost:5173` — start with `cd playground && npm run dev` +- **Test harness**: MCP Playwright plugin — all 23 `mcp__plugin_playwright_playwright__*` tools +- **testId prefix**: `playground-calendar` (set in `playground/src/App.tsx:35`) +- **Results file**: results are tracked in this file (`.claude/commands/playwright.md`) +- **Screenshots**: save to `tests/screenshots/` using names from the Visual Regression section +- **Total cases**: 310 (A:18 + B:14 + C:128 + D:26 + E:18 + F:18 + G:24 + H:13 + I:14 + J:14 + K:15 + L:8) +- **Library version**: `calendar-simple` v1.2.0 — branch `version_2` +- **Cross-browser**: Firefox + WebKit MCP servers in `.mcp.json`; run `/playwright --browser=firefox` or `/playwright --browser=webkit`. One-time setup: `npx playwright install firefox webkit`. The 27-case CB subset covers RTL CSS, matchMedia, ResizeObserver, focus/ARIA, and popover positioning. + +--- + +## Known Issues + +> Check this list at the start of each run. Rows marked **OPEN** have confirmed bugs — record FAIL in this file; do not skip them. + +| Status | ID | Test | Observed | Expected | Repro | +| ------ | -------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | +| CLOSED | G-12 | `onMoreClick(date, hiddenEvents)` | Fixed in run 2026-05-16 — second arg now correctly an array of hidden CalendarEvent objects (with enrichment fields) | Should receive `CalendarEvent[]` of hidden events | Month view + `maxEvents=2` + "Month Overflow" fixture → click "+10 more" → check console | +| CLOSED | K-03 | `eventsAreSorted=true` with unsorted input | Fixed in commits 73d480f + 87eba25 (verified run 2026-05-19) — `useEvents` now emits `console.warn` when an unsorted array is detected; JSDoc on `eventsAreSorted` documents the warn behavior. | `console.warn` logged in browser console | Edge Cases fixture → toggle `eventsAreSorted=true` → check console | +| CLOSED | K-05 | `enableEnrichedEvents=true` without map | Fixed in commits 73d480f + 87eba25 (verified run 2026-05-19) — `useEvents` now emits `console.warn` when `enrichedEventsByDate` is absent; JSDoc updated on `enableEnrichedEvents` and `enrichedEventsByDate`. | `console.warn` logged in browser console | ControlPanel → Performance → toggle `enableEnrichedEvents=true`, omit map → check console | +| CLOSED | TC3 | Negative-duration events | Closed by commit 7e5714b (verified in run 2026-05-17 retest). Filter reaches all views via context; Schedule view total = 48 (without TC3); +N more popover does not include TC3. Earlier partial-fix observation was stale HMR state in long-running browser session — fresh navigation confirms full fix. | n/a — closed | n/a — closed | +| CLOSED | TC3-ROOT | `useEvents` filter bypassed via context | Closed by commit 7e5714b — `config={{ ...allProps, events: validEvents }}` correctly threads the filtered array through context for Week/Day/Month main cells. | Schedule view and Month overflow popover still need investigation — see TC3 row above. | n/a — closed | + +--- + +## Session Initialization + +Run these steps at the **start of every test session** before any sweep. + +### Step 1 — Start Dev Server + +``` +cd playground && npm run dev # keep running in terminal; port 5173 +``` + +### Step 2 — Open Browser & Baseline + +``` +browser_navigate(url="http://localhost:5173") +browser_wait_for(text="playground-calendar-container") +browser_snapshot() → verify 2-panel layout: calendar left, ControlPanel right sidebar +browser_console_messages() → MUST be empty (zero errors/warnings) +browser_network_requests() → all assets return 200 +``` + +### Step 3 — Reset ControlPanel + +``` +browser_snapshot() → locate "Reset All" button at top of ControlPanel sidebar +browser_click(element="Reset All") +browser_snapshot() → confirm all section badges read 0 (no modified controls) +``` + +### Step 4 — Verify Root testId + +``` +browser_evaluate(script="!!document.querySelector('[data-testid=\"playground-calendar-container\"]')") +→ must return true +``` + +--- + +## Cross-Browser Sessions + +> One-time setup (run once in a terminal before first Firefox/WebKit sweep): +> +> ``` +> npx playwright install firefox webkit +> ``` +> +> This downloads the browser binaries used by `@playwright/mcp`. No npm package.json changes needed — `@playwright/mcp` is invoked via `npx` directly from `.mcp.json`. + +### Tool Namespace per Browser + +All sweep steps use `browser_*` commands. In cross-browser mode, substitute the MCP tool prefix: + +| Browser | Tool prefix | Argument | +| ------------------ | ------------------------------------- | ------------------- | +| Chromium (default) | `mcp__plugin_playwright_playwright__` | _(none)_ | +| Firefox | `mcp__playwright-firefox__` | `--browser=firefox` | +| WebKit | `mcp__playwright-webkit__` | `--browser=webkit` | + +For example, `browser_navigate(url="http://localhost:5173")` becomes: + +- Chromium: `mcp__plugin_playwright_playwright__browser_navigate(url=...)` +- Firefox: `mcp__playwright-firefox__browser_navigate(url=...)` +- WebKit: `mcp__playwright-webkit__browser_navigate(url=...)` + +> The `playwright-firefox` and `playwright-webkit` MCP servers are defined in `.mcp.json` and auto-approved via `enabledMcpjsonServers` in `.claude/settings.local.json`. They become available after restarting Claude Code. + +### Cross-Browser Subset (CB) + +When `/playwright --browser=firefox` or `/playwright --browser=webkit` is run **without specific sweep letters**, execute only this curated subset. These are the highest browser-risk cases — areas where CSS engines, `matchMedia`, `ResizeObserver`, and ARIA/focus handling differ across browsers. + +**Total CB cases: 27** + +| Sweep | IDs | Risk area | +| ----- | ---------------------------------- | ----------------------------------------------- | +| A | A-01, A-11, A-17 | Basic mount, CSS custom props, console hygiene | +| B | B-01, B-02, B-03, B-04 | Navigation click handlers | +| D | D-03, D-16, D-17 | CSS vars, `showCurrentTime`, `scrollIntoView` | +| E | E-01, E-02, E-03 | `colorScheme` attribute, `matchMedia` dark mode | +| F | F-06, F-10, F-12, F-13, F-14 | RTL CSS logical props, direction, time format | +| J | J-03, J-07, J-10, J-11, J-12 | Responsive layout, `ResizeObserver` | +| L | L-01, L-02, L-04, L-05, L-06, L-08 | Tab order, ARIA, popover keyboard | + +When sweep letters **are** specified (e.g. `/playwright --browser=firefox A B`), run exactly those sweeps in full using the Firefox tool namespace. + +### Cross-Browser Known Issues + +| Status | ID | Browser | Test | Observed | Expected | +| ----------------- | --------- | --------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | +| RESOLVED → PASS | D-17 | Firefox + WebKit | `autoScrollToCurrentTime` | Verified 2026-05-21 via a `scrollTo` spy: the effect (`DayView.tsx:103-118`) calls `dayView.scrollTo({top:~970, behavior:"smooth"})` with the correct current-time target on both engines. WebKit observably scrolls (`scrollTop=882`, clamped to max); Firefox's `scrollTop` stays 0 **only because headless Firefox doesn't execute programmatic smooth scrolls** (instant `scrollTo` works). Feature is correct — earlier SKIP was a test-method error (wrong trigger order + wrong element measured). | Region scrolls toward current time | +| INFO (playground) | A-11/J-12 | Firefox + WebKit + Chromium | Layout "1280px" width preset | `--calendar-width` resolves to invalid `"1280pxpx"` (double unit). The custom-property mechanism itself works identically in all three engines; the doubled unit is a browser-independent playground preset quirk (string width gets `px` appended). | `--calendar-width: 1280px` | + +> No **browser-specific** defects found. Every CB case that ran produced identical behavior across Firefox, WebKit, and Chromium. The two rows above are a harness limitation and a pre-existing browser-independent playground quirk, respectively. + +--- + +## Common Selectors & MCP Command Reference + +### Calendar testId Selectors (prefix = `playground-calendar`) + +All selectors use `playground-calendar` as the testId prefix. Source: `Header.tsx:194-287`, `Calendar.tsx:74`, `Popover.tsx:172-222`, `ScheduleView.tsx:61-130`. + +| Element | Selector | +| --------------------- | -------------------------------------------------------------- | +| Root container | `[data-testid="playground-calendar-container"]` | +| Header bar | `[data-testid="playground-calendar-header"]` | +| Today button | `[data-testid="playground-calendar-header-today-btn"]` | +| Previous button | `[data-testid="playground-calendar-header-prev-btn"]` | +| Next button | `[data-testid="playground-calendar-header-next-btn"]` | +| View select | `[data-testid="playground-calendar-header-view-select"]` | +| Month select | `[data-testid="playground-calendar-header-month-select"]` | +| Year select | `[data-testid="playground-calendar-header-year-select"]` | +| Month view wrapper | `[data-testid="playground-calendar-month-view"]` | +| Schedule view wrapper | `[data-testid="playground-calendar-schedule-view"]` | +| Popover dialog | `[data-testid="playground-calendar-popover-content"]` | +| Popover event item | `[data-testid="playground-calendar-{eventId}-popover-item"]` | +| Schedule event | `[data-testid="playground-calendar-{eventId}-schedule-event"]` | + +### ARIA Selectors + +| Element | Selector | +| --------------- | ------------------------------------------------- | +| Header nav | `nav[aria-label="Calendar navigation"]` | +| Today button | `button[aria-label="Today"]` (or localized value) | +| Previous | `button[aria-label="Previous period"]` | +| Next | `button[aria-label="Next period"]` | +| View select | `select[aria-label="Select calendar view"]` | +| Month select | `select[aria-label="Select month"]` | +| Year select | `select[aria-label="Select year"]` | +| Popover | `[role="dialog"][aria-modal="true"]` | +| Schedule region | `[role="region"][aria-label="Schedule view"]` | + +### Data Attribute Checks + +```javascript +// Color scheme resolved value +browser_evaluate( + (script = + "document.querySelector('[data-testid=\"playground-calendar-container\"]').dataset.colorScheme"), +); + +// Direction attribute (ltr / rtl) +browser_evaluate( + (script = + "document.querySelector('[data-testid=\"playground-calendar-container\"]').dir"), +); + +// Calendar width CSS var +browser_evaluate( + (script = + "getComputedStyle(document.querySelector('[data-testid=\"playground-calendar-container\"]')).getPropertyValue('--calendar-width')"), +); +``` + +### Common MCP Operations + +``` +# Navigation +browser_navigate(url="http://localhost:5173") +browser_click(element="Today") → by aria-label +browser_click(element="Previous period") +browser_click(element="Next period") + +# View switching (via header dropdown) +browser_select_option(element="Select calendar view", values=["month"]) +# values: month | week | day | schedule | customDays + +# Month / Year dropdowns +browser_select_option(element="Select month", values=["12"]) → December +browser_select_option(element="Select year", values=["2030"]) + +# Keyboard interaction +browser_press_key(key="Escape") +browser_press_key(key="Tab") +browser_press_key(key="Enter") +browser_press_key(key="Space") + +# Screenshots +browser_take_screenshot(filename="tests/screenshots/.png") + +# Console & network hygiene +browser_console_messages() +browser_network_requests() + +# Viewport resize +browser_resize(width=1280, height=800) → desktop +browser_resize(width=768, height=900) → tablet +browser_resize(width=480, height=800) → phone +browser_resize(width=360, height=640) → narrow phone + +# DOM evaluation +browser_evaluate(script="") + +# Wait for element / text +browser_wait_for(text="") +``` + +--- + +## ControlPanel Quick-Action Guide + +The ControlPanel sidebar has 12 collapsible sections. **No `data-testid` attributes** exist on ControlPanel elements — always `browser_snapshot()` first, then target by visible label text or accessible name. + +### Standard Pattern + +``` +browser_snapshot() → read accessibility tree +browser_click(element="