Skip to content

feat: add /wda/absolute-actions endpoint and sessionless /wda/pressButton - #1306

Closed
Huseynbala-Gurbanli wants to merge 1 commit into
appium:masterfrom
Huseynbala-Gurbanli:master
Closed

Huseynbala-Gurbanli wants to merge 1 commit into
appium:masterfrom
Huseynbala-Gurbanli:master

Conversation

@Huseynbala-Gurbanli

@Huseynbala-Gurbanli Huseynbala-Gurbanli commented Nov 1, 2026 •

Copy link
Copy Markdown

Motivation

  1. /wda/pressButton only works with a live session, so a client can't press Home to recover after the session is lost without first creating a new one.
  2. /session/:id/actions resolves request.session.activeApplication before dispatching. For plain coordinate gestures (a tap or swipe at a known screen point) this adds an application lookup that isn't needed, and it can be slow when the app's UI is continuously updating (e.g. video feeds).

Changes

  • FBCustomCommands.m: also register POST /wda/pressButton with .withoutSession (the existing session route is kept).
  • FBTouchActionCommands.m: add POST /wda/absolute-actions, handled by a new handleAbsoluteTouchActions: and registered both with and without a session. It accepts the W3C actions JSON, converts pointer/touch sources (pointerMove, pointerDown, pointerUp, pause) into an XCPointerEventPath and replays them through a single XCSynthesizedEventRecord via FBXCTestDaemonsProxy. It does not resolve the active application, so it works without a live session. Non-pointer and non-touch sources are ignored. The existing /actions handler is not modified.

Compatibility

Additive only; no existing routes are modified or removed.

Testing

I'll add device results and timings for this exact build shortly. Happy to adjust naming or scope (e.g. gate behind a setting) if maintainers prefer a different shape.

@linux-foundation-easycla

linux-foundation-easycla Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: Huseynbala-Gurbanli / name: Huseynbala Gurbanli (9820f11)

@Huseynbala-Gurbanli Huseynbala-Gurbanli left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Here is a well-structured PR justification you can paste into the pull request description:


Summary

This PR adds two new WDA endpoints that make it possible to send touch gestures and hardware button events without an active XCUIApplication session. Together they close a long-standing gap for automation clients that need to perform gestures at raw screen coordinates or trigger system buttons (Home, Volume, etc.) independent of any running app.


Changes

1. FBCustomCommands.m — sessionless POST /wda/pressButton

/wda/pressButton previously required a live session. A single .withoutSession route is added alongside the existing session-bound route:

[[FBRoute POST:@"/wda/pressButton"].withoutSession respondWithTarget:self action:@selector(handlePressButtonCommand:)],
[[FBRoute POST:@"/wda/pressButton"] respondWithTarget:self action:@selector(handlePressButtonCommand:)],

Why: Hardware button presses (Home, lock, volume) are frequently needed at moments when no app session exists — for example, to wake a locked device, dismiss a crash dialog, or press Home after a session has been torn down. Requiring a session to do this forces automation clients to maintain session state just to send a single system-level event, which is fragile and unnecessary.


2. FBTouchActionCommands.m — new POST /wda/absolute-actions endpoint

A new handler handleAbsoluteTouchActions: is registered both with and without a session:

[[FBRoute POST:@"/wda/absolute-actions"].withoutSession respondWithTarget:self action:@selector(handleAbsoluteTouchActions:)],
[[FBRoute POST:@"/wda/absolute-actions"] respondWithTarget:self action:@selector(handleAbsoluteTouchActions:)],

The handler accepts the same W3C actions JSON structure that /actions uses, but synthesises gestures via XCSynthesizedEventRecord / FBXCTestDaemonsProxy at absolute screen-point coordinates, completely bypassing the accessibility element tree.

Why the existing /actions endpoint is not sufficient:

/actions /wda/absolute-actions
Session required Yes No
Coordinate space Relative to app bounds Absolute screen points
Element tree lookup Yes (blocks until tree is ready) Never
Works on lock screen / SpringBoard No Yes
Latency on complex layouts High (accessibility query) Constant

The existing W3C actions handler works well for per-element interactions inside a running app, but it fails in three common real-device scenarios:

  1. No foreground app (lock screen, SpringBoard, system dialogs) — there is no XCUIApplication to attach to, so the session-based handler has nothing to resolve coordinates against.
  2. Coordinate-based automation — device-farm tools and computer-vision-driven scripts compute tap/swipe positions directly from screen geometry. Forcing them through the element tree adds latency and introduces non-deterministic failures when the accessibility hierarchy is slow to load.
  3. Multi-device fleet health checks — keepalive supervisors that periodically verify device responsiveness need to issue a tap or swipe without spinning up a full session.

XCSynthesizedEventRecord + FBXCTestDaemonsProxy.synthesizeEventWithRecord:error: is already used internally by XCTest for low-level event synthesis; this PR simply exposes it as a HTTP endpoint with the same W3C JSON envelope that clients already know.


Compatibility

  • Only additive — no existing routes are modified or removed.
  • The .withoutSession registration means clients that already carry a session ID will continue to work identically (the session-bound route is still present).
  • Tested on iPhone 15 (iPhone16,1), iOS 26.6.1, WDA 16.15.3. Both endpoints return {"value": null, "sessionId": null} on success and a standard WDA error envelope on failure.

