devicely 2.1.7 → 2.1.8
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/lib/scriptLoader.js +2 -2
- package/lib/server.js +51 -17
- package/package.json +1 -1
- package/scripts/shell/android_device_control.enc +1 -0
- package/scripts/shell/connect_android_usb_multi_final.enc +1 -0
- package/scripts/shell/connect_android_wireless.enc +1 -0
- package/scripts/shell/connect_android_wireless_multi_final.enc +1 -0
- package/scripts/shell/connect_ios_usb_multi_final.enc +1 -0
- package/scripts/shell/connect_ios_wireless_multi_final.enc +1 -0
- package/scripts/shell/ios_device_control.enc +1 -0
- package/scripts/shell/android_device_control.sh +0 -848
- package/scripts/shell/connect_android_usb_multi_final.sh +0 -289
- package/scripts/shell/connect_android_wireless.sh +0 -58
- package/scripts/shell/connect_android_wireless_multi_final.sh +0 -476
- package/scripts/shell/connect_ios_usb_multi_final.sh +0 -4225
- package/scripts/shell/connect_ios_wireless_multi_final.sh +0 -4167
- package/scripts/shell/create_production_scripts.sh +0 -38
- package/scripts/shell/install_uiautomator2.sh +0 -93
- package/scripts/shell/ios_device_control.sh +0 -220
- package/scripts/shell/organize_project.sh +0 -59
- package/scripts/shell/pre-publish-check.sh +0 -238
- package/scripts/shell/publish-to-npm.sh +0 -366
- package/scripts/shell/publish.sh +0 -100
- package/scripts/shell/setup.sh +0 -121
- package/scripts/shell/start.sh +0 -59
- package/scripts/shell/sync-to-npm-package-final.sh +0 -60
- package/scripts/shell/test-local-package.sh +0 -95
- package/scripts/shell/verify-shell-protection.sh +0 -73
|
@@ -1,4167 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# iOS Multi-Device Control - ENHANCED VERSION WITH AI
|
|
3
|
-
# Features: Revolutionary AI-powered click detection + Trust-preserving cleanup
|
|
4
|
-
# Usage:
|
|
5
|
-
# ./connect_ios_wireless_multi_final.sh # Interactive mode
|
|
6
|
-
# ./connect_ios_wireless_multi_final.sh "text" # Send text to all
|
|
7
|
-
# ./connect_ios_wireless_multi_final.sh -d device "text" # Send to specific device
|
|
8
|
-
# ./connect_ios_wireless_multi_final.sh "click 100,200" # Click at coordinates
|
|
9
|
-
# ./connect_ios_wireless_multi_final.sh "click 'Button'" # AI-powered click on element
|
|
10
|
-
# ./connect_ios_wireless_multi_final.sh "longpress 'Menu'" # Long press on element
|
|
11
|
-
#
|
|
12
|
-
# Revolutionary Features:
|
|
13
|
-
# 🚀 AI-powered element detection - Pre-caches all screen elements
|
|
14
|
-
# 🧠 Smart fuzzy matching - Finds elements like human would
|
|
15
|
-
# 🎯 Multi-strategy fallback - Professional automation approaches
|
|
16
|
-
# 📸 Screen analysis caching - Instant element lookup after first scan
|
|
17
|
-
# 🔄 Self-refreshing cache - Updates when screen changes detected
|
|
18
|
-
#
|
|
19
|
-
# Commands:
|
|
20
|
-
# click <x,y> or click <text> - AI-powered click detection
|
|
21
|
-
# longpress <x,y> or longpress <text> - AI-powered long press
|
|
22
|
-
# getLocators - Get all locators from current screen
|
|
23
|
-
# refresh - Force refresh screen element cache
|
|
24
|
-
# forcecleanup - Force terminate WDA (may affect device trust)
|
|
25
|
-
#
|
|
26
|
-
# Trust Preservation:
|
|
27
|
-
# Normal 'quit' preserves device trust by keeping WDA processes running
|
|
28
|
-
|
|
29
|
-
# Load the revolutionary AI click system
|
|
30
|
-
SCRIPT_DIR="$(dirname "$0")"
|
|
31
|
-
source "$SCRIPT_DIR/advanced_click_system.sh" 2>/dev/null || echo "⚠️ AI system not loaded, using fallback"
|
|
32
|
-
source "$SCRIPT_DIR/ios_click_fix.sh" 2>/dev/null || echo "⚠️ Non-AI click helper not loaded"
|
|
33
|
-
|
|
34
|
-
WDA_PORT=8100
|
|
35
|
-
WDA_PORT_BASE=8100
|
|
36
|
-
SCRIPT_DIR="$(dirname "$0")"
|
|
37
|
-
|
|
38
|
-
# Function to find config files in multiple locations
|
|
39
|
-
find_config() {
|
|
40
|
-
local filename="$1"
|
|
41
|
-
local search_paths=(
|
|
42
|
-
"$SCRIPT_DIR/$filename"
|
|
43
|
-
"$SCRIPT_DIR/../../config/$filename"
|
|
44
|
-
"$SCRIPT_DIR/../config/$filename"
|
|
45
|
-
"$HOME/.devicely/$filename"
|
|
46
|
-
"/opt/homebrew/lib/node_modules/devicely/config/$filename"
|
|
47
|
-
"$(npm root -g 2>/dev/null)/devicely/config/$filename"
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
for path in "${search_paths[@]}"; do
|
|
51
|
-
if [ -f "$path" ]; then
|
|
52
|
-
echo "$path"
|
|
53
|
-
return 0
|
|
54
|
-
fi
|
|
55
|
-
done
|
|
56
|
-
|
|
57
|
-
return 1
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
# Find config files
|
|
61
|
-
CONFIG_FILE=$(find_config "devices.conf")
|
|
62
|
-
if [ -z "$CONFIG_FILE" ]; then
|
|
63
|
-
if [ -f "$HOME/.devicely/devices.conf" ]; then
|
|
64
|
-
CONFIG_FILE="$HOME/.devicely/devices.conf"
|
|
65
|
-
else
|
|
66
|
-
echo "❌ Required file not found: devices.conf"
|
|
67
|
-
echo " Searched in: script dir, config/, ~/.devicely/, global npm"
|
|
68
|
-
exit 1
|
|
69
|
-
fi
|
|
70
|
-
fi
|
|
71
|
-
|
|
72
|
-
APPS_PRESETS_FILE=$(find_config "apps_presets.conf")
|
|
73
|
-
if [ -z "$APPS_PRESETS_FILE" ]; then
|
|
74
|
-
if [ -f "$HOME/.devicely/apps_presets.conf" ]; then
|
|
75
|
-
APPS_PRESETS_FILE="$HOME/.devicely/apps_presets.conf"
|
|
76
|
-
else
|
|
77
|
-
echo "❌ Required file not found: apps_presets.conf"
|
|
78
|
-
echo " Searched in: script dir, config/, ~/.devicely/, global npm"
|
|
79
|
-
exit 1
|
|
80
|
-
fi
|
|
81
|
-
fi
|
|
82
|
-
|
|
83
|
-
# Track iproxy processes for USB devices
|
|
84
|
-
IPROXY_PIDS_FILE="/tmp/ios_multi_iproxy_pids.$$"
|
|
85
|
-
DEVICE_PORT_MAP_FILE="/tmp/ios_multi_device_ports.$$"
|
|
86
|
-
DEVICE_CONNECTION_TYPE_FILE="/tmp/ios_multi_connection_types.$$"
|
|
87
|
-
|
|
88
|
-
# Command history variables (like original script)
|
|
89
|
-
HISTORY_FILE="/tmp/.ios_multi_history"
|
|
90
|
-
HISTORY_MAX=50
|
|
91
|
-
declare -a TEXT_HISTORY
|
|
92
|
-
HISTORY_INDEX=-1
|
|
93
|
-
CURRENT_INPUT=""
|
|
94
|
-
|
|
95
|
-
# Disable bash history to avoid showing shell commands (like original script)
|
|
96
|
-
set +o history
|
|
97
|
-
unset HISTFILE
|
|
98
|
-
|
|
99
|
-
# Colors
|
|
100
|
-
RED='\033[0;31m'
|
|
101
|
-
GREEN='\033[0;32m'
|
|
102
|
-
YELLOW='\033[1;33m'
|
|
103
|
-
BLUE='\033[0;34m'
|
|
104
|
-
CYAN='\033[0;36m'
|
|
105
|
-
NC='\033[0m'
|
|
106
|
-
|
|
107
|
-
print_color() {
|
|
108
|
-
echo -e "$1$2${NC}"
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
# Global session tracking (use fixed filename so sessions persist across command invocations)
|
|
112
|
-
DEVICE_SESSIONS_FILE="/tmp/ios_multi_sessions"
|
|
113
|
-
|
|
114
|
-
# Parse arguments
|
|
115
|
-
DEVICE_NAME_REQUEST=""
|
|
116
|
-
TEXT=""
|
|
117
|
-
APP_BUNDLE=""
|
|
118
|
-
|
|
119
|
-
if [ "$1" = "-d" ]; then
|
|
120
|
-
DEVICE_NAME_REQUEST="$2"
|
|
121
|
-
TEXT="$3"
|
|
122
|
-
elif [ -n "$1" ]; then
|
|
123
|
-
TEXT="$1"
|
|
124
|
-
fi
|
|
125
|
-
|
|
126
|
-
# Check files
|
|
127
|
-
for file in "$CONFIG_FILE" "$APPS_PRESETS_FILE"; do
|
|
128
|
-
if [ ! -f "$file" ]; then
|
|
129
|
-
echo "❌ Required file not found: $file"
|
|
130
|
-
exit 1
|
|
131
|
-
fi
|
|
132
|
-
done
|
|
133
|
-
|
|
134
|
-
# Get device names from config
|
|
135
|
-
get_all_device_names() {
|
|
136
|
-
local devices=()
|
|
137
|
-
while IFS=',' read -r name rest; do
|
|
138
|
-
[[ "$name" =~ ^# ]] || [[ -z "$name" ]] && continue
|
|
139
|
-
devices+=("$name")
|
|
140
|
-
done < "$CONFIG_FILE"
|
|
141
|
-
printf '%s\n' "${devices[@]}"
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
# Check if device is connected via USB
|
|
145
|
-
is_usb_connected() {
|
|
146
|
-
local udid="$1"
|
|
147
|
-
if command -v idevice_id >/dev/null 2>&1; then
|
|
148
|
-
idevice_id -l 2>/dev/null | grep -q "^${udid}$"
|
|
149
|
-
return $?
|
|
150
|
-
fi
|
|
151
|
-
return 1
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
# Get USB device name from UDID
|
|
155
|
-
get_usb_device_name() {
|
|
156
|
-
local udid="$1"
|
|
157
|
-
if command -v idevicename >/dev/null 2>&1; then
|
|
158
|
-
idevicename -u "$udid" 2>/dev/null
|
|
159
|
-
fi
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
# Get device details (host,udid)
|
|
163
|
-
get_device_details() {
|
|
164
|
-
local device_name="$1"
|
|
165
|
-
|
|
166
|
-
# First check if it's a USB device
|
|
167
|
-
if command -v idevice_id >/dev/null 2>&1; then
|
|
168
|
-
local usb_udids=($(idevice_id -l 2>/dev/null))
|
|
169
|
-
for udid in "${usb_udids[@]}"; do
|
|
170
|
-
local usb_name=$(idevicename -u "$udid" 2>/dev/null || echo "")
|
|
171
|
-
if [ "$usb_name" = "$device_name" ] || [ "$udid" = "$device_name" ]; then
|
|
172
|
-
# For USB devices, return localhost as host
|
|
173
|
-
echo "localhost,$udid"
|
|
174
|
-
return 0
|
|
175
|
-
fi
|
|
176
|
-
done
|
|
177
|
-
fi
|
|
178
|
-
|
|
179
|
-
# If not found in USB, check config file for wireless
|
|
180
|
-
# IMPORTANT: For wireless script, prioritize wireless entries (entries with IP addresses)
|
|
181
|
-
local best_match_ip=""
|
|
182
|
-
local best_match_udid=""
|
|
183
|
-
|
|
184
|
-
while IFS=',' read -r name udid ip platform type rest; do
|
|
185
|
-
[[ "$name" =~ ^# ]] || [[ -z "$name" ]] && continue
|
|
186
|
-
|
|
187
|
-
# Clean up whitespace
|
|
188
|
-
name=$(echo "$name" | xargs)
|
|
189
|
-
udid=$(echo "$udid" | xargs)
|
|
190
|
-
ip=$(echo "$ip" | xargs)
|
|
191
|
-
platform=$(echo "$platform" | xargs)
|
|
192
|
-
type=$(echo "$type" | xargs)
|
|
193
|
-
|
|
194
|
-
if [ "$name" = "$device_name" ]; then
|
|
195
|
-
# Prefer wireless entries (ones with IP addresses)
|
|
196
|
-
if [ -n "$ip" ] && [[ "$type" == "wireless" ]]; then
|
|
197
|
-
# Found wireless entry - return immediately
|
|
198
|
-
echo "$ip,$udid"
|
|
199
|
-
return 0
|
|
200
|
-
elif [ -z "$best_match_ip" ]; then
|
|
201
|
-
# Store first match as fallback
|
|
202
|
-
best_match_ip="$ip"
|
|
203
|
-
best_match_udid="$udid"
|
|
204
|
-
fi
|
|
205
|
-
fi
|
|
206
|
-
done < "$CONFIG_FILE"
|
|
207
|
-
|
|
208
|
-
# If we didn't find a wireless entry, use the first match (if any)
|
|
209
|
-
if [ -n "$best_match_udid" ]; then
|
|
210
|
-
if [ -n "$best_match_ip" ]; then
|
|
211
|
-
echo "$best_match_ip,$best_match_udid"
|
|
212
|
-
else
|
|
213
|
-
echo "$best_match_udid,$best_match_udid"
|
|
214
|
-
fi
|
|
215
|
-
return 0
|
|
216
|
-
fi
|
|
217
|
-
|
|
218
|
-
# Not found
|
|
219
|
-
return 1
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
# Resolve app name to bundle ID
|
|
223
|
-
resolve_app_bundle() {
|
|
224
|
-
local input="$1"
|
|
225
|
-
|
|
226
|
-
# If already a bundle ID, return as-is
|
|
227
|
-
if [[ "$input" == *"."* ]] && [[ "$input" != *" "* ]]; then
|
|
228
|
-
echo "$input"
|
|
229
|
-
return
|
|
230
|
-
fi
|
|
231
|
-
|
|
232
|
-
# Look up in presets
|
|
233
|
-
if [ -f "$APPS_PRESETS_FILE" ]; then
|
|
234
|
-
local input_lower=$(echo "$input" | tr '[:upper:]' '[:lower:]')
|
|
235
|
-
while IFS=',' read -r name bundle rest; do
|
|
236
|
-
[[ "$name" =~ ^# ]] || [[ -z "$name" ]] && continue
|
|
237
|
-
if [ "$(echo "$name" | tr '[:upper:]' '[:lower:]')" = "$input_lower" ]; then
|
|
238
|
-
echo "$bundle"
|
|
239
|
-
return
|
|
240
|
-
fi
|
|
241
|
-
done < "$APPS_PRESETS_FILE"
|
|
242
|
-
fi
|
|
243
|
-
|
|
244
|
-
# Default bundle IDs for common apps
|
|
245
|
-
case "$input_lower" in
|
|
246
|
-
"safari") echo "com.apple.mobilesafari" ;;
|
|
247
|
-
"settings") echo "com.apple.Preferences" ;;
|
|
248
|
-
"messages") echo "com.apple.MobileSMS" ;;
|
|
249
|
-
"camera") echo "com.apple.camera" ;;
|
|
250
|
-
"photos") echo "com.apple.mobileslideshow" ;;
|
|
251
|
-
"notes") echo "com.apple.mobilenotes" ;;
|
|
252
|
-
*) echo "$input" ;;
|
|
253
|
-
esac
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
# Check device connectivity
|
|
257
|
-
check_device_connectivity() {
|
|
258
|
-
local device_name="$1"
|
|
259
|
-
local device_details
|
|
260
|
-
device_details=$(get_device_details "$device_name")
|
|
261
|
-
|
|
262
|
-
if [ $? -ne 0 ]; then
|
|
263
|
-
echo " ❌ Device not found in config"
|
|
264
|
-
return 1
|
|
265
|
-
fi
|
|
266
|
-
|
|
267
|
-
local host=$(echo "$device_details" | cut -d',' -f1)
|
|
268
|
-
|
|
269
|
-
# First check network connectivity with ping
|
|
270
|
-
echo " 🌐 Ping test to $host..."
|
|
271
|
-
if ping -c 1 -W 2000 "$host" >/dev/null 2>&1; then
|
|
272
|
-
echo " ✅ Network reachable"
|
|
273
|
-
else
|
|
274
|
-
echo " ❌ Network unreachable"
|
|
275
|
-
return 1
|
|
276
|
-
fi
|
|
277
|
-
|
|
278
|
-
# Then test WDA connection with optimized timeout
|
|
279
|
-
echo " 🔍 Testing WDA connection..."
|
|
280
|
-
if curl -s --connect-timeout 2 --max-time 3 "http://$host:$WDA_PORT/status" 2>/dev/null | grep -q '"state"'; then
|
|
281
|
-
echo " ✅ WDA ready"
|
|
282
|
-
return 0
|
|
283
|
-
else
|
|
284
|
-
echo " ⚠️ WDA not ready, attempting auto-start..."
|
|
285
|
-
|
|
286
|
-
# Auto-start WDA wirelessly (like original script)
|
|
287
|
-
local udid=$(echo "$device_details" | cut -d',' -f2)
|
|
288
|
-
if [ -n "$udid" ]; then
|
|
289
|
-
start_wda_wirelessly "$device_name" "$host" "$udid"
|
|
290
|
-
else
|
|
291
|
-
return 1
|
|
292
|
-
fi
|
|
293
|
-
fi
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
# Start WDA wirelessly (adapted from original script)
|
|
297
|
-
start_wda_wirelessly() {
|
|
298
|
-
local device_name="$1"
|
|
299
|
-
local host="$2"
|
|
300
|
-
local udid="$3"
|
|
301
|
-
|
|
302
|
-
echo " 🔧 Starting WDA wirelessly for $device_name (UDID: $udid)..."
|
|
303
|
-
|
|
304
|
-
# Check if WebDriverAgent path exists
|
|
305
|
-
local WDA_PATH="$HOME/Downloads/WebDriverAgent-10.2.1"
|
|
306
|
-
if [ ! -d "$WDA_PATH" ]; then
|
|
307
|
-
print_color "$RED" " ❌ WebDriverAgent path not found: $WDA_PATH"
|
|
308
|
-
print_color "$YELLOW" " ℹ️ Please download WebDriverAgent and place it in $WDA_PATH"
|
|
309
|
-
return 1
|
|
310
|
-
fi
|
|
311
|
-
|
|
312
|
-
# Check if xcrun is available
|
|
313
|
-
if ! command -v xcrun >/dev/null 2>&1; then
|
|
314
|
-
print_color "$RED" " ❌ xcrun not available (Xcode not installed)"
|
|
315
|
-
return 1
|
|
316
|
-
fi
|
|
317
|
-
|
|
318
|
-
# Create device-specific log file
|
|
319
|
-
local wda_log="/tmp/wda_output_${device_name}.log"
|
|
320
|
-
: > "$wda_log"
|
|
321
|
-
|
|
322
|
-
print_color "$YELLOW" " → Starting WDA (optimized)..."
|
|
323
|
-
|
|
324
|
-
# Start WDA build/test in background with better error handling
|
|
325
|
-
(
|
|
326
|
-
cd "$WDA_PATH" || exit 1
|
|
327
|
-
|
|
328
|
-
# Check if already built, skip build if possible
|
|
329
|
-
if [ -d "build" ] && [ -f "build/Build/Products/Debug-iphoneos/WebDriverAgentRunner-Runner.app/Info.plist" ]; then
|
|
330
|
-
echo " → Using existing build, launching test..."
|
|
331
|
-
xcodebuild -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination "platform=iOS,id=$udid" test-without-building > "$wda_log" 2>&1 &
|
|
332
|
-
else
|
|
333
|
-
echo " → Building and launching WDA..."
|
|
334
|
-
xcodebuild -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination "platform=iOS,id=$udid" test > "$wda_log" 2>&1 &
|
|
335
|
-
fi
|
|
336
|
-
) &
|
|
337
|
-
|
|
338
|
-
# Wait for WDA to start (optimized timing)
|
|
339
|
-
echo " ⏳ Waiting for WDA to start (timeout: 60s)..."
|
|
340
|
-
for i in {1..60}; do
|
|
341
|
-
if curl -s --connect-timeout 1 --max-time 2 "http://$host:$WDA_PORT/status" 2>/dev/null | grep -q '"state"'; then
|
|
342
|
-
print_color "$GREEN" " ✅ WDA started in ${i}s for $device_name"
|
|
343
|
-
return 0
|
|
344
|
-
fi
|
|
345
|
-
|
|
346
|
-
# Show progress every 10 seconds
|
|
347
|
-
if [ $((i % 10)) -eq 0 ]; then
|
|
348
|
-
echo " ⏳ Still waiting... (${i}s elapsed)"
|
|
349
|
-
fi
|
|
350
|
-
|
|
351
|
-
sleep 1
|
|
352
|
-
done
|
|
353
|
-
|
|
354
|
-
# Check for test failures in log
|
|
355
|
-
if grep -q "Failing tests:" "$wda_log" 2>/dev/null; then
|
|
356
|
-
print_color "$RED" " ❌ WDA test failure detected for $device_name"
|
|
357
|
-
return 1
|
|
358
|
-
fi
|
|
359
|
-
|
|
360
|
-
# Try to extract IP from log if auto-detection failed
|
|
361
|
-
if ! curl -s --connect-timeout 2 --max-time 3 "http://$host:$WDA_PORT/status" >/dev/null 2>&1; then
|
|
362
|
-
local log_ip=$(grep -oE 'ServerURLHere->http://([0-9]{1,3}(\.[0-9]{1,3}){3}):8100<-ServerURLHere' "$wda_log" 2>/dev/null | head -1 | sed -E 's/.*http:\/\/([0-9\.]+):8100.*/\1/')
|
|
363
|
-
if [ -n "$log_ip" ] && [ "$log_ip" != "$host" ]; then
|
|
364
|
-
print_color "$YELLOW" " → Trying extracted IP: $log_ip"
|
|
365
|
-
if curl -s --connect-timeout 2 --max-time 3 "http://$log_ip:$WDA_PORT/status" 2>/dev/null | grep -q '"state"'; then
|
|
366
|
-
# Update the host in devices.conf or return success with new IP
|
|
367
|
-
print_color "$GREEN" " ✅ WDA started on $log_ip for $device_name"
|
|
368
|
-
return 0
|
|
369
|
-
fi
|
|
370
|
-
fi
|
|
371
|
-
fi
|
|
372
|
-
|
|
373
|
-
print_color "$RED" " ❌ WDA failed to start for $device_name"
|
|
374
|
-
print_color "$YELLOW" " ⚠️ Tips: Ensure device is unlocked, on same WiFi, and WDA built once manually via Xcode"
|
|
375
|
-
return 1
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
# History management functions (adapted from original script)
|
|
379
|
-
load_text_history() {
|
|
380
|
-
# Ensure history file exists and is clean
|
|
381
|
-
if [ ! -f "$HISTORY_FILE" ]; then
|
|
382
|
-
touch "$HISTORY_FILE"
|
|
383
|
-
fi
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
# Add text to history
|
|
387
|
-
add_to_history() {
|
|
388
|
-
local text="$1"
|
|
389
|
-
# Skip empty text, commands, and special keys
|
|
390
|
-
if [ -z "$text" ] || [[ "$text" == /* ]] || [[ "$text" == "{*}" ]] || [[ "$text" == "\\n" ]] || [[ "$text" == "quit" ]] || [[ "$text" == "exit" ]] || [[ "$text" == "help" ]]; then
|
|
391
|
-
return
|
|
392
|
-
fi
|
|
393
|
-
|
|
394
|
-
# Add to history file (remove duplicates and keep last entries)
|
|
395
|
-
grep -v "^$(printf '%s\n' "$text" | sed 's/[[\.*^$()+?{|]/\\&/g')$" "$HISTORY_FILE" > "${HISTORY_FILE}.tmp" 2>/dev/null || true
|
|
396
|
-
echo "$text" >> "${HISTORY_FILE}.tmp"
|
|
397
|
-
tail -n "$HISTORY_MAX" "${HISTORY_FILE}.tmp" > "$HISTORY_FILE"
|
|
398
|
-
rm -f "${HISTORY_FILE}.tmp"
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
# Create persistent WDA session (like original script - reuse same session)
|
|
402
|
-
create_persistent_session() {
|
|
403
|
-
local device_name="$1"
|
|
404
|
-
local device_details
|
|
405
|
-
device_details=$(get_device_details "$device_name")
|
|
406
|
-
local host=$(echo "$device_details" | cut -d',' -f1)
|
|
407
|
-
|
|
408
|
-
# Create ONE session per device for the entire interactive session
|
|
409
|
-
# Don't launch any specific app - just create a session to the home screen
|
|
410
|
-
local session=$(curl -s -X POST "http://$host:$WDA_PORT/session" \
|
|
411
|
-
-H 'Content-Type: application/json' \
|
|
412
|
-
-d '{"capabilities":{"alwaysMatch":{}}}' | \
|
|
413
|
-
python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('sessionId','') or d.get('value',{}).get('sessionId',''))" 2>/dev/null)
|
|
414
|
-
|
|
415
|
-
if [ -n "$session" ]; then
|
|
416
|
-
# Use a lock file for atomic writes when running in parallel
|
|
417
|
-
local lock_file="${DEVICE_SESSIONS_FILE}.lock"
|
|
418
|
-
while ! mkdir "$lock_file" 2>/dev/null; do
|
|
419
|
-
sleep 0.01
|
|
420
|
-
done
|
|
421
|
-
echo "$device_name:$session" >> "$DEVICE_SESSIONS_FILE"
|
|
422
|
-
rmdir "$lock_file"
|
|
423
|
-
echo "$session"
|
|
424
|
-
else
|
|
425
|
-
echo ""
|
|
426
|
-
fi
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
# Get existing session for device
|
|
430
|
-
get_device_session() {
|
|
431
|
-
local device_name="$1"
|
|
432
|
-
if [ -f "$DEVICE_SESSIONS_FILE" ]; then
|
|
433
|
-
grep "^$device_name:" "$DEVICE_SESSIONS_FILE" | cut -d':' -f2 | tail -1
|
|
434
|
-
fi
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
# Helper function to find element using multiple strategies (like Appium)
|
|
438
|
-
find_element_by_text() {
|
|
439
|
-
local session="$1"
|
|
440
|
-
local host="$2"
|
|
441
|
-
local text="$3"
|
|
442
|
-
local device_name="$4"
|
|
443
|
-
|
|
444
|
-
local element_id=""
|
|
445
|
-
local found_strategy=""
|
|
446
|
-
|
|
447
|
-
# Strategy 1: Accessibility ID (most reliable for iOS)
|
|
448
|
-
echo "[$device_name] Trying accessibility ID..."
|
|
449
|
-
local elements_result=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/elements" \
|
|
450
|
-
-H "Content-Type: application/json" \
|
|
451
|
-
-d "{\"using\": \"accessibility id\", \"value\": \"$text\"}" 2>/dev/null)
|
|
452
|
-
|
|
453
|
-
if echo "$elements_result" | grep -q '"ELEMENT"'; then
|
|
454
|
-
element_id=$(echo "$elements_result" | python3 -c "
|
|
455
|
-
import sys, json
|
|
456
|
-
try:
|
|
457
|
-
data = json.load(sys.stdin)
|
|
458
|
-
elements = data.get('value', [])
|
|
459
|
-
if elements and len(elements) > 0:
|
|
460
|
-
print(elements[0].get('ELEMENT', ''))
|
|
461
|
-
except:
|
|
462
|
-
pass" 2>/dev/null)
|
|
463
|
-
found_strategy="accessibility id"
|
|
464
|
-
fi
|
|
465
|
-
|
|
466
|
-
# Strategy 2: Name attribute
|
|
467
|
-
if [ -z "$element_id" ]; then
|
|
468
|
-
echo "[$device_name] Trying name attribute..."
|
|
469
|
-
elements_result=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/elements" \
|
|
470
|
-
-H "Content-Type: application/json" \
|
|
471
|
-
-d "{\"using\": \"name\", \"value\": \"$text\"}" 2>/dev/null)
|
|
472
|
-
|
|
473
|
-
if echo "$elements_result" | grep -q '"ELEMENT"'; then
|
|
474
|
-
element_id=$(echo "$elements_result" | python3 -c "
|
|
475
|
-
import sys, json
|
|
476
|
-
try:
|
|
477
|
-
data = json.load(sys.stdin)
|
|
478
|
-
elements = data.get('value', [])
|
|
479
|
-
if elements and len(elements) > 0:
|
|
480
|
-
print(elements[0].get('ELEMENT', ''))
|
|
481
|
-
except:
|
|
482
|
-
pass" 2>/dev/null)
|
|
483
|
-
found_strategy="name"
|
|
484
|
-
fi
|
|
485
|
-
fi
|
|
486
|
-
|
|
487
|
-
# Strategy 3: Predicate string (iOS specific - very powerful)
|
|
488
|
-
if [ -z "$element_id" ]; then
|
|
489
|
-
echo "[$device_name] Trying predicate string..."
|
|
490
|
-
local predicate="name CONTAINS '$text' OR label CONTAINS '$text' OR value CONTAINS '$text'"
|
|
491
|
-
elements_result=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/elements" \
|
|
492
|
-
-H "Content-Type: application/json" \
|
|
493
|
-
-d "{\"using\": \"predicate string\", \"value\": \"$predicate\"}" 2>/dev/null)
|
|
494
|
-
|
|
495
|
-
if echo "$elements_result" | grep -q '"ELEMENT"'; then
|
|
496
|
-
element_id=$(echo "$elements_result" | python3 -c "
|
|
497
|
-
import sys, json
|
|
498
|
-
try:
|
|
499
|
-
data = json.load(sys.stdin)
|
|
500
|
-
elements = data.get('value', [])
|
|
501
|
-
if elements and len(elements) > 0:
|
|
502
|
-
print(elements[0].get('ELEMENT', ''))
|
|
503
|
-
except:
|
|
504
|
-
pass" 2>/dev/null)
|
|
505
|
-
found_strategy="predicate string"
|
|
506
|
-
fi
|
|
507
|
-
fi
|
|
508
|
-
|
|
509
|
-
# Strategy 4: Class chain for buttons (iOS specific)
|
|
510
|
-
if [ -z "$element_id" ]; then
|
|
511
|
-
echo "[$device_name] Trying class chain for buttons..."
|
|
512
|
-
local class_chain="**/XCUIElementTypeButton[\`name CONTAINS '$text'\`]"
|
|
513
|
-
elements_result=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/elements" \
|
|
514
|
-
-H "Content-Type: application/json" \
|
|
515
|
-
-d "{\"using\": \"-ios class chain\", \"value\": \"$class_chain\"}" 2>/dev/null)
|
|
516
|
-
|
|
517
|
-
if echo "$elements_result" | grep -q '"ELEMENT"'; then
|
|
518
|
-
element_id=$(echo "$elements_result" | python3 -c "
|
|
519
|
-
import sys, json
|
|
520
|
-
try:
|
|
521
|
-
data = json.load(sys.stdin)
|
|
522
|
-
elements = data.get('value', [])
|
|
523
|
-
if elements and len(elements) > 0:
|
|
524
|
-
print(elements[0].get('ELEMENT', ''))
|
|
525
|
-
except:
|
|
526
|
-
pass" 2>/dev/null)
|
|
527
|
-
found_strategy="class chain (button)"
|
|
528
|
-
fi
|
|
529
|
-
fi
|
|
530
|
-
|
|
531
|
-
# Strategy 5: Class chain for any element (broader search)
|
|
532
|
-
if [ -z "$element_id" ]; then
|
|
533
|
-
echo "[$device_name] Trying class chain for any element..."
|
|
534
|
-
local class_chain="**/*[\`name CONTAINS '$text'\`]"
|
|
535
|
-
elements_result=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/elements" \
|
|
536
|
-
-H "Content-Type: application/json" \
|
|
537
|
-
-d "{\"using\": \"-ios class chain\", \"value\": \"$class_chain\"}" 2>/dev/null)
|
|
538
|
-
|
|
539
|
-
if echo "$elements_result" | grep -q '"ELEMENT"'; then
|
|
540
|
-
element_id=$(echo "$elements_result" | python3 -c "
|
|
541
|
-
import sys, json
|
|
542
|
-
try:
|
|
543
|
-
data = json.load(sys.stdin)
|
|
544
|
-
elements = data.get('value', [])
|
|
545
|
-
if elements and len(elements) > 0:
|
|
546
|
-
print(elements[0].get('ELEMENT', ''))
|
|
547
|
-
except:
|
|
548
|
-
pass" 2>/dev/null)
|
|
549
|
-
found_strategy="class chain (any)"
|
|
550
|
-
fi
|
|
551
|
-
fi
|
|
552
|
-
|
|
553
|
-
# Output results
|
|
554
|
-
if [ -n "$element_id" ]; then
|
|
555
|
-
echo "$element_id|$found_strategy"
|
|
556
|
-
else
|
|
557
|
-
echo "|"
|
|
558
|
-
fi
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
# Helper function to get element center coordinates with multiple methods
|
|
562
|
-
get_element_center() {
|
|
563
|
-
local session="$1"
|
|
564
|
-
local host="$2"
|
|
565
|
-
local element_id="$3"
|
|
566
|
-
|
|
567
|
-
# Method 1: Try /rect endpoint
|
|
568
|
-
local rect_response=$(curl -s -X GET "http://$host:$WDA_PORT/session/$session/element/$element_id/rect" \
|
|
569
|
-
-H 'Content-Type: application/json' 2>/dev/null)
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if echo "$rect_response" | grep -q '"x"' && echo "$rect_response" | grep -q '"y"'; then
|
|
573
|
-
local x=$(echo "$rect_response" | python3 -c "
|
|
574
|
-
import sys, json
|
|
575
|
-
try:
|
|
576
|
-
data = json.load(sys.stdin)
|
|
577
|
-
rect = data.get('value', {})
|
|
578
|
-
x = rect.get('x', 0)
|
|
579
|
-
width = rect.get('width', 0)
|
|
580
|
-
center_x = int(x + width/2)
|
|
581
|
-
print(center_x)
|
|
582
|
-
except Exception as e:
|
|
583
|
-
print('0')" 2>/dev/null)
|
|
584
|
-
|
|
585
|
-
local y=$(echo "$rect_response" | python3 -c "
|
|
586
|
-
import sys, json
|
|
587
|
-
try:
|
|
588
|
-
data = json.load(sys.stdin)
|
|
589
|
-
rect = data.get('value', {})
|
|
590
|
-
y = rect.get('y', 0)
|
|
591
|
-
height = rect.get('height', 0)
|
|
592
|
-
center_y = int(y + height/2)
|
|
593
|
-
print(center_y)
|
|
594
|
-
except Exception as e:
|
|
595
|
-
print('0')" 2>/dev/null)
|
|
596
|
-
|
|
597
|
-
if [[ "$x" =~ ^[0-9]+$ ]] && [[ "$y" =~ ^[0-9]+$ ]] && [ "$x" -gt 0 ] && [ "$y" -gt 0 ]; then
|
|
598
|
-
echo "$x,$y"
|
|
599
|
-
return
|
|
600
|
-
fi
|
|
601
|
-
fi
|
|
602
|
-
|
|
603
|
-
# Method 2: Try /location endpoint
|
|
604
|
-
local location_response=$(curl -s -X GET "http://$host:$WDA_PORT/session/$session/element/$element_id/location" \
|
|
605
|
-
-H 'Content-Type: application/json' 2>/dev/null)
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
if echo "$location_response" | grep -q '"x"' && echo "$location_response" | grep -q '"y"'; then
|
|
609
|
-
local x=$(echo "$location_response" | python3 -c "
|
|
610
|
-
import sys, json
|
|
611
|
-
try:
|
|
612
|
-
data = json.load(sys.stdin)
|
|
613
|
-
location = data.get('value', {})
|
|
614
|
-
print(int(location.get('x', 0)))
|
|
615
|
-
except:
|
|
616
|
-
print('0')" 2>/dev/null)
|
|
617
|
-
|
|
618
|
-
local y=$(echo "$location_response" | python3 -c "
|
|
619
|
-
import sys, json
|
|
620
|
-
try:
|
|
621
|
-
data = json.load(sys.stdin)
|
|
622
|
-
location = data.get('value', {})
|
|
623
|
-
print(int(location.get('y', 0)))
|
|
624
|
-
except:
|
|
625
|
-
print('0')" 2>/dev/null)
|
|
626
|
-
|
|
627
|
-
if [[ "$x" =~ ^[0-9]+$ ]] && [[ "$y" =~ ^[0-9]+$ ]] && [ "$x" -gt 0 ] && [ "$y" -gt 0 ]; then
|
|
628
|
-
echo "$x,$y"
|
|
629
|
-
return
|
|
630
|
-
fi
|
|
631
|
-
fi
|
|
632
|
-
|
|
633
|
-
# Method 3: Try WebDriverAgent specific endpoint
|
|
634
|
-
local wda_response=$(curl -s -X GET "http://$host:$WDA_PORT/session/$session/wda/element/$element_id/accessibilityContainer" \
|
|
635
|
-
-H 'Content-Type: application/json' 2>/dev/null)
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
# Method 4: Use element attribute to get frame info
|
|
639
|
-
local attr_response=$(curl -s -X GET "http://$host:$WDA_PORT/session/$session/element/$element_id/attribute/frame" \
|
|
640
|
-
-H 'Content-Type: application/json' 2>/dev/null)
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
# If all methods fail, return 0,0
|
|
644
|
-
echo "0,0"
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
cleanup_wda_session() {
|
|
648
|
-
local device_name="$1"
|
|
649
|
-
local preserve_trust="${2:-true}"
|
|
650
|
-
local session=""
|
|
651
|
-
|
|
652
|
-
if [ -f "$DEVICE_SESSIONS_FILE" ]; then
|
|
653
|
-
session=$(grep "^$device_name:" "$DEVICE_SESSIONS_FILE" | cut -d':' -f2 | tail -1)
|
|
654
|
-
fi
|
|
655
|
-
|
|
656
|
-
if [ -n "$session" ]; then
|
|
657
|
-
local device_details
|
|
658
|
-
device_details=$(get_device_details "$device_name")
|
|
659
|
-
local host=$(echo "$device_details" | cut -d',' -f1)
|
|
660
|
-
|
|
661
|
-
curl -s -X DELETE "http://$host:$WDA_PORT/session/$session" >/dev/null 2>&1
|
|
662
|
-
|
|
663
|
-
# Remove session from tracking
|
|
664
|
-
if [ -f "$DEVICE_SESSIONS_FILE" ]; then
|
|
665
|
-
grep -v "^$device_name:" "$DEVICE_SESSIONS_FILE" > "${DEVICE_SESSIONS_FILE}.tmp" 2>/dev/null || true
|
|
666
|
-
mv "${DEVICE_SESSIONS_FILE}.tmp" "$DEVICE_SESSIONS_FILE" 2>/dev/null || true
|
|
667
|
-
fi
|
|
668
|
-
|
|
669
|
-
if [ "$preserve_trust" = "true" ] && [ "${preserve_trust}" != "false" ]; then
|
|
670
|
-
print_color "$GREEN" "✅ Session closed for $device_name (WebDriverAgent preserved)"
|
|
671
|
-
fi
|
|
672
|
-
fi
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
# Execute WDA command directly
|
|
676
|
-
execute_wda_command() {
|
|
677
|
-
local device_name="$1"
|
|
678
|
-
local command="$2"
|
|
679
|
-
local log_prefix="[${device_name}]"
|
|
680
|
-
local device_details
|
|
681
|
-
device_details=$(get_device_details "$device_name")
|
|
682
|
-
local host=$(echo "$device_details" | cut -d',' -f1)
|
|
683
|
-
|
|
684
|
-
# Parse command
|
|
685
|
-
local cmd_type=$(echo "$command" | awk '{print $1}')
|
|
686
|
-
local cmd_args=$(echo "$command" | sed "s/^$cmd_type *//" | xargs)
|
|
687
|
-
|
|
688
|
-
# Show what we parsed for debugging
|
|
689
|
-
echo "${log_prefix} Processing command: '$cmd_type' with args: '$cmd_args'"
|
|
690
|
-
|
|
691
|
-
# Check the actual values with explicit debugging
|
|
692
|
-
|
|
693
|
-
case "$cmd_type" in
|
|
694
|
-
"launch"|"open")
|
|
695
|
-
echo "${log_prefix} CASE: Launch/open matched"
|
|
696
|
-
local bundle_id=$(resolve_app_bundle "$cmd_args")
|
|
697
|
-
echo "${log_prefix} Launching: $cmd_args → $bundle_id"
|
|
698
|
-
|
|
699
|
-
# DON'T clean up existing session - just activate the app
|
|
700
|
-
# This prevents the app from closing
|
|
701
|
-
|
|
702
|
-
# Try to activate app with existing session first
|
|
703
|
-
local session=$(get_device_session "$device_name")
|
|
704
|
-
|
|
705
|
-
if [ -n "$session" ]; then
|
|
706
|
-
# Use existing session to activate app
|
|
707
|
-
local result=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/apps/activate" \
|
|
708
|
-
-H 'Content-Type: application/json' \
|
|
709
|
-
-d "{\"bundleId\":\"$bundle_id\"}" 2>/dev/null)
|
|
710
|
-
|
|
711
|
-
if echo "$result" | grep -q '"value"'; then
|
|
712
|
-
echo "${log_prefix} ✅ App launched (existing session)"
|
|
713
|
-
else
|
|
714
|
-
# If activation fails, try global endpoint
|
|
715
|
-
local result2=$(curl -s -X POST "http://$host:$WDA_PORT/wda/apps/activate" \
|
|
716
|
-
-H 'Content-Type: application/json' \
|
|
717
|
-
-d "{\"bundleId\":\"$bundle_id\"}" 2>/dev/null)
|
|
718
|
-
|
|
719
|
-
if echo "$result2" | grep -q '"value"'; then
|
|
720
|
-
echo "${log_prefix} ✅ App launched (global)"
|
|
721
|
-
else
|
|
722
|
-
echo "${log_prefix} ❌ Launch failed"
|
|
723
|
-
fi
|
|
724
|
-
fi
|
|
725
|
-
else
|
|
726
|
-
# No session exists, create one with the app
|
|
727
|
-
local new_session=$(curl -s -X POST "http://$host:$WDA_PORT/session" \
|
|
728
|
-
-H 'Content-Type: application/json' \
|
|
729
|
-
-d "{\"capabilities\":{\"alwaysMatch\":{\"bundleId\":\"$bundle_id\"}}}" | \
|
|
730
|
-
python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('sessionId','') or d.get('value',{}).get('sessionId',''))" 2>/dev/null)
|
|
731
|
-
|
|
732
|
-
if [ -n "$new_session" ]; then
|
|
733
|
-
# Store new session
|
|
734
|
-
echo "$device_name:$new_session" >> "$DEVICE_SESSIONS_FILE"
|
|
735
|
-
echo "${log_prefix} ✅ App launched (new session)"
|
|
736
|
-
else
|
|
737
|
-
echo "${log_prefix} ❌ Launch failed"
|
|
738
|
-
fi
|
|
739
|
-
fi
|
|
740
|
-
;;
|
|
741
|
-
|
|
742
|
-
"kill"|"close"|"terminate")
|
|
743
|
-
local bundle_id=$(resolve_app_bundle "$cmd_args")
|
|
744
|
-
echo "${log_prefix} Terminating: $cmd_args → $bundle_id"
|
|
745
|
-
|
|
746
|
-
# Method 1: Try WDA terminate endpoint
|
|
747
|
-
local session=$(get_device_session "$device_name")
|
|
748
|
-
local success=false
|
|
749
|
-
|
|
750
|
-
if [ -n "$session" ]; then
|
|
751
|
-
echo "${log_prefix} → Trying WDA terminate endpoint..."
|
|
752
|
-
local response=$(curl -s -X POST "http://$host:$WDA_PORT/wda/apps/terminate" \
|
|
753
|
-
-H 'Content-Type: application/json' \
|
|
754
|
-
-d "{\"bundleId\":\"$bundle_id\"}")
|
|
755
|
-
|
|
756
|
-
# Debug: Show response for troubleshooting
|
|
757
|
-
# echo "${log_prefix} DEBUG: Response: $response"
|
|
758
|
-
|
|
759
|
-
if echo "$response" | grep -q '"value".*true' && ! echo "$response" | grep -q '"error"'; then
|
|
760
|
-
echo "${log_prefix} ✅ App terminated via WDA"
|
|
761
|
-
success=true
|
|
762
|
-
elif echo "$response" | grep -q '"sessionId"' && ! echo "$response" | grep -q '"error"'; then
|
|
763
|
-
echo "${log_prefix} ✅ App terminated via WDA"
|
|
764
|
-
success=true
|
|
765
|
-
else
|
|
766
|
-
echo "${log_prefix} → WDA terminate failed, trying alternatives..."
|
|
767
|
-
fi
|
|
768
|
-
fi
|
|
769
|
-
|
|
770
|
-
# Method 2: Try alternative WDA endpoint if first method failed
|
|
771
|
-
if [ "$success" = false ] && [ -n "$session" ]; then
|
|
772
|
-
echo "${log_prefix} → Trying session-based terminate method..."
|
|
773
|
-
local response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/apps/terminate" \
|
|
774
|
-
-H 'Content-Type: application/json' \
|
|
775
|
-
-d "{\"bundleId\":\"$bundle_id\"}")
|
|
776
|
-
|
|
777
|
-
if echo "$response" | grep -q '"value".*true' || (echo "$response" | grep -q '"sessionId"' && ! echo "$response" | grep -q '"error"'); then
|
|
778
|
-
echo "${log_prefix} ✅ App terminated via session API"
|
|
779
|
-
success=true
|
|
780
|
-
else
|
|
781
|
-
echo "${log_prefix} → Session terminate also failed..."
|
|
782
|
-
fi
|
|
783
|
-
fi
|
|
784
|
-
|
|
785
|
-
# Method 3: Try using xcrun devicectl (iOS 17+)
|
|
786
|
-
if [ "$success" = false ]; then
|
|
787
|
-
echo "${log_prefix} → Trying xcrun devicectl..."
|
|
788
|
-
local device_details
|
|
789
|
-
device_details=$(get_device_details "$device_name")
|
|
790
|
-
local udid=$(echo "$device_details" | cut -d',' -f2)
|
|
791
|
-
|
|
792
|
-
if [ -n "$udid" ] && command -v xcrun >/dev/null 2>&1; then
|
|
793
|
-
# Try process kill first
|
|
794
|
-
local kill_result=$(xcrun devicectl device process kill --device "$udid" "$bundle_id" 2>&1)
|
|
795
|
-
if [ $? -eq 0 ]; then
|
|
796
|
-
echo "${log_prefix} ✅ App terminated via xcrun devicectl (process kill)"
|
|
797
|
-
success=true
|
|
798
|
-
else
|
|
799
|
-
# Try app terminate
|
|
800
|
-
local terminate_result=$(xcrun devicectl device apps terminate --device "$udid" "$bundle_id" 2>&1)
|
|
801
|
-
if [ $? -eq 0 ]; then
|
|
802
|
-
echo "${log_prefix} ✅ App terminated via xcrun devicectl (app terminate)"
|
|
803
|
-
success=true
|
|
804
|
-
fi
|
|
805
|
-
fi
|
|
806
|
-
fi
|
|
807
|
-
fi
|
|
808
|
-
|
|
809
|
-
# Method 4: Try launching home screen to background the app
|
|
810
|
-
if [ "$success" = false ]; then
|
|
811
|
-
echo "${log_prefix} → Backgrounding app (launching home screen)..."
|
|
812
|
-
local response=$(curl -s -X POST "http://$host:$WDA_PORT/wda/homescreen" \
|
|
813
|
-
-H 'Content-Type: application/json' -d '{}')
|
|
814
|
-
|
|
815
|
-
if ! echo "$response" | grep -q '"error"'; then
|
|
816
|
-
echo "${log_prefix} ✅ App backgrounded (sent to home screen)"
|
|
817
|
-
success=true
|
|
818
|
-
fi
|
|
819
|
-
fi
|
|
820
|
-
|
|
821
|
-
if [ "$success" = false ]; then
|
|
822
|
-
echo "${log_prefix} ❌ All terminate methods failed"
|
|
823
|
-
echo "${log_prefix} 💡 Try manually closing the app or use 'home' command"
|
|
824
|
-
fi
|
|
825
|
-
;;
|
|
826
|
-
|
|
827
|
-
"home")
|
|
828
|
-
echo "${log_prefix} Going to home screen"
|
|
829
|
-
# Use the same endpoint as original script (no session ID needed)
|
|
830
|
-
local response=$(curl -s -X POST "http://$host:$WDA_PORT/wda/homescreen" \
|
|
831
|
-
-H 'Content-Type: application/json' -d '{}')
|
|
832
|
-
|
|
833
|
-
if ! echo "$response" | grep -q '"error"'; then
|
|
834
|
-
echo "${log_prefix} ✅ Went to home"
|
|
835
|
-
else
|
|
836
|
-
echo "${log_prefix} ❌ Home failed"
|
|
837
|
-
fi
|
|
838
|
-
;;
|
|
839
|
-
|
|
840
|
-
"url")
|
|
841
|
-
local url="$cmd_args"
|
|
842
|
-
if ! echo "$url" | grep -qE '^[a-z]+://'; then
|
|
843
|
-
url="https://$url"
|
|
844
|
-
fi
|
|
845
|
-
echo "${log_prefix} Opening URL: $url"
|
|
846
|
-
|
|
847
|
-
# Use existing session - first activate Safari, then navigate
|
|
848
|
-
local session=$(get_device_session "$device_name")
|
|
849
|
-
if [ -n "$session" ]; then
|
|
850
|
-
# Activate Safari first
|
|
851
|
-
curl -s -X POST "http://$host:$WDA_PORT/wda/apps/activate" \
|
|
852
|
-
-H 'Content-Type: application/json' \
|
|
853
|
-
-d '{"bundleId":"com.apple.mobilesafari"}' >/dev/null 2>&1
|
|
854
|
-
sleep 1
|
|
855
|
-
# Navigate to URL
|
|
856
|
-
curl -s -X POST "http://$host:$WDA_PORT/session/$session/url" \
|
|
857
|
-
-H 'Content-Type: application/json' \
|
|
858
|
-
-d "{\"url\":\"$url\"}" >/dev/null 2>&1
|
|
859
|
-
echo "${log_prefix} ✅ URL opened in Safari"
|
|
860
|
-
else
|
|
861
|
-
echo "${log_prefix} ❌ URL open failed"
|
|
862
|
-
fi
|
|
863
|
-
;;
|
|
864
|
-
|
|
865
|
-
"screenshot")
|
|
866
|
-
echo "${log_prefix} Taking screenshot"
|
|
867
|
-
local session=$(get_device_session "$device_name")
|
|
868
|
-
if [ -n "$session" ]; then
|
|
869
|
-
# Save to webapp screenshots directory
|
|
870
|
-
local screenshots_dir="$SCRIPT_DIR/webapp/backend/screenshots"
|
|
871
|
-
mkdir -p "$screenshots_dir"
|
|
872
|
-
local out_file="${screenshots_dir}/screenshot_ios_${device_name}_$(date +%Y%m%d_%H%M%S).png"
|
|
873
|
-
local response=$(curl -s -X GET "http://$host:$WDA_PORT/screenshot")
|
|
874
|
-
|
|
875
|
-
if echo "$response" | python3 -c "
|
|
876
|
-
import sys,base64,json
|
|
877
|
-
try:
|
|
878
|
-
j=json.load(sys.stdin)
|
|
879
|
-
img_b64=j.get('value','')
|
|
880
|
-
if img_b64:
|
|
881
|
-
with open('$out_file','wb') as f:
|
|
882
|
-
f.write(base64.b64decode(img_b64))
|
|
883
|
-
print('✅ Screenshot saved: $out_file')
|
|
884
|
-
else:
|
|
885
|
-
print('❌ Screenshot failed')
|
|
886
|
-
except:
|
|
887
|
-
print('❌ Screenshot error')
|
|
888
|
-
" 2>/dev/null; then
|
|
889
|
-
echo "${log_prefix} Screenshot completed"
|
|
890
|
-
fi
|
|
891
|
-
else
|
|
892
|
-
echo "${log_prefix} ❌ Screenshot failed"
|
|
893
|
-
fi
|
|
894
|
-
;;
|
|
895
|
-
|
|
896
|
-
"lock")
|
|
897
|
-
echo "${log_prefix} Locking device"
|
|
898
|
-
local response=$(curl -s -X POST "http://$host:$WDA_PORT/wda/lock" \
|
|
899
|
-
-H 'Content-Type: application/json' -d '{}')
|
|
900
|
-
|
|
901
|
-
if ! echo "$response" | grep -q '"error"'; then
|
|
902
|
-
echo "${log_prefix} ✅ Device locked"
|
|
903
|
-
else
|
|
904
|
-
echo "${log_prefix} ❌ Lock failed"
|
|
905
|
-
fi
|
|
906
|
-
;;
|
|
907
|
-
|
|
908
|
-
"unlock")
|
|
909
|
-
echo "${log_prefix} Unlocking device"
|
|
910
|
-
local response=$(curl -s -X POST "http://$host:$WDA_PORT/wda/unlock" \
|
|
911
|
-
-H 'Content-Type: application/json' -d '{}')
|
|
912
|
-
|
|
913
|
-
if ! echo "$response" | grep -q '"error"'; then
|
|
914
|
-
echo "${log_prefix} ✅ Device unlocked"
|
|
915
|
-
else
|
|
916
|
-
echo "${log_prefix} ❌ Unlock failed"
|
|
917
|
-
fi
|
|
918
|
-
;;
|
|
919
|
-
|
|
920
|
-
"back"|"goback")
|
|
921
|
-
echo "${log_prefix} Going back"
|
|
922
|
-
# iOS doesn't have a universal back button like Android
|
|
923
|
-
# Use swipe from left edge gesture which is the iOS equivalent
|
|
924
|
-
local session=$(get_device_session "$device_name")
|
|
925
|
-
if [ -n "$session" ]; then
|
|
926
|
-
# Swipe from left edge (back gesture)
|
|
927
|
-
local swipe_payload='{
|
|
928
|
-
"actions": [
|
|
929
|
-
{
|
|
930
|
-
"type": "pointer",
|
|
931
|
-
"id": "finger1",
|
|
932
|
-
"parameters": {"pointerType": "touch"},
|
|
933
|
-
"actions": [
|
|
934
|
-
{"type": "pointerMove", "duration": 0, "x": 10, "y": 400},
|
|
935
|
-
{"type": "pointerDown", "button": 0},
|
|
936
|
-
{"type": "pointerMove", "duration": 300, "x": 200, "y": 400},
|
|
937
|
-
{"type": "pointerUp", "button": 0}
|
|
938
|
-
]
|
|
939
|
-
}
|
|
940
|
-
]
|
|
941
|
-
}'
|
|
942
|
-
|
|
943
|
-
curl -s -X POST "http://$host:$WDA_PORT/session/$session/actions" \
|
|
944
|
-
-H 'Content-Type: application/json' \
|
|
945
|
-
-d "$swipe_payload" >/dev/null 2>&1
|
|
946
|
-
|
|
947
|
-
echo "${log_prefix} ✅ Back gesture performed"
|
|
948
|
-
else
|
|
949
|
-
echo "${log_prefix} ❌ Back failed"
|
|
950
|
-
fi
|
|
951
|
-
;;
|
|
952
|
-
|
|
953
|
-
"install")
|
|
954
|
-
local app_path="$cmd_args"
|
|
955
|
-
echo "${log_prefix} Installing app: $app_path"
|
|
956
|
-
|
|
957
|
-
# Check if file exists
|
|
958
|
-
if [ ! -f "$app_path" ]; then
|
|
959
|
-
echo "${log_prefix} ❌ App file not found: $app_path"
|
|
960
|
-
return 1
|
|
961
|
-
fi
|
|
962
|
-
|
|
963
|
-
# Check if it's an IPA file
|
|
964
|
-
if [[ ! "$app_path" =~ \.ipa$ ]]; then
|
|
965
|
-
echo "${log_prefix} ❌ File must be a .ipa file: $app_path"
|
|
966
|
-
return 1
|
|
967
|
-
fi
|
|
968
|
-
|
|
969
|
-
# Get device details for wireless installation
|
|
970
|
-
local device_details
|
|
971
|
-
device_details=$(get_device_details "$device_name")
|
|
972
|
-
local udid=$(echo "$device_details" | cut -d',' -f2)
|
|
973
|
-
|
|
974
|
-
if [ -n "$udid" ]; then
|
|
975
|
-
echo "${log_prefix} 📦 Installing app via wireless-compatible tools..."
|
|
976
|
-
|
|
977
|
-
# For wireless devices, prioritize xcrun devicectl
|
|
978
|
-
if command -v xcrun >/dev/null 2>&1; then
|
|
979
|
-
# Use xcrun devicectl (works with wireless devices on iOS 17+)
|
|
980
|
-
echo "${log_prefix} 🔄 Using xcrun devicectl for wireless installation..."
|
|
981
|
-
local install_result=$(xcrun devicectl device install app --device "$udid" "$app_path" 2>&1)
|
|
982
|
-
if [ $? -eq 0 ]; then
|
|
983
|
-
echo "${log_prefix} ✅ App installed successfully via xcrun devicectl"
|
|
984
|
-
else
|
|
985
|
-
echo "${log_prefix} ❌ Installation failed via xcrun devicectl: $install_result"
|
|
986
|
-
echo "${log_prefix} 💡 For wireless devices, ensure:"
|
|
987
|
-
echo "${log_prefix} • Device is paired and trusted in Xcode"
|
|
988
|
-
echo "${log_prefix} • iOS 17+ for best wireless support"
|
|
989
|
-
fi
|
|
990
|
-
elif command -v ios-deploy >/dev/null 2>&1; then
|
|
991
|
-
# Try ios-deploy with wireless flag
|
|
992
|
-
echo "${log_prefix} 🔄 Trying ios-deploy with wireless support..."
|
|
993
|
-
local install_result=$(ios-deploy --id "$udid" --bundle "$app_path" 2>&1)
|
|
994
|
-
if [ $? -eq 0 ]; then
|
|
995
|
-
echo "${log_prefix} ✅ App installed successfully via ios-deploy"
|
|
996
|
-
else
|
|
997
|
-
echo "${log_prefix} ❌ Installation failed via ios-deploy: $install_result"
|
|
998
|
-
fi
|
|
999
|
-
elif command -v ideviceinstaller >/dev/null 2>&1; then
|
|
1000
|
-
# ideviceinstaller may not work with wireless, but try anyway
|
|
1001
|
-
echo "${log_prefix} 🔄 Trying ideviceinstaller (may require USB)..."
|
|
1002
|
-
local install_result=$(ideviceinstaller -u "$udid" install "$app_path" 2>&1)
|
|
1003
|
-
if [ $? -eq 0 ]; then
|
|
1004
|
-
echo "${log_prefix} ✅ App installed successfully via ideviceinstaller"
|
|
1005
|
-
else
|
|
1006
|
-
echo "${log_prefix} ❌ Installation failed via ideviceinstaller (wireless not supported): $install_result"
|
|
1007
|
-
fi
|
|
1008
|
-
else
|
|
1009
|
-
echo "${log_prefix} ❌ No wireless-compatible installation tools found"
|
|
1010
|
-
echo "${log_prefix} 💡 For wireless installation:"
|
|
1011
|
-
echo "${log_prefix} • Install via Xcode → Window → Devices and Simulators"
|
|
1012
|
-
echo "${log_prefix} • Or use: xcrun devicectl device install app --device $udid $app_path"
|
|
1013
|
-
fi
|
|
1014
|
-
else
|
|
1015
|
-
echo "${log_prefix} ❌ Device UDID not available"
|
|
1016
|
-
fi
|
|
1017
|
-
;;
|
|
1018
|
-
|
|
1019
|
-
"uninstall")
|
|
1020
|
-
local bundle_id="$cmd_args"
|
|
1021
|
-
echo "${log_prefix} Uninstalling app: $bundle_id"
|
|
1022
|
-
|
|
1023
|
-
# Get device UDID for wireless uninstallation
|
|
1024
|
-
local device_details
|
|
1025
|
-
device_details=$(get_device_details "$device_name")
|
|
1026
|
-
local udid=$(echo "$device_details" | cut -d',' -f2)
|
|
1027
|
-
|
|
1028
|
-
if [ -n "$udid" ]; then
|
|
1029
|
-
echo "${log_prefix} 🗑️ Uninstalling app via wireless-compatible tools..."
|
|
1030
|
-
|
|
1031
|
-
# For wireless devices, prioritize xcrun devicectl
|
|
1032
|
-
if command -v xcrun >/dev/null 2>&1; then
|
|
1033
|
-
# Use xcrun devicectl (works with wireless devices on iOS 17+)
|
|
1034
|
-
echo "${log_prefix} 🔄 Using xcrun devicectl for wireless uninstallation..."
|
|
1035
|
-
local uninstall_result=$(xcrun devicectl device uninstall app --device "$udid" "$bundle_id" 2>&1)
|
|
1036
|
-
if [ $? -eq 0 ]; then
|
|
1037
|
-
echo "${log_prefix} ✅ App uninstalled successfully via xcrun devicectl"
|
|
1038
|
-
else
|
|
1039
|
-
echo "${log_prefix} ❌ Uninstallation failed via xcrun devicectl: $uninstall_result"
|
|
1040
|
-
echo "${log_prefix} 💡 For wireless devices, ensure:"
|
|
1041
|
-
echo "${log_prefix} • Device is paired and trusted in Xcode"
|
|
1042
|
-
echo "${log_prefix} • App bundle ID is correct: $bundle_id"
|
|
1043
|
-
fi
|
|
1044
|
-
elif command -v ideviceinstaller >/dev/null 2>&1; then
|
|
1045
|
-
# ideviceinstaller may not work with wireless, but try anyway
|
|
1046
|
-
echo "${log_prefix} 🔄 Trying ideviceinstaller (may require USB)..."
|
|
1047
|
-
local uninstall_result=$(ideviceinstaller -u "$udid" uninstall "$bundle_id" 2>&1)
|
|
1048
|
-
if [ $? -eq 0 ]; then
|
|
1049
|
-
echo "${log_prefix} ✅ App uninstalled successfully via ideviceinstaller"
|
|
1050
|
-
else
|
|
1051
|
-
echo "${log_prefix} ❌ Uninstallation failed via ideviceinstaller (wireless not supported): $uninstall_result"
|
|
1052
|
-
fi
|
|
1053
|
-
else
|
|
1054
|
-
echo "${log_prefix} ❌ No wireless-compatible uninstallation tools found"
|
|
1055
|
-
echo "${log_prefix} 💡 For wireless uninstallation:"
|
|
1056
|
-
echo "${log_prefix} • Uninstall manually on device (long-press app icon)"
|
|
1057
|
-
echo "${log_prefix} • Or use Xcode → Window → Devices and Simulators"
|
|
1058
|
-
echo "${log_prefix} • Or use: xcrun devicectl device uninstall app --device $udid $bundle_id"
|
|
1059
|
-
fi
|
|
1060
|
-
else
|
|
1061
|
-
echo "${log_prefix} ❌ Device UDID not available"
|
|
1062
|
-
fi
|
|
1063
|
-
;;
|
|
1064
|
-
|
|
1065
|
-
"listapps"|"apps")
|
|
1066
|
-
local search_term="$cmd_args"
|
|
1067
|
-
if [ -n "$search_term" ]; then
|
|
1068
|
-
echo "${log_prefix} 🔍 Searching installed apps for: '$search_term'"
|
|
1069
|
-
echo "${log_prefix} 💡 Searching by app name, bundle ID, and display name..."
|
|
1070
|
-
else
|
|
1071
|
-
echo "${log_prefix} Listing all installed apps..."
|
|
1072
|
-
fi
|
|
1073
|
-
|
|
1074
|
-
# For wireless devices, use WebDriverAgent or xcrun devicectl
|
|
1075
|
-
local device_details
|
|
1076
|
-
device_details=$(get_device_details "$device_name")
|
|
1077
|
-
local host=$(echo "$device_details" | cut -d',' -f1)
|
|
1078
|
-
local udid=$(echo "$device_details" | cut -d',' -f2)
|
|
1079
|
-
|
|
1080
|
-
echo "${log_prefix} 📱 Using wireless device tools for app listing..."
|
|
1081
|
-
|
|
1082
|
-
# Method 1: Try xcrun devicectl for wireless devices (iOS 17+)
|
|
1083
|
-
if command -v xcrun >/dev/null 2>&1 && [ -n "$udid" ]; then
|
|
1084
|
-
echo "${log_prefix} 🔄 Trying xcrun devicectl (wireless-compatible)..."
|
|
1085
|
-
|
|
1086
|
-
if [ -n "$search_term" ]; then
|
|
1087
|
-
# First try exact bundle ID match
|
|
1088
|
-
local list_result=$(xcrun devicectl device info apps --device "$udid" --bundle-id "$search_term" 2>&1)
|
|
1089
|
-
local found_exact=false
|
|
1090
|
-
if [ $? -eq 0 ] && [ -n "$list_result" ] && ! echo "$list_result" | grep -q "No matching apps found"; then
|
|
1091
|
-
echo "${log_prefix} ✅ Found exact bundle ID match: $search_term"
|
|
1092
|
-
echo "$list_result" | while read line; do
|
|
1093
|
-
echo "${log_prefix} $line"
|
|
1094
|
-
done
|
|
1095
|
-
found_exact=true
|
|
1096
|
-
fi
|
|
1097
|
-
|
|
1098
|
-
# Always also search by app name/partial match for better user experience
|
|
1099
|
-
local list_result=$(xcrun devicectl device info apps --device "$udid" --include-all-apps 2>&1)
|
|
1100
|
-
if [ $? -eq 0 ]; then
|
|
1101
|
-
# Enhanced search: look for matches in app name, display name, and bundle ID
|
|
1102
|
-
local filtered_apps=$(echo "$list_result" | grep -i "$search_term" 2>/dev/null)
|
|
1103
|
-
if [ -n "$filtered_apps" ]; then
|
|
1104
|
-
if [ "$found_exact" = false ]; then
|
|
1105
|
-
echo "${log_prefix} ✅ Found matching apps:"
|
|
1106
|
-
else
|
|
1107
|
-
echo "${log_prefix} ✅ Additional matches found:"
|
|
1108
|
-
fi
|
|
1109
|
-
echo "$filtered_apps" | while read line; do
|
|
1110
|
-
echo "${log_prefix} $line"
|
|
1111
|
-
done
|
|
1112
|
-
return
|
|
1113
|
-
elif [ "$found_exact" = false ]; then
|
|
1114
|
-
echo "${log_prefix} ❌ No apps found matching: '$search_term'"
|
|
1115
|
-
fi
|
|
1116
|
-
fi
|
|
1117
|
-
else
|
|
1118
|
-
# List all apps
|
|
1119
|
-
local list_result=$(xcrun devicectl device info apps --device "$udid" --include-all-apps 2>&1)
|
|
1120
|
-
if [ $? -eq 0 ]; then
|
|
1121
|
-
echo "${log_prefix} ✅ App list retrieved via xcrun devicectl"
|
|
1122
|
-
echo "${log_prefix} ✅ Installed apps:"
|
|
1123
|
-
echo "$list_result" | head -20 | while read line; do
|
|
1124
|
-
echo "${log_prefix} $line"
|
|
1125
|
-
done
|
|
1126
|
-
local app_count=$(echo "$list_result" | wc -l)
|
|
1127
|
-
if [ "$app_count" -gt 20 ]; then
|
|
1128
|
-
echo "${log_prefix} ... and $((app_count - 20)) more apps"
|
|
1129
|
-
fi
|
|
1130
|
-
return
|
|
1131
|
-
else
|
|
1132
|
-
echo "${log_prefix} ❌ xcrun devicectl failed: $list_result"
|
|
1133
|
-
fi
|
|
1134
|
-
fi
|
|
1135
|
-
fi
|
|
1136
|
-
|
|
1137
|
-
# Method 2: Use WebDriverAgent to get basic app info
|
|
1138
|
-
local session=$(get_device_session "$device_name")
|
|
1139
|
-
if [ -n "$session" ] && [ -n "$host" ]; then
|
|
1140
|
-
echo "${log_prefix} 🔄 Trying WebDriverAgent for basic app detection..."
|
|
1141
|
-
|
|
1142
|
-
# Test a few common apps to demonstrate the approach
|
|
1143
|
-
local test_apps=(
|
|
1144
|
-
"com.apple.mobilesafari"
|
|
1145
|
-
"com.apple.mobilemail"
|
|
1146
|
-
"com.apple.MobileSMS"
|
|
1147
|
-
"com.apple.camera"
|
|
1148
|
-
"com.apple.mobilenotes"
|
|
1149
|
-
"com.apple.Preferences"
|
|
1150
|
-
"com.mobileiron.enterprise.anyware.ios"
|
|
1151
|
-
"com.facebook.Facebook"
|
|
1152
|
-
"com.google.chrome.ios"
|
|
1153
|
-
"com.microsoft.Office.Outlook"
|
|
1154
|
-
)
|
|
1155
|
-
|
|
1156
|
-
echo "${log_prefix} 📱 Checking common/enterprise apps via WebDriverAgent..."
|
|
1157
|
-
local found_apps=()
|
|
1158
|
-
|
|
1159
|
-
for bundle_id in "${test_apps[@]}"; do
|
|
1160
|
-
local app_state_response=$(curl -s -X GET "http://$host:$WDA_PORT/wda/apps/state" \
|
|
1161
|
-
-H 'Content-Type: application/json' \
|
|
1162
|
-
-d "{\"bundleId\":\"$bundle_id\"}" 2>/dev/null)
|
|
1163
|
-
|
|
1164
|
-
if echo "$app_state_response" | grep -q '"value"' && ! echo "$app_state_response" | grep -q '"error"'; then
|
|
1165
|
-
local state_value=$(echo "$app_state_response" | python3 -c "
|
|
1166
|
-
import sys, json
|
|
1167
|
-
try:
|
|
1168
|
-
data = json.load(sys.stdin)
|
|
1169
|
-
state = data.get('value', 0)
|
|
1170
|
-
if state > 0: # App is installed if state > 0
|
|
1171
|
-
print('installed')
|
|
1172
|
-
else:
|
|
1173
|
-
print('not_installed')
|
|
1174
|
-
except:
|
|
1175
|
-
print('error')" 2>/dev/null)
|
|
1176
|
-
|
|
1177
|
-
if [ "$state_value" = "installed" ]; then
|
|
1178
|
-
found_apps+=("$bundle_id")
|
|
1179
|
-
fi
|
|
1180
|
-
fi
|
|
1181
|
-
done
|
|
1182
|
-
|
|
1183
|
-
if [ ${#found_apps[@]} -gt 0 ]; then
|
|
1184
|
-
echo "${log_prefix} ✅ Found installed apps via WebDriverAgent:"
|
|
1185
|
-
local matches=0
|
|
1186
|
-
for app in "${found_apps[@]}"; do
|
|
1187
|
-
if [ -z "$search_term" ] || echo "$app" | grep -qi "$search_term"; then
|
|
1188
|
-
echo "${log_prefix} $app - installed"
|
|
1189
|
-
((matches++))
|
|
1190
|
-
fi
|
|
1191
|
-
done
|
|
1192
|
-
|
|
1193
|
-
if [ -n "$search_term" ] && [ $matches -eq 0 ]; then
|
|
1194
|
-
echo "${log_prefix} ❌ No apps found matching: '$search_term'"
|
|
1195
|
-
elif [ -n "$search_term" ]; then
|
|
1196
|
-
echo "${log_prefix} 🎯 Found $matches app(s) matching: '$search_term'"
|
|
1197
|
-
fi
|
|
1198
|
-
else
|
|
1199
|
-
echo "${log_prefix} ❌ No apps detected via WebDriverAgent"
|
|
1200
|
-
fi
|
|
1201
|
-
else
|
|
1202
|
-
echo "${log_prefix} ❌ No WebDriverAgent session available"
|
|
1203
|
-
fi
|
|
1204
|
-
|
|
1205
|
-
# Method 3: Suggest manual verification
|
|
1206
|
-
echo "${log_prefix} 💡 For complete app listing on wireless devices:"
|
|
1207
|
-
echo "${log_prefix} • Use Xcode → Window → Devices and Simulators"
|
|
1208
|
-
echo "${log_prefix} • Or check specific apps with: appinfo <bundle_id>"
|
|
1209
|
-
;;
|
|
1210
|
-
|
|
1211
|
-
"debug"|"test")
|
|
1212
|
-
echo "${log_prefix} 🔍 Running debug tests..."
|
|
1213
|
-
|
|
1214
|
-
# Get device UDID
|
|
1215
|
-
local device_details
|
|
1216
|
-
device_details=$(get_device_details "$device_name")
|
|
1217
|
-
local udid=$(echo "$device_details" | cut -d',' -f2)
|
|
1218
|
-
|
|
1219
|
-
echo "${log_prefix} Device: $device_name"
|
|
1220
|
-
echo "${log_prefix} UDID: $udid"
|
|
1221
|
-
|
|
1222
|
-
if [ -n "$udid" ]; then
|
|
1223
|
-
echo "${log_prefix} 🧪 Testing idevice tools..."
|
|
1224
|
-
|
|
1225
|
-
# Test 1: Check if device is detected
|
|
1226
|
-
echo "${log_prefix} Test 1: Device detection"
|
|
1227
|
-
if command -v idevice_id >/dev/null 2>&1; then
|
|
1228
|
-
local detected_devices=$(idevice_id -l 2>/dev/null)
|
|
1229
|
-
if echo "$detected_devices" | grep -q "$udid"; then
|
|
1230
|
-
echo "${log_prefix} ✅ Device detected by idevice_id"
|
|
1231
|
-
else
|
|
1232
|
-
echo "${log_prefix} ❌ Device NOT detected by idevice_id"
|
|
1233
|
-
echo "${log_prefix} Available devices: $detected_devices"
|
|
1234
|
-
fi
|
|
1235
|
-
else
|
|
1236
|
-
echo "${log_prefix} ❌ idevice_id not found"
|
|
1237
|
-
fi
|
|
1238
|
-
|
|
1239
|
-
# Test 2: Raw ideviceinstaller command
|
|
1240
|
-
echo "${log_prefix} Test 2: Raw ideviceinstaller"
|
|
1241
|
-
local raw_command="ideviceinstaller -u '$udid' list --user"
|
|
1242
|
-
echo "${log_prefix} Command: $raw_command"
|
|
1243
|
-
local raw_result=$(eval $raw_command 2>&1)
|
|
1244
|
-
local raw_exit=$?
|
|
1245
|
-
echo "${log_prefix} Exit code: $raw_exit"
|
|
1246
|
-
if [ $raw_exit -eq 0 ]; then
|
|
1247
|
-
local app_count=$(echo "$raw_result" | wc -l)
|
|
1248
|
-
echo "${log_prefix} ✅ Success! Found $app_count apps"
|
|
1249
|
-
echo "${log_prefix} Sample apps:"
|
|
1250
|
-
echo "$raw_result" | head -3 | while read line; do
|
|
1251
|
-
echo "${log_prefix} $line"
|
|
1252
|
-
done
|
|
1253
|
-
else
|
|
1254
|
-
echo "${log_prefix} ❌ Failed: $raw_result"
|
|
1255
|
-
fi
|
|
1256
|
-
|
|
1257
|
-
# Test 3: Your specific app search
|
|
1258
|
-
echo "${log_prefix} Test 3: MobileIron app search"
|
|
1259
|
-
local mobileiron_search=$(eval $raw_command 2>/dev/null | grep -i mobileiron)
|
|
1260
|
-
if [ -n "$mobileiron_search" ]; then
|
|
1261
|
-
echo "${log_prefix} ✅ Found MobileIron apps:"
|
|
1262
|
-
echo "$mobileiron_search" | while read line; do
|
|
1263
|
-
echo "${log_prefix} $line"
|
|
1264
|
-
done
|
|
1265
|
-
else
|
|
1266
|
-
echo "${log_prefix} ❌ No MobileIron apps found"
|
|
1267
|
-
fi
|
|
1268
|
-
|
|
1269
|
-
else
|
|
1270
|
-
echo "${log_prefix} ❌ No UDID available for testing"
|
|
1271
|
-
fi
|
|
1272
|
-
;;
|
|
1273
|
-
|
|
1274
|
-
"appinfo"|"version")
|
|
1275
|
-
local bundle_id="$cmd_args"
|
|
1276
|
-
echo "${log_prefix} Getting app info: $bundle_id"
|
|
1277
|
-
|
|
1278
|
-
# Get device UDID for app info
|
|
1279
|
-
local device_details
|
|
1280
|
-
device_details=$(get_device_details "$device_name")
|
|
1281
|
-
local udid=$(echo "$device_details" | cut -d',' -f2)
|
|
1282
|
-
|
|
1283
|
-
if [ -n "$udid" ]; then
|
|
1284
|
-
echo "${log_prefix} 📱 Checking app info via iOS tools..."
|
|
1285
|
-
|
|
1286
|
-
# Try different methods to get app info
|
|
1287
|
-
if command -v ideviceinstaller >/dev/null 2>&1; then
|
|
1288
|
-
# Method 1: Search by exact bundle ID
|
|
1289
|
-
echo "${log_prefix} 🔍 Searching for exact bundle ID: $bundle_id"
|
|
1290
|
-
local app_info=$(ideviceinstaller -u "$udid" list --bundle-identifier "$bundle_id" 2>/dev/null)
|
|
1291
|
-
|
|
1292
|
-
if [ -n "$app_info" ]; then
|
|
1293
|
-
echo "${log_prefix} ✅ App found (exact match):"
|
|
1294
|
-
echo "${log_prefix} $app_info"
|
|
1295
|
-
else
|
|
1296
|
-
# Method 2: List all apps and grep for partial matches
|
|
1297
|
-
echo "${log_prefix} 🔍 Searching all installed apps for pattern..."
|
|
1298
|
-
local all_apps=$(ideviceinstaller -u "$udid" list --all 2>/dev/null)
|
|
1299
|
-
local found_apps=$(echo "$all_apps" | grep -i "$bundle_id" 2>/dev/null)
|
|
1300
|
-
|
|
1301
|
-
if [ -n "$found_apps" ]; then
|
|
1302
|
-
echo "${log_prefix} ✅ Found matching apps:"
|
|
1303
|
-
echo "$found_apps" | while read line; do
|
|
1304
|
-
echo "${log_prefix} $line"
|
|
1305
|
-
done
|
|
1306
|
-
else
|
|
1307
|
-
# Method 3: Search by partial bundle ID (useful for enterprise apps)
|
|
1308
|
-
local partial_search=$(echo "$bundle_id" | sed 's/.*\.//') # Get last part after dot
|
|
1309
|
-
local partial_apps=$(echo "$all_apps" | grep -i "$partial_search" 2>/dev/null)
|
|
1310
|
-
|
|
1311
|
-
if [ -n "$partial_apps" ]; then
|
|
1312
|
-
echo "${log_prefix} ✅ Found apps with similar names:"
|
|
1313
|
-
echo "$partial_apps" | while read line; do
|
|
1314
|
-
echo "${log_prefix} $line"
|
|
1315
|
-
done
|
|
1316
|
-
else
|
|
1317
|
-
echo "${log_prefix} ❌ App not found: $bundle_id"
|
|
1318
|
-
fi
|
|
1319
|
-
fi
|
|
1320
|
-
fi
|
|
1321
|
-
elif command -v xcrun >/dev/null 2>&1; then
|
|
1322
|
-
# Use xcrun devicectl to get app info (iOS 17+)
|
|
1323
|
-
echo "${log_prefix} 🔄 Checking via xcrun devicectl..."
|
|
1324
|
-
local app_info=$(xcrun devicectl device info apps --device "$udid" --bundle-id "$bundle_id" 2>&1)
|
|
1325
|
-
|
|
1326
|
-
if [ $? -eq 0 ] && [ -n "$app_info" ] && ! echo "$app_info" | grep -q "No matching apps found"; then
|
|
1327
|
-
echo "${log_prefix} ✅ App info:"
|
|
1328
|
-
echo "$app_info" | while read line; do
|
|
1329
|
-
echo "${log_prefix} $line"
|
|
1330
|
-
done
|
|
1331
|
-
else
|
|
1332
|
-
echo "${log_prefix} ❌ App not found or info unavailable: $bundle_id"
|
|
1333
|
-
echo "${log_prefix} Response: $app_info"
|
|
1334
|
-
fi
|
|
1335
|
-
else
|
|
1336
|
-
echo "${log_prefix} ❌ No iOS app info tools found"
|
|
1337
|
-
echo "${log_prefix} Please install ideviceinstaller or use Xcode"
|
|
1338
|
-
fi
|
|
1339
|
-
|
|
1340
|
-
# Alternative: Try to get app info through WebDriverAgent if available
|
|
1341
|
-
local session=$(get_device_session "$device_name")
|
|
1342
|
-
if [ -n "$session" ]; then
|
|
1343
|
-
echo "${log_prefix} 🔍 Checking if app is installed via WDA..."
|
|
1344
|
-
local wda_response=$(curl -s -X GET "http://$host:$WDA_PORT/wda/apps/state" \
|
|
1345
|
-
-H 'Content-Type: application/json' \
|
|
1346
|
-
-d "{\"bundleId\":\"$bundle_id\"}" 2>/dev/null)
|
|
1347
|
-
|
|
1348
|
-
if echo "$wda_response" | grep -q '"value"' && ! echo "$wda_response" | grep -q '"error"'; then
|
|
1349
|
-
local app_state=$(echo "$wda_response" | python3 -c "
|
|
1350
|
-
import sys, json
|
|
1351
|
-
try:
|
|
1352
|
-
data = json.load(sys.stdin)
|
|
1353
|
-
state = data.get('value', 0)
|
|
1354
|
-
states = {0: 'Not installed', 1: 'Not running', 2: 'Running in background', 3: 'Running in foreground', 4: 'Running but suspended'}
|
|
1355
|
-
print(f'App State: {states.get(state, f\"Unknown ({state})\")}')" 2>/dev/null)
|
|
1356
|
-
|
|
1357
|
-
if [ -n "$app_state" ]; then
|
|
1358
|
-
echo "${log_prefix} ✅ $app_state"
|
|
1359
|
-
fi
|
|
1360
|
-
fi
|
|
1361
|
-
fi
|
|
1362
|
-
else
|
|
1363
|
-
echo "${log_prefix} ❌ Device UDID not available"
|
|
1364
|
-
fi
|
|
1365
|
-
;;
|
|
1366
|
-
|
|
1367
|
-
"darkmode"|"lightmode"|"theme"|"appearance")
|
|
1368
|
-
if [ "$cmd_type" = "appearance" ] && [ -z "$cmd_args" ]; then
|
|
1369
|
-
# Just check current appearance
|
|
1370
|
-
echo "${log_prefix} Checking current appearance..."
|
|
1371
|
-
check_appearance_mode "$device_name"
|
|
1372
|
-
else
|
|
1373
|
-
local mode=""
|
|
1374
|
-
case "$cmd_type" in
|
|
1375
|
-
"darkmode") mode="dark" ;;
|
|
1376
|
-
"lightmode") mode="light" ;;
|
|
1377
|
-
"theme") mode="$cmd_args" ;;
|
|
1378
|
-
"appearance") mode="$cmd_args" ;;
|
|
1379
|
-
esac
|
|
1380
|
-
if [ -z "$mode" ]; then
|
|
1381
|
-
mode="toggle"
|
|
1382
|
-
fi
|
|
1383
|
-
echo "${log_prefix} Setting theme to: $mode"
|
|
1384
|
-
|
|
1385
|
-
# Try multiple methods for theme switching
|
|
1386
|
-
if ! toggle_dark_mode "$device_name" "$mode"; then
|
|
1387
|
-
# Fallback: Try direct URL scheme approach
|
|
1388
|
-
echo "${log_prefix} Trying alternative method..."
|
|
1389
|
-
local session=$(get_device_session "$device_name")
|
|
1390
|
-
if [ -n "$session" ]; then
|
|
1391
|
-
case "$mode" in
|
|
1392
|
-
"dark"|"light")
|
|
1393
|
-
# Use Settings URL scheme
|
|
1394
|
-
local url_result=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/url" \
|
|
1395
|
-
-H "Content-Type: application/json" \
|
|
1396
|
-
-d '{"url": "prefs:root=DISPLAY"}' 2>/dev/null)
|
|
1397
|
-
|
|
1398
|
-
if echo "$url_result" | grep -q '"sessionId"'; then
|
|
1399
|
-
echo "${log_prefix} ✅ Opened Display settings - manual selection may be needed"
|
|
1400
|
-
sleep 3
|
|
1401
|
-
execute_wda_command "$device_name" "home" >/dev/null
|
|
1402
|
-
else
|
|
1403
|
-
echo "${log_prefix} 💡 Please manually go to Settings > Display & Brightness > Appearance"
|
|
1404
|
-
fi
|
|
1405
|
-
;;
|
|
1406
|
-
esac
|
|
1407
|
-
fi
|
|
1408
|
-
fi
|
|
1409
|
-
fi
|
|
1410
|
-
;;
|
|
1411
|
-
|
|
1412
|
-
"rotate"|"rotation")
|
|
1413
|
-
local orientation="$cmd_args"
|
|
1414
|
-
if [ -z "$orientation" ]; then
|
|
1415
|
-
orientation="toggle"
|
|
1416
|
-
fi
|
|
1417
|
-
echo "${log_prefix} Rotating screen to: $orientation"
|
|
1418
|
-
rotate_screen "$device_name" "$orientation"
|
|
1419
|
-
;;
|
|
1420
|
-
|
|
1421
|
-
"rotationlock"|"lockrotation")
|
|
1422
|
-
local lock_state="$cmd_args"
|
|
1423
|
-
if [ -z "$lock_state" ]; then
|
|
1424
|
-
lock_state="toggle"
|
|
1425
|
-
fi
|
|
1426
|
-
echo "${log_prefix} Setting rotation lock: $lock_state"
|
|
1427
|
-
toggle_rotation_lock "$device_name" "$lock_state"
|
|
1428
|
-
;;
|
|
1429
|
-
|
|
1430
|
-
"restart"|"reboot")
|
|
1431
|
-
echo "${log_prefix} 🔄 Attempting device restart..."
|
|
1432
|
-
|
|
1433
|
-
# Get device UDID for wireless restart
|
|
1434
|
-
local device_details
|
|
1435
|
-
device_details=$(get_device_details "$device_name")
|
|
1436
|
-
local udid=$(echo "$device_details" | cut -d',' -f2)
|
|
1437
|
-
|
|
1438
|
-
if [ -n "$udid" ]; then
|
|
1439
|
-
echo "${log_prefix} 🔄 Restarting device via wireless-compatible tools..."
|
|
1440
|
-
|
|
1441
|
-
# Method 1: Try xcrun devicectl (works with wireless devices on iOS 17+)
|
|
1442
|
-
if command -v xcrun >/dev/null 2>&1; then
|
|
1443
|
-
echo "${log_prefix} 🔄 Using xcrun devicectl for wireless restart..."
|
|
1444
|
-
|
|
1445
|
-
# First try with hardware UDID
|
|
1446
|
-
local restart_result=$(xcrun devicectl device reboot --device "$udid" --timeout 10 2>&1)
|
|
1447
|
-
if [ $? -eq 0 ]; then
|
|
1448
|
-
echo "${log_prefix} ✅ Restart command sent via xcrun devicectl (UDID)"
|
|
1449
|
-
echo "${log_prefix} ⏳ Device will reboot in a few seconds..."
|
|
1450
|
-
else
|
|
1451
|
-
# Try to find device by name in devicectl list and use that identifier
|
|
1452
|
-
echo "${log_prefix} 🔍 Searching for device in CoreDevice list..."
|
|
1453
|
-
local coredevice_info=$(xcrun devicectl list devices --timeout 5 2>/dev/null | grep -v "Name\|--" | head -20)
|
|
1454
|
-
|
|
1455
|
-
# Look for connected devices first, then any device
|
|
1456
|
-
local target_device=""
|
|
1457
|
-
if [ -n "$coredevice_info" ]; then
|
|
1458
|
-
# First try to find a connected device
|
|
1459
|
-
target_device=$(echo "$coredevice_info" | grep "connected" | head -1 | awk '{print $1}')
|
|
1460
|
-
|
|
1461
|
-
# If no connected device, try any available device
|
|
1462
|
-
if [ -z "$target_device" ]; then
|
|
1463
|
-
target_device=$(echo "$coredevice_info" | head -1 | awk '{print $1}')
|
|
1464
|
-
fi
|
|
1465
|
-
fi
|
|
1466
|
-
|
|
1467
|
-
if [ -n "$target_device" ]; then
|
|
1468
|
-
echo "${log_prefix} 🎯 Found target device: $target_device"
|
|
1469
|
-
local retry_result=$(xcrun devicectl device reboot --device "$target_device" --timeout 10 2>&1)
|
|
1470
|
-
if [ $? -eq 0 ]; then
|
|
1471
|
-
echo "${log_prefix} ✅ Restart command sent via xcrun devicectl (device name)"
|
|
1472
|
-
echo "${log_prefix} ⏳ Device will reboot in a few seconds..."
|
|
1473
|
-
else
|
|
1474
|
-
echo "${log_prefix} ❌ Restart failed with both UDID and device name"
|
|
1475
|
-
echo "${log_prefix} 💡 Error: $retry_result"
|
|
1476
|
-
|
|
1477
|
-
# Final fallback to USB method
|
|
1478
|
-
echo "${log_prefix} 🔄 Trying USB fallback..."
|
|
1479
|
-
local device_control_script="$SCRIPT_DIR/ios_device_control.sh"
|
|
1480
|
-
if [ -f "$device_control_script" ] && "$device_control_script" restart "$device_name" 2>&1 | grep -q "✅"; then
|
|
1481
|
-
echo "${log_prefix} ✅ Restart command sent via USB fallback"
|
|
1482
|
-
echo "${log_prefix} ⏳ Device will reboot in a few seconds..."
|
|
1483
|
-
else
|
|
1484
|
-
echo "${log_prefix} ❌ All restart methods failed"
|
|
1485
|
-
echo "${log_prefix} 💡 Try connecting device via USB cable"
|
|
1486
|
-
fi
|
|
1487
|
-
fi
|
|
1488
|
-
else
|
|
1489
|
-
echo "${log_prefix} ❌ No devices found in CoreDevice list"
|
|
1490
|
-
echo "${log_prefix} 💡 Ensure device is paired and trusted in Xcode"
|
|
1491
|
-
fi
|
|
1492
|
-
fi
|
|
1493
|
-
elif command -v idevicediagnostics >/dev/null 2>&1; then
|
|
1494
|
-
# Fallback to USB-based restart
|
|
1495
|
-
echo "${log_prefix} 🔄 xcrun not available, trying USB restart..."
|
|
1496
|
-
if idevicediagnostics -u "$udid" restart 2>/dev/null; then
|
|
1497
|
-
echo "${log_prefix} ✅ Restart command sent via USB"
|
|
1498
|
-
echo "${log_prefix} ⏳ Device will reboot in a few seconds..."
|
|
1499
|
-
else
|
|
1500
|
-
echo "${log_prefix} ❌ Restart failed - device must be USB connected"
|
|
1501
|
-
echo "${log_prefix} 💡 For wireless devices, ensure iOS 17+ and Xcode pairing"
|
|
1502
|
-
fi
|
|
1503
|
-
else
|
|
1504
|
-
echo "${log_prefix} ❌ No restart tools available"
|
|
1505
|
-
echo "${log_prefix} 💡 Install Xcode command line tools or libimobiledevice"
|
|
1506
|
-
fi
|
|
1507
|
-
else
|
|
1508
|
-
echo "${log_prefix} ❌ Device UDID not available"
|
|
1509
|
-
fi
|
|
1510
|
-
;;
|
|
1511
|
-
|
|
1512
|
-
"shutdown"|"poweroff")
|
|
1513
|
-
echo "${log_prefix} 🔌 Attempting device shutdown..."
|
|
1514
|
-
echo "${log_prefix} ⚠️ WARNING: Wireless shutdown not supported - will attempt restart instead!"
|
|
1515
|
-
|
|
1516
|
-
# Get device UDID for wireless operations
|
|
1517
|
-
local device_details
|
|
1518
|
-
device_details=$(get_device_details "$device_name")
|
|
1519
|
-
local udid=$(echo "$device_details" | cut -d',' -f2)
|
|
1520
|
-
|
|
1521
|
-
if [ -n "$udid" ]; then
|
|
1522
|
-
echo "${log_prefix} 🔄 Note: xcrun devicectl doesn't support shutdown, using restart..."
|
|
1523
|
-
|
|
1524
|
-
# Method 1: Try xcrun devicectl reboot (closest alternative for wireless)
|
|
1525
|
-
if command -v xcrun >/dev/null 2>&1; then
|
|
1526
|
-
echo "${log_prefix} 🔄 Using xcrun devicectl for wireless restart (shutdown not supported)..."
|
|
1527
|
-
|
|
1528
|
-
# First try with hardware UDID
|
|
1529
|
-
local restart_result=$(xcrun devicectl device reboot --device "$udid" --timeout 10 2>&1)
|
|
1530
|
-
if [ $? -eq 0 ]; then
|
|
1531
|
-
echo "${log_prefix} ⚠️ Device will restart instead of shutdown (wireless limitation)"
|
|
1532
|
-
echo "${log_prefix} ⏳ Device will reboot in a few seconds..."
|
|
1533
|
-
else
|
|
1534
|
-
# Try to find device by name in devicectl list
|
|
1535
|
-
echo "${log_prefix} 🔍 Searching for device in CoreDevice list..."
|
|
1536
|
-
local coredevice_info=$(xcrun devicectl list devices --timeout 5 2>/dev/null | grep -v "Name\|--" | head -20)
|
|
1537
|
-
|
|
1538
|
-
# Look for connected devices first
|
|
1539
|
-
local target_device=""
|
|
1540
|
-
if [ -n "$coredevice_info" ]; then
|
|
1541
|
-
target_device=$(echo "$coredevice_info" | grep "connected" | head -1 | awk '{print $1}')
|
|
1542
|
-
if [ -z "$target_device" ]; then
|
|
1543
|
-
target_device=$(echo "$coredevice_info" | head -1 | awk '{print $1}')
|
|
1544
|
-
fi
|
|
1545
|
-
fi
|
|
1546
|
-
|
|
1547
|
-
if [ -n "$target_device" ]; then
|
|
1548
|
-
echo "${log_prefix} 🎯 Found target device: $target_device"
|
|
1549
|
-
local retry_result=$(xcrun devicectl device reboot --device "$target_device" --timeout 10 2>&1)
|
|
1550
|
-
if [ $? -eq 0 ]; then
|
|
1551
|
-
echo "${log_prefix} ⚠️ Device will restart instead of shutdown (wireless limitation)"
|
|
1552
|
-
echo "${log_prefix} ⏳ Device will reboot in a few seconds..."
|
|
1553
|
-
else
|
|
1554
|
-
echo "${log_prefix} ❌ Wireless restart failed, trying USB shutdown..."
|
|
1555
|
-
|
|
1556
|
-
# Final fallback to USB shutdown
|
|
1557
|
-
local device_control_script="$SCRIPT_DIR/ios_device_control.sh"
|
|
1558
|
-
if [ -f "$device_control_script" ]; then
|
|
1559
|
-
# Auto-confirm for batch operations
|
|
1560
|
-
if echo "y" | "$device_control_script" shutdown "$device_name" 2>&1 | grep -q "✅"; then
|
|
1561
|
-
echo "${log_prefix} ✅ Shutdown command sent via USB fallback"
|
|
1562
|
-
echo "${log_prefix} ⏳ Device will power off in a few seconds..."
|
|
1563
|
-
else
|
|
1564
|
-
echo "${log_prefix} ❌ Both wireless and USB methods failed"
|
|
1565
|
-
echo "${log_prefix} 💡 Try connecting via USB or manually power off device"
|
|
1566
|
-
fi
|
|
1567
|
-
fi
|
|
1568
|
-
fi
|
|
1569
|
-
else
|
|
1570
|
-
echo "${log_prefix} ❌ No devices found for wireless operation"
|
|
1571
|
-
echo "${log_prefix} 💡 Ensure device is paired and trusted in Xcode"
|
|
1572
|
-
fi
|
|
1573
|
-
fi
|
|
1574
|
-
elif command -v idevicediagnostics >/dev/null 2>&1; then
|
|
1575
|
-
# Fallback to USB-based shutdown
|
|
1576
|
-
echo "${log_prefix} 🔄 xcrun not available, trying USB shutdown..."
|
|
1577
|
-
if idevicediagnostics -u "$udid" shutdown 2>/dev/null; then
|
|
1578
|
-
echo "${log_prefix} ✅ Shutdown command sent via USB"
|
|
1579
|
-
echo "${log_prefix} ⏳ Device will power off in a few seconds..."
|
|
1580
|
-
else
|
|
1581
|
-
echo "${log_prefix} ❌ Shutdown failed - device must be USB connected"
|
|
1582
|
-
echo "${log_prefix} 💡 For wireless devices, only restart is supported"
|
|
1583
|
-
fi
|
|
1584
|
-
else
|
|
1585
|
-
echo "${log_prefix} ❌ No device control tools available"
|
|
1586
|
-
echo "${log_prefix} 💡 Install Xcode command line tools or libimobiledevice"
|
|
1587
|
-
fi
|
|
1588
|
-
else
|
|
1589
|
-
echo "${log_prefix} ❌ Device UDID not available"
|
|
1590
|
-
fi
|
|
1591
|
-
;;
|
|
1592
|
-
|
|
1593
|
-
"back"|"goback"|"navigate back"|"←")
|
|
1594
|
-
echo "${log_prefix} CASE: Back navigation matched!"
|
|
1595
|
-
local back_context="$cmd_args"
|
|
1596
|
-
|
|
1597
|
-
local session=$(get_device_session "$device_name")
|
|
1598
|
-
if [ -n "$session" ]; then
|
|
1599
|
-
echo "${log_prefix} 🔙 Executing smart back navigation..."
|
|
1600
|
-
|
|
1601
|
-
# Method 1: Try back button click first (most reliable)
|
|
1602
|
-
echo "${log_prefix} Trying Method 1: Back button click..."
|
|
1603
|
-
local back_click_payload='{
|
|
1604
|
-
"actions": [
|
|
1605
|
-
{
|
|
1606
|
-
"type": "pointer",
|
|
1607
|
-
"id": "finger1",
|
|
1608
|
-
"parameters": {"pointerType": "touch"},
|
|
1609
|
-
"actions": [
|
|
1610
|
-
{"type": "pointerMove", "duration": 0, "x": 40, "y": 110},
|
|
1611
|
-
{"type": "pointerDown", "button": 0},
|
|
1612
|
-
{"type": "pause", "duration": 150},
|
|
1613
|
-
{"type": "pointerUp", "button": 0}
|
|
1614
|
-
]
|
|
1615
|
-
}
|
|
1616
|
-
]
|
|
1617
|
-
}'
|
|
1618
|
-
|
|
1619
|
-
local click_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/actions" \
|
|
1620
|
-
-H 'Content-Type: application/json' \
|
|
1621
|
-
-d "$back_click_payload" 2>/dev/null)
|
|
1622
|
-
|
|
1623
|
-
if echo "$click_response" | grep -q '"sessionId"' && ! echo "$click_response" | grep -q '"error"'; then
|
|
1624
|
-
echo "${log_prefix} ✅ Back navigation via button click"
|
|
1625
|
-
else
|
|
1626
|
-
# Method 2: Try improved iOS native back gesture (proper edge swipe)
|
|
1627
|
-
echo "${log_prefix} Trying Method 2: iOS back gesture (proper edge swipe)..."
|
|
1628
|
-
local swipe_payload='{
|
|
1629
|
-
"actions": [
|
|
1630
|
-
{
|
|
1631
|
-
"type": "pointer",
|
|
1632
|
-
"id": "finger1",
|
|
1633
|
-
"parameters": {"pointerType": "touch"},
|
|
1634
|
-
"actions": [
|
|
1635
|
-
{"type": "pointerMove", "duration": 0, "x": 0, "y": 400},
|
|
1636
|
-
{"type": "pointerDown", "button": 0},
|
|
1637
|
-
{"type": "pointerMove", "duration": 800, "x": 250, "y": 400},
|
|
1638
|
-
{"type": "pointerUp", "button": 0}
|
|
1639
|
-
]
|
|
1640
|
-
}
|
|
1641
|
-
]
|
|
1642
|
-
}'
|
|
1643
|
-
|
|
1644
|
-
local swipe_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/actions" \
|
|
1645
|
-
-H 'Content-Type: application/json' \
|
|
1646
|
-
-d "$swipe_payload" 2>/dev/null)
|
|
1647
|
-
|
|
1648
|
-
if echo "$swipe_response" | grep -q '"sessionId"' && ! echo "$swipe_response" | grep -q '"error"'; then
|
|
1649
|
-
echo "${log_prefix} ✅ Back navigation via edge swipe"
|
|
1650
|
-
else
|
|
1651
|
-
# Method 3: Try WebDriverAgent drag method as fallback
|
|
1652
|
-
echo "${log_prefix} Trying Method 3: WDA drag method..."
|
|
1653
|
-
local drag_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/dragfromtoforduration" \
|
|
1654
|
-
-H 'Content-Type: application/json' \
|
|
1655
|
-
-d '{"fromX": 0, "fromY": 400, "toX": 250, "toY": 400, "duration": 0.8}' 2>/dev/null)
|
|
1656
|
-
|
|
1657
|
-
if echo "$drag_response" | grep -q '"sessionId"' && ! echo "$drag_response" | grep -q '"error"'; then
|
|
1658
|
-
echo "${log_prefix} ✅ Back navigation via drag method"
|
|
1659
|
-
else
|
|
1660
|
-
# Method 4: Try context-specific back navigation
|
|
1661
|
-
echo "${log_prefix} Trying Method 4: Context-specific navigation..."
|
|
1662
|
-
case "${back_context,,}" in
|
|
1663
|
-
"settings"|"preferences")
|
|
1664
|
-
# Settings app back - try Settings-specific position
|
|
1665
|
-
local settings_back='{"actions":[{"type":"pointer","id":"finger1","parameters":{"pointerType":"touch"},"actions":[{"type":"pointerMove","duration":0,"x":35,"y":94},{"type":"pointerDown","button":0},{"type":"pause","duration":100},{"type":"pointerUp","button":0}]}]}'
|
|
1666
|
-
curl -s -X POST "http://$host:$WDA_PORT/session/$session/actions" -H 'Content-Type: application/json' -d "$settings_back" >/dev/null
|
|
1667
|
-
echo "${log_prefix} ✅ Settings back navigation"
|
|
1668
|
-
;;
|
|
1669
|
-
"safari"|"browser"|"web")
|
|
1670
|
-
# Safari back navigation
|
|
1671
|
-
local safari_back='{"actions":[{"type":"pointer","id":"finger1","parameters":{"pointerType":"touch"},"actions":[{"type":"pointerMove","duration":0,"x":44,"y":94},{"type":"pointerDown","button":0},{"type":"pause","duration":100},{"type":"pointerUp","button":0}]}]}'
|
|
1672
|
-
curl -s -X POST "http://$host:$WDA_PORT/session/$session/actions" -H 'Content-Type: application/json' -d "$safari_back" >/dev/null
|
|
1673
|
-
echo "${log_prefix} ✅ Safari back navigation"
|
|
1674
|
-
;;
|
|
1675
|
-
*)
|
|
1676
|
-
# Method 5: Hardware home button simulation (universal fallback)
|
|
1677
|
-
echo "${log_prefix} Trying Method 5: Home button fallback..."
|
|
1678
|
-
local home_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/homescreen" \
|
|
1679
|
-
-H 'Content-Type: application/json' -d '{}' 2>/dev/null)
|
|
1680
|
-
|
|
1681
|
-
if echo "$home_response" | grep -q '"sessionId"' && ! echo "$home_response" | grep -q '"error"'; then
|
|
1682
|
-
echo "${log_prefix} ✅ Navigated to home screen"
|
|
1683
|
-
else
|
|
1684
|
-
echo "${log_prefix} ❌ All back navigation methods failed"
|
|
1685
|
-
fi
|
|
1686
|
-
;;
|
|
1687
|
-
esac
|
|
1688
|
-
fi
|
|
1689
|
-
fi
|
|
1690
|
-
fi
|
|
1691
|
-
else
|
|
1692
|
-
echo "${log_prefix} ❌ No session available"
|
|
1693
|
-
fi
|
|
1694
|
-
;;
|
|
1695
|
-
|
|
1696
|
-
"click"|"tap")
|
|
1697
|
-
echo "${log_prefix} CASE: Accessibility-first Click Activated"
|
|
1698
|
-
local coords="$cmd_args"
|
|
1699
|
-
|
|
1700
|
-
if [[ "$coords" =~ ^[0-9]+,[0-9]+$ ]]; then
|
|
1701
|
-
# Direct coordinates - use precise W3C Actions API
|
|
1702
|
-
local x=$(echo "$coords" | cut -d',' -f1)
|
|
1703
|
-
local y=$(echo "$coords" | cut -d',' -f2)
|
|
1704
|
-
echo "${log_prefix} 🎯 Precise coordinate click: ($x, $y)"
|
|
1705
|
-
|
|
1706
|
-
local session=$(get_device_session "$device_name")
|
|
1707
|
-
if [ -n "$session" ]; then
|
|
1708
|
-
local actions_payload='{
|
|
1709
|
-
"actions": [
|
|
1710
|
-
{
|
|
1711
|
-
"type": "pointer",
|
|
1712
|
-
"id": "finger1",
|
|
1713
|
-
"parameters": {"pointerType": "touch"},
|
|
1714
|
-
"actions": [
|
|
1715
|
-
{"type": "pointerMove", "duration": 0, "x": '$x', "y": '$y'},
|
|
1716
|
-
{"type": "pointerDown", "button": 0},
|
|
1717
|
-
{"type": "pause", "duration": 100},
|
|
1718
|
-
{"type": "pointerUp", "button": 0}
|
|
1719
|
-
]
|
|
1720
|
-
}
|
|
1721
|
-
]
|
|
1722
|
-
}'
|
|
1723
|
-
|
|
1724
|
-
local response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/actions" \
|
|
1725
|
-
-H 'Content-Type: application/json' \
|
|
1726
|
-
-d "$actions_payload" 2>/dev/null)
|
|
1727
|
-
|
|
1728
|
-
if echo "$response" | grep -q '"sessionId"' && ! echo "$response" | grep -q '"error"'; then
|
|
1729
|
-
echo "${log_prefix} ✅ Coordinate click successful at ($x, $y)"
|
|
1730
|
-
else
|
|
1731
|
-
echo "${log_prefix} ❌ Coordinate click failed"
|
|
1732
|
-
echo "${log_prefix} Response: $response"
|
|
1733
|
-
fi
|
|
1734
|
-
else
|
|
1735
|
-
echo "${log_prefix} ❌ No session available"
|
|
1736
|
-
fi
|
|
1737
|
-
else
|
|
1738
|
-
# Non-AI click: Inline accessibility-first implementation
|
|
1739
|
-
local text="$coords"
|
|
1740
|
-
echo "${log_prefix} 🔎 Accessibility search: '$text'"
|
|
1741
|
-
local session=$(get_device_session "$device_name")
|
|
1742
|
-
if [ -n "$session" ]; then
|
|
1743
|
-
# Method 1: Try accessibility ID
|
|
1744
|
-
local acc_elements=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/elements" \
|
|
1745
|
-
-H 'Content-Type: application/json' \
|
|
1746
|
-
-d '{"using":"accessibility id","value":"'"$text"'"}' 2>/dev/null)
|
|
1747
|
-
|
|
1748
|
-
local element_id=$(echo "$acc_elements" | python3 -c "
|
|
1749
|
-
import sys, json
|
|
1750
|
-
try:
|
|
1751
|
-
data = json.load(sys.stdin)
|
|
1752
|
-
elements = data.get('value', [])
|
|
1753
|
-
if elements and 'ELEMENT' in elements[0]:
|
|
1754
|
-
print(elements[0]['ELEMENT'])
|
|
1755
|
-
except: pass
|
|
1756
|
-
" 2>/dev/null)
|
|
1757
|
-
|
|
1758
|
-
if [ -z "$element_id" ]; then
|
|
1759
|
-
# Method 2: Try name attribute
|
|
1760
|
-
local name_elements=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/elements" \
|
|
1761
|
-
-H 'Content-Type: application/json' \
|
|
1762
|
-
-d '{"using":"name","value":"'"$text"'"}' 2>/dev/null)
|
|
1763
|
-
element_id=$(echo "$name_elements" | python3 -c "
|
|
1764
|
-
import sys, json
|
|
1765
|
-
try:
|
|
1766
|
-
data = json.load(sys.stdin)
|
|
1767
|
-
elements = data.get('value', [])
|
|
1768
|
-
if elements and 'ELEMENT' in elements[0]:
|
|
1769
|
-
print(elements[0]['ELEMENT'])
|
|
1770
|
-
except: pass
|
|
1771
|
-
" 2>/dev/null)
|
|
1772
|
-
fi
|
|
1773
|
-
|
|
1774
|
-
# Method 3: Try predicate string (searches across all apps including system)
|
|
1775
|
-
if [ -z "$element_id" ]; then
|
|
1776
|
-
echo "${log_prefix} 🔍 Searching with predicate string..."
|
|
1777
|
-
# Try multiple predicate variations for better matching
|
|
1778
|
-
for pred_query in \
|
|
1779
|
-
"type == 'XCUIElementTypeButton' AND (name CONTAINS[cd] '$text' OR label CONTAINS[cd] '$text')" \
|
|
1780
|
-
"name CONTAINS[cd] '$text' OR label CONTAINS[cd] '$text'" \
|
|
1781
|
-
"type == 'XCUIElementTypeButton' AND (name == '$text' OR label == '$text')" \
|
|
1782
|
-
"name == '$text' OR label == '$text'"; do
|
|
1783
|
-
|
|
1784
|
-
local pred_elements=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/elements" \
|
|
1785
|
-
-H 'Content-Type: application/json' \
|
|
1786
|
-
-d '{"using":"predicate string","value":"'"$pred_query"'"}' 2>/dev/null)
|
|
1787
|
-
element_id=$(echo "$pred_elements" | python3 -c "
|
|
1788
|
-
import sys, json
|
|
1789
|
-
try:
|
|
1790
|
-
data = json.load(sys.stdin)
|
|
1791
|
-
elements = data.get('value', [])
|
|
1792
|
-
if elements and 'ELEMENT' in elements[0]:
|
|
1793
|
-
print(elements[0]['ELEMENT'])
|
|
1794
|
-
except: pass
|
|
1795
|
-
" 2>/dev/null)
|
|
1796
|
-
if [ -n "$element_id" ]; then
|
|
1797
|
-
echo "${log_prefix} ✅ Found via predicate (system UI)"
|
|
1798
|
-
break
|
|
1799
|
-
fi
|
|
1800
|
-
done
|
|
1801
|
-
fi
|
|
1802
|
-
|
|
1803
|
-
# Method 4: Try class chain with partial matching
|
|
1804
|
-
if [ -z "$element_id" ]; then
|
|
1805
|
-
echo "${log_prefix} 🔍 Searching with class chain..."
|
|
1806
|
-
for chain_pattern in \
|
|
1807
|
-
"**/XCUIElementTypeButton[\`name CONTAINS '$text' OR label CONTAINS '$text'\`]" \
|
|
1808
|
-
"**/XCUIElementTypeButton[\`name == '$text' OR label == '$text'\`]" \
|
|
1809
|
-
"**/XCUIElementTypeButton[\`name BEGINSWITH '$text' OR label BEGINSWITH '$text'\`]" \
|
|
1810
|
-
"**/*[\`name CONTAINS '$text' OR label CONTAINS '$text'\`]"; do
|
|
1811
|
-
|
|
1812
|
-
local chain_elements=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/elements" \
|
|
1813
|
-
-H 'Content-Type: application/json' \
|
|
1814
|
-
-d '{"using":"class chain","value":"'"$chain_pattern"'"}' 2>/dev/null)
|
|
1815
|
-
element_id=$(echo "$chain_elements" | python3 -c "
|
|
1816
|
-
import sys, json
|
|
1817
|
-
try:
|
|
1818
|
-
data = json.load(sys.stdin)
|
|
1819
|
-
elements = data.get('value', [])
|
|
1820
|
-
if elements and 'ELEMENT' in elements[0]:
|
|
1821
|
-
print(elements[0]['ELEMENT'])
|
|
1822
|
-
except: pass
|
|
1823
|
-
" 2>/dev/null)
|
|
1824
|
-
if [ -n "$element_id" ]; then
|
|
1825
|
-
echo "${log_prefix} ✅ Found via class chain (pattern: $chain_pattern)"
|
|
1826
|
-
break
|
|
1827
|
-
fi
|
|
1828
|
-
done
|
|
1829
|
-
fi
|
|
1830
|
-
|
|
1831
|
-
# Method 5: Try getting full page source and searching with partial text
|
|
1832
|
-
if [ -z "$element_id" ]; then
|
|
1833
|
-
echo "${log_prefix} 🔍 Searching in full page source..."
|
|
1834
|
-
local page_source=$(curl -s -X GET "http://$host:$WDA_PORT/session/$session/source" 2>/dev/null)
|
|
1835
|
-
|
|
1836
|
-
# Check if element exists in page source (case insensitive partial match)
|
|
1837
|
-
if echo "$page_source" | grep -iq "$text"; then
|
|
1838
|
-
echo "${log_prefix} 📄 Element found in page source (partial match), trying XPath..."
|
|
1839
|
-
|
|
1840
|
-
# Try multiple XPath strategies with partial text matching
|
|
1841
|
-
for xpath in \
|
|
1842
|
-
"//XCUIElementTypeButton[@name='$text']" \
|
|
1843
|
-
"//XCUIElementTypeButton[@label='$text']" \
|
|
1844
|
-
"//XCUIElementTypeButton[contains(@name,'$text')]" \
|
|
1845
|
-
"//XCUIElementTypeButton[contains(@label,'$text')]" \
|
|
1846
|
-
"//XCUIElementTypeStaticText[@name='$text']/parent::XCUIElementTypeButton" \
|
|
1847
|
-
"//XCUIElementTypeStaticText[contains(@name,'$text')]/parent::XCUIElementTypeButton" \
|
|
1848
|
-
"//*[@name='$text']" \
|
|
1849
|
-
"//*[@label='$text']" \
|
|
1850
|
-
"//*[contains(@name,'$text')]" \
|
|
1851
|
-
"//*[contains(@label,'$text')]"; do
|
|
1852
|
-
|
|
1853
|
-
local xpath_elements=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/elements" \
|
|
1854
|
-
-H 'Content-Type: application/json' \
|
|
1855
|
-
-d '{"using":"xpath","value":"'"$xpath"'"}' 2>/dev/null)
|
|
1856
|
-
element_id=$(echo "$xpath_elements" | python3 -c "
|
|
1857
|
-
import sys, json
|
|
1858
|
-
try:
|
|
1859
|
-
data = json.load(sys.stdin)
|
|
1860
|
-
elements = data.get('value', [])
|
|
1861
|
-
if elements and 'ELEMENT' in elements[0]:
|
|
1862
|
-
print(elements[0]['ELEMENT'])
|
|
1863
|
-
except: pass
|
|
1864
|
-
" 2>/dev/null)
|
|
1865
|
-
if [ -n "$element_id" ]; then
|
|
1866
|
-
echo "${log_prefix} ✅ Found via XPath: $xpath"
|
|
1867
|
-
break
|
|
1868
|
-
fi
|
|
1869
|
-
done
|
|
1870
|
-
else
|
|
1871
|
-
echo "${log_prefix} 📄 Text '$text' not found in page source - element may not be visible"
|
|
1872
|
-
fi
|
|
1873
|
-
fi
|
|
1874
|
-
|
|
1875
|
-
if [ -n "$element_id" ]; then
|
|
1876
|
-
echo "${log_prefix} ✅ Found element via accessibility"
|
|
1877
|
-
# Get element rect for coordinates
|
|
1878
|
-
local rect_response=$(curl -s -X GET "http://$host:$WDA_PORT/session/$session/element/$element_id/rect" 2>/dev/null)
|
|
1879
|
-
local elem_x=$(echo "$rect_response" | python3 -c "
|
|
1880
|
-
import sys, json
|
|
1881
|
-
try:
|
|
1882
|
-
data = json.load(sys.stdin)
|
|
1883
|
-
value = data.get('value', {})
|
|
1884
|
-
print(int(value.get('x', 0) + value.get('width', 0) / 2))
|
|
1885
|
-
except: print(0)
|
|
1886
|
-
" 2>/dev/null)
|
|
1887
|
-
local elem_y=$(echo "$rect_response" | python3 -c "
|
|
1888
|
-
import sys, json
|
|
1889
|
-
try:
|
|
1890
|
-
data = json.load(sys.stdin)
|
|
1891
|
-
value = data.get('value', {})
|
|
1892
|
-
print(int(value.get('y', 0) + value.get('height', 0) / 2))
|
|
1893
|
-
except: print(0)
|
|
1894
|
-
" 2>/dev/null)
|
|
1895
|
-
|
|
1896
|
-
if [ "$elem_x" -gt 0 ] && [ "$elem_y" -gt 0 ]; then
|
|
1897
|
-
echo "${log_prefix} 🎯 Element center: ($elem_x,$elem_y)"
|
|
1898
|
-
local elem_actions='{"actions":[{"type":"pointer","id":"finger1","parameters":{"pointerType":"touch"},"actions":[{"type":"pointerMove","duration":0,"x":'$elem_x',"y":'$elem_y'},{"type":"pointerDown","button":0},{"type":"pause","duration":150},{"type":"pointerUp","button":0}]}]}'
|
|
1899
|
-
local elem_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/actions" \
|
|
1900
|
-
-H 'Content-Type: application/json' -d "$elem_actions" 2>/dev/null)
|
|
1901
|
-
|
|
1902
|
-
if echo "$elem_response" | grep -q '"sessionId"' && ! echo "$elem_response" | grep -q '"error"'; then
|
|
1903
|
-
echo "${log_prefix} ✅ Clicked '$text' at ($elem_x,$elem_y)"
|
|
1904
|
-
else
|
|
1905
|
-
echo "${log_prefix} ❌ Click failed"
|
|
1906
|
-
fi
|
|
1907
|
-
else
|
|
1908
|
-
# Fallback: Direct element click
|
|
1909
|
-
local click_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/element/$element_id/click" \
|
|
1910
|
-
-H 'Content-Type: application/json' -d '{}' 2>/dev/null)
|
|
1911
|
-
if echo "$click_response" | grep -q '"sessionId"' && ! echo "$click_response" | grep -q '"error"'; then
|
|
1912
|
-
echo "${log_prefix} ✅ Clicked '$text' via element click"
|
|
1913
|
-
else
|
|
1914
|
-
echo "${log_prefix} ❌ Click failed"
|
|
1915
|
-
fi
|
|
1916
|
-
fi
|
|
1917
|
-
else
|
|
1918
|
-
echo "${log_prefix} ❌ Element '$text' not found"
|
|
1919
|
-
fi
|
|
1920
|
-
else
|
|
1921
|
-
echo "${log_prefix} ❌ No session available"
|
|
1922
|
-
fi
|
|
1923
|
-
fi
|
|
1924
|
-
;;
|
|
1925
|
-
|
|
1926
|
-
"refresh"|"refresh_screen"|"reload_cache")
|
|
1927
|
-
echo "${log_prefix} 🔄 REFRESHING SCREEN CACHE..."
|
|
1928
|
-
refresh_screen_cache "$device_name"
|
|
1929
|
-
echo "${log_prefix} ✅ Screen cache refreshed. Next click will re-analyze screen."
|
|
1930
|
-
;;
|
|
1931
|
-
|
|
1932
|
-
"longpress"|"longclick")
|
|
1933
|
-
local coords="$cmd_args"
|
|
1934
|
-
if [[ "$coords" =~ ^[0-9]+,[0-9]+$ ]]; then
|
|
1935
|
-
local x=$(echo "$coords" | cut -d',' -f1)
|
|
1936
|
-
local y=$(echo "$coords" | cut -d',' -f2)
|
|
1937
|
-
echo "${log_prefix} 🎯 Precise long press at ($x, $y)"
|
|
1938
|
-
local session=$(get_device_session "$device_name")
|
|
1939
|
-
if [ -n "$session" ]; then
|
|
1940
|
-
local actions_payload='{"actions":[{"type":"pointer","id":"finger1","parameters":{"pointerType":"touch"},"actions":[{"type":"pointerMove","duration":0,"x":'$x',"y":'$y'},{"type":"pointerDown","button":0},{"type":"pause","duration":800},{"type":"pointerUp","button":0}]}]}'
|
|
1941
|
-
local response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/actions" -H 'Content-Type: application/json' -d "$actions_payload" 2>/dev/null)
|
|
1942
|
-
if echo "$response" | grep -q '"sessionId"' && ! echo "$response" | grep -q '"error"'; then
|
|
1943
|
-
echo "${log_prefix} ✅ Long press successful at ($x, $y)"
|
|
1944
|
-
else
|
|
1945
|
-
echo "${log_prefix} ❌ Long press failed"
|
|
1946
|
-
echo "${log_prefix} Response: $response"
|
|
1947
|
-
fi
|
|
1948
|
-
else
|
|
1949
|
-
echo "${log_prefix} ❌ No session available"
|
|
1950
|
-
fi
|
|
1951
|
-
else
|
|
1952
|
-
# Search for element by text - GENIUS SCALABLE APPROACH!
|
|
1953
|
-
local text="$coords"
|
|
1954
|
-
echo "${log_prefix} Searching for element: '$text'"
|
|
1955
|
-
|
|
1956
|
-
local session=$(get_device_session "$device_name")
|
|
1957
|
-
if [ -n "$session" ]; then
|
|
1958
|
-
# Step 1: Try dynamic element detection first (like long press does)
|
|
1959
|
-
echo "${log_prefix} 🔍 Attempting dynamic element detection..."
|
|
1960
|
-
local search_result=$(find_element_by_text "$session" "$host" "$text" "$device_name")
|
|
1961
|
-
local element_id=$(echo "$search_result" | cut -d'|' -f1)
|
|
1962
|
-
local found_strategy=$(echo "$search_result" | cut -d'|' -f2)
|
|
1963
|
-
|
|
1964
|
-
if [ -n "$element_id" ]; then
|
|
1965
|
-
echo "${log_prefix} ✅ Found element dynamically using: $found_strategy"
|
|
1966
|
-
echo "${log_prefix} Element ID: $element_id"
|
|
1967
|
-
|
|
1968
|
-
# Get element center coordinates
|
|
1969
|
-
local coordinates=$(get_element_center "$session" "$host" "$element_id")
|
|
1970
|
-
local x=$(echo "$coordinates" | cut -d',' -f1)
|
|
1971
|
-
local y=$(echo "$coordinates" | cut -d',' -f2)
|
|
1972
|
-
|
|
1973
|
-
if [[ "$x" =~ ^[0-9]+$ ]] && [[ "$y" =~ ^[0-9]+$ ]] && [ "$x" -gt 0 ] && [ "$y" -gt 0 ]; then
|
|
1974
|
-
echo "${log_prefix} 🎯 Dynamic element center: ($x, $y)"
|
|
1975
|
-
|
|
1976
|
-
# Use W3C Actions API for precise click
|
|
1977
|
-
echo "${log_prefix} Trying Method 1: W3C Actions API (dynamic)..."
|
|
1978
|
-
local actions_payload='{
|
|
1979
|
-
"actions": [
|
|
1980
|
-
{
|
|
1981
|
-
"type": "pointer",
|
|
1982
|
-
"id": "finger1",
|
|
1983
|
-
"parameters": {"pointerType": "touch"},
|
|
1984
|
-
"actions": [
|
|
1985
|
-
{"type": "pointerMove", "duration": 0, "x": '$x', "y": '$y'},
|
|
1986
|
-
{"type": "pointerDown", "button": 0},
|
|
1987
|
-
{"type": "pause", "duration": 100},
|
|
1988
|
-
{"type": "pointerUp", "button": 0}
|
|
1989
|
-
]
|
|
1990
|
-
}
|
|
1991
|
-
]
|
|
1992
|
-
}'
|
|
1993
|
-
|
|
1994
|
-
local tap_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/actions" \
|
|
1995
|
-
-H 'Content-Type: application/json' \
|
|
1996
|
-
-d "$actions_payload" 2>/dev/null)
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
if echo "$tap_response" | grep -q '"sessionId"' && ! echo "$tap_response" | grep -q '"error"'; then
|
|
2000
|
-
echo "${log_prefix} ✅ Successfully clicked '$text' at ($x, $y) - Dynamic Detection"
|
|
2001
|
-
echo "${log_prefix} Completed"
|
|
2002
|
-
return
|
|
2003
|
-
fi
|
|
2004
|
-
fi
|
|
2005
|
-
fi
|
|
2006
|
-
|
|
2007
|
-
# Step 2: Fallback to device-specific coordinate mapping
|
|
2008
|
-
echo "${log_prefix} 🔄 Falling back to coordinate mapping..."
|
|
2009
|
-
local text_lower=$(echo "$text" | tr '[:upper:]' '[:lower:]')
|
|
2010
|
-
local x y found=false
|
|
2011
|
-
|
|
2012
|
-
# Get device screen dimensions for coordinate adaptation
|
|
2013
|
-
local screen_width screen_height
|
|
2014
|
-
case "$device_name" in
|
|
2015
|
-
"iphone16p")
|
|
2016
|
-
screen_width=430; screen_height=932
|
|
2017
|
-
;;
|
|
2018
|
-
"iphone17")
|
|
2019
|
-
screen_width=402; screen_height=874
|
|
2020
|
-
;;
|
|
2021
|
-
"ipad")
|
|
2022
|
-
screen_width=834; screen_height=1210
|
|
2023
|
-
;;
|
|
2024
|
-
*)
|
|
2025
|
-
screen_width=400; screen_height=800 # fallback
|
|
2026
|
-
;;
|
|
2027
|
-
esac
|
|
2028
|
-
|
|
2029
|
-
# Common app icons (home screen)
|
|
2030
|
-
case "$text_lower" in
|
|
2031
|
-
"settings")
|
|
2032
|
-
x=200; y=300; found=true
|
|
2033
|
-
;;
|
|
2034
|
-
"safari")
|
|
2035
|
-
x=75; y=300; found=true
|
|
2036
|
-
;;
|
|
2037
|
-
"camera")
|
|
2038
|
-
x=325; y=300; found=true
|
|
2039
|
-
;;
|
|
2040
|
-
"photos")
|
|
2041
|
-
x=75; y=425; found=true
|
|
2042
|
-
;;
|
|
2043
|
-
"messages")
|
|
2044
|
-
x=200; y=425; found=true
|
|
2045
|
-
;;
|
|
2046
|
-
"mail")
|
|
2047
|
-
x=325; y=425; found=true
|
|
2048
|
-
;;
|
|
2049
|
-
"phone")
|
|
2050
|
-
x=75; y=550; found=true
|
|
2051
|
-
;;
|
|
2052
|
-
"facetime")
|
|
2053
|
-
x=200; y=550; found=true
|
|
2054
|
-
;;
|
|
2055
|
-
# Settings app elements
|
|
2056
|
-
"general")
|
|
2057
|
-
x=200; y=200; found=true
|
|
2058
|
-
;;
|
|
2059
|
-
"wi-fi"|"wifi")
|
|
2060
|
-
x=200; y=150; found=true
|
|
2061
|
-
;;
|
|
2062
|
-
"bluetooth")
|
|
2063
|
-
x=200; y=200; found=true
|
|
2064
|
-
;;
|
|
2065
|
-
"cellular")
|
|
2066
|
-
x=200; y=250; found=true
|
|
2067
|
-
;;
|
|
2068
|
-
"personal hotspot"|"hotspot")
|
|
2069
|
-
x=200; y=300; found=true
|
|
2070
|
-
;;
|
|
2071
|
-
"privacy & security"|"privacy")
|
|
2072
|
-
x=200; y=400; found=true
|
|
2073
|
-
;;
|
|
2074
|
-
# Navigation elements - COMPREHENSIVE BACK SYSTEM (device-aware)
|
|
2075
|
-
"settings back"|"settings <"|"< settings")
|
|
2076
|
-
# Back button in Settings app
|
|
2077
|
-
x=$(echo "$screen_width * 0.0875" | bc); y=$(echo "$screen_height * 0.117" | bc); found=true
|
|
2078
|
-
;;
|
|
2079
|
-
"safari back"|"safari <"|"< safari")
|
|
2080
|
-
# Back button in Safari
|
|
2081
|
-
x=$(echo "$screen_width * 0.11" | bc); y=$(echo "$screen_height * 0.117" | bc); found=true
|
|
2082
|
-
;;
|
|
2083
|
-
"app back"|"navigation back")
|
|
2084
|
-
# Generic app back button
|
|
2085
|
-
x=$(echo "$screen_width * 0.08" | bc); y=$(echo "$screen_height * 0.1225" | bc); found=true
|
|
2086
|
-
;;
|
|
2087
|
-
"cancel"|"cancel button")
|
|
2088
|
-
# Top-left area
|
|
2089
|
-
x=$(echo "$screen_width * 0.125" | bc); y=$(echo "$screen_width * 0.125" | bc); found=true
|
|
2090
|
-
;;
|
|
2091
|
-
"next"|"next button")
|
|
2092
|
-
# Top-right
|
|
2093
|
-
x=$(echo "$screen_width * 0.875" | bc); y=$(echo "$screen_height * 0.125" | bc); found=true
|
|
2094
|
-
;;
|
|
2095
|
-
"edit"|"edit button")
|
|
2096
|
-
# Top-right
|
|
2097
|
-
x=$(echo "$screen_width * 0.875" | bc); y=$(echo "$screen_height * 0.125" | bc); found=true
|
|
2098
|
-
;;
|
|
2099
|
-
"save"|"save button")
|
|
2100
|
-
x=350; y=100; found=true
|
|
2101
|
-
;;
|
|
2102
|
-
# Tab Bar Navigation (bottom of screen)
|
|
2103
|
-
"tab back"|"previous tab")
|
|
2104
|
-
x=100; y=680; found=true
|
|
2105
|
-
;;
|
|
2106
|
-
"home tab"|"house"|"🏠")
|
|
2107
|
-
x=200; y=680; found=true
|
|
2108
|
-
;;
|
|
2109
|
-
"search tab"|"🔍"|"magnifying glass")
|
|
2110
|
-
x=300; y=680; found=true
|
|
2111
|
-
;;
|
|
2112
|
-
# Browser Navigation
|
|
2113
|
-
"browser back"|"web back")
|
|
2114
|
-
x=44; y=650; found=true
|
|
2115
|
-
;;
|
|
2116
|
-
"browser forward"|"web forward")
|
|
2117
|
-
x=100; y=650; found=true
|
|
2118
|
-
;;
|
|
2119
|
-
"refresh"|"reload"|"↻")
|
|
2120
|
-
x=200; y=650; found=true
|
|
2121
|
-
;;
|
|
2122
|
-
# Control Center elements
|
|
2123
|
-
"wifi toggle"|"wifi button")
|
|
2124
|
-
x=100; y=200; found=true
|
|
2125
|
-
;;
|
|
2126
|
-
"bluetooth toggle"|"bluetooth button")
|
|
2127
|
-
x=200; y=200; found=true
|
|
2128
|
-
;;
|
|
2129
|
-
"airplane mode")
|
|
2130
|
-
x=50; y=200; found=true
|
|
2131
|
-
;;
|
|
2132
|
-
# Common UI Elements - Device-aware coordinates
|
|
2133
|
-
"learn more"|"learn more >")
|
|
2134
|
-
# Typically at bottom half of screen
|
|
2135
|
-
x=$(echo "$screen_width / 2" | bc); y=$(echo "$screen_height * 0.5" | bc); found=true
|
|
2136
|
-
;;
|
|
2137
|
-
"continue"|"continue button")
|
|
2138
|
-
# Typically at bottom of screen (75% down)
|
|
2139
|
-
x=$(echo "$screen_width / 2" | bc); y=$(echo "$screen_height * 0.75" | bc); found=true
|
|
2140
|
-
;;
|
|
2141
|
-
"get started"|"start"|"begin")
|
|
2142
|
-
# Usually in lower half
|
|
2143
|
-
x=$(echo "$screen_width / 2" | bc); y=$(echo "$screen_height * 0.625" | bc); found=true
|
|
2144
|
-
;;
|
|
2145
|
-
"sign in"|"log in"|"login")
|
|
2146
|
-
# Mid-lower screen
|
|
2147
|
-
x=$(echo "$screen_width / 2" | bc); y=$(echo "$screen_height * 0.56" | bc); found=true
|
|
2148
|
-
;;
|
|
2149
|
-
"sign up"|"register"|"create account")
|
|
2150
|
-
# Lower screen
|
|
2151
|
-
x=$(echo "$screen_width / 2" | bc); y=$(echo "$screen_height * 0.625" | bc); found=true
|
|
2152
|
-
;;
|
|
2153
|
-
"skip"|"skip for now"|"maybe later")
|
|
2154
|
-
# Top area, left-ish
|
|
2155
|
-
x=$(echo "$screen_width * 0.2" | bc); y=$(echo "$screen_height * 0.125" | bc); found=true
|
|
2156
|
-
;;
|
|
2157
|
-
"allow"|"allow access"|"ok"|"okay")
|
|
2158
|
-
# Dialog right side
|
|
2159
|
-
x=$(echo "$screen_width * 0.75" | bc); y=$(echo "$screen_height * 0.5" | bc); found=true
|
|
2160
|
-
;;
|
|
2161
|
-
"don't allow"|"deny"|"not now")
|
|
2162
|
-
# Dialog left side
|
|
2163
|
-
x=$(echo "$screen_width * 0.25" | bc); y=$(echo "$screen_height * 0.5" | bc); found=true
|
|
2164
|
-
;;
|
|
2165
|
-
"agree"|"accept"|"i agree")
|
|
2166
|
-
# Bottom area
|
|
2167
|
-
x=$(echo "$screen_width / 2" | bc); y=$(echo "$screen_height * 0.625" | bc); found=true
|
|
2168
|
-
;;
|
|
2169
|
-
"try again"|"retry")
|
|
2170
|
-
# Mid screen
|
|
2171
|
-
x=$(echo "$screen_width / 2" | bc); y=$(echo "$screen_height * 0.56" | bc); found=true
|
|
2172
|
-
;;
|
|
2173
|
-
# Navigation elements with device-aware coordinates
|
|
2174
|
-
"back"|"< back"|"back button"|"←"|"<")
|
|
2175
|
-
# Top-left corner
|
|
2176
|
-
x=$(echo "$screen_width * 0.075" | bc); y=$(echo "$screen_height * 0.125" | bc); found=true
|
|
2177
|
-
;;
|
|
2178
|
-
"close"|"×"|"x"|"close button")
|
|
2179
|
-
# Top-right corner
|
|
2180
|
-
x=$(echo "$screen_width * 0.875" | bc); y=$(echo "$screen_height * 0.125" | bc); found=true
|
|
2181
|
-
;;
|
|
2182
|
-
"done"|"done button")
|
|
2183
|
-
# Top-right
|
|
2184
|
-
x=$(echo "$screen_width * 0.875" | bc); y=$(echo "$screen_height * 0.125" | bc); found=true
|
|
2185
|
-
;;
|
|
2186
|
-
"share"|"share button")
|
|
2187
|
-
x=350; y=100; found=true
|
|
2188
|
-
;;
|
|
2189
|
-
"add"|"+ add"|"add item")
|
|
2190
|
-
x=350; y=100; found=true
|
|
2191
|
-
;;
|
|
2192
|
-
"delete"|"remove"|"trash")
|
|
2193
|
-
x=350; y=100; found=true
|
|
2194
|
-
;;
|
|
2195
|
-
"search"|"🔍 search")
|
|
2196
|
-
x=200; y=150; found=true
|
|
2197
|
-
;;
|
|
2198
|
-
"more"|"more options"|"⋯"|"...")
|
|
2199
|
-
x=350; y=100; found=true
|
|
2200
|
-
;;
|
|
2201
|
-
"info"|"ⓘ"|"information")
|
|
2202
|
-
x=350; y=100; found=true
|
|
2203
|
-
;;
|
|
2204
|
-
*)
|
|
2205
|
-
found=false
|
|
2206
|
-
;;
|
|
2207
|
-
esac
|
|
2208
|
-
|
|
2209
|
-
if [ "$found" = "true" ]; then
|
|
2210
|
-
echo "${log_prefix} ✅ Using known coordinates for '$text': ($x, $y)"
|
|
2211
|
-
|
|
2212
|
-
# Use CORRECT WebDriverAgent endpoint - try multiple methods
|
|
2213
|
-
echo "${log_prefix} Trying Method 1: W3C Actions API..."
|
|
2214
|
-
local actions_payload='{
|
|
2215
|
-
"actions": [
|
|
2216
|
-
{
|
|
2217
|
-
"type": "pointer",
|
|
2218
|
-
"id": "finger1",
|
|
2219
|
-
"parameters": {"pointerType": "touch"},
|
|
2220
|
-
"actions": [
|
|
2221
|
-
{"type": "pointerMove", "duration": 0, "x": '$x', "y": '$y'},
|
|
2222
|
-
{"type": "pointerDown", "button": 0},
|
|
2223
|
-
{"type": "pause", "duration": 100},
|
|
2224
|
-
{"type": "pointerUp", "button": 0}
|
|
2225
|
-
]
|
|
2226
|
-
}
|
|
2227
|
-
]
|
|
2228
|
-
}'
|
|
2229
|
-
|
|
2230
|
-
local tap_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/actions" \
|
|
2231
|
-
-H 'Content-Type: application/json' \
|
|
2232
|
-
-d "$actions_payload" 2>/dev/null)
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
if echo "$tap_response" | grep -q '"sessionId"' && ! echo "$tap_response" | grep -q '"error"'; then
|
|
2236
|
-
echo "${log_prefix} ✅ Successfully clicked '$text' at ($x, $y) - W3C Actions"
|
|
2237
|
-
else
|
|
2238
|
-
# Method 2: Try WebDriverAgent touch endpoint
|
|
2239
|
-
echo "${log_prefix} Trying Method 2: WDA touch endpoint..."
|
|
2240
|
-
local touch_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/touch/perform" \
|
|
2241
|
-
-H 'Content-Type: application/json' \
|
|
2242
|
-
-d '[{"action": "press", "options": {"x": '$x', "y": '$y'}}, {"action": "wait", "options": {"ms": 100}}, {"action": "release"}]' 2>/dev/null)
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
if echo "$touch_response" | grep -q '"sessionId"' && ! echo "$touch_response" | grep -q '"error"'; then
|
|
2246
|
-
echo "${log_prefix} ✅ Successfully clicked '$text' at ($x, $y) - WDA Touch"
|
|
2247
|
-
else
|
|
2248
|
-
# Method 3: Try dragFromToForDuration with same point (acts as tap)
|
|
2249
|
-
echo "${log_prefix} Trying Method 3: Drag endpoint as tap..."
|
|
2250
|
-
local drag_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/dragfromtoforduration" \
|
|
2251
|
-
-H 'Content-Type: application/json' \
|
|
2252
|
-
-d '{"fromX": '$x', "fromY": '$y', "toX": '$x', "toY": '$y', "duration": 0.1}' 2>/dev/null)
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
if echo "$drag_response" | grep -q '"sessionId"' && ! echo "$drag_response" | grep -q '"error"'; then
|
|
2256
|
-
echo "${log_prefix} ✅ Successfully clicked '$text' at ($x, $y) - Drag Method"
|
|
2257
|
-
else
|
|
2258
|
-
# Method 4: Try element-based approach with known coordinates
|
|
2259
|
-
echo "${log_prefix} Trying Method 4: Element at coordinates..."
|
|
2260
|
-
local element_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/element" \
|
|
2261
|
-
-H 'Content-Type: application/json' \
|
|
2262
|
-
-d '{"using": "xpath", "value": "//XCUIElementTypeApplication"}' 2>/dev/null)
|
|
2263
|
-
|
|
2264
|
-
if echo "$element_response" | grep -q '"ELEMENT"'; then
|
|
2265
|
-
local element_id=$(echo "$element_response" | python3 -c "
|
|
2266
|
-
import sys, json
|
|
2267
|
-
try:
|
|
2268
|
-
data = json.load(sys.stdin)
|
|
2269
|
-
print(data.get('value', {}).get('ELEMENT', ''))
|
|
2270
|
-
except: pass" 2>/dev/null)
|
|
2271
|
-
|
|
2272
|
-
if [ -n "$element_id" ]; then
|
|
2273
|
-
local click_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/element/$element_id/click" \
|
|
2274
|
-
-H 'Content-Type: application/json' \
|
|
2275
|
-
-d "{\"x\": $x, \"y\": $y}" 2>/dev/null)
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
if echo "$click_response" | grep -q '"sessionId"' && ! echo "$click_response" | grep -q '"error"'; then
|
|
2279
|
-
echo "${log_prefix} ✅ Successfully clicked '$text' at ($x, $y) - Element Click"
|
|
2280
|
-
else
|
|
2281
|
-
echo "${log_prefix} ❌ All click methods failed for '$text' at ($x, $y)"
|
|
2282
|
-
echo "${log_prefix} 💡 WebDriverAgent version may not support standard tap endpoints"
|
|
2283
|
-
echo "${log_prefix} 🔍 Debug info - WDA Response: $tap_response"
|
|
2284
|
-
fi
|
|
2285
|
-
fi
|
|
2286
|
-
else
|
|
2287
|
-
echo "${log_prefix} ❌ All click methods failed - no supported endpoints found"
|
|
2288
|
-
echo "${log_prefix} 💡 Check WebDriverAgent version and capabilities"
|
|
2289
|
-
fi
|
|
2290
|
-
fi
|
|
2291
|
-
fi
|
|
2292
|
-
fi
|
|
2293
|
-
else
|
|
2294
|
-
# Advanced fallback: Try element detection with multiple strategies
|
|
2295
|
-
echo "${log_prefix} Unknown element '$text' - using advanced detection..."
|
|
2296
|
-
|
|
2297
|
-
# Strategy 1: Try direct accessibility ID (most common)
|
|
2298
|
-
local element_found=false
|
|
2299
|
-
local element_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/elements" \
|
|
2300
|
-
-H "Content-Type: application/json" \
|
|
2301
|
-
-d "{\"using\": \"accessibility id\", \"value\": \"$text\"}" 2>/dev/null)
|
|
2302
|
-
|
|
2303
|
-
if echo "$element_response" | grep -q '"ELEMENT"'; then
|
|
2304
|
-
echo "${log_prefix} ✅ Found via accessibility ID"
|
|
2305
|
-
# Extract element ID and try multiple interaction methods
|
|
2306
|
-
local element_id=$(echo "$element_response" | python3 -c "
|
|
2307
|
-
import sys, json
|
|
2308
|
-
try:
|
|
2309
|
-
data = json.load(sys.stdin)
|
|
2310
|
-
elements = data.get('value', [])
|
|
2311
|
-
if elements: print(elements[0].get('ELEMENT', ''))
|
|
2312
|
-
except: pass" 2>/dev/null)
|
|
2313
|
-
|
|
2314
|
-
if [ -n "$element_id" ]; then
|
|
2315
|
-
echo "${log_prefix} Element ID: $element_id"
|
|
2316
|
-
|
|
2317
|
-
# Method 1: Try direct element click
|
|
2318
|
-
echo "${log_prefix} Trying element click method..."
|
|
2319
|
-
local click_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/element/$element_id/click" \
|
|
2320
|
-
-H 'Content-Type: application/json' \
|
|
2321
|
-
-d '{}' 2>/dev/null)
|
|
2322
|
-
|
|
2323
|
-
if echo "$click_response" | grep -q '"sessionId"' && ! echo "$click_response" | grep -q '"error"'; then
|
|
2324
|
-
echo "${log_prefix} ✅ Successfully clicked '$text' (Element click method)"
|
|
2325
|
-
element_found=true
|
|
2326
|
-
else
|
|
2327
|
-
# Method 2: Get element coordinates and use W3C Actions
|
|
2328
|
-
echo "${log_prefix} Trying coordinate extraction..."
|
|
2329
|
-
local rect_response=$(curl -s -X GET "http://$host:$WDA_PORT/session/$session/element/$element_id/rect" \
|
|
2330
|
-
-H 'Content-Type: application/json' 2>/dev/null)
|
|
2331
|
-
|
|
2332
|
-
local center_x=$(echo "$rect_response" | python3 -c "
|
|
2333
|
-
import sys, json
|
|
2334
|
-
try:
|
|
2335
|
-
data = json.load(sys.stdin)
|
|
2336
|
-
rect = data.get('value', {})
|
|
2337
|
-
x = rect.get('x', 0)
|
|
2338
|
-
width = rect.get('width', 0)
|
|
2339
|
-
print(int(x + width/2))
|
|
2340
|
-
except: print('0')" 2>/dev/null)
|
|
2341
|
-
|
|
2342
|
-
local center_y=$(echo "$rect_response" | python3 -c "
|
|
2343
|
-
import sys, json
|
|
2344
|
-
try:
|
|
2345
|
-
data = json.load(sys.stdin)
|
|
2346
|
-
rect = data.get('value', {})
|
|
2347
|
-
y = rect.get('y', 0)
|
|
2348
|
-
height = rect.get('height', 0)
|
|
2349
|
-
print(int(y + height/2))
|
|
2350
|
-
except: print('0')" 2>/dev/null)
|
|
2351
|
-
|
|
2352
|
-
if [[ "$center_x" =~ ^[0-9]+$ ]] && [[ "$center_y" =~ ^[0-9]+$ ]] && [ "$center_x" -gt 0 ] && [ "$center_y" -gt 0 ]; then
|
|
2353
|
-
echo "${log_prefix} Element center: ($center_x, $center_y)"
|
|
2354
|
-
|
|
2355
|
-
# Use W3C Actions with extracted coordinates
|
|
2356
|
-
local actions_payload='{
|
|
2357
|
-
"actions": [
|
|
2358
|
-
{
|
|
2359
|
-
"type": "pointer",
|
|
2360
|
-
"id": "finger1",
|
|
2361
|
-
"parameters": {"pointerType": "touch"},
|
|
2362
|
-
"actions": [
|
|
2363
|
-
{"type": "pointerMove", "duration": 0, "x": '$center_x', "y": '$center_y'},
|
|
2364
|
-
{"type": "pointerDown", "button": 0},
|
|
2365
|
-
{"type": "pause", "duration": 100},
|
|
2366
|
-
{"type": "pointerUp", "button": 0}
|
|
2367
|
-
]
|
|
2368
|
-
}
|
|
2369
|
-
]
|
|
2370
|
-
}'
|
|
2371
|
-
|
|
2372
|
-
local tap_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/actions" \
|
|
2373
|
-
-H 'Content-Type: application/json' \
|
|
2374
|
-
-d "$actions_payload" 2>/dev/null)
|
|
2375
|
-
|
|
2376
|
-
if echo "$tap_response" | grep -q '"sessionId"' && ! echo "$tap_response" | grep -q '"error"'; then
|
|
2377
|
-
echo "${log_prefix} ✅ Successfully clicked '$text' at ($center_x, $center_y) (Coordinate method)"
|
|
2378
|
-
element_found=true
|
|
2379
|
-
else
|
|
2380
|
-
# Method 3: Fallback to Enter key (for text fields)
|
|
2381
|
-
echo "${log_prefix} Trying Enter key fallback..."
|
|
2382
|
-
local key_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/element/$element_id/value" \
|
|
2383
|
-
-H 'Content-Type: application/json' \
|
|
2384
|
-
-d '{"value":["\n"]}' 2>/dev/null)
|
|
2385
|
-
|
|
2386
|
-
if echo "$key_response" | grep -q '"sessionId"' && ! echo "$key_response" | grep -q '"error"'; then
|
|
2387
|
-
echo "${log_prefix} ✅ Successfully activated '$text' (Enter key method)"
|
|
2388
|
-
element_found=true
|
|
2389
|
-
fi
|
|
2390
|
-
fi
|
|
2391
|
-
else
|
|
2392
|
-
echo "${log_prefix} ❌ Could not extract valid coordinates from element"
|
|
2393
|
-
fi
|
|
2394
|
-
fi
|
|
2395
|
-
else
|
|
2396
|
-
echo "${log_prefix} ❌ Could not extract element ID"
|
|
2397
|
-
fi
|
|
2398
|
-
fi
|
|
2399
|
-
|
|
2400
|
-
# Strategy 2: Try partial name matching if not found
|
|
2401
|
-
if [ "$element_found" = "false" ]; then
|
|
2402
|
-
local name_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/elements" \
|
|
2403
|
-
-H "Content-Type: application/json" \
|
|
2404
|
-
-d "{\"using\": \"name\", \"value\": \"$text\"}" 2>/dev/null)
|
|
2405
|
-
|
|
2406
|
-
if echo "$name_response" | grep -q '"ELEMENT"'; then
|
|
2407
|
-
echo "${log_prefix} ✅ Found via name attribute"
|
|
2408
|
-
local element_id=$(echo "$name_response" | python3 -c "
|
|
2409
|
-
import sys, json
|
|
2410
|
-
try:
|
|
2411
|
-
data = json.load(sys.stdin)
|
|
2412
|
-
elements = data.get('value', [])
|
|
2413
|
-
if elements: print(elements[0].get('ELEMENT', ''))
|
|
2414
|
-
except: pass" 2>/dev/null)
|
|
2415
|
-
|
|
2416
|
-
if [ -n "$element_id" ]; then
|
|
2417
|
-
local key_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/element/$element_id/value" \
|
|
2418
|
-
-H 'Content-Type: application/json' \
|
|
2419
|
-
-d '{"value":["\n"]}' 2>/dev/null)
|
|
2420
|
-
|
|
2421
|
-
if echo "$key_response" | grep -q '"sessionId"' && ! echo "$key_response" | grep -q '"error"'; then
|
|
2422
|
-
echo "${log_prefix} ✅ Successfully activated '$text' (Enter key method)"
|
|
2423
|
-
element_found=true
|
|
2424
|
-
fi
|
|
2425
|
-
fi
|
|
2426
|
-
fi
|
|
2427
|
-
fi
|
|
2428
|
-
|
|
2429
|
-
# Strategy 3: Suggest coordinate-based approach if all fails
|
|
2430
|
-
if [ "$element_found" = "false" ]; then
|
|
2431
|
-
echo "${log_prefix} ❌ Element '$text' not found with any method"
|
|
2432
|
-
echo "${log_prefix} 💡 Try using coordinates: click 200,300"
|
|
2433
|
-
echo "${log_prefix} 💡 Or add '$text' to the coordinate mapping system"
|
|
2434
|
-
fi
|
|
2435
|
-
fi
|
|
2436
|
-
else
|
|
2437
|
-
echo "${log_prefix} ❌ No session available"
|
|
2438
|
-
fi
|
|
2439
|
-
fi
|
|
2440
|
-
;;
|
|
2441
|
-
|
|
2442
|
-
"longpress"|"longclick")
|
|
2443
|
-
echo "${log_prefix} CASE: Longpress matched!"
|
|
2444
|
-
local coords="$cmd_args"
|
|
2445
|
-
local duration="1.5" # Default 1.5 seconds
|
|
2446
|
-
|
|
2447
|
-
# Check if duration is specified (e.g., "100,200,2.0")
|
|
2448
|
-
if [[ "$coords" =~ ^[0-9]+,[0-9]+,[0-9.]+$ ]]; then
|
|
2449
|
-
duration=$(echo "$coords" | cut -d',' -f3)
|
|
2450
|
-
coords=$(echo "$coords" | cut -d',' -f1,2)
|
|
2451
|
-
fi
|
|
2452
|
-
|
|
2453
|
-
if [[ "$coords" =~ ^[0-9]+,[0-9]+$ ]]; then
|
|
2454
|
-
# Direct coordinates - use W3C Actions API for more reliable long press
|
|
2455
|
-
local x=$(echo "$coords" | cut -d',' -f1)
|
|
2456
|
-
local y=$(echo "$coords" | cut -d',' -f2)
|
|
2457
|
-
echo "${log_prefix} Long pressing at coordinates: ($x, $y) for ${duration}s"
|
|
2458
|
-
|
|
2459
|
-
local session=$(get_device_session "$device_name")
|
|
2460
|
-
if [ -n "$session" ]; then
|
|
2461
|
-
# Use W3C Actions API for precise long press
|
|
2462
|
-
local duration_ms=$(python3 -c "print(int(float('$duration') * 1000))" 2>/dev/null || echo "1500")
|
|
2463
|
-
local actions_payload='{
|
|
2464
|
-
"actions": [
|
|
2465
|
-
{
|
|
2466
|
-
"type": "pointer",
|
|
2467
|
-
"id": "finger1",
|
|
2468
|
-
"parameters": {"pointerType": "touch"},
|
|
2469
|
-
"actions": [
|
|
2470
|
-
{"type": "pointerMove", "duration": 0, "x": '$x', "y": '$y'},
|
|
2471
|
-
{"type": "pointerDown", "button": 0},
|
|
2472
|
-
{"type": "pause", "duration": '$duration_ms'},
|
|
2473
|
-
{"type": "pointerUp", "button": 0}
|
|
2474
|
-
]
|
|
2475
|
-
}
|
|
2476
|
-
]
|
|
2477
|
-
}'
|
|
2478
|
-
|
|
2479
|
-
local response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/actions" \
|
|
2480
|
-
-H 'Content-Type: application/json' \
|
|
2481
|
-
-d "$actions_payload")
|
|
2482
|
-
|
|
2483
|
-
if echo "$response" | grep -q '"sessionId"' && ! echo "$response" | grep -q '"error"'; then
|
|
2484
|
-
echo "${log_prefix} ✅ Long pressed at ($x, $y) for ${duration}s"
|
|
2485
|
-
else
|
|
2486
|
-
# Fallback to touchAndHold endpoint
|
|
2487
|
-
echo "${log_prefix} Trying fallback touchAndHold..."
|
|
2488
|
-
response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/touchAndHold" \
|
|
2489
|
-
-H 'Content-Type: application/json' \
|
|
2490
|
-
-d "{\"x\":$x,\"y\":$y,\"duration\":$duration}")
|
|
2491
|
-
|
|
2492
|
-
if echo "$response" | grep -q '"sessionId"' && ! echo "$response" | grep -q '"error"'; then
|
|
2493
|
-
echo "${log_prefix} ✅ Long pressed at ($x, $y)"
|
|
2494
|
-
else
|
|
2495
|
-
echo "${log_prefix} ❌ Long press failed"
|
|
2496
|
-
echo "${log_prefix} Response: $response"
|
|
2497
|
-
fi
|
|
2498
|
-
fi
|
|
2499
|
-
else
|
|
2500
|
-
echo "${log_prefix} ❌ No session available"
|
|
2501
|
-
fi
|
|
2502
|
-
else
|
|
2503
|
-
# Search for element by text - PROFESSIONAL APPIUM WAY
|
|
2504
|
-
local text="$coords"
|
|
2505
|
-
echo "${log_prefix} Searching for element to long press: '$text'"
|
|
2506
|
-
|
|
2507
|
-
local session=$(get_device_session "$device_name")
|
|
2508
|
-
if [ -n "$session" ]; then
|
|
2509
|
-
# Use helper function to find element
|
|
2510
|
-
local search_result=$(find_element_by_text "$session" "$host" "$text" "$device_name")
|
|
2511
|
-
local element_id=$(echo "$search_result" | cut -d'|' -f1)
|
|
2512
|
-
local found_strategy=$(echo "$search_result" | cut -d'|' -f2)
|
|
2513
|
-
|
|
2514
|
-
if [ -n "$element_id" ]; then
|
|
2515
|
-
echo "${log_prefix} ✅ Found element using: $found_strategy"
|
|
2516
|
-
|
|
2517
|
-
# Get element center coordinates
|
|
2518
|
-
local coordinates=$(get_element_center "$session" "$host" "$element_id")
|
|
2519
|
-
local x=$(echo "$coordinates" | cut -d',' -f1)
|
|
2520
|
-
local y=$(echo "$coordinates" | cut -d',' -f2)
|
|
2521
|
-
|
|
2522
|
-
if [[ "$x" =~ ^[0-9]+$ ]] && [[ "$y" =~ ^[0-9]+$ ]] && [ "$x" -gt 0 ] && [ "$y" -gt 0 ]; then
|
|
2523
|
-
echo "${log_prefix} Element center: ($x, $y)"
|
|
2524
|
-
|
|
2525
|
-
# Use W3C Actions API for precise long press
|
|
2526
|
-
local duration_ms=$(python3 -c "print(int(float('$duration') * 1000))" 2>/dev/null || echo "1500")
|
|
2527
|
-
local actions_payload='{
|
|
2528
|
-
"actions": [
|
|
2529
|
-
{
|
|
2530
|
-
"type": "pointer",
|
|
2531
|
-
"id": "finger1",
|
|
2532
|
-
"parameters": {"pointerType": "touch"},
|
|
2533
|
-
"actions": [
|
|
2534
|
-
{"type": "pointerMove", "duration": 0, "x": '$x', "y": '$y'},
|
|
2535
|
-
{"type": "pointerDown", "button": 0},
|
|
2536
|
-
{"type": "pause", "duration": '$duration_ms'},
|
|
2537
|
-
{"type": "pointerUp", "button": 0}
|
|
2538
|
-
]
|
|
2539
|
-
}
|
|
2540
|
-
]
|
|
2541
|
-
}'
|
|
2542
|
-
|
|
2543
|
-
local longpress_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/actions" \
|
|
2544
|
-
-H 'Content-Type: application/json' \
|
|
2545
|
-
-d "$actions_payload")
|
|
2546
|
-
|
|
2547
|
-
if echo "$longpress_response" | grep -q '"sessionId"' && ! echo "$longpress_response" | grep -q '"error"'; then
|
|
2548
|
-
echo "${log_prefix} ✅ Successfully long pressed '$text' for ${duration}s"
|
|
2549
|
-
else
|
|
2550
|
-
echo "${log_prefix} ❌ Long press failed on '$text'"
|
|
2551
|
-
echo "${log_prefix} Response: $longpress_response"
|
|
2552
|
-
fi
|
|
2553
|
-
else
|
|
2554
|
-
echo "${log_prefix} ❌ Could not get valid element coordinates"
|
|
2555
|
-
fi
|
|
2556
|
-
else
|
|
2557
|
-
echo "${log_prefix} ❌ Element '$text' not found with any locator strategy"
|
|
2558
|
-
fi
|
|
2559
|
-
else
|
|
2560
|
-
echo "${log_prefix} ❌ No session available"
|
|
2561
|
-
fi
|
|
2562
|
-
fi
|
|
2563
|
-
;;
|
|
2564
|
-
|
|
2565
|
-
"swipe"|"swipe_up"|"swipe_down"|"swipe_left"|"swipe_right"|"scroll")
|
|
2566
|
-
echo "${log_prefix} CASE: Swipe gesture matched!"
|
|
2567
|
-
local swipe_params="$cmd_args"
|
|
2568
|
-
local direction=""
|
|
2569
|
-
local start_x="" start_y="" end_x="" end_y=""
|
|
2570
|
-
local duration="300" # Default 300ms
|
|
2571
|
-
|
|
2572
|
-
# Get device screen dimensions for optimal coordinates
|
|
2573
|
-
local screen_width screen_height
|
|
2574
|
-
|
|
2575
|
-
# Device-specific coordinate profiles (fallback when session detection fails)
|
|
2576
|
-
case "$device_name" in
|
|
2577
|
-
"iphone16p")
|
|
2578
|
-
screen_width=430; screen_height=932 # iPhone 16 Pro actual resolution
|
|
2579
|
-
echo "${log_prefix} 📱 Using iPhone 16 Pro profile: ${screen_width}×${screen_height}"
|
|
2580
|
-
;;
|
|
2581
|
-
"iphone17")
|
|
2582
|
-
screen_width=402; screen_height=874 # iPhone 17 actual resolution
|
|
2583
|
-
echo "${log_prefix} 📱 Using iPhone 17 profile: ${screen_width}×${screen_height}"
|
|
2584
|
-
;;
|
|
2585
|
-
"ipad")
|
|
2586
|
-
screen_width=834; screen_height=1210 # iPad actual resolution
|
|
2587
|
-
echo "${log_prefix} 📱 Using iPad profile: ${screen_width}×${screen_height}"
|
|
2588
|
-
;;
|
|
2589
|
-
*)
|
|
2590
|
-
# Try to get from session first, then fallback
|
|
2591
|
-
if [ -n "$session" ]; then
|
|
2592
|
-
local screen_info=$(curl -s "http://$host:$WDA_PORT/session/$session/window/size" 2>/dev/null)
|
|
2593
|
-
if echo "$screen_info" | grep -q '"width"' 2>/dev/null; then
|
|
2594
|
-
screen_width=$(echo "$screen_info" | python3 -c "import sys,json; data=json.load(sys.stdin); print(data['value']['width']) if 'value' in data else print('400')" 2>/dev/null || echo "400")
|
|
2595
|
-
screen_height=$(echo "$screen_info" | python3 -c "import sys,json; data=json.load(sys.stdin); print(data['value']['height']) if 'value' in data else print('800')" 2>/dev/null || echo "800")
|
|
2596
|
-
echo "${log_prefix} 📱 Detected via session: ${screen_width}×${screen_height}"
|
|
2597
|
-
fi
|
|
2598
|
-
fi
|
|
2599
|
-
;;
|
|
2600
|
-
esac
|
|
2601
|
-
|
|
2602
|
-
# Default fallback dimensions
|
|
2603
|
-
screen_width=${screen_width:-400}
|
|
2604
|
-
screen_height=${screen_height:-800}
|
|
2605
|
-
|
|
2606
|
-
# Parse swipe parameters
|
|
2607
|
-
if [[ "$swipe_params" =~ ^(up|down|left|right)$ ]]; then
|
|
2608
|
-
# Device-optimized directional swipes based on actual screen dimensions
|
|
2609
|
-
direction="$swipe_params"
|
|
2610
|
-
local center_x=$((screen_width / 2))
|
|
2611
|
-
local quarter_height=$((screen_height / 4))
|
|
2612
|
-
local three_quarter_height=$((screen_height * 3 / 4))
|
|
2613
|
-
local left_edge=$((screen_width / 8))
|
|
2614
|
-
local right_edge=$((screen_width * 7 / 8))
|
|
2615
|
-
local mid_height=$((screen_height / 2))
|
|
2616
|
-
|
|
2617
|
-
case "$direction" in
|
|
2618
|
-
"up")
|
|
2619
|
-
# Scroll up - start from 75% height, swipe to 25% height
|
|
2620
|
-
start_x=$center_x; start_y=$three_quarter_height; end_x=$center_x; end_y=$quarter_height
|
|
2621
|
-
duration="600" # Slower for better recognition
|
|
2622
|
-
echo "${log_prefix} 👆 Optimized swipe up for ${screen_width}×${screen_height}"
|
|
2623
|
-
;;
|
|
2624
|
-
"down")
|
|
2625
|
-
# Scroll down - start from 25% height, swipe to 75% height
|
|
2626
|
-
start_x=$center_x; start_y=$quarter_height; end_x=$center_x; end_y=$three_quarter_height
|
|
2627
|
-
duration="600" # Slower for better recognition
|
|
2628
|
-
echo "${log_prefix} 👇 Optimized swipe down for ${screen_width}×${screen_height}"
|
|
2629
|
-
;;
|
|
2630
|
-
"left")
|
|
2631
|
-
# Swipe left - from right edge to left edge at mid height
|
|
2632
|
-
start_x=$right_edge; start_y=$mid_height; end_x=$left_edge; end_y=$mid_height
|
|
2633
|
-
duration="500" # Medium speed for page transitions
|
|
2634
|
-
echo "${log_prefix} 👈 Optimized swipe left for ${screen_width}×${screen_height}"
|
|
2635
|
-
;;
|
|
2636
|
-
"right")
|
|
2637
|
-
# Swipe right - from left edge to right edge at mid height
|
|
2638
|
-
start_x=$left_edge; start_y=$mid_height; end_x=$right_edge; end_y=$mid_height
|
|
2639
|
-
duration="500" # Medium speed for page transitions
|
|
2640
|
-
echo "${log_prefix} 👉 Optimized swipe right for ${screen_width}×${screen_height}"
|
|
2641
|
-
;;
|
|
2642
|
-
esac
|
|
2643
|
-
elif [[ "$swipe_params" =~ ^[0-9]+,[0-9]+,[0-9]+,[0-9]+$ ]]; then
|
|
2644
|
-
# Custom coordinates: "x1,y1,x2,y2"
|
|
2645
|
-
start_x=$(echo "$swipe_params" | cut -d',' -f1)
|
|
2646
|
-
start_y=$(echo "$swipe_params" | cut -d',' -f2)
|
|
2647
|
-
end_x=$(echo "$swipe_params" | cut -d',' -f3)
|
|
2648
|
-
end_y=$(echo "$swipe_params" | cut -d',' -f4)
|
|
2649
|
-
direction="custom"
|
|
2650
|
-
elif [[ "$swipe_params" =~ ^[0-9]+,[0-9]+,[0-9]+,[0-9]+,[0-9]+$ ]]; then
|
|
2651
|
-
# Custom coordinates with duration: "x1,y1,x2,y2,duration_ms"
|
|
2652
|
-
start_x=$(echo "$swipe_params" | cut -d',' -f1)
|
|
2653
|
-
start_y=$(echo "$swipe_params" | cut -d',' -f2)
|
|
2654
|
-
end_x=$(echo "$swipe_params" | cut -d',' -f3)
|
|
2655
|
-
end_y=$(echo "$swipe_params" | cut -d',' -f4)
|
|
2656
|
-
duration=$(echo "$swipe_params" | cut -d',' -f5)
|
|
2657
|
-
direction="custom"
|
|
2658
|
-
else
|
|
2659
|
-
echo "${log_prefix} ❌ Invalid swipe format. Use: swipe up/down/left/right OR swipe x1,y1,x2,y2[,duration]"
|
|
2660
|
-
echo "${log_prefix} Examples: swipe up, swipe 100,200,300,400, swipe 50,50,350,600,500"
|
|
2661
|
-
continue
|
|
2662
|
-
fi
|
|
2663
|
-
|
|
2664
|
-
local session=$(get_device_session "$device_name")
|
|
2665
|
-
if [ -n "$session" ]; then
|
|
2666
|
-
if [ "$direction" = "custom" ]; then
|
|
2667
|
-
echo "${log_prefix} 👆 Swiping custom from ($start_x,$start_y) to ($end_x,$end_y) [${duration}ms]"
|
|
2668
|
-
else
|
|
2669
|
-
echo "${log_prefix} 👆 Swiping ${direction} from ($start_x,$start_y) to ($end_x,$end_y) [${duration}ms] - Screen ${screen_width}×${screen_height}"
|
|
2670
|
-
fi
|
|
2671
|
-
|
|
2672
|
-
# Use W3C Actions API for precise swipe gesture
|
|
2673
|
-
local swipe_payload='{
|
|
2674
|
-
"actions": [
|
|
2675
|
-
{
|
|
2676
|
-
"type": "pointer",
|
|
2677
|
-
"id": "finger1",
|
|
2678
|
-
"parameters": {"pointerType": "touch"},
|
|
2679
|
-
"actions": [
|
|
2680
|
-
{"type": "pointerMove", "duration": 0, "x": '$start_x', "y": '$start_y'},
|
|
2681
|
-
{"type": "pointerDown", "button": 0},
|
|
2682
|
-
{"type": "pointerMove", "duration": '$duration', "x": '$end_x', "y": '$end_y'},
|
|
2683
|
-
{"type": "pointerUp", "button": 0}
|
|
2684
|
-
]
|
|
2685
|
-
}
|
|
2686
|
-
]
|
|
2687
|
-
}'
|
|
2688
|
-
|
|
2689
|
-
local response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/actions" \
|
|
2690
|
-
-H 'Content-Type: application/json' \
|
|
2691
|
-
-d "$swipe_payload" 2>/dev/null)
|
|
2692
|
-
|
|
2693
|
-
if echo "$response" | grep -q '"sessionId"' && ! echo "$response" | grep -q '"error"'; then
|
|
2694
|
-
echo "${log_prefix} ✅ Swipe gesture successful"
|
|
2695
|
-
else
|
|
2696
|
-
# Fallback: Try WebDriverAgent drag endpoint
|
|
2697
|
-
echo "${log_prefix} Trying fallback drag method..."
|
|
2698
|
-
local duration_sec=$(python3 -c "print(float('$duration')/1000)" 2>/dev/null || echo "0.3")
|
|
2699
|
-
local drag_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/dragfromtoforduration" \
|
|
2700
|
-
-H 'Content-Type: application/json' \
|
|
2701
|
-
-d "{\"fromX\": $start_x, \"fromY\": $start_y, \"toX\": $end_x, \"toY\": $end_y, \"duration\": $duration_sec}" 2>/dev/null)
|
|
2702
|
-
|
|
2703
|
-
if echo "$drag_response" | grep -q '"sessionId"' && ! echo "$drag_response" | grep -q '"error"'; then
|
|
2704
|
-
echo "${log_prefix} ✅ Swipe gesture successful (drag method)"
|
|
2705
|
-
else
|
|
2706
|
-
echo "${log_prefix} ❌ Swipe gesture failed"
|
|
2707
|
-
echo "${log_prefix} Response: $response"
|
|
2708
|
-
fi
|
|
2709
|
-
fi
|
|
2710
|
-
else
|
|
2711
|
-
echo "${log_prefix} ❌ No session available"
|
|
2712
|
-
fi
|
|
2713
|
-
;;
|
|
2714
|
-
|
|
2715
|
-
"getLocators"|"getlocators"|"locators")
|
|
2716
|
-
[ "$DEBUG" = "1" ] && echo "${log_prefix} 🔍 Getting all locators from current screen"
|
|
2717
|
-
|
|
2718
|
-
local session=$(get_device_session "$device_name")
|
|
2719
|
-
if [ -n "$session" ]; then
|
|
2720
|
-
# Check what app is currently active (don't disrupt it)
|
|
2721
|
-
[ "$DEBUG" = "1" ] && echo "${log_prefix} 📱 Checking active app..."
|
|
2722
|
-
local active_app_info=$(curl -s -X GET "http://$host:$WDA_PORT/wda/activeAppInfo" 2>/dev/null)
|
|
2723
|
-
local bundle_id=$(echo "$active_app_info" | python3 -c "
|
|
2724
|
-
import sys, json
|
|
2725
|
-
try:
|
|
2726
|
-
data = json.load(sys.stdin)
|
|
2727
|
-
print(data.get('value', {}).get('bundleId', 'com.apple.springboard'))
|
|
2728
|
-
except:
|
|
2729
|
-
print('com.apple.springboard')
|
|
2730
|
-
" 2>/dev/null)
|
|
2731
|
-
|
|
2732
|
-
if [ "$bundle_id" = "com.apple.springboard" ]; then
|
|
2733
|
-
[ "$DEBUG" = "1" ] && echo "${log_prefix} ℹ️ Currently on Home Screen"
|
|
2734
|
-
else
|
|
2735
|
-
[ "$DEBUG" = "1" ] && echo "${log_prefix} ℹ️ Active app: $bundle_id"
|
|
2736
|
-
fi
|
|
2737
|
-
|
|
2738
|
-
[ "$DEBUG" = "1" ] && echo "${log_prefix} 📱 Fetching page source..."
|
|
2739
|
-
local page_source=$(curl -s -X GET "http://$host:$WDA_PORT/session/$session/source" 2>/dev/null)
|
|
2740
|
-
|
|
2741
|
-
if [ -n "$page_source" ] && ! echo "$page_source" | grep -q '"error"'; then
|
|
2742
|
-
[ "$DEBUG" = "1" ] && echo "${log_prefix} 🔍 Extracting locators..."
|
|
2743
|
-
|
|
2744
|
-
# Extract all locator information using Python
|
|
2745
|
-
local locators=$(echo "$page_source" | python3 -c "
|
|
2746
|
-
import sys
|
|
2747
|
-
import json
|
|
2748
|
-
import xml.etree.ElementTree as ET
|
|
2749
|
-
|
|
2750
|
-
try:
|
|
2751
|
-
data = json.load(sys.stdin)
|
|
2752
|
-
xml_str = data.get('value', '')
|
|
2753
|
-
|
|
2754
|
-
if not xml_str:
|
|
2755
|
-
print('❌ No page source found')
|
|
2756
|
-
sys.exit(1)
|
|
2757
|
-
|
|
2758
|
-
root = ET.fromstring(xml_str)
|
|
2759
|
-
locators = []
|
|
2760
|
-
|
|
2761
|
-
# Extract locators from all elements
|
|
2762
|
-
for elem in root.iter():
|
|
2763
|
-
locator_info = {}
|
|
2764
|
-
|
|
2765
|
-
# Get element type
|
|
2766
|
-
elem_type = elem.tag
|
|
2767
|
-
locator_info['type'] = elem_type
|
|
2768
|
-
|
|
2769
|
-
# Get name/label
|
|
2770
|
-
name = elem.get('name', '')
|
|
2771
|
-
label = elem.get('label', '')
|
|
2772
|
-
value = elem.get('value', '')
|
|
2773
|
-
|
|
2774
|
-
if name:
|
|
2775
|
-
locator_info['name'] = name
|
|
2776
|
-
if label:
|
|
2777
|
-
locator_info['label'] = label
|
|
2778
|
-
if value:
|
|
2779
|
-
locator_info['value'] = value
|
|
2780
|
-
|
|
2781
|
-
# Get accessible property
|
|
2782
|
-
accessible = elem.get('accessible', 'false')
|
|
2783
|
-
locator_info['accessible'] = accessible
|
|
2784
|
-
|
|
2785
|
-
# Get visible property
|
|
2786
|
-
visible = elem.get('visible', 'false')
|
|
2787
|
-
locator_info['visible'] = visible
|
|
2788
|
-
|
|
2789
|
-
# Get enabled property
|
|
2790
|
-
enabled = elem.get('enabled', 'true')
|
|
2791
|
-
locator_info['enabled'] = enabled
|
|
2792
|
-
|
|
2793
|
-
# Get coordinates
|
|
2794
|
-
x = elem.get('x', '')
|
|
2795
|
-
y = elem.get('y', '')
|
|
2796
|
-
width = elem.get('width', '')
|
|
2797
|
-
height = elem.get('height', '')
|
|
2798
|
-
|
|
2799
|
-
if x and y and width and height:
|
|
2800
|
-
locator_info['coordinates'] = f'({x},{y}) {width}x{height}'
|
|
2801
|
-
|
|
2802
|
-
# Only add if we have meaningful locator information
|
|
2803
|
-
if name or label or value:
|
|
2804
|
-
locators.append(locator_info)
|
|
2805
|
-
|
|
2806
|
-
# Print formatted output - only in DEBUG mode
|
|
2807
|
-
import os
|
|
2808
|
-
debug = os.environ.get('DEBUG', '0') == '1'
|
|
2809
|
-
|
|
2810
|
-
if locators:
|
|
2811
|
-
if debug:
|
|
2812
|
-
import sys
|
|
2813
|
-
print(f'\\n📋 Found {len(locators)} interactive elements:\\n', file=sys.stderr)
|
|
2814
|
-
for i, loc in enumerate(locators, 1):
|
|
2815
|
-
# Build single line output with Name first, better formatting
|
|
2816
|
-
parts = []
|
|
2817
|
-
|
|
2818
|
-
# Start with number and Name (most important for clicking)
|
|
2819
|
-
name = loc.get('name', '')
|
|
2820
|
-
if name:
|
|
2821
|
-
parts.append(f'{i:3d}. 📍 {name:40s}')
|
|
2822
|
-
else:
|
|
2823
|
-
parts.append(f'{i:3d}. 📍 {\"(no name)\":40s}')
|
|
2824
|
-
|
|
2825
|
-
# Add Type (shortened for readability)
|
|
2826
|
-
elem_type = loc.get('type', 'unknown').replace('XCUIElementType', '')
|
|
2827
|
-
parts.append(f'[{elem_type:15s}]')
|
|
2828
|
-
|
|
2829
|
-
# Add Label if different from Name
|
|
2830
|
-
label = loc.get('label', '')
|
|
2831
|
-
if label and label != name:
|
|
2832
|
-
parts.append(f'Label:{label:30s}' if len(label) <= 30 else f'Label:{label[:27]}...')
|
|
2833
|
-
|
|
2834
|
-
# Add Value if present
|
|
2835
|
-
value = loc.get('value', '')
|
|
2836
|
-
if value and value != name and value != label:
|
|
2837
|
-
parts.append(f'Val:{value:20s}' if len(value) <= 20 else f'Val:{value[:17]}...')
|
|
2838
|
-
|
|
2839
|
-
# Add Position
|
|
2840
|
-
if loc.get('coordinates'):
|
|
2841
|
-
parts.append(f'@ {loc[\"coordinates\"]}')
|
|
2842
|
-
|
|
2843
|
-
# Add status flags (compact)
|
|
2844
|
-
flags = []
|
|
2845
|
-
if loc.get('visible') == 'true':
|
|
2846
|
-
flags.append('✓Vis')
|
|
2847
|
-
if loc.get('enabled') == 'true':
|
|
2848
|
-
flags.append('✓En')
|
|
2849
|
-
if flags:
|
|
2850
|
-
parts.append(' '.join(flags))
|
|
2851
|
-
|
|
2852
|
-
print(' │ '.join(parts), file=sys.stderr)
|
|
2853
|
-
|
|
2854
|
-
# Output JSON to stdout (always, regardless of DEBUG)
|
|
2855
|
-
import json
|
|
2856
|
-
print(json.dumps(locators))
|
|
2857
|
-
|
|
2858
|
-
except Exception as e:
|
|
2859
|
-
import os
|
|
2860
|
-
import sys
|
|
2861
|
-
debug = os.environ.get('DEBUG', '0') == '1'
|
|
2862
|
-
if debug:
|
|
2863
|
-
print(f'❌ Error parsing page source: {e}', file=sys.stderr)
|
|
2864
|
-
sys.exit(1)
|
|
2865
|
-
" 2>&1)
|
|
2866
|
-
|
|
2867
|
-
if [ $? -eq 0 ]; then
|
|
2868
|
-
echo "${log_prefix} $locators"
|
|
2869
|
-
echo "${log_prefix} ✅ Locators extraction complete"
|
|
2870
|
-
else
|
|
2871
|
-
echo "${log_prefix} ❌ Failed to extract locators: $locators"
|
|
2872
|
-
fi
|
|
2873
|
-
else
|
|
2874
|
-
echo "${log_prefix} ❌ Failed to get page source"
|
|
2875
|
-
fi
|
|
2876
|
-
else
|
|
2877
|
-
echo "${log_prefix} ❌ No session available"
|
|
2878
|
-
fi
|
|
2879
|
-
;;
|
|
2880
|
-
|
|
2881
|
-
*)
|
|
2882
|
-
# Text input - use existing persistent session (like original script)
|
|
2883
|
-
echo "${log_prefix} CASE: Default text input"
|
|
2884
|
-
echo "${log_prefix} Sending text: $command"
|
|
2885
|
-
|
|
2886
|
-
# Convert text to JSON array format (properly escape for shell safety)
|
|
2887
|
-
local json_array=$(python3 -c "
|
|
2888
|
-
import json
|
|
2889
|
-
import sys
|
|
2890
|
-
text = sys.argv[1] if len(sys.argv) > 1 else ''
|
|
2891
|
-
print(json.dumps([text]))" "$command" 2>/dev/null)
|
|
2892
|
-
|
|
2893
|
-
if [ -n "$json_array" ]; then
|
|
2894
|
-
# Use the persistent session for this device
|
|
2895
|
-
local session=$(get_device_session "$device_name")
|
|
2896
|
-
|
|
2897
|
-
if [ -n "$session" ]; then
|
|
2898
|
-
# Send text using existing session (preserves current app context)
|
|
2899
|
-
local response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/keys" \
|
|
2900
|
-
-H 'Content-Type: application/json' \
|
|
2901
|
-
-d "{\"value\":$json_array}")
|
|
2902
|
-
|
|
2903
|
-
if echo "$response" | grep -q '"sessionId"' && ! echo "$response" | grep -q '"error"'; then
|
|
2904
|
-
echo "${log_prefix} ✅ Text sent"
|
|
2905
|
-
|
|
2906
|
-
# Auto-press Enter after text with minimal delay
|
|
2907
|
-
sleep 0.1
|
|
2908
|
-
local enter_array=$(python3 -c "import json; print(json.dumps(['\\n']))" 2>/dev/null)
|
|
2909
|
-
local enter_response=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/keys" \
|
|
2910
|
-
-H 'Content-Type: application/json' \
|
|
2911
|
-
-d "{\"value\":$enter_array}")
|
|
2912
|
-
|
|
2913
|
-
if echo "$enter_response" | grep -q '"sessionId"' && ! echo "$enter_response" | grep -q '"error"'; then
|
|
2914
|
-
echo "${log_prefix} ✅ Auto-pressed Enter"
|
|
2915
|
-
fi
|
|
2916
|
-
else
|
|
2917
|
-
echo "${log_prefix} ❌ Text send failed"
|
|
2918
|
-
fi
|
|
2919
|
-
|
|
2920
|
-
# DO NOT clean up session - keep it for future commands!
|
|
2921
|
-
else
|
|
2922
|
-
echo "${log_prefix} ❌ No session available"
|
|
2923
|
-
fi
|
|
2924
|
-
else
|
|
2925
|
-
echo "${log_prefix} ❌ JSON encoding failed"
|
|
2926
|
-
fi
|
|
2927
|
-
;;
|
|
2928
|
-
esac
|
|
2929
|
-
}
|
|
2930
|
-
|
|
2931
|
-
# Check current appearance mode (helper function)
|
|
2932
|
-
check_appearance_mode() {
|
|
2933
|
-
local device="$1"
|
|
2934
|
-
|
|
2935
|
-
local device_details
|
|
2936
|
-
device_details=$(get_device_details "$device")
|
|
2937
|
-
if [ $? -ne 0 ]; then
|
|
2938
|
-
print_color "$RED" "❌ Device not found: $device"
|
|
2939
|
-
return 1
|
|
2940
|
-
fi
|
|
2941
|
-
|
|
2942
|
-
local host=$(echo "$device_details" | cut -d',' -f1)
|
|
2943
|
-
local session=$(get_device_session "$device")
|
|
2944
|
-
|
|
2945
|
-
if [ -n "$session" ]; then
|
|
2946
|
-
# Try to get current appearance info via WDA
|
|
2947
|
-
local appearance_info=$(curl -s -X GET "http://$host:$WDA_PORT/session/$session/appium/device/current_activity" 2>/dev/null)
|
|
2948
|
-
|
|
2949
|
-
if echo "$appearance_info" | grep -q "dark\|Dark"; then
|
|
2950
|
-
print_color "$BLUE" "🌙 $device: Currently in Dark Mode"
|
|
2951
|
-
elif echo "$appearance_info" | grep -q "light\|Light"; then
|
|
2952
|
-
print_color "$YELLOW" "☀️ $device: Currently in Light Mode"
|
|
2953
|
-
else
|
|
2954
|
-
print_color "$CYAN" "❓ $device: Appearance mode unknown"
|
|
2955
|
-
fi
|
|
2956
|
-
else
|
|
2957
|
-
print_color "$RED" "❌ $device: No session available to check appearance"
|
|
2958
|
-
fi
|
|
2959
|
-
}
|
|
2960
|
-
|
|
2961
|
-
# Dark Mode / Light Mode Toggle
|
|
2962
|
-
toggle_dark_mode() {
|
|
2963
|
-
local device="$1"
|
|
2964
|
-
local mode="${2:-toggle}" # toggle, dark, light
|
|
2965
|
-
|
|
2966
|
-
local device_details
|
|
2967
|
-
device_details=$(get_device_details "$device")
|
|
2968
|
-
if [ $? -ne 0 ]; then
|
|
2969
|
-
print_color "$RED" "❌ Device not found: $device"
|
|
2970
|
-
return 1
|
|
2971
|
-
fi
|
|
2972
|
-
|
|
2973
|
-
local host=$(echo "$device_details" | cut -d',' -f1)
|
|
2974
|
-
local udid=$(echo "$device_details" | cut -d',' -f2)
|
|
2975
|
-
|
|
2976
|
-
# Method 1: Try using xcrun devicectl (iOS 17+)
|
|
2977
|
-
if command -v xcrun >/dev/null 2>&1; then
|
|
2978
|
-
print_color "$CYAN" "$device: Trying xcrun devicectl for appearance..."
|
|
2979
|
-
|
|
2980
|
-
case "$mode" in
|
|
2981
|
-
"dark")
|
|
2982
|
-
local result=$(xcrun devicectl device install app --device "$udid" --json 2>/dev/null || echo "not_supported")
|
|
2983
|
-
if [ "$result" != "not_supported" ]; then
|
|
2984
|
-
# Try using xcrun to set appearance (if supported)
|
|
2985
|
-
xcrun simctl ui "$udid" appearance dark 2>/dev/null && {
|
|
2986
|
-
print_color "$GREEN" "✅ $device: Set to Dark Mode via xcrun"
|
|
2987
|
-
return 0
|
|
2988
|
-
}
|
|
2989
|
-
fi
|
|
2990
|
-
;;
|
|
2991
|
-
"light")
|
|
2992
|
-
xcrun simctl ui "$udid" appearance light 2>/dev/null && {
|
|
2993
|
-
print_color "$GREEN" "✅ $device: Set to Light Mode via xcrun"
|
|
2994
|
-
return 0
|
|
2995
|
-
}
|
|
2996
|
-
;;
|
|
2997
|
-
esac
|
|
2998
|
-
fi
|
|
2999
|
-
|
|
3000
|
-
# Method 2: Try using idevice tools with terminal commands
|
|
3001
|
-
if command -v idevice-app-runner >/dev/null 2>&1 || command -v ios-deploy >/dev/null 2>&1; then
|
|
3002
|
-
print_color "$CYAN" "$device: Trying iOS terminal commands..."
|
|
3003
|
-
|
|
3004
|
-
# Use shortcuts if available (requires Shortcuts app)
|
|
3005
|
-
local shortcuts_bundle="com.apple.shortcuts"
|
|
3006
|
-
local session=$(get_device_session "$device")
|
|
3007
|
-
|
|
3008
|
-
if [ -n "$session" ]; then
|
|
3009
|
-
# Try to create a simple shortcut command via WDA
|
|
3010
|
-
local shortcut_url="http://$host:$WDA_PORT/session/$session/url"
|
|
3011
|
-
|
|
3012
|
-
case "$mode" in
|
|
3013
|
-
"dark")
|
|
3014
|
-
# Use URL scheme to trigger shortcuts or settings
|
|
3015
|
-
local result=$(curl -s -X POST "$shortcut_url" \
|
|
3016
|
-
-H "Content-Type: application/json" \
|
|
3017
|
-
-d '{"url": "prefs:root=DISPLAY&path=APPEARANCE"}' 2>/dev/null)
|
|
3018
|
-
|
|
3019
|
-
if echo "$result" | grep -q '"sessionId"'; then
|
|
3020
|
-
sleep 1
|
|
3021
|
-
# Try to tap the Dark option
|
|
3022
|
-
local tap_result=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/tap/0" \
|
|
3023
|
-
-H "Content-Type: application/json" \
|
|
3024
|
-
-d '{"x": 100, "y": 400}' 2>/dev/null)
|
|
3025
|
-
|
|
3026
|
-
print_color "$GREEN" "✅ $device: Attempted Dark Mode via Settings URL"
|
|
3027
|
-
execute_wda_command "$device" "home" >/dev/null
|
|
3028
|
-
return 0
|
|
3029
|
-
fi
|
|
3030
|
-
;;
|
|
3031
|
-
"light")
|
|
3032
|
-
local result=$(curl -s -X POST "$shortcut_url" \
|
|
3033
|
-
-H "Content-Type: application/json" \
|
|
3034
|
-
-d '{"url": "prefs:root=DISPLAY&path=APPEARANCE"}' 2>/dev/null)
|
|
3035
|
-
|
|
3036
|
-
if echo "$result" | grep -q '"sessionId"'; then
|
|
3037
|
-
sleep 1
|
|
3038
|
-
# Try to tap the Light option
|
|
3039
|
-
local tap_result=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/tap/0" \
|
|
3040
|
-
-H "Content-Type: application/json" \
|
|
3041
|
-
-d '{"x": 100, "y": 300}' 2>/dev/null)
|
|
3042
|
-
|
|
3043
|
-
print_color "$GREEN" "✅ $device: Attempted Light Mode via Settings URL"
|
|
3044
|
-
execute_wda_command "$device" "home" >/dev/null
|
|
3045
|
-
return 0
|
|
3046
|
-
fi
|
|
3047
|
-
;;
|
|
3048
|
-
esac
|
|
3049
|
-
fi
|
|
3050
|
-
fi
|
|
3051
|
-
|
|
3052
|
-
# Method 3: Enhanced WDA approach with better element detection
|
|
3053
|
-
local session=$(get_device_session "$device")
|
|
3054
|
-
if [ -n "$session" ]; then
|
|
3055
|
-
print_color "$CYAN" "$device: Using enhanced WDA method..."
|
|
3056
|
-
|
|
3057
|
-
# Open Settings using URL scheme first
|
|
3058
|
-
local settings_result=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/url" \
|
|
3059
|
-
-H "Content-Type: application/json" \
|
|
3060
|
-
-d '{"url": "App-prefs:DISPLAY"}' 2>/dev/null)
|
|
3061
|
-
|
|
3062
|
-
if ! echo "$settings_result" | grep -q '"sessionId"'; then
|
|
3063
|
-
# Fallback: Launch Settings app manually
|
|
3064
|
-
execute_wda_command "$device" "launch com.apple.Preferences" >/dev/null
|
|
3065
|
-
sleep 2
|
|
3066
|
-
|
|
3067
|
-
# Try to find and tap Display & Brightness
|
|
3068
|
-
local search_url="http://$host:$WDA_PORT/session/$session/elements"
|
|
3069
|
-
local elements_result=$(curl -s -X POST "$search_url" \
|
|
3070
|
-
-H "Content-Type: application/json" \
|
|
3071
|
-
-d '{"using": "partial link text", "value": "Display"}' 2>/dev/null)
|
|
3072
|
-
|
|
3073
|
-
if ! echo "$elements_result" | grep -q '"ELEMENT"'; then
|
|
3074
|
-
# Try alternative search methods
|
|
3075
|
-
elements_result=$(curl -s -X POST "$search_url" \
|
|
3076
|
-
-H "Content-Type: application/json" \
|
|
3077
|
-
-d '{"using": "accessibility id", "value": "Display & Brightness"}' 2>/dev/null)
|
|
3078
|
-
fi
|
|
3079
|
-
|
|
3080
|
-
if echo "$elements_result" | grep -q '"ELEMENT"'; then
|
|
3081
|
-
local element_id=$(echo "$elements_result" | grep -o '"ELEMENT":"[^"]*"' | head -1 | cut -d'"' -f4)
|
|
3082
|
-
curl -s -X POST "http://$host:$WDA_PORT/session/$session/element/$element_id/click" >/dev/null
|
|
3083
|
-
sleep 1
|
|
3084
|
-
fi
|
|
3085
|
-
fi
|
|
3086
|
-
|
|
3087
|
-
sleep 2
|
|
3088
|
-
|
|
3089
|
-
# Now try to find and click the appearance option
|
|
3090
|
-
case "$mode" in
|
|
3091
|
-
"dark")
|
|
3092
|
-
# Look for Dark mode option with multiple strategies
|
|
3093
|
-
local dark_elements=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/elements" \
|
|
3094
|
-
-H "Content-Type: application/json" \
|
|
3095
|
-
-d '{"using": "accessibility id", "value": "Dark"}' 2>/dev/null)
|
|
3096
|
-
|
|
3097
|
-
if echo "$dark_elements" | grep -q '"ELEMENT"'; then
|
|
3098
|
-
local dark_id=$(echo "$dark_elements" | grep -o '"ELEMENT":"[^"]*"' | head -1 | cut -d'"' -f4)
|
|
3099
|
-
curl -s -X POST "http://$host:$WDA_PORT/session/$session/element/$dark_id/click" >/dev/null
|
|
3100
|
-
print_color "$GREEN" "✅ $device: Switched to Dark Mode"
|
|
3101
|
-
else
|
|
3102
|
-
# Fallback: Try coordinates (approximate location of Dark option)
|
|
3103
|
-
curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/tap/0" \
|
|
3104
|
-
-H "Content-Type: application/json" \
|
|
3105
|
-
-d '{"x": 60, "y": 450}' >/dev/null
|
|
3106
|
-
print_color "$YELLOW" "⚠️ $device: Attempted Dark Mode (coordinate fallback)"
|
|
3107
|
-
fi
|
|
3108
|
-
;;
|
|
3109
|
-
"light")
|
|
3110
|
-
local light_elements=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/elements" \
|
|
3111
|
-
-H "Content-Type: application/json" \
|
|
3112
|
-
-d '{"using": "accessibility id", "value": "Light"}' 2>/dev/null)
|
|
3113
|
-
|
|
3114
|
-
if echo "$light_elements" | grep -q '"ELEMENT"'; then
|
|
3115
|
-
local light_id=$(echo "$light_elements" | grep -o '"ELEMENT":"[^"]*"' | head -1 | cut -d'"' -f4)
|
|
3116
|
-
curl -s -X POST "http://$host:$WDA_PORT/session/$session/element/$light_id/click" >/dev/null
|
|
3117
|
-
print_color "$GREEN" "✅ $device: Switched to Light Mode"
|
|
3118
|
-
else
|
|
3119
|
-
# Fallback: Try coordinates (approximate location of Light option)
|
|
3120
|
-
curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/tap/0" \
|
|
3121
|
-
-H "Content-Type: application/json" \
|
|
3122
|
-
-d '{"x": 60, "y": 350}' >/dev/null
|
|
3123
|
-
print_color "$YELLOW" "⚠️ $device: Attempted Light Mode (coordinate fallback)"
|
|
3124
|
-
fi
|
|
3125
|
-
;;
|
|
3126
|
-
*)
|
|
3127
|
-
# Toggle mode - try to detect current and switch
|
|
3128
|
-
print_color "$YELLOW" "⚠️ $device: Toggle mode - manual selection may be needed"
|
|
3129
|
-
;;
|
|
3130
|
-
esac
|
|
3131
|
-
|
|
3132
|
-
# Go back to home
|
|
3133
|
-
sleep 1
|
|
3134
|
-
execute_wda_command "$device" "home" >/dev/null
|
|
3135
|
-
return 0
|
|
3136
|
-
else
|
|
3137
|
-
print_color "$RED" "❌ $device: No WDA session available"
|
|
3138
|
-
return 1
|
|
3139
|
-
fi
|
|
3140
|
-
}
|
|
3141
|
-
|
|
3142
|
-
# Screen Rotation Functions
|
|
3143
|
-
rotate_screen() {
|
|
3144
|
-
local device="$1"
|
|
3145
|
-
local orientation="${2:-toggle}" # portrait, landscape-left, landscape-right, portrait-upside-down, toggle
|
|
3146
|
-
|
|
3147
|
-
local device_details
|
|
3148
|
-
device_details=$(get_device_details "$device")
|
|
3149
|
-
if [ $? -ne 0 ]; then
|
|
3150
|
-
print_color "$RED" "❌ Device not found: $device"
|
|
3151
|
-
return 1
|
|
3152
|
-
fi
|
|
3153
|
-
|
|
3154
|
-
local host=$(echo "$device_details" | cut -d',' -f1)
|
|
3155
|
-
local udid=$(echo "$device_details" | cut -d',' -f2)
|
|
3156
|
-
|
|
3157
|
-
# Method 1: Try using xcrun devicectl for rotation (iOS 17+)
|
|
3158
|
-
if command -v xcrun >/dev/null 2>&1; then
|
|
3159
|
-
case "$orientation" in
|
|
3160
|
-
"portrait"|"1")
|
|
3161
|
-
if xcrun simctl status_bar "$udid" override --orientation portrait 2>/dev/null; then
|
|
3162
|
-
print_color "$GREEN" "✅ $device: Rotated to Portrait via xcrun"
|
|
3163
|
-
return 0
|
|
3164
|
-
fi
|
|
3165
|
-
;;
|
|
3166
|
-
"landscape-left"|"landscape"|"3")
|
|
3167
|
-
if xcrun simctl status_bar "$udid" override --orientation landscapeLeft 2>/dev/null; then
|
|
3168
|
-
print_color "$GREEN" "✅ $device: Rotated to Landscape Left via xcrun"
|
|
3169
|
-
return 0
|
|
3170
|
-
fi
|
|
3171
|
-
;;
|
|
3172
|
-
"landscape-right"|"4")
|
|
3173
|
-
if xcrun simctl status_bar "$udid" override --orientation landscapeRight 2>/dev/null; then
|
|
3174
|
-
print_color "$GREEN" "✅ $device: Rotated to Landscape Right via xcrun"
|
|
3175
|
-
return 0
|
|
3176
|
-
fi
|
|
3177
|
-
;;
|
|
3178
|
-
esac
|
|
3179
|
-
fi
|
|
3180
|
-
|
|
3181
|
-
# Method 2: Use WDA orientation API
|
|
3182
|
-
local session=$(get_device_session "$device")
|
|
3183
|
-
if [ -n "$session" ]; then
|
|
3184
|
-
local orientation_url="http://$host:$WDA_PORT/session/$session/orientation"
|
|
3185
|
-
|
|
3186
|
-
# Get current orientation first
|
|
3187
|
-
local current_orientation=$(curl -s -X GET "$orientation_url" | grep -o '"value":"[^"]*"' | cut -d'"' -f4)
|
|
3188
|
-
|
|
3189
|
-
# Map orientation names to WDA values
|
|
3190
|
-
local orientation_value
|
|
3191
|
-
case "$orientation" in
|
|
3192
|
-
"portrait"|"1")
|
|
3193
|
-
orientation_value="PORTRAIT"
|
|
3194
|
-
;;
|
|
3195
|
-
"landscape-left"|"landscape"|"3")
|
|
3196
|
-
orientation_value="LANDSCAPE"
|
|
3197
|
-
;;
|
|
3198
|
-
"landscape-right"|"4")
|
|
3199
|
-
orientation_value="UIA_DEVICE_ORIENTATION_LANDSCAPERIGHT"
|
|
3200
|
-
;;
|
|
3201
|
-
"portrait-upside-down"|"2")
|
|
3202
|
-
orientation_value="UIA_DEVICE_ORIENTATION_PORTRAIT_UPSIDEDOWN"
|
|
3203
|
-
;;
|
|
3204
|
-
"toggle"|*)
|
|
3205
|
-
# Toggle between portrait and landscape
|
|
3206
|
-
case "$current_orientation" in
|
|
3207
|
-
"PORTRAIT") orientation_value="LANDSCAPE" ;;
|
|
3208
|
-
"LANDSCAPE") orientation_value="PORTRAIT" ;;
|
|
3209
|
-
*) orientation_value="LANDSCAPE" ;; # Default to landscape if unknown
|
|
3210
|
-
esac
|
|
3211
|
-
;;
|
|
3212
|
-
esac
|
|
3213
|
-
|
|
3214
|
-
# Set orientation via WDA
|
|
3215
|
-
local response=$(curl -s -X POST "$orientation_url" \
|
|
3216
|
-
-H "Content-Type: application/json" \
|
|
3217
|
-
-d "{\"orientation\": \"$orientation_value\"}")
|
|
3218
|
-
|
|
3219
|
-
if echo "$response" | grep -q '"value":null\|"sessionId"'; then
|
|
3220
|
-
print_color "$GREEN" "✅ $device: Rotated to $orientation_value"
|
|
3221
|
-
return 0
|
|
3222
|
-
else
|
|
3223
|
-
print_color "$YELLOW" "⚠️ $device: Rotation may not be supported or device is locked"
|
|
3224
|
-
fi
|
|
3225
|
-
fi
|
|
3226
|
-
|
|
3227
|
-
# Method 3: Try using idevice tools if available
|
|
3228
|
-
if command -v idevice-app-runner >/dev/null 2>&1; then
|
|
3229
|
-
print_color "$CYAN" "$device: Trying idevice rotation tools..."
|
|
3230
|
-
# Note: Most idevice tools don't support rotation directly
|
|
3231
|
-
print_color "$YELLOW" "⚠️ $device: idevice rotation not directly supported"
|
|
3232
|
-
fi
|
|
3233
|
-
|
|
3234
|
-
print_color "$RED" "❌ $device: Could not rotate screen - may need manual rotation"
|
|
3235
|
-
return 1
|
|
3236
|
-
}
|
|
3237
|
-
|
|
3238
|
-
# Lock/Unlock Rotation
|
|
3239
|
-
toggle_rotation_lock() {
|
|
3240
|
-
local device="$1"
|
|
3241
|
-
local lock_state="${2:-toggle}" # lock, unlock, toggle
|
|
3242
|
-
|
|
3243
|
-
local device_details
|
|
3244
|
-
device_details=$(get_device_details "$device")
|
|
3245
|
-
if [ $? -ne 0 ]; then
|
|
3246
|
-
print_color "$RED" "❌ Device not found: $device"
|
|
3247
|
-
return 1
|
|
3248
|
-
fi
|
|
3249
|
-
|
|
3250
|
-
local host=$(echo "$device_details" | cut -d',' -f1)
|
|
3251
|
-
local udid=$(echo "$device_details" | cut -d',' -f2)
|
|
3252
|
-
|
|
3253
|
-
# Method 1: Try using terminal/xcrun commands
|
|
3254
|
-
if command -v xcrun >/dev/null 2>&1; then
|
|
3255
|
-
# Note: xcrun doesn't directly support rotation lock for real devices
|
|
3256
|
-
print_color "$CYAN" "$device: Rotation lock via terminal not directly supported for physical devices"
|
|
3257
|
-
fi
|
|
3258
|
-
|
|
3259
|
-
# Method 2: Use WDA to access Control Center
|
|
3260
|
-
local session=$(get_device_session "$device")
|
|
3261
|
-
if [ -n "$session" ]; then
|
|
3262
|
-
print_color "$CYAN" "$device: Opening Control Center to toggle rotation lock..."
|
|
3263
|
-
|
|
3264
|
-
# Open Control Center by swiping from top-right (iOS 12+) or bottom (older iOS)
|
|
3265
|
-
# Try modern swipe first (from top-right corner)
|
|
3266
|
-
local swipe_result=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/dragfromtoforduration" \
|
|
3267
|
-
-H "Content-Type: application/json" \
|
|
3268
|
-
-d '{"fromX": 350, "fromY": 10, "toX": 350, "toY": 200, "duration": 0.5}' 2>/dev/null)
|
|
3269
|
-
|
|
3270
|
-
if ! echo "$swipe_result" | grep -q '"sessionId"'; then
|
|
3271
|
-
# Fallback: Try swipe up from bottom (older iOS)
|
|
3272
|
-
swipe_result=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/dragfromtoforduration" \
|
|
3273
|
-
-H "Content-Type: application/json" \
|
|
3274
|
-
-d '{"fromX": 200, "fromY": 800, "toX": 200, "toY": 400, "duration": 0.5}' 2>/dev/null)
|
|
3275
|
-
fi
|
|
3276
|
-
|
|
3277
|
-
sleep 2
|
|
3278
|
-
|
|
3279
|
-
# Look for rotation lock button with multiple strategies
|
|
3280
|
-
local search_url="http://$host:$WDA_PORT/session/$session/elements"
|
|
3281
|
-
|
|
3282
|
-
# Strategy 1: Find by accessibility identifier
|
|
3283
|
-
local rotation_elements=$(curl -s -X POST "$search_url" \
|
|
3284
|
-
-H "Content-Type: application/json" \
|
|
3285
|
-
-d '{"using": "accessibility id", "value": "portrait-orientation-lock"}' 2>/dev/null)
|
|
3286
|
-
|
|
3287
|
-
if ! echo "$rotation_elements" | grep -q '"ELEMENT"'; then
|
|
3288
|
-
# Strategy 2: Find by name
|
|
3289
|
-
rotation_elements=$(curl -s -X POST "$search_url" \
|
|
3290
|
-
-H "Content-Type: application/json" \
|
|
3291
|
-
-d '{"using": "name", "value": "Portrait Orientation Lock"}' 2>/dev/null)
|
|
3292
|
-
fi
|
|
3293
|
-
|
|
3294
|
-
if ! echo "$rotation_elements" | grep -q '"ELEMENT"'; then
|
|
3295
|
-
# Strategy 3: Find by partial name match
|
|
3296
|
-
rotation_elements=$(curl -s -X POST "$search_url" \
|
|
3297
|
-
-H "Content-Type: application/json" \
|
|
3298
|
-
-d '{"using": "partial link text", "value": "orientation"}' 2>/dev/null)
|
|
3299
|
-
fi
|
|
3300
|
-
|
|
3301
|
-
if echo "$rotation_elements" | grep -q '"ELEMENT"'; then
|
|
3302
|
-
local rotation_id=$(echo "$rotation_elements" | grep -o '"ELEMENT":"[^"]*"' | head -1 | cut -d'"' -f4)
|
|
3303
|
-
local tap_result=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/element/$rotation_id/click" 2>/dev/null)
|
|
3304
|
-
|
|
3305
|
-
if echo "$tap_result" | grep -q '"sessionId"'; then
|
|
3306
|
-
print_color "$GREEN" "✅ $device: Toggled rotation lock"
|
|
3307
|
-
else
|
|
3308
|
-
print_color "$YELLOW" "⚠️ $device: Rotation lock button found but tap may have failed"
|
|
3309
|
-
fi
|
|
3310
|
-
else
|
|
3311
|
-
# Fallback: Try common coordinates for rotation lock button
|
|
3312
|
-
print_color "$YELLOW" "$device: Using coordinate fallback for rotation lock..."
|
|
3313
|
-
|
|
3314
|
-
# Common positions for rotation lock in Control Center
|
|
3315
|
-
local coords=("60,300" "60,350" "80,320" "100,340")
|
|
3316
|
-
|
|
3317
|
-
for coord in "${coords[@]}"; do
|
|
3318
|
-
local x=$(echo "$coord" | cut -d',' -f1)
|
|
3319
|
-
local y=$(echo "$coord" | cut -d',' -f2)
|
|
3320
|
-
|
|
3321
|
-
local tap_result=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/tap/0" \
|
|
3322
|
-
-H "Content-Type: application/json" \
|
|
3323
|
-
-d "{\"x\": $x, \"y\": $y}" 2>/dev/null)
|
|
3324
|
-
|
|
3325
|
-
if echo "$tap_result" | grep -q '"sessionId"'; then
|
|
3326
|
-
print_color "$YELLOW" "⚠️ $device: Attempted rotation lock toggle at ($x,$y)"
|
|
3327
|
-
break
|
|
3328
|
-
fi
|
|
3329
|
-
done
|
|
3330
|
-
fi
|
|
3331
|
-
|
|
3332
|
-
# Close Control Center by tapping outside or swiping down
|
|
3333
|
-
sleep 1
|
|
3334
|
-
local close_result=$(curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/tap/0" \
|
|
3335
|
-
-H "Content-Type: application/json" \
|
|
3336
|
-
-d '{"x": 200, "y": 600}' 2>/dev/null)
|
|
3337
|
-
|
|
3338
|
-
# Alternative: Swipe down to close Control Center
|
|
3339
|
-
if ! echo "$close_result" | grep -q '"sessionId"'; then
|
|
3340
|
-
curl -s -X POST "http://$host:$WDA_PORT/session/$session/wda/dragfromtoforduration" \
|
|
3341
|
-
-H "Content-Type: application/json" \
|
|
3342
|
-
-d '{"fromX": 200, "fromY": 200, "toX": 200, "toY": 600, "duration": 0.3}' >/dev/null
|
|
3343
|
-
fi
|
|
3344
|
-
|
|
3345
|
-
return 0
|
|
3346
|
-
else
|
|
3347
|
-
print_color "$RED" "❌ $device: No WDA session available for rotation lock"
|
|
3348
|
-
return 1
|
|
3349
|
-
fi
|
|
3350
|
-
}
|
|
3351
|
-
|
|
3352
|
-
# Discover and connect devices
|
|
3353
|
-
discover_and_connect_devices() {
|
|
3354
|
-
print_color "$BLUE" "🔍 Discovering iOS devices..."
|
|
3355
|
-
echo ""
|
|
3356
|
-
|
|
3357
|
-
local ALL_DEVICES=($(get_all_device_names))
|
|
3358
|
-
|
|
3359
|
-
if [ ${#ALL_DEVICES[@]} -eq 0 ]; then
|
|
3360
|
-
print_color "$RED" "❌ No devices found in $CONFIG_FILE"
|
|
3361
|
-
exit 1
|
|
3362
|
-
fi
|
|
3363
|
-
|
|
3364
|
-
print_color "$YELLOW" "Configured devices:"
|
|
3365
|
-
for device in "${ALL_DEVICES[@]}"; do
|
|
3366
|
-
local details=$(get_device_details "$device")
|
|
3367
|
-
local host=$(echo "$details" | cut -d',' -f1)
|
|
3368
|
-
print_color "$CYAN" " • $device ($host)"
|
|
3369
|
-
done
|
|
3370
|
-
echo ""
|
|
3371
|
-
|
|
3372
|
-
print_color "$YELLOW" "Testing connections in parallel..."
|
|
3373
|
-
echo ""
|
|
3374
|
-
|
|
3375
|
-
CONNECTED_DEVICES=()
|
|
3376
|
-
local failed_devices=()
|
|
3377
|
-
|
|
3378
|
-
# Create temp file for parallel results
|
|
3379
|
-
local results_file="/tmp/wireless_connect_results_$$.txt"
|
|
3380
|
-
: > "$results_file"
|
|
3381
|
-
|
|
3382
|
-
# Test all devices in parallel
|
|
3383
|
-
local pids=()
|
|
3384
|
-
for device in "${ALL_DEVICES[@]}"; do
|
|
3385
|
-
{
|
|
3386
|
-
local details=$(get_device_details "$device")
|
|
3387
|
-
local host=$(echo "$details" | cut -d',' -f1)
|
|
3388
|
-
print_color "$CYAN" "📱 Testing $device ($host):"
|
|
3389
|
-
|
|
3390
|
-
if check_device_connectivity "$device"; then
|
|
3391
|
-
# Use lock for atomic write
|
|
3392
|
-
local lock_file="${results_file}.lock"
|
|
3393
|
-
while ! mkdir "$lock_file" 2>/dev/null; do
|
|
3394
|
-
sleep 0.01
|
|
3395
|
-
done
|
|
3396
|
-
echo "SUCCESS:$device" >> "$results_file"
|
|
3397
|
-
rmdir "$lock_file"
|
|
3398
|
-
print_color "$GREEN" " ✅ $device ready"
|
|
3399
|
-
else
|
|
3400
|
-
# Use lock for atomic write
|
|
3401
|
-
local lock_file="${results_file}.lock"
|
|
3402
|
-
while ! mkdir "$lock_file" 2>/dev/null; do
|
|
3403
|
-
sleep 0.01
|
|
3404
|
-
done
|
|
3405
|
-
echo "FAILED:$device" >> "$results_file"
|
|
3406
|
-
rmdir "$lock_file"
|
|
3407
|
-
print_color "$RED" " ❌ $device failed to connect"
|
|
3408
|
-
fi
|
|
3409
|
-
echo ""
|
|
3410
|
-
} &
|
|
3411
|
-
pids+=($!)
|
|
3412
|
-
done
|
|
3413
|
-
|
|
3414
|
-
# Wait for all connectivity checks to complete
|
|
3415
|
-
for pid in "${pids[@]}"; do
|
|
3416
|
-
wait "$pid"
|
|
3417
|
-
done
|
|
3418
|
-
|
|
3419
|
-
# Parse results
|
|
3420
|
-
while IFS=':' read -r status device; do
|
|
3421
|
-
if [ "$status" = "SUCCESS" ]; then
|
|
3422
|
-
CONNECTED_DEVICES+=("$device")
|
|
3423
|
-
elif [ "$status" = "FAILED" ]; then
|
|
3424
|
-
failed_devices+=("$device")
|
|
3425
|
-
fi
|
|
3426
|
-
done < "$results_file"
|
|
3427
|
-
|
|
3428
|
-
# Cleanup temp file
|
|
3429
|
-
rm -f "$results_file" "${results_file}.lock" 2>/dev/null
|
|
3430
|
-
|
|
3431
|
-
print_color "$YELLOW" "📋 Connection Summary:"
|
|
3432
|
-
echo ""
|
|
3433
|
-
|
|
3434
|
-
if [ ${#CONNECTED_DEVICES[@]} -gt 0 ]; then
|
|
3435
|
-
print_color "$GREEN" "🎉 ${#CONNECTED_DEVICES[@]} device(s) ready!"
|
|
3436
|
-
print_color "$CYAN" "Connected: $(IFS=', '; echo "${CONNECTED_DEVICES[*]}")"
|
|
3437
|
-
if [ ${#failed_devices[@]} -gt 0 ]; then
|
|
3438
|
-
print_color "$YELLOW" "⚠️ Offline: $(IFS=', '; echo "${failed_devices[*]}")"
|
|
3439
|
-
fi
|
|
3440
|
-
else
|
|
3441
|
-
print_color "$RED" "❌ No devices are online"
|
|
3442
|
-
exit 1
|
|
3443
|
-
fi
|
|
3444
|
-
|
|
3445
|
-
echo ""
|
|
3446
|
-
}
|
|
3447
|
-
|
|
3448
|
-
# Send command to multiple devices
|
|
3449
|
-
send_to_all_devices() {
|
|
3450
|
-
local devices=("$@")
|
|
3451
|
-
local pids=()
|
|
3452
|
-
|
|
3453
|
-
print_color "$YELLOW" "🚀 Executing on ${#devices[@]} device(s)..."
|
|
3454
|
-
echo ""
|
|
3455
|
-
|
|
3456
|
-
for device in "${devices[@]}"; do
|
|
3457
|
-
{
|
|
3458
|
-
execute_wda_command "$device" "$TEXT"
|
|
3459
|
-
echo "[${device}] Completed"
|
|
3460
|
-
} &
|
|
3461
|
-
pids+=($!)
|
|
3462
|
-
print_color "$CYAN" "📱 Started: $device (PID: $!)"
|
|
3463
|
-
done
|
|
3464
|
-
|
|
3465
|
-
echo ""
|
|
3466
|
-
print_color "$YELLOW" "⏳ Waiting for completion..."
|
|
3467
|
-
|
|
3468
|
-
for pid in "${pids[@]}"; do
|
|
3469
|
-
wait "$pid"
|
|
3470
|
-
done
|
|
3471
|
-
|
|
3472
|
-
echo ""
|
|
3473
|
-
print_color "$GREEN" "✅ All devices completed!"
|
|
3474
|
-
}
|
|
3475
|
-
|
|
3476
|
-
# Cleanup all sessions and terminate WDA processes on respective devices
|
|
3477
|
-
# Graceful cleanup function (preserves device trust)
|
|
3478
|
-
graceful_cleanup() {
|
|
3479
|
-
local preserve_trust="${1:-true}"
|
|
3480
|
-
|
|
3481
|
-
if [ -f "$DEVICE_SESSIONS_FILE" ]; then
|
|
3482
|
-
print_color "$YELLOW" "🧹 Gracefully closing sessions..."
|
|
3483
|
-
|
|
3484
|
-
# Only close WebDriver sessions, don't terminate WDA processes
|
|
3485
|
-
while IFS=':' read -r device session; do
|
|
3486
|
-
if [ -n "$device" ] && [ -n "$session" ]; then
|
|
3487
|
-
local device_details
|
|
3488
|
-
device_details=$(get_device_details "$device")
|
|
3489
|
-
local host=$(echo "$device_details" | cut -d',' -f1)
|
|
3490
|
-
|
|
3491
|
-
# Just close the WebDriver session, keep WDA running
|
|
3492
|
-
curl -s -X DELETE "http://$host:$WDA_PORT/session/$session" >/dev/null 2>&1 || true
|
|
3493
|
-
echo " ✅ Session closed: $device"
|
|
3494
|
-
fi
|
|
3495
|
-
done < "$DEVICE_SESSIONS_FILE" 2>/dev/null || true
|
|
3496
|
-
|
|
3497
|
-
rm -f "$DEVICE_SESSIONS_FILE" 2>/dev/null || true
|
|
3498
|
-
|
|
3499
|
-
if [ "$preserve_trust" = "true" ]; then
|
|
3500
|
-
print_color "$GREEN" "✅ Sessions closed gracefully"
|
|
3501
|
-
print_color "$CYAN" "💡 WebDriverAgent processes left running to preserve device trust"
|
|
3502
|
-
print_color "$YELLOW" " Note: Devices should remain trusted and ready for reconnection"
|
|
3503
|
-
else
|
|
3504
|
-
# Call the full cleanup if explicitly requested
|
|
3505
|
-
cleanup_all_sessions_full
|
|
3506
|
-
fi
|
|
3507
|
-
fi
|
|
3508
|
-
}
|
|
3509
|
-
|
|
3510
|
-
# Full cleanup function (original behavior - may affect trust)
|
|
3511
|
-
cleanup_all_sessions_full() {
|
|
3512
|
-
if [ -f "$DEVICE_SESSIONS_FILE" ]; then
|
|
3513
|
-
print_color "$YELLOW" "🧹 Cleaning up sessions and stopping WDA on devices..."
|
|
3514
|
-
|
|
3515
|
-
# Collect device UDIDs for WDA termination
|
|
3516
|
-
local device_udids=()
|
|
3517
|
-
|
|
3518
|
-
while IFS=':' read -r device session; do
|
|
3519
|
-
if [ -n "$device" ] && [ -n "$session" ]; then
|
|
3520
|
-
local device_details
|
|
3521
|
-
device_details=$(get_device_details "$device")
|
|
3522
|
-
local host=$(echo "$device_details" | cut -d',' -f1)
|
|
3523
|
-
local udid=$(echo "$device_details" | cut -d',' -f2)
|
|
3524
|
-
|
|
3525
|
-
# Close WebDriver session first
|
|
3526
|
-
curl -s -X DELETE "http://$host:$WDA_PORT/session/$session" >/dev/null 2>&1 || true
|
|
3527
|
-
echo " ✅ Session closed: $device"
|
|
3528
|
-
|
|
3529
|
-
# Collect UDID for WDA process termination
|
|
3530
|
-
if [ -n "$udid" ]; then
|
|
3531
|
-
device_udids+=("$udid")
|
|
3532
|
-
fi
|
|
3533
|
-
fi
|
|
3534
|
-
done < "$DEVICE_SESSIONS_FILE" 2>/dev/null || true
|
|
3535
|
-
|
|
3536
|
-
rm -f "$DEVICE_SESSIONS_FILE" 2>/dev/null || true
|
|
3537
|
-
|
|
3538
|
-
# Terminate WDA processes for the connected devices
|
|
3539
|
-
print_color "$CYAN" "🛑 Terminating WDA processes on respective devices..."
|
|
3540
|
-
|
|
3541
|
-
if [ ${#device_udids[@]} -gt 0 ]; then
|
|
3542
|
-
for udid in "${device_udids[@]}"; do
|
|
3543
|
-
# Kill WDA process for this specific device UDID
|
|
3544
|
-
local killed_count=$(pkill -f "WebDriverAgentRunner.*$udid" 2>/dev/null; echo $?)
|
|
3545
|
-
if [ $killed_count -eq 0 ]; then
|
|
3546
|
-
echo " ✅ WDA terminated on device: ${udid:0:8}..."
|
|
3547
|
-
else
|
|
3548
|
-
echo " ℹ️ No WDA process found for: ${udid:0:8}..."
|
|
3549
|
-
fi
|
|
3550
|
-
done
|
|
3551
|
-
else
|
|
3552
|
-
# Fallback: stop all WDA processes if no specific devices found
|
|
3553
|
-
print_color "$YELLOW" " → No specific devices found, stopping all WDA processes..."
|
|
3554
|
-
pkill -f "WebDriverAgentRunner" 2>/dev/null || true
|
|
3555
|
-
fi
|
|
3556
|
-
|
|
3557
|
-
# Also terminate any lingering WDA processes (in case other scripts left them)
|
|
3558
|
-
print_color "$CYAN" "🧹 Cleaning up any remaining WDA processes..."
|
|
3559
|
-
local remaining=$(pgrep -f "WebDriverAgentRunner" 2>/dev/null | wc -l)
|
|
3560
|
-
if [ "$remaining" -gt 0 ]; then
|
|
3561
|
-
pkill -TERM -f "WebDriverAgentRunner" 2>/dev/null || true
|
|
3562
|
-
sleep 1
|
|
3563
|
-
pkill -KILL -f "WebDriverAgentRunner" 2>/dev/null || true
|
|
3564
|
-
echo " ✅ Cleaned up $remaining remaining WDA processes"
|
|
3565
|
-
else
|
|
3566
|
-
echo " ✅ No remaining WDA processes found"
|
|
3567
|
-
fi
|
|
3568
|
-
|
|
3569
|
-
print_color "$GREEN" "✅ All WDA sessions and processes terminated"
|
|
3570
|
-
print_color "$CYAN" "💡 Ready for manual testing - completely clean environment"
|
|
3571
|
-
print_color "$YELLOW" " Note: You may need to re-establish wireless connections in Xcode"
|
|
3572
|
-
fi
|
|
3573
|
-
}
|
|
3574
|
-
|
|
3575
|
-
# Legacy cleanup function name (redirects to graceful cleanup)
|
|
3576
|
-
cleanup_all_sessions() {
|
|
3577
|
-
graceful_cleanup true
|
|
3578
|
-
}
|
|
3579
|
-
|
|
3580
|
-
# Signal cleanup function (preserves device trust on interruption)
|
|
3581
|
-
cleanup_on_signal() {
|
|
3582
|
-
echo ""
|
|
3583
|
-
print_color "$YELLOW" "⚠️ Script interrupted - cleaning up gracefully..."
|
|
3584
|
-
graceful_cleanup true
|
|
3585
|
-
exit 0
|
|
3586
|
-
}
|
|
3587
|
-
|
|
3588
|
-
# Signal handlers for proper cleanup (only on interruption, not normal exit)
|
|
3589
|
-
trap cleanup_on_signal INT
|
|
3590
|
-
trap cleanup_on_signal TERM
|
|
3591
|
-
|
|
3592
|
-
# Main execution
|
|
3593
|
-
if [ -n "$DEVICE_NAME_REQUEST" ]; then
|
|
3594
|
-
# Single device mode
|
|
3595
|
-
if ! get_device_details "$DEVICE_NAME_REQUEST" >/dev/null; then
|
|
3596
|
-
print_color "$RED" "❌ Device '$DEVICE_NAME_REQUEST' not found"
|
|
3597
|
-
exit 1
|
|
3598
|
-
fi
|
|
3599
|
-
|
|
3600
|
-
print_color "$BLUE" "🎯 Single device: $DEVICE_NAME_REQUEST"
|
|
3601
|
-
if check_device_connectivity "$DEVICE_NAME_REQUEST"; then
|
|
3602
|
-
if [ -n "$TEXT" ]; then
|
|
3603
|
-
# Single command mode - execute and exit
|
|
3604
|
-
# Check if session already exists (e.g., from a launched app)
|
|
3605
|
-
existing_session=$(get_device_session "$DEVICE_NAME_REQUEST")
|
|
3606
|
-
session="$existing_session"
|
|
3607
|
-
|
|
3608
|
-
if [ -z "$session" ]; then
|
|
3609
|
-
# No existing session, create new SpringBoard session
|
|
3610
|
-
session=$(create_persistent_session "$DEVICE_NAME_REQUEST")
|
|
3611
|
-
else
|
|
3612
|
-
echo " ℹ️ Using existing session: $session"
|
|
3613
|
-
fi
|
|
3614
|
-
|
|
3615
|
-
if [ -n "$session" ]; then
|
|
3616
|
-
execute_wda_command "$DEVICE_NAME_REQUEST" "$TEXT"
|
|
3617
|
-
fi
|
|
3618
|
-
# DON'T cleanup session - keep app running!
|
|
3619
|
-
# cleanup_wda_session "$DEVICE_NAME_REQUEST"
|
|
3620
|
-
exit 0
|
|
3621
|
-
else
|
|
3622
|
-
# Single device interactive mode
|
|
3623
|
-
print_color "$YELLOW" "🔧 Initializing interactive session..."
|
|
3624
|
-
session=$(create_persistent_session "$DEVICE_NAME_REQUEST")
|
|
3625
|
-
if [ -n "$session" ]; then
|
|
3626
|
-
print_color "$GREEN" " ✅ Session ready: $DEVICE_NAME_REQUEST"
|
|
3627
|
-
else
|
|
3628
|
-
print_color "$RED" " ❌ Session failed: $DEVICE_NAME_REQUEST"
|
|
3629
|
-
exit 1
|
|
3630
|
-
fi
|
|
3631
|
-
|
|
3632
|
-
echo ""
|
|
3633
|
-
load_text_history
|
|
3634
|
-
|
|
3635
|
-
print_color "$CYAN" "🔄 Single-device Interactive Mode: $DEVICE_NAME_REQUEST"
|
|
3636
|
-
print_color "$YELLOW" " Commands: launch/kill/home/url/screenshot + text input"
|
|
3637
|
-
print_color "$YELLOW" " Type 'help' for commands • 'quit' to exit"
|
|
3638
|
-
echo ""
|
|
3639
|
-
|
|
3640
|
-
while true; do
|
|
3641
|
-
# Enable readline for this input with custom history
|
|
3642
|
-
set -o history
|
|
3643
|
-
HISTFILE="$HISTORY_FILE"
|
|
3644
|
-
HISTSIZE="$HISTORY_MAX"
|
|
3645
|
-
HISTFILESIZE="$HISTORY_MAX"
|
|
3646
|
-
|
|
3647
|
-
# Load history from file for this session
|
|
3648
|
-
if [ -f "$HISTORY_FILE" ]; then
|
|
3649
|
-
history -r "$HISTORY_FILE"
|
|
3650
|
-
fi
|
|
3651
|
-
|
|
3652
|
-
read -e -p "$DEVICE_NAME_REQUEST > " INPUT_TEXT
|
|
3653
|
-
|
|
3654
|
-
# Add any non-empty input to history (except quit commands)
|
|
3655
|
-
if [ -n "$INPUT_TEXT" ] && [[ "$INPUT_TEXT" != "quit" ]] && [[ "$INPUT_TEXT" != "exit" ]] && [[ "$INPUT_TEXT" != "q" ]] && [[ "$INPUT_TEXT" != "help" ]]; then
|
|
3656
|
-
add_to_history "$INPUT_TEXT"
|
|
3657
|
-
fi
|
|
3658
|
-
|
|
3659
|
-
# Disable history again to avoid shell command pollution
|
|
3660
|
-
set +o history
|
|
3661
|
-
unset HISTFILE
|
|
3662
|
-
|
|
3663
|
-
case "$INPUT_TEXT" in
|
|
3664
|
-
"quit"|"q"|"exit")
|
|
3665
|
-
print_color "$YELLOW" "👋 Goodbye!"
|
|
3666
|
-
cleanup_wda_session "$DEVICE_NAME_REQUEST"
|
|
3667
|
-
exit 0
|
|
3668
|
-
;;
|
|
3669
|
-
"forcecleanup"|"killwda")
|
|
3670
|
-
print_color "$YELLOW" "🛑 Force cleaning up - this may affect device trust..."
|
|
3671
|
-
cleanup_all_sessions_full
|
|
3672
|
-
print_color "$YELLOW" "👋 Goodbye!"
|
|
3673
|
-
exit 0
|
|
3674
|
-
;;
|
|
3675
|
-
"help"|"commands")
|
|
3676
|
-
print_color "$YELLOW" "📱 Available Commands:"
|
|
3677
|
-
echo ""
|
|
3678
|
-
print_color "$CYAN" "🚀 App Control:"
|
|
3679
|
-
print_color "$YELLOW" " launch <app> - Launch application"
|
|
3680
|
-
print_color "$YELLOW" " open <app> - Same as launch"
|
|
3681
|
-
print_color "$YELLOW" " kill <app> - Terminate application"
|
|
3682
|
-
print_color "$YELLOW" " close <app> - Same as kill"
|
|
3683
|
-
print_color "$YELLOW" " install <path> - Install IPA file (requires iOS tools)"
|
|
3684
|
-
print_color "$YELLOW" " uninstall <id> - Uninstall app by bundle ID (requires iOS tools)"
|
|
3685
|
-
print_color "$YELLOW" " appinfo <id> - Check app version/info by bundle ID"
|
|
3686
|
-
print_color "$YELLOW" " version <id> - Same as appinfo"
|
|
3687
|
-
print_color "$YELLOW" " listapps [word] - List all apps or search by name/word"
|
|
3688
|
-
print_color "$YELLOW" " apps [word] - Same as listapps (search any installed app)"
|
|
3689
|
-
print_color "$YELLOW" " debug - Test device connection and tools"
|
|
3690
|
-
echo ""
|
|
3691
|
-
print_color "$CYAN" "🌐 Navigation:"
|
|
3692
|
-
print_color "$YELLOW" " home - Go to home screen"
|
|
3693
|
-
print_color "$YELLOW" " back [context] - Smart back navigation (button/swipe/context)"
|
|
3694
|
-
print_color "$YELLOW" " goback - Same as back"
|
|
3695
|
-
print_color "$YELLOW" " url <url> - Open URL in Safari"
|
|
3696
|
-
print_color "$YELLOW" " screenshot - Take screenshot"
|
|
3697
|
-
print_color "$YELLOW" " click <element> - Click on element or coordinates"
|
|
3698
|
-
print_color "$YELLOW" " longpress <el> - Long press on element"
|
|
3699
|
-
print_color "$YELLOW" " swipe <dir> - Swipe up/down/left/right or custom coordinates"
|
|
3700
|
-
print_color "$YELLOW" " scroll <dir> - Same as swipe"
|
|
3701
|
-
echo ""
|
|
3702
|
-
print_color "$CYAN" "⚙️ Device Control:"
|
|
3703
|
-
print_color "$YELLOW" " restart - Restart device (wireless + USB supported)"
|
|
3704
|
-
print_color "$YELLOW" " reboot - Same as restart"
|
|
3705
|
-
print_color "$YELLOW" " shutdown - Shutdown device (USB only, wireless does restart)"
|
|
3706
|
-
print_color "$YELLOW" " poweroff - Same as shutdown"
|
|
3707
|
-
echo ""
|
|
3708
|
-
print_color "$CYAN" "🎨 Display & Appearance:"
|
|
3709
|
-
print_color "$YELLOW" " darkmode - Switch to dark mode"
|
|
3710
|
-
print_color "$YELLOW" " lightmode - Switch to light mode"
|
|
3711
|
-
print_color "$YELLOW" " theme <mode> - Set theme (dark/light/toggle)"
|
|
3712
|
-
print_color "$YELLOW" " appearance - Check current appearance mode"
|
|
3713
|
-
print_color "$CYAN" " Note: May open Settings for manual selection"
|
|
3714
|
-
echo ""
|
|
3715
|
-
print_color "$CYAN" "🔄 Screen Rotation:"
|
|
3716
|
-
print_color "$YELLOW" " rotate - Toggle orientation (portrait/landscape)"
|
|
3717
|
-
print_color "$YELLOW" " rotate <orient> - Set orientation (portrait/landscape-left/landscape-right)"
|
|
3718
|
-
print_color "$YELLOW" " rotationlock - Toggle rotation lock (opens Control Center)"
|
|
3719
|
-
print_color "$YELLOW" " lockrotation - Toggle rotation lock (same as rotationlock)"
|
|
3720
|
-
print_color "$CYAN" " Note: May require manual interaction in Control Center"
|
|
3721
|
-
echo ""
|
|
3722
|
-
print_color "$CYAN" "📝 Text & Control:"
|
|
3723
|
-
print_color "$YELLOW" " any text - Send to active field"
|
|
3724
|
-
print_color "$YELLOW" " click <x,y> - Click at coordinates (e.g., click 100,200)"
|
|
3725
|
-
print_color "$YELLOW" " click <text> - Click on button/text element"
|
|
3726
|
-
print_color "$YELLOW" " tap <x,y> - Same as click"
|
|
3727
|
-
print_color "$YELLOW" " longpress <x,y> - Long press at coordinates (1.5s default)"
|
|
3728
|
-
print_color "$YELLOW" " longpress <text>- Long press on text element"
|
|
3729
|
-
print_color "$YELLOW" " longclick <x,y> - Same as longpress"
|
|
3730
|
-
print_color "$YELLOW" " help - Show this help"
|
|
3731
|
-
print_color "$YELLOW" " quit - Exit and cleanup (preserves device trust)"
|
|
3732
|
-
print_color "$YELLOW" " forcecleanup - Force kill WDA processes (may affect trust)"
|
|
3733
|
-
echo ""
|
|
3734
|
-
print_color "$CYAN" "💡 Examples:"
|
|
3735
|
-
print_color "$YELLOW" " launch safari"
|
|
3736
|
-
print_color "$YELLOW" " url google.com"
|
|
3737
|
-
print_color "$YELLOW" " click 200,300"
|
|
3738
|
-
print_color "$YELLOW" " click 'Sign In'"
|
|
3739
|
-
print_color "$YELLOW" " longpress 'Settings'"
|
|
3740
|
-
print_color "$YELLOW" " longpress 100,150,2.0"
|
|
3741
|
-
print_color "$YELLOW" " darkmode"
|
|
3742
|
-
print_color "$YELLOW" " rotate landscape-left"
|
|
3743
|
-
print_color "$YELLOW" " rotationlock"
|
|
3744
|
-
print_color "$YELLOW" " restart"
|
|
3745
|
-
print_color "$YELLOW" " shutdown"
|
|
3746
|
-
print_color "$YELLOW" " install /path/to/app.ipa"
|
|
3747
|
-
print_color "$YELLOW" " uninstall com.example.app"
|
|
3748
|
-
print_color "$YELLOW" " appinfo com.example.app"
|
|
3749
|
-
print_color "$YELLOW" " listapps mobileiron"
|
|
3750
|
-
print_color "$YELLOW" " Hello World"
|
|
3751
|
-
print_color "$YELLOW" " kill safari"
|
|
3752
|
-
continue
|
|
3753
|
-
;;
|
|
3754
|
-
"")
|
|
3755
|
-
continue
|
|
3756
|
-
;;
|
|
3757
|
-
*)
|
|
3758
|
-
execute_wda_command "$DEVICE_NAME_REQUEST" "$INPUT_TEXT"
|
|
3759
|
-
echo ""
|
|
3760
|
-
;;
|
|
3761
|
-
esac
|
|
3762
|
-
done
|
|
3763
|
-
fi
|
|
3764
|
-
fi
|
|
3765
|
-
exit 0
|
|
3766
|
-
fi
|
|
3767
|
-
|
|
3768
|
-
# Multi-device mode
|
|
3769
|
-
discover_and_connect_devices
|
|
3770
|
-
|
|
3771
|
-
if [ -z "$TEXT" ]; then
|
|
3772
|
-
# Interactive mode - create persistent sessions for all devices in parallel
|
|
3773
|
-
print_color "$YELLOW" "🔧 Initializing persistent sessions..."
|
|
3774
|
-
|
|
3775
|
-
session_pids=()
|
|
3776
|
-
for device in "${CONNECTED_DEVICES[@]}"; do
|
|
3777
|
-
{
|
|
3778
|
-
session=$(create_persistent_session "$device")
|
|
3779
|
-
if [ -n "$session" ]; then
|
|
3780
|
-
print_color "$GREEN" " ✅ Session ready: $device"
|
|
3781
|
-
else
|
|
3782
|
-
print_color "$RED" " ❌ Session failed: $device"
|
|
3783
|
-
fi
|
|
3784
|
-
} &
|
|
3785
|
-
session_pids+=($!)
|
|
3786
|
-
done
|
|
3787
|
-
|
|
3788
|
-
# Wait for all session creations to complete
|
|
3789
|
-
for pid in "${session_pids[@]}"; do
|
|
3790
|
-
wait "$pid"
|
|
3791
|
-
done
|
|
3792
|
-
|
|
3793
|
-
echo ""
|
|
3794
|
-
# Load history at startup
|
|
3795
|
-
load_text_history
|
|
3796
|
-
|
|
3797
|
-
# Initialize device selection (start with all devices selected)
|
|
3798
|
-
SELECTED_DEVICES=("${CONNECTED_DEVICES[@]}")
|
|
3799
|
-
|
|
3800
|
-
print_color "$CYAN" "🔄 Multi-device Interactive Mode"
|
|
3801
|
-
print_color "$YELLOW" " Commands: launch/kill/home/url/screenshot + text input"
|
|
3802
|
-
print_color "$YELLOW" " Type 'help' for commands • 'select' for device targeting • 'quit' to exit"
|
|
3803
|
-
print_color "$CYAN" " Available: $(IFS=', '; echo "${CONNECTED_DEVICES[*]}")"
|
|
3804
|
-
print_color "$GREEN" " Active: $(IFS=', '; echo "${SELECTED_DEVICES[*]}")"
|
|
3805
|
-
echo ""
|
|
3806
|
-
|
|
3807
|
-
while true; do
|
|
3808
|
-
# Enable readline for this input with custom history
|
|
3809
|
-
set -o history
|
|
3810
|
-
HISTFILE="$HISTORY_FILE"
|
|
3811
|
-
HISTSIZE="$HISTORY_MAX"
|
|
3812
|
-
HISTFILESIZE="$HISTORY_MAX"
|
|
3813
|
-
|
|
3814
|
-
# Load history from file for this session
|
|
3815
|
-
if [ -f "$HISTORY_FILE" ]; then
|
|
3816
|
-
history -r "$HISTORY_FILE"
|
|
3817
|
-
fi
|
|
3818
|
-
|
|
3819
|
-
read -e -p "Multi-device [${#SELECTED_DEVICES[@]}/${#CONNECTED_DEVICES[@]}] > " INPUT_TEXT
|
|
3820
|
-
|
|
3821
|
-
# Add any non-empty input to history (except quit commands)
|
|
3822
|
-
if [ -n "$INPUT_TEXT" ] && [[ "$INPUT_TEXT" != "quit" ]] && [[ "$INPUT_TEXT" != "exit" ]] && [[ "$INPUT_TEXT" != "q" ]] && [[ "$INPUT_TEXT" != "help" ]]; then
|
|
3823
|
-
add_to_history "$INPUT_TEXT"
|
|
3824
|
-
fi
|
|
3825
|
-
|
|
3826
|
-
# Disable history again to avoid shell command pollution
|
|
3827
|
-
set +o history
|
|
3828
|
-
unset HISTFILE
|
|
3829
|
-
|
|
3830
|
-
case "$INPUT_TEXT" in
|
|
3831
|
-
"quit"|"q"|"exit")
|
|
3832
|
-
print_color "$YELLOW" "👋 Goodbye!"
|
|
3833
|
-
cleanup_all_sessions
|
|
3834
|
-
exit 0
|
|
3835
|
-
;;
|
|
3836
|
-
"forcecleanup"|"killwda")
|
|
3837
|
-
print_color "$YELLOW" "🛑 Force cleaning up - this may affect device trust..."
|
|
3838
|
-
cleanup_all_sessions_full
|
|
3839
|
-
print_color "$YELLOW" "👋 Goodbye!"
|
|
3840
|
-
exit 0
|
|
3841
|
-
;;
|
|
3842
|
-
"help"|"commands")
|
|
3843
|
-
print_color "$YELLOW" "📱 Available Commands:"
|
|
3844
|
-
echo ""
|
|
3845
|
-
print_color "$CYAN" "🚀 App Control:"
|
|
3846
|
-
print_color "$YELLOW" " launch <app> - Launch application"
|
|
3847
|
-
print_color "$YELLOW" " open <app> - Same as launch"
|
|
3848
|
-
print_color "$YELLOW" " kill <app> - Terminate application"
|
|
3849
|
-
print_color "$YELLOW" " close <app> - Same as kill"
|
|
3850
|
-
print_color "$YELLOW" " install <path> - Install IPA file (requires iOS tools)"
|
|
3851
|
-
print_color "$YELLOW" " uninstall <id> - Uninstall app by bundle ID (requires iOS tools)"
|
|
3852
|
-
print_color "$YELLOW" " appinfo <id> - Check app version/info by bundle ID"
|
|
3853
|
-
print_color "$YELLOW" " version <id> - Same as appinfo"
|
|
3854
|
-
print_color "$YELLOW" " listapps [word] - List all apps or search by name/word"
|
|
3855
|
-
print_color "$YELLOW" " apps [word] - Same as listapps (search any installed app)"
|
|
3856
|
-
print_color "$YELLOW" " debug - Test device connection and tools"
|
|
3857
|
-
echo ""
|
|
3858
|
-
print_color "$CYAN" "🌐 Navigation:"
|
|
3859
|
-
print_color "$YELLOW" " home - Go to home screen"
|
|
3860
|
-
print_color "$YELLOW" " back [context] - Smart back navigation (button/swipe/context)"
|
|
3861
|
-
print_color "$YELLOW" " goback - Same as back"
|
|
3862
|
-
print_color "$YELLOW" " url <url> - Open URL in Safari"
|
|
3863
|
-
print_color "$YELLOW" " screenshot - Take screenshot"
|
|
3864
|
-
print_color "$YELLOW" " click <element> - Click on element or coordinates"
|
|
3865
|
-
print_color "$YELLOW" " longpress <el> - Long press on element"
|
|
3866
|
-
print_color "$YELLOW" " swipe <dir> - Swipe up/down/left/right or custom coordinates"
|
|
3867
|
-
print_color "$YELLOW" " scroll <dir> - Same as swipe"
|
|
3868
|
-
echo ""
|
|
3869
|
-
print_color "$CYAN" "⚙️ Device Control:"
|
|
3870
|
-
print_color "$YELLOW" " restart - Restart device (wireless + USB supported)"
|
|
3871
|
-
print_color "$YELLOW" " reboot - Same as restart"
|
|
3872
|
-
print_color "$YELLOW" " shutdown - Shutdown device (USB only, wireless does restart)"
|
|
3873
|
-
print_color "$YELLOW" " poweroff - Same as shutdown"
|
|
3874
|
-
echo ""
|
|
3875
|
-
print_color "$CYAN" "🎨 Display & Appearance:"
|
|
3876
|
-
print_color "$YELLOW" " darkmode - Switch to dark mode"
|
|
3877
|
-
print_color "$YELLOW" " lightmode - Switch to light mode"
|
|
3878
|
-
print_color "$YELLOW" " theme <mode> - Set theme (dark/light/toggle)"
|
|
3879
|
-
print_color "$YELLOW" " appearance - Check current appearance mode"
|
|
3880
|
-
print_color "$CYAN" " Note: May open Settings for manual selection"
|
|
3881
|
-
echo ""
|
|
3882
|
-
print_color "$CYAN" "🔄 Screen Rotation:"
|
|
3883
|
-
print_color "$YELLOW" " rotate - Toggle orientation (portrait/landscape)"
|
|
3884
|
-
print_color "$YELLOW" " rotate <orient> - Set orientation (portrait/landscape-left/landscape-right)"
|
|
3885
|
-
print_color "$YELLOW" " rotationlock - Toggle rotation lock (opens Control Center)"
|
|
3886
|
-
print_color "$YELLOW" " lockrotation - Toggle rotation lock (same as rotationlock)"
|
|
3887
|
-
print_color "$CYAN" " Note: May require manual interaction in Control Center"
|
|
3888
|
-
echo ""
|
|
3889
|
-
print_color "$CYAN" "📝 Text & Control:"
|
|
3890
|
-
print_color "$YELLOW" " any text - Send to active field"
|
|
3891
|
-
print_color "$YELLOW" " click <x,y> - Click at coordinates (e.g., click 100,200)"
|
|
3892
|
-
print_color "$YELLOW" " click <text> - Click on button/text element"
|
|
3893
|
-
print_color "$YELLOW" " tap <x,y> - Same as click"
|
|
3894
|
-
print_color "$YELLOW" " longpress <x,y> - Long press at coordinates (1.5s default)"
|
|
3895
|
-
print_color "$YELLOW" " longpress <text>- Long press on text element"
|
|
3896
|
-
print_color "$YELLOW" " longclick <x,y> - Same as longpress"
|
|
3897
|
-
print_color "$YELLOW" " help - Show this help"
|
|
3898
|
-
print_color "$YELLOW" " quit - Exit and cleanup (preserves device trust)"
|
|
3899
|
-
print_color "$YELLOW" " forcecleanup - Force kill WDA processes (may affect trust)"
|
|
3900
|
-
echo ""
|
|
3901
|
-
print_color "$CYAN" "🎯 Device Targeting:"
|
|
3902
|
-
print_color "$YELLOW" " select - Choose which devices to target"
|
|
3903
|
-
print_color "$YELLOW" " -d <dev> <cmd> - Run command on specific device (e.g., -d ipad home)"
|
|
3904
|
-
print_color "$YELLOW" " status - Show connected devices"
|
|
3905
|
-
echo ""
|
|
3906
|
-
print_color "$CYAN" "💡 Examples:"
|
|
3907
|
-
print_color "$YELLOW" " launch safari"
|
|
3908
|
-
print_color "$YELLOW" " -d ipad home # Run home only on ipad"
|
|
3909
|
-
print_color "$YELLOW" " -d iphone16p getLocators # Get locators only from iphone16p"
|
|
3910
|
-
print_color "$YELLOW" " -d ipad click Submit # Click only on ipad"
|
|
3911
|
-
print_color "$YELLOW" " url google.com"
|
|
3912
|
-
print_color "$YELLOW" " click 200,300"
|
|
3913
|
-
print_color "$YELLOW" " click 'Sign In'"
|
|
3914
|
-
print_color "$YELLOW" " longpress 'Settings'"
|
|
3915
|
-
print_color "$YELLOW" " longpress 100,150,2.0"
|
|
3916
|
-
print_color "$YELLOW" " darkmode"
|
|
3917
|
-
print_color "$YELLOW" " rotate landscape-left"
|
|
3918
|
-
print_color "$YELLOW" " rotationlock"
|
|
3919
|
-
print_color "$YELLOW" " restart"
|
|
3920
|
-
print_color "$YELLOW" " shutdown"
|
|
3921
|
-
print_color "$YELLOW" " install /path/to/app.ipa"
|
|
3922
|
-
print_color "$YELLOW" " uninstall com.example.app"
|
|
3923
|
-
print_color "$YELLOW" " appinfo com.example.app"
|
|
3924
|
-
print_color "$YELLOW" " listapps mobileiron"
|
|
3925
|
-
print_color "$YELLOW" " Hello World"
|
|
3926
|
-
print_color "$YELLOW" " kill safari"
|
|
3927
|
-
continue
|
|
3928
|
-
;;
|
|
3929
|
-
"status"|"devices")
|
|
3930
|
-
print_color "$CYAN" "📱 Connected: $(IFS=', '; echo "${CONNECTED_DEVICES[*]}")"
|
|
3931
|
-
continue
|
|
3932
|
-
;;
|
|
3933
|
-
"")
|
|
3934
|
-
continue
|
|
3935
|
-
;;
|
|
3936
|
-
"select"|"devices"|"target")
|
|
3937
|
-
print_color "$CYAN" "📱 Device Selection Menu:"
|
|
3938
|
-
echo ""
|
|
3939
|
-
print_color "$YELLOW" "Available devices:"
|
|
3940
|
-
for i in "${!CONNECTED_DEVICES[@]}"; do
|
|
3941
|
-
device="${CONNECTED_DEVICES[$i]}"
|
|
3942
|
-
is_selected=""
|
|
3943
|
-
if [[ " ${SELECTED_DEVICES[*]} " == *" $device "* ]]; then
|
|
3944
|
-
is_selected="✅"
|
|
3945
|
-
else
|
|
3946
|
-
is_selected="⬜"
|
|
3947
|
-
fi
|
|
3948
|
-
echo " $((i+1)). $is_selected $device"
|
|
3949
|
-
done
|
|
3950
|
-
echo ""
|
|
3951
|
-
print_color "$CYAN" "Commands:"
|
|
3952
|
-
print_color "$YELLOW" " all - Select all devices"
|
|
3953
|
-
print_color "$YELLOW" " none - Deselect all devices"
|
|
3954
|
-
print_color "$YELLOW" " 1,2,3 - Select devices by numbers"
|
|
3955
|
-
print_color "$YELLOW" " +1,2 - Add devices to selection"
|
|
3956
|
-
print_color "$YELLOW" " -1,2 - Remove devices from selection"
|
|
3957
|
-
print_color "$YELLOW" " toggle 1,2 - Toggle device selection"
|
|
3958
|
-
print_color "$YELLOW" " list - Show current selection"
|
|
3959
|
-
print_color "$YELLOW" " done - Finish selection"
|
|
3960
|
-
echo ""
|
|
3961
|
-
|
|
3962
|
-
while true; do
|
|
3963
|
-
read -e -p "Device Selection > " DEVICE_CMD
|
|
3964
|
-
|
|
3965
|
-
case "$DEVICE_CMD" in
|
|
3966
|
-
"done"|"exit"|"")
|
|
3967
|
-
break
|
|
3968
|
-
;;
|
|
3969
|
-
"all")
|
|
3970
|
-
SELECTED_DEVICES=("${CONNECTED_DEVICES[@]}")
|
|
3971
|
-
print_color "$GREEN" "✅ All devices selected: $(IFS=', '; echo "${SELECTED_DEVICES[*]}")"
|
|
3972
|
-
;;
|
|
3973
|
-
"none"|"clear")
|
|
3974
|
-
SELECTED_DEVICES=()
|
|
3975
|
-
print_color "$YELLOW" "🔄 All devices deselected"
|
|
3976
|
-
;;
|
|
3977
|
-
"list"|"show")
|
|
3978
|
-
if [ ${#SELECTED_DEVICES[@]} -eq 0 ]; then
|
|
3979
|
-
print_color "$RED" "❌ No devices selected"
|
|
3980
|
-
else
|
|
3981
|
-
print_color "$GREEN" "✅ Selected: $(IFS=', '; echo "${SELECTED_DEVICES[*]}")"
|
|
3982
|
-
fi
|
|
3983
|
-
;;
|
|
3984
|
-
+*)
|
|
3985
|
-
# Add devices
|
|
3986
|
-
add_nums="${DEVICE_CMD:1}"
|
|
3987
|
-
IFS=',' read -ra NUMS <<< "$add_nums"
|
|
3988
|
-
for num in "${NUMS[@]}"; do
|
|
3989
|
-
num=$(echo "$num" | xargs) # trim whitespace
|
|
3990
|
-
if [[ "$num" =~ ^[0-9]+$ ]] && [ "$num" -ge 1 ] && [ "$num" -le ${#CONNECTED_DEVICES[@]} ]; then
|
|
3991
|
-
device="${CONNECTED_DEVICES[$((num-1))]}"
|
|
3992
|
-
if [[ ! " ${SELECTED_DEVICES[*]} " == *" $device "* ]]; then
|
|
3993
|
-
SELECTED_DEVICES+=("$device")
|
|
3994
|
-
print_color "$GREEN" "✅ Added: $device"
|
|
3995
|
-
fi
|
|
3996
|
-
fi
|
|
3997
|
-
done
|
|
3998
|
-
;;
|
|
3999
|
-
-*)
|
|
4000
|
-
# Remove devices
|
|
4001
|
-
remove_nums="${DEVICE_CMD:1}"
|
|
4002
|
-
IFS=',' read -ra NUMS <<< "$remove_nums"
|
|
4003
|
-
for num in "${NUMS[@]}"; do
|
|
4004
|
-
num=$(echo "$num" | xargs)
|
|
4005
|
-
if [[ "$num" =~ ^[0-9]+$ ]] && [ "$num" -ge 1 ] && [ "$num" -le ${#CONNECTED_DEVICES[@]} ]; then
|
|
4006
|
-
device="${CONNECTED_DEVICES[$((num-1))]}"
|
|
4007
|
-
new_selected=()
|
|
4008
|
-
for selected in "${SELECTED_DEVICES[@]}"; do
|
|
4009
|
-
if [ "$selected" != "$device" ]; then
|
|
4010
|
-
new_selected+=("$selected")
|
|
4011
|
-
fi
|
|
4012
|
-
done
|
|
4013
|
-
SELECTED_DEVICES=("${new_selected[@]}")
|
|
4014
|
-
print_color "$YELLOW" "➖ Removed: $device"
|
|
4015
|
-
fi
|
|
4016
|
-
done
|
|
4017
|
-
;;
|
|
4018
|
-
toggle*)
|
|
4019
|
-
# Toggle devices
|
|
4020
|
-
toggle_nums="${DEVICE_CMD#toggle }"
|
|
4021
|
-
IFS=',' read -ra NUMS <<< "$toggle_nums"
|
|
4022
|
-
for num in "${NUMS[@]}"; do
|
|
4023
|
-
num=$(echo "$num" | xargs)
|
|
4024
|
-
if [[ "$num" =~ ^[0-9]+$ ]] && [ "$num" -ge 1 ] && [ "$num" -le ${#CONNECTED_DEVICES[@]} ]; then
|
|
4025
|
-
device="${CONNECTED_DEVICES[$((num-1))]}"
|
|
4026
|
-
if [[ " ${SELECTED_DEVICES[*]} " == *" $device "* ]]; then
|
|
4027
|
-
# Remove from selection
|
|
4028
|
-
new_selected=()
|
|
4029
|
-
for selected in "${SELECTED_DEVICES[@]}"; do
|
|
4030
|
-
if [ "$selected" != "$device" ]; then
|
|
4031
|
-
new_selected+=("$selected")
|
|
4032
|
-
fi
|
|
4033
|
-
done
|
|
4034
|
-
SELECTED_DEVICES=("${new_selected[@]}")
|
|
4035
|
-
print_color "$YELLOW" "⬜ Deselected: $device"
|
|
4036
|
-
else
|
|
4037
|
-
# Add to selection
|
|
4038
|
-
SELECTED_DEVICES+=("$device")
|
|
4039
|
-
print_color "$GREEN" "✅ Selected: $device"
|
|
4040
|
-
fi
|
|
4041
|
-
fi
|
|
4042
|
-
done
|
|
4043
|
-
;;
|
|
4044
|
-
*)
|
|
4045
|
-
# Direct number selection (replace existing selection)
|
|
4046
|
-
if [[ "$DEVICE_CMD" =~ ^[0-9,[:space:]]+$ ]]; then
|
|
4047
|
-
SELECTED_DEVICES=()
|
|
4048
|
-
IFS=',' read -ra NUMS <<< "$DEVICE_CMD"
|
|
4049
|
-
for num in "${NUMS[@]}"; do
|
|
4050
|
-
num=$(echo "$num" | xargs)
|
|
4051
|
-
if [[ "$num" =~ ^[0-9]+$ ]] && [ "$num" -ge 1 ] && [ "$num" -le ${#CONNECTED_DEVICES[@]} ]; then
|
|
4052
|
-
device="${CONNECTED_DEVICES[$((num-1))]}"
|
|
4053
|
-
if [[ ! " ${SELECTED_DEVICES[*]} " == *" $device "* ]]; then
|
|
4054
|
-
SELECTED_DEVICES+=("$device")
|
|
4055
|
-
fi
|
|
4056
|
-
fi
|
|
4057
|
-
done
|
|
4058
|
-
if [ ${#SELECTED_DEVICES[@]} -gt 0 ]; then
|
|
4059
|
-
print_color "$GREEN" "✅ Selected: $(IFS=', '; echo "${SELECTED_DEVICES[*]}")"
|
|
4060
|
-
else
|
|
4061
|
-
print_color "$RED" "❌ No valid device numbers provided"
|
|
4062
|
-
fi
|
|
4063
|
-
else
|
|
4064
|
-
print_color "$RED" "❌ Unknown command: $DEVICE_CMD"
|
|
4065
|
-
fi
|
|
4066
|
-
;;
|
|
4067
|
-
esac
|
|
4068
|
-
done
|
|
4069
|
-
|
|
4070
|
-
# Update the prompt to show selected devices
|
|
4071
|
-
if [ ${#SELECTED_DEVICES[@]} -eq 0 ]; then
|
|
4072
|
-
SELECTED_DEVICES=("${CONNECTED_DEVICES[@]}")
|
|
4073
|
-
print_color "$YELLOW" "⚠️ No devices selected, using all devices"
|
|
4074
|
-
fi
|
|
4075
|
-
|
|
4076
|
-
print_color "$CYAN" "🎯 Active devices: $(IFS=', '; echo "${SELECTED_DEVICES[*]}")"
|
|
4077
|
-
continue
|
|
4078
|
-
;;
|
|
4079
|
-
*)
|
|
4080
|
-
# Check if this is a runtime device selection: -d devicename command
|
|
4081
|
-
if [[ "$INPUT_TEXT" =~ ^-d[[:space:]]+([^[:space:]]+)[[:space:]]+(.+)$ ]]; then
|
|
4082
|
-
runtime_device="${BASH_REMATCH[1]}"
|
|
4083
|
-
runtime_command="${BASH_REMATCH[2]}"
|
|
4084
|
-
|
|
4085
|
-
# Validate device exists in connected devices
|
|
4086
|
-
device_found=false
|
|
4087
|
-
for device in "${CONNECTED_DEVICES[@]}"; do
|
|
4088
|
-
if [ "$device" = "$runtime_device" ]; then
|
|
4089
|
-
device_found=true
|
|
4090
|
-
break
|
|
4091
|
-
fi
|
|
4092
|
-
done
|
|
4093
|
-
|
|
4094
|
-
if [ "$device_found" = true ]; then
|
|
4095
|
-
print_color "$YELLOW" "🎯 Executing on single device: $runtime_device"
|
|
4096
|
-
print_color "$CYAN" " Command: $runtime_command"
|
|
4097
|
-
echo ""
|
|
4098
|
-
|
|
4099
|
-
execute_wda_command "$runtime_device" "$runtime_command"
|
|
4100
|
-
echo ""
|
|
4101
|
-
print_color "$GREEN" "✅ Completed on $runtime_device"
|
|
4102
|
-
else
|
|
4103
|
-
print_color "$RED" "❌ Device '$runtime_device' not found in connected devices"
|
|
4104
|
-
print_color "$YELLOW" " Available devices: $(IFS=', '; echo "${CONNECTED_DEVICES[*]}")"
|
|
4105
|
-
fi
|
|
4106
|
-
echo ""
|
|
4107
|
-
continue
|
|
4108
|
-
fi
|
|
4109
|
-
|
|
4110
|
-
# Check if this is a command (starts with known command keywords)
|
|
4111
|
-
first_word="${INPUT_TEXT%% *}" # More reliable word extraction
|
|
4112
|
-
|
|
4113
|
-
case "$first_word" in
|
|
4114
|
-
"launch"|"open"|"kill"|"close"|"home"|"url"|"screenshot"|"restart"|"reboot"|"shutdown"|"poweroff"|"darkmode"|"lightmode"|"theme"|"appearance"|"rotate"|"rotationlock"|"lockrotation"|"install"|"uninstall"|"appinfo"|"version"|"listapps"|"apps"|"debug"|"click"|"tap"|"longpress"|"longclick"|"back"|"goback"|"swipe"|"scroll"|"getLocators"|"getlocators"|"locators")
|
|
4115
|
-
# This is a command - send to execute_wda_command for each device
|
|
4116
|
-
print_color "$YELLOW" "🚀 Executing command on ${#SELECTED_DEVICES[@]} device(s): $INPUT_TEXT"
|
|
4117
|
-
echo ""
|
|
4118
|
-
|
|
4119
|
-
pids=()
|
|
4120
|
-
for device in "${SELECTED_DEVICES[@]}"; do
|
|
4121
|
-
{
|
|
4122
|
-
execute_wda_command "$device" "$INPUT_TEXT"
|
|
4123
|
-
echo "[${device}] Completed"
|
|
4124
|
-
} &
|
|
4125
|
-
pids+=($!)
|
|
4126
|
-
print_color "$CYAN" "📱 Started: $device (PID: $!)"
|
|
4127
|
-
done
|
|
4128
|
-
|
|
4129
|
-
echo ""
|
|
4130
|
-
print_color "$YELLOW" "⏳ Waiting for completion..."
|
|
4131
|
-
|
|
4132
|
-
for pid in "${pids[@]}"; do
|
|
4133
|
-
wait "$pid"
|
|
4134
|
-
done
|
|
4135
|
-
|
|
4136
|
-
echo ""
|
|
4137
|
-
print_color "$GREEN" "✅ All devices completed!"
|
|
4138
|
-
;;
|
|
4139
|
-
*)
|
|
4140
|
-
# This is regular text - send as text input
|
|
4141
|
-
TEXT="$INPUT_TEXT"
|
|
4142
|
-
# Use selected devices instead of all devices
|
|
4143
|
-
if [ ${#SELECTED_DEVICES[@]} -eq 0 ]; then
|
|
4144
|
-
send_to_all_devices "${CONNECTED_DEVICES[@]}"
|
|
4145
|
-
else
|
|
4146
|
-
send_to_all_devices "${SELECTED_DEVICES[@]}"
|
|
4147
|
-
fi
|
|
4148
|
-
TEXT=""
|
|
4149
|
-
;;
|
|
4150
|
-
esac
|
|
4151
|
-
echo ""
|
|
4152
|
-
;;
|
|
4153
|
-
esac
|
|
4154
|
-
done
|
|
4155
|
-
else
|
|
4156
|
-
# Single command mode - create temporary sessions
|
|
4157
|
-
print_color "$YELLOW" "🎯 Command: $TEXT"
|
|
4158
|
-
print_color "$CYAN" " Targets: $(IFS=', '; echo "${CONNECTED_DEVICES[*]}")"
|
|
4159
|
-
echo ""
|
|
4160
|
-
|
|
4161
|
-
# Create sessions for all devices
|
|
4162
|
-
for device in "${CONNECTED_DEVICES[@]}"; do
|
|
4163
|
-
create_persistent_session "$device" >/dev/null
|
|
4164
|
-
done
|
|
4165
|
-
|
|
4166
|
-
send_to_all_devices "${CONNECTED_DEVICES[@]}"
|
|
4167
|
-
fi
|