minitap-mobile-use 3.3.0__py3-none-any.whl
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.
- minitap/mobile_use/__init__.py +0 -0
- minitap/mobile_use/agents/contextor/contextor.md +55 -0
- minitap/mobile_use/agents/contextor/contextor.py +175 -0
- minitap/mobile_use/agents/contextor/types.py +36 -0
- minitap/mobile_use/agents/cortex/cortex.md +135 -0
- minitap/mobile_use/agents/cortex/cortex.py +152 -0
- minitap/mobile_use/agents/cortex/types.py +15 -0
- minitap/mobile_use/agents/executor/executor.md +42 -0
- minitap/mobile_use/agents/executor/executor.py +87 -0
- minitap/mobile_use/agents/executor/tool_node.py +152 -0
- minitap/mobile_use/agents/hopper/hopper.md +15 -0
- minitap/mobile_use/agents/hopper/hopper.py +44 -0
- minitap/mobile_use/agents/orchestrator/human.md +12 -0
- minitap/mobile_use/agents/orchestrator/orchestrator.md +21 -0
- minitap/mobile_use/agents/orchestrator/orchestrator.py +134 -0
- minitap/mobile_use/agents/orchestrator/types.py +11 -0
- minitap/mobile_use/agents/outputter/human.md +25 -0
- minitap/mobile_use/agents/outputter/outputter.py +85 -0
- minitap/mobile_use/agents/outputter/test_outputter.py +167 -0
- minitap/mobile_use/agents/planner/human.md +14 -0
- minitap/mobile_use/agents/planner/planner.md +126 -0
- minitap/mobile_use/agents/planner/planner.py +101 -0
- minitap/mobile_use/agents/planner/types.py +51 -0
- minitap/mobile_use/agents/planner/utils.py +70 -0
- minitap/mobile_use/agents/summarizer/summarizer.py +35 -0
- minitap/mobile_use/agents/video_analyzer/__init__.py +5 -0
- minitap/mobile_use/agents/video_analyzer/human.md +5 -0
- minitap/mobile_use/agents/video_analyzer/video_analyzer.md +37 -0
- minitap/mobile_use/agents/video_analyzer/video_analyzer.py +111 -0
- minitap/mobile_use/clients/browserstack_client.py +477 -0
- minitap/mobile_use/clients/idb_client.py +429 -0
- minitap/mobile_use/clients/ios_client.py +332 -0
- minitap/mobile_use/clients/ios_client_config.py +141 -0
- minitap/mobile_use/clients/ui_automator_client.py +330 -0
- minitap/mobile_use/clients/wda_client.py +526 -0
- minitap/mobile_use/clients/wda_lifecycle.py +367 -0
- minitap/mobile_use/config.py +413 -0
- minitap/mobile_use/constants.py +3 -0
- minitap/mobile_use/context.py +106 -0
- minitap/mobile_use/controllers/__init__.py +0 -0
- minitap/mobile_use/controllers/android_controller.py +524 -0
- minitap/mobile_use/controllers/controller_factory.py +46 -0
- minitap/mobile_use/controllers/device_controller.py +182 -0
- minitap/mobile_use/controllers/ios_controller.py +436 -0
- minitap/mobile_use/controllers/platform_specific_commands_controller.py +199 -0
- minitap/mobile_use/controllers/types.py +106 -0
- minitap/mobile_use/controllers/unified_controller.py +193 -0
- minitap/mobile_use/graph/graph.py +160 -0
- minitap/mobile_use/graph/state.py +115 -0
- minitap/mobile_use/main.py +309 -0
- minitap/mobile_use/sdk/__init__.py +12 -0
- minitap/mobile_use/sdk/agent.py +1294 -0
- minitap/mobile_use/sdk/builders/__init__.py +10 -0
- minitap/mobile_use/sdk/builders/agent_config_builder.py +307 -0
- minitap/mobile_use/sdk/builders/index.py +15 -0
- minitap/mobile_use/sdk/builders/task_request_builder.py +236 -0
- minitap/mobile_use/sdk/constants.py +1 -0
- minitap/mobile_use/sdk/examples/README.md +83 -0
- minitap/mobile_use/sdk/examples/__init__.py +1 -0
- minitap/mobile_use/sdk/examples/app_lock_messaging.py +54 -0
- minitap/mobile_use/sdk/examples/platform_manual_task_example.py +67 -0
- minitap/mobile_use/sdk/examples/platform_minimal_example.py +48 -0
- minitap/mobile_use/sdk/examples/simple_photo_organizer.py +76 -0
- minitap/mobile_use/sdk/examples/smart_notification_assistant.py +225 -0
- minitap/mobile_use/sdk/examples/video_transcription_example.py +117 -0
- minitap/mobile_use/sdk/services/cloud_mobile.py +656 -0
- minitap/mobile_use/sdk/services/platform.py +434 -0
- minitap/mobile_use/sdk/types/__init__.py +51 -0
- minitap/mobile_use/sdk/types/agent.py +84 -0
- minitap/mobile_use/sdk/types/exceptions.py +138 -0
- minitap/mobile_use/sdk/types/platform.py +183 -0
- minitap/mobile_use/sdk/types/task.py +269 -0
- minitap/mobile_use/sdk/utils.py +29 -0
- minitap/mobile_use/services/accessibility.py +100 -0
- minitap/mobile_use/services/llm.py +247 -0
- minitap/mobile_use/services/telemetry.py +421 -0
- minitap/mobile_use/tools/index.py +67 -0
- minitap/mobile_use/tools/mobile/back.py +52 -0
- minitap/mobile_use/tools/mobile/erase_one_char.py +56 -0
- minitap/mobile_use/tools/mobile/focus_and_clear_text.py +317 -0
- minitap/mobile_use/tools/mobile/focus_and_input_text.py +153 -0
- minitap/mobile_use/tools/mobile/launch_app.py +86 -0
- minitap/mobile_use/tools/mobile/long_press_on.py +169 -0
- minitap/mobile_use/tools/mobile/open_link.py +62 -0
- minitap/mobile_use/tools/mobile/press_key.py +83 -0
- minitap/mobile_use/tools/mobile/stop_app.py +62 -0
- minitap/mobile_use/tools/mobile/swipe.py +156 -0
- minitap/mobile_use/tools/mobile/tap.py +154 -0
- minitap/mobile_use/tools/mobile/video_recording.py +177 -0
- minitap/mobile_use/tools/mobile/wait_for_delay.py +81 -0
- minitap/mobile_use/tools/scratchpad.py +147 -0
- minitap/mobile_use/tools/test_utils.py +413 -0
- minitap/mobile_use/tools/tool_wrapper.py +16 -0
- minitap/mobile_use/tools/types.py +35 -0
- minitap/mobile_use/tools/utils.py +336 -0
- minitap/mobile_use/utils/app_launch_utils.py +173 -0
- minitap/mobile_use/utils/cli_helpers.py +37 -0
- minitap/mobile_use/utils/cli_selection.py +143 -0
- minitap/mobile_use/utils/conversations.py +31 -0
- minitap/mobile_use/utils/decorators.py +124 -0
- minitap/mobile_use/utils/errors.py +6 -0
- minitap/mobile_use/utils/file.py +13 -0
- minitap/mobile_use/utils/logger.py +183 -0
- minitap/mobile_use/utils/media.py +186 -0
- minitap/mobile_use/utils/recorder.py +52 -0
- minitap/mobile_use/utils/requests_utils.py +37 -0
- minitap/mobile_use/utils/shell_utils.py +20 -0
- minitap/mobile_use/utils/test_ui_hierarchy.py +178 -0
- minitap/mobile_use/utils/time.py +6 -0
- minitap/mobile_use/utils/ui_hierarchy.py +132 -0
- minitap/mobile_use/utils/video.py +281 -0
- minitap_mobile_use-3.3.0.dist-info/METADATA +329 -0
- minitap_mobile_use-3.3.0.dist-info/RECORD +115 -0
- minitap_mobile_use-3.3.0.dist-info/WHEEL +4 -0
- minitap_mobile_use-3.3.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import re
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from minitap.mobile_use.utils.logger import get_logger
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
# WDA bundle ID (default for Appium's WDA)
|
|
13
|
+
WDA_BUNDLE_ID = "com.facebook.WebDriverAgentRunner.xctrunner"
|
|
14
|
+
WDA_BUNDLE_ID_ALT = "com.apple.test.WebDriverAgentRunner-Runner"
|
|
15
|
+
|
|
16
|
+
# Default ports
|
|
17
|
+
DEFAULT_WDA_PORT = 8100
|
|
18
|
+
DEFAULT_DEVICE_WDA_PORT = 8100
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def check_wda_running(port: int = DEFAULT_WDA_PORT, timeout: float = 5.0) -> bool:
|
|
22
|
+
"""Check if WDA server is running and responding.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
port: WDA port to check (default: 8100)
|
|
26
|
+
timeout: Request timeout in seconds
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
True if WDA is running and responding, False otherwise
|
|
30
|
+
"""
|
|
31
|
+
url = f"http://localhost:{port}/status"
|
|
32
|
+
try:
|
|
33
|
+
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
34
|
+
response = await client.get(url)
|
|
35
|
+
if response.status_code == 200:
|
|
36
|
+
data = response.json()
|
|
37
|
+
# WDA returns a status object with sessionId
|
|
38
|
+
if "value" in data or "sessionId" in data:
|
|
39
|
+
logger.debug(f"WDA is running on port {port}")
|
|
40
|
+
return True
|
|
41
|
+
return False
|
|
42
|
+
except httpx.ConnectError:
|
|
43
|
+
logger.debug(f"WDA not responding on port {port} (connection refused)")
|
|
44
|
+
return False
|
|
45
|
+
except httpx.TimeoutException:
|
|
46
|
+
logger.debug(f"WDA not responding on port {port} (timeout)")
|
|
47
|
+
return False
|
|
48
|
+
except Exception as e:
|
|
49
|
+
logger.debug(f"Error checking WDA status: {e}")
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def check_iproxy_running(port: int = DEFAULT_WDA_PORT) -> bool:
|
|
54
|
+
"""Check if iproxy is running for the specified port.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
port: Local port to check (default: 8100)
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
True if iproxy is running for this port, False otherwise
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
# Check for iproxy process listening on the port
|
|
64
|
+
result = subprocess.run(
|
|
65
|
+
["pgrep", "-f", f"iproxy.*{port}"],
|
|
66
|
+
capture_output=True,
|
|
67
|
+
text=True,
|
|
68
|
+
timeout=5,
|
|
69
|
+
)
|
|
70
|
+
return result.returncode == 0
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.debug(f"Error checking iproxy status: {e}")
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_iproxy_pid(port: int = DEFAULT_WDA_PORT) -> int | None:
|
|
77
|
+
"""Get the PID of iproxy process for the specified port.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
port: Local port to check
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
PID if found, None otherwise
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
result = subprocess.run(
|
|
87
|
+
["pgrep", "-f", f"iproxy.*{port}"],
|
|
88
|
+
capture_output=True,
|
|
89
|
+
text=True,
|
|
90
|
+
timeout=5,
|
|
91
|
+
)
|
|
92
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
93
|
+
return int(result.stdout.strip().split()[0])
|
|
94
|
+
return None
|
|
95
|
+
except Exception:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def start_iproxy(
|
|
100
|
+
local_port: int = DEFAULT_WDA_PORT,
|
|
101
|
+
device_port: int = DEFAULT_DEVICE_WDA_PORT,
|
|
102
|
+
udid: str | None = None,
|
|
103
|
+
) -> subprocess.Popen | None:
|
|
104
|
+
"""Start iproxy for port forwarding.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
local_port: Local port to forward to (default: 8100)
|
|
108
|
+
device_port: Device port to forward from (default: 8100)
|
|
109
|
+
udid: Optional device UDID (uses first device if not specified)
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Popen process object if started successfully, None otherwise
|
|
113
|
+
"""
|
|
114
|
+
if not shutil.which("iproxy"):
|
|
115
|
+
logger.error("iproxy not found. Install libimobiledevice:\n brew install libimobiledevice")
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
# Check if already running
|
|
119
|
+
if check_iproxy_running(local_port):
|
|
120
|
+
logger.info(f"iproxy already running on port {local_port}")
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
cmd = ["iproxy", str(local_port), str(device_port)]
|
|
125
|
+
if udid:
|
|
126
|
+
cmd.extend(["-u", udid])
|
|
127
|
+
|
|
128
|
+
logger.info(f"Starting iproxy: {' '.join(cmd)}")
|
|
129
|
+
process = subprocess.Popen(
|
|
130
|
+
cmd,
|
|
131
|
+
stdout=subprocess.PIPE,
|
|
132
|
+
stderr=subprocess.PIPE,
|
|
133
|
+
start_new_session=True,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Give it a moment to start
|
|
137
|
+
await asyncio.sleep(0.5)
|
|
138
|
+
|
|
139
|
+
# Check if it's still running
|
|
140
|
+
if process.poll() is not None:
|
|
141
|
+
stderr = process.stderr.read().decode() if process.stderr else ""
|
|
142
|
+
logger.error(f"iproxy failed to start: {stderr}")
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
logger.info(f"iproxy started successfully (PID: {process.pid})")
|
|
146
|
+
return process
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
logger.error(f"Failed to start iproxy: {e}")
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def find_wda_project() -> str | None:
|
|
154
|
+
"""Find WebDriverAgent.xcodeproj in common locations.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Path to WebDriverAgent.xcodeproj or None if not found
|
|
158
|
+
"""
|
|
159
|
+
import os
|
|
160
|
+
from pathlib import Path
|
|
161
|
+
|
|
162
|
+
# Common locations to search
|
|
163
|
+
search_paths = [
|
|
164
|
+
# Current directory
|
|
165
|
+
Path.cwd() / "WebDriverAgent" / "WebDriverAgent.xcodeproj",
|
|
166
|
+
Path.cwd() / "WebDriverAgent.xcodeproj",
|
|
167
|
+
# Home directory
|
|
168
|
+
Path.home() / "WebDriverAgent" / "WebDriverAgent.xcodeproj",
|
|
169
|
+
# Appium installation
|
|
170
|
+
Path.home()
|
|
171
|
+
/ ".appium"
|
|
172
|
+
/ "node_modules"
|
|
173
|
+
/ "appium-xcuitest-driver"
|
|
174
|
+
/ "node_modules"
|
|
175
|
+
/ "appium-webdriveragent"
|
|
176
|
+
/ "WebDriverAgent.xcodeproj",
|
|
177
|
+
# Common dev locations
|
|
178
|
+
Path.home() / "Developer" / "WebDriverAgent" / "WebDriverAgent.xcodeproj",
|
|
179
|
+
Path.home() / "Projects" / "WebDriverAgent" / "WebDriverAgent.xcodeproj",
|
|
180
|
+
Path(
|
|
181
|
+
"/usr/local/lib/node_modules/appium/node_modules/appium-webdriveragent"
|
|
182
|
+
"/WebDriverAgent.xcodeproj"
|
|
183
|
+
),
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
# Also check WDA_PROJECT_PATH environment variable
|
|
187
|
+
env_path = os.environ.get("WDA_PROJECT_PATH")
|
|
188
|
+
if env_path:
|
|
189
|
+
search_paths.insert(0, Path(env_path))
|
|
190
|
+
|
|
191
|
+
for path in search_paths:
|
|
192
|
+
if path.exists():
|
|
193
|
+
logger.debug(f"Found WDA project at: {path}")
|
|
194
|
+
return str(path)
|
|
195
|
+
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
async def build_and_run_wda(
|
|
200
|
+
udid: str,
|
|
201
|
+
project_path: str | None = None,
|
|
202
|
+
timeout: float = 120.0,
|
|
203
|
+
) -> subprocess.Popen | None:
|
|
204
|
+
"""Build and run WebDriverAgent on the device using xcodebuild.
|
|
205
|
+
|
|
206
|
+
This starts xcodebuild test in the background, which builds WDA if needed
|
|
207
|
+
and runs it on the device.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
udid: Device UDID
|
|
211
|
+
project_path: Path to WebDriverAgent.xcodeproj (auto-detected if None)
|
|
212
|
+
timeout: Build timeout in seconds (default: 120)
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Popen process object if started successfully, None otherwise
|
|
216
|
+
"""
|
|
217
|
+
if not shutil.which("xcodebuild"):
|
|
218
|
+
logger.error("xcodebuild not found. Please install Xcode.")
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
# Find WDA project
|
|
222
|
+
if project_path is None:
|
|
223
|
+
project_path = find_wda_project()
|
|
224
|
+
|
|
225
|
+
if project_path is None:
|
|
226
|
+
logger.error(
|
|
227
|
+
"WebDriverAgent.xcodeproj not found.\n"
|
|
228
|
+
"Please clone it first:\n"
|
|
229
|
+
" git clone https://github.com/appium/WebDriverAgent.git\n"
|
|
230
|
+
"Or set WDA_PROJECT_PATH environment variable."
|
|
231
|
+
)
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
logger.info(f"Building and running WDA from: {project_path}")
|
|
235
|
+
logger.info(f"Target device: {udid[:16]}...")
|
|
236
|
+
logger.info("This may take a minute on first run...")
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
# Build and test command
|
|
240
|
+
cmd = [
|
|
241
|
+
"xcodebuild",
|
|
242
|
+
"test",
|
|
243
|
+
"-project",
|
|
244
|
+
project_path,
|
|
245
|
+
"-scheme",
|
|
246
|
+
"WebDriverAgentRunner",
|
|
247
|
+
"-destination",
|
|
248
|
+
f"id={udid}",
|
|
249
|
+
"-allowProvisioningUpdates",
|
|
250
|
+
]
|
|
251
|
+
|
|
252
|
+
# Start xcodebuild in background
|
|
253
|
+
process = subprocess.Popen(
|
|
254
|
+
cmd,
|
|
255
|
+
stdout=subprocess.PIPE,
|
|
256
|
+
stderr=subprocess.STDOUT,
|
|
257
|
+
text=True,
|
|
258
|
+
start_new_session=True,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Wait for WDA to start (look for "ServerURLHere" in output)
|
|
262
|
+
start_time = asyncio.get_event_loop().time()
|
|
263
|
+
server_started = False
|
|
264
|
+
|
|
265
|
+
while asyncio.get_event_loop().time() - start_time < timeout:
|
|
266
|
+
if process.poll() is not None:
|
|
267
|
+
# Process ended - check if it failed
|
|
268
|
+
output = process.stdout.read() if process.stdout else ""
|
|
269
|
+
if "BUILD FAILED" in output or "error:" in output.lower():
|
|
270
|
+
logger.error(f"WDA build failed:\n{output[-1000:]}")
|
|
271
|
+
return None
|
|
272
|
+
break
|
|
273
|
+
|
|
274
|
+
# Check output for server start indicator
|
|
275
|
+
if process.stdout:
|
|
276
|
+
line = process.stdout.readline()
|
|
277
|
+
if line:
|
|
278
|
+
logger.debug(f"xcodebuild: {line.strip()}")
|
|
279
|
+
if "ServerURLHere" in line or "WebDriverAgent" in line:
|
|
280
|
+
server_started = True
|
|
281
|
+
logger.info("WDA server starting on device...")
|
|
282
|
+
|
|
283
|
+
# If server started, give it a moment then return
|
|
284
|
+
if server_started:
|
|
285
|
+
await asyncio.sleep(3)
|
|
286
|
+
if process.poll() is None:
|
|
287
|
+
logger.info("WDA build and run started successfully")
|
|
288
|
+
return process
|
|
289
|
+
|
|
290
|
+
await asyncio.sleep(0.5)
|
|
291
|
+
|
|
292
|
+
if process.poll() is None:
|
|
293
|
+
# Still running after timeout - assume it's working
|
|
294
|
+
logger.info("WDA process running (build may still be in progress)")
|
|
295
|
+
return process
|
|
296
|
+
|
|
297
|
+
return None
|
|
298
|
+
|
|
299
|
+
except Exception as e:
|
|
300
|
+
logger.error(f"Failed to start xcodebuild: {e}")
|
|
301
|
+
return None
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def get_wda_setup_instructions(udid: str) -> str:
|
|
305
|
+
"""Get WDA setup instructions (only manual steps not handled by code)."""
|
|
306
|
+
return f"""WebDriverAgent Setup (device: {udid[:16]}...)
|
|
307
|
+
|
|
308
|
+
1. Clone WebDriverAgent:
|
|
309
|
+
git clone https://github.com/appium/WebDriverAgent.git
|
|
310
|
+
|
|
311
|
+
2. Configure code signing in Xcode:
|
|
312
|
+
- Open WebDriverAgent.xcodeproj
|
|
313
|
+
- Select WebDriverAgentLib target → Signing & Capabilities
|
|
314
|
+
- Set your Team and Bundle Identifier
|
|
315
|
+
- Repeat for WebDriverAgentRunner target
|
|
316
|
+
|
|
317
|
+
Build and run are handled automatically after signing is configured.
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
async def wait_for_wda(
|
|
322
|
+
port: int = DEFAULT_WDA_PORT,
|
|
323
|
+
timeout: float = 60.0,
|
|
324
|
+
poll_interval: float = 2.0,
|
|
325
|
+
) -> bool:
|
|
326
|
+
"""Wait for WDA to become available.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
port: WDA port to check
|
|
330
|
+
timeout: Maximum time to wait in seconds
|
|
331
|
+
poll_interval: Time between checks in seconds
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
True if WDA became available, False if timeout
|
|
335
|
+
"""
|
|
336
|
+
logger.info(
|
|
337
|
+
f"Waiting for WDA on port {port} (unlock your device, password may be required) "
|
|
338
|
+
f"(timeout: {timeout}s)..."
|
|
339
|
+
)
|
|
340
|
+
elapsed = 0.0
|
|
341
|
+
|
|
342
|
+
while elapsed < timeout:
|
|
343
|
+
if await check_wda_running(port):
|
|
344
|
+
logger.info(f"WDA is ready on port {port}")
|
|
345
|
+
return True
|
|
346
|
+
|
|
347
|
+
await asyncio.sleep(poll_interval)
|
|
348
|
+
elapsed += poll_interval
|
|
349
|
+
logger.debug(f"Waiting for WDA... ({elapsed:.0f}s/{timeout:.0f}s)")
|
|
350
|
+
|
|
351
|
+
logger.error(f"Timeout waiting for WDA on port {port}")
|
|
352
|
+
return False
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def parse_wda_port_from_url(url: str) -> int:
|
|
356
|
+
"""Extract port number from WDA URL.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
url: WDA URL (e.g., "http://localhost:8100")
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
Port number (default: 8100 if not found)
|
|
363
|
+
"""
|
|
364
|
+
match = re.search(r":(\d+)", url)
|
|
365
|
+
if match:
|
|
366
|
+
return int(match.group(1))
|
|
367
|
+
return DEFAULT_WDA_PORT
|