Clear and Type Into a React or Angular Input Reliably
Replace the value of a framework-controlled input when clear() does not stick, using select-all plus type and the native setter fallback.
Problem
clear() empties the field but the framework restores the old value on the next render, or clear() followed by sendKeys results in the new text appended to the old. Controlled inputs re-render from state, and clear() does not always fire the events that update that state.
Solution
Select all and type over it, which is what a user does and always fires input events. Keep a setter-based fallback for widgets that block keyboard selection.
public static void replaceText(WebDriver driver, WebElement input, String text) { input.click(); String modifier = System.getProperty("os.name").toLowerCase().contains("mac") ? Keys.COMMAND : Keys.CONTROL; input.sendKeys(Keys.chord(modifier, "a"), Keys.DELETE); input.sendKeys(text);
// Verify; fall back to the native setter if the framework fought back if (!text.equals(input.getDomProperty("value"))) { ((JavascriptExecutor) driver).executeScript(""" const el = arguments[0]; Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set.call(el, arguments[1]); el.dispatchEvent(new Event('input', {bubbles: true})); el.dispatchEvent(new Event('change', {bubbles: true})); """, input, text); }}import platform
def replace_text(driver, element, text): element.click() modifier = Keys.COMMAND if platform.system() == "Darwin" else Keys.CONTROL element.send_keys(modifier + "a", Keys.DELETE) element.send_keys(text)
if element.get_property("value") != text: driver.execute_script(""" const el = arguments[0]; Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set.call(el, arguments[1]); el.dispatchEvent(new Event('input', {bubbles: true})); el.dispatchEvent(new Event('change', {bubbles: true})); """, element, text)async function replaceText(driver, input, text) {await input.click();const modifier = process.platform === 'darwin' ? Key.COMMAND : Key.CONTROL;await input.sendKeys(Key.chord(modifier, 'a'), Key.DELETE);await input.sendKeys(text);
if ((await input.getProperty('value')) !== text) { await driver.executeScript(` const el = arguments[0]; Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set.call(el, arguments[1]); el.dispatchEvent(new Event('input', {bubbles: true})); el.dispatchEvent(new Event('change', {bubbles: true})); `, input, text);}}public static void ReplaceText(IWebDriver driver, IWebElement input, string text){ input.Click(); var modifier = OperatingSystem.IsMacOS() ? Keys.Command : Keys.Control; input.SendKeys(modifier + "a" + Keys.Delete); input.SendKeys(text);
if (input.GetDomProperty("value") != text) { ((IJavaScriptExecutor)driver).ExecuteScript(@" const el = arguments[0]; Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set.call(el, arguments[1]); el.dispatchEvent(new Event('input', {bubbles: true})); el.dispatchEvent(new Event('change', {bubbles: true}));", input, text); }}Why It Works
Select-all plus Delete plus typing generates the same keydown, beforeinput, input sequence a user generates, so the frameworkâs onChange runs for every character and state stays in sync. The verification step catches the rare widget that intercepts keyboard shortcuts, and the setter fallback updates state through the property React and friends observe.
Gotchas
- On a Grid the browser OS decides the modifier key, not the runner; read
platformNamefrom capabilities if runner and browser differ. Keys.chordis Java-only; other bindings concatenate the modifier and the letter.- Inputs with masks (phone numbers, currency) may reformat while typing; assert on the final formatted value rather than the raw text you sent.