test(ios): repro test for setting caret on adjacent thought (#4173)#4538
Draft
ethan-james wants to merge 14 commits into
Draft
test(ios): repro test for setting caret on adjacent thought (#4173)#4538ethan-james wants to merge 14 commits into
ethan-james wants to merge 14 commits into
Conversation
Adds an iOS/wdio regression test that attempts to reproduce #4173, where tapping an adjacent thought within ~1s of a previous tap fails to move the cursor. Introduces a `doubleTap` touch helper that fires two real touch taps with a deterministic sub-second interval, since a hand-driven repro cannot reliably tap fast enough. The bug does not reproduce in the local iOS Simulator via WebDriver touch injection; this test is intended to run on a real BrowserStack device in CI to determine whether the failure reproduces on real hardware. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
|
|
Adopts the touch techniques proven in #4407 to give the #4173 repro a real chance of failing on hardware: - doubleTap now dispatches from the NATIVE_APP context using a finger-sized contact area (width/height/pressure). A zero-radius webview-context tap does not trigger Safari's touch-adjustment / rapid-tap focus handling and cannot reproduce the bug. Both element centers are resolved in the webview context before switching, so the sub-second interval stays deterministic. - Moves the #4173 test into its own spec (caretAdjacentTap.ts) pinned to a dedicated iOS 18 capability (iPhone 16 Pro Max), mirroring #4407's split. The retargeting behavior fires on iOS 18 but not the suite's default iOS 17, which is why the earlier iOS 17 run gave a false pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The iPhone 16 has no Force Touch hardware, so the `pressure: 0.9` field made WebDriverAgent attempt a force-press and fail the whole tap with "This device does not support force press interactions". Keep the finger-sized contact radius (width/height), which is the part that drives Safari's touch-adjustment retargeting; pressure is not needed for it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Parametrizes the adjacent-tap spec over a range of inter-tap intervals (120/200/300/500/800ms) so a single BrowserStack session probes the whole window. #4173 occurs "within 1 second" of the first tap and may only surface for a subset of intervals near WebKit's double-tap detection threshold. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Switches the adjacent-tap repro from performActions HID synthesis to Appium's `mobile: tap`, following the established showEditMenu.ts pattern: a native tap issued from the webview context with getBoundingClientRect coordinates is processed through WebKit's touch/gesture recognizers - the path #4173 depends on. Sweeps the inter-tap interval and logs the measured gap. Removes the now unused doubleTap performActions helper. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
mobile: tap without an element uses absolute screen coordinates, so the previous getBoundingClientRect (webview CSS) coords landed above the thoughts in the Safari chrome and the taps missed (cursor stayed on the post-paste thought c). Switch to getElementRectByScreen, which adds the native Safari content offset - the same screen-coordinate space that landed correctly with performActions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The previous run passed at every interval, but the logged a->b gap was ~3s even at interval 0ms: getElementRectByScreen switches to the NATIVE_APP context and back, and doing that for b *between* the two taps injected ~3s of latency - pushing the second tap far outside #4173's ~1s window, so the bug could never surface. Prime the keyboard with an initial tap on a (which also settles the layout that opening the keyboard shifts), then pre-resolve BOTH screen coordinates up-front, then fire the measured rapid sequence (tap a, pause interval, tap b) with no context switch in between. Now the a->b gap reflects only intervalMs plus a single mobile:tap execution, actually exercising the sub-second window. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The pre-resolve variant hit sub-second gaps but every tap missed (cursor stuck on c): the two back-to-back NATIVE_APP context switches used to resolve the screen coordinates disturbed the webview/keyboard state, so the coordinates went stale. The Safari webview container's screen offset is constant, so read it once via a single native switch, then compute each editable's screen center from its fast webview-context rect plus that cached offset. This drops the priming tap and the per-tap context switches: a's and b's rects are read fresh (b's just before its tap, so it reflects the keyboard-open layout) with no switch between the two taps, keeping the a->b gap in the sub-second window while landing the taps accurately. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sequential mobile: tap calls have a ~751ms floor, above WebKit's double-tap window (~300-350ms), so they could never exercise the sub-350ms regime. Dispatch both taps in ONE atomic performActions chain whose in-chain pause is the exact inter-tap gap, sweeping [0,100,200,300]ms - the only way to probe the double-tap window at two distinct points (a then b). Coordinates use the proven cached-Safari-offset screen centers; both are resolved up front while the keyboard is still closed, which is correct because both taps fire within the sub-second chain before the keyboard opens. Finger-sized contact (no pressure) drives touch-adjustment like a physical tap; short hold keeps each tap crisp for rapid-tap detection. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…repro The single-chain two-tap reproduces #4173's symptom (cursor stuck on a) only at a 0ms gap and passes at >=100ms, which is inconsistent with the manually reported "within a second" window. That raises the question of whether the 0ms failure is a faithful repro or a synthetic-input artifact (two ultra-fast touches coalesced / the second dropped). Add a discriminating control: at the SAME 0ms timing, tap a->c (non-adjacent), which per the issue must still move the cursor. If a->b fails at 0ms but a->c passes at 0ms, the bug is adjacency-specific (genuine #4173); if a->c also fails, the second tap is merely being dropped (artifact). Also sweep the adjacent gap at 25/50/75/100ms to map where the trigger boundary actually is. Every case logs the received cursor value for diagnosis regardless of pass/fail. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…porary] Add [#4173]-tagged console.info logging with high-resolution performance.now() timestamps across the full tap handling path in Editable.tsx: - native touchend/click listeners (earliest per-tap signal) - handleTapBehavior (long-press bail, hidden/multicursor branch, or the common no-op case where the cursor is set via onFocus instead) - onFocus (suppressFocus return, long-press skip, -> setCursorOnThought) - setCursorOnThought (the unchanged-cursor guard return vs. the setCursor dispatch) Purpose: on a real iOS device where a human CAN reproduce #4173, inspect via Safari Web Inspector (filter "[#4173]") to see which handler fires (or fails to fire) on the second tap, whether the second setCursor is suppressed by the guard or never dispatched at all, and the true inter-tap interval. This determines the real trigger window and mechanism, which no synthetic-touch vector reproduced. Temporary investigation scaffolding - remove before merge. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…n; skip synthetic 0ms artifact cases Adds a faithful, handler-level regression test for #4173 in src/components/__tests__/Editable.ts. It fires touchend on an adjacent thought while the keyboard is open (the state left by a prior tap) and asserts the cursor moves. This reproduces the bug's real mechanism: the touchend hits handleTapBehavior's 'no-op (cursor set via onFocus)' branch and, since iOS does not fire onFocus on a rapid re-tap, the cursor never moves. Marked it.fails to document the unfixed bug while keeping the unit suite green; remove .fails once #4173 is fixed. A pure synthetic-touch e2e cannot reproduce #4173 because WDA taps force focus onto the target, bypassing the real-finger rapid-tap focus suppression. Skips the two 0ms cases in the iOS synthetic spec (caretAdjacentTap.ts) that only reflected a synthetic-input coalescing artifact, keeping the passing 25-100ms cases and documentation of the boundary. Removes the temporary [#4173] console.info instrumentation from Editable.tsx now that the mechanism is pinned. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…fails The project's global `it` is typed via jest-compatible types that do not declare vitest's `.fails`, so `lint:tsc` (and every build-dependent CI job: Lint, Puppeteer, BrowserStack, Vercel) failed with TS2339. Rewrite the #4173 handler-level test as a running, green characterization test that asserts the current buggy outcome (cursor stays on 'a') with an explicit instruction to flip the expected value to 'b' when #4173 is fixed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The prior single assertion (touchend while keyboard open does not move the cursor) only documented the branch structure: deferring to onFocus is by design, so on its own it did not demonstrate a bug, and it could pass green even if the touchend handler were detached. Add a positive control that fires the SAME touchend on the adjacent thought with the keyboard CLOSED and asserts the cursor DOES move to it. The two cases now differ only in keyboard state, isolating #4173: touchend is proven to drive the cursor-setting chain (control -> 'b'), and the keyboard-open deferral is what suppresses it (bug -> stays 'a'). Extracts a shared tapAdjacentThoughtOnTouchEnd helper. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#4173
Goal
Determine whether #4173 — tapping an adjacent thought within ~1s of a prior tap fails to move the caret — can be reproduced by an automated iOS/wdio test, so it can be landed as a regression test.
Approach
Isolated spec
src/e2e/iOS/__tests__/caretAdjacentTap.ts, pinned to iOS 18 (iPhone 16 Pro Max). It seedsa/b/cand tapsathen a target with a controlled inter-tap gap, asserting the cursor moved to the target. Taps are dispatched as a single atomicperformActionschain (finger-sized contact, native context) so sub-100ms gaps are reachable — sequentialmobile: tapcalls have a ~751ms floor.Finding: NOT faithfully reproducible by synthetic touch
a→bat a 0ms gap fails (cursor stuck ona), deterministically. Gaps ≥25ms pass.a→c(which per the issue must still work) lands the cursor onb— the geometric midpoint betweenaandc— not oncand not ona. This is WebKit/WDA spatially coalescing two near-simultaneous synthetic touches into one averaged tap, not the adjacency-specific focus suppression Cannot set caret on adjacent thought within 1 second of tap #4173 describes.Vectors ruled out: zero-radius
performActions(iOS 17); fat-taps ±pressure (iOS 18); native-contextperformActions; nativemobile: tapgestures at ~751ms; single-chain two-tap 0–800ms. The app has no ~1s tap guard (Editable.tsxhandleTapBehavior/onFocus→setCursorOnThought), so #4173 is a browser/WebKit-level focus issue that synthetic touches don't drive faithfully.Next
Instrument the tap→
setCursorpath and measure the real inter-tap timing/handler flow on a device where a human can reproduce the bug, to learn the true trigger window and which code path drops the secondsetCursor.Draft — reproduction investigation, kept open.