mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
feat(companion): add appState, eraseText, pressKey runner handlers
The Go runner transport already calls these methods; the in-device runner implemented them only latently. They become load-bearing on the device path, where the hybrid's legacy-companion fallback is absent. Backward compatible: the simulator hybrid never calls them.
This commit is contained in:
1 parent
e04631d4c9
commit
99ae7c9294
3 files changed
+70
No files matched your search
@@ -31,6 +31,29 @@ enum AppLifecycle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// state reports the app's run state in the vocabulary the Go transport maps
|
||||||
|
// to a process state: foreground, background, or notRunning. On the
|
||||||
|
// simulator the hybrid never calls it; on device it backs ForegroundApp.
|
||||||
|
static func state(bundleIdentifier: String) -> String {
|
||||||
|
var result = "notRunning"
|
||||||
|
let collect = {
|
||||||
|
switch XCUIApplication(bundleIdentifier: bundleIdentifier).state {
|
||||||
|
case .runningForeground:
|
||||||
|
result = "foreground"
|
||||||
|
case .runningBackground, .runningBackgroundSuspended:
|
||||||
|
result = "background"
|
||||||
|
default:
|
||||||
|
result = "notRunning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if Thread.isMainThread {
|
||||||
|
collect()
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync(execute: collect)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// onMainCatching runs automation work on the main thread and converts a
|
// onMainCatching runs automation work on the main thread and converts a
|
||||||
// framework assertion into a thrown error so the server survives it.
|
// framework assertion into a thrown error so the server survives it.
|
||||||
private static func onMainCatching(_ work: @escaping () -> Void) throws {
|
private static func onMainCatching(_ work: @escaping () -> Void) throws {
|
||||||
|
|||||||
@@ -122,6 +122,19 @@ final class Server {
|
|||||||
let bundleIdentifier = params["bundleId"] as? String ?? currentBundleIdentifier
|
let bundleIdentifier = params["bundleId"] as? String ?? currentBundleIdentifier
|
||||||
try TextInput.type(text: text, replace: replace, bundleIdentifier: bundleIdentifier)
|
try TextInput.type(text: text, replace: replace, bundleIdentifier: bundleIdentifier)
|
||||||
return ["ok": true]
|
return ["ok": true]
|
||||||
|
case "eraseText":
|
||||||
|
let count = params["count"] as? Int ?? 0
|
||||||
|
let bundleIdentifier = params["bundleId"] as? String ?? currentBundleIdentifier
|
||||||
|
try TextInput.erase(count: count, bundleIdentifier: bundleIdentifier)
|
||||||
|
return ["ok": true]
|
||||||
|
case "pressKey":
|
||||||
|
let key = params["key"] as? String ?? ""
|
||||||
|
let bundleIdentifier = params["bundleId"] as? String ?? currentBundleIdentifier
|
||||||
|
try TextInput.pressKey(key: key, bundleIdentifier: bundleIdentifier)
|
||||||
|
return ["ok": true]
|
||||||
|
case "appState":
|
||||||
|
let bundleIdentifier = params["bundleId"] as? String ?? currentBundleIdentifier
|
||||||
|
return ["state": AppLifecycle.state(bundleIdentifier: bundleIdentifier)]
|
||||||
case "screenshot":
|
case "screenshot":
|
||||||
return try screenshot()
|
return try screenshot()
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -37,6 +37,40 @@ enum TextInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// erase deletes count characters from the focused field by typing that many
|
||||||
|
// delete keys. Used on device, where EraseText routes to the runner instead
|
||||||
|
// of the legacy companion's HID stream.
|
||||||
|
static func erase(count: Int, bundleIdentifier: String) throws {
|
||||||
|
guard count > 0 else { return }
|
||||||
|
let deletes = String(repeating: XCUIKeyboardKey.delete.rawValue, count: count)
|
||||||
|
try typeOnFocus(deletes, bundleIdentifier: bundleIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pressKey types a single logical key into the focused field. Only return is
|
||||||
|
// supported, matching the simulator companion's key surface.
|
||||||
|
static func pressKey(key: String, bundleIdentifier: String) throws {
|
||||||
|
switch key {
|
||||||
|
case "return", "enter", "Return", "Enter":
|
||||||
|
try typeOnFocus(XCUIKeyboardKey.return.rawValue, bundleIdentifier: bundleIdentifier)
|
||||||
|
default:
|
||||||
|
throw TextInputError.typingFailed("unsupported key \(key)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func typeOnFocus(_ payload: String, bundleIdentifier: String) throws {
|
||||||
|
let application = XCUIApplication(bundleIdentifier: bundleIdentifier)
|
||||||
|
var caughtError: NSError?
|
||||||
|
var completed = false
|
||||||
|
runOnMain {
|
||||||
|
completed = CompanionRunCatching({
|
||||||
|
application.typeText(payload)
|
||||||
|
}, &caughtError)
|
||||||
|
}
|
||||||
|
if !completed {
|
||||||
|
throw TextInputError.typingFailed(caughtError?.localizedDescription ?? "unknown")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static func runOnMain(_ work: () -> Void) {
|
private static func runOnMain(_ work: () -> Void) {
|
||||||
if Thread.isMainThread {
|
if Thread.isMainThread {
|
||||||
work()
|
work()
|
||||||
|
|||||||
Reference in new issue
Block a user