Repository navigation
fix: wait for a hittable key only when keyboardWaitTimeout is set - #93
Conversation
|
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 |
|
@KazuCocoa It has happened on the React Native apps I automate. The first 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. |
|
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 |
|
@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 |
There was a problem hiding this comment.
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]; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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 |
|
@KazuCocoa I changed the approach. The wait is no longer the default.
With it on:
What do you think? |
KazuCocoa
left a comment
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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), or0: 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) { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
|
Please add it as documentation in the xcuitest driver |
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.