appium-webdriveragent 16.14.1 → 16.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,7 @@
15
15
  #import "FBMacros.h"
16
16
  #import "FBMathUtils.h"
17
17
  #import "FBProtocolHelpers.h"
18
+ #import "FBScreen.h"
18
19
  #import "FBW3CActionsHelpers.h"
19
20
  #import "FBXCodeCompatibility.h"
20
21
  #import "FBXCTestDaemonsProxy.h"
@@ -28,6 +29,7 @@
28
29
  #import "XCSynthesizedEventRecord.h"
29
30
  #import "XCPointerEventPath.h"
30
31
  #import "XCPointerEvent.h"
32
+ #import "XCUIScreen.h"
31
33
 
32
34
 
33
35
  static NSString *const FB_KEY_TYPE = @"type";
@@ -66,7 +68,10 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
66
68
  #if !TARGET_OS_TV && !TARGET_OS_WATCH
67
69
  @interface FBW3CGestureItem : FBBaseGestureItem
68
70
 
69
- @property (nullable, readonly, nonatomic) FBBaseGestureItem *previousItem;
71
+ @property (nullable, readonly, nonatomic) FBW3CGestureItem *previousItem;
72
+ @property (nonatomic) CGVector displayCorrection;
73
+ @property (nonatomic) BOOL usesDisplayCorrection;
74
+ @property (readonly, nonatomic) CGPoint screenPoint;
70
75
 
71
76
  @end
72
77
 
@@ -117,7 +122,7 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
117
122
 
118
123
  - (nullable instancetype)initWithActionItem:(NSDictionary<NSString *, id> *)actionItem
119
124
  application:(XCUIApplication *)application
120
- previousItem:(nullable FBBaseGestureItem *)previousItem
125
+ previousItem:(nullable FBW3CGestureItem *)previousItem
121
126
  offset:(double)offset
122
127
  error:(NSError **)error
