enterText method

Future<DriveStep> enterText(
  1. dynamic target,
  2. String text, {
  3. Duration? settle,
})

Focuses the field at target and sets text as one editing value, the way a widget reports an edit the user made.

Both halves have to be told, which is why this is not TextInput.updateEditingValue. That call is control-side: it pushes a value into the framework, and the platform's own editing state — the UITextField/InputConnection shadow the IME edits against — never hears about it. Measured on both an iOS simulator and an Android emulator (2026-08-11): after the agent wrote a sentence, the human's next keystroke on the soft keyboard replaced it, because as far as the platform knew the field was still empty. On a desktop with no soft keyboard this goes unnoticed; on a phone it breaks co-driving, which is the workflow this surface exists for.

EditableTextState.userUpdateTextEditingValue is the framework's own name for "a user edit that did not come from the platform": it runs the input formatters, fires onChanged, and — through endBatchEdit — calls setEditingState on the live input connection, so the IME's next edit is a delta against what is actually on screen. It needs the connection to exist, which is what EditableTextState.requestKeyboard below is for.

Implementation

Future<DriveStep> enterText(dynamic target, String text, {Duration? settle}) {
  return _act('enterText', target, settle, (finder) async {
    var elements = editableWithin(finder).evaluate().toList();
    if (elements.length != 1) {
      throw TargetError(
        TargetFailure.notFound,
        '${describeTarget(target)} contains ${elements.length} text fields, '
        'and `enterText` needs one.',
      );
    }
    var state =
        (elements.single as StatefulElement).state as EditableTextState;
    state.requestKeyboard();
    // Focus and the input connection apply over a frame; give them one.
    await lane.settle(const Duration(milliseconds: 100));
    state.userUpdateTextEditingValue(
      TextEditingValue(
        text: text,
        selection: TextSelection.collapsed(offset: text.length),
      ),
      SelectionChangedCause.keyboard,
    );
  });
}