Skip to content

fix: align Duo W3C actions and scrolling - #1288

Merged
KazuCocoa merged 37 commits into
masterfrom
fix/duo-display-gestures
Oct 8, 2026
Merged

KazuCocoa merged 37 commits into
masterfrom
fix/duo-display-gestures

Conversation

@KazuCocoa

@KazuCocoa KazuCocoa commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

On Duo's inner display, element-origin W3C actions could land hundreds of points away from viewport-origin actions, and scrolling could drag outside the app. Ordinary iPhones also used portrait visible rectangles while scrolling in landscape. This change aligns gesture coordinates with the selected display and the application's orientation.

  • Apply the secondary-display correction per W3C position, preserving element origins and pointer-relative moves when origins change within a touch. Keyboard-only and pause-only sequences remain usable after the selected display disappears.
  • Resolve application-element origins on secondary displays from the client-visible rect center and apply viewport display correction. Ordinary element origins and main-display behavior remain unchanged; pointer-relative moves inherit the preceding origin space.
  • Convert clipped visible rectangles on both main and secondary displays, using a live element anchor. Final cell-alignment drags compensate for pan recognition distance and use a bounded speed to avoid flicking past the target.
  • Return NO and NSError from scroll-to-visible when display lookup fails. Directional void APIs retain their explicit exception behavior.
  • Keep each multi-drag scroll's frame and vector in one coordinate space. Rotation/folding must finish between commands. Element-origin actions require the selected display to match the element; display selection remains explicit and defaults to the main display.
  • Preserve the shared Alerts fixture in the display-independent input test so later W3C touch tests remain isolated from its display checks.
  • Add regression tests using actual delivered touches, content offsets, and fully visible row indices, including closed → book → open → closed transitions and ordinary phone/tablet landscape scrolling.

Targets master, incorporating the gesture work from #1269 and the merged #1291/#1292 geometry and native-coordinate changes. Also corrects portrait screen metadata on physical iPads whose native screen bounds are landscape: a portrait iPad mini previously reported 1133 × 744 points and no status bar; it now reports 744 × 1133 points and its 32-point status bar.

Validation

  • Xcode 27.2 beta 2, iPhone 18 Pro / iOS 27.2: the focused gesture/action suite passes, including both landscape directions, portrait scrolling, unavailable-display errors, and default-main-display coordinate parity. The fold-only test is skipped on this ordinary phone.

  • watchOS simulator runner build-for-testing passes with Xcode 27.2 beta 2.

  • Earlier Duo validation: 54/54 HTTP W3C touch cases and 9/9 direction-scroll/native-swipe/W3C-scroll checks passed across closed, book, and open poses.

  • Combined validation with fix: report geometry for the selected display #1291/fix: anchor native Duo coordinates to application windows #1292 covers iPhone 17 on iOS 26.5 and 27.0, iPhone 18 Pro on 27.2, iPhone SE (2nd generation) on 26.5, iPad A16 on iPadOS 26.5/27.0/27.2, and Duo on 27.1. Final applicable gesture, error, default-selection, and geometry cases pass; hardware-specific cases are skipped on other devices. Native landscape cases are checked independently from a settled portrait state, avoiding an existing UIKit race during an immediate 180-degree flip.

Latest validation:

  • Integrated native regression suite on physical iPad mini (A17 Pro), iPadOS 27.0.1: 45 passed, 6 capability/disabled-test skips. After the portrait geometry correction, the full screen suite passed again (20 passed, 2 capability skips).
  • The expanded portrait → both landscape directions → portrait test exposed the old screen implementation on the physical iPad. After the fix, Driver screenshot dimensions and viewport crops agree: portrait 1488 × 2266 → 1488 × 2202; landscape 2266 × 1488 → 2266 × 1424. This fixes a WDA regression; no driver default changed.
  • The new orientation test passes on iPhone/iPad simulators with iOS/iPadOS 26.5, 27.0, and 27.2, plus iPhone SE on 26.5. Duo 27.1's fold-state screen test also passes (8 configurations total).
  • Earlier iPadOS 26.5 W3C alert failures reproduced on the baseline while the button frame was still transposed after rotation. Waiting for the fixture's settled button geometry fixes the full class: 9 passed, 3 existing capability/disabled-test skips.
  • Physical iPhone SE, iOS 27.2: 41 passed, 7 skips, 3 coordinate tests failed with the AssistiveTouch overlay covering the target in failure screenshots. Those original points failed again with AssistiveTouch enabled. A local-only control moved right-hand sample points 40 points inward and all 3 cases passed, without changing AssistiveTouch or its position. This supports overlay interception; the original covered points are not counted as passes and the repository tests retain their original coordinates. The new screen-orientation test also passes on the SE.
  • Additional WDA-free compatibility comparison: old/new SDK apps, cold and warm round trips through 0 → 45 → 90 → 135 → 180 → 90 → 0 degrees. New SDK: 42/42 delivered taps; old SDK: 24/42, with misses in the inner-display compatibility states. Full observations and limits are documented in docs/multiple-displays.md.

