agent-device 0.20.1 → 0.20.3
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/README.md +0 -2
- package/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.1.apk → agent-device-android-ime-helper-0.20.3.apk} +0 -0
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.3.apk.sha256 +1 -0
- package/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.1.manifest.json → agent-device-android-ime-helper-0.20.3.manifest.json} +4 -4
- package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.20.1.apk → agent-device-android-snapshot-helper-0.20.3.apk} +0 -0
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.3.apk.sha256 +1 -0
- package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.20.1.manifest.json → agent-device-android-snapshot-helper-0.20.3.manifest.json} +6 -6
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +2 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +1 -35
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +0 -208
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Keyboard.swift +184 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Orientation.swift +92 -0
- package/dist/src/agent-device-client.js +2 -2
- package/dist/src/android-adb.d.ts +3 -26
- package/dist/src/android-adb.js +1 -1
- package/dist/src/android.js +1 -1
- package/dist/src/app-helpers.js +1 -0
- package/dist/src/app-lifecycle.js +5 -5
- package/dist/src/app-log-request-scope.js +1 -1
- package/dist/src/app-resolution.js +1 -1
- package/dist/src/apps.js +5 -5
- package/dist/src/cli-config.js +1 -1
- package/dist/src/cli-help.js +6 -6
- package/dist/src/cli-runner.js +1 -1
- package/dist/src/cli.js +14 -14
- package/dist/src/click-button.d.ts +5 -0
- package/dist/src/client-metro.js +1 -0
- package/dist/src/command-schema.js +3 -3
- package/dist/src/context.js +1 -1
- package/dist/src/contracts.d.ts +1 -1
- package/dist/src/device-input-state.d.ts +26 -0
- package/dist/src/device-input-state.js +1 -1
- package/dist/src/device.js +9 -0
- package/dist/src/devices2.js +1 -1
- package/dist/src/find.js +1 -1
- package/dist/src/generic.js +1 -1
- package/dist/src/gesture-plan-types.js +1 -0
- package/dist/src/gesture-plan.js +1 -0
- package/dist/src/index.d.ts +1174 -1413
- package/dist/src/input-actions.js +4 -4
- package/dist/src/install-artifact.js +5 -1
- package/dist/src/install-artifact2.js +1 -0
- package/dist/src/interaction-snapshot.js +1 -1
- package/dist/src/interaction.d.ts +3 -6
- package/dist/src/interaction.js +1 -1
- package/dist/src/interactor.js +2 -2
- package/dist/src/interactor2.js +1 -1
- package/dist/src/internal/bin.js +1 -1
- package/dist/src/internal/daemon.js +14 -22
- package/dist/src/lease-context.js +1 -1
- package/dist/src/lease.js +1 -1
- package/dist/src/limrun.d.ts +437 -0
- package/dist/src/limrun.js +1 -0
- package/dist/src/linux.js +1 -1
- package/dist/src/logcat.js +1 -5
- package/dist/src/metro.d.ts +71 -2
- package/dist/src/metro.js +1 -1
- package/dist/src/metro2.js +1 -1
- package/dist/src/notifications.js +1 -1
- package/dist/src/physical-device-control.js +1 -0
- package/dist/src/plist.js +1 -0
- package/dist/src/react-native.js +1 -1
- package/dist/src/recording-export-quality.js +12 -13
- package/dist/src/register-builtins.js +1 -1
- package/dist/src/registry.js +18 -18
- package/dist/src/remote-config.d.ts +5 -2
- package/dist/src/remote-config.js +1 -1
- package/dist/src/remote-config2.js +1 -0
- package/dist/src/reporting.js +8 -8
- package/dist/src/runner-client.js +1 -1
- package/dist/src/runner-disposal.js +1 -2
- package/dist/src/runner-lease.js +1 -1
- package/dist/src/runtime-transport.js +1 -1
- package/dist/src/runtime.js +3 -3
- package/dist/src/runtime2.js +1 -1
- package/dist/src/sdk-android-adb.d.ts +4 -2
- package/dist/src/sdk-batch-runner.d.ts +13 -3
- package/dist/src/sdk-batch-runner.js +1 -1
- package/dist/src/sdk-contracts.d.ts +28 -2
- package/dist/src/sdk-contracts.js +2 -2
- package/dist/src/sdk-finders.d.ts +1 -1
- package/dist/src/sdk-remote-config.d.ts +119 -4
- package/dist/src/selector-runtime.js +1 -1
- package/dist/src/selector-vocabulary.js +1 -1
- package/dist/src/selectors.d.ts +1 -1
- package/dist/src/server.js +2 -2
- package/dist/src/session-target-evidence.js +1 -1
- package/dist/src/session.js +8 -8
- package/dist/src/simulator.js +1 -1
- package/dist/src/snapshot-diagnostics.js +4 -4
- package/dist/src/snapshot2.js +1 -1
- package/dist/src/tool-provider.js +3 -3
- package/dist/src/tool-provider2.js +1 -1
- package/dist/src/tool-provider3.js +1 -1
- package/dist/src/tv-remote.d.ts +213 -0
- package/dist/src/xml.js +2 -0
- package/package.json +49 -30
- package/server.json +2 -2
- package/smithery.yaml +1 -1
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.1.apk.sha256 +0 -1
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.1.apk.sha256 +0 -1
- package/dist/src/android-system-chrome.js +0 -1
- package/dist/src/client-metro-companion.js +0 -1
- package/dist/src/metro-types.d.ts +0 -52
- package/dist/src/project-runtime.js +0 -1
- package/dist/src/scoped-provider.js +0 -1
- package/dist/src/simctl.js +0 -1
- package/dist/src/snapshot-quality.js +0 -1
package/README.md
CHANGED
|
@@ -28,9 +28,7 @@ Install the CLI and check setup. It requires Node.js 22.12 or newer; web automat
|
|
|
28
28
|
```bash
|
|
29
29
|
npm install -g agent-device@latest
|
|
30
30
|
agent-device doctor
|
|
31
|
-
agent-device --version
|
|
32
31
|
agent-device help workflow
|
|
33
|
-
agent-device help tv
|
|
34
32
|
```
|
|
35
33
|
|
|
36
34
|
Run `agent-device doctor` yourself before handing the CLI to an agent. The installed CLI help defines current behavior. `agent-device help workflow` links to guides for debugging, replay, React Native profiling, and other tasks.
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
02777b8c7e55044210eafa255ac92e627977679d75ba4a97f563f3e23dea3267 agent-device-android-ime-helper-0.20.3.apk
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "android-ime-helper",
|
|
3
|
-
"version": "0.20.
|
|
4
|
-
"assetName": "agent-device-android-ime-helper-0.20.
|
|
5
|
-
"sha256": "
|
|
3
|
+
"version": "0.20.3",
|
|
4
|
+
"assetName": "agent-device-android-ime-helper-0.20.3.apk",
|
|
5
|
+
"sha256": "02777b8c7e55044210eafa255ac92e627977679d75ba4a97f563f3e23dea3267",
|
|
6
6
|
"packageName": "com.callstack.agentdevice.imehelper",
|
|
7
|
-
"versionCode":
|
|
7
|
+
"versionCode": 20003,
|
|
8
8
|
"serviceComponent": "com.callstack.agentdevice.imehelper/.TestInputMethodService",
|
|
9
9
|
"broadcastProtocol": "android-ime-helper-v1"
|
|
10
10
|
}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
de7ecffc80d54dd1c173d3bf445ff8c9a02de2d124bcf39abeafeb5a6a08c95e agent-device-android-snapshot-helper-0.20.3.apk
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "android-snapshot-helper",
|
|
3
|
-
"version": "0.20.
|
|
4
|
-
"releaseTag": "v0.20.
|
|
5
|
-
"assetName": "agent-device-android-snapshot-helper-0.20.
|
|
3
|
+
"version": "0.20.3",
|
|
4
|
+
"releaseTag": "v0.20.3",
|
|
5
|
+
"assetName": "agent-device-android-snapshot-helper-0.20.3.apk",
|
|
6
6
|
"apkUrl": null,
|
|
7
|
-
"sha256": "
|
|
8
|
-
"checksumName": "agent-device-android-snapshot-helper-0.20.
|
|
7
|
+
"sha256": "de7ecffc80d54dd1c173d3bf445ff8c9a02de2d124bcf39abeafeb5a6a08c95e",
|
|
8
|
+
"checksumName": "agent-device-android-snapshot-helper-0.20.3.apk.sha256",
|
|
9
9
|
"packageName": "com.callstack.agentdevice.snapshothelper",
|
|
10
|
-
"versionCode":
|
|
10
|
+
"versionCode": 20003,
|
|
11
11
|
"instrumentationRunner": "com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",
|
|
12
12
|
"minSdk": 23,
|
|
13
13
|
"targetSdk": 36,
|
package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift
CHANGED
|
@@ -220,10 +220,12 @@ extension RunnerTests {
|
|
|
220
220
|
"yes",
|
|
221
221
|
"continue",
|
|
222
222
|
"done",
|
|
223
|
+
"open",
|
|
223
224
|
"open settings"
|
|
224
225
|
].contains(normalized) || normalized.hasPrefix("confirm")
|
|
225
226
|
}
|
|
226
227
|
|
|
228
|
+
|
|
227
229
|
private func isDismissButton(_ label: String) -> Bool {
|
|
228
230
|
[
|
|
229
231
|
"cancel",
|
|
@@ -1287,41 +1287,7 @@ extension RunnerTests {
|
|
|
1287
1287
|
pressHomeButton()
|
|
1288
1288
|
return Response(ok: true, data: DataPayload(message: "home"))
|
|
1289
1289
|
case .rotate:
|
|
1290
|
-
|
|
1291
|
-
!orientation.isEmpty
|
|
1292
|
-
else {
|
|
1293
|
-
return Response(ok: false, error: ErrorPayload(message: "rotate requires orientation"))
|
|
1294
|
-
}
|
|
1295
|
-
let supportedOrientations = [
|
|
1296
|
-
"portrait",
|
|
1297
|
-
"portrait-upside-down",
|
|
1298
|
-
"landscape-left",
|
|
1299
|
-
"landscape-right",
|
|
1300
|
-
]
|
|
1301
|
-
guard supportedOrientations.contains(orientation) else {
|
|
1302
|
-
return Response(
|
|
1303
|
-
ok: false,
|
|
1304
|
-
error: ErrorPayload(message: "unsupported rotate orientation: \(orientation)")
|
|
1305
|
-
)
|
|
1306
|
-
}
|
|
1307
|
-
guard let observedOrientation = rotateDevice(to: orientation) else {
|
|
1308
|
-
return Response(
|
|
1309
|
-
ok: false,
|
|
1310
|
-
error: ErrorPayload(message: "unable to observe device orientation after rotate")
|
|
1311
|
-
)
|
|
1312
|
-
}
|
|
1313
|
-
guard observedOrientation == orientation else {
|
|
1314
|
-
return Response(
|
|
1315
|
-
ok: false,
|
|
1316
|
-
error: ErrorPayload(
|
|
1317
|
-
message: "device orientation is \(observedOrientation), expected \(orientation)"
|
|
1318
|
-
)
|
|
1319
|
-
)
|
|
1320
|
-
}
|
|
1321
|
-
return Response(
|
|
1322
|
-
ok: true,
|
|
1323
|
-
data: DataPayload(message: "rotate", orientation: observedOrientation)
|
|
1324
|
-
)
|
|
1290
|
+
return executeRotateCommand(command)
|
|
1325
1291
|
case .appSwitcher:
|
|
1326
1292
|
performAppSwitcherGesture(app: activeApp)
|
|
1327
1293
|
return Response(ok: true, data: DataPayload(message: "appSwitcher"))
|
package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift
CHANGED
|
@@ -118,38 +118,6 @@ extension RunnerTests {
|
|
|
118
118
|
#endif
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
func rotateDevice(to orientationName: String) -> String? {
|
|
122
|
-
#if os(macOS) || os(tvOS) || os(visionOS)
|
|
123
|
-
return nil
|
|
124
|
-
#else
|
|
125
|
-
switch orientationName {
|
|
126
|
-
case "portrait":
|
|
127
|
-
XCUIDevice.shared.orientation = .portrait
|
|
128
|
-
case "portrait-upside-down":
|
|
129
|
-
XCUIDevice.shared.orientation = .portraitUpsideDown
|
|
130
|
-
case "landscape-left":
|
|
131
|
-
XCUIDevice.shared.orientation = .landscapeLeft
|
|
132
|
-
case "landscape-right":
|
|
133
|
-
XCUIDevice.shared.orientation = .landscapeRight
|
|
134
|
-
default:
|
|
135
|
-
return nil
|
|
136
|
-
}
|
|
137
|
-
sleepFor(0.2)
|
|
138
|
-
switch XCUIDevice.shared.orientation {
|
|
139
|
-
case .portrait:
|
|
140
|
-
return "portrait"
|
|
141
|
-
case .portraitUpsideDown:
|
|
142
|
-
return "portrait-upside-down"
|
|
143
|
-
case .landscapeLeft:
|
|
144
|
-
return "landscape-left"
|
|
145
|
-
case .landscapeRight:
|
|
146
|
-
return "landscape-right"
|
|
147
|
-
default:
|
|
148
|
-
return nil
|
|
149
|
-
}
|
|
150
|
-
#endif
|
|
151
|
-
}
|
|
152
|
-
|
|
153
121
|
func findElement(app: XCUIApplication, text: String) -> XCUIElement? {
|
|
154
122
|
let predicate = NSPredicate(format: "label CONTAINS[c] %@ OR identifier CONTAINS[c] %@ OR value CONTAINS[c] %@", text, text, text)
|
|
155
123
|
let element = app.descendants(matching: .any).matching(predicate).firstMatch
|
|
@@ -367,168 +335,6 @@ extension RunnerTests {
|
|
|
367
335
|
&& point.y <= frame.maxY + tolerance
|
|
368
336
|
}
|
|
369
337
|
|
|
370
|
-
func isKeyboardVisible(app: XCUIApplication) -> Bool {
|
|
371
|
-
return visibleKeyboardFrame(app: app) != nil
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
func dismissKeyboard(app: XCUIApplication) -> (wasVisible: Bool, dismissed: Bool, visible: Bool) {
|
|
375
|
-
let wasVisible = isKeyboardVisible(app: app)
|
|
376
|
-
guard wasVisible else {
|
|
377
|
-
return (wasVisible: false, dismissed: false, visible: false)
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
#if os(tvOS)
|
|
381
|
-
_ = pressTvRemote(.menu)
|
|
382
|
-
sleepFor(0.2)
|
|
383
|
-
let visible = isKeyboardVisible(app: app)
|
|
384
|
-
return (wasVisible: true, dismissed: !visible, visible: visible)
|
|
385
|
-
#else
|
|
386
|
-
if tapKeyboardDismissControl(app: app) {
|
|
387
|
-
sleepFor(0.2)
|
|
388
|
-
let visible = isKeyboardVisible(app: app)
|
|
389
|
-
return (wasVisible: true, dismissed: !visible, visible: visible)
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
return (wasVisible: true, dismissed: false, visible: isKeyboardVisible(app: app))
|
|
393
|
-
#endif
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
func pressKeyboardReturn(app: XCUIApplication) -> (wasVisible: Bool, pressed: Bool, visible: Bool) {
|
|
397
|
-
#if os(tvOS)
|
|
398
|
-
return (wasVisible: false, pressed: pressTvRemote(.select), visible: false)
|
|
399
|
-
#elseif os(iOS)
|
|
400
|
-
let wasVisible = isKeyboardVisible(app: app)
|
|
401
|
-
if tapKeyboardReturnControl(app: app) {
|
|
402
|
-
sleepFor(0.2)
|
|
403
|
-
return (wasVisible: wasVisible, pressed: true, visible: isKeyboardVisible(app: app))
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
var typed = false
|
|
407
|
-
let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
|
|
408
|
-
app.typeText(XCUIKeyboardKey.return.rawValue)
|
|
409
|
-
typed = true
|
|
410
|
-
})
|
|
411
|
-
if let exceptionMessage {
|
|
412
|
-
NSLog(
|
|
413
|
-
"AGENT_DEVICE_RUNNER_KEYBOARD_RETURN_IGNORED_EXCEPTION=%@",
|
|
414
|
-
exceptionMessage
|
|
415
|
-
)
|
|
416
|
-
if let singleTarget = singleTextEntryElement(app: app) {
|
|
417
|
-
return pressKeyboardReturn(on: singleTarget, app: app, wasVisible: wasVisible)
|
|
418
|
-
}
|
|
419
|
-
return (wasVisible: wasVisible, pressed: false, visible: isKeyboardVisible(app: app))
|
|
420
|
-
}
|
|
421
|
-
sleepFor(0.2)
|
|
422
|
-
return (wasVisible: wasVisible, pressed: typed, visible: isKeyboardVisible(app: app))
|
|
423
|
-
#else
|
|
424
|
-
return (wasVisible: false, pressed: false, visible: false)
|
|
425
|
-
#endif
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
private func pressKeyboardReturn(
|
|
429
|
-
on element: XCUIElement,
|
|
430
|
-
app: XCUIApplication,
|
|
431
|
-
wasVisible: Bool
|
|
432
|
-
) -> (wasVisible: Bool, pressed: Bool, visible: Bool) {
|
|
433
|
-
#if os(iOS)
|
|
434
|
-
let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
|
|
435
|
-
element.tap()
|
|
436
|
-
element.typeText(XCUIKeyboardKey.return.rawValue)
|
|
437
|
-
})
|
|
438
|
-
if let exceptionMessage {
|
|
439
|
-
NSLog(
|
|
440
|
-
"AGENT_DEVICE_RUNNER_KEYBOARD_RETURN_TARGET_IGNORED_EXCEPTION=%@",
|
|
441
|
-
exceptionMessage
|
|
442
|
-
)
|
|
443
|
-
return (wasVisible: wasVisible, pressed: false, visible: isKeyboardVisible(app: app))
|
|
444
|
-
}
|
|
445
|
-
sleepFor(0.2)
|
|
446
|
-
return (wasVisible: wasVisible, pressed: true, visible: isKeyboardVisible(app: app))
|
|
447
|
-
#else
|
|
448
|
-
return (wasVisible: wasVisible, pressed: false, visible: false)
|
|
449
|
-
#endif
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
private func singleTextEntryElement(app: XCUIApplication) -> XCUIElement? {
|
|
453
|
-
#if os(iOS)
|
|
454
|
-
let matches = safely("KEYBOARD_RETURN_TEXT_ENTRY_QUERY", []) {
|
|
455
|
-
app.descendants(matching: .any).allElementsBoundByIndex.filter { element in
|
|
456
|
-
guard element.exists else { return false }
|
|
457
|
-
switch element.elementType {
|
|
458
|
-
case .textField, .secureTextField, .searchField, .textView:
|
|
459
|
-
return true
|
|
460
|
-
default:
|
|
461
|
-
return false
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
return matches.count == 1 ? matches[0] : nil
|
|
466
|
-
#else
|
|
467
|
-
return nil
|
|
468
|
-
#endif
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
private func tapKeyboardDismissControl(app: XCUIApplication) -> Bool {
|
|
472
|
-
#if os(tvOS)
|
|
473
|
-
return false
|
|
474
|
-
#else
|
|
475
|
-
guard let keyboardFrame = visibleKeyboardFrame(app: app) else {
|
|
476
|
-
return false
|
|
477
|
-
}
|
|
478
|
-
for label in ["Hide keyboard", "Dismiss keyboard", "Done"] {
|
|
479
|
-
let candidates = [
|
|
480
|
-
app.keyboards.buttons[label],
|
|
481
|
-
app.keyboards.keys[label],
|
|
482
|
-
app.keyboards.toolbars.buttons[label],
|
|
483
|
-
]
|
|
484
|
-
if let hittable = candidates.first(where: { $0.exists && $0.isHittable }) {
|
|
485
|
-
hittable.tap()
|
|
486
|
-
return true
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
let toolbarButtonPredicate = NSPredicate(
|
|
490
|
-
format: "label == %@ OR identifier == %@",
|
|
491
|
-
label,
|
|
492
|
-
label
|
|
493
|
-
)
|
|
494
|
-
let toolbarButtons = app.descendants(matching: .button)
|
|
495
|
-
.matching(toolbarButtonPredicate)
|
|
496
|
-
.allElementsBoundByIndex
|
|
497
|
-
if let hittable = toolbarButtons.first(where: {
|
|
498
|
-
$0.exists && $0.isHittable && isKeyboardAccessoryControl($0, keyboardFrame: keyboardFrame)
|
|
499
|
-
}) {
|
|
500
|
-
hittable.tap()
|
|
501
|
-
return true
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
return false
|
|
505
|
-
#endif
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
private func tapKeyboardReturnControl(app: XCUIApplication) -> Bool {
|
|
509
|
-
#if os(iOS)
|
|
510
|
-
for label in ["return", "Return", "Enter", "Go", "Search", "Next", "Done", "Send", "Join"] {
|
|
511
|
-
let candidates = [
|
|
512
|
-
app.keyboards.buttons[label],
|
|
513
|
-
app.keyboards.keys[label],
|
|
514
|
-
]
|
|
515
|
-
if let hittable = candidates.first(where: { $0.exists && $0.isHittable }) {
|
|
516
|
-
hittable.tap()
|
|
517
|
-
return true
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
#endif
|
|
521
|
-
return false
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
private func isKeyboardAccessoryControl(_ element: XCUIElement, keyboardFrame: CGRect) -> Bool {
|
|
525
|
-
let frame = element.frame
|
|
526
|
-
guard !frame.isEmpty && !keyboardFrame.isEmpty else {
|
|
527
|
-
return false
|
|
528
|
-
}
|
|
529
|
-
return frame.intersects(keyboardFrame) || abs(frame.maxY - keyboardFrame.minY) <= 80
|
|
530
|
-
}
|
|
531
|
-
|
|
532
338
|
private func readableText(for element: XCUIElement) -> String? {
|
|
533
339
|
let label = element.label.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
534
340
|
let identifier = element.identifier.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
@@ -1037,20 +843,6 @@ extension RunnerTests {
|
|
|
1037
843
|
#endif
|
|
1038
844
|
}
|
|
1039
845
|
|
|
1040
|
-
private func visibleKeyboardFrame(app: XCUIApplication) -> CGRect? {
|
|
1041
|
-
#if os(iOS)
|
|
1042
|
-
return safely("KEYBOARD_FRAME") {
|
|
1043
|
-
let keyboard = app.keyboards.firstMatch
|
|
1044
|
-
guard keyboard.exists else { return nil }
|
|
1045
|
-
let keyboardFrame = keyboard.frame
|
|
1046
|
-
guard !keyboardFrame.isEmpty else { return nil }
|
|
1047
|
-
return keyboardFrame
|
|
1048
|
-
}
|
|
1049
|
-
#else
|
|
1050
|
-
return nil
|
|
1051
|
-
#endif
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
846
|
func axFreeSynthesizedDragPlan(
|
|
1055
847
|
app: XCUIApplication,
|
|
1056
848
|
x: Double,
|
package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Keyboard.swift
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
|
|
3
|
+
private enum KeyboardDismissObservationTiming {
|
|
4
|
+
static let timeout: TimeInterval = 2
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
extension RunnerTests {
|
|
8
|
+
func isKeyboardVisible(app: XCUIApplication) -> Bool {
|
|
9
|
+
return visibleKeyboardFrame(app: app) != nil
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
func dismissKeyboard(app: XCUIApplication) -> (wasVisible: Bool, dismissed: Bool, visible: Bool) {
|
|
13
|
+
let keyboard = app.keyboards.firstMatch
|
|
14
|
+
let wasVisible = isKeyboardVisible(app: app)
|
|
15
|
+
guard wasVisible else {
|
|
16
|
+
return (wasVisible: false, dismissed: false, visible: false)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#if os(tvOS)
|
|
20
|
+
_ = pressTvRemote(.menu)
|
|
21
|
+
sleepFor(0.2)
|
|
22
|
+
let visible = isKeyboardVisible(app: app)
|
|
23
|
+
return (wasVisible: true, dismissed: !visible, visible: visible)
|
|
24
|
+
#else
|
|
25
|
+
if tapKeyboardDismissControl(app: app) {
|
|
26
|
+
_ = keyboard.waitForNonExistence(timeout: KeyboardDismissObservationTiming.timeout)
|
|
27
|
+
let visible = isKeyboardVisible(app: app)
|
|
28
|
+
return (wasVisible: true, dismissed: !visible, visible: visible)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (wasVisible: true, dismissed: false, visible: isKeyboardVisible(app: app))
|
|
32
|
+
#endif
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func pressKeyboardReturn(app: XCUIApplication) -> (wasVisible: Bool, pressed: Bool, visible: Bool) {
|
|
36
|
+
#if os(tvOS)
|
|
37
|
+
return (wasVisible: false, pressed: pressTvRemote(.select), visible: false)
|
|
38
|
+
#elseif os(iOS)
|
|
39
|
+
let wasVisible = isKeyboardVisible(app: app)
|
|
40
|
+
if tapKeyboardReturnControl(app: app) {
|
|
41
|
+
sleepFor(0.2)
|
|
42
|
+
return (wasVisible: wasVisible, pressed: true, visible: isKeyboardVisible(app: app))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
var typed = false
|
|
46
|
+
let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
|
|
47
|
+
app.typeText(XCUIKeyboardKey.return.rawValue)
|
|
48
|
+
typed = true
|
|
49
|
+
})
|
|
50
|
+
if let exceptionMessage {
|
|
51
|
+
NSLog(
|
|
52
|
+
"AGENT_DEVICE_RUNNER_KEYBOARD_RETURN_IGNORED_EXCEPTION=%@",
|
|
53
|
+
exceptionMessage
|
|
54
|
+
)
|
|
55
|
+
if let singleTarget = singleTextEntryElement(app: app) {
|
|
56
|
+
return pressKeyboardReturn(on: singleTarget, app: app, wasVisible: wasVisible)
|
|
57
|
+
}
|
|
58
|
+
return (wasVisible: wasVisible, pressed: false, visible: isKeyboardVisible(app: app))
|
|
59
|
+
}
|
|
60
|
+
sleepFor(0.2)
|
|
61
|
+
return (wasVisible: wasVisible, pressed: typed, visible: isKeyboardVisible(app: app))
|
|
62
|
+
#else
|
|
63
|
+
return (wasVisible: false, pressed: false, visible: false)
|
|
64
|
+
#endif
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func visibleKeyboardFrame(app: XCUIApplication) -> CGRect? {
|
|
68
|
+
#if os(iOS)
|
|
69
|
+
return safely("KEYBOARD_FRAME") {
|
|
70
|
+
let keyboard = app.keyboards.firstMatch
|
|
71
|
+
guard keyboard.exists else { return nil }
|
|
72
|
+
let keyboardFrame = keyboard.frame
|
|
73
|
+
guard !keyboardFrame.isEmpty else { return nil }
|
|
74
|
+
return keyboardFrame
|
|
75
|
+
}
|
|
76
|
+
#else
|
|
77
|
+
return nil
|
|
78
|
+
#endif
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private func pressKeyboardReturn(
|
|
82
|
+
on element: XCUIElement,
|
|
83
|
+
app: XCUIApplication,
|
|
84
|
+
wasVisible: Bool
|
|
85
|
+
) -> (wasVisible: Bool, pressed: Bool, visible: Bool) {
|
|
86
|
+
#if os(iOS)
|
|
87
|
+
let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
|
|
88
|
+
element.tap()
|
|
89
|
+
element.typeText(XCUIKeyboardKey.return.rawValue)
|
|
90
|
+
})
|
|
91
|
+
if let exceptionMessage {
|
|
92
|
+
NSLog(
|
|
93
|
+
"AGENT_DEVICE_RUNNER_KEYBOARD_RETURN_TARGET_IGNORED_EXCEPTION=%@",
|
|
94
|
+
exceptionMessage
|
|
95
|
+
)
|
|
96
|
+
return (wasVisible: wasVisible, pressed: false, visible: isKeyboardVisible(app: app))
|
|
97
|
+
}
|
|
98
|
+
sleepFor(0.2)
|
|
99
|
+
return (wasVisible: wasVisible, pressed: true, visible: isKeyboardVisible(app: app))
|
|
100
|
+
#else
|
|
101
|
+
return (wasVisible: wasVisible, pressed: false, visible: false)
|
|
102
|
+
#endif
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private func singleTextEntryElement(app: XCUIApplication) -> XCUIElement? {
|
|
106
|
+
#if os(iOS)
|
|
107
|
+
let matches = safely("KEYBOARD_RETURN_TEXT_ENTRY_QUERY", []) {
|
|
108
|
+
app.descendants(matching: .any).allElementsBoundByIndex.filter { element in
|
|
109
|
+
guard element.exists else { return false }
|
|
110
|
+
switch element.elementType {
|
|
111
|
+
case .textField, .secureTextField, .searchField, .textView:
|
|
112
|
+
return true
|
|
113
|
+
default:
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return matches.count == 1 ? matches[0] : nil
|
|
119
|
+
#else
|
|
120
|
+
return nil
|
|
121
|
+
#endif
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private func tapKeyboardDismissControl(app: XCUIApplication) -> Bool {
|
|
125
|
+
#if os(tvOS)
|
|
126
|
+
return false
|
|
127
|
+
#else
|
|
128
|
+
guard let keyboardFrame = visibleKeyboardFrame(app: app) else {
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
for label in ["Hide keyboard", "Dismiss keyboard", "Done"] {
|
|
132
|
+
let candidates = [
|
|
133
|
+
app.keyboards.buttons[label],
|
|
134
|
+
app.keyboards.keys[label],
|
|
135
|
+
app.keyboards.toolbars.buttons[label],
|
|
136
|
+
]
|
|
137
|
+
if let hittable = candidates.first(where: { $0.exists && $0.isHittable }) {
|
|
138
|
+
hittable.tap()
|
|
139
|
+
return true
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let toolbarButtonPredicate = NSPredicate(
|
|
143
|
+
format: "label == %@ OR identifier == %@",
|
|
144
|
+
label,
|
|
145
|
+
label
|
|
146
|
+
)
|
|
147
|
+
let toolbarButtons = app.descendants(matching: .button)
|
|
148
|
+
.matching(toolbarButtonPredicate)
|
|
149
|
+
.allElementsBoundByIndex
|
|
150
|
+
if let hittable = toolbarButtons.first(where: {
|
|
151
|
+
$0.exists && $0.isHittable && isKeyboardAccessoryControl($0, keyboardFrame: keyboardFrame)
|
|
152
|
+
}) {
|
|
153
|
+
hittable.tap()
|
|
154
|
+
return true
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return false
|
|
158
|
+
#endif
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private func tapKeyboardReturnControl(app: XCUIApplication) -> Bool {
|
|
162
|
+
#if os(iOS)
|
|
163
|
+
for label in ["return", "Return", "Enter", "Go", "Search", "Next", "Done", "Send", "Join"] {
|
|
164
|
+
let candidates = [
|
|
165
|
+
app.keyboards.buttons[label],
|
|
166
|
+
app.keyboards.keys[label],
|
|
167
|
+
]
|
|
168
|
+
if let hittable = candidates.first(where: { $0.exists && $0.isHittable }) {
|
|
169
|
+
hittable.tap()
|
|
170
|
+
return true
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
#endif
|
|
174
|
+
return false
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private func isKeyboardAccessoryControl(_ element: XCUIElement, keyboardFrame: CGRect) -> Bool {
|
|
178
|
+
let frame = element.frame
|
|
179
|
+
guard !frame.isEmpty && !keyboardFrame.isEmpty else {
|
|
180
|
+
return false
|
|
181
|
+
}
|
|
182
|
+
return frame.intersects(keyboardFrame) || abs(frame.maxY - keyboardFrame.minY) <= 80
|
|
183
|
+
}
|
|
184
|
+
}
|
package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Orientation.swift
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
|
|
3
|
+
private enum OrientationObservationTiming {
|
|
4
|
+
static let pollInterval: TimeInterval = 0.05
|
|
5
|
+
static let timeout: TimeInterval = 2
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
extension RunnerTests {
|
|
9
|
+
func executeRotateCommand(_ command: Command) -> Response {
|
|
10
|
+
guard let orientation = command.orientation?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
11
|
+
!orientation.isEmpty
|
|
12
|
+
else {
|
|
13
|
+
return Response(ok: false, error: ErrorPayload(message: "rotate requires orientation"))
|
|
14
|
+
}
|
|
15
|
+
let supportedOrientations = [
|
|
16
|
+
"portrait",
|
|
17
|
+
"portrait-upside-down",
|
|
18
|
+
"landscape-left",
|
|
19
|
+
"landscape-right",
|
|
20
|
+
]
|
|
21
|
+
guard supportedOrientations.contains(orientation) else {
|
|
22
|
+
return Response(
|
|
23
|
+
ok: false,
|
|
24
|
+
error: ErrorPayload(message: "unsupported rotate orientation: \(orientation)")
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
guard let observedOrientation = rotateDevice(to: orientation) else {
|
|
28
|
+
return Response(
|
|
29
|
+
ok: false,
|
|
30
|
+
error: ErrorPayload(message: "unable to observe device orientation after rotate")
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
guard observedOrientation == orientation else {
|
|
34
|
+
return Response(
|
|
35
|
+
ok: false,
|
|
36
|
+
error: ErrorPayload(
|
|
37
|
+
message: "device orientation is \(observedOrientation), expected \(orientation)"
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
return Response(
|
|
42
|
+
ok: true,
|
|
43
|
+
data: DataPayload(message: "rotate", orientation: observedOrientation)
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private func rotateDevice(to orientationName: String) -> String? {
|
|
48
|
+
#if os(macOS) || os(tvOS) || os(visionOS)
|
|
49
|
+
return nil
|
|
50
|
+
#else
|
|
51
|
+
switch orientationName {
|
|
52
|
+
case "portrait":
|
|
53
|
+
XCUIDevice.shared.orientation = .portrait
|
|
54
|
+
case "portrait-upside-down":
|
|
55
|
+
XCUIDevice.shared.orientation = .portraitUpsideDown
|
|
56
|
+
case "landscape-left":
|
|
57
|
+
XCUIDevice.shared.orientation = .landscapeLeft
|
|
58
|
+
case "landscape-right":
|
|
59
|
+
XCUIDevice.shared.orientation = .landscapeRight
|
|
60
|
+
default:
|
|
61
|
+
return nil
|
|
62
|
+
}
|
|
63
|
+
let deadline = Date().addingTimeInterval(OrientationObservationTiming.timeout)
|
|
64
|
+
repeat {
|
|
65
|
+
if observedDeviceOrientation() == orientationName {
|
|
66
|
+
return orientationName
|
|
67
|
+
}
|
|
68
|
+
sleepFor(OrientationObservationTiming.pollInterval)
|
|
69
|
+
} while Date() < deadline
|
|
70
|
+
return observedDeviceOrientation()
|
|
71
|
+
#endif
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private func observedDeviceOrientation() -> String? {
|
|
75
|
+
#if os(macOS) || os(tvOS) || os(visionOS)
|
|
76
|
+
return nil
|
|
77
|
+
#else
|
|
78
|
+
switch XCUIDevice.shared.orientation {
|
|
79
|
+
case .portrait:
|
|
80
|
+
return "portrait"
|
|
81
|
+
case .portraitUpsideDown:
|
|
82
|
+
return "portrait-upside-down"
|
|
83
|
+
case .landscapeLeft:
|
|
84
|
+
return "landscape-left"
|
|
85
|
+
case .landscapeRight:
|
|
86
|
+
return "landscape-right"
|
|
87
|
+
default:
|
|
88
|
+
return nil
|
|
89
|
+
}
|
|
90
|
+
#endif
|
|
91
|
+
}
|
|
92
|
+
}
|