Skip to content

fix: wait for a hittable key only when keyboardWaitTimeout is set - #93

Merged
KazuCocoa merged 3 commits into
appium:mainfrom
Mochxd:fix/ios-type-after-keyboard-opens
Oct 23, 2026
Merged

KazuCocoa merged 3 commits into
appium:mainfrom
Mochxd:fix/ios-type-after-keyboard-opens

Conversation

@Mochxd

@Mochxd Mochxd commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Refs #92

keyboardWaitTimeout is a number of seconds. 0 or null types with FBTypeText and does not look at the keyboard. A positive value waits up to that long for a hittable key, then sends the whole string once. If no key shows up, the string is still sent. A boolean or a negative number is invalid argument.

FBTypeText is unchanged. W3C key actions are unchanged. watchOS and tvOS are unchanged. /wda/keys uses the same wait.

FBTypingTest uses a storyboard text field. It checks that the string arrives. It does not reproduce the drop from #92, so it does not show that waiting before the string fixes it.

@github-actions github-actions Bot added bug Something isn't working package:webdriveragent labels Oct 18, 2026
@KazuCocoa

Copy link
Copy Markdown
Member

Could this occur all React Native Applications, or specific condition - depending on the app's implementation? This PR could add some kind of delay for all cases. If you really need this, probably adding this kind of wait config in the settings, and this behavior could occur only when the settings is configured

@Mochxd

Mochxd commented Oct 18, 2026

Copy link
Copy Markdown
Contributor Author

@KazuCocoa It has happened on the React Native apps I automate. The first sendKeys after the keyboard opens drops the characters that follow the first one. A native text field on the same simulator kept every character. Once a key was already hittable, the React Native field kept the full string too, so it is that opening window rather than every call.

The 0.5s wait here still runs for any iOS field when no key is hittable yet, not only React Native. I can put it behind a setting so the wait only happens when that setting is on.

@KazuCocoa

Copy link
Copy Markdown
Member

I have tested with RN app built with Codex but the reported behavior didn't occur. Perhaps we need a minimum reproducible app for testing, although, I'd go with the settings way as primary direction

@Mochxd

Mochxd commented Oct 18, 2026

Copy link
Copy Markdown
Contributor Author

@KazuCocoa The drop is not on every run. I have seen it on more than one simulator, and then the same field keeps the full string on the next try. A single run of a React Native app can miss it. That is why a small sample app is hard to pin down: the characters are lost only while the keyboard is still opening, and only sometimes.

I agree the wait should not be the default. I will put it behind a setting, off unless the session turns it on.