No physical Duo is available. Arbitrary rotations during an active gesture are not supported. Physical Safari iframe validation is separate: the final driver #3015 code now passes portrait → landscape → portrait iframe taps on both iPad and SE.

Application-origin review follow-up

The HTTP reproduction used an explicit app bundle ID and a cached application element. Before the fix, viewport and canvas-element origins delivered the expected point, but the application origin landed 273 points to the right on Duo’s open inner display. After the fix, all three origins deliver the same point within one point of tolerance. The new regression tests resolve serialized element IDs through the HTTP API’s element cache; raw XCTest element objects could hide this discrepancy.

  • The added fold-state test fails on the original implementation with the 273-point mismatch and passes with the fix through closed → open → book → closed.

  • Ordinary-device simulator regression: 28/28 tests pass across iPhone and iPad on OS 26.5, 27.0, and 27.2, plus iPhone SE on 26.5. Coverage includes portrait, both landscape directions, mixed origins within one touch, and pointer-relative continuation.

  • This follow-up was validated on simulators; no physical Duo is available. The earlier physical-device results above are separate.

Native edge-coordinate review follow-up

Native tap/drag coordinates now use a non-empty window on the selected display only as a coordinate anchor. The previous containment check incorrectly rejected right/bottom edge points and off-window drag endpoints on Duo while accepting them on main-display devices. Removing that check preserves the requested coordinates without introducing nearest-window selection, clamping, or additional coordinate corrections. Missing windows on the selected display still produce an error.

W3C actions set the display ID on their synthesized event record; native XCUICoordinate operations obtain display affinity from the anchor element. The native path therefore retains its existing window anchor instead of applying the W3C numeric correction to an application anchor.

  • Duo iOS 27.1: 48/48 HTTP native tap/drag requests accepted through 0 → 180 → 90 → 0 degrees, covering exact right/bottom boundaries and coordinates 30 points outside all four sides. Interior control taps also reached the expected app coordinates. Acceptance of off-screen requests does not imply an off-screen UI target receives a touch.
  • The new coordinate assertions verify that boundary/outside coordinates are not clamped, and fail against the original containment check. The updated fold-state regression and mismatched-display rejection tests pass after the fix.
  • Ordinary simulator regression: 35/35 tests pass across iPhone/iPad OS 26.5, 27.0, 27.2 and iPhone SE on 26.5, including portrait, both landscape directions, and default main-display behavior. This follow-up adds simulator validation; it does not claim new physical-device coverage.

Full regression recheck at 1f944b57

Rechecked the PR's earlier fixes after the application-origin and native edge-coordinate changes. No additional regression was found; no further code change was needed.

Earlier fix Regression coverage
W3C element/viewport alignment Delivered corner/center touches, element hitpoints, mixed origins, pointer-relative continuation, application-element origins
Native secondary-display coordinates Delivered app-relative taps, main-display defaults, both ordinary landscape directions, fold round trips, mismatched-display errors, edge/outside coordinate preservation
Scrolling geometry and cell alignment Content movement while app stays foreground, full target-cell visibility, both landscape directions, fold-state scrolling
Display disappearance/error handling Invalid-display scroll errors, keyboard/pause actions after display disappears, invalid-display touch rejection
Screen geometry Portrait/landscape screen info, selected-display updates and reset, fold-state screen info, status-bar checks on ordinary devices
Shared W3C fixture isolation Full W3C touch Part1 class on iPad OS 26.5, including actions after the display-independent test
Simulator Passed Skipped Failed
iPad OS 26.5 46 9 0
Duo OS 27.1 40 7 0
iPhone OS 26.5 40 7 0
iPad OS 27.0 40 7 0
iPhone OS 27.0 40 7 0
iPad OS 27.2 40 7 0
iPhone OS 27.2 40 7 0
iPhone SE OS 26.5 40 7 0

