@astur-mobile/ios 0.1.0-beta.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.
@@ -0,0 +1,32 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>CFBundleDevelopmentRegion</key>
6
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
7
+ <key>CFBundleExecutable</key>
8
+ <string>$(EXECUTABLE_NAME)</string>
9
+ <key>CFBundleIdentifier</key>
10
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11
+ <key>CFBundleInfoDictionaryVersion</key>
12
+ <string>6.0</string>
13
+ <key>CFBundleName</key>
14
+ <string>$(PRODUCT_NAME)</string>
15
+ <key>CFBundlePackageType</key>
16
+ <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
17
+ <key>CFBundleShortVersionString</key>
18
+ <string>1.0</string>
19
+ <key>CFBundleVersion</key>
20
+ <string>1</string>
21
+ <key>LSRequiresIPhoneOS</key>
22
+ <true/>
23
+ <key>UIApplicationSupportsIndirectInputEvents</key>
24
+ <true/>
25
+ <key>UILaunchScreen</key>
26
+ <dict/>
27
+ <key>UISupportedInterfaceOrientations</key>
28
+ <array>
29
+ <string>UIInterfaceOrientationPortrait</string>
30
+ </array>
31
+ </dict>
32
+ </plist>
@@ -0,0 +1,74 @@
1
+ # iOS XCUITest Agent
2
+
3
+ iOS native UI automation must run through XCTest/XCUITest. The iOS agent is
4
+ Astur's Swift device-side automation bridge. It is not an AI agent and it is not
5
+ an Appium/WebDriver server.
6
+
7
+ The public test API should match Android:
8
+
9
+ ```ts
10
+ await device.getById('login-submit-button').tap();
11
+ await expect(device.getByText('Welcome')).toBeVisible();
12
+ ```
13
+
14
+ The runtime path is:
15
+
16
+ ```text
17
+ Astur locator/action API
18
+ -> @astur-mobile/core session element methods
19
+ -> @astur-mobile/ios agent transport
20
+ -> Swift XCUITest agent
21
+ -> XCUIApplication / XCUIElement
22
+ ```
23
+
24
+ This directory is the code that makes iOS inspection and native interaction possible. The host-side `@astur-mobile/ios` package can install, launch, and screenshot simulator apps through Xcode tools, but only this XCTest runner can read the iOS accessibility tree and perform native element actions inside Apple's supported automation boundary.
25
+
26
+ Current command surface:
27
+
28
+ - `agent.ping`
29
+ - `device.setOrientation`
30
+ - `tree.get`
31
+ - `element.find`
32
+ - `element.findAll`
33
+ - `element.findMany`
34
+ - `element.wait`
35
+ - `element.tap`
36
+ - `element.doubleTap`
37
+ - `element.longPress`
38
+ - `element.fill`
39
+ - `element.drag`
40
+ - `gesture.tap`
41
+ - `gesture.doubleTap`
42
+ - `gesture.longPress`
43
+ - `gesture.swipe`
44
+ - `gesture.drag`
45
+ - `keyboard.state`
46
+ - `keyboard.dismiss`
47
+
48
+ Current implementation status:
49
+
50
+ - Swift XCUITest command dispatcher is implemented in `AsturAgent.swift`
51
+ - host-side iOS driver can connect to a provided endpoint or bootstrap the bundled simulator agent
52
+ - reverse HTTP bridge mode is available for simulator execution
53
+ - the bundled simulator agent is started once per Astur worker session, and Xcode DerivedData is reused per simulator id to avoid rebuilding into a fresh temp directory on every run
54
+ - native lookup, wait, tap, double tap, long press, fill, drag, swipe, and keyboard commands run in XCUITest
55
+ - fill replaces existing text inside the agent, using bounded delete input so React Native text fields do not leak state between specs
56
+ - orientation changes run through `XCUIDevice.shared.orientation`
57
+ - lifecycle, install, launch, screenshots, and video remain host-side through Xcode tools
58
+
59
+ Implementation rules:
60
+
61
+ - Use XCTest/XCUITest selectors and expectations for native UI control.
62
+ - Do wait, lookup, actionability checks, and the final action inside the agent.
63
+ - Return compact JSON snapshots for diagnostics and assertions.
64
+ - Keep simulator lifecycle in the host driver; keep native element control here.
65
+ - Preserve the public TypeScript API; complexity belongs in this layer.
66
+
67
+ Astur avoids Appium and WebDriver, but it cannot bypass Apple's XCTest
68
+ requirement for native iOS UI control.
69
+
70
+ Current platform limits:
71
+
72
+ - real-device execution still needs signing, provisioning, trusted-device handling, and real-device transport validation
73
+ - system alerts are limited to what XCTest exposes through stable queries
74
+ - direct per-app data/cache clearing is not available through `simctl`; use uninstall/reinstall reset for simulator clean state
@@ -0,0 +1,9 @@
1
+ import { type ChildProcess, type SpawnOptions } from 'node:child_process';
2
+ export interface CommandResult {
3
+ stdout: Buffer;
4
+ stderr: Buffer;
5
+ }
6
+ export declare function run(file: string, args: readonly string[], maxBuffer?: number): Promise<CommandResult>;
7
+ export declare function runText(file: string, args: readonly string[]): Promise<string>;
8
+ export declare function spawnCommand(file: string, args: readonly string[], options?: SpawnOptions): ChildProcess;
9
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,YAAY,EAA0B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGnH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,SAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAc/G;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAGpF;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,YAAiB,GAAG,YAAY,CAK5G"}
@@ -0,0 +1,37 @@
1
+ import { execFile, spawn } from 'node:child_process';
2
+ import { AsturError } from '@astur-mobile/core';
3
+ export function run(file, args, maxBuffer = 20 * 1024 * 1024) {
4
+ return new Promise((resolve, reject) => {
5
+ execFile(file, [...args], { encoding: 'buffer', maxBuffer }, (error, stdout, stderr) => {
6
+ if (error) {
7
+ reject(toCommandError(file, args, error, stdout, stderr));
8
+ return;
9
+ }
10
+ resolve({
11
+ stdout: Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout ?? ''),
12
+ stderr: Buffer.isBuffer(stderr) ? stderr : Buffer.from(stderr ?? '')
13
+ });
14
+ });
15
+ });
16
+ }
17
+ export async function runText(file, args) {
18
+ const result = await run(file, args);
19
+ return result.stdout.toString('utf8');
20
+ }
21
+ export function spawnCommand(file, args, options = {}) {
22
+ return spawn(file, [...args], {
23
+ ...options,
24
+ stdio: options.stdio ?? 'ignore'
25
+ });
26
+ }
27
+ function toCommandError(file, args, error, stdout, stderr) {
28
+ const stdoutText = Buffer.isBuffer(stdout) ? stdout.toString('utf8') : stdout;
29
+ const stderrText = Buffer.isBuffer(stderr) ? stderr.toString('utf8') : stderr;
30
+ const message = stderrText.trim() || stdoutText.trim() || error.message;
31
+ return new AsturError('COMMAND_FAILED', `${file} ${args.join(' ')} failed: ${message}`, {
32
+ code: error.code,
33
+ stdout: stdoutText,
34
+ stderr: stderrText
35
+ });
36
+ }
37
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAgE,MAAM,oBAAoB,CAAC;AACnH,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAOhD,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,IAAuB,EAAE,SAAS,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI;IACrF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACrF,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;gBAC1D,OAAO;YACT,CAAC;YAED,OAAO,CAAC;gBACN,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;gBACpE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;aACrE,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAY,EAAE,IAAuB;IACjE,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACrC,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,IAAuB,EAAE,UAAwB,EAAE;IAC5F,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE;QAC5B,GAAG,OAAO;QACV,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,QAAQ;KACjC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,cAAc,CACrB,IAAY,EACZ,IAAuB,EACvB,KAAwB,EACxB,MAAuB,EACvB,MAAuB;IAEvB,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9E,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9E,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,IAAI,UAAU,CAAC,IAAI,EAAE,IAAI,KAAK,CAAC,OAAO,CAAC;IAExE,OAAO,IAAI,UAAU,CAAC,gBAAgB,EAAE,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,OAAO,EAAE,EAAE;QACtF,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,UAAU;QAClB,MAAM,EAAE,UAAU;KACnB,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,61 @@
1
+ import type { DeviceInfo, DoctorCheck, NormalizedCapabilities } from '@astur-mobile/protocol';
2
+ import { type PlatformDriver, type PlatformSession } from '@astur-mobile/core';
3
+ export interface IosDriverOptions {
4
+ xcrunPath?: string;
5
+ xcodebuildPath?: string;
6
+ }
7
+ interface DevicectlDevicesJson {
8
+ result?: {
9
+ devices?: DevicectlDeviceJson[];
10
+ };
11
+ }
12
+ interface DevicectlDeviceJson {
13
+ identifier?: string;
14
+ connectionProperties?: {
15
+ pairingState?: string;
16
+ tunnelState?: string;
17
+ transportType?: string;
18
+ };
19
+ deviceProperties?: {
20
+ bootState?: string;
21
+ developerModeStatus?: string;
22
+ name?: string;
23
+ osVersionNumber?: string;
24
+ };
25
+ hardwareProperties?: {
26
+ marketingName?: string;
27
+ platform?: string;
28
+ udid?: string;
29
+ };
30
+ }
31
+ export declare function createIosDriver(options?: IosDriverOptions): IosDriver;
32
+ export declare class IosDriver implements PlatformDriver {
33
+ readonly platform: "ios";
34
+ private readonly xcrunPath;
35
+ private readonly xcodebuildPath;
36
+ constructor(options?: IosDriverOptions);
37
+ doctor(): Promise<DoctorCheck[]>;
38
+ listDevices(): Promise<DeviceInfo[]>;
39
+ private listDevicesForSelector;
40
+ private listSimulators;
41
+ private listConnectedDevices;
42
+ createSession(capabilities: NormalizedCapabilities): Promise<PlatformSession>;
43
+ private ensureDeviceReady;
44
+ private resolveNativeAgent;
45
+ private tryBootstrapBundledNativeAgent;
46
+ }
47
+ export declare function iosAgentDerivedDataRoot(): string;
48
+ /**
49
+ * Removes the managed XCUITest agent DerivedData directories left behind by
50
+ * previous agent source versions for a device, keeping only the current build
51
+ * (`keepPath`). Without this, every change to the bundled Swift agent — and each
52
+ * source-stamp it produces — leaves a full Xcode build behind, and they
53
+ * accumulate until the disk fills. Other devices' builds are left untouched.
54
+ * Best-effort: never throws.
55
+ */
56
+ export declare function pruneStaleIosAgentDerivedData(deviceId: string, keepPath: string, root?: string): Promise<void>;
57
+ export declare function parseSimctlDevices(output: string): DeviceInfo[];
58
+ export declare function parseXcdeviceDevices(output: string): DeviceInfo[];
59
+ export declare function parseDevicectlDevices(output: string | DevicectlDevicesJson): DeviceInfo[];
60
+ export {};
61
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAKV,UAAU,EAGV,WAAW,EAmBX,sBAAsB,EAGvB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAKL,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAG5B,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAoJD,UAAU,oBAAoB;IAC5B,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,mBAAmB,EAAE,CAAC;KACjC,CAAC;CACH;AAED,UAAU,mBAAmB;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB,CAAC,EAAE;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,gBAAgB,CAAC,EAAE;QACjB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IACF,kBAAkB,CAAC,EAAE;QACnB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AA8BD,wBAAgB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS,CAEzE;AAED,qBAAa,SAAU,YAAW,cAAc;IAC9C,QAAQ,CAAC,QAAQ,EAAG,KAAK,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,OAAO,GAAE,gBAAqB;IAKpC,MAAM,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAoGhC,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YAS5B,sBAAsB;YAYtB,cAAc;YAKd,oBAAoB;IAS5B,aAAa,CAAC,YAAY,EAAE,sBAAsB,GAAG,OAAO,CAAC,eAAe,CAAC;YA6BrE,iBAAiB;YAyCjB,kBAAkB;YAqDlB,8BAA8B;CAuO7C;AA29CD,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD;AAED;;;;;;;GAOG;AACH,wBAAsB,6BAA6B,CACjD,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,MAAkC,GACvC,OAAO,CAAC,IAAI,CAAC,CAiBf;AAoND,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE,CAuB/D;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE,CAkBjE;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,oBAAoB,GAAG,UAAU,EAAE,CAgBzF"}