Skip to content

Commit e6aca51

Browse files
committed
launch()v8.3:不重启只auto+TCP探测端口+connect重试8次x5秒+模拟器启动等待10秒
1 parent 6d5f2b7 commit e6aca51

1 file changed

Lines changed: 92 additions & 52 deletions

File tree

src/controller/miniprogram.py

Lines changed: 92 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,31 @@ def _next_step(self) -> int:
139139

140140
# ── BaseController 实现 ──────────────────────
141141

142+
def _run_cli(self, cmd_name: str, args: list, timeout: int = 15) -> bool:
143+
"""执行CLI命令,返回是否成功。"""
144+
try:
145+
r = subprocess.run(
146+
args, capture_output=True, timeout=timeout,
147+
encoding="utf-8", errors="replace",
148+
)
149+
logger.info("cli {} 完成 (rc={})", cmd_name, r.returncode)
150+
return True
151+
except Exception as e:
152+
logger.warning("cli {} 失败: {}", cmd_name, e)
153+
return False
154+
142155
async def launch(self) -> None:
143-
"""启动小程序自动化连接(长连接模式)。
144-
145-
完整流程(参考盲测v4/v5和test_connect.js跑通的经验):
146-
1. cli close 关闭开发者工具中的项目(防止端口占用)
147-
2. cli open 重新打开项目
148-
3. cli auto --auto-port 9420 启动自动化,指定固定端口
149-
4. 等待WebSocket端口就绪(TCP探测)
150-
5. 启动桥接服务器(HTTP端口9421)
151-
6. 等HTTP服务器就绪 → POST connect命令主动触发连接
152-
7. 桥接服务器中导航全部使用evaluate(wx.xxx())原生API(SDK方法会超时!)
156+
"""启动小程序自动化连接(长连接模式 v8.3)。
157+
158+
流程(参考test_connect.js和官方CLI文档):
159+
阶段1: cli auto 开启自动化(若项目未打开则先open再auto)
160+
阶段2: TCP探测WS端口就绪(最多20秒)
161+
阶段3: 启动桥接HTTP服务器
162+
阶段4: 等HTTP就绪
163+
阶段5: POST connect触发automator连接(重试8次x5秒=40秒)
164+
165+
重要:官方文档要求先在设置→安全设置中开启服务端口!
166+
导航全部使用evaluate(wx.xxx())原生API(SDK方法会超时!)
153167
"""
154168
import socket
155169
import urllib.request
@@ -162,45 +176,52 @@ async def launch(self) -> None:
162176
project_path = self._config.project_path
163177

164178
if not cli_path:
165-
raise RuntimeError("未找到微信开发者工具cli,请安装微信开发者工具并确保cli.bat在默认路径")
166-
167-
# ═══ 阶段1: 强制重启小程序 ═══
168-
logger.info("═══ 阶段1: 强制重启小程序 ═══")
169-
logger.info("cli: {} | 项目: {}", cli_path, project_path)
179+
raise RuntimeError(
180+
"未找到微信开发者工具cli,请安装微信开发者工具"
181+
)
170182

171-
for cmd_name, cmd_args in [
172-
("close", [cli_path, "close", "--project", project_path]),
173-
("open", [cli_path, "open", "--project", project_path]),
174-
("auto", [cli_path, "auto", "--project", project_path, "--auto-port", str(ws_port)]),
175-
]:
176-
try:
177-
subprocess.run(cmd_args, capture_output=True, timeout=15,
178-
encoding="utf-8", errors="replace")
179-
logger.info("cli {} 完成", cmd_name)
180-
except Exception as e:
181-
logger.warning("cli {} 失败: {}", cmd_name, e)
183+
# ═══ 阶段1: 启动自动化 ═══
184+
logger.info("═══ 阶段1: 启动自动化 ═══")
185+
logger.info("cli: {} | 项目: {} | 端口: {}", cli_path, project_path, ws_port)
186+
187+
# 先尝试直接 cli auto(如果项目已打开,这步就够了)
188+
auto_ok = self._run_cli("auto", [
189+
cli_path, "auto", "--project", project_path,
190+
"--auto-port", str(ws_port),
191+
])
192+
await asyncio.sleep(3)
193+
194+
# 检查端口是否已通
195+
port_ok = self._check_tcp_port(ws_port)
196+
197+
if not port_ok:
198+
# 项目可能没打开,先open再auto
199+
logger.info("端口未通,尝试 cli open + cli auto ...")
200+
self._run_cli("open", [cli_path, "open", "--project", project_path])
201+
# open后需要等模拟器启动(关键!模拟器需要时间加载)
202+
logger.info("等待模拟器启动(10秒)...")
203+
await asyncio.sleep(10)
204+
self._run_cli("auto", [
205+
cli_path, "auto", "--project", project_path,
206+
"--auto-port", str(ws_port),
207+
])
182208
await asyncio.sleep(3)
183209

184-
# ═══ 阶段2: 等待WebSocket端口就绪(TCP探测,最多等15秒) ═══
210+
# ═══ 阶段2: 等待WS端口就绪(TCP探测,最多20秒) ═══
185211
logger.info("═══ 阶段2: 等待WS端口 {} 就绪 ═══", ws_port)
186212
port_ready = False
187-
for i in range(15):
188-
try:
189-
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
190-
s.settimeout(1)
191-
s.connect(("127.0.0.1", ws_port))
192-
s.close()
213+
for i in range(20):
214+
if self._check_tcp_port(ws_port):
193215
port_ready = True
194216
logger.info("WS端口 {} 已就绪(第{}秒)", ws_port, i + 1)
195217
break
196-
except Exception:
197-
s.close()
198-
await asyncio.sleep(1)
218+
await asyncio.sleep(1)
199219

200220
if not port_ready:
201221
raise RuntimeError(
202-
f"WebSocket端口 {ws_port} 未就绪(等了15秒)。"
203-
f"请检查微信开发者工具是否已打开项目并开启了服务端口。"
222+
f"WebSocket端口 {ws_port} 未就绪(等了20秒)。\n"
223+
f"请确认:1) 微信开发者工具已打开项目 "
224+
f"2) 设置→安全设置→已开启服务端口"
204225
)
205226

206227
# ═══ 阶段3: 启动桥接服务器 ═══
@@ -227,9 +248,9 @@ async def launch(self) -> None:
227248
try:
228249
req = urllib.request.Request(self._http_base)
229250
with urllib.request.urlopen(req, timeout=2) as resp:
230-
json.loads(resp.read()) # 只要能响应就行,不管connected状态
251+
json.loads(resp.read())
231252
http_ready = True
232-
logger.info("HTTP服务器已就绪({:.1f}秒)", (i + 1) * 0.5)
253+
logger.info("HTTP服务器已就绪({:.1f}秒)", (i + 1) * 0.5)
233254
break
234255
except Exception:
235256
continue
@@ -238,35 +259,54 @@ async def launch(self) -> None:
238259
self._kill_bridge()
239260
raise RuntimeError("桥接服务器HTTP端口9421未响应")
240261

241-
# ═══ 阶段5: POST connect命令,主动触发连接(重试5次×3秒) ═══
242-
logger.info("═══ 阶段5: 发送connect命令 ═══")
243-
for i in range(5):
262+
# ═══ 阶段5: POST connect(重试8次x5秒=40秒) ═══
263+
logger.info("═══ 阶段5: 发送connect命令(最多重试8次) ═══")
264+
last_err = ""
265+
for i in range(8):
244266
try:
245-
payload = json.dumps({"action": "connect", "params": {}}).encode("utf-8")
267+
payload = json.dumps({"action": "connect", "params": {}}).encode()
246268
req = urllib.request.Request(
247269
self._http_base, data=payload,
248270
headers={"Content-Type": "application/json"}, method="POST",
249271
)
250-
with urllib.request.urlopen(req, timeout=10) as resp:
272+
with urllib.request.urlopen(req, timeout=15) as resp:
251273
data = json.loads(resp.read())
252274
if data.get("success"):
253275
self._connected = True
254276
self._device.is_connected = True
255277
self._device.extra = {"project": project_path, "port": ws_port}
256-
logger.info("小程序自动化已连接 | 端口: {} | 第{}次尝试", ws_port, i + 1)
278+
logger.info("小程序自动化已连接 | 端口:{} | 第{}", ws_port, i + 1)
257279
return
258-
else:
259-
logger.warning("connect返回失败: {}(第{}次)", data.get("error", ""), i + 1)
280+
last_err = data.get("error", "未知错误")
281+
logger.warning("connect失败: {}(第{}次)", last_err, i + 1)
260282
except Exception as e:
261-
logger.warning("connect请求异常: {}(第{}次)", e, i + 1)
262-
await asyncio.sleep(3)
283+
last_err = str(e)
284+
logger.warning("connect异常: {}(第{}次)", last_err, i + 1)
285+
await asyncio.sleep(5)
263286

264287
self._kill_bridge()
265288
raise RuntimeError(
266-
f"桥接服务器connect命令5次均失败。WS端口{ws_port}已确认打开,"
267-
f"但automator连接失败。请确认开发者工具已打开项目。"
289+
f"automator连接8次均失败(最后错误: {last_err})。\n"
290+
f"WS端口{ws_port}已通但连接失败。请确认模拟器已完全启动。"
268291
)
269292

293+
@staticmethod
294+
def _check_tcp_port(port: int, host: str = "127.0.0.1") -> bool:
295+
"""检查TCP端口是否可连接。"""
296+
import socket
297+
try:
298+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
299+
s.settimeout(1)
300+
s.connect((host, port))
301+
s.close()
302+
return True
303+
except Exception:
304+
try:
305+
s.close()
306+
except Exception:
307+
pass
308+
return False
309+
270310
def _kill_bridge(self) -> None:
271311
"""清理桥接服务器进程。"""
272312
if self._bridge_proc and self._bridge_proc.poll() is None:

0 commit comments

Comments
 (0)