appium-webdriveragent 16.12.11 → 16.13.1
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 +20 -0
- package/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.h +53 -0
- package/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m +124 -0
- package/WebDriverAgentLib/Commands/FBElementCommands.m +20 -5
- package/WebDriverAgentLib/Info.plist +2 -2
- package/WebDriverAgentLib/Routing/FBHTTPServer.m +14 -1
- package/WebDriverAgentLib/Routing/FBTCPSocket.h +5 -1
- package/WebDriverAgentLib/Routing/FBTCPSocket.m +10 -1
- package/WebDriverAgentLib/Utilities/FBConfiguration.h +6 -0
- package/WebDriverAgentLib/Utilities/FBConfiguration.m +1 -0
- package/WebDriverAgentLib/Utilities/FBImageProcessor.m +3 -1
- package/WebDriverAgentLib/Utilities/FBMjpegServer.m +2 -0
- package/WebDriverAgentLib/Utilities/FBScreen.h +17 -0
- package/WebDriverAgentLib/Utilities/FBScreen.m +32 -0
- package/WebDriverAgentLib/Utilities/FBScreenshot.h +2 -2
- package/WebDriverAgentLib/Utilities/FBScreenshot.m +6 -2
- package/WebDriverAgentLib/Utilities/FBSettings.h +1 -0
- package/WebDriverAgentLib/Utilities/FBSettings.m +1 -0
- package/WebDriverAgentLib/Utilities/FBSettingsHandler.m +24 -0
- package/WebDriverAgentLib/Utilities/FBXPath.m +36 -4
- package/WebDriverAgentLib/Utilities/LRUCache/LRUCache.m +8 -5
- package/WebDriverAgentLib/Utilities/LRUCache/LRUCacheNode.h +1 -1
- package/WebDriverAgentTests/IntegrationTests/FBElementSwipingTests.m +95 -0
- package/WebDriverAgentTests/IntegrationTests/FBImageProcessorTests.m +34 -0
- package/WebDriverAgentTests/IntegrationTests/FBScreenTests.m +67 -0
- package/WebDriverAgentTests/UnitTests/FBHTTPServerTests.m +149 -5
- package/WebDriverAgentTests/UnitTests/FBLRUCacheTests.m +34 -0
- package/WebDriverAgentTests/UnitTests/FBXPathTests.m +36 -0
- package/build/lib/xcodebuild.d.ts +0 -1
- package/build/lib/xcodebuild.d.ts.map +1 -1
- package/build/lib/xcodebuild.js +7 -7
- package/build/lib/xcodebuild.js.map +1 -1
- package/lib/xcodebuild.ts +7 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## [16.13.1](https://github.com/appium/WebDriverAgent/compare/v16.13.0...v16.13.1) (2026-09-30)
|
|
2
|
+
|
|
3
|
+
### Performance Improvements
|
|
4
|
+
|
|
5
|
+
* reuse sibling indices and geometry during XML serialization ([#1283](https://github.com/appium/WebDriverAgent/issues/1283)) ([f352660](https://github.com/appium/WebDriverAgent/commit/f352660deb52728c195a875b4172e38e1a1496b8))
|
|
6
|
+
|
|
7
|
+
## [16.13.0](https://github.com/appium/WebDriverAgent/compare/v16.12.11...v16.13.0) (2026-09-30)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* add currentDisplayId setting for screenshots ([#1267](https://github.com/appium/WebDriverAgent/issues/1267)) ([c9366a2](https://github.com/appium/WebDriverAgent/commit/c9366a227b6de19381c96d56a548e9fb53d77dba))
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* apply screenshot scaling once when correcting orientation ([#1278](https://github.com/appium/WebDriverAgent/issues/1278)) ([17fd4d0](https://github.com/appium/WebDriverAgent/commit/17fd4d059c351d73384604be40bbade4a03be9a9))
|
|
16
|
+
* avoid infinite-point assertion and hang in coordinate drag gestures ([#1270](https://github.com/appium/WebDriverAgent/issues/1270)) ([c9290a0](https://github.com/appium/WebDriverAgent/commit/c9290a01a63fa195c8aba35c9f0cdad1cd9a6d64)), closes [#1056](https://github.com/appium/WebDriverAgent/issues/1056)
|
|
17
|
+
* cancel connections that end on the client side ([#1273](https://github.com/appium/WebDriverAgent/issues/1273)) ([261c08d](https://github.com/appium/WebDriverAgent/commit/261c08d53917b138eaf2f7d8a744d1151fe3bc8d))
|
|
18
|
+
* isolate xcodebuild readiness state between launches ([#1275](https://github.com/appium/WebDriverAgent/issues/1275)) ([ccc46fe](https://github.com/appium/WebDriverAgent/commit/ccc46fe6de1871a020205488533c1d5b9b7dc756))
|
|
19
|
+
* release LRU cache values when the cache is destroyed ([#1274](https://github.com/appium/WebDriverAgent/issues/1274)) ([fb52865](https://github.com/appium/WebDriverAgent/commit/fb5286515584157659ed86bccdbb64bc3094d91f))
|
|
20
|
+
|
|
1
21
|
## [16.12.11](https://github.com/appium/WebDriverAgent/compare/v16.12.10...v16.12.11) (2026-09-28)
|
|
2
22
|
|
|
3
23
|
### Bug Fixes
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
#import <XCTest/XCTest.h>
|
|
11
11
|
#import "FBElementCache.h"
|
|
12
12
|
|
|
13
|
+
@class XCSynthesizedEventRecord;
|
|
14
|
+
|
|
13
15
|
NS_ASSUME_NONNULL_BEGIN
|
|
14
16
|
|
|
15
17
|
@interface XCUIApplication (FBTouchAction)
|
|
@@ -24,6 +26,57 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
24
26
|
*/
|
|
25
27
|
- (BOOL)fb_performW3CActions:(NSArray *)actions elementCache:(nullable FBElementCache *)elementCache error:(NSError * _Nullable*)error;
|
|
26
28
|
|
|
29
|
+
/**
|
|
30
|
+
Presses at startCoordinate, drags to endCoordinate and holds there, producing the same touch event
|
|
31
|
+
path as -[XCUICoordinate pressForDuration:thenDragToCoordinate:withVelocity:thenHoldForDuration:].
|
|
32
|
+
|
|
33
|
+
Unlike that XCTest method, both screen points are resolved exactly once, up front, and validated
|
|
34
|
+
before anything is sent; the resulting event is then synthesized directly, without XCTest's
|
|
35
|
+
dispatch-time re-snapshot and quiescence wait of the application. XCTest resolves the points
|
|
36
|
+
inside that dispatch, so an application that is busy (e.g. right after an app switch) makes it
|
|
37
|
+
hang and then assert on an infinite point instead of failing cleanly.
|
|
38
|
+
See https://github.com/appium/WebDriverAgent/issues/1056
|
|
39
|
+
|
|
40
|
+
@param startCoordinate The coordinate to press at
|
|
41
|
+
@param pressDuration How long to hold still at startCoordinate before dragging, in seconds (>= 0)
|
|
42
|
+
@param endCoordinate The coordinate to drag to
|
|
43
|
+
@param velocity Drag velocity in points per second, or one of the XCUIGestureVelocity constants
|
|
44
|
+
@param holdDuration How long to hold still at endCoordinate before lifting, in seconds (>= 0)
|
|
45
|
+
@param error If there is an error, upon return contains an NSError object that describes the problem
|
|
46
|
+
@return YES if the gesture has been successfully performed
|
|
47
|
+
@throws FBInvalidArgumentException if a duration or the velocity is out of range
|
|
48
|
+
*/
|
|
49
|
+
- (BOOL)fb_pressAtCoordinate:(XCUICoordinate *)startCoordinate
|
|
50
|
+
forDuration:(NSTimeInterval)pressDuration
|
|
51
|
+
thenDragToCoordinate:(XCUICoordinate *)endCoordinate
|
|
52
|
+
withVelocity:(XCUIGestureVelocity)velocity
|
|
53
|
+
thenHoldForDuration:(NSTimeInterval)holdDuration
|
|
54
|
+
error:(NSError * _Nullable*)error;
|
|
55
|
+
|
|
56
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
57
|
+
/**
|
|
58
|
+
Builds the synthesized event used by
|
|
59
|
+
-fb_pressAtCoordinate:forDuration:thenDragToCoordinate:withVelocity:thenHoldForDuration:error:
|
|
60
|
+
from already resolved screen points. Exposed for testing.
|
|
61
|
+
|
|
62
|
+
@param startPoint The screen point to press at
|
|
63
|
+
@param pressDuration How long to hold still at startPoint before dragging, in seconds
|
|
64
|
+
@param endPoint The screen point to drag to
|
|
65
|
+
@param pointsPerSecond The drag speed
|
|
66
|
+
@param holdDuration How long to hold still at endPoint before lifting, in seconds
|
|
67
|
+
@param displayID The display to send the gesture to (0 is the main display)
|
|
68
|
+
@param interfaceOrientation The interface orientation the points are expressed in
|
|
69
|
+
@return The event record, ready to be synthesized
|
|
70
|
+
*/
|
|
71
|
+
+ (XCSynthesizedEventRecord *)fb_pressDragEventFromPoint:(CGPoint)startPoint
|
|
72
|
+
forDuration:(NSTimeInterval)pressDuration
|
|
73
|
+
toPoint:(CGPoint)endPoint
|
|
74
|
+
pointsPerSecond:(CGFloat)pointsPerSecond
|
|
75
|
+
thenHoldForDuration:(NSTimeInterval)holdDuration
|
|
76
|
+
displayID:(long long)displayID
|
|
77
|
+
interfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
|
|
78
|
+
#endif
|
|
79
|
+
|
|
27
80
|
@end
|
|
28
81
|
|
|
29
82
|
NS_ASSUME_NONNULL_END
|
|
@@ -9,17 +9,67 @@
|
|
|
9
9
|
|
|
10
10
|
#import "XCUIApplication+FBTouchAction.h"
|
|
11
11
|
|
|
12
|
+
#import <dlfcn.h>
|
|
13
|
+
|
|
12
14
|
#import "FBBaseActionsSynthesizer.h"
|
|
13
15
|
#import "FBConfiguration.h"
|
|
16
|
+
#import "FBErrorBuilder.h"
|
|
14
17
|
#import "FBExceptions.h"
|
|
15
18
|
#import "FBLogger.h"
|
|
19
|
+
#import "FBMathUtils.h"
|
|
16
20
|
#import "FBRunLoopSpinner.h"
|
|
17
21
|
#import "FBW3CActionsSynthesizer.h"
|
|
18
22
|
#import "FBXCTestDaemonsProxy.h"
|
|
23
|
+
#import "XCPointerEventPath.h"
|
|
24
|
+
#import "XCSynthesizedEventRecord.h"
|
|
25
|
+
#import "XCUIElement.h"
|
|
19
26
|
#import "XCUIElement+FBUtilities.h"
|
|
20
27
|
|
|
21
28
|
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
22
29
|
|
|
30
|
+
/**
|
|
31
|
+
Maps a drag XCUIGestureVelocity to the speed XCTest drags at. XCTest calls the result "pixels per
|
|
32
|
+
second", but divides the distance between the two screen points (which are in points) by it.
|
|
33
|
+
Prefers XCTest's own (exported, but undeclared) mapping function, so any future change to its
|
|
34
|
+
presets is picked up; falls back to the values it returns now.
|
|
35
|
+
|
|
36
|
+
@throws FBInvalidArgumentException if the velocity is neither one of the XCUIGestureVelocity
|
|
37
|
+
presets nor a positive finite number
|
|
38
|
+
*/
|
|
39
|
+
static CGFloat FBPointsPerSecondForDragVelocity(XCUIGestureVelocity velocity)
|
|
40
|
+
{
|
|
41
|
+
BOOL isDefault = FBFloatFuzzyEqualToFloat(velocity, XCUIGestureVelocityDefault, 0);
|
|
42
|
+
BOOL isSlow = FBFloatFuzzyEqualToFloat(velocity, XCUIGestureVelocitySlow, 0);
|
|
43
|
+
BOOL isFast = FBFloatFuzzyEqualToFloat(velocity, XCUIGestureVelocityFast, 0);
|
|
44
|
+
if (!isDefault && !isSlow && !isFast && !(isfinite(velocity) && velocity > 0)) {
|
|
45
|
+
NSString *reason = [NSString stringWithFormat:@"%@ is an invalid drag velocity. It must be greater than 0", @(velocity)];
|
|
46
|
+
@throw [NSException exceptionWithName:FBInvalidArgumentException reason:reason userInfo:nil];
|
|
47
|
+
}
|
|
48
|
+
static double (*xctestMapping)(double) = NULL;
|
|
49
|
+
static dispatch_once_t onceToken;
|
|
50
|
+
dispatch_once(&onceToken, ^{
|
|
51
|
+
xctestMapping = (double (*)(double))dlsym(RTLD_DEFAULT, "XCUIPixelsPerSecondForDragGestureVelocity");
|
|
52
|
+
if (NULL == xctestMapping) {
|
|
53
|
+
[FBLogger log:@"Could not find XCUIPixelsPerSecondForDragGestureVelocity. Using built-in drag velocity presets instead"];
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
if (NULL != xctestMapping) {
|
|
57
|
+
return (CGFloat)xctestMapping(velocity);
|
|
58
|
+
}
|
|
59
|
+
if (isSlow) {
|
|
60
|
+
return 250;
|
|
61
|
+
}
|
|
62
|
+
if (isFast) {
|
|
63
|
+
return 750;
|
|
64
|
+
}
|
|
65
|
+
return isDefault ? 500 : velocity;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static BOOL FBIsFinitePoint(CGPoint point)
|
|
69
|
+
{
|
|
70
|
+
return isfinite(point.x) && isfinite(point.y);
|
|
71
|
+
}
|
|
72
|
+
|
|
23
73
|
@implementation XCUIApplication (FBTouchAction)
|
|
24
74
|
|
|
25
75
|
+ (BOOL)handleEventSynthesWithError:(NSError *)error
|
|
@@ -65,6 +115,80 @@
|
|
|
65
115
|
return YES;
|
|
66
116
|
}
|
|
67
117
|
|
|
118
|
+
- (BOOL)fb_pressAtCoordinate:(XCUICoordinate *)startCoordinate
|
|
119
|
+
forDuration:(NSTimeInterval)pressDuration
|
|
120
|
+
thenDragToCoordinate:(XCUICoordinate *)endCoordinate
|
|
121
|
+
withVelocity:(XCUIGestureVelocity)velocity
|
|
122
|
+
thenHoldForDuration:(NSTimeInterval)holdDuration
|
|
123
|
+
error:(NSError **)error
|
|
124
|
+
{
|
|
125
|
+
CGFloat pointsPerSecond = FBPointsPerSecondForDragVelocity(velocity);
|
|
126
|
+
if (!isfinite(pressDuration) || pressDuration < 0 || !isfinite(holdDuration) || holdDuration < 0) {
|
|
127
|
+
NSString *reason = [NSString stringWithFormat:@"Drag press and hold durations must be non-negative numbers of seconds. Got %@ and %@", @(pressDuration), @(holdDuration)];
|
|
128
|
+
@throw [NSException exceptionWithName:FBInvalidArgumentException reason:reason userInfo:nil];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Resolved once, here, so the points validated below are exactly the ones sent. XCTest's own
|
|
132
|
+
// drag re-resolves them inside its event dispatch instead, after re-snapshotting the application.
|
|
133
|
+
CGPoint startPoint = startCoordinate.screenPoint;
|
|
134
|
+
CGPoint endPoint = endCoordinate.screenPoint;
|
|
135
|
+
if (!FBIsFinitePoint(startPoint) || !FBIsFinitePoint(endPoint)) {
|
|
136
|
+
return [[[FBErrorBuilder builder]
|
|
137
|
+
withDescriptionFormat:@"Cannot resolve the on-screen position of the drag gesture (from %@ to %@). "
|
|
138
|
+
"This usually means '%@' did not deliver a valid accessibility snapshot, e.g. because it is busy "
|
|
139
|
+
"or still transitioning after an application switch. Make sure it is idle and retry",
|
|
140
|
+
NSStringFromCGPoint(startPoint), NSStringFromCGPoint(endPoint), self.bundleID]
|
|
141
|
+
buildError:error];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Like XCTest, target the display of the element the start coordinate belongs to. displayID
|
|
145
|
+
// only reads the element's last snapshot, which resolving screenPoint above has just refreshed.
|
|
146
|
+
XCUIElement *referencedElement = startCoordinate.referencedElement;
|
|
147
|
+
long long displayID = [referencedElement respondsToSelector:@selector(displayID)]
|
|
148
|
+
? referencedElement.displayID
|
|
149
|
+
: 0;
|
|
150
|
+
XCSynthesizedEventRecord *event = [self.class fb_pressDragEventFromPoint:startPoint
|
|
151
|
+
forDuration:pressDuration
|
|
152
|
+
toPoint:endPoint
|
|
153
|
+
pointsPerSecond:pointsPerSecond
|
|
154
|
+
thenHoldForDuration:holdDuration
|
|
155
|
+
displayID:displayID
|
|
156
|
+
interfaceOrientation:self.interfaceOrientation];
|
|
157
|
+
if (![self fb_synthesizeEvent:event error:error]) {
|
|
158
|
+
return NO;
|
|
159
|
+
}
|
|
160
|
+
[self fb_waitUntilStableWithTimeout:FBConfiguration.sharedInstance.animationCoolOffTimeout];
|
|
161
|
+
return YES;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
+ (XCSynthesizedEventRecord *)fb_pressDragEventFromPoint:(CGPoint)startPoint
|
|
165
|
+
forDuration:(NSTimeInterval)pressDuration
|
|
166
|
+
toPoint:(CGPoint)endPoint
|
|
167
|
+
pointsPerSecond:(CGFloat)pointsPerSecond
|
|
168
|
+
thenHoldForDuration:(NSTimeInterval)holdDuration
|
|
169
|
+
displayID:(long long)displayID
|
|
170
|
+
interfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
|
|
171
|
+
{
|
|
172
|
+
// Mirrors XCTest's own press-hold-drag event builder
|
|
173
|
+
XCPointerEventPath *path = [[XCPointerEventPath alloc] initForTouchAtPoint:startPoint offset:0];
|
|
174
|
+
if (pressDuration > 0) {
|
|
175
|
+
[path moveToPoint:startPoint atOffset:pressDuration];
|
|
176
|
+
}
|
|
177
|
+
CGFloat distance = (CGFloat)hypot(endPoint.x - startPoint.x, endPoint.y - startPoint.y);
|
|
178
|
+
NSTimeInterval dragEndOffset = pressDuration + distance / pointsPerSecond;
|
|
179
|
+
[path moveToPoint:endPoint atOffset:dragEndOffset];
|
|
180
|
+
[path liftUpAtOffset:dragEndOffset + holdDuration];
|
|
181
|
+
|
|
182
|
+
NSString *name = @"Press, drag and hold";
|
|
183
|
+
XCSynthesizedEventRecord *event = [XCSynthesizedEventRecord instancesRespondToSelector:@selector(initWithName:displayID:interfaceOrientation:)]
|
|
184
|
+
? [[XCSynthesizedEventRecord alloc] initWithName:name
|
|
185
|
+
displayID:(unsigned long long)displayID
|
|
186
|
+
interfaceOrientation:interfaceOrientation]
|
|
187
|
+
: [[XCSynthesizedEventRecord alloc] initWithName:name interfaceOrientation:interfaceOrientation];
|
|
188
|
+
[event addPointerEventPath:path];
|
|
189
|
+
return event;
|
|
190
|
+
}
|
|
191
|
+
|
|
68
192
|
- (BOOL)fb_synthesizeEvent:(XCSynthesizedEventRecord *)event error:(NSError *__autoreleasing*)error
|
|
69
193
|
{
|
|
70
194
|
return [FBXCTestDaemonsProxy synthesizeEventWithRecord:event error:error];
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
#import "FBRuntimeUtils.h"
|
|
23
23
|
#import "NSPredicate+FBFormat.h"
|
|
24
24
|
#import "XCTestPrivateSymbols.h"
|
|
25
|
+
#import "XCUIApplication+FBTouchAction.h"
|
|
25
26
|
#import "XCUICoordinate.h"
|
|
26
27
|
#import "XCUIDevice.h"
|
|
27
28
|
#import "XCUIElement+FBIsVisible.h"
|
|
@@ -370,10 +371,14 @@
|
|
|
370
371
|
if (nil == endCoordinate) {
|
|
371
372
|
return FBResponseWithStatus([FBCommandStatus invalidElementStateErrorWithMessage:error.description traceback:nil]);
|
|
372
373
|
}
|
|
373
|
-
[
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
374
|
+
if (![application fb_pressAtCoordinate:startCoordinate
|
|
375
|
+
forDuration:[request.arguments[@"pressDuration"] doubleValue]
|
|
376
|
+
thenDragToCoordinate:endCoordinate
|
|
377
|
+
withVelocity:[request.arguments[@"velocity"] doubleValue]
|
|
378
|
+
thenHoldForDuration:[request.arguments[@"holdDuration"] doubleValue]
|
|
379
|
+
error:&error]) {
|
|
380
|
+
return FBResponseWithStatus([FBCommandStatus invalidElementStateErrorWithMessage:error.description traceback:nil]);
|
|
381
|
+
}
|
|
377
382
|
return FBResponseWithOK();
|
|
378
383
|
}
|
|
379
384
|
|
|
@@ -456,7 +461,17 @@
|
|
|
456
461
|
return FBResponseWithStatus([FBCommandStatus invalidElementStateErrorWithMessage:error.description traceback:nil]);
|
|
457
462
|
}
|
|
458
463
|
NSTimeInterval duration = [request.arguments[@"duration"] doubleValue];
|
|
459
|
-
[
|
|
464
|
+
XCUIApplication *application = [target isKindOfClass:XCUIApplication.class]
|
|
465
|
+
? (XCUIApplication *)target
|
|
466
|
+
: target.application;
|
|
467
|
+
if (![application fb_pressAtCoordinate:startCoordinate
|
|
468
|
+
forDuration:duration
|
|
469
|
+
thenDragToCoordinate:endCoordinate
|
|
470
|
+
withVelocity:XCUIGestureVelocityDefault
|
|
471
|
+
thenHoldForDuration:0
|
|
472
|
+
error:&error]) {
|
|
473
|
+
return FBResponseWithStatus([FBCommandStatus invalidElementStateErrorWithMessage:error.description traceback:nil]);
|
|
474
|
+
}
|
|
460
475
|
return FBResponseWithOK();
|
|
461
476
|
}
|
|
462
477
|
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
<key>CFBundlePackageType</key>
|
|
16
16
|
<string>FMWK</string>
|
|
17
17
|
<key>CFBundleShortVersionString</key>
|
|
18
|
-
<string>16.
|
|
18
|
+
<string>16.13.1</string>
|
|
19
19
|
<key>CFBundleSignature</key>
|
|
20
20
|
<string>????</string>
|
|
21
21
|
<key>CFBundleVersion</key>
|
|
22
|
-
<string>16.
|
|
22
|
+
<string>16.13.1</string>
|
|
23
23
|
<key>NSPrincipalClass</key>
|
|
24
24
|
<string/>
|
|
25
25
|
</dict>
|
|
@@ -340,12 +340,18 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
|
|
|
340
340
|
|
|
341
341
|
- (void)didClientDisconnect:(nw_connection_t)client
|
|
342
342
|
{
|
|
343
|
+
BOOL isAwaitingResponse;
|
|
343
344
|
@synchronized (self.connectionBuffers) {
|
|
345
|
+
isAwaitingResponse = [self.connectionsAwaitingResponse containsObject:client];
|
|
344
346
|
[self.connectionBuffers removeObjectForKey:client];
|
|
345
347
|
[self.pendingRequestHeaders removeObjectForKey:client];
|
|
346
|
-
[self.connectionsAwaitingResponse removeObject:client];
|
|
347
348
|
[self.incompleteRequestStarts removeObjectForKey:client];
|
|
348
349
|
}
|
|
350
|
+
// A client that only stopped sending may still read the response to its in-flight request, so
|
|
351
|
+
// that response's send completion closes the connection instead (it finds no buffer left).
|
|
352
|
+
if (!isAwaitingResponse) {
|
|
353
|
+
nw_connection_cancel(client);
|
|
354
|
+
}
|
|
349
355
|
}
|
|
350
356
|
|
|
351
357
|
- (void)client:(nw_connection_t)client didReceiveData:(NSData *)data
|
|
@@ -881,14 +887,21 @@ static const int64_t FBStaleConnectionSweepIntervalSec = 10;
|
|
|
881
887
|
if (nil == queuedSelf) {
|
|
882
888
|
return;
|
|
883
889
|
}
|
|
890
|
+
BOOL isDisconnected;
|
|
884
891
|
@synchronized (queuedSelf.connectionBuffers) {
|
|
885
892
|
[queuedSelf.connectionsAwaitingResponse removeObject:client];
|
|
893
|
+
isDisconnected = nil == [queuedSelf.connectionBuffers objectForKey:client];
|
|
886
894
|
// Mid-request connections get their window from when parsing could resume, not from the
|
|
887
895
|
// previous request. Absent entries stay absent, so idle keep-alives remain exempt.
|
|
888
896
|
if (nil != [queuedSelf.incompleteRequestStarts objectForKey:client]) {
|
|
889
897
|
[queuedSelf.incompleteRequestStarts setObject:[NSDate date] forKey:client];
|
|
890
898
|
}
|
|
891
899
|
}
|
|
900
|
+
if (isDisconnected) {
|
|
901
|
+
// The peer stopped sending while this request was in flight - see -didClientDisconnect:.
|
|
902
|
+
[queuedSelf closeClient:client];
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
892
905
|
[queuedSelf processBufferForClient:client];
|
|
893
906
|
});
|
|
894
907
|
}];
|
|
@@ -36,7 +36,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
36
36
|
- (void)client:(nw_connection_t)client didReceiveData:(NSData *)data;
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
The callback which is fired when TCP client disconnects
|
|
39
|
+
The callback which is fired when TCP client disconnects or finishes sending
|
|
40
|
+
|
|
41
|
+
A connection that failed is cancelled by the socket. One whose peer only finished sending stays
|
|
42
|
+
writable (the peer may be awaiting a response), and the delegate must cancel it once it has
|
|
43
|
+
nothing left to send - an uncancelled connection keeps its socket open.
|
|
40
44
|
|
|
41
45
|
@param client The actual disconnected client
|
|
42
46
|
*/
|
|
@@ -137,6 +137,8 @@
|
|
|
137
137
|
} else if (nw_connection_state_failed == state || nw_connection_state_cancelled == state) {
|
|
138
138
|
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
139
139
|
[strongSelf handleDisconnectForConnection:connection];
|
|
140
|
+
// A failed connection keeps its socket until cancelled (a no-op once cancelled).
|
|
141
|
+
nw_connection_cancel(connection);
|
|
140
142
|
}
|
|
141
143
|
});
|
|
142
144
|
nw_connection_start(connection);
|
|
@@ -167,7 +169,14 @@
|
|
|
167
169
|
}
|
|
168
170
|
}
|
|
169
171
|
}
|
|
170
|
-
if (nil != receiveError
|
|
172
|
+
if (nil != receiveError) {
|
|
173
|
+
[strongSelf handleDisconnectForConnection:connection];
|
|
174
|
+
nw_connection_cancel(connection);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (isComplete && nil == content) {
|
|
178
|
+
// The peer is done sending but may still be reading - a half-closed client awaits its
|
|
179
|
+
// response - so the delegate cancels the connection once it has nothing left to send.
|
|
171
180
|
[strongSelf handleDisconnectForConnection:connection];
|
|
172
181
|
return;
|
|
173
182
|
}
|
|
@@ -368,6 +368,12 @@ typedef NS_ENUM(NSInteger, FBConfigurationKeyboardPreference) {
|
|
|
368
368
|
*/
|
|
369
369
|
@property (atomic, assign) BOOL enforceCustomSnapshots;
|
|
370
370
|
|
|
371
|
+
/**
|
|
372
|
+
* The identifier of the display targeted by screenshots.
|
|
373
|
+
* nil means the main display. Reset to nil for each new session.
|
|
374
|
+
*/
|
|
375
|
+
@property (atomic, copy, nullable) NSNumber *currentDisplayId;
|
|
376
|
+
|
|
371
377
|
@end
|
|
372
378
|
|
|
373
379
|
NS_ASSUME_NONNULL_END
|
|
@@ -357,6 +357,7 @@ static NSString *const axSettingsClassName = @"AXSettings";
|
|
|
357
357
|
FBSetCustomParameterForElementSnapshot(FBSnapshotMaxChildrenKey, @INT_MAX);
|
|
358
358
|
self.useClearTextShortcut = YES;
|
|
359
359
|
self.limitXpathContextScope = YES;
|
|
360
|
+
self.currentDisplayId = nil;
|
|
360
361
|
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
361
362
|
self.screenshotOrientationStorage = UIInterfaceOrientationUnknown;
|
|
362
363
|
#endif
|
|
@@ -144,7 +144,9 @@ const CGFloat FBMaxCompressionQuality = 1.0f;
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
UIGraphicsImageRendererFormat *format = [[UIGraphicsImageRendererFormat alloc] init];
|
|
147
|
-
|
|
147
|
+
// scaledSize already applies the requested factor in points. Preserve the
|
|
148
|
+
// image's pixels-per-point scale instead of applying that factor twice.
|
|
149
|
+
format.scale = image.scale;
|
|
148
150
|
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:scaledSize
|
|
149
151
|
format:format];
|
|
150
152
|
UIImageOrientation desiredOrientation = orientation == nil
|
|
@@ -221,6 +221,8 @@ static NSUInteger FBNormalizedMjpegFramerate(NSUInteger framerate)
|
|
|
221
221
|
[self.listeningClients removeObject:client];
|
|
222
222
|
[self.pendingFrameCounts removeObjectForKey:client];
|
|
223
223
|
}
|
|
224
|
+
// Nothing is owed to a client that stopped listening, so its socket can go right away.
|
|
225
|
+
nw_connection_cancel(client);
|
|
224
226
|
[FBLogger log:@"Disconnected a client from screenshots broadcast"];
|
|
225
227
|
}
|
|
226
228
|
|
|
@@ -17,6 +17,23 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
17
17
|
*/
|
|
18
18
|
+ (nullable NSArray<NSDictionary<NSString *, id> *> *)screensWithError:(NSError **)error;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
The display with the given identifier
|
|
22
|
+
|
|
23
|
+
@param displayID The display identifier, as returned by screensWithError:
|
|
24
|
+
@param error Set if the display is not available
|
|
25
|
+
@return The matching display or nil
|
|
26
|
+
*/
|
|
27
|
+
+ (nullable XCUIScreen *)screenWithDisplayID:(long long)displayID error:(NSError **)error;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
The display selected by the currentDisplayId setting, or the main display if the setting is not set
|
|
31
|
+
|
|
32
|
+
@param error Set if the selected display is no longer available
|
|
33
|
+
@return The selected display or nil
|
|
34
|
+
*/
|
|
35
|
+
+ (nullable XCUIScreen *)currentScreenWithError:(NSError **)error;
|
|
36
|
+
|
|
20
37
|
/**
|
|
21
38
|
The identifier of the main device's display
|
|
22
39
|
*/
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
#import "FBScreen.h"
|
|
10
|
+
#import "FBConfiguration.h"
|
|
11
|
+
#import "FBErrorBuilder.h"
|
|
10
12
|
#import "XCUIElement+FBIsVisible.h"
|
|
11
13
|
#import "FBXCodeCompatibility.h"
|
|
12
14
|
#import "XCUIDevice.h"
|
|
@@ -40,6 +42,36 @@
|
|
|
40
42
|
return result.copy;
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
+ (nullable XCUIScreen *)screenWithDisplayID:(long long)displayID error:(NSError **)error
|
|
46
|
+
{
|
|
47
|
+
NSArray<XCUIScreen *> *screens = [XCUIDevice.sharedDevice screensOrError:error];
|
|
48
|
+
if (nil == screens) {
|
|
49
|
+
return nil;
|
|
50
|
+
}
|
|
51
|
+
for (XCUIScreen *screen in screens) {
|
|
52
|
+
if (screen.displayID == displayID) {
|
|
53
|
+
return screen;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
NSMutableArray<NSNumber *> *availableIDs = [NSMutableArray arrayWithCapacity:screens.count];
|
|
57
|
+
for (XCUIScreen *screen in screens) {
|
|
58
|
+
[availableIDs addObject:@(screen.displayID)];
|
|
59
|
+
}
|
|
60
|
+
[[FBErrorBuilder.builder withDescriptionFormat:@"No display with id %lld is available. Available display ids: [%@]",
|
|
61
|
+
displayID, [availableIDs componentsJoinedByString:@", "]]
|
|
62
|
+
buildError:error];
|
|
63
|
+
return nil;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
+ (nullable XCUIScreen *)currentScreenWithError:(NSError **)error
|
|
67
|
+
{
|
|
68
|
+
NSNumber *displayID = FBConfiguration.sharedInstance.currentDisplayId;
|
|
69
|
+
if (nil == displayID) {
|
|
70
|
+
return XCUIScreen.mainScreen;
|
|
71
|
+
}
|
|
72
|
+
return [self screenWithDisplayID:displayID.longLongValue error:error];
|
|
73
|
+
}
|
|
74
|
+
|
|
43
75
|
+ (long long)displayID
|
|
44
76
|
{
|
|
45
77
|
return XCUIScreen.mainScreen.displayID;
|
|
@@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
14
14
|
@interface FBScreenshot : NSObject
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
Retrieves non-scaled screenshot of the
|
|
17
|
+
Retrieves non-scaled screenshot of the display selected by the currentDisplayId setting
|
|
18
18
|
|
|
19
19
|
@param quality The number in range 0-3, where 0 is PNG (lossless), 3 is HEIC (lossless), 1- low quality JPEG and 2 - high quality JPEG
|
|
20
20
|
@param error If there is an error, upon return contains an NSError object that describes the problem.
|
|
@@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
24
24
|
error:(NSError **)error;
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
Retrieves non-scaled screenshot of the
|
|
27
|
+
Retrieves non-scaled screenshot of the display selected by the currentDisplayId setting
|
|
28
28
|
|
|
29
29
|
@param screenID The screen identifier to take the screenshot from
|
|
30
30
|
@param compressionQuality Normalized screenshot quality value in range 0..1, where 1 is the best quality
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
#import "FBImageProcessor.h"
|
|
16
16
|
#import "FBLogger.h"
|
|
17
17
|
#import "FBMacros.h"
|
|
18
|
+
#import "FBScreen.h"
|
|
18
19
|
#import "FBXCodeCompatibility.h"
|
|
19
20
|
#import "FBXCTestDaemonsProxy.h"
|
|
20
21
|
#import "XCTMessagingChannel_RunnerToDaemon-Protocol.h"
|
|
@@ -60,8 +61,11 @@ NSString *formatTimeInterval(NSTimeInterval interval) {
|
|
|
60
61
|
+ (NSData *)takeInOriginalResolutionWithQuality:(NSUInteger)quality
|
|
61
62
|
error:(NSError **)error
|
|
62
63
|
{
|
|
63
|
-
XCUIScreen *
|
|
64
|
-
|
|
64
|
+
XCUIScreen *screen = [FBScreen currentScreenWithError:error];
|
|
65
|
+
if (nil == screen) {
|
|
66
|
+
return nil;
|
|
67
|
+
}
|
|
68
|
+
return [self.class takeWithScreenID:screen.displayID
|
|
65
69
|
scale:SCREENSHOT_SCALE
|
|
66
70
|
compressionQuality:[self.class compressionQualityWithQuality:quality]
|
|
67
71
|
sourceUTI:[self.class imageUtiWithQuality:quality]
|
|
@@ -46,5 +46,6 @@ extern NSString *const FB_SETTING_INCLUDE_NATIVE_ACCESSIBILITY_ELEMENT_IN_PAGE_S
|
|
|
46
46
|
extern NSString *const FB_SETTING_INCLUDE_MIN_MAX_VALUE_IN_PAGE_SOURCE;
|
|
47
47
|
extern NSString *const FB_SETTING_INCLUDE_CUSTOM_ACTIONS_IN_PAGE_SOURCE;
|
|
48
48
|
extern NSString *const FB_SETTING_ENFORCE_CUSTOM_SNAPSHOTS;
|
|
49
|
+
extern NSString *const FB_SETTING_CURRENT_DISPLAY_ID;
|
|
49
50
|
|
|
50
51
|
NS_ASSUME_NONNULL_END
|
|
@@ -42,3 +42,4 @@ NSString* const FB_SETTING_INCLUDE_NATIVE_ACCESSIBILITY_ELEMENT_IN_PAGE_SOURCE =
|
|
|
42
42
|
NSString* const FB_SETTING_INCLUDE_MIN_MAX_VALUE_IN_PAGE_SOURCE = @"includeMinMaxValueInPageSource";
|
|
43
43
|
NSString* const FB_SETTING_INCLUDE_CUSTOM_ACTIONS_IN_PAGE_SOURCE = @"includeCustomActionsInPageSource";
|
|
44
44
|
NSString* const FB_SETTING_ENFORCE_CUSTOM_SNAPSHOTS = @"enforceCustomSnapshots";
|
|
45
|
+
NSString* const FB_SETTING_CURRENT_DISPLAY_ID = @"currentDisplayId";
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
#import "FBClassChainQueryParser.h"
|
|
13
13
|
#import "FBCommandStatus.h"
|
|
14
14
|
#import "FBConfiguration.h"
|
|
15
|
+
#import "FBScreen.h"
|
|
15
16
|
#import "FBSession.h"
|
|
16
17
|
#import "FBSettings.h"
|
|
17
18
|
|
|
@@ -33,6 +34,7 @@ static NSSet<NSString *> *FBNilClearableSettingKeys(void)
|
|
|
33
34
|
FB_SETTING_ACCEPT_ALERT_BUTTON_SELECTOR,
|
|
34
35
|
FB_SETTING_DISMISS_ALERT_BUTTON_SELECTOR,
|
|
35
36
|
FB_SETTING_AUTO_CLICK_ALERT_SELECTOR,
|
|
37
|
+
FB_SETTING_CURRENT_DISPLAY_ID,
|
|
36
38
|
nil];
|
|
37
39
|
});
|
|
38
40
|
return keys;
|
|
@@ -190,6 +192,25 @@ static NSSet<NSString *> *FBNilClearableSettingKeys(void)
|
|
|
190
192
|
FBConfiguration.sharedInstance.limitXpathContextScope = [value boolValue];
|
|
191
193
|
return nil;
|
|
192
194
|
};
|
|
195
|
+
map[FB_SETTING_CURRENT_DISPLAY_ID] = ^FBCommandStatus *(FBSession *session, id value) {
|
|
196
|
+
if (nil == value) {
|
|
197
|
+
FBConfiguration.sharedInstance.currentDisplayId = nil;
|
|
198
|
+
return nil;
|
|
199
|
+
}
|
|
200
|
+
if (![value isKindOfClass:NSNumber.class]
|
|
201
|
+
|| CFGetTypeID((__bridge CFTypeRef)value) == CFBooleanGetTypeID()
|
|
202
|
+
|| [value compare:@([value longLongValue])] != NSOrderedSame) {
|
|
203
|
+
return [FBCommandStatus invalidArgumentErrorWithMessage:@"currentDisplayId must be an integer display id returned by /wda/screens"
|
|
204
|
+
traceback:nil];
|
|
205
|
+
}
|
|
206
|
+
NSError *error;
|
|
207
|
+
if (nil == [FBScreen screenWithDisplayID:[value longLongValue] error:&error]) {
|
|
208
|
+
return [FBCommandStatus invalidArgumentErrorWithMessage:error.localizedDescription
|
|
209
|
+
traceback:nil];
|
|
210
|
+
}
|
|
211
|
+
FBConfiguration.sharedInstance.currentDisplayId = @([value longLongValue]);
|
|
212
|
+
return nil;
|
|
213
|
+
};
|
|
193
214
|
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
194
215
|
map[FB_SETTING_SCREENSHOT_ORIENTATION] = ^FBCommandStatus *(FBSession *session, id value) {
|
|
195
216
|
NSError *error;
|
|
@@ -310,6 +331,9 @@ static NSSet<NSString *> *FBNilClearableSettingKeys(void)
|
|
|
310
331
|
map[FB_SETTING_LIMIT_XPATH_CONTEXT_SCOPE] = ^id(FBSession *session) {
|
|
311
332
|
return @(FBConfiguration.sharedInstance.limitXpathContextScope);
|
|
312
333
|
};
|
|
334
|
+
map[FB_SETTING_CURRENT_DISPLAY_ID] = ^id(FBSession *session) {
|
|
335
|
+
return FBConfiguration.sharedInstance.currentDisplayId ?: @([FBScreen displayID]);
|
|
336
|
+
};
|
|
313
337
|
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
314
338
|
map[FB_SETTING_SCREENSHOT_ORIENTATION] = ^id(FBSession *session) {
|
|
315
339
|
return FBConfiguration.sharedInstance.humanReadableScreenshotOrientation;
|