appium-mac2-driver 4.3.6 → 4.5.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.
- package/CHANGELOG.md +12 -0
- package/WebDriverAgentMac/IntegrationTests/AMSessionTests.m +14 -0
- package/WebDriverAgentMac/IntegrationTests/AMWindowTests.m +427 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Commands/AMWindowCommands.m +63 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Commands/FBDebugCommands.m +2 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Commands/FBSessionCommands.m +17 -5
- package/WebDriverAgentMac/WebDriverAgentLib/Routing/FBCommandStatus.h +3 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Routing/FBCommandStatus.m +13 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Routing/FBSession.m +5 -1
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMScreenUtils.h +4 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMScreenUtils.m +23 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMSnapshotUtils.h +18 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMSnapshotUtils.m +43 -28
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMWindowUtils.h +111 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMWindowUtils.m +780 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMXCTAccessibility.h +61 -0
- package/WebDriverAgentMac/WebDriverAgentLib/Utilities/AMXCTAccessibility.m +108 -0
- package/WebDriverAgentMac/WebDriverAgentMac.xcodeproj/project.pbxproj +20 -0
- package/build/lib/commands/native-record-screen.d.ts +2 -0
- package/build/lib/commands/native-record-screen.d.ts.map +1 -1
- package/build/lib/commands/native-record-screen.js.map +1 -1
- package/build/lib/commands/windows.d.ts +22 -0
- package/build/lib/commands/windows.d.ts.map +1 -0
- package/build/lib/commands/windows.js +32 -0
- package/build/lib/commands/windows.js.map +1 -0
- package/build/lib/driver.d.ts +15 -0
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +55 -32
- package/build/lib/driver.js.map +1 -1
- package/build/lib/execute-method-map.d.ts +9 -0
- package/build/lib/execute-method-map.d.ts.map +1 -1
- package/build/lib/execute-method-map.js +9 -0
- package/build/lib/execute-method-map.js.map +1 -1
- package/build/lib/session-claim-handler.d.ts +41 -0
- package/build/lib/session-claim-handler.d.ts.map +1 -0
- package/build/lib/session-claim-handler.js +236 -0
- package/build/lib/session-claim-handler.js.map +1 -0
- package/build/lib/types.d.ts +26 -0
- package/build/lib/types.d.ts.map +1 -1
- package/build/lib/wda-mac.d.ts +6 -2
- package/build/lib/wda-mac.d.ts.map +1 -1
- package/build/lib/wda-mac.js +39 -9
- package/build/lib/wda-mac.js.map +1 -1
- package/lib/commands/native-record-screen.ts +2 -0
- package/lib/commands/windows.ts +44 -0
- package/lib/driver.ts +59 -31
- package/lib/execute-method-map.ts +9 -0
- package/lib/session-claim-handler.ts +306 -0
- package/lib/types.ts +28 -0
- package/lib/wda-mac.ts +41 -10
- package/node_modules/appium-xcode/CHANGELOG.md +7 -0
- package/node_modules/appium-xcode/package.json +4 -4
- package/node_modules/async-lock/AUTHORS +21 -0
- package/node_modules/async-lock/History.md +132 -0
- package/node_modules/async-lock/LICENSE +22 -0
- package/node_modules/async-lock/README.md +177 -0
- package/node_modules/async-lock/index.js +2 -0
- package/node_modules/async-lock/lib/index.js +312 -0
- package/node_modules/async-lock/package.json +48 -0
- package/node_modules/bare-fs/binding.c +1 -1
- package/node_modules/bare-fs/index.js +30 -6
- package/node_modules/bare-fs/package.json +1 -1
- package/node_modules/bare-fs/prebuilds/android-arm/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/android-arm64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/android-ia32/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/android-x64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/darwin-arm64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/darwin-x64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/ios-arm64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/ios-arm64-simulator/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/ios-x64-simulator/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/linux-arm64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/linux-x64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/win32-arm64/bare-fs.bare +0 -0
- package/node_modules/bare-fs/prebuilds/win32-x64/bare-fs.bare +0 -0
- package/node_modules/bare-url/README.md +4 -0
- package/node_modules/bare-url/index.d.ts +3 -0
- package/node_modules/bare-url/index.js +42 -18
- package/node_modules/bare-url/lib/errors.js +4 -0
- package/node_modules/bare-url/package.json +1 -1
- package/node_modules/bare-url/prebuilds/android-arm/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/android-arm64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/android-ia32/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/android-x64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/darwin-arm64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/darwin-x64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/ios-arm64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/ios-arm64-simulator/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/ios-x64-simulator/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/linux-arm64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/linux-x64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/win32-arm64/bare-url.bare +0 -0
- package/node_modules/bare-url/prebuilds/win32-x64/bare-url.bare +0 -0
- package/node_modules/readable-stream/lib/internal/shims/process.js +19 -0
- package/node_modules/readable-stream/lib/internal/streams/destroy.js +1 -1
- package/node_modules/readable-stream/lib/internal/streams/duplexify.js +1 -1
- package/node_modules/readable-stream/lib/internal/streams/end-of-stream.js +1 -1
- package/node_modules/readable-stream/lib/internal/streams/from.js +1 -1
- package/node_modules/readable-stream/lib/internal/streams/legacy.js +6 -1
- package/node_modules/readable-stream/lib/internal/streams/pipeline.js +1 -1
- package/node_modules/readable-stream/lib/internal/streams/readable.js +1 -1
- package/node_modules/readable-stream/lib/internal/streams/writable.js +1 -1
- package/node_modules/readable-stream/package.json +3 -2
- package/node_modules/tar-stream/package.json +7 -7
- package/package.json +6 -3
- package/scripts/setup-accessibility.mjs +175 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [4.5.0](https://github.com/appium/appium-mac2-driver/compare/v4.4.0...v4.5.0) (2026-10-19)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* add window listing and adjustment extension methods ([#428](https://github.com/appium/appium-mac2-driver/issues/428)) ([42759b6](https://github.com/appium/appium-mac2-driver/commit/42759b603ade35832369b22f790ebe083bff2f3d))
|
|
6
|
+
|
|
7
|
+
## [4.4.0](https://github.com/appium/appium-mac2-driver/compare/v4.3.6...v4.4.0) (2026-10-11)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* enforce a single session, a new session replaces the previous one ([#426](https://github.com/appium/appium-mac2-driver/issues/426)) ([fb10372](https://github.com/appium/appium-mac2-driver/commit/fb10372c16d861fb7ff9463b345ba6fcb3f67387))
|
|
12
|
+
|
|
1
13
|
## [4.3.6](https://github.com/appium/appium-mac2-driver/compare/v4.3.5...v4.3.6) (2026-10-02)
|
|
2
14
|
|
|
3
15
|
### Bug Fixes
|
|
@@ -43,6 +43,20 @@ static NSString *const SETTINGS_BUNDLE_ID = @"com.apple.systempreferences";
|
|
|
43
43
|
[super tearDown];
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
- (void)testNewSessionOverridesThePreviousOne
|
|
47
|
+
{
|
|
48
|
+
NSString *oldId = self.session.identifier;
|
|
49
|
+
FBSession *newSession = [FBSession initWithApplication:nil];
|
|
50
|
+
XCTAssertNotEqualObjects(oldId, newSession.identifier);
|
|
51
|
+
XCTAssertEqual(FBSession.activeSession, newSession);
|
|
52
|
+
XCTAssertNil([FBSession sessionWithIdentifier:oldId]);
|
|
53
|
+
// Killing the stale session must not drop the active one
|
|
54
|
+
[self.session kill];
|
|
55
|
+
XCTAssertEqual(FBSession.activeSession, newSession);
|
|
56
|
+
[newSession kill];
|
|
57
|
+
XCTAssertNil(FBSession.activeSession);
|
|
58
|
+
}
|
|
59
|
+
|
|
46
60
|
- (void)testSettingsAppCanBeOpenedInScopeOfTheCurrentSession
|
|
47
61
|
{
|
|
48
62
|
[self.session launchApplicationWithBundleId:SETTINGS_BUNDLE_ID
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
* you may not use this file except in compliance with the License.
|
|
4
|
+
* See the NOTICE file distributed with this work for additional
|
|
5
|
+
* information regarding copyright ownership.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
#import <XCTest/XCTest.h>
|
|
18
|
+
|
|
19
|
+
#import "AMIntegrationTestCase.h"
|
|
20
|
+
#import "AMScreenUtils.h"
|
|
21
|
+
#import "AMSnapshotUtils.h"
|
|
22
|
+
#import "AMWindowUtils.h"
|
|
23
|
+
#import "AMXCTAccessibility.h"
|
|
24
|
+
#import "FBTestMacros.h"
|
|
25
|
+
|
|
26
|
+
static const CGFloat AM_GEOMETRY_ACCURACY = 1.0;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
The windows are inspected and changed through the XCTest accessibility interface, which does not
|
|
30
|
+
need the Accessibility permission, except for the window geometry. AMWindowAccessibilityApiTests
|
|
31
|
+
runs the same tests using the Accessibility API only.
|
|
32
|
+
*/
|
|
33
|
+
@interface AMWindowTests : AMIntegrationTestCase
|
|
34
|
+
- (BOOL)usesUnprivilegedRoutes;
|
|
35
|
+
@end
|
|
36
|
+
|
|
37
|
+
@implementation AMWindowTests
|
|
38
|
+
|
|
39
|
+
- (BOOL)usesUnprivilegedRoutes
|
|
40
|
+
{
|
|
41
|
+
return YES;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
- (void)setUp
|
|
45
|
+
{
|
|
46
|
+
[super setUp];
|
|
47
|
+
AMWindowUtils.usesUnprivilegedRoutes = [self usesUnprivilegedRoutes];
|
|
48
|
+
[self launchApplication];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
- (void)tearDown
|
|
52
|
+
{
|
|
53
|
+
// Leave the window in a regular state for the other tests
|
|
54
|
+
NSDictionary *normal = @{@"state": AMWindowStateNormal};
|
|
55
|
+
[AMWindowUtils adjustMinimizedWindowOfApplication:self.testedApplication withArguments:normal error:nil];
|
|
56
|
+
XCUIElement *window = [AMWindowUtils mainWindowOfApplication:self.testedApplication];
|
|
57
|
+
if (nil != window) {
|
|
58
|
+
[AMWindowUtils adjustWindow:window inApplication:self.testedApplication withArguments:normal error:nil];
|
|
59
|
+
}
|
|
60
|
+
AMWindowUtils.usesUnprivilegedRoutes = YES;
|
|
61
|
+
[super tearDown];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Changing states needs the XCTest accessibility interface, or the Accessibility permission if it is not used
|
|
65
|
+
- (void)skipUnlessStateRoutesAvailable
|
|
66
|
+
{
|
|
67
|
+
if ([self usesUnprivilegedRoutes]) {
|
|
68
|
+
XCTSkipUnless(AMXCTAccessibility.isAvailable, @"The XCTest accessibility interface is not available");
|
|
69
|
+
} else {
|
|
70
|
+
[self skipUnlessAccessibilityTrusted];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#pragma mark - Helpers
|
|
75
|
+
|
|
76
|
+
- (XCUIElement *)mainWindow
|
|
77
|
+
{
|
|
78
|
+
XCUIElement *window = [AMWindowUtils mainWindowOfApplication:self.testedApplication];
|
|
79
|
+
XCTAssertNotNil(window);
|
|
80
|
+
return window;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
- (AMScreenProperties *)mainDisplay
|
|
84
|
+
{
|
|
85
|
+
for (AMScreenProperties *screen in AMListScreens()) {
|
|
86
|
+
if (screen.isMain) {
|
|
87
|
+
return screen;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
XCTFail(@"No main display is reported");
|
|
91
|
+
return nil;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
- (NSDictionary<NSString *, id> *)adjustWindow:(XCUIElement *)window arguments:(NSDictionary<NSString *, id> *)arguments
|
|
95
|
+
{
|
|
96
|
+
NSError *error = nil;
|
|
97
|
+
NSDictionary *result = [AMWindowUtils adjustWindow:window inApplication:self.testedApplication withArguments:arguments error:&error];
|
|
98
|
+
XCTAssertNotNil(result, @"The call has failed: %@", error);
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
- (NSDictionary<NSString *, id> *)adjustMainWindowWithArguments:(NSDictionary<NSString *, id> *)arguments
|
|
103
|
+
{
|
|
104
|
+
return [self adjustWindow:[self mainWindow] arguments:arguments];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
- (void)assertInvalidArgumentWithMessage:(NSString *)fragment forArguments:(NSDictionary<NSString *, id> *)arguments
|
|
108
|
+
{
|
|
109
|
+
[self assertInvalidArgumentWithMessage:fragment forArguments:arguments window:[self mainWindow]];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
- (void)assertInvalidArgumentWithMessage:(NSString *)fragment
|
|
113
|
+
forArguments:(NSDictionary<NSString *, id> *)arguments
|
|
114
|
+
window:(XCUIElement *)window
|
|
115
|
+
{
|
|
116
|
+
NSError *error = nil;
|
|
117
|
+
XCTAssertNil([AMWindowUtils adjustWindow:window inApplication:self.testedApplication withArguments:arguments error:&error]);
|
|
118
|
+
XCTAssertEqualObjects(error.domain, AMWindowErrorDomain);
|
|
119
|
+
XCTAssertEqual(error.code, AMWindowErrorInvalidArgument);
|
|
120
|
+
XCTAssertTrue([error.localizedDescription containsString:fragment], @"Unexpected message: %@", error.localizedDescription);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
- (CGRect)rectFromInfo:(NSDictionary<NSString *, id> *)info
|
|
124
|
+
{
|
|
125
|
+
NSDictionary<NSString *, NSNumber *> *rect = info[@"rect"];
|
|
126
|
+
return CGRectMake(rect[@"x"].doubleValue, rect[@"y"].doubleValue, rect[@"width"].doubleValue, rect[@"height"].doubleValue);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
- (void)assertRect:(CGRect)actual equalsRect:(CGRect)expected
|
|
130
|
+
{
|
|
131
|
+
XCTAssertEqualWithAccuracy(actual.origin.x, expected.origin.x, AM_GEOMETRY_ACCURACY);
|
|
132
|
+
XCTAssertEqualWithAccuracy(actual.origin.y, expected.origin.y, AM_GEOMETRY_ACCURACY);
|
|
133
|
+
XCTAssertEqualWithAccuracy(actual.size.width, expected.size.width, AM_GEOMETRY_ACCURACY);
|
|
134
|
+
XCTAssertEqualWithAccuracy(actual.size.height, expected.size.height, AM_GEOMETRY_ACCURACY);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
- (void)assertRect:(CGRect)rect isWithinRect:(CGRect)container
|
|
138
|
+
{
|
|
139
|
+
XCTAssertTrue(CGRectContainsRect(CGRectInset(container, -AM_GEOMETRY_ACCURACY, -AM_GEOMETRY_ACCURACY), rect),
|
|
140
|
+
@"%@ is not within %@", NSStringFromRect(rect), NSStringFromRect(container));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#pragma mark - Displays
|
|
144
|
+
|
|
145
|
+
- (void)testDisplaysReportTheirBounds
|
|
146
|
+
{
|
|
147
|
+
AMScreenProperties *display = [self mainDisplay];
|
|
148
|
+
XCTAssertTrue(display.bounds.size.width > 0 && display.bounds.size.height > 0);
|
|
149
|
+
// The main display is the origin of the global coordinate space
|
|
150
|
+
XCTAssertEqualWithAccuracy(display.bounds.origin.x, 0, AM_GEOMETRY_ACCURACY);
|
|
151
|
+
XCTAssertEqualWithAccuracy(display.bounds.origin.y, 0, AM_GEOMETRY_ACCURACY);
|
|
152
|
+
// The menu bar and the Dock are excluded
|
|
153
|
+
[self assertRect:display.visibleBounds isWithinRect:display.bounds];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#pragma mark - Listing
|
|
157
|
+
|
|
158
|
+
- (void)testWindowsAreListed
|
|
159
|
+
{
|
|
160
|
+
NSArray<XCUIElement *> *windows = [AMWindowUtils windowsOfApplication:self.testedApplication];
|
|
161
|
+
XCTAssertTrue(windows.count > 0);
|
|
162
|
+
NSDictionary *info = [AMWindowUtils infoForWindow:windows.firstObject];
|
|
163
|
+
XCTAssertNotNil(info[@"title"]);
|
|
164
|
+
CGRect rect = [self rectFromInfo:info];
|
|
165
|
+
XCTAssertTrue(rect.size.width > 0 && rect.size.height > 0);
|
|
166
|
+
XCTAssertEqualObjects(info[@"displayId"], @([self mainDisplay].identifier));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
- (void)testWindowTraitsAreListed
|
|
170
|
+
{
|
|
171
|
+
[self skipUnlessStateRoutesAvailable];
|
|
172
|
+
|
|
173
|
+
NSDictionary *info = [AMWindowUtils infoForWindow:[self mainWindow]];
|
|
174
|
+
for (NSString *trait in @[@"isMain", @"isFocused", @"isMinimized", @"isFullscreen"]) {
|
|
175
|
+
XCTAssertTrue([info[trait] isKindOfClass:NSNumber.class], @"'%@' must be a boolean: %@", trait, info);
|
|
176
|
+
}
|
|
177
|
+
XCTAssertTrue([info[@"isMain"] boolValue]);
|
|
178
|
+
XCTAssertFalse([info[@"isMinimized"] boolValue]);
|
|
179
|
+
XCTAssertFalse([info[@"isFullscreen"] boolValue]);
|
|
180
|
+
// These can only be asked through the Accessibility API
|
|
181
|
+
if (AMSnapshotUtils.isAccessibilityTrusted) {
|
|
182
|
+
XCTAssertTrue([info[@"isMovable"] boolValue]);
|
|
183
|
+
XCTAssertTrue([info[@"isResizable"] boolValue]);
|
|
184
|
+
} else {
|
|
185
|
+
XCTAssertEqualObjects(info[@"isMovable"], NSNull.null);
|
|
186
|
+
XCTAssertEqualObjects(info[@"isResizable"], NSNull.null);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#pragma mark - Argument validation
|
|
191
|
+
|
|
192
|
+
- (void)testAdjustingRequiresAtLeastOneArgument
|
|
193
|
+
{
|
|
194
|
+
[self assertInvalidArgumentWithMessage:@"At least one of" forArguments:@{}];
|
|
195
|
+
[self assertInvalidArgumentWithMessage:@"At least one of" forArguments:@{@"windowId": @"foo"}];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
- (void)testAdjustingRejectsUnknownState
|
|
199
|
+
{
|
|
200
|
+
[self assertInvalidArgumentWithMessage:@"'state' must be one of" forArguments:@{@"state": @"foo"}];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
- (void)testAdjustingRejectsInvalidGeometry
|
|
204
|
+
{
|
|
205
|
+
[self assertInvalidArgumentWithMessage:@"'width'" forArguments:@{@"width": @0}];
|
|
206
|
+
[self assertInvalidArgumentWithMessage:@"'height'" forArguments:@{@"height": @(-10)}];
|
|
207
|
+
[self assertInvalidArgumentWithMessage:@"'x'" forArguments:@{@"x": @"foo"}];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
- (void)testAdjustingRejectsStateCombinedWithGeometry
|
|
211
|
+
{
|
|
212
|
+
[self assertInvalidArgumentWithMessage:@"cannot be combined"
|
|
213
|
+
forArguments:@{@"state": AMWindowStateMinimized, @"x": @10}];
|
|
214
|
+
[self assertInvalidArgumentWithMessage:@"cannot be combined"
|
|
215
|
+
forArguments:@{@"state": AMWindowStateFullscreen, @"displayId": @([self mainDisplay].identifier)}];
|
|
216
|
+
[self assertInvalidArgumentWithMessage:@"cannot be combined"
|
|
217
|
+
forArguments:@{@"state": AMWindowStateMaximized, @"width": @500}];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
- (void)testAdjustingRejectsUnknownDisplay
|
|
221
|
+
{
|
|
222
|
+
[self assertInvalidArgumentWithMessage:@"is unknown" forArguments:@{@"displayId": @999999}];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
- (void)testAdjustingRejectsMalformedDisplayId
|
|
226
|
+
{
|
|
227
|
+
[self assertInvalidArgumentWithMessage:@"'displayId' must be a number" forArguments:@{@"displayId": @{}}];
|
|
228
|
+
[self assertInvalidArgumentWithMessage:@"'displayId' must be a number" forArguments:@{@"displayId": @"foo"}];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
- (void)testAdjustingRejectsNonWindowElements
|
|
232
|
+
{
|
|
233
|
+
XCUIElement *button = self.testedApplication.buttons.firstMatch;
|
|
234
|
+
XCTAssertTrue([button waitForExistenceWithTimeout:5.0]);
|
|
235
|
+
[self assertInvalidArgumentWithMessage:@"is not a window" forArguments:@{@"x": @10} window:button];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
#pragma mark - Adjusting (requires the Accessibility permission)
|
|
239
|
+
|
|
240
|
+
- (void)testWindowCanBeResizedAndMoved
|
|
241
|
+
{
|
|
242
|
+
[self skipUnlessAccessibilityTrusted];
|
|
243
|
+
|
|
244
|
+
XCUIElement *window = [self mainWindow];
|
|
245
|
+
CGRect original = [self rectFromInfo:[AMWindowUtils infoForWindow:window]];
|
|
246
|
+
CGRect expected = CGRectMake(original.origin.x + 30, original.origin.y + 30,
|
|
247
|
+
original.size.width + 20, original.size.height + 20);
|
|
248
|
+
NSDictionary *result = [self adjustWindow:window arguments:@{
|
|
249
|
+
@"x": @(expected.origin.x),
|
|
250
|
+
@"y": @(expected.origin.y),
|
|
251
|
+
@"width": @(expected.size.width),
|
|
252
|
+
@"height": @(expected.size.height),
|
|
253
|
+
}];
|
|
254
|
+
[self assertRect:[self rectFromInfo:result] equalsRect:expected];
|
|
255
|
+
// The change is observable from the outside
|
|
256
|
+
[self assertRect:[self rectFromInfo:[AMWindowUtils infoForWindow:window]] equalsRect:expected];
|
|
257
|
+
[self assertRect:window.frame equalsRect:expected];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
- (void)testOmittedGeometryIsKept
|
|
261
|
+
{
|
|
262
|
+
[self skipUnlessAccessibilityTrusted];
|
|
263
|
+
|
|
264
|
+
XCUIElement *window = [self mainWindow];
|
|
265
|
+
CGRect original = [self rectFromInfo:[AMWindowUtils infoForWindow:window]];
|
|
266
|
+
NSDictionary *result = [self adjustWindow:window arguments:@{@"width": @(original.size.width + 20)}];
|
|
267
|
+
[self assertRect:[self rectFromInfo:result]
|
|
268
|
+
equalsRect:CGRectMake(original.origin.x, original.origin.y, original.size.width + 20, original.size.height)];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
- (void)testWindowCanBeMovedToDisplay
|
|
272
|
+
{
|
|
273
|
+
[self skipUnlessAccessibilityTrusted];
|
|
274
|
+
|
|
275
|
+
AMScreenProperties *display = [self mainDisplay];
|
|
276
|
+
NSDictionary *result = [self adjustMainWindowWithArguments:@{
|
|
277
|
+
@"displayId": @(display.identifier),
|
|
278
|
+
@"x": @80,
|
|
279
|
+
@"y": @120,
|
|
280
|
+
}];
|
|
281
|
+
CGRect rect = [self rectFromInfo:result];
|
|
282
|
+
// Coordinates are relative to the display
|
|
283
|
+
XCTAssertEqualWithAccuracy(rect.origin.x, display.bounds.origin.x + 80, AM_GEOMETRY_ACCURACY);
|
|
284
|
+
XCTAssertEqualWithAccuracy(rect.origin.y, display.bounds.origin.y + 120, AM_GEOMETRY_ACCURACY);
|
|
285
|
+
XCTAssertEqualObjects(result[@"displayId"], @(display.identifier));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
- (void)testWindowIsKeptWithinDisplay
|
|
289
|
+
{
|
|
290
|
+
[self skipUnlessAccessibilityTrusted];
|
|
291
|
+
|
|
292
|
+
AMScreenProperties *display = [self mainDisplay];
|
|
293
|
+
NSDictionary *result = [self adjustMainWindowWithArguments:@{
|
|
294
|
+
@"displayId": @(display.identifier),
|
|
295
|
+
@"x": @(display.bounds.size.width * 10),
|
|
296
|
+
@"y": @(display.bounds.size.height * 10),
|
|
297
|
+
}];
|
|
298
|
+
[self assertRect:[self rectFromInfo:result] isWithinRect:display.bounds];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
- (void)testWindowCanBeMaximized
|
|
302
|
+
{
|
|
303
|
+
[self skipUnlessAccessibilityTrusted];
|
|
304
|
+
|
|
305
|
+
AMScreenProperties *display = [self mainDisplay];
|
|
306
|
+
CGRect original = [self rectFromInfo:[AMWindowUtils infoForWindow:[self mainWindow]]];
|
|
307
|
+
CGRect rect = [self rectFromInfo:[self adjustMainWindowWithArguments:@{@"state": AMWindowStateMaximized}]];
|
|
308
|
+
XCTAssertTrue(rect.size.width * rect.size.height >= original.size.width * original.size.height,
|
|
309
|
+
@"%@ must not be smaller than %@", NSStringFromRect(rect), NSStringFromRect(original));
|
|
310
|
+
// Fills the display, except the menu bar and the Dock
|
|
311
|
+
[self assertRect:rect isWithinRect:display.bounds];
|
|
312
|
+
XCTAssertTrue(rect.size.width >= display.visibleBounds.size.width - AM_GEOMETRY_ACCURACY);
|
|
313
|
+
|
|
314
|
+
// Maximizing is idempotent
|
|
315
|
+
NSDictionary *repeated = [self adjustMainWindowWithArguments:@{@"state": AMWindowStateMaximized}];
|
|
316
|
+
[self assertRect:[self rectFromInfo:repeated] equalsRect:rect];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
- (void)testWindowCanBeMinimizedAndRestored
|
|
320
|
+
{
|
|
321
|
+
[self skipUnlessStateRoutesAvailable];
|
|
322
|
+
|
|
323
|
+
XCUIApplication *application = self.testedApplication;
|
|
324
|
+
CGRect original = [self rectFromInfo:[AMWindowUtils infoForWindow:[self mainWindow]]];
|
|
325
|
+
NSDictionary *minimized = [self adjustMainWindowWithArguments:@{@"state": AMWindowStateMinimized}];
|
|
326
|
+
XCTAssertEqualObjects(minimized[@"isMinimized"], @YES);
|
|
327
|
+
FBAssertWaitTillBecomesTrue(1 == [AMWindowUtils minimizedWindowsCountOfApplication:application]);
|
|
328
|
+
|
|
329
|
+
// Minimized windows are not a part of the element hierarchy
|
|
330
|
+
NSError *error = nil;
|
|
331
|
+
NSDictionary *restored = [AMWindowUtils adjustMinimizedWindowOfApplication:application
|
|
332
|
+
withArguments:@{@"state": AMWindowStateNormal}
|
|
333
|
+
error:&error];
|
|
334
|
+
XCTAssertNotNil(restored, @"The call has failed: %@", error);
|
|
335
|
+
XCTAssertEqualObjects(restored[@"isMinimized"], @NO);
|
|
336
|
+
[self assertRect:[self rectFromInfo:restored] equalsRect:original];
|
|
337
|
+
FBAssertWaitTillBecomesTrue(0 == [AMWindowUtils minimizedWindowsCountOfApplication:application]);
|
|
338
|
+
FBAssertWaitTillBecomesTrue(nil != [AMWindowUtils mainWindowOfApplication:application]);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
- (void)testMinimizedWindowIsRestoredByGeometryChange
|
|
342
|
+
{
|
|
343
|
+
[self skipUnlessAccessibilityTrusted];
|
|
344
|
+
|
|
345
|
+
XCUIApplication *application = self.testedApplication;
|
|
346
|
+
CGRect original = [self rectFromInfo:[AMWindowUtils infoForWindow:[self mainWindow]]];
|
|
347
|
+
[self adjustMainWindowWithArguments:@{@"state": AMWindowStateMinimized}];
|
|
348
|
+
FBAssertWaitTillBecomesTrue(1 == [AMWindowUtils minimizedWindowsCountOfApplication:application]);
|
|
349
|
+
|
|
350
|
+
NSError *error = nil;
|
|
351
|
+
NSDictionary *result = [AMWindowUtils adjustMinimizedWindowOfApplication:application
|
|
352
|
+
withArguments:@{@"width": @(original.size.width + 10)}
|
|
353
|
+
error:&error];
|
|
354
|
+
XCTAssertNotNil(result, @"The call has failed: %@", error);
|
|
355
|
+
XCTAssertEqualObjects(result[@"isMinimized"], @NO);
|
|
356
|
+
XCTAssertEqualWithAccuracy([self rectFromInfo:result].size.width, original.size.width + 10, AM_GEOMETRY_ACCURACY);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
- (void)testRestoringRequiresMinimizedWindow
|
|
360
|
+
{
|
|
361
|
+
[self skipUnlessStateRoutesAvailable];
|
|
362
|
+
|
|
363
|
+
NSError *error = nil;
|
|
364
|
+
XCTAssertNil([AMWindowUtils adjustMinimizedWindowOfApplication:self.testedApplication
|
|
365
|
+
withArguments:@{@"state": AMWindowStateNormal}
|
|
366
|
+
error:&error]);
|
|
367
|
+
XCTAssertEqual(error.code, AMWindowErrorNotFound);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
- (void)testGeometryRequiresAccessibilityPermission
|
|
371
|
+
{
|
|
372
|
+
XCTSkipIf(AMSnapshotUtils.isAccessibilityTrusted, @"The process is trusted for the Accessibility API");
|
|
373
|
+
|
|
374
|
+
NSError *error = nil;
|
|
375
|
+
XCTAssertNil([AMWindowUtils adjustWindow:[self mainWindow]
|
|
376
|
+
inApplication:self.testedApplication
|
|
377
|
+
withArguments:@{@"width": @600}
|
|
378
|
+
error:&error]);
|
|
379
|
+
XCTAssertEqual(error.code, AMWindowErrorFailure);
|
|
380
|
+
XCTAssertTrue([error.localizedDescription containsString:@"Accessibility"], @"%@", error.localizedDescription);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
- (void)testWindowCanEnterAndLeaveFullscreen
|
|
384
|
+
{
|
|
385
|
+
[self skipUnlessStateRoutesAvailable];
|
|
386
|
+
|
|
387
|
+
XCUIElement *window = [self mainWindow];
|
|
388
|
+
NSDictionary *fullscreen = [self adjustWindow:window arguments:@{@"state": AMWindowStateFullscreen}];
|
|
389
|
+
XCTAssertEqualObjects(fullscreen[@"isFullscreen"], @YES);
|
|
390
|
+
|
|
391
|
+
NSDictionary *restored = [self adjustWindow:window arguments:@{@"state": AMWindowStateNormal}];
|
|
392
|
+
XCTAssertEqualObjects(restored[@"isFullscreen"], @NO);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
- (void)testFullscreenWindowCanBeMinimized
|
|
396
|
+
{
|
|
397
|
+
[self skipUnlessStateRoutesAvailable];
|
|
398
|
+
|
|
399
|
+
XCUIApplication *application = self.testedApplication;
|
|
400
|
+
XCUIElement *window = [self mainWindow];
|
|
401
|
+
NSDictionary *fullscreen = [self adjustWindow:window arguments:@{@"state": AMWindowStateFullscreen}];
|
|
402
|
+
XCTAssertEqualObjects(fullscreen[@"isFullscreen"], @YES);
|
|
403
|
+
|
|
404
|
+
NSDictionary *minimized = [self adjustWindow:window arguments:@{@"state": AMWindowStateMinimized}];
|
|
405
|
+
XCTAssertEqualObjects(minimized[@"isMinimized"], @YES);
|
|
406
|
+
XCTAssertEqualObjects(minimized[@"isFullscreen"], @NO);
|
|
407
|
+
|
|
408
|
+
NSError *error = nil;
|
|
409
|
+
XCTAssertNotNil([AMWindowUtils adjustMinimizedWindowOfApplication:application
|
|
410
|
+
withArguments:@{@"state": AMWindowStateNormal}
|
|
411
|
+
error:&error], @"The call has failed: %@", error);
|
|
412
|
+
FBAssertWaitTillBecomesTrue(nil != [AMWindowUtils mainWindowOfApplication:application]);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
@end
|
|
416
|
+
|
|
417
|
+
@interface AMWindowAccessibilityApiTests : AMWindowTests
|
|
418
|
+
@end
|
|
419
|
+
|
|
420
|
+
@implementation AMWindowAccessibilityApiTests
|
|
421
|
+
|
|
422
|
+
- (BOOL)usesUnprivilegedRoutes
|
|
423
|
+
{
|
|
424
|
+
return NO;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
@end
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
#import "AMWindowCommands.h"
|
|
11
11
|
|
|
12
|
+
#import "AMWindowUtils.h"
|
|
13
|
+
#import "FBElementCache.h"
|
|
12
14
|
#import "FBRoute.h"
|
|
13
15
|
#import "FBRouteRequest.h"
|
|
14
16
|
#import "FBSession.h"
|
|
@@ -23,6 +25,8 @@
|
|
|
23
25
|
[[FBRoute POST:@"/window/maximize"] respondWithTarget:self action:@selector(handleMaximizeWindow:)],
|
|
24
26
|
[[FBRoute POST:@"/window/fullscreen"] respondWithTarget:self action:@selector(handleFullscreenWindow:)],
|
|
25
27
|
[[FBRoute POST:@"/window/minimize"] respondWithTarget:self action:@selector(handleMinimizeWindow:)],
|
|
28
|
+
[[FBRoute GET:@"/wda/windows"] respondWithTarget:self action:@selector(handleListWindows:)],
|
|
29
|
+
[[FBRoute POST:@"/wda/window/adjust"] respondWithTarget:self action:@selector(handleAdjustWindow:)],
|
|
26
30
|
];
|
|
27
31
|
}
|
|
28
32
|
|
|
@@ -49,6 +53,65 @@
|
|
|
49
53
|
errorMessage:[NSString stringWithFormat:@"%@ window cannot be minimized because the corresponding button is not available", request.session.currentApplication.description]];
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
+ (id<FBResponsePayload>)handleListWindows:(FBRouteRequest *)request
|
|
57
|
+
{
|
|
58
|
+
FBElementCache *cache = request.session.elementCache;
|
|
59
|
+
NSMutableArray *result = [NSMutableArray array];
|
|
60
|
+
for (XCUIElement *window in [AMWindowUtils windowsOfApplication:request.session.currentApplication]) {
|
|
61
|
+
NSDictionary *info = [AMWindowUtils infoForWindow:window];
|
|
62
|
+
if (nil != info) {
|
|
63
|
+
[result addObject:[self info:info withWindowId:[cache storeElement:window]]];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return FBResponseWithObject(result.copy);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
+ (id<FBResponsePayload>)handleAdjustWindow:(FBRouteRequest *)request
|
|
70
|
+
{
|
|
71
|
+
FBElementCache *cache = request.session.elementCache;
|
|
72
|
+
XCUIApplication *application = request.session.currentApplication;
|
|
73
|
+
id rawWindowId = request.arguments[@"windowId"];
|
|
74
|
+
if (nil != rawWindowId && ![rawWindowId isKindOfClass:NSNull.class] && ![rawWindowId isKindOfClass:NSString.class]) {
|
|
75
|
+
return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:
|
|
76
|
+
[NSString stringWithFormat:@"'windowId' must be a string, got '%@'", rawWindowId]
|
|
77
|
+
traceback:nil]);
|
|
78
|
+
}
|
|
79
|
+
NSString *windowId = [rawWindowId isKindOfClass:NSString.class] ? rawWindowId : nil;
|
|
80
|
+
XCUIElement *window = nil != windowId
|
|
81
|
+
? [cache elementForUUID:windowId]
|
|
82
|
+
: [AMWindowUtils mainWindowOfApplication:application];
|
|
83
|
+
NSError *error = nil;
|
|
84
|
+
NSDictionary *info = nil;
|
|
85
|
+
if (nil != window) {
|
|
86
|
+
info = [AMWindowUtils adjustWindow:window inApplication:application withArguments:request.arguments error:&error];
|
|
87
|
+
} else if (nil == windowId) {
|
|
88
|
+
// There are no visible windows, so a minimized one could be restored
|
|
89
|
+
info = [AMWindowUtils adjustMinimizedWindowOfApplication:application withArguments:request.arguments error:&error];
|
|
90
|
+
}
|
|
91
|
+
NSString *resultWindowId = nil != info && nil != window ? (windowId ?: [cache storeElement:window]) : nil;
|
|
92
|
+
if (nil == info) {
|
|
93
|
+
NSString *message = error.localizedDescription;
|
|
94
|
+
switch (error.code) {
|
|
95
|
+
case AMWindowErrorInvalidArgument:
|
|
96
|
+
return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:message traceback:nil]);
|
|
97
|
+
case AMWindowErrorNotFound:
|
|
98
|
+
return FBResponseWithStatus([FBCommandStatus noSuchWindowErrorWithMessage:message traceback:nil]);
|
|
99
|
+
default:
|
|
100
|
+
return FBResponseWithStatus([FBCommandStatus unknownErrorWithMessage:message traceback:nil]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return FBResponseWithObject([self info:info withWindowId:resultWindowId]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#pragma mark - Helpers
|
|
107
|
+
|
|
108
|
+
+ (NSDictionary<NSString *, id> *)info:(NSDictionary<NSString *, id> *)info withWindowId:(nullable NSString *)windowId
|
|
109
|
+
{
|
|
110
|
+
NSMutableDictionary *result = [info mutableCopy];
|
|
111
|
+
result[@"windowId"] = windowId ?: NSNull.null;
|
|
112
|
+
return result.copy;
|
|
113
|
+
}
|
|
114
|
+
|
|
52
115
|
+ (id<FBResponsePayload>)performClickWithButtonId:(NSString *)buttonId
|
|
53
116
|
request:(FBRouteRequest *)request
|
|
54
117
|
errorMessage:(NSString *)errorMessage
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
#import "FBDebugCommands.h"
|
|
11
11
|
|
|
12
|
+
#import "AMGeometryUtils.h"
|
|
12
13
|
#import "AMScreenUtils.h"
|
|
13
14
|
#import "FBRouteRequest.h"
|
|
14
15
|
#import "FBSession.h"
|
|
@@ -65,6 +66,7 @@ static NSString *const SOURCE_FORMAT_DESCRIPTION = @"description";
|
|
|
65
66
|
result[[NSString stringWithFormat:@"%lld", screenInfo.identifier]] = @{
|
|
66
67
|
@"id": @(screenInfo.identifier),
|
|
67
68
|
@"isMain": @(screenInfo.isMain),
|
|
69
|
+
@"rect": AMCGRectToDict(screenInfo.bounds),
|
|
68
70
|
};
|
|
69
71
|
}
|
|
70
72
|
return FBResponseWithObject(result.copy);
|
|
@@ -99,9 +99,15 @@ const static NSString *CAPABILITIES_KEY = @"capabilities";
|
|
|
99
99
|
NSString *appPath = requirements[AM_APP_PATH_CAPABILITY];
|
|
100
100
|
NSString *deepLink = requirements[AM_INITIAL_DEEPLINK_URL_CAPABILITY];
|
|
101
101
|
BOOL noReset = [requirements[AM_NO_RESET_CAPABILITY] boolValue];
|
|
102
|
-
|
|
102
|
+
|
|
103
|
+
// XCTest is single-threaded, so there can only be one session. A new one always
|
|
104
|
+
// takes over, and the previous one must be gone before any app gets touched.
|
|
105
|
+
[FBSession.activeSession kill];
|
|
106
|
+
|
|
107
|
+
XCUIApplication *app = nil;
|
|
108
|
+
BOOL didLaunchApp = NO;
|
|
103
109
|
if (nil == bundleID && nil == appPath && nil == deepLink) {
|
|
104
|
-
|
|
110
|
+
// no app to manage
|
|
105
111
|
} else if (nil != deepLink) {
|
|
106
112
|
NSURL *url = [NSURL URLWithString:deepLink];
|
|
107
113
|
NSError *error;
|
|
@@ -113,13 +119,12 @@ const static NSString *CAPABILITIES_KEY = @"capabilities";
|
|
|
113
119
|
traceback:nil]);
|
|
114
120
|
}
|
|
115
121
|
if (nil != bundleID) {
|
|
116
|
-
|
|
122
|
+
app = [[XCUIApplication alloc] initWithBundleIdentifier:bundleID];
|
|
117
123
|
}
|
|
118
124
|
} else {
|
|
119
|
-
|
|
125
|
+
app = nil != appPath
|
|
120
126
|
? [[XCUIApplication alloc] initWithURL:[NSURL fileURLWithPath:appPath]]
|
|
121
127
|
: [[XCUIApplication alloc] initWithBundleIdentifier:bundleID];
|
|
122
|
-
session = [FBSession initWithApplication:app];
|
|
123
128
|
if (noReset && app.state > XCUIApplicationStateNotRunning) {
|
|
124
129
|
[app activate];
|
|
125
130
|
} else {
|
|
@@ -140,6 +145,7 @@ const static NSString *CAPABILITIES_KEY = @"capabilities";
|
|
|
140
145
|
}
|
|
141
146
|
app.launchEnvironment = [launchEnv copy];
|
|
142
147
|
[app launch];
|
|
148
|
+
didLaunchApp = YES;
|
|
143
149
|
if (app.state <= XCUIApplicationStateNotRunning) {
|
|
144
150
|
NSString *message = [NSString stringWithFormat:@"Failed to launch '%@' application", appPath ?: bundleID];
|
|
145
151
|
return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:message
|
|
@@ -149,6 +155,10 @@ const static NSString *CAPABILITIES_KEY = @"capabilities";
|
|
|
149
155
|
if (nil != bundleID && nil != appPath) {
|
|
150
156
|
NSString *realBundleID = app.am_bundleID;
|
|
151
157
|
if (![realBundleID isEqualToString:bundleID]) {
|
|
158
|
+
// no session owns the app, so don't leave it behind
|
|
159
|
+
if (didLaunchApp) {
|
|
160
|
+
[app terminate];
|
|
161
|
+
}
|
|
152
162
|
NSString *message = [NSString stringWithFormat:@"The bundle identifier %@ of the '%@' does not match to the one provided in capabilities: %@",
|
|
153
163
|
realBundleID, appPath, bundleID];
|
|
154
164
|
return FBResponseWithStatus([FBCommandStatus sessionNotCreatedError:message
|
|
@@ -156,6 +166,8 @@ const static NSString *CAPABILITIES_KEY = @"capabilities";
|
|
|
156
166
|
}
|
|
157
167
|
}
|
|
158
168
|
}
|
|
169
|
+
// The session is only registered once startup has fully succeeded
|
|
170
|
+
FBSession *session = [FBSession initWithApplication:app];
|
|
159
171
|
if (nil != requirements[AM_SKIP_APP_KILL_CAPABILITY]) {
|
|
160
172
|
session.skipAppTermination = [requirements[AM_SKIP_APP_KILL_CAPABILITY] boolValue];
|
|
161
173
|
} else if (nil == bundleID) {
|
|
@@ -37,6 +37,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
37
37
|
+ (instancetype)noSuchElementErrorWithMessage:(nullable NSString *)message
|
|
38
38
|
traceback:(nullable NSString *)traceback;
|
|
39
39
|
|
|
40
|
+
+ (instancetype)noSuchWindowErrorWithMessage:(nullable NSString *)message
|
|
41
|
+
traceback:(nullable NSString *)traceback;
|
|
42
|
+
|
|
40
43
|
+ (instancetype)invalidElementStateErrorWithMessage:(nullable NSString *)message
|
|
41
44
|
traceback:(nullable NSString *)traceback;
|
|
42
45
|
|
|
@@ -21,6 +21,10 @@ static NSString *const FB_NO_SUCH_ELEMENT_ERROR = @"no such element";
|
|
|
21
21
|
static const HTTPStatusCode FB_NO_SUCH_ELEMENT_ERROR_CODE = kHTTPStatusCodeNotFound;
|
|
22
22
|
static NSString *const FB_NO_SUCH_ELEMENT_MSG = @"An element could not be located on the page using the given search parameters";
|
|
23
23
|
|
|
24
|
+
static NSString *const FB_NO_SUCH_WINDOW_ERROR = @"no such window";
|
|
25
|
+
static const HTTPStatusCode FB_NO_SUCH_WINDOW_ERROR_CODE = kHTTPStatusCodeNotFound;
|
|
26
|
+
static NSString *const FB_NO_SUCH_WINDOW_MSG = @"A window could not be found. It might have been closed";
|
|
27
|
+
|
|
24
28
|
static NSString *const FB_INVALID_ELEMENT_STATE_ERROR = @"invalid element state";
|
|
25
29
|
static const HTTPStatusCode FB_INVALID_ELEMENT_STATE_ERROR_CODE = kHTTPStatusCodeBadRequest;
|
|
26
30
|
static NSString *const FB_INVALID_ELEMENT_STATE_MSG = @"An element command could not be completed because the element is in an invalid state (e.g. attempting to click a disabled element)";
|
|
@@ -155,6 +159,15 @@ static NSString *const FB_NO_SUCH_DRIVER_MSG = @"A session is either terminated
|
|
|
155
159
|
traceback:traceback];
|
|
156
160
|
}
|
|
157
161
|
|
|
162
|
+
+ (instancetype)noSuchWindowErrorWithMessage:(NSString *)message
|
|
163
|
+
traceback:(NSString *)traceback
|
|
164
|
+
{
|
|
165
|
+
return [[FBCommandStatus alloc] initWithError:FB_NO_SUCH_WINDOW_ERROR
|
|
166
|
+
statusCode:FB_NO_SUCH_WINDOW_ERROR_CODE
|
|
167
|
+
message:message ?: FB_NO_SUCH_WINDOW_MSG
|
|
168
|
+
traceback:traceback];
|
|
169
|
+
}
|
|
170
|
+
|
|
158
171
|
+ (instancetype)invalidElementStateErrorWithMessage:(NSString *)message
|
|
159
172
|
traceback:(NSString *)traceback
|
|
160
173
|
{
|