fix(cua): preserve window metadata and support Apple CLI test runtimes

This commit is contained in:
creatixchu committed 2026-09-24 18:23:08 +08:00
1 parent d994a3326b
commit bf508eaa2f
6 files changed
+34 -8

No files matched your search

+1 -1
View File
@@ -30,7 +30,7 @@ Element clicks prefer advertised AX actions and otherwise use pointer delivery.
The responsible host/helper needs macOS Accessibility permission for AX state and input, and Screen Recording permission for screenshots. Capability preflight and permission errors do not grant permissions. Capture refuses a window that moves or resizes while the image is being read. Unassociated system panels and accessory-only applications are not selectable.
Use `swift test --package-path packages/experimental/cua/native` for offline unit checks and `python3 packages/experimental/cua/native/tests/protocol_e2e.py` for JSONL admission, ordered replies and EOF shutdown without desktop operations. All Python native checks accept `--helper` to select a separately built comparison binary.
Use `python3 packages/experimental/cua/native/tests/test_swift.py` for offline unit checks with the selected Apple toolchain’s Swift Testing framework/runtime paths and `python3 packages/experimental/cua/native/tests/protocol_e2e.py` for JSONL admission, ordered replies and EOF shutdown without desktop operations. All Python native checks accept `--helper` to select a separately built comparison binary.
Run `python3 packages/experimental/cua/native/tests/axless_e2e.py` after building and unlocking the Mac to verify an owned window whose application removes and later restores its AX roots. The check requires existing Accessibility/Screen Recording grants, rejects stale element indices, captures the AXless window, verifies a coordinate click in the fixture state file, and checks restoration. It never changes system permission settings.
@@ -61,7 +61,7 @@ final class AXAccess {
}
func elementValue(_ value: CFTypeRef, _ name: String) throws -> AXUIElement {
guard CFGetTypeID(value) == AXUIElementGetTypeID() else { throw NativeFailure(code: "NATIVE_ERROR", message: "Accessibility \(name) is not an element") }
return unsafeBitCast(value, to: AXUIElement.self)
return unsafeDowncast(value, to: AXUIElement.self)
}
func element(_ element: AXUIElement, _ name: String) throws -> AXUIElement? {
guard let value = try attribute(element, name) else { return nil }
@@ -123,8 +123,8 @@ final class AXAccess {
func bounds(_ element: AXUIElement) throws -> WindowBounds? {
guard let position = try attribute(element, "AXPosition"), let size = try attribute(element, "AXSize") else { return nil }
guard CFGetTypeID(position) == AXValueGetTypeID(), CFGetTypeID(size) == AXValueGetTypeID() else { return nil }
let positionValue = unsafeBitCast(position, to: AXValue.self)
let sizeValue = unsafeBitCast(size, to: AXValue.self)
let positionValue = unsafeDowncast(position, to: AXValue.self)
let sizeValue = unsafeDowncast(size, to: AXValue.self)
var point = CGPoint.zero
var dimensions = CGSize.zero
guard AXValueGetValue(positionValue, .cgPoint, &point), AXValueGetValue(sizeValue, .cgSize, &dimensions),
@@ -183,9 +183,7 @@ actor AXWorker {
try NativePlatform.requireAccessibility()
if let window { _ = try NativePlatform.ownedWindow(identity.pid, window) }
try cancellation.check()
let observedWindows: [WindowInfo]
if let window { observedWindows = [try NativePlatform.ownedWindow(identity.pid, window)] }
else { observedWindows = NativePlatform.windows().filter { $0.pid == identity.pid } }
let observedWindows = NativePlatform.windows().filter { $0.pid == identity.pid }
let id = try identifier("snapshot")
let scope = SnapshotScope(appHandle: handle, windowID: window)
snapshots[scope] = NativeSnapshot(id: id, scope: scope, elements: projection.indexed)
@@ -0,0 +1,27 @@
"""Run Swift Testing with the selected Apple toolchain's runtime search paths."""
import subprocess
import sys
from pathlib import Path
def main():
compiler = Path(subprocess.check_output(["xcrun", "--find", "swift"], text=True).strip())
package = Path(__file__).resolve().parents[1]
command = [str(compiler), "test", "--package-path", str(package),
"--disable-xctest", "--enable-swift-testing"]
# Command Line Tools ships these libraries outside SwiftPM's default search
# paths. Full Xcode and other selected toolchains keep their normal lookup.
framework_root = compiler.parents[2] / "Library/Developer/Frameworks"
if (framework_root / "Testing.framework").is_dir():
command += ["-Xswiftc", "-F", "-Xswiftc", str(framework_root),
"-Xlinker", "-F", "-Xlinker", str(framework_root),
"-Xlinker", "-rpath", "-Xlinker", str(framework_root)]
runtime = framework_root.parent / "usr/lib"
if (runtime / "lib_TestingInterop.dylib").is_file():
command += ["-Xlinker", "-rpath", "-Xlinker", str(runtime)]
command += sys.argv[1:]
raise SystemExit(subprocess.call(command))
if __name__ == "__main__":
main()
+1
View File
@@ -36,6 +36,7 @@
"native/Package.swift",
"native/Sources",
"native/tests/swift",
"native/tests/test_swift.py",
"native/LICENSE.cua-driver",
"native/upstream.json",
"native/README.txt",
+1 -1
View File
@@ -175,7 +175,7 @@ export function readWorkspaceManifests(repositoryRoot: string): WorkspaceManifes
const packageFileExtras: Readonly<Record<string, readonly string[]>> = {
// CUA publishes its client API and attributed native source for local macOS builds.
'@deepseek-ai/dsh-experimental-cua': ['lib/api.js', 'lib/bridge.js', 'native/Package.swift', 'native/Sources', 'native/tests/swift', 'native/LICENSE.cua-driver', 'native/upstream.json', 'native/README.txt', 'native/protocol.schema.json'],
'@deepseek-ai/dsh-experimental-cua': ['lib/api.js', 'lib/bridge.js', 'native/Package.swift', 'native/Sources', 'native/tests/swift', 'native/tests/test_swift.py', 'native/LICENSE.cua-driver', 'native/upstream.json', 'native/README.txt', 'native/protocol.schema.json'],
// Owned Worker bundles import this public bootstrap before their business entry.
'@deepseek-ai/dsh-app-boot': ['lib/worker/profile-resolution-bootstrap.js'],
// Statically linked client libraries keep their stylesheets next to the emitted