FBXCElementSnapshotWrapper *wrapped = [FBXCElementSnapshotWrapper ensureWrapped:snapshot];
#if TARGET_OS_IOS
NSError *visibilityError = nil;
BOOL keyboardWasVisible = [FBKeyboard waitUntilVisibleForApplication:self.application

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check runs on every iOS typing call, even when the keyboard is already up, and even for 1 character or a single emoji that never gets split.

It adds about 180 to 200 ms per call. FBTypingTest on iPhone 17 Pro sim, iOS 26.5, one run each:

Call main this PR
keyboard already up 418 ms 612 ms
keyboard up, clear then type 806 ms 971 ms

if (!FBTypeText([text substringWithRange:firstCharacter], typingSpeed, error)) {
return NO;
}
[NSThread sleepForTimeInterval:kFBTypingKeyboardSettleDelay];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The split path costs about 1 s per call, not 0.5 s. It pays for the keyboard check, a separate typing event for the first character, this sleep, and a second typing event.

Plain tap then type in a native text field hits it, because keys are not hittable yet right after the tap. FBTypingTest on iPhone 17 Pro sim, iOS 26.5, one run each:

Call main this PR
testTextTypingOnFocusedElement, first type after tap 409 ms 1419 ms
testTextTypingImmediatelyAfterFocus 620 ms 1512 ms
testTextTyping, no tap 777 ms 1688 ms

[self typeText:text];
return YES;
#elif TARGET_OS_IOS
return FBTypeTextLettingKeyboardSettle(text, frequency, keyboardWasVisible, error);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clear then type always takes the split path. The clear shortcut (on by default) sends a hardware clear key, and that moves the software keyboard off screen. The field keeps focus, but the keyboard check says not visible, and the keyboard does not come back. So the wait is for a keyboard that is not opening.

Repro on iPhone 17 Pro sim, iOS 26.5: tap field, type, fb_clearTextWithError:, then fb_typeText:

  • before clear: keyboard visible, frame y 583, field focused
  • right after clear: not visible, frame y 874 (off screen), field still focused
  • 2 s later: still y 874
  • next fb_typeText: took 1401 ms

// character after the first. Once a key is hittable the burst can be sent whole.
static const NSTimeInterval kFBTypingKeyboardSettleDelay = 0.5;

static BOOL FBTypeTextLettingKeyboardSettle(NSString *text,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only fb_typeText: gets this. FBTypeText itself is unchanged, and handleKeys: in FBElementCommands (/wda/keys) calls FBTypeText directly. So the driver keys command after a click still has the old behavior.

The PR description says FBTypeText now checks for a hittable key, which does not match the code.

XCTAssertEqualObjects(textField.value, expectedText);
}

- (void)testTextTypingImmediatelyAfterFocus

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test passes on main too (ran it on iPhone 17 Pro sim, iOS 26.5), so it does not catch #92 coming back. The field is a plain UIKit text field from the storyboard, not the React Native TextInput from #92.

@Mochxd Mochxd changed the title fix: type the remainder after the iOS keyboard has opened fix: wait for the iOS keyboard only when waitForKeyboardOpening is on Oct 20, 2026
@Mochxd

Mochxd commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

@KazuCocoa I changed the approach. The wait is no longer the default.

waitForKeyboardOpening is off unless the session turns it on. With it off, typing is the same as before: FBTypeText, no keyboard check, and no sleep.

With it on:

  • If this call focuses the field and no key is hittable yet, the first character is sent, then the rest after 0.5 seconds.
  • If the field already had focus, the agent waits up to 0.5 seconds for a key and then sends the whole string once. That covers click then setValue while the keyboard is still opening. If no key shows up, it is still one burst, so the clear shortcut does not wait for a keyboard that will not come back.
  • /wda/keys uses that already-focused path.

FBTypeText itself is unchanged. W3C key actions still use their own path.

What do you think?

@KazuCocoa KazuCocoa left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The opt-in direction makes sense. I would prefer a configurable keyboard wait timeout with a single, explicit behavior, while preserving the existing path when it is disabled. Details inline.

/*! The maximum typing frequency for all typing activities */
@property (atomic, assign) NSUInteger maxTypingFrequency;
/*! When YES, a type that starts while the keyboard is not up yet sends the first character, waits, then sends the rest. Default NO. */
@property (atomic, assign) BOOL waitForKeyboardOpening;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make this a numeric setting such as keyboardWaitTimeout (seconds), rather than a boolean tied to a fixed 0.5-second delay?

Suggested semantics:

  • null (default), or 0: disabled; no keyboard lookup or wait.
  • A positive number: wait for a hittable keyboard key before typing, returning from the wait as soon as one is available.
  • If the timeout expires, proceed with normal typing rather than failing the command.

Please document the units and reject negative or invalid values. I would avoid naming this a stroke interval: that suggests spacing between every character and overlaps with maxTypingFrequency.

&& text.length > 1
&& application != nil) {
NSError *visibilityError = nil;
if (!fieldAlreadyFocused) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use the configured timeout to wait before sending the whole string in both cases, instead of combining a fixed sleep after the first character with a pre-input wait depending on the previous focus state? That would give the setting one clear role and avoid splitting the text into two synthesized events.

This would change the workaround, so please verify in the reproducing app that waiting before the first character actually prevents the loss. Our minimal React Native test did not reproduce #92, so it cannot establish equivalence. If the pause specifically needs to come after the first character, that should instead be an explicitly named delay (for example, delayAfterFirstCharacter), not a keyboard wait timeout.

error:(NSError **)error
{
FBXCElementSnapshotWrapper *wrapped = [FBXCElementSnapshotWrapper ensureWrapped:snapshot];
BOOL fieldAlreadyFocused = wrapped.fb_hasKeyboardFocus;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please gate the extra work at the call sites when the setting is disabled. This added fb_hasKeyboardFocus evaluation runs unconditionally, and self.application is evaluated before entering FBTypeTextRespectingKeyboardOpening, so the guard inside that helper is too late to skip those operations.

The same applies to the newly added active-application lookup in handleKeys:. With the setting disabled, preserve the existing preparation/clear behavior and call FBTypeText directly without these additional lookups. If the timeout approach removes the need for the previous focus state, this extra focus evaluation can disappear altogether.

@Mochxd Mochxd changed the title fix: wait for the iOS keyboard only when waitForKeyboardOpening is on fix: wait for a hittable key only when keyboardWaitTimeout is set Oct 21, 2026
@KazuCocoa
KazuCocoa requested a review from harsha509 October 23, 2026 06:39

@harsha509 harsha509 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

@KazuCocoa
KazuCocoa merged commit 5a3eed4 into appium:main Oct 23, 2026
66 of 67 checks passed
@KazuCocoa

Copy link
Copy Markdown
Member

Please add it as documentation in the xcuitest driver

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working package:webdriveragent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants