snapdrive-ios 0.1.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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +95 -0
  3. package/README.md +95 -0
  4. package/dist/cli.d.ts +7 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +265 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/core/command-executor.d.ts +15 -0
  9. package/dist/core/command-executor.d.ts.map +1 -0
  10. package/dist/core/command-executor.js +64 -0
  11. package/dist/core/command-executor.js.map +1 -0
  12. package/dist/core/element-finder.d.ts +81 -0
  13. package/dist/core/element-finder.d.ts.map +1 -0
  14. package/dist/core/element-finder.js +246 -0
  15. package/dist/core/element-finder.js.map +1 -0
  16. package/dist/core/idb-client.d.ts +68 -0
  17. package/dist/core/idb-client.d.ts.map +1 -0
  18. package/dist/core/idb-client.js +327 -0
  19. package/dist/core/idb-client.js.map +1 -0
  20. package/dist/core/image-differ.d.ts +55 -0
  21. package/dist/core/image-differ.d.ts.map +1 -0
  22. package/dist/core/image-differ.js +211 -0
  23. package/dist/core/image-differ.js.map +1 -0
  24. package/dist/core/index.d.ts +9 -0
  25. package/dist/core/index.d.ts.map +1 -0
  26. package/dist/core/index.js +9 -0
  27. package/dist/core/index.js.map +1 -0
  28. package/dist/core/report-generator.d.ts +31 -0
  29. package/dist/core/report-generator.d.ts.map +1 -0
  30. package/dist/core/report-generator.js +675 -0
  31. package/dist/core/report-generator.js.map +1 -0
  32. package/dist/core/scenario-runner.d.ts +54 -0
  33. package/dist/core/scenario-runner.d.ts.map +1 -0
  34. package/dist/core/scenario-runner.js +701 -0
  35. package/dist/core/scenario-runner.js.map +1 -0
  36. package/dist/core/simctl-client.d.ts +64 -0
  37. package/dist/core/simctl-client.d.ts.map +1 -0
  38. package/dist/core/simctl-client.js +214 -0
  39. package/dist/core/simctl-client.js.map +1 -0
  40. package/dist/index.d.ts +7 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +11 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/interfaces/config.interface.d.ts +37 -0
  45. package/dist/interfaces/config.interface.d.ts.map +1 -0
  46. package/dist/interfaces/config.interface.js +14 -0
  47. package/dist/interfaces/config.interface.js.map +1 -0
  48. package/dist/interfaces/element.interface.d.ts +49 -0
  49. package/dist/interfaces/element.interface.d.ts.map +1 -0
  50. package/dist/interfaces/element.interface.js +5 -0
  51. package/dist/interfaces/element.interface.js.map +1 -0
  52. package/dist/interfaces/index.d.ts +7 -0
  53. package/dist/interfaces/index.d.ts.map +1 -0
  54. package/dist/interfaces/index.js +7 -0
  55. package/dist/interfaces/index.js.map +1 -0
  56. package/dist/interfaces/scenario.interface.d.ts +101 -0
  57. package/dist/interfaces/scenario.interface.d.ts.map +1 -0
  58. package/dist/interfaces/scenario.interface.js +5 -0
  59. package/dist/interfaces/scenario.interface.js.map +1 -0
  60. package/dist/server.d.ts +28 -0
  61. package/dist/server.d.ts.map +1 -0
  62. package/dist/server.js +943 -0
  63. package/dist/server.js.map +1 -0
  64. package/dist/utils/index.d.ts +5 -0
  65. package/dist/utils/index.d.ts.map +1 -0
  66. package/dist/utils/index.js +5 -0
  67. package/dist/utils/index.js.map +1 -0
  68. package/dist/utils/logger.d.ts +24 -0
  69. package/dist/utils/logger.d.ts.map +1 -0
  70. package/dist/utils/logger.js +50 -0
  71. package/dist/utils/logger.js.map +1 -0
  72. package/package.json +67 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element-finder.d.ts","sourceRoot":"","sources":["../../src/core/element-finder.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,oBAAoB,EACpB,KAAK,EACL,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,oCAAoC,CAAC;AAE5C,MAAM,WAAW,cAAc;IAC7B,WAAW,CAAC,QAAQ,EAAE,oBAAoB,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,oBAAoB,EAAE,CAAC;IACrF,mBAAmB,CAAC,QAAQ,EAAE,oBAAoB,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,oBAAoB,EAAE,CAAC;IAC/F,UAAU,CAAC,QAAQ,EAAE,oBAAoB,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,oBAAoB,EAAE,CAAC;IACnF,UAAU,CAAC,QAAQ,EAAE,oBAAoB,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,oBAAoB,EAAE,CAAC;IACnF,eAAe,CACb,QAAQ,EAAE,oBAAoB,EAAE,EAChC,SAAS,EAAE,gBAAgB,GAC1B,oBAAoB,EAAE,CAAC;IAC1B,QAAQ,CACN,QAAQ,EAAE,oBAAoB,EAAE,EAChC,SAAS,EAAE,gBAAgB,EAC3B,YAAY,CAAC,EAAE,KAAK,GACnB,mBAAmB,CAAC;IACvB,cAAc,CAAC,OAAO,EAAE,oBAAoB,GAAG,KAAK,CAAC;IACrD,YAAY,CAAC,QAAQ,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,CAAC;IACzD,gBAAgB,CAAC,QAAQ,EAAE,oBAAoB,EAAE,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACjG;AAED,qBAAa,aAAc,YAAW,cAAc;IAClD;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,oBAAoB,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,oBAAoB,EAAE;IAIpF;;OAEG;IACH,mBAAmB,CAAC,QAAQ,EAAE,oBAAoB,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,oBAAoB,EAAE;IAK9F;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,oBAAoB,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,oBAAoB,EAAE;IAKlF;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,oBAAoB,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,oBAAoB,EAAE;IAKlF;;OAEG;IACH,eAAe,CACb,QAAQ,EAAE,oBAAoB,EAAE,EAChC,SAAS,EAAE,gBAAgB,GAC1B,oBAAoB,EAAE;IAIzB;;OAEG;IACH,QAAQ,CACN,QAAQ,EAAE,oBAAoB,EAAE,EAChC,SAAS,EAAE,gBAAgB,EAC3B,YAAY,GAAE,KAA0B,GACvC,mBAAmB;IAwBtB;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,oBAAoB,GAAG,KAAK;IAOpD;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE;IAUxD;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA6CxB;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAsBpB;;OAEG;IACH,OAAO,CAAC,YAAY;IAWpB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAOxB;;;OAGG;IACH,gBAAgB,CAAC,QAAQ,EAAE,oBAAoB,EAAE,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;CAgFhG;AAED,eAAO,MAAM,aAAa,eAAsB,CAAC"}
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Element finder - search and locate UI elements
3
+ */
4
+ export class ElementFinder {
5
+ /**
6
+ * Find elements with exact label match
7
+ */
8
+ findByLabel(elements, label) {
9
+ return elements.filter((el) => el.label === label);
10
+ }
11
+ /**
12
+ * Find elements with partial label match
13
+ */
14
+ findByLabelContains(elements, partial) {
15
+ const lowerPartial = partial.toLowerCase();
16
+ return elements.filter((el) => el.label?.toLowerCase().includes(lowerPartial));
17
+ }
18
+ /**
19
+ * Find elements by type
20
+ */
21
+ findByType(elements, type) {
22
+ const lowerType = type.toLowerCase();
23
+ return elements.filter((el) => el.type?.toLowerCase() === lowerType);
24
+ }
25
+ /**
26
+ * Find elements by role
27
+ */
28
+ findByRole(elements, role) {
29
+ const lowerRole = role.toLowerCase();
30
+ return elements.filter((el) => el.role?.toLowerCase().includes(lowerRole));
31
+ }
32
+ /**
33
+ * Find elements matching a predicate
34
+ */
35
+ findByPredicate(elements, predicate) {
36
+ return elements.filter((el) => this.matchesPredicate(el, predicate));
37
+ }
38
+ /**
39
+ * Find the best matching element and return search result with tap coordinates
40
+ */
41
+ findBest(elements, predicate, screenCenter = { x: 200, y: 400 }) {
42
+ const matches = this.findByPredicate(elements, predicate);
43
+ if (matches.length === 0) {
44
+ return {
45
+ found: false,
46
+ elements: [],
47
+ count: 0,
48
+ };
49
+ }
50
+ // Rank and sort matches
51
+ const ranked = this.rankElements(matches, screenCenter);
52
+ const best = ranked[0];
53
+ return {
54
+ found: true,
55
+ element: best,
56
+ elements: ranked,
57
+ count: ranked.length,
58
+ tapCoordinates: this.getCenterPoint(best),
59
+ };
60
+ }
61
+ /**
62
+ * Calculate center point of element's frame
63
+ */
64
+ getCenterPoint(element) {
65
+ return {
66
+ x: Math.round(element.frame.x + element.frame.width / 2),
67
+ y: Math.round(element.frame.y + element.frame.height / 2),
68
+ };
69
+ }
70
+ /**
71
+ * Get all labels from elements (for debugging/suggestions)
72
+ */
73
+ getAllLabels(elements) {
74
+ const labels = new Set();
75
+ for (const el of elements) {
76
+ if (el.label) {
77
+ labels.add(el.label);
78
+ }
79
+ }
80
+ return Array.from(labels).sort();
81
+ }
82
+ /**
83
+ * Check if element matches predicate
84
+ */
85
+ matchesPredicate(el, pred) {
86
+ // Check label (exact match or regex)
87
+ if (pred.label !== undefined) {
88
+ if (pred.label instanceof RegExp) {
89
+ if (!el.label || !pred.label.test(el.label)) {
90
+ return false;
91
+ }
92
+ }
93
+ else {
94
+ if (el.label !== pred.label) {
95
+ return false;
96
+ }
97
+ }
98
+ }
99
+ // Check labelContains
100
+ if (pred.labelContains !== undefined) {
101
+ if (!el.label?.toLowerCase().includes(pred.labelContains.toLowerCase())) {
102
+ return false;
103
+ }
104
+ }
105
+ // Check type
106
+ if (pred.type !== undefined) {
107
+ if (el.type?.toLowerCase() !== pred.type.toLowerCase()) {
108
+ return false;
109
+ }
110
+ }
111
+ // Check role
112
+ if (pred.role !== undefined) {
113
+ if (!el.role?.toLowerCase().includes(pred.role.toLowerCase())) {
114
+ return false;
115
+ }
116
+ }
117
+ // Check enabled
118
+ if (pred.enabled !== undefined) {
119
+ if (el.enabled !== pred.enabled) {
120
+ return false;
121
+ }
122
+ }
123
+ return true;
124
+ }
125
+ /**
126
+ * Rank elements by priority:
127
+ * 1. Buttons and tappable elements first
128
+ * 2. Enabled elements before disabled
129
+ * 3. Closer to screen center
130
+ */
131
+ rankElements(elements, screenCenter) {
132
+ return [...elements].sort((a, b) => {
133
+ // Priority 1: Button-like elements
134
+ const aIsButton = this.isButtonLike(a);
135
+ const bIsButton = this.isButtonLike(b);
136
+ if (aIsButton && !bIsButton)
137
+ return -1;
138
+ if (!aIsButton && bIsButton)
139
+ return 1;
140
+ // Priority 2: Enabled elements
141
+ if (a.enabled && !b.enabled)
142
+ return -1;
143
+ if (!a.enabled && b.enabled)
144
+ return 1;
145
+ // Priority 3: Closer to screen center
146
+ const aDistance = this.distanceToCenter(a, screenCenter);
147
+ const bDistance = this.distanceToCenter(b, screenCenter);
148
+ return aDistance - bDistance;
149
+ });
150
+ }
151
+ /**
152
+ * Check if element is button-like (more likely to be tappable)
153
+ */
154
+ isButtonLike(el) {
155
+ const buttonTypes = ['button', 'link', 'tab', 'cell'];
156
+ const type = el.type?.toLowerCase() ?? '';
157
+ const role = el.role?.toLowerCase() ?? '';
158
+ return (buttonTypes.some((t) => type.includes(t) || role.includes(t)) ||
159
+ el.traits?.some((t) => t.toLowerCase().includes('button')) === true);
160
+ }
161
+ /**
162
+ * Calculate distance from element center to screen center
163
+ */
164
+ distanceToCenter(el, screenCenter) {
165
+ const center = this.getCenterPoint(el);
166
+ const dx = center.x - screenCenter.x;
167
+ const dy = center.y - screenCenter.y;
168
+ return Math.sqrt(dx * dx + dy * dy);
169
+ }
170
+ /**
171
+ * Find the best scroll region by analyzing element frames
172
+ * Returns center point of the largest container that likely contains scrollable content
173
+ */
174
+ findScrollRegion(elements) {
175
+ // Default fallback
176
+ const defaultCenter = { centerX: 200, centerY: 400 };
177
+ if (elements.length === 0) {
178
+ return defaultCenter;
179
+ }
180
+ // Find container elements (Group, etc.) that could be scroll containers
181
+ const containerTypes = ['group', 'axgroup', 'scrollview', 'tableview', 'collectionview', 'list'];
182
+ const containers = elements.filter((el) => {
183
+ const type = el.type?.toLowerCase() ?? '';
184
+ const role = el.role?.toLowerCase() ?? '';
185
+ return containerTypes.some(t => type.includes(t) || role.includes(t));
186
+ });
187
+ // If we found containers, find the largest one that's not at the very top
188
+ // (to avoid navigation bars which are usually at y < 100)
189
+ let bestContainer = null;
190
+ let maxArea = 0;
191
+ const candidateContainers = containers.length > 0 ? containers : elements;
192
+ for (const el of candidateContainers) {
193
+ const frame = el.frame;
194
+ if (!frame)
195
+ continue;
196
+ // Skip elements that are likely navigation bars (at very top)
197
+ if (frame.y < 50 && frame.height < 100)
198
+ continue;
199
+ // Skip elements that are likely tab bars (at very bottom, small height)
200
+ if (frame.y > 700 && frame.height < 100)
201
+ continue;
202
+ // Skip very small elements
203
+ if (frame.width < 100 || frame.height < 100)
204
+ continue;
205
+ const area = frame.width * frame.height;
206
+ if (area > maxArea) {
207
+ maxArea = area;
208
+ bestContainer = el;
209
+ }
210
+ }
211
+ if (bestContainer && bestContainer.frame) {
212
+ const frame = bestContainer.frame;
213
+ return {
214
+ centerX: Math.round(frame.x + frame.width / 2),
215
+ centerY: Math.round(frame.y + frame.height / 2),
216
+ };
217
+ }
218
+ // Fallback: calculate center from all element bounds
219
+ let minY = Infinity, maxY = 0;
220
+ let avgX = 0;
221
+ let count = 0;
222
+ for (const el of elements) {
223
+ if (!el.frame)
224
+ continue;
225
+ const frame = el.frame;
226
+ // Skip likely navigation/tab bars
227
+ if (frame.y < 50 && frame.height < 100)
228
+ continue;
229
+ if (frame.y > 700 && frame.height < 100)
230
+ continue;
231
+ minY = Math.min(minY, frame.y);
232
+ maxY = Math.max(maxY, frame.y + frame.height);
233
+ avgX += frame.x + frame.width / 2;
234
+ count++;
235
+ }
236
+ if (count > 0) {
237
+ return {
238
+ centerX: Math.round(avgX / count),
239
+ centerY: Math.round((minY + maxY) / 2),
240
+ };
241
+ }
242
+ return defaultCenter;
243
+ }
244
+ }
245
+ export const elementFinder = new ElementFinder();
246
+ //# sourceMappingURL=element-finder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element-finder.js","sourceRoot":"","sources":["../../src/core/element-finder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * idb (iOS Development Bridge) client wrapper
3
+ * Wraps Facebook's idb CLI for UI automation
4
+ */
5
+ import type { AccessibilityElement, UITree } from '../interfaces/element.interface.js';
6
+ import type { DeviceButton } from '../interfaces/config.interface.js';
7
+ import { type ICommandExecutor } from './command-executor.js';
8
+ import { type ILogger } from '../utils/logger.js';
9
+ export interface TapOptions {
10
+ duration?: number;
11
+ deviceUdid?: string;
12
+ }
13
+ export interface SwipeOptions {
14
+ delta?: number;
15
+ duration?: number;
16
+ deviceUdid?: string;
17
+ }
18
+ export interface IDBClientOptions {
19
+ deviceUdid?: string;
20
+ timeoutMs?: number;
21
+ }
22
+ export interface IIDBClient {
23
+ tap(x: number, y: number, options?: TapOptions): Promise<void>;
24
+ swipe(startX: number, startY: number, endX: number, endY: number, options?: SwipeOptions): Promise<void>;
25
+ typeText(text: string, deviceUdid?: string): Promise<void>;
26
+ describeAll(deviceUdid?: string): Promise<UITree>;
27
+ describePoint(x: number, y: number, deviceUdid?: string): Promise<AccessibilityElement | null>;
28
+ pressButton(button: DeviceButton, duration?: number, deviceUdid?: string): Promise<void>;
29
+ keyEvent(keyCode: number, duration?: number, deviceUdid?: string): Promise<void>;
30
+ }
31
+ export declare class IDBClient implements IIDBClient {
32
+ private executor;
33
+ private logger;
34
+ private defaultUdid?;
35
+ private timeoutMs;
36
+ private connectedUdids;
37
+ constructor(options?: IDBClientOptions, executor?: ICommandExecutor, logger?: ILogger);
38
+ /**
39
+ * Ensure idb is connected to the target device
40
+ */
41
+ private ensureConnected;
42
+ private buildArgs;
43
+ tap(x: number, y: number, options?: TapOptions): Promise<void>;
44
+ swipe(startX: number, startY: number, endX: number, endY: number, options?: SwipeOptions): Promise<void>;
45
+ typeText(text: string, deviceUdid?: string): Promise<void>;
46
+ describeAll(deviceUdid?: string): Promise<UITree>;
47
+ describePoint(x: number, y: number, deviceUdid?: string): Promise<AccessibilityElement | null>;
48
+ pressButton(button: DeviceButton, duration?: number, deviceUdid?: string): Promise<void>;
49
+ keyEvent(keyCode: number, duration?: number, deviceUdid?: string): Promise<void>;
50
+ /**
51
+ * Parse idb describe-all output into AccessibilityElement array
52
+ * Handles both JSON array and newline-delimited JSON formats
53
+ */
54
+ private parseDescribeOutput;
55
+ /**
56
+ * Normalize element from idb output to our interface
57
+ * Handles different field naming conventions defensively
58
+ */
59
+ private normalizeElement;
60
+ private getString;
61
+ private getBoolean;
62
+ private getStringArray;
63
+ private parseFrame;
64
+ private parseAXFrameString;
65
+ private isValidFrame;
66
+ }
67
+ export declare const idbClient: IDBClient;
68
+ //# sourceMappingURL=idb-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idb-client.d.ts","sourceRoot":"","sources":["../../src/core/idb-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAS,MAAM,oCAAoC,CAAC;AAC9F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,KAAK,gBAAgB,EAAmB,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,oBAAoB,CAAC;AAE1D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,KAAK,CACH,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,WAAW,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IAC/F,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzF,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClF;AAED,qBAAa,SAAU,YAAW,UAAU;IAC1C,OAAO,CAAC,QAAQ,CAAmB;IACnC,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAA0B;gBAEpC,OAAO,GAAE,gBAAqB,EAAE,QAAQ,CAAC,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,OAAO;IAOzF;;OAEG;YACW,eAAe;IAsD7B,OAAO,CAAC,SAAS;IAKX,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBlE,KAAK,CACT,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,IAAI,CAAC;IA+BV,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB1D,WAAW,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqBjD,aAAa,CACjB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IA0BjC,WAAW,CACf,MAAM,EAAE,YAAY,EACpB,QAAQ,CAAC,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;IA4BV,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBtF;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAmC3B;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAwBxB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,UAAU;IA6BlB,OAAO,CAAC,kBAAkB;IAmB1B,OAAO,CAAC,YAAY;CAGrB;AAED,eAAO,MAAM,SAAS,WAAkB,CAAC"}
@@ -0,0 +1,327 @@
1
+ /**
2
+ * idb (iOS Development Bridge) client wrapper
3
+ * Wraps Facebook's idb CLI for UI automation
4
+ */
5
+ import { CommandExecutor } from './command-executor.js';
6
+ import { Logger } from '../utils/logger.js';
7
+ export class IDBClient {
8
+ executor;
9
+ logger;
10
+ defaultUdid;
11
+ timeoutMs;
12
+ connectedUdids = new Set();
13
+ constructor(options = {}, executor, logger) {
14
+ this.executor = executor ?? new CommandExecutor();
15
+ this.logger = logger ?? new Logger('idb-client');
16
+ this.defaultUdid = options.deviceUdid;
17
+ this.timeoutMs = options.timeoutMs ?? 30000;
18
+ }
19
+ /**
20
+ * Ensure idb is connected to the target device
21
+ */
22
+ async ensureConnected(deviceUdid) {
23
+ // Get target UDID
24
+ let udid = deviceUdid ?? this.defaultUdid;
25
+ if (!udid) {
26
+ // Try to find booted device
27
+ const result = await this.executor.execute('xcrun', ['simctl', 'list', 'devices', 'booted', '-j'], { timeoutMs: 10000 });
28
+ if (result.exitCode === 0) {
29
+ try {
30
+ const data = JSON.parse(result.stdout);
31
+ for (const deviceList of Object.values(data.devices ?? {})) {
32
+ for (const device of deviceList) {
33
+ if (device.state === 'Booted' && device.udid) {
34
+ udid = device.udid;
35
+ break;
36
+ }
37
+ }
38
+ if (udid)
39
+ break;
40
+ }
41
+ }
42
+ catch {
43
+ // ignore parse error
44
+ }
45
+ }
46
+ }
47
+ if (!udid) {
48
+ throw new Error('No simulator UDID specified and no booted simulator found');
49
+ }
50
+ // Connect if not already connected
51
+ if (!this.connectedUdids.has(udid)) {
52
+ this.logger.debug(`Connecting idb to device: ${udid}`);
53
+ const connectResult = await this.executor.execute('idb', ['connect', udid], { timeoutMs: 10000 });
54
+ if (connectResult.exitCode !== 0) {
55
+ throw new Error(`Failed to connect idb to device ${udid}: ${connectResult.stderr}`);
56
+ }
57
+ this.connectedUdids.add(udid);
58
+ this.logger.info(`Connected idb to device: ${udid}`);
59
+ }
60
+ return udid;
61
+ }
62
+ buildArgs(baseArgs) {
63
+ // No --udid flag needed after idb connect
64
+ return baseArgs;
65
+ }
66
+ async tap(x, y, options = {}) {
67
+ await this.ensureConnected(options.deviceUdid);
68
+ const args = this.buildArgs(['ui', 'tap', String(Math.round(x)), String(Math.round(y))]);
69
+ if (options.duration && options.duration > 0) {
70
+ args.push('--duration', String(options.duration));
71
+ }
72
+ const result = await this.executor.execute('idb', args, {
73
+ timeoutMs: this.timeoutMs,
74
+ });
75
+ if (result.exitCode !== 0) {
76
+ throw new Error(`idb tap failed: ${result.stderr}`);
77
+ }
78
+ this.logger.debug(`Tapped at (${x}, ${y})`);
79
+ }
80
+ async swipe(startX, startY, endX, endY, options = {}) {
81
+ await this.ensureConnected(options.deviceUdid);
82
+ const args = this.buildArgs([
83
+ 'ui',
84
+ 'swipe',
85
+ String(Math.round(startX)),
86
+ String(Math.round(startY)),
87
+ String(Math.round(endX)),
88
+ String(Math.round(endY)),
89
+ ]);
90
+ if (options.delta) {
91
+ args.push('--delta', String(options.delta));
92
+ }
93
+ if (options.duration) {
94
+ args.push('--duration', String(options.duration));
95
+ }
96
+ const result = await this.executor.execute('idb', args, {
97
+ timeoutMs: this.timeoutMs,
98
+ });
99
+ if (result.exitCode !== 0) {
100
+ throw new Error(`idb swipe failed: ${result.stderr}`);
101
+ }
102
+ this.logger.debug(`Swiped from (${startX}, ${startY}) to (${endX}, ${endY})`);
103
+ }
104
+ async typeText(text, deviceUdid) {
105
+ await this.ensureConnected(deviceUdid);
106
+ const args = this.buildArgs(['ui', 'text', text]);
107
+ const result = await this.executor.execute('idb', args, {
108
+ timeoutMs: this.timeoutMs,
109
+ });
110
+ if (result.exitCode !== 0) {
111
+ throw new Error(`idb text failed: ${result.stderr}`);
112
+ }
113
+ this.logger.debug(`Typed text: "${text.slice(0, 20)}${text.length > 20 ? '...' : ''}"`);
114
+ }
115
+ async describeAll(deviceUdid) {
116
+ await this.ensureConnected(deviceUdid);
117
+ const args = this.buildArgs(['ui', 'describe-all']);
118
+ const result = await this.executor.execute('idb', args, {
119
+ timeoutMs: this.timeoutMs,
120
+ });
121
+ if (result.exitCode !== 0) {
122
+ throw new Error(`idb describe-all failed: ${result.stderr}`);
123
+ }
124
+ const elements = this.parseDescribeOutput(result.stdout);
125
+ return {
126
+ elements,
127
+ timestamp: new Date().toISOString(),
128
+ };
129
+ }
130
+ async describePoint(x, y, deviceUdid) {
131
+ await this.ensureConnected(deviceUdid);
132
+ const args = this.buildArgs([
133
+ 'ui',
134
+ 'describe-point',
135
+ String(Math.round(x)),
136
+ String(Math.round(y)),
137
+ ]);
138
+ const result = await this.executor.execute('idb', args, {
139
+ timeoutMs: this.timeoutMs,
140
+ });
141
+ if (result.exitCode !== 0) {
142
+ return null;
143
+ }
144
+ try {
145
+ const parsed = JSON.parse(result.stdout);
146
+ return this.normalizeElement(parsed);
147
+ }
148
+ catch {
149
+ return null;
150
+ }
151
+ }
152
+ async pressButton(button, duration, deviceUdid) {
153
+ await this.ensureConnected(deviceUdid);
154
+ const buttonMap = {
155
+ HOME: 'HOME',
156
+ LOCK: 'LOCK',
157
+ SIDE_BUTTON: 'SIDE_BUTTON',
158
+ SIRI: 'SIRI',
159
+ APPLE_PAY: 'APPLE_PAY',
160
+ };
161
+ const args = this.buildArgs(['ui', 'button', buttonMap[button]]);
162
+ if (duration) {
163
+ args.push('--duration', String(duration));
164
+ }
165
+ const result = await this.executor.execute('idb', args, {
166
+ timeoutMs: this.timeoutMs,
167
+ });
168
+ if (result.exitCode !== 0) {
169
+ throw new Error(`idb button press failed: ${result.stderr}`);
170
+ }
171
+ this.logger.debug(`Pressed button: ${button}`);
172
+ }
173
+ async keyEvent(keyCode, duration, deviceUdid) {
174
+ await this.ensureConnected(deviceUdid);
175
+ const args = this.buildArgs(['ui', 'key', String(keyCode)]);
176
+ if (duration) {
177
+ args.push('--duration', String(duration));
178
+ }
179
+ const result = await this.executor.execute('idb', args, {
180
+ timeoutMs: this.timeoutMs,
181
+ });
182
+ if (result.exitCode !== 0) {
183
+ throw new Error(`idb key event failed: ${result.stderr}`);
184
+ }
185
+ this.logger.debug(`Sent key event: ${keyCode}`);
186
+ }
187
+ /**
188
+ * Parse idb describe-all output into AccessibilityElement array
189
+ * Handles both JSON array and newline-delimited JSON formats
190
+ */
191
+ parseDescribeOutput(output) {
192
+ const trimmed = output.trim();
193
+ if (!trimmed) {
194
+ return [];
195
+ }
196
+ try {
197
+ // Try parsing as JSON array first
198
+ const parsed = JSON.parse(trimmed);
199
+ if (Array.isArray(parsed)) {
200
+ return parsed.map((el) => this.normalizeElement(el)).filter(Boolean);
201
+ }
202
+ // Single object
203
+ const normalized = this.normalizeElement(parsed);
204
+ return normalized ? [normalized] : [];
205
+ }
206
+ catch {
207
+ // Try parsing as newline-delimited JSON
208
+ const elements = [];
209
+ for (const line of trimmed.split('\n')) {
210
+ const lineTrimmed = line.trim();
211
+ if (!lineTrimmed)
212
+ continue;
213
+ try {
214
+ const parsed = JSON.parse(lineTrimmed);
215
+ const normalized = this.normalizeElement(parsed);
216
+ if (normalized) {
217
+ elements.push(normalized);
218
+ }
219
+ }
220
+ catch {
221
+ this.logger.debug(`Failed to parse line: ${lineTrimmed.slice(0, 50)}`);
222
+ }
223
+ }
224
+ return elements;
225
+ }
226
+ }
227
+ /**
228
+ * Normalize element from idb output to our interface
229
+ * Handles different field naming conventions defensively
230
+ */
231
+ normalizeElement(raw) {
232
+ if (!raw || typeof raw !== 'object') {
233
+ return null;
234
+ }
235
+ // Parse frame - handle multiple formats
236
+ const frame = this.parseFrame(raw);
237
+ if (!frame || !this.isValidFrame(frame)) {
238
+ return null;
239
+ }
240
+ return {
241
+ label: this.getString(raw, ['AXLabel', 'label', 'title', 'name']),
242
+ value: this.getString(raw, ['AXValue', 'value']),
243
+ type: this.getString(raw, ['type', 'AXType', 'element_type']),
244
+ role: this.getString(raw, ['role', 'AXRole']),
245
+ roleDescription: this.getString(raw, ['role_description', 'AXRoleDescription']),
246
+ identifier: this.getString(raw, ['AXUniqueId', 'identifier', 'accessibilityIdentifier']),
247
+ frame,
248
+ enabled: this.getBoolean(raw, ['enabled', 'AXEnabled'], true),
249
+ traits: this.getStringArray(raw, ['traits', 'AXTraits']),
250
+ };
251
+ }
252
+ getString(obj, keys) {
253
+ for (const key of keys) {
254
+ const val = obj[key];
255
+ if (typeof val === 'string' && val.length > 0) {
256
+ return val;
257
+ }
258
+ }
259
+ return undefined;
260
+ }
261
+ getBoolean(obj, keys, defaultVal) {
262
+ for (const key of keys) {
263
+ const val = obj[key];
264
+ if (typeof val === 'boolean') {
265
+ return val;
266
+ }
267
+ }
268
+ return defaultVal;
269
+ }
270
+ getStringArray(obj, keys) {
271
+ for (const key of keys) {
272
+ const val = obj[key];
273
+ if (Array.isArray(val)) {
274
+ return val.filter((v) => typeof v === 'string');
275
+ }
276
+ }
277
+ return undefined;
278
+ }
279
+ parseFrame(obj) {
280
+ // Format 1: {frame: {x, y, width, height}}
281
+ const frameObj = obj['frame'];
282
+ if (frameObj && typeof frameObj === 'object') {
283
+ const f = frameObj;
284
+ if (typeof f['x'] === 'number' &&
285
+ typeof f['y'] === 'number' &&
286
+ typeof f['width'] === 'number' &&
287
+ typeof f['height'] === 'number') {
288
+ return {
289
+ x: f['x'],
290
+ y: f['y'],
291
+ width: f['width'],
292
+ height: f['height'],
293
+ };
294
+ }
295
+ }
296
+ // Format 2: AXFrame string "{{x, y}, {width, height}}"
297
+ const axFrame = obj['AXFrame'];
298
+ if (typeof axFrame === 'string') {
299
+ return this.parseAXFrameString(axFrame);
300
+ }
301
+ return null;
302
+ }
303
+ parseAXFrameString(frameStr) {
304
+ // Format: "{{123.0, 456.0}, {100.0, 50.0}}"
305
+ try {
306
+ const cleaned = frameStr.replace(/\s/g, '').replace(/[{}]/g, '');
307
+ const nums = cleaned.split(',').map(Number);
308
+ if (nums.length === 4 && nums.every((n) => !isNaN(n))) {
309
+ return {
310
+ x: nums[0],
311
+ y: nums[1],
312
+ width: nums[2],
313
+ height: nums[3],
314
+ };
315
+ }
316
+ }
317
+ catch {
318
+ // Fall through to return null
319
+ }
320
+ return null;
321
+ }
322
+ isValidFrame(frame) {
323
+ return frame.width > 0 && frame.height > 0 && frame.x >= 0 && frame.y >= 0;
324
+ }
325
+ }
326
+ export const idbClient = new IDBClient();
327
+ //# sourceMappingURL=idb-client.js.map