8.8 KiB
8.8 KiB
id, title, status, wave, depends_on, plan, spec
| id | title | status | wave | depends_on | plan | spec | |
|---|---|---|---|---|---|---|---|
| 03-e2e | E2E hover duration spec | done | 3 |
|
plan.md | ../../specs/ui/requirements/prompt-turn-duration.md |
Task 03: E2E hover duration spec
- Acceptance:
- New Playwright spec
apps/web/e2e/tests/task/prompt-turn-duration.spec.tsverifies: in a settled task session, the persisted user prompt's row is located within the ACTIVE chat, the action row (parent ofmessage-turn-duration, obtained e.g. viaduration.locator("xpath=..")) has computedopacity: 0before hover andopacity: 1after hover — asserted withtoHaveCSS(it auto-retries through thetransition-opacitytransition;toBeVisible/not.toBeVisibleare NOT usable because Playwright ignoresopacity: 0) — andmessage-turn-duration's text matchesDURATION_SHAPE(/^\d+s$|^\d+m \d+s$|^\d+h \d+m \d+s$/). HOVER TARGET (verified against the code):#msg-<id>is the FULL-WIDTH outer wrapper (message-list-native.tsxMessageRow); thegroupclass lives on a descendant (chat-message.tsx— themax-w-[85%] ... overflow-hidden groupdiv) with the right-aligneduser-message-bubble. Playwrighthover()centers the pointer on the target, so hovering#msg-<id>can land OUTSIDE the bubble and never triggergroup-hover(opacity stays 0). The hover target MUST be an element inside the.groupcontainer:row.getByTestId("user-message-bubble")(stable testid) orrow.locator(".group").first()— not the outer#msg-<id>itself. - The spec follows
prompt-history-panel.spec.tsconventions:createTaskWithAgent+DONE_STATESsettle poll,SessionPagehelpers. BEFORE navigating, capture the seeded prompt's persisted message id byexpect.poll-ingapiClient.listSessionMessages(sessionId)until the seeded user prompt appears (pre-navigation timing frommobile-prompt-history-panel.spec.ts— which reads messages ONCE after the settle poll; the explicitexpect.pollis an ADDED robustness requirement, because a terminal session state can precede message observability and a single read could strand the test with no id). Scope every transcript locator tosession.activeChat()—#msg-<id>exists on every mountedMessageRow, and portal-mounted inactive chat panels duplicate rows, so useconst chat = session.activeChat(); const row = chat.locator(`#msg-${messageId}`)andawait expect(row).toHaveCount(1), mirroringprompt-history-panel.spec.ts. Duration assertion is shape-only (wall clock is uncontrolled), with the same comment explaining why. - A mobile spec
apps/web/e2e/tests/task/mobile-prompt-turn-duration.spec.ts(mobile-chrome project, Pixel 5; modeled onmobile-prompt-history-panel.spec.ts) asserts: at mobile width the settled prompt'smessage-turn-durationrenders inside the ACTIVE chat AND the action row (the duration's parent) has computedopacity: 1(toHaveCSS— belowsmthe row is always visible, baseopacity-100; Playwright does NOT treat opacity as non-visible, so presence alone proves nothing). It also asserts the duration is not clipped and stays on one line: the duration span has computedwhite-space: nowrap, the ACTION ROW hasscrollWidth <= clientWidth, and the duration's bounding rect stays within the action row's rect. The outer#msg-<id>row check (scrollWidth <= clientWidth) stays only as an ADDITIONAL assertion — the user-message bubble and its wrappers areoverflow-hidden(chat-message.tsx), so inner clipping can happen without the outer row overflowing. NOTE: the E2E wall-clock duration is uncontrolled and normally sub-minute, so the mobile spec can only assert the no-wrap STYLING — the deterministic multi-unit (5m 23s) no-wrap/geometry proof lives in task-02's component test. 3a. The mobile spec also resizes the touch-emulated page to 700px wide. At this coarse-pointer width, which is above the CSSsmbreakpoint, it asserts that the duration's action row remains opaque without hover. - The desktop spec also proves keyboard reveal INDEPENDENTLY of hover: after the hover assertions, move the mouse to a point OUTSIDE the user row (e.g.
page.mouse.move(0, 0)), assert the action row's computedopacityreturns to0(proving the pointer is no longer over the row), THEN focus an existing action button inside the user row (e.g. the copy button, or Tab into the row) and assert the duration parent's computedopacityis1(toHaveCSS) withmessage-turn-durationpresent — the row reveals viafocus-within:opacity-100(message-actions.tsx); without the pointer-reset, a focused-while-hovered button would pass purely fromgroup-hoverand a focus-within regression would go unnoticed.
- New Playwright spec
- Verification: (run from the repo root; the backend builds work from any directory via
-C, thencdintoapps/web)(E2E runs the PREBUILT backend and web bundle:make -C apps/backend build && make -C apps/backend e2e-plugin-package && cd apps/web && pnpm run build:e2e && pnpm e2e:raw tests/task/prompt-turn-duration.spec.ts --project=chromium && pnpm e2e:raw tests/task/mobile-prompt-turn-duration.spec.ts --project=mobile-chrome && pnpm run e2e:sleep-ratchetfixtures/backend.tsspawnsapps/backend/bin/kandev,build:e2eonly rebuilds the Vite bundle, andglobal-setup.tsfails fast when a backend artifact is missing or older than any file underapps/backend— somake -C apps/backend build+make -C apps/backend e2e-plugin-packageare MANDATORY prerequisites perapps/web/e2e/README.md, alongsidepnpm run build:e2e. PlaywrighttestDirise2e/tests;--project=chromiumis the desktop project,--project=mobile-chromeis Pixel 5. Each worker bootstraps backend+frontend, so allow several minutes.e2e:sleep-ratchetmust pass on both new specs — no new sleeps.) - Files likely touched:
apps/web/e2e/tests/task/prompt-turn-duration.spec.ts(new, desktop)apps/web/e2e/tests/task/mobile-prompt-turn-duration.spec.ts(new, mobile)- Possibly
apps/web/e2e/pages/session-page.tsif a row-hover helper is needed (prefer inline locators to avoid touching shared pages)
- Dependencies: task 02 (UI must render the duration).
- Parallelism: sequential.
- Inputs:
- Spec:
docs/specs/ui/requirements/prompt-turn-duration.md— golden-path scenario (hover reveal). - Plan:
docs/plans/prompt-turn-duration/plan.md— E2E Tests section. - Pattern:
apps/web/e2e/tests/task/prompt-history-panel.spec.ts(seeding, settle polling, sentinel message-id capture,session.activeChat()scoping withtoHaveCount(1),DURATION_SHAPEregex + comment);activeChat()ispage.locator("[data-testid='session-chat']:visible").first()(apps/web/e2e/pages/session-page.ts) because background dockview chat panels stay mounted. - Mobile:
apps/web/e2e/tests/task/mobile-prompt-history-panel.spec.ts(mobile-chrome project, Pixel 5, perapps/web/e2e/playwright.config.ts);pnpm run build:e2ebeforepnpm e2e:raw(perapps/web/e2e/README.md). - Row locator: the transcript's stable outer row is
#msg-<key>(apps/web/components/task/chat/message-list-native.tsx,MessageRow) — always scoped undersession.activeChat(), neverpage. There is NOchat-messagetest id/class to select — do not invent one. The duration is inside theMessageActionsrow of the user message; the fine-pointer reveal isopacity-based and coarse pointers useopacity-100at every width, so assertions use computed opacity, not visibility.
- Spec:
- Output contract: summary; files changed; test run result; blockers/risks; update task status and
plan.mdcheckbox.
Results
- TDD red:
cd apps/web && pnpm e2e:raw tests/task/prompt-turn-duration.spec.ts --project=chromiumfailed as expected when the outer#msg-<id>hover target did not activate the descendant.groupstate; the three retry traces are in the Playwright test-results directory. - Verification:
make fmt,make -C apps/backend build,make -C apps/backend e2e-plugin-package, andcd apps/web && pnpm run build:e2epassed. - Verification:
cd apps/web && pnpm e2e:raw tests/task/prompt-turn-duration.spec.ts --project=chromiumpassed: 1 test. - Verification:
cd apps/web && pnpm e2e:raw tests/task/mobile-prompt-turn-duration.spec.ts --project=mobile-chromepassed: 1 test. - Verification:
cd apps/web && pnpm run e2e:sleep-ratchetpassed. - Generated artifacts: production web bundle, backend binaries, plugin E2E package, and failed retry traces. Build outputs are required E2E artifacts; failed traces are retained for the recorded red run.
- Fixup verification:
make -C apps/backend build,make -C apps/backend e2e-plugin-package, andcd apps/web && pnpm run build:e2e— passed. The desktop duration E2E and the Pixel 5 mobile duration E2E passed; the mobile spec also verified a 700px coarse-pointer viewport.pnpm run e2e:sleep-ratchetpassed.