123
128
  {
@@ -127,6 +132,7 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
127
132
  self.application = application;
128
133
  self.offset = offset;
129
134
  _previousItem = previousItem;
135
+ _usesDisplayCorrection = previousItem.usesDisplayCorrection;
130
136
  NSNumber *durationObj = FBOptDuration(actionItem, @0, error);
131
137
  if (nil == durationObj) {
132
138
  return nil;
@@ -142,6 +148,14 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
142
148
  return self;
143
149
  }
144
150
 
151
+ - (CGPoint)screenPoint
152
+ {
153
+ CGPoint point = self.atPosition.screenPoint;
154
+ return self.usesDisplayCorrection
155
+ ? CGPointMake(point.x + self.displayCorrection.dx, point.y + self.displayCorrection.dy)
156
+ : point;
157
+ }
158
+
145
159
  - (nullable XCUICoordinate *)positionWithError:(NSError **)error
146
160
  {
147
161
  if (nil == self.previousItem || nil == self.previousItem.atPosition) {
@@ -158,6 +172,27 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
158
172
  positionOffset:(nullable NSValue *)positionOffset
159
173
  error:(NSError **)error
160
174
  {
175
+ XCUIScreen *screen = nil;
176
+ if (element.elementType == XCUIElementTypeApplication) {
177
+ screen = [FBScreen currentScreenWithError:error];
178
+ if (nil == screen) {
179
+ return nil;
180
+ }
181
+ }
182
+ if (nil != screen && screen.displayID != XCUIScreen.mainScreen.displayID) {
183
+ // Unlike ordinary elements, application-root coordinates rotate using the
184
+ // main display. Resolve the client-visible rect's center into viewport
185
+ // coordinates, then let screenPoint apply the selected-display correction.
186
+ // Do not use a normalized app center: XCTest may report a portrait app rect
187
+ // for a landscape window, so its normalized center uses a different space.
188
+ CGRect frame = element.wdFrame;
189
+ if (CGRectIsEmpty(frame)) {
190
+ return [super hitpointWithElement:element positionOffset:positionOffset error:error];
191
+ }
192
+ CGPoint offset = nil == positionOffset ? CGPointZero : positionOffset.CGPointValue;
193
+ CGPoint point = CGPointMake(CGRectGetMidX(frame) + offset.x, CGRectGetMidY(frame) + offset.y);
194
+ return [super hitpointWithElement:nil positionOffset:[NSValue valueWithCGPoint:point] error:error];
195
+ }
161
196
  if (nil == element || nil == positionOffset) {
162
197
  return [super hitpointWithElement:element positionOffset:positionOffset error:error];
163
198
  }
@@ -223,7 +258,7 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
223
258
  }
224
259
  }
225
260
  if (nil == self.pressure) {
226
- XCPointerEventPath *result = [[XCPointerEventPath alloc] initForTouchAtPoint:self.atPosition.screenPoint
261
+ XCPointerEventPath *result = [[XCPointerEventPath alloc] initForTouchAtPoint:self.screenPoint
227
262
  offset:FBMillisToSeconds(self.offset)];
228
263
  return @[result];
229
264
  }
@@ -280,6 +315,10 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
280
315
  }
281
316
 
282
317
  if (nil != element) {
318
+ // Application origins need viewport display correction; other elements use their own display.
319
+ // This correction does not fix XCTest's legacy-SDK compatibility issue:
320
+ // see docs/multiple-displays.md#legacy-sdk-compatibility-windows.
321
+ self.usesDisplayCorrection = element.elementType == XCUIElementTypeApplication;
283
322
  if (nil == x && nil == y) {
284
323
  return [self hitpointWithElement:element positionOffset:nil error:error];
285
324
  }
@@ -287,6 +326,7 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
287
326
  }
288
327
 
289
328
  if ([origin isKindOfClass:NSString.class] && [origin isEqualToString:FB_ORIGIN_TYPE_VIEWPORT]) {
329
+ self.usesDisplayCorrection = YES;
290
330
  return [self hitpointWithElement:nil positionOffset:[NSValue valueWithCGPoint:CGPointMake(x.floatValue, y.floatValue)] error:error];
291
331
  }
292
332
 
@@ -300,6 +340,7 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
300
340
  }
301
341
  XCUICoordinate *recentPosition = self.previousItem.atPosition;
302
342
  CGVector offsetRelativeToRecentPosition = (nil == x && nil == y) ? CGVectorMake(0, 0) : CGVectorMake(x.floatValue, y.floatValue);
343
+ // Pointer-relative moves keep the coordinate space of their preceding position.
303
344
  return [recentPosition coordinateWithOffset:offsetRelativeToRecentPosition];
304
345
  }
305
346
 
@@ -314,10 +355,10 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
314
355
  error:(NSError **)error
315
356
  {
316
357
  if (nil == eventPath) {
317
- return @[[[XCPointerEventPath alloc] initForTouchAtPoint:self.atPosition.screenPoint
358
+ return @[[[XCPointerEventPath alloc] initForTouchAtPoint:self.screenPoint
318
359
  offset:FBMillisToSeconds(self.offset + self.duration)]];
319
360
  }
320
- [eventPath moveToPoint:self.atPosition.screenPoint
361
+ [eventPath moveToPoint:self.screenPoint
321
362
  atOffset:FBMillisToSeconds(self.offset + self.duration)];
322
363
  return @[];
323
364
  }
@@ -637,6 +678,10 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
637
678
  @end
638
679
 
639
680
 
681
+ @interface FBW3CActionsSynthesizer ()
682
+ @property (nonatomic) CGVector displayCorrection;
683
+ @end
684
+
640
685
  @implementation FBW3CActionsSynthesizer
641
686
 
642
687
  - (NSArray<NSDictionary<NSString *, id> *> *)preprocessedActionItemsWith:(NSArray<NSDictionary<NSString *, id> *> *)actionItems
@@ -821,6 +866,7 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
821
866
  return nil;
822
867
  }
823
868
 
869
+ gestureItem.displayCorrection = self.displayCorrection;
824
870
  [chain addItem:gestureItem];
825
871
  }
826
872
 
@@ -847,13 +893,10 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
847
893
  return [self eventPathsWithKeyAction:actionDescription forActionId:actionId error:error];
848
894
  }
849
895
 
850
- - (nullable XCSynthesizedEventRecord *)synthesizeWithError:(NSError **)error
896
+ - (nullable NSArray<NSDictionary<NSString *, id> *> *)validatedActionsWithError:(NSError **)error
851
897
  {
852
- XCSynthesizedEventRecord *eventRecord = [[XCSynthesizedEventRecord alloc]
853
- initWithName:@"W3C Touch Action"
854
- interfaceOrientation:self.application.interfaceOrientation];
855
898
  NSMutableDictionary<NSString *, NSDictionary<NSString *, id> *> *actionsMapping = [NSMutableDictionary new];
856
- NSMutableArray<NSString *> *actionIds = [NSMutableArray new];
899
+ NSMutableArray<NSDictionary<NSString *, id> *> *validatedActions = [NSMutableArray new];
857
900
  for (NSDictionary<NSString *, id> *action in self.actions) {
858
901
  id actionId = [action objectForKey:FB_KEY_ID];
859
902
  if (![actionId isKindOfClass:NSString.class] || 0 == [actionId length]) {
@@ -883,11 +926,76 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
883
926
  continue;
884
927
  }
885
928
 
886
- [actionIds addObject:actionId];
929
+ [validatedActions addObject:action];
887
930
  [actionsMapping setObject:action forKey:actionId];
888
931
  }
889
- for (NSString *actionId in actionIds.copy) {
890
- NSDictionary<NSString *, id> *actionDescription = [actionsMapping objectForKey:actionId];
932
+ return validatedActions.copy;
933
+ }
934
+
935
+ - (BOOL)actionsRequireDisplay:(NSArray<NSDictionary<NSString *, id> *> *)actions
936
+ {
937
+ for (NSDictionary<NSString *, id> *action in actions) {
938
+ if (![action[FB_KEY_TYPE] isEqual:FB_ACTION_TYPE_POINTER]) {
939
+ continue;
940
+ }
941
+ for (NSDictionary *item in action[FB_KEY_ACTIONS]) {
942
+ if (![item[FB_ACTION_ITEM_KEY_TYPE] isEqual:FB_ACTION_ITEM_TYPE_PAUSE]) {
943
+ return YES;
944
+ }
945
+ }
946
+ }
947
+ return NO;
948
+ }
949
+
950
+ - (nullable XCSynthesizedEventRecord *)eventRecordForActions:(NSArray<NSDictionary<NSString *, id> *> *)actions
951
+ error:(NSError **)error
952
+ {
953
+ // Keyboard and pause-only sequences have no display coordinates. A display
954
+ // disappearing must not prevent these input sources from being synthesized.
955
+ XCUIScreen *screen = [self actionsRequireDisplay:actions] ? [FBScreen currentScreenWithError:error] : XCUIScreen.mainScreen;
956
+ if (nil == screen) {
957
+ return nil;
958
+ }
959
+ XCSynthesizedEventRecord *eventRecord;
960
+ self.displayCorrection = CGVectorMake(0, 0);
961
+ if (screen.displayID == XCUIScreen.mainScreen.displayID) {
962
+ eventRecord = [[XCSynthesizedEventRecord alloc] initWithName:@"W3C Touch Action"
963
+ interfaceOrientation:self.application.interfaceOrientation];
964
+ } else {
965
+ if (![XCSynthesizedEventRecord instancesRespondToSelector:@selector(initWithName:displayID:interfaceOrientation:)]) {
966
+ if (error) {
967
+ *error = [[FBErrorBuilder.builder
968
+ withDescription:@"Actions on a display other than the main one are not supported by this XCTest version"] build];
969
+ }
970
+ return nil;
971
+ }
972
+ eventRecord = [[XCSynthesizedEventRecord alloc] initWithName:@"W3C Touch Action"
973
+ displayID:(unsigned long long)screen.displayID
974
+ interfaceOrientation:self.application.interfaceOrientation];
975
+ // Viewport coordinates use the main display's size when rotating screenPoint.
976
+ // Element coordinates already use their own display. Apply this correction
977
+ // per gesture item so mixed origins and pointer-relative moves stay aligned.
978
+ XCUIScreen *mainScreen = XCUIScreen.mainScreen;
979
+ self.displayCorrection = FBDisplayCoordinateOffset(
980
+ CGSizeMake(mainScreen.bounds.size.width / mainScreen.scale, mainScreen.bounds.size.height / mainScreen.scale),
981
+ CGSizeMake(screen.bounds.size.width / screen.scale, screen.bounds.size.height / screen.scale),
982
+ eventRecord.interfaceOrientation);
983
+ }
984
+ return eventRecord;
985
+ }
986
+
987
+ - (nullable XCSynthesizedEventRecord *)synthesizeWithError:(NSError **)error
988
+ {
989
+ NSArray<NSDictionary<NSString *, id> *> *actions = [self validatedActionsWithError:error];
990
+ if (nil == actions) {
991
+ return nil;
992
+ }
993
+ XCSynthesizedEventRecord *eventRecord = [self eventRecordForActions:actions error:error];
994
+ if (nil == eventRecord) {
995
+ return nil;
996
+ }
997
+ for (NSDictionary<NSString *, id> *actionDescription in actions) {
998
+ NSString *actionId = actionDescription[FB_KEY_ID];
891
999
  NSArray<XCPointerEventPath *> *eventPaths = [self eventPathsWithActionDescription:actionDescription forActionId:actionId error:error];
892
1000
  if (nil == eventPaths) {
893
1001
  return nil;
@@ -21,7 +21,9 @@ shows 80 numbered rows, each 44 points high.
21
21
 
22
22
  The `probe-status` label exposes `start`, `last`, `phase`, and `count` after a
23
23
  touch. `start` and `last` are **canvas-local app points**. It also exposes
24
- `canvasBounds`, `canvasWindowRect`, `windowSize`, `screenSize`, and `scrollY`.
24
+ `canvasBounds`, `canvasWindowRect`, `windowSize`, `screenSize`, `scrollY`, and
25
+ `fullyVisibleRows` (row indices whose full rectangles lie inside the table bounds).
26
+ `rotationInProgress` stays true until the fixture's rotation transition completes.
25
27
  Geometry is refreshed when the view lays out, including after rotation or window
26
28
  resizing. Touch-related fields are absent before the first touch.
27
29
 
@@ -106,6 +106,17 @@
106
106
  [self.view addSubview:self.table];
107
107
  }
108
108
 
109
+ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
110
+ {
111
+ [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
112
+ self.measurement[@"rotationInProgress"] = @YES;
113
+ [self publishMeasurement];
114
+ [coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
115
+ self.measurement[@"rotationInProgress"] = @NO;
116
+ [self publishMeasurement];
117
+ }];
118
+ }
119
+
109
120
  - (void)viewDidLayoutSubviews
110
121
  {
111
122
  [super viewDidLayoutSubviews];
@@ -139,6 +150,13 @@
139
150
  self.measurement[@"windowSize"] = @[@(window.bounds.size.width), @(window.bounds.size.height)];
140
151
  self.measurement[@"screenSize"] = @[@(window.screen.bounds.size.width), @(window.screen.bounds.size.height)];
141
152
  self.measurement[@"scrollY"] = @(self.table.contentOffset.y);
153
+ NSMutableArray<NSNumber *> *fullyVisibleRows = [NSMutableArray new];
154
+ for (NSIndexPath *indexPath in self.table.indexPathsForVisibleRows) {
155
+ if (CGRectContainsRect(self.table.bounds, [self.table rectForRowAtIndexPath:indexPath])) {
156
+ [fullyVisibleRows addObject:@(indexPath.row)];
157
+ }
158
+ }
159
+ self.measurement[@"fullyVisibleRows"] = fullyVisibleRows;
142
160
  NSData *data = [NSJSONSerialization dataWithJSONObject:self.measurement
143
161
  options:NSJSONWritingSortedKeys
144
162
  error:nil];