Integration total: 326 passed, 58 skipped, 0 failed. Coordinate/scroll math unit tests: 16 passed, 0 failed. Skips are capability-specific (ordinary-device vs foldable cases, pressure support) or the existing disabled long-press test; they are not counted as passes. Production and regression source files in the integrated test checkout were compared with this PR before execution.

This is new simulator coverage only. It does not replace the earlier physical-iPad portrait-geometry validation or claim a fix for the documented legacy-SDK XCTest compatibility limitation.

CI is tracked separately: the tvOS 18.5 functional job failed before test execution while installing its Runner (Missing bundle ID) and was rerun once. This does not count as a successful CI run.

@KazuCocoa
KazuCocoa marked this pull request as draft October 3, 2026 16:18
Base automatically changed from fix/duo-display-app-detection to master October 4, 2026 07:13
@KazuCocoa
KazuCocoa marked this pull request as ready for review October 4, 2026 07:16

@DanMPerfecto DanMPerfecto left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I checked several review findings on real hardware and simulators, all built with Xcode 27.2 beta 2 at ecb650b:

  • iPhone Duo simulator, iOS 27.1, unfolded. The inner display is display 3 and the app runs in LandscapeLeft.
  • iPhone 18 Pro simulator, iOS 27.2.
  • Physical iPhone 12 Pro, iOS 26.7. It also exposes secondary displays 2 and 3.

The touch fixture measured where touches actually landed. The new FBDisplayGestureTests and the display tests in FBW3CTouchActionsIntegrationTestsPart1 pass 11/11 on the Duo and on the iPhone 12 Pro. The viewport correction for LandscapeLeft is accurate: the PR's offset of (0, 273) lands within 0.1 pt of the target. Inline comments cover the issues that reproduced.

Note: with Xcode 26.x, the IntegrationApp runs in a compatibility window on the iOS 27 Duo, and XCTest reports wrong element frames there. Build with Xcode 27.x to reproduce.

Comment thread WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m Outdated
Comment thread WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m Outdated
Comment thread WebDriverAgentLib/Categories/XCUIElement+FBScrolling.m Outdated
Comment thread WebDriverAgentLib/Categories/XCUIElement+FBScrolling.m Outdated
Comment thread WebDriverAgentLib/Categories/XCUIElement+FBScrolling.m
Comment thread WebDriverAgentLib/Categories/XCUIElement+FBScrolling.m Outdated
}

