appium-webdriveragent 16.12.1 → 16.12.2
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.
- package/CHANGELOG.md +6 -0
- package/WebDriverAgentLib/Info.plist +2 -2
- package/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m +23 -5
- package/WebDriverAgentTests/IntegrationTests/FBW3CMultiTouchActionsIntegrationTests.m +35 -0
- package/WebDriverAgentTests/IntegrationTests/FBW3CTouchActionsIntegrationTests.m +95 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [16.12.2](https://github.com/appium/WebDriverAgent/compare/v16.12.1...v16.12.2) (2026-09-03)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* allow pause action items to appear before any pointer movement ([#1246](https://github.com/appium/WebDriverAgent/issues/1246)) ([282478a](https://github.com/appium/WebDriverAgent/commit/282478a2e631501fd904b0675717123c779d9b84))
|
|
6
|
+
|
|
1
7
|
## [16.12.1](https://github.com/appium/WebDriverAgent/compare/v16.12.0...v16.12.1) (2026-09-01)
|
|
2
8
|
|
|
3
9
|
### Miscellaneous Chores
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
<key>CFBundlePackageType</key>
|
|
16
16
|
<string>FMWK</string>
|
|
17
17
|
<key>CFBundleShortVersionString</key>
|
|
18
|
-
<string>16.12.
|
|
18
|
+
<string>16.12.2</string>
|
|
19
19
|
<key>CFBundleSignature</key>
|
|
20
20
|
<string>????</string>
|
|
21
21
|
<key>CFBundleVersion</key>
|
|
22
|
-
<string>16.12.
|
|
22
|
+
<string>16.12.2</string>
|
|
23
23
|
<key>NSPrincipalClass</key>
|
|
24
24
|
<string/>
|
|
25
25
|
</dict>
|
|
@@ -133,7 +133,8 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
|
|
|
133
133
|
}
|
|
134
134
|
self.duration = durationObj.doubleValue;
|
|
135
135
|
XCUICoordinate *position = [self positionWithError:error];
|
|
136
|
-
|
|
136
|
+
// A pause may legally have no position yet (nil position, no error set)
|
|
137
|
+
if (nil == position && error && nil != *error) {
|
|
137
138
|
return nil;
|
|
138
139
|
}
|
|
139
140
|
self.atPosition = position;
|
|
@@ -143,7 +144,7 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
|
|
|
143
144
|
|
|
144
145
|
- (nullable XCUICoordinate *)positionWithError:(NSError **)error
|
|
145
146
|
{
|
|
146
|
-
if (nil == self.previousItem) {
|
|
147
|
+
if (nil == self.previousItem || nil == self.previousItem.atPosition) {
|
|
147
148
|
NSString *errorDescription = [NSString stringWithFormat:@"The '%@' action item must be preceded by %@ item", self.actionItem, FB_ACTION_ITEM_TYPE_POINTER_MOVE];
|
|
148
149
|
if (error) {
|
|
149
150
|
*error = [[FBErrorBuilder.builder withDescription:errorDescription] build];
|
|
@@ -205,9 +206,19 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
|
|
|
205
206
|
currentItemIndex:(NSUInteger)currentItemIndex
|
|
206
207
|
error:(NSError **)error
|
|
207
208
|
{
|
|
208
|
-
if (nil != eventPath && currentItemIndex
|
|
209
|
+
if (nil != eventPath && currentItemIndex >= 1) {
|
|
209
210
|
FBW3CGestureItem *preceedingItem = [allItems objectAtIndex:currentItemIndex - 1];
|
|
210
|
-
if
|
|
211
|
+
// Only skip creating a new touch if the preceding pointerMove is the one that
|
|
212
|
+
// implicitly opened this touch, i.e. nothing but (possibly zero-duration) pauses
|
|
213
|
+
// came before it. Pauses never create an event path themselves.
|
|
214
|
+
BOOL isPreceedingMoveTheFirstRealItem = YES;
|
|
215
|
+
for (NSInteger index = (NSInteger)currentItemIndex - 2; index >= 0; index--) {
|
|
216
|
+
if (![[allItems objectAtIndex:index] isKindOfClass:FBPointerPauseItem.class]) {
|
|
217
|
+
isPreceedingMoveTheFirstRealItem = NO;
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if ([preceedingItem isKindOfClass:FBPointerMoveItem.class] && isPreceedingMoveTheFirstRealItem) {
|
|
211
222
|
return @[];
|
|
212
223
|
}
|
|
213
224
|
}
|
|
@@ -280,7 +291,7 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
|
|
|
280
291
|
}
|
|
281
292
|
|
|
282
293
|
// origin == FB_ORIGIN_TYPE_POINTER
|
|
283
|
-
if (nil == self.previousItem) {
|
|
294
|
+
if (nil == self.previousItem || nil == self.previousItem.atPosition) {
|
|
284
295
|
NSString *errorDescription = [NSString stringWithFormat:@"There is no previous item for '%@' action item, however %@ is set to '%@'", self.actionItem, FB_ACTION_ITEM_KEY_ORIGIN, FB_ORIGIN_TYPE_POINTER];
|
|
285
296
|
if (error) {
|
|
286
297
|
*error = [[FBErrorBuilder.builder withDescription:errorDescription] build];
|
|
@@ -320,6 +331,13 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
|
|
|
320
331
|
return FB_ACTION_ITEM_TYPE_PAUSE;
|
|
321
332
|
}
|
|
322
333
|
|
|
334
|
+
- (nullable XCUICoordinate *)positionWithError:(NSError **)error
|
|
335
|
+
{
|
|
336
|
+
// A pause has no position of its own; proxy whatever real move preceded
|
|
337
|
+
// it, or nil (not a fabricated point) if none has run yet
|
|
338
|
+
return self.previousItem.atPosition;
|
|
339
|
+
}
|
|
340
|
+
|
|
323
341
|
- (NSArray<XCPointerEventPath *> *)addToEventPath:(XCPointerEventPath *)eventPath
|
|
324
342
|
allItems:(NSArray *)allItems
|
|
325
343
|
currentItemIndex:(NSUInteger)currentItemIndex
|
|
@@ -120,5 +120,40 @@
|
|
|
120
120
|
[self verifyGesture:gesture orientation:UIDeviceOrientationPortrait];
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
- (void)testTwoFingersTapWithLeadingZeroDurationPause
|
|
124
|
+
{
|
|
125
|
+
// Selenium clients pad shorter action sequences with a zero-duration pause
|
|
126
|
+
// so that all pointers/devices end up with the same number of ticks
|
|
127
|
+
XCUIElement *element = self.testedApplication.buttons[FBShowAlertButtonName];
|
|
128
|
+
NSArray<NSDictionary<NSString *, id> *> *gesture =
|
|
129
|
+
@[
|
|
130
|
+
@{
|
|
131
|
+
@"type": @"pointer",
|
|
132
|
+
@"id": @"finger1",
|
|
133
|
+
@"parameters": @{@"pointerType": @"touch"},
|
|
134
|
+
@"actions": @[
|
|
135
|
+
@{@"type": @"pointerMove", @"duration": @0, @"origin": element, @"x": @0, @"y": @0},
|
|
136
|
+
@{@"type": @"pointerDown"},
|
|
137
|
+
@{@"type": @"pause", @"duration": @100},
|
|
138
|
+
@{@"type": @"pointerUp"},
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
@{
|
|
142
|
+
@"type": @"pointer",
|
|
143
|
+
@"id": @"finger2",
|
|
144
|
+
@"parameters": @{@"pointerType": @"touch"},
|
|
145
|
+
@"actions": @[
|
|
146
|
+
@{@"type": @"pause", @"duration": @0},
|
|
147
|
+
@{@"type": @"pointerMove", @"duration": @0, @"origin": element, @"x": @0, @"y": @0},
|
|
148
|
+
@{@"type": @"pointerDown"},
|
|
149
|
+
@{@"type": @"pause", @"duration": @100},
|
|
150
|
+
@{@"type": @"pointerUp"},
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
[self verifyGesture:gesture orientation:UIDeviceOrientationPortrait];
|
|
156
|
+
}
|
|
157
|
+
|
|
123
158
|
@end
|
|
124
159
|
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
#import "XCUIDevice+FBRotation.h"
|
|
18
18
|
#import "FBRunLoopSpinner.h"
|
|
19
19
|
#import "FBXCodeCompatibility.h"
|
|
20
|
+
#import "FBW3CActionsSynthesizer.h"
|
|
21
|
+
#import "XCSynthesizedEventRecord.h"
|
|
22
|
+
#import "XCPointerEventPath.h"
|
|
23
|
+
#import "XCPointerEvent.h"
|
|
20
24
|
|
|
21
25
|
@interface FBW3CTouchActionsIntegrationTestsPart1 : FBIntegrationTestCase
|
|
22
26
|
@end
|
|
@@ -185,14 +189,13 @@
|
|
|
185
189
|
},
|
|
186
190
|
],
|
|
187
191
|
|
|
188
|
-
// Chain element where action
|
|
192
|
+
// Chain element where pointerMove action item does not contain coordinates
|
|
189
193
|
@[@{
|
|
190
194
|
@"type": @"pointer",
|
|
191
195
|
@"id": @"finger1",
|
|
192
196
|
@"parameters": @{@"pointerType": @"touch"},
|
|
193
197
|
@"actions": @[
|
|
194
|
-
@{@"type": @"
|
|
195
|
-
@{@"type": @"pointerMove", @"duration": @0, @"x": @1, @"y": @1},
|
|
198
|
+
@{@"type": @"pointerMove", @"duration": @0},
|
|
196
199
|
@{@"type": @"pointerDown"},
|
|
197
200
|
@{@"type": @"pause", @"duration": @100},
|
|
198
201
|
@{@"type": @"pointerUp"},
|
|
@@ -200,13 +203,13 @@
|
|
|
200
203
|
},
|
|
201
204
|
],
|
|
202
205
|
|
|
203
|
-
// Chain element where pointerMove action item
|
|
206
|
+
// Chain element where pointerMove action item cannot use coordinates of the previous item
|
|
204
207
|
@[@{
|
|
205
208
|
@"type": @"pointer",
|
|
206
209
|
@"id": @"finger1",
|
|
207
210
|
@"parameters": @{@"pointerType": @"touch"},
|
|
208
211
|
@"actions": @[
|
|
209
|
-
@{@"type": @"pointerMove", @"duration": @0},
|
|
212
|
+
@{@"type": @"pointerMove", @"duration": @0, @"origin": @"pointer"},
|
|
210
213
|
@{@"type": @"pointerDown"},
|
|
211
214
|
@{@"type": @"pause", @"duration": @100},
|
|
212
215
|
@{@"type": @"pointerUp"},
|
|
@@ -214,34 +217,51 @@
|
|
|
214
217
|
},
|
|
215
218
|
],
|
|
216
219
|
|
|
217
|
-
// Chain element where
|
|
220
|
+
// Chain element where action items contains negative duration
|
|
218
221
|
@[@{
|
|
219
222
|
@"type": @"pointer",
|
|
220
223
|
@"id": @"finger1",
|
|
221
224
|
@"parameters": @{@"pointerType": @"touch"},
|
|
222
225
|
@"actions": @[
|
|
223
|
-
@{@"type": @"pointerMove", @"duration": @0, @"
|
|
226
|
+
@{@"type": @"pointerMove", @"duration": @0, @"x": @1, @"y": @1},
|
|
224
227
|
@{@"type": @"pointerDown"},
|
|
225
|
-
@{@"type": @"pause", @"duration":
|
|
228
|
+
@{@"type": @"pause", @"duration": @-100},
|
|
226
229
|
@{@"type": @"pointerUp"},
|
|
227
230
|
],
|
|
228
231
|
},
|
|
229
232
|
],
|
|
230
233
|
|
|
231
|
-
// Chain element where
|
|
234
|
+
// Chain element where a leading pause is followed directly by pointerDown,
|
|
235
|
+
// with no real pointerMove ever establishing a position
|
|
232
236
|
@[@{
|
|
233
237
|
@"type": @"pointer",
|
|
234
238
|
@"id": @"finger1",
|
|
235
239
|
@"parameters": @{@"pointerType": @"touch"},
|
|
236
240
|
@"actions": @[
|
|
237
|
-
@{@"type": @"
|
|
241
|
+
@{@"type": @"pause", @"duration": @0},
|
|
238
242
|
@{@"type": @"pointerDown"},
|
|
239
|
-
@{@"type": @"pause", @"duration":
|
|
243
|
+
@{@"type": @"pause", @"duration": @100},
|
|
240
244
|
@{@"type": @"pointerUp"},
|
|
241
245
|
],
|
|
242
246
|
},
|
|
243
247
|
],
|
|
244
|
-
|
|
248
|
+
|
|
249
|
+
// Chain element where a leading pause is followed directly by a relative
|
|
250
|
+
// pointerMove, with no real preceding position to be relative to
|
|
251
|
+
@[@{
|
|
252
|
+
@"type": @"pointer",
|
|
253
|
+
@"id": @"finger1",
|
|
254
|
+
@"parameters": @{@"pointerType": @"touch"},
|
|
255
|
+
@"actions": @[
|
|
256
|
+
@{@"type": @"pause", @"duration": @0},
|
|
257
|
+
@{@"type": @"pointerMove", @"duration": @0, @"origin": @"pointer"},
|
|
258
|
+
@{@"type": @"pointerDown"},
|
|
259
|
+
@{@"type": @"pause", @"duration": @100},
|
|
260
|
+
@{@"type": @"pointerUp"},
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
|
|
245
265
|
// Chain element where action items start with an incorrect one, because the correct one is canceled
|
|
246
266
|
@[@{
|
|
247
267
|
@"type": @"pointer",
|
|
@@ -299,6 +319,69 @@
|
|
|
299
319
|
[self verifyGesture:gesture orientation:UIDeviceOrientationPortrait];
|
|
300
320
|
}
|
|
301
321
|
|
|
322
|
+
- (void)testLeadingZeroDurationPauseDoesNotAddExtraTouch
|
|
323
|
+
{
|
|
324
|
+
// A leading pause must not defeat the down-after-move dedup logic in
|
|
325
|
+
// FBPointerDownItem and make WDA synthesize a second, separate touch-down
|
|
326
|
+
// for the same finger. Inspect the actual synthesized XCTest event stream
|
|
327
|
+
// (without dispatching it) rather than only checking the gesture's visible
|
|
328
|
+
// side effect, since a duplicate touch at the same point may still produce
|
|
329
|
+
// the same visible outcome.
|
|
330
|
+
XCUIElement *element = self.testedApplication.buttons[FBShowAlertButtonName];
|
|
331
|
+
NSDictionary<NSString *, id> *(^sequenceWithLeadingPause)(BOOL) = ^NSDictionary<NSString *, id> *(BOOL withLeadingPause) {
|
|
332
|
+
NSMutableArray<NSDictionary<NSString *, id> *> *actions = [NSMutableArray array];
|
|
333
|
+
if (withLeadingPause) {
|
|
334
|
+
[actions addObject:@{@"type": @"pause", @"duration": @0}];
|
|
335
|
+
}
|
|
336
|
+
[actions addObjectsFromArray:@[
|
|
337
|
+
@{@"type": @"pointerMove", @"duration": @0, @"origin": element, @"x": @0, @"y": @0},
|
|
338
|
+
@{@"type": @"pointerDown"},
|
|
339
|
+
@{@"type": @"pause", @"duration": @100},
|
|
340
|
+
@{@"type": @"pointerUp"},
|
|
341
|
+
]];
|
|
342
|
+
return @{
|
|
343
|
+
@"type": @"pointer",
|
|
344
|
+
@"id": @"finger1",
|
|
345
|
+
@"parameters": @{@"pointerType": @"touch"},
|
|
346
|
+
@"actions": actions.copy,
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
NSError *error;
|
|
351
|
+
FBW3CActionsSynthesizer *baselineSynthesizer =
|
|
352
|
+
[[FBW3CActionsSynthesizer alloc] initWithActions:@[sequenceWithLeadingPause(NO)]
|
|
353
|
+
forApplication:self.testedApplication
|
|
354
|
+
elementCache:nil
|
|
355
|
+
error:&error];
|
|
356
|
+
XCTAssertNotNil(baselineSynthesizer);
|
|
357
|
+
XCSynthesizedEventRecord *baselineRecord = [baselineSynthesizer synthesizeWithError:&error];
|
|
358
|
+
XCTAssertNotNil(baselineRecord, @"%@", error);
|
|
359
|
+
|
|
360
|
+
FBW3CActionsSynthesizer *pausedSynthesizer =
|
|
361
|
+
[[FBW3CActionsSynthesizer alloc] initWithActions:@[sequenceWithLeadingPause(YES)]
|
|
362
|
+
forApplication:self.testedApplication
|
|
363
|
+
elementCache:nil
|
|
364
|
+
error:&error];
|
|
365
|
+
XCTAssertNotNil(pausedSynthesizer);
|
|
366
|
+
XCSynthesizedEventRecord *pausedRecord = [pausedSynthesizer synthesizeWithError:&error];
|
|
367
|
+
XCTAssertNotNil(pausedRecord, @"%@", error);
|
|
368
|
+
|
|
369
|
+
XCTAssertEqual(baselineRecord.eventPaths.count, (NSUInteger)1);
|
|
370
|
+
XCTAssertEqual(pausedRecord.eventPaths.count, baselineRecord.eventPaths.count);
|
|
371
|
+
|
|
372
|
+
XCPointerEventPath *baselinePath = baselineRecord.eventPaths.firstObject;
|
|
373
|
+
XCPointerEventPath *pausedPath = pausedRecord.eventPaths.firstObject;
|
|
374
|
+
XCTAssertEqual(pausedPath.pointerEvents.count, baselinePath.pointerEvents.count);
|
|
375
|
+
for (NSUInteger i = 0; i < baselinePath.pointerEvents.count; i++) {
|
|
376
|
+
XCPointerEvent *baselineEvent = baselinePath.pointerEvents[i];
|
|
377
|
+
XCPointerEvent *pausedEvent = pausedPath.pointerEvents[i];
|
|
378
|
+
XCTAssertEqual(pausedEvent.eventType, baselineEvent.eventType);
|
|
379
|
+
XCTAssertEqualWithAccuracy(pausedEvent.offset, baselineEvent.offset, 0.001);
|
|
380
|
+
XCTAssertEqualWithAccuracy(pausedEvent.coordinate.x, baselineEvent.coordinate.x, 0.001);
|
|
381
|
+
XCTAssertEqualWithAccuracy(pausedEvent.coordinate.y, baselineEvent.coordinate.y, 0.001);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
302
385
|
- (void)testDoubleTap
|
|
303
386
|
{
|
|
304
387
|
NSArray<NSDictionary<NSString *, id> *> *gesture =
|