press_key double-types printable characters via duplicate keyDown+char events
Problem
Calling press_key (or fill_input / type_text which use it) with a single printable character inserts that character twice into framework-controlled inputs (React, Vue, Ember).
Example:
from browser_harness.helpers import fill_input
fill_input("#email", "test1@propxchain.com")
# Read back: document.querySelector("#email").value
# Expected: "test1@propxchain.com"
# Actual: "tteesstt11@@pprrooppxxcchhaaiinn..ccoomm"
Discovered while building an e2e test suite against a React/Supabase consumer app (PropXchain) — the password-reset email field couldn't be filled correctly, the form submitted with an invalid address and was silently accepted by the backend.
Minimal reproduction
# Inside `browser-harness` against any page with a text <input>
press_key("a")
# Then check the focused input: it now contains "aa", not "a"
Also reproduces via fill_input("#anyInput", "abc") → value becomes "aabbcc".
Root cause
In src/browser_harness/helpers.py, press_key() fires two events that both insert the character:
def press_key(key, modifiers=0):
vk, code, text = _KEYS.get(key, (ord(key[0]) if len(key) == 1 else 0, key, key if len(key) == 1 else ""))
base = {"key": key, "code": code, "modifiers": modifiers,
"windowsVirtualKeyCode": vk, "nativeVirtualKeyCode": vk}
cdp("Input.dispatchKeyEvent", type="keyDown", **base,
**({"text": text} if text else {})) # ← inserts char
if text and len(text) == 1:
cdp("Input.dispatchKeyEvent", type="char", text=text,
**{k: v for k, v in base.items() if k != "text"}) # ← inserts char AGAIN
cdp("Input.dispatchKeyEvent", type="keyUp", **base)
When text is included on the keyDown event, Chrome's Input.dispatchKeyEvent inserts the character into the focused input. The subsequent char event then inserts it a second time.
Per the CDP Input.dispatchKeyEvent docs and how Puppeteer/Playwright implement the same operation, the standard pattern for typing a printable character is:
keyDown without text — fires the keydown event for listeners
char with text — this is the event that actually inserts the character
keyUp — fires the keyup event
Setting text on both keyDown and char produces double-insertion.
Proposed fix
Drop text from the keyDown event when a printable char event will follow:
def press_key(key, modifiers=0):
vk, code, text = _KEYS.get(key, (ord(key[0]) if len(key) == 1 else 0, key, key if len(key) == 1 else ""))
base = {"key": key, "code": code, "modifiers": modifiers,
"windowsVirtualKeyCode": vk, "nativeVirtualKeyCode": vk}
is_printable = text and len(text) == 1
# keyDown — never carry text when a char event will fire; only attach text
# for special keys (Enter/Tab) where there is no char event.
keydown_extras = {} if is_printable else ({"text": text} if text else {})
cdp("Input.dispatchKeyEvent", type="keyDown", **base, **keydown_extras)
if is_printable:
cdp("Input.dispatchKeyEvent", type="char", text=text, **base)
cdp("Input.dispatchKeyEvent", type="keyUp", **base)
Listeners watching keydown events still see e.key, e.code, e.keyCode (those come from key/code/windowsVirtualKeyCode, not text). The text field is what causes Chrome to insert the character — and only the char event needs it.
Workaround in user code
For React/Vue/Angular inputs, set the value via the native setter then fire input+change:
js(f"""
const el = document.querySelector({selector!r});
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set;
setter.call(el, {value!r});
el.dispatchEvent(new Event('input', {{bubbles: true}}));
el.dispatchEvent(new Event('change', {{bubbles: true}}));
""")
This bypasses press_key entirely. Works reliably on React/Supabase auth forms.
Why this matters
fill_input is documented (and shipped as a fix for #257) as the way to fill framework-managed inputs. With this bug, every flow that types into an input gets corrupted data. Login forms, search boxes, signup flows — all affected.
We discovered this while building an e2e test suite against a real React/Supabase app. The first password-reset flow failed silently — the backend's "Check your email" response is identical whether the submitted email is real or garbage, so the broken value was invisible until we read back input.value.
Environment
browser-harness v0.1.0 (editable install from main, commit at the time of report)
- Chrome 147.0.7727.116
- WSL2 Ubuntu, Linux 5.15.167.4-microsoft-standard
Searched existing issues
Checked open + closed issues for press_key, type_text, fill_input, double, doubled, character, duplicate keyDown — found #257 (the closed feature-request that introduced fill_input) but no existing report of the doubling bug.
press_key double-types printable characters via duplicate keyDown+char events
Problem
Calling
press_key(orfill_input/type_textwhich use it) with a single printable character inserts that character twice into framework-controlled inputs (React, Vue, Ember).Example:
Discovered while building an e2e test suite against a React/Supabase consumer app (PropXchain) — the password-reset email field couldn't be filled correctly, the form submitted with an invalid address and was silently accepted by the backend.
Minimal reproduction
Also reproduces via
fill_input("#anyInput", "abc")→ value becomes"aabbcc".Root cause
In
src/browser_harness/helpers.py,press_key()fires two events that both insert the character:When
textis included on thekeyDownevent, Chrome'sInput.dispatchKeyEventinserts the character into the focused input. The subsequentcharevent then inserts it a second time.Per the CDP Input.dispatchKeyEvent docs and how Puppeteer/Playwright implement the same operation, the standard pattern for typing a printable character is:
keyDownwithouttext— fires the keydown event for listenerscharwithtext— this is the event that actually inserts the characterkeyUp— fires the keyup eventSetting
texton bothkeyDownandcharproduces double-insertion.Proposed fix
Drop
textfrom thekeyDownevent when a printablecharevent will follow:Listeners watching
keydownevents still seee.key,e.code,e.keyCode(those come fromkey/code/windowsVirtualKeyCode, nottext). Thetextfield is what causes Chrome to insert the character — and only thecharevent needs it.Workaround in user code
For React/Vue/Angular inputs, set the value via the native setter then fire
input+change:This bypasses
press_keyentirely. Works reliably on React/Supabase auth forms.Why this matters
fill_inputis documented (and shipped as a fix for #257) as the way to fill framework-managed inputs. With this bug, every flow that types into an input gets corrupted data. Login forms, search boxes, signup flows — all affected.We discovered this while building an e2e test suite against a real React/Supabase app. The first password-reset flow failed silently — the backend's "Check your email" response is identical whether the submitted email is real or garbage, so the broken value was invisible until we read back
input.value.Environment
browser-harnessv0.1.0 (editable install from main, commit at the time of report)Searched existing issues
Checked open + closed issues for
press_key,type_text,fill_input,double,doubled,character,duplicate keyDown— found #257 (the closed feature-request that introducedfill_input) but no existing report of the doubling bug.