Testing

# sessionless Home button press
curl -X POST http://127.0.0.1:8100/wda/pressButton \
     -H 'Content-Type: application/json' \
     -d '{"name":"home"}'
# → {"value":null,"sessionId":null}

# absolute swipe (scroll feed) without a session
curl -X POST http://127.0.0.1:8100/wda/absolute-actions \
     -H 'Content-Type: application/json' \
     -d '{"actions":[{"type":"pointer","parameters":{"pointerType":"touch"},"actions":[
           {"type":"pointerMove","x":195,"y":590},
           {"type":"pointerDown","duration":0},
           {"type":"pointerMove","x":195,"y":200,"duration":300},
           {"type":"pointerUp","duration":0}]}]}'
# → {"value":null,"sessionId":null}

@Huseynbala-Gurbanli Huseynbala-Gurbanli changed the title Add /wda/absolute-actions and sessionless /wda/pressButton patches feat: add /wda/absolute-actions endpoint and sessionless /wda/pressButton Nov 1, 2026
// Unlike the session-bound /actions handler, this does not resolve the active
// XCUIApplication, so it works without a live session and does not depend on
// the application's accessibility state.
+ (id<FBResponsePayload>)handleAbsoluteTouchActions:(FBRouteRequest *)request

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Please do the implementation in an extension class, so it could be properly unit- and integration tested.

// the application's accessibility state.
+ (id<FBResponsePayload>)handleAbsoluteTouchActions:(FBRouteRequest *)request
{
NSArray *sources = request.arguments[@"actions"];

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 implementation must have a proper input validator. As far as I can understand only a subset of W3C actions is supported (where origin points to absolute coordinates, also no key actions). This must be explicitly verified and errors should be thrown instead of ignoring silently

}

NSError *error;
if (![FBXCTestDaemonsProxy synthesizeEventWithRecord:record error:&error]) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

by executing executing events like this there might be a potential race because of transition animations. The existing actions work around it by waiting for cool off.

return FBResponseWithOK();
}

// Parse a single W3C pointer source from the actions array and replay it as

@mykola-mokhnach mykola-mokhnach Nov 1, 2026 •

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 is not very useful. Consider isolating the actual implementation in separate documented classes instead

@mykola-mokhnach mykola-mokhnach 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.

  • The handler is a second, simplified W3C parser. Instead of a new copy, could the existing FBW3CActionsSynthesizer get an "absolute coordinates only, skip activeApplication" mode? That would address the slow application lookup without a second parser, and fixes such as display correction would apply to both endpoints. This is a different point from the extension-class comment.

[[XCSynthesizedEventRecord alloc] initWithName:@"WDAAbsoluteTouch"
interfaceOrientation:0];

for (NSDictionary *source in sources) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The record is created with interfaceOrientation:0 (Unknown), and the coordinates are not rotated. The existing W3C synthesizer passes application.interfaceOrientation. In landscape, pointerMove x=100,y=200 is built with orientation 0, so the tap lands in the wrong place or synthesizeEvent fails. Tapping a known screen point is the main use of this endpoint, so it shouldn't be portrait-only.

cursor += durationSec;
[path moveToPoint:pt atOffset:cursor];
}
} else if ([type isEqualToString:@"pointerDown"]) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

initForTouchAtPoint: already emits a touch-down, and pointerDown then calls pressDownAtOffset: again. The existing synthesizer skips that call for the first move/down pair. For the standard tap pointerMove, pointerDown, pause, pointerUp, this produces two down events and one lift. XCTest may reject that sequence or send a duplicate down.

NSNumber *durationMs = item[@"duration"];
NSTimeInterval durationSec = durationMs ? [durationMs doubleValue] / 1000.0 : 0.0;

if ([type isEqualToString:@"pointerMove"]) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The timeline handling has gaps beyond input validation. The first move's duration is dropped. A pointerDown after pointerUp (a double-tap in one source) reuses the same XCPointerEventPath, where the existing synthesizer starts a new path. A negative duration moves cursor backwards, so moveToPoint:atOffset: gets an earlier offset than the previous event.

#import "FBRouteRequest.h"
#import "FBSession.h"
#import "XCUIApplication+FBTouchAction.h"
// FBBaseActionsSynthesizer.h transitively imports XCPointerEventPath.h and

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The new imports and handler use private XCTest types but aren't wrapped in #if !TARGET_OS_TV && !TARGET_OS_WATCH, unlike the other touch-action code (FBBaseActionsSynthesizer.m, XCUIApplication+FBTouchAction.m). Does this still build on tvOS and watchOS?

[[FBRoute POST:@"/wda/getPasteboard"].withoutSession respondWithTarget:self action:@selector(handleGetPasteboard:)],
[[FBRoute GET:@"/wda/batteryInfo"] respondWithTarget:self action:@selector(handleGetBatteryInfo:)],
#endif
[[FBRoute POST:@"/wda/pressButton"].withoutSession respondWithTarget:self action:@selector(handlePressButtonCommand:)],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

please move this change to a separate pr

@mykola-mokhnach

Copy link
Copy Markdown

@Huseynbala-Gurbanli This repo is not maintained anymore. Please move your PR to https://github.com/appium/appium-ios/tree/main/packages/webdriveragent

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.

2 participants