diff --git a/.claude/agent-memory/e2e-test-engineer/MEMORY.md b/.claude/agent-memory/e2e-test-engineer/MEMORY.md index 56a4bfd04..585558626 100644 --- a/.claude/agent-memory/e2e-test-engineer/MEMORY.md +++ b/.claude/agent-memory/e2e-test-engineer/MEMORY.md @@ -1,7 +1,161 @@ # E2E Test Engineer — Agent Memory (Index) > Detailed notes live in topic files. This index links to them. -> See: `e2e-pom-patterns.md`, `e2e-parallel-isolation.md`, `story-epic08-e2e.md`, `story-933-dav-vendor-contacts.md`, `milestones-e2e.md`, `story-1248-mass-move.md`, `photo-annotator-e2e.md` +> See: `e2e-pom-patterns.md`, `e2e-parallel-isolation.md`, `story-epic08-e2e.md`, `story-933-dav-vendor-contacts.md`, `milestones-e2e.md`, `story-1248-mass-move.md`, `photo-annotator-e2e.md`, `searchpicker-mobile-1708.md` + +## Diary Scenario 14 flake — FIXED (2026-06-16) — `e2e/tests/diary/diary-drafts.spec.ts:846` + +- **Root cause**: `toBeVisible({ timeout: 15_000 })` on heading/draftBadge timed out when `GET /api/diary-entries/:id` took >15s under heavy CI load. `test.slow()` triples expect.timeout from 15s→45s but the explicit `{ timeout: 15_000 }` override overrode it — the budget was 15s not 45s. +- **Fix applied (fix/diary-scenario14-e2e-flake)**: Register `page.waitForResponse` BEFORE `entryCard.click()` to catch `GET /api/diary-entries/${draftId}` (method=GET, status=200, URL ends with `/api/diary-entries/${draftId}`), await it after `waitForURL`, then assert heading/draftBadge with `{ timeout: 45_000 }`. +- **Pattern**: Always register `waitForResponse` BEFORE the triggering click, not after. Use `resp.url().endsWith(...)` for exact path matching (avoids matching list endpoint `GET /api/diary-entries?...`). +- **No production files changed** — test-only fix. + +## SearchPicker mobile anchor regression (Issue #1708, 2026-06-16) — `e2e/tests/responsive/search-picker-mobile.spec.ts` + +- Scenario 1 (@responsive, mobile-only MOBILE_MAX_WIDTH=499): focuses AreaPicker on WorkItemCreate, asserts `[data-search-picker-dropdown]` visible, `Math.abs(dropdownBox.y - inputBottom) < 20`. +- Scenario 2: `test.skip(true)` — no lightweight modal+picker E2E fixture; covered by SearchPicker.test.tsx unit test. +- AreaPicker uses `showItemsOnFocus={true}` → dropdown opens on `.click()` with no typing needed. +- Dropdown closes after `.click()` on an option — asserted via `not.toBeVisible()`. +- See `searchpicker-mobile-1708.md` for full context. + +## Photo Annotator Responsive Scaling E2E (fix #1705, 2026-06-16) — `e2e/tests/photoAnnotation.spec.ts` Scenarios 24–26 + +- Scenarios 24–26 added for ResizeObserver + fitScale + pointer-events fix. +- Scenario 24 (`@smoke @responsive`): asserts `getKonvaStageBox()` fits within `page.locator('[role="application"]').boundingBox()` (1px tolerance). `[role="application"]` IS the `.canvasArea` div (confirmed in PhotoAnnotator.tsx line 945). +- Scenarios 25–26 (`@responsive`): use `viewer.drawRectangle()` / `viewer.drawLine()` (both `page.mouse.*`) — same pattern as all other desktop tests. `drawFreehandTouch` / `drawLineTouch` are identical to their non-Touch counterparts (both use `page.mouse.*` since Konva migrated to pointer events). No separate touch variant needed. +- The tablet/mobile projects (`grep: /@responsive/`) run on iPad (gen 7, WebKit hasTouch) and iPhone 13 (WebKit hasTouch). Playwright `page.mouse.*` dispatches full pointer+mouse event chain, which fires Konva's `onPointerDown/Move/Up`. +- Scenario 24 tests the fitScale assertion at ALL viewports (desktop canvas = 100×100 ≤ container; mobile canvas is scaled down to fit ≤ container width ~375px). + +## OrientationsTab E2E coverage (fix #1687, 2026-06-15) — `e2e/tests/orientations.spec.ts`, `e2e/pages/OrientationsPage.ts` + +- Comprehensive 8-scenario spec already exists in `e2e/tests/orientations.spec.ts` (NOT under `navigation/`) — covers CRUD, dark mode, sort order, tab navigation, empty state +- CSS fix #1687 changed `styles.listContainer→itemsList` + `styles.listItem→itemRow` in ManagePage.tsx OrientationsTab +- POM `getOrientationRow()` still uses `[class*="itemName"]` — stable anchor regardless of row-level class +- `?tab=orientations` deep-link test added to `settings-manage.spec.ts` URL deep-linking describe block (was the only missing coverage) +- Panel ID: `orientations-panel` (dynamically generated: `${activeTab}-panel`) +- Create form h2: "Create orientation" (NOT "Create New Orientation" — unlike Areas/Trades/HI) + +## Paperless correspondent query param name (2026-06-15) — `e2e/tests/invoices/paperless-first-invoice.spec.ts` + +- `listPaperlessDocuments()` sends `?correspondent=` (integer, per `paperlessApi.ts` line 27: `params.set('correspondent', ...)`) +- NOT `?correspondentId=` — that is the DocumentBrowser/usePaperless PROP name, not the URL param +- Always check URL param as `url.searchParams.get('correspondent')` in route mocks +- `usePaperless` hook receives `correspondentId` prop → sends `correspondent` param in HTTP request + +## LinkedDocumentsSection defaultHideLinked change (Story #1679, 2026-06-15) — `document-linking.spec.ts` Scenario 7 + +- `LinkedDocumentsSection` now passes `defaultHideLinked={true}` to `DocumentBrowser` (line 420 of LinkedDocumentsSection.tsx) +- `InvoicePaperlessPickerModal` also passes `defaultHideLinked={true}` with `linkedDocumentIds={[]}` +- With empty `linkedDocumentIds`, `defaultHideLinked=true` doesn't hide anything (client-side filter excludes nothing) +- Updated Scenario 7a (now tests toggle defaults ON, initially 1 doc visible) and 7b (renamed "checked by default") +- When `linkedDocumentIds` has entries, docs with those IDs are hidden by default + +## SearchPicker Display Mode in E2E (2026-06-15) — `e2e/pages/PaperlessInvoiceReviewPage.ts` + +- When SearchPicker has `initialTitle + value` set (pre-filled), it renders `
` NOT the ``. Assert `vendorSelectedDisplay` = `[class*="card"].first() [class*="selectedDisplay"]`, NOT `#vendor-picker`. +- `#vendor-picker` input only present when SearchPicker is in search/input mode (no pre-fill or pre-fill cleared). +- `handleSave` on PaperlessInvoiceReviewPage validates each included line has `budgetCategoryId` OR `assignedBudgetLineId`. MOCK_EXTRACTED_LINES have neither — must inject `budgetCategoryId` via `page.request.get(API.budgetCategories)` before mocking preview. + +## PaperlessInvoiceReviewPage CSS Module vs TSX class-name mismatch (fix/1679, 2026-06-15) + +- **Root cause**: TSX was reworked to use new class names (`lineList`, `lineCard`, `lineCardExcluded`, `cardTopRow`, `cardMetricGrid`, `assignedBadge`, `pickerBudgetLineRow`, etc.) mirroring AutoItemizePage, but `PaperlessInvoiceReviewPage.module.css` was NOT updated — it still only defines old names (`linesList`, `lineItem`, `lineItemExcluded`, `pageContainer`, `mainColumn`, `card`, etc.). All the new class refs resolve to `undefined` in JS/CSS Modules. +- **DOM impact**: `