Skip to content

feat(playwright): form-interaction primitives (doubleClick, check/uncheck, selectOption, upload) + MCP tools#43

Merged
totigm merged 2 commits into
mainfrom
feat/form-primitives
Jun 1, 2026
Merged

feat(playwright): form-interaction primitives (doubleClick, check/uncheck, selectOption, upload) + MCP tools#43
totigm merged 2 commits into
mainfrom
feat/form-primitives

Conversation

@totigm

@totigm totigm commented May 31, 2026

Copy link
Copy Markdown
Owner

What & why

Real flows — forms, checkout, settings — hit checkboxes, dropdowns, double-clicks, and file inputs. Until now those weren't in @humanjs/playwright, so anyone running a real flow had to drop back to raw Playwright exactly where the humanization matters. This closes that gap.

New primitives (@humanjs/playwright)

  • human.doubleClick(target) — same humanized approach as click() (Bezier path, hover dwell, near-miss), double-click dispatch. Reuses executeClick via a clickCount option; the single-click path is unchanged.
  • human.check(target) / human.uncheck(target) — tick/untick a checkbox or radio. Clicks only when the state needs to change (a real user doesn't re-click a ticked box) and verifies the result. Tolerates non-checkable <label>/role targets (toggles without verifying). Radios can't be unchecked by clicking → clear error.
  • human.selectOption(target, values) — native <select>: cursor moves to the dropdown, then the value is set (firing input/change). Returns the selected values.
  • human.upload(target, files) — file input: cursor moves to the control, then setInputFiles. Never opens the OS dialog (would hang); tolerates hidden inputs behind styled buttons.

Parity (the "mirror the MCP surface" rule)

  • MCP: human_doubleClick, human_check, human_uncheck, human_selectOption, human_upload.
  • Recorder codegen: all five export via toPlaywright / toHumanJS.
  • Skill: added to the primitives reference in skill-body.md.
  • Core: matching KnownActionType entries so plugins observe them.
  • Docs: CLAUDE.md API shape + README quick-start.

Tests

Colocated forms/forms.test.ts covers humanized + instant paths, the idempotency guard (no click when already in state), state verification (throws when a toggle doesn't take), hidden-input tolerance for upload, and the clickCount: 2 dispatch for doubleClick. Full gate green: lint, typecheck (9/9), tests (222+), build, check:exports (publint, 11/11).

Changeset

minor for @humanjs/{playwright,mcp,core}, patch for @humanjs/skill.

🤖 Generated with Claude Code

Adds doubleClick, check/uncheck, selectOption, and upload to
@humanjs/playwright so real flows (forms, checkout, settings) stay
humanized instead of falling back to raw Playwright.

- doubleClick reuses executeClick via a clickCount option (same approach
  motion as click; single-click dispatch is unchanged).
- check/uncheck click only when the state needs changing, then verify;
  tolerate non-checkable label/role targets.
- selectOption moves the cursor to the <select> then sets the value.
- upload moves to the control then setInputFiles (no OS dialog; tolerates
  hidden inputs).

Each is mirrored as an MCP tool, exported by the recorder codegen
(toPlaywright/toHumanJS), documented in the skill primitives table, and
backed by new KnownActionType entries in @humanjs/core. Colocated tests
cover humanized + instant paths and the idempotency/verify behavior.
@vercel

vercel Bot commented May 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
humanjs Ready Ready Preview, Comment Jun 1, 2026 5:10am

Address branch-review findings on the form-primitives PR:

- human_upload now reads files by basename from HUMANJS_UPLOAD_DIR
  (resolveUploadPath, mirroring the output-file path-safety model) —
  ../, subdirectories, and absolute paths are rejected, so a prompt-
  injected path can't read+exfiltrate arbitrary local files to a form.
  Documented in the MCP README security section + env table.
- check/uncheck tool docs drop the "or its label" hint and point at the
  checkbox input / [role=checkbox] so state is readable and the click
  stays idempotent (a label target can't be made idempotent).
- selectOption tolerates a hover failure (no visible control) and lets
  selectOption raise the authoritative error instead of "Cannot hover".

Adds resolveUploadPath tests; fixes the McpEnv test mock for uploadDir.
@totigm totigm merged commit a0a11c4 into main Jun 1, 2026
6 checks passed
@totigm totigm deleted the feat/form-primitives branch June 1, 2026 14:54
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