if (nil != element) {
// Normalized element coordinates already resolve against their own display.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

👍

Comment thread WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m
* fix: anchor native Duo coordinates to application windows

* test: cover native coordinates across displays and orientations

* fix: restrict native coordinates to windows on the selected display

* refactor: use guards for native gesture coordinate routing
@KazuCocoa

Copy link
Copy Markdown
Member Author

I have added 1b82530#diff-1683161d6856ce0798e3fd7c257ff81c041a906b3b5a0e14c99331195161487c about compatibility issue for old SDK built app with Duo - this came from XCTest so not WDA specific

@KazuCocoa

Copy link
Copy Markdown
Member Author

appium/appium#22857 has my testing note on simulator/real devices

@DanMPerfecto DanMPerfecto left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I tested these two on the iPhone Duo simulator (iOS 27.1, Xcode 27.2 beta 2), fully open, with the app in landscape on the inner display (display 3, window 951×669) and currentDisplayId=3. Touch positions came from the IntegrationApp coordinate probe. For comparison I ran the same requests on an iPhone 18 Pro simulator (iOS 27.2) and a physical iPhone 12 Pro (iOS 26.7).

// element may report displayID == 0.
(void)window.frame;
CGRect frame = window.wdFrame;
if (window.screen.displayID == screen.displayID && CGRectContainsPoint(frame, point)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CGRectContainsPoint doesn't count points on the right or bottom edge as inside, and any point outside every window is rejected. So application-root coordinates behave differently on a secondary display than on the main one.

On the Duo inner display, POST /wda/tap:

  • x=950.99: accepted
  • x=951 (window width), y=669 (window height), x=956: all fail with No application window on display 3 contains the requested coordinate

/wda/dragfromtoforduration ending at x=951 or x=981 fails the same way. On the iPhone simulator and the physical iPhone (main display), the same edge and off-screen taps and drags are accepted.

Edge swipes that end exactly at the screen edge are common in client code. Consider an inclusive check that clamps or tolerates edge points, or fall back to the window that is nearest to the point or contains it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Re-tested at 1f944b5 on the Duo simulator (inner display, currentDisplayId=3, Xcode 27.2 beta 2). /wda/tap at x=951, y=669, x=981 and x=-30 is now accepted, matching the main display. Taps near the canvas edges land within 0.3 pt. Main-display behavior on the iPhone 18 Pro simulator and a physical iPhone 12 Pro is unchanged. Fixed, thanks!

// taps even in pure XCTest without WDA; the same app built with SDK 27.2 works.
// Do not apply the viewport offset as a workaround for this separate issue.
// See docs/multiple-displays.md#legacy-sdk-compatibility-windows for evidence.
self.usesDisplayCorrection = NO;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This turns off the display correction for every element origin, including the XCUIApplication root. The gestureCoordinateWithOffset comment says those coordinates are rotated using the main display on Duo.

A W3C tap at the same target point on the Duo inner display:

  • origin: viewport: lands at canvas (206.7, 284.3), expected (206.8, 284.2) ✅
  • origin: the canvas element: (206.7, 284.3) ✅
  • origin: the application element, offset from the window centre: no touch delivered ❌
  • origin: the application element, offset from the app's reported rect centre: lands at x=479.7 instead of 206.8 ❌

The error is about 273 pt, which is the inner long side minus the main display's long side (951 − 678), so the application root needs the same correction as viewport origins. Separately, the app element reports its rect as 669×951 while its window is 951×669, so a client computing the centre offset from the app rect is also off by its swapped width and height. On the iPhone simulator and the physical iPhone in landscape, all origin types land on target.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thank you! Actually I missed to cover the case. 5f4fa77

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Re-tested at 1f944b5 on the Duo simulator with Xcode 27.2 beta 2. With an application-element origin and the offset taken from the returned app rect, the tap now lands at canvas (206.7, 284.3), the same as viewport and canvas-element origins. A following pointer-relative move and a canvas→app drag also end on target. Checked at 180° and 90° (inner display) and 0° (outer display). No change on the main display for the iPhone 18 Pro simulator or a physical iPhone 12 Pro, in portrait and landscape. Fixed, thanks!

Comment thread docs/multiple-displays.md

This gesture support builds on [#1269](https://github.com/appium/WebDriverAgent/pull/1269).

## Legacy SDK compatibility windows

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this AI generated document contains lots of nitty-gritty details, that are not really necessary and make it harder to read by humans. Make sure the document is concise and only contains useful information to the point

}

if (nil != element) {
// Application origins are converted to viewport coordinates above and need

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this comment could be shortened

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yup, will shorten docs/comments once we confirm this doesn't have further issues to support Duo.

@DanMPerfecto DanMPerfecto left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM. I verified the edge-coordinate and application-origin fixes on the iPhone Duo simulator (iOS 27.1) in closed, 90° and fully open poses, plus the iPhone 18 Pro simulator (iOS 27.2) and a physical iPhone 12 Pro (iOS 26.7), all with Xcode 27.2 beta 2. Touch positions were checked with the coordinate probe, and I saw no regressions on the main display.

@KazuCocoa
KazuCocoa merged commit 104a31c into master Oct 8, 2026
13 of 14 checks passed
@KazuCocoa
KazuCocoa deleted the fix/duo-display-gestures branch October 8, 2026 14:48
github-actions Bot pushed a commit that referenced this pull request Oct 8, 2026
## [16.14.2](v16.14.1...v16.14.2) (2026-10-08)

### Bug Fixes

* align Duo W3C actions and scrolling ([#1288](#1288)) ([104a31c](104a31c)), closes [#1294](#1294) [#1292](#1292)
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 16.14.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants