Skip to content

test(ios): reproduce #4394 keyboard opens on right-edge tap of non-cursor thought#4534

Draft
ethan-james wants to merge 11 commits into
cybersemics:mainfrom
ethan-james:ethan-james-fix-caret-focus-wdio-test
Draft

test(ios): reproduce #4394 keyboard opens on right-edge tap of non-cursor thought#4534
ethan-james wants to merge 11 commits into
cybersemics:mainfrom
ethan-james:ethan-james-fix-caret-focus-wdio-test

Conversation

@ethan-james

@ethan-james ethan-james commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

#4394

Summary

The iOS caret test 'Focus is prevented after clearing the cursor' was tapping the right edge with a synthetic point-tap. A zero-radius synthetic tap never triggers iOS Safari's touch-adjustment ("fuzzy tapping") retargeting, so the bug could never reproduce and the test always passed — it did not actually guard against #4394.

This PR reworks the test to faithfully reproduce #4394's exact steps on a non-cursor thought:

  1. Create thought Hello
  2. Dismiss the keyboard
  3. Cursor Back (swipe right) to set the cursor to null — so Hello becomes a non-cursor thought (verified editing === null before the tap)
  4. Tap ~4px past the right edge, vertically centered, using a finger-sized contact area (width/height/pressure)

Mechanism

The finger-sized contact area triggers iOS Safari's touch-adjustment heuristic:

touchstart (…) tgt=DIV#thought-annotation   ← touch lands on the annotation overlay
touchend   (…) tgt=DIV#thought-annotation   ← editable's onTouchEnd never fires → no preventDefault
mousedown  (…) tgt=DIV[EDITABLE]            ← Safari retargets the synthesized mouse events into the editable
focusin        tgt=DIV[EDITABLE]            ← focus proceeds
→ *** KEYBOARD OPEN ***

Because the touch events are dispatched on the annotation (not the editable), the editable's onTouchEnd never runs to preventDefault; Safari then redirects the mouse events into the editable and focus proceeds, so the virtual keyboard incorrectly opens.

Expected outcome

This test fails, demonstrating #4394 (isKeyboardShown() returns true for a non-cursor thought). It will pass once focus is correctly prevented for non-cursor thoughts.

Environment: why it runs on iOS 18 only

The touch-adjustment retargeting that #4394 depends on is iOS-version-specific. The bug reproduces on iOS 18 (verified on both an iPhone 16 Pro Max simulator and BrowserStack), but not on the suite's default BrowserStack device (iPhone 15 Plus, iOS 17) — there the synthesized tap is not retargeted, so the keyboard stays down and the test passes without exercising the bug.

To reproduce the bug in CI without moving the whole suite to a newer OS, the regression is isolated into its own spec file (src/e2e/iOS/__tests__/caretFocus.ts) and BrowserStack is configured with two capabilities:

  • iPhone 15 Plus / iOS 17 — runs the entire suite except caretFocus.ts (unchanged from before).
  • iPhone 16 Pro Max / iOS 18 — runs only caretFocus.ts.

This scopes the intended failure to a single spec and keeps the rest of the iOS suite on its original device/OS.

Notes

  • Native priming tap. A priming step (a real native tap on the thought's center, then keyboard dismissal) is required so the synthetic edge-touch reliably wins the timing race that a real finger triggers on its own. It uses a native performActions tap rather than a webview element.click() because on real hardware only a native touch focuses the editable and opens the keyboard (a webview click does not, which previously caused the priming's keyboard dismissal to fail on BrowserStack).
  • No releaseActions() is called after performActions — Safari/XCUITest does not support the DELETE /actions endpoint (the gesture helper avoids it for the same reason).
  • getElementRectByScreen computes the status-bar offset per-device, so the tap coordinates map correctly across the simulator and BrowserStack hardware.

ethan-james and others added 2 commits July 6, 2026 10:04
…p in caret test

The 'Focus is prevented after clearing the cursor' test tapped the right
edge with a synthetic point-tap, which never triggered iOS Safari's
touch-adjustment retargeting, so the bug could not reproduce and the test
always passed.

Rework the test to the exact cybersemics#4394 repro on a non-cursor thought:
- create "Hello", dismiss the keyboard, Cursor Back (swipe right) to null
  the cursor so "Hello" becomes a non-cursor thought
- tap ~4px past the right edge, vertically centered, with a finger-sized
  contact area (width/height/pressure)

The finger-sized contact triggers Safari's touch-adjustment: the touch
lands on the #thought-annotation overlay (so the editable's onTouchEnd
never runs to preventDefault), while the synthesized mousedown is
retargeted into the editable, so focus proceeds and the virtual keyboard
incorrectly opens.

This test now fails on this branch, faithfully demonstrating cybersemics#4394. It
will pass once focus is correctly prevented for non-cursor thoughts.

cybersemics#4394

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Vercel preview: https://em-h76m27yqw-cybersemics.vercel.app

The clickThought priming was tuned to the local simulator's timing race and
crashed on BrowserStack real hardware (the priming tap did not reopen the
keyboard, so the follow-up Done dismissal found no button). Strip the priming
so the test follows cybersemics#4394's literal repro steps, which real hardware should
reproduce like a real finger.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…S 18

BrowserStack Safari 17 did not trigger the touch-adjustment retargeting that
cybersemics#4394 depends on, so the synthetic fat-tap passed. Match the local simulator
environment (iPhone 16 Pro Max, iOS 18) where the bug provably reproduces, and
restore the clickThought priming that the reproduction requires.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ad of webview click

On BrowserStack real devices a webview element.click() (clickThought) does not
focus the editable or open the keyboard, so the priming's follow-up Done
dismissal crashed with 'element wasn't found'. Replace the priming with a native
performActions tap on the thought's center, which focuses the editable and opens
the keyboard on real hardware, then restore the webview context for the
subsequent cursorBack + edge-tap steps.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Safari/XCUITest does not support the DELETE /actions endpoint that
browser.releaseActions() calls, which aborted the test right after the native
priming tap. performActions does not require an explicit release here (the
gesture helper avoids it for the same reason).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…capability

The regression only reproduces on iOS 18's Safari touch-adjustment heuristic.
Move the test into its own spec file (caretFocus.ts) and give BrowserStack two
capabilities: the existing iOS 17 device runs the whole suite except this spec,
and a new iOS 18 device (iPhone 16 Pro Max) runs only this spec. This scopes the
intended failure and keeps the rest of the suite off iOS 18.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Replace hideKeyboardByTappingDone (which depends on em's Done accessory,
absent for a bare contentEditable) with a webview-context blur, and make
nativeTap restore the webview context in a finally so a failed tap can't
strand the session in NATIVE_APP and break the next test.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant