Skip to content

fix(ios): keep the runner session when keyboard return finds nothing focused - #4032

Merged
thymikee merged 3 commits into
callstack:mainfrom
okwasniewski:oskar/ios-keyboard-return-no-focus
Oct 29, 2026
Merged

thymikee merged 3 commits into
callstack:mainfrom
okwasniewski:oskar/ios-keyboard-return-no-focus

Conversation

@okwasniewski

@okwasniewski okwasniewski commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Keep the iOS runner session alive when keyboard enter encounters no keyboard focus. Contain only XCTest's no-focus refusal; replay other held issues through the existing recorder so real failures keep their normal handling. With one text input, tap it and submit through the application; with no input, return UNSUPPORTED_OPERATION.

The one-input regression now waits for its named field before sending Return. The zero-input case keeps its foreground-only wait.

3 files. Relative to merge base 3537dc549a: production +53/-25, tests +66/-0, docs 0. No scope expansion. Reported by react-native-pager-view CI; original live CLI evidence.

Validation

Tested head: 0e74498663b500107439d2bd69207ed43ff6898a.

  • Build, full-repository format, and XCTest-selection checks passed.
  • pnpm check:affected --run --head 0e74498663 passed: exactCommit=true, checkoutChanged=false.
  • Built the arm64 iOS runner with unit tests. On iOS 26.2, six focused XCTests passed with zero failures; xcodebuild exited 0. Includes one-input submission, zero-input refusal, and probe containment. Validation details.
  • Independent review of the full PR: no findings. All inline threads addressed.
  • Final-head lint/package, repo guards, Integration Tests, Coverage, Android, and Linux checks passed. Apple smoke is still running. Vercel requires team authorization for the fork deployment.

…focused

keyboard enter with nothing focused fell back to app.typeText, which XCTest
answers by recording 'Neither element nor any descendant has keyboard focus'
as an issue rather than throwing. The exception catcher never saw it, the
issue invalidated the runner session, and the command failed as
XCTEST_RECORDED_FAILURE. The fallback typing now runs with its issues held
back: that refusal reads as a return key that was not pressed, the single
text input fallback gets its turn, and every other issue is recorded as
before, since the return may have gone out.
Copilot AI balanced review requested due to automatic review settings October 28, 2026 16:52
@vercel

vercel Bot commented Oct 28, 2026

Copy link
Copy Markdown

@okwasniewski is attempting to deploy a commit to the Callstack Team on Vercel.

A member of the Team first needs to authorize it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 3 files

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

@thymikee

Copy link
Copy Markdown
Member

This fix is not ready yet: the new regression test cannot tell the two paths apart, and one of the fixes drops real runner failures. I reviewed commit 2f52996.

The other CI checks pass, and the failing Vercel check is a deployment authorization step for a fork PR. This diff only touches apple/runner Swift files, so that failure looks unrelated. There are no conflicts.

The regression test at RunnerTests+KeyboardTests.swift:278 launches a fixture with exactly one text input. After the no-focus refusal, singleTextEntryElement returns that input, so the fallback taps it and types return. The test expects UNSUPPORTED_OPERATION, so it passes only if the fallback also fails. Please pin each branch on a fixture that forces it: a screen with zero (or two or more) text inputs must give UNSUPPORTED_OPERATION and 0 recorded failures, and this one-input fixture must give ok:true and 0 recorded failures. The fixture's text-input count decides the branch, so each assertion should name the count it depends on.

typeKeyboardReturn at RunnerTests+Keyboard.swift:335 returns false without recording any held issue when one of them is the no-focus refusal. pressKeyboardReturn(on:) holds the tap and the typeText in one batch. A tap timeout or "Application X is not running" issue is dropped when typeText then reports no focus. The command then reports UNSUPPORTED_OPERATION and keeps the session on a hung or dead target. That is the class of issue RunnerTests.swift:262-267 says must keep driving XCTEST_RECORDED_FAILURE. Please record every held issue that is not the no-focus refusal, then return false if a no-focus issue was present, so only the classified refusal is ever dropped.

Before this PR, the single-text-entry fallback ran only after an ObjC exception, because the no-focus case was fatal first. Now the fallback is reachable for every unfocused keyboard enter on a screen with one text input: the runner taps the field and types return. That changes live device behavior and has no live evidence. The reported run names no screen. Please run keyboard enter on an iOS screen with exactly one unfocused text field and show ok:true, the field's submit action firing, and no XCTEST_RECORDED_FAILURE. Then run snapshot on the same runner and show that the log has no second AGENT_DEVICE_RUNNER startup. Please also name the app and screen for the zero-input route and show UNSUPPORTED_OPERATION, the AGENT_DEVICE_RUNNER_KEYBOARD_RETURN_NO_FOCUS log line, and a following snapshot on the same runner. I could not confirm that a Swift unit or iOS device lane ran on this fork PR, so please show the unit lane result for the new tests at this head.

The two open inline threads still apply: one-input fixture vs UNSUPPORTED_OPERATION and dropped tap issues.

Not blocking, and you can take or leave these: the TextInputProbeIssues scope in RunnerTests+TextInputProbe.swift now serves keyboard return too, so a held-issue name (or moving it next to record(_:)) would fit better; holdingRecordedIssues returns only a Bool, so the ObjC exception message the old code logged is lost and both sites share one tag; and isNoKeyboardFocusIssue matches XCTest's English wording like the AX precedent at RunnerTests.swift:264, so a doc line saying the wording is the only handle (or checking associatedError first, if set) would help.

I did not run the simulator or the XCTest lane. Whether the one-input fallback succeeds in the fixture is inferred from the code, and the author's live runs are taken from the PR body.

…eld, keep tap issues

Review on callstack#4032: record every held issue except the no-focus refusal, and
pin both fallback branches with fixtures whose text-input count decides
them. The single-input fallback typed into an index-bound element that the
raised keyboard re-pointed at its own window, so it never submitted; it now
types through the app into whatever the tap focused.
Copilot AI balanced review requested due to automatic review settings October 28, 2026 21:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@okwasniewski

Copy link
Copy Markdown
Contributor Author

Thanks, all three points held. Addressed in da54017.

Dropped issues: typeKeyboardReturn now records every held issue except the no-focus refusal, then returns false if that refusal was present. It logs the ObjC exception message again, and isNoKeyboardFocusIssue has a doc line saying XCTest's English wording is the only handle (same as the AX precedent).

The fallback was broken. singleTextEntryElement returns an index-bound element (descendants(.any)[n]). The tap raises the keyboard, which shifts those indices, so element.typeText resolved to the keyboard window and refused with "no focus" again (the XCUI log shows Event dispatch snapshot: Other). After the tap, the fallback now types through app.typeText into whatever the tap focused.

Tests: both branches are pinned, and each asserts its text-input count:

  • testKeyboardReturnWithNothingFocusedSubmitsTheSoleTextInput (count 1; regression fixture with soft-keyboard, submit-clears, long-prefill): ok, the field is cleared, 0 recorded failures.
  • testKeyboardReturnWithNothingFocusedAndNoTextInputFailsWithoutInvalidatingTheRunnerSession (count 0, default launch): UNSUPPORTED_OPERATION, 0 recorded failures.

Unit lane at da54017 (local; build:xcuitest:ios with unit tests, arm64, iOS 26.5 sim, test-without-building): both new tests, the classifier test, and the existing testTextInputProbe* / testHealthyCoordinateTapPreservesBareTypingWitness all pass. I don't think the fork PR runs the Swift unit lane, so this is local evidence only.

Live, CLI at da54017, one daemon/runner:

  • One unfocused input, com.callstack.agentdevice.runner opened with --launch-args text-entry-regression, soft-keyboard, submit-clears, long-prefill:
    • Before: @e3 [text-field] "agent-device-hardware-keyboard-input" [editable] [preview:"stale long value…"]
    • keyboard enter: ok, "Keyboard enter pressed".
    • After: @e3 [text-field] … [focused] [editable]. The value is gone, so the submit-clears action fired.
  • Zero inputs, com.pagerviewexample home screen (no text field, text view, or search field in the snapshot): keyboard enter gives UNSUPPORTED_OPERATION, and the next snapshot returns 28 visible nodes.
  • runner.log across both: 1 AGENT_DEVICE_RUNNER_HEADLESS_STARTUP, 2 AGENT_DEVICE_RUNNER_KEYBOARD_RETURN_NO_FOCUS (one per keyboard enter), 0 xctest_recorded_failure.
  • Focused Enter is unchanged: react-native-pager-view issue-1096-keyboard-shrink.e2e.ts passes 3/3.

pnpm check:affected --run --head HEAD passed at da54017 with exactCommit=true.

Not done: renaming the TextInputProbeIssues scope. It now serves both callers through withIssueScope/holdingRecordedIssues. Happy to rename it in this PR if you'd like.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 3 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

@thymikee

Copy link
Copy Markdown
Member

The earlier findings on this PR are fixed at da54017: the one-input fixture now expects ok and a cleared field, the zero-input launch expects UNSUPPORTED_OPERATION, and typeKeyboardReturn re-records every held issue except the no-focus one. The code looks good, and there are no conflicts. The only failing check is Vercel, a team deployment-authorization step for a fork PR. The diff touches only apple/runner Swift files, so it is likely unrelated.

Before merge, the open inline thread on the one-input test still stands (#4032 (comment)): KeyboardTests.swift now waits only for the app to run in the foreground, so a slow launch could give a spurious UNSUPPORTED_OPERATION. Please wait for agent-device-hardware-keyboard-input in the one-input test before keyboardReturn, and keep the foreground wait in the zero-input test. Or answer and resolve the thread. The two other threads, already resolved, are fixed at this commit: #4032 (comment) and #4032 (comment).

Not blocking, and fine to take or leave: withIssueScope and the held-issue list are now general ("hold this block's XCTest issues") and keyboard return uses them too, but the names still say text-input probe (TextInputProbeIssues, textInputProbeIssues, containTextInputProbeIssue at RunnerTests.swift:283). Neutral names such as HeldIssueScope, heldIssueScope and holdIssueInScope would match the generic AGENT_DEVICE_RUNNER_RECORDED_ISSUE_HELD marker (https://github.com/callstack/agent-device/blob/da54017/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextInputProbe.swift#L4).

I did not run the simulator, the runner unit lane, or the live CLI routes. The live and unit-lane results come from your comment 6481712767. The index-shift cause of the old element.typeText failure is your diagnosis from the XCUI log; I only checked that the head types through app.typeText after the tap. I also did not confirm whether XCTest raises an interruption after the no-focus issue when the record override does not call super, though your 0 recorded failures and no second runner startup suggest it does not.

@thymikee

Copy link
Copy Markdown
Member

overtaking this

Copilot AI balanced review requested due to automatic review settings October 29, 2026 13:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@thymikee

thymikee commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

Validated 0e74498663b500107439d2bd69207ed43ff6898a and pushed the remaining fixture-readiness fix.

  • Independent read-only review of 3537dc549a5edb248f918990a137c536fc46ee50..0e74498663b500107439d2bd69207ed43ff6898a: no findings.
  • pnpm install --frozen-lockfile, pnpm build, pnpm format, and pnpm check:xctest-selection: passed.
  • pnpm check:affected --run --head 0e74498663: passed; receipt run-TXl2IK, exactCommit=true, checkoutChanged=false. Native duplicate, XCTest selection, and packaged Swift source checks passed. The receipt correctly leaves Apple runtime and replay lanes to CI; it does not claim a local full-suite or coverage run.
  • Built this checkout's arm64 iOS XCTest runner with unit tests enabled, using Xcode 27.1 and simulator bench-2188-final / iOS 26.2 / 5FEADD02-98E4-4F01-861C-07003C1A3291.

The focused xcodebuild test-without-building selection ran these six tests:

  1. testKeyboardReturnWithNothingFocusedSubmitsTheSoleTextInput: one input, successful response, submit clears the prefilled field, zero recorded failures.
  2. testKeyboardReturnWithNothingFocusedAndNoTextInputFailsWithoutInvalidatingTheRunnerSession: zero inputs, UNSUPPORTED_OPERATION, zero recorded failures.
  3. testNoKeyboardFocusIssueIsOnlyXCTestsRefusalToTypeUnfocused.
  4. testTextInputProbePreservesEnclosingRunnerWait.
  5. testTextInputProbeIssueScopeIsThreadBound.
  6. testHealthyCoordinateTapPreservesBareTypingWitness.

The suite reported 6 tests and 0 failures in 20.929 seconds. The outer xcodebuild command exited 0 and the result bundle is complete. Xcode spent additional time collecting simulator diagnostics after the passing suite; no process was interrupted. Both keyboard cases logged AGENT_DEVICE_RUNNER_KEYBOARD_RETURN_NO_FOCUS from the same runner process, PID 13868. The existing CLI/session-survival evidence remains attributed to the author's run at da54017da in the earlier validation comment; this pass reran the production command through the runner's XCTest boundary. The simulator has been shut down to restore its original state.

Final diff relative to the merge base: 3 files; production +53/-25, tests +66/-0, docs 0. The only addition since da54017da is the test-readiness fix (+9/-2). All inline review threads are addressed. Final-head hosted CI remains a separate obligation; Vercel currently fails on fork deployment authorization.

@thymikee

Copy link
Copy Markdown
Member

The earlier findings from da54017 (#4032 (comment)) are fixed at 0e74498, and I found no new problems. The one-input test now waits for the text field before it sends keyboardReturn (RunnerTests+KeyboardTests.swift lines 260-290), so the flake source is gone and the runner session stays alive when nothing is focused. The cubic-dev-ai P2 thread on that wait is fixed at this head and already resolved. The only check that is not passing is the Vercel authorization check. It is a deployment permission gate for a cross-repo fork, and this diff touches only the Swift runner and its unit test, so it looks unrelated. I did not run the iOS XCTest suite or any device run, so the claim that the wait removes the flake rests on reading the code and the author's reported local validation. There are no conflicts.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 29, 2026
@thymikee
thymikee merged commit 3b9e8d1 into callstack:main Oct 29, 2026
11 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants