Skip to content

feat: page interaction verbs β€” type, hover, press, drag, scroll with receipt evidence and assertable interaction counts - #2

Merged
thejackshelton merged 3 commits into
mainfrom
feat/page-interaction-verbs
Aug 22, 2026
Merged

feat: page interaction verbs β€” type, hover, press, drag, scroll with receipt evidence and assertable interaction counts#2
thejackshelton merged 3 commits into
mainfrom
feat/page-interaction-verbs

Conversation

@thejackshelton

Copy link
Copy Markdown
Contributor

Extends the click primitive to the other ways a user makes UI state: type, hover, press, drag, and scroll.

The verbs

  • type β€” per-character key events pipelined on the session socket, or a single insertText. Typed text is redacted by default (textLength stays honest); password fields are always redacted, even with redact: false. Ctrl/Meta chords send no text, so Chrome cannot both insert the character and fire the shortcut.
  • hover β€” pointer move onto the element.
  • press β€” key presses including modifier chords.
  • drag β€” a pipelined pointer sequence with interpolated moves, because drag libraries only track past a movement threshold. The target probe runs after the source probe, since scrollIntoView moves coordinates. Native HTML5 drag-and-drop is documented as out of scope, with the Input.setInterceptDrags follow-up path noted.
  • scroll β€” wheel semantics (+y = down) with an event-driven confirmation that something actually scrolled. gesture: true uses Input.synthesizeScrollGesture, with the spec's inverted sign normalized inside the driver.

Receipt evidence

Every verb records evidence on PageRecord.interactions under the driver witness and never asserts β€” assertions stay in expect.page.*. The interaction is a fact on the receipt whether or not anything is claimed about it.

expect.page.outcome gains exact per-verb interaction counts, which makes the recorded interaction evidence itself assertable: a box can state how many times it typed, hovered, pressed, dragged, or scrolled, and that claim lands on the record as a named assertion rather than as prose.

Spec, README, and changelog updates ride the same change.

Commits

  • fbefac9 β€” type, hover, and press interaction verbs with receipt evidence
  • 83b86de β€” drag and scroll verbs plus assertable interaction counts

Possible follow-ups

Observations from consuming this API in a downstream verification suite (a browser suite driving built pages over witness's own CDP driver). These are notes, not commitments β€” nothing beyond the two commits above ships in this PR.

  • An expect.network assertion verb. expect covers DOM and page evidence; there is no network equivalent, so every network claim in that suite is a hand-rolled networkRequests() diff raised through a local assert() helper. It lands in the receipt as a box error rather than a named, passing assertion, which under-states what the box actually checked. Something shaped like expect.network(page, { since, requested, notRequested, maxBytes }) would put those claims on the record.
  • A network-idle primitive. "Nothing new was fetched" is only meaningful against a settled network. That suite samples networkRequests() until the count holds still; a first-class page.waitForNetworkIdle() (or a settled option on the assertion above) would remove the hand-rolled loop.
  • data:-URL filtering in network capture. CDP reports inline assets through the same Network.* events, so an inline SVG in a stylesheet shows up as a 0-byte "request" as soon as it renders. Boxes filter by scheme themselves today; a scheme- or resourceType-aware filter in the handle would be kinder.
  • Viewport control. BrowserVisitOptions carries only networkConditions. Nothing in that suite needed a fixed viewport β€” witness scrolls elements into view before acting β€” but a layout-sensitive suite has no knob.
  • browser.visit() dev-server behavior for absolute URLs. It always starts the project's dev server, even when every route visited is an absolute external URL. Absolute URLs do pass through cleanly (new URL(route, baseUrl)), so this is a small cost rather than a blocker, but it forces a placeholder config to exist.

πŸ€– Generated with Claude Code

Extends the click primitive to the other ways a user makes UI state:
typing (per-character key events pipelined on the session socket, or one
insertText), hovering, and key presses including modifier chords. Every
verb records pages[].interactions evidence under the driver witness and
never asserts β€” assertions stay in expect.page.*.

Typed text is redacted by default (textLength stays honest); password
fields are always redacted, even with redact: false. Ctrl/Meta chords
send no text so Chrome cannot both insert the character and fire the
shortcut. Drag and scroll interaction kinds and their timeline witness
mappings are registered now; their drivers land in a follow-up.
drag dispatches a pipelined pointer sequence with interpolated moves
(drag libraries only track past a movement threshold); the target probe
runs after the source probe since scrollIntoView moves coordinates.
Native HTML5 drag-and-drop is documented as out of scope with the
Input.setInterceptDrags follow-up path.

scroll uses wheel semantics (+y = down) with an event-driven
confirmation that something actually scrolled; gesture: true uses
Input.synthesizeScrollGesture with the spec's inverted sign normalized
inside the driver.

expect.page.outcome gains exact per-verb interaction counts, making the
recorded interaction evidence itself assertable. Spec, README, and
changelog updates ride the same change.
@thejackshelton
thejackshelton merged commit 46dd71c into main Aug 22, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant