ReviewOS

stacks/very-happy-dom

Locator form-control actions missing: selectOption, press, clear, dblclick, setInputFiles

#1608
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Problem

Five locator actions are missing, and the DOM underneath each one already exists:

const loc = page.locator('select')
typeof loc.selectOption    // 'undefined'   — HTMLSelectElement + selectedIndex exist
typeof loc.press           // 'undefined'   — page.keyboard.press exists
typeof loc.clear           // 'undefined'   — _fillElement exists
typeof loc.dblclick        // 'undefined'
typeof loc.setInputFiles   // 'undefined'   — File and FileList are exported

page has no selectOption, press, check, uncheck or setInputFiles either, so there is no selector-level fallback for most of them.

wildloop's specs: selectOption 4, setInputFiles 3, press 2.

Why it matters

<select> and <input type=file> are the two controls with no reasonable hand-rolled substitute. Setting select.value directly does not fire change, so a component listening for it never updates, and the test passes against a DOM the app never saw. The same is true of assigning to input.files — it needs a FileList, which is why setInputFiles exists.

press matters for the keys that are behaviour rather than text: Enter to submit, Escape to close, Tab to move focus, ArrowDown through a combobox. page.keyboard.press can reach them, but only against whatever has focus, so the call site becomes focus-then-press and the focus half is easy to get wrong.

clear() and dblclick() are conveniences — fill('') and two click()s cover them — but they are cheap and their absence is noticed immediately.

Proposed scope

  • locator.selectOption(value | { label } | { index } | { value } | array) — set selected on the matching <option>s, update selectedIndex, and fire input then change, in that order. Multiple values only for a multiple select; throw otherwise rather than silently taking the last.
  • locator.press(key, { delay }) — focus the resolved element, then route through the existing page.keyboard so the key parsing lives in one place. Accept the Control+Enter modifier form.
  • locator.clear() — fill(''), so it inherits whatever events _fillElement fires.
  • locator.dblclick() — two clicks plus a dblclick event, which is the part two click()s does not give.
  • locator.setInputFiles(path | paths | { name, mimeType, buffer }) — build a FileList from the existing File implementation, assign it, fire input and change. Reading from disk needs a decision: Playwright takes paths, and taking only the in-memory form would be a quieter API but a different one.
  • The corresponding page.selectOption(selector, ...) etc. for parity with the other page-level actions.

Acceptance

  • selectOption fires input and change, asserted with listeners, and a component reading event.target.value sees the new value.
  • selectOption on a single-value select rejects an array.
  • press('Enter') on an input inside a form triggers submit; press('Escape') reaches a keydown listener on the element.
  • setInputFiles leaves input.files a FileList whose item(0).name matches, and fires change.
  • clear() empties a filled input and fires the same events fill('') does.

Found while assessing whether wildloop's Playwright specs could port here.

Sign in to comment on this issue.