@@ -142,13 +142,18 @@ def _next_step(self) -> int:
142142 async def launch (self ) -> None :
143143 """启动小程序自动化连接(长连接模式)。
144144
145- 完整流程(参考盲测v4/v5跑通的经验 ):
145+ 完整流程(参考盲测v4/v5和test_connect.js跑通的经验 ):
146146 1. cli close 关闭开发者工具中的项目(防止端口占用)
147147 2. cli open 重新打开项目
148148 3. cli auto --auto-port 9420 启动自动化,指定固定端口
149- 4. 启动桥接服务器连接该端口
150- 5. 桥接服务器中导航全部使用evaluate(wx.xxx())原生API(SDK方法会超时!)
149+ 4. 等待WebSocket端口就绪(TCP探测)
150+ 5. 启动桥接服务器(HTTP端口9421)
151+ 6. 等HTTP服务器就绪 → POST connect命令主动触发连接
152+ 7. 桥接服务器中导航全部使用evaluate(wx.xxx())原生API(SDK方法会超时!)
151153 """
154+ import socket
155+ import urllib .request
156+
152157 if not self ._config .project_path :
153158 raise RuntimeError ("请指定小程序项目路径 (project_path)" )
154159
@@ -159,48 +164,50 @@ async def launch(self) -> None:
159164 if not cli_path :
160165 raise RuntimeError ("未找到微信开发者工具cli,请安装微信开发者工具并确保cli.bat在默认路径" )
161166
162- # ── 强制重启小程序(关键!不重启可能端口不通或状态不干净) ──
163- logger .info ("强制重启小程序 | cli: {} | 项目: {}" , cli_path , project_path )
164-
165- # 步骤1: cli close 关闭项目
166- try :
167- subprocess .run (
168- [cli_path , "close" , "--project" , project_path ],
169- capture_output = True , timeout = 15 , encoding = "utf-8" , errors = "replace" ,
170- )
171- logger .info ("cli close 完成" )
172- except Exception as e :
173- logger .warning ("cli close 失败(可忽略): {}" , e )
174- await asyncio .sleep (2 )
167+ # ═══ 阶段1: 强制重启小程序 ═══
168+ logger .info ("═══ 阶段1: 强制重启小程序 ═══" )
169+ logger .info ("cli: {} | 项目: {}" , cli_path , project_path )
175170
176- # 步骤2: cli open 打开项目
177- try :
178- subprocess .run (
179- [cli_path , "open" , "--project" , project_path ],
180- capture_output = True , timeout = 15 , encoding = "utf-8" , errors = "replace" ,
181- )
182- logger .info ("cli open 完成" )
183- except Exception as e :
184- logger .warning ("cli open 失败: {}" , e )
185- await asyncio .sleep (2 )
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 )
182+ await asyncio .sleep (3 )
183+
184+ # ═══ 阶段2: 等待WebSocket端口就绪(TCP探测,最多等15秒) ═══
185+ logger .info ("═══ 阶段2: 等待WS端口 {} 就绪 ═══" , ws_port )
186+ 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 ()
193+ port_ready = True
194+ logger .info ("WS端口 {} 已就绪(第{}秒)" , ws_port , i + 1 )
195+ break
196+ except Exception :
197+ s .close ()
198+ await asyncio .sleep (1 )
186199
187- # 步骤3: cli auto 启动自动化,指定端口
188- try :
189- subprocess .run (
190- [cli_path , "auto" , "--project" , project_path , "--auto-port" , str (ws_port )],
191- capture_output = True , timeout = 15 , encoding = "utf-8" , errors = "replace" ,
200+ if not port_ready :
201+ raise RuntimeError (
202+ f"WebSocket端口 { ws_port } 未就绪(等了15秒)。"
203+ f"请检查微信开发者工具是否已打开项目并开启了服务端口。"
192204 )
193- logger .info ("cli auto --auto-port {} 完成" , ws_port )
194- except Exception as e :
195- logger .warning ("cli auto 失败: {}" , e )
196- # cli auto后开发者工具需要较长时间准备WebSocket端口(首次可能弹信任确认框)
197- await asyncio .sleep (5 )
198205
199- # ── 启动桥接服务器连接 ──
206+ # ═══ 阶段3: 启动桥接服务器 ═══
200207 self ._http_port = 9421
201208 self ._http_base = f"http://127.0.0.1:{ self ._http_port } "
202209
203- logger .info ("启动桥接服务器 | WS端口: {} | HTTP端口: {}" , ws_port , self ._http_port )
210+ logger .info ("═══ 阶段3: 启动桥接服务器 ═══ WS: {} HTTP: {}" , ws_port , self ._http_port )
204211
205212 bridge_server = Path (__file__ ).parent / "miniprogram_bridge_server.js"
206213 if not bridge_server .exists ():
@@ -212,31 +219,63 @@ async def launch(self) -> None:
212219 stderr = subprocess .PIPE ,
213220 )
214221
215- # 等待服务器启动并连接(最多等30秒)
216- # 桥接服务器内部也有重试逻辑(6次×3秒),所以Python端要等够时间
217- import urllib . request
218- for i in range (60 ):
222+ # ═══ 阶段4: 等HTTP服务器就绪(最多5秒) ═══
223+ logger . info ( "═══ 阶段4: 等待HTTP服务器就绪 ═══" )
224+ http_ready = False
225+ for i in range (10 ):
219226 await asyncio .sleep (0.5 )
220227 try :
221228 req = urllib .request .Request (self ._http_base )
222229 with urllib .request .urlopen (req , timeout = 2 ) as resp :
230+ json .loads (resp .read ()) # 只要能响应就行,不管connected状态
231+ http_ready = True
232+ logger .info ("HTTP服务器已就绪(第{:.1f}秒)" , (i + 1 ) * 0.5 )
233+ break
234+ except Exception :
235+ continue
236+
237+ if not http_ready :
238+ self ._kill_bridge ()
239+ raise RuntimeError ("桥接服务器HTTP端口9421未响应" )
240+
241+ # ═══ 阶段5: POST connect命令,主动触发连接(重试5次×3秒) ═══
242+ logger .info ("═══ 阶段5: 发送connect命令 ═══" )
243+ for i in range (5 ):
244+ try :
245+ payload = json .dumps ({"action" : "connect" , "params" : {}}).encode ("utf-8" )
246+ req = urllib .request .Request (
247+ self ._http_base , data = payload ,
248+ headers = {"Content-Type" : "application/json" }, method = "POST" ,
249+ )
250+ with urllib .request .urlopen (req , timeout = 10 ) as resp :
223251 data = json .loads (resp .read ())
224- if data .get ("connected " ):
252+ if data .get ("success " ):
225253 self ._connected = True
226254 self ._device .is_connected = True
227255 self ._device .extra = {"project" : project_path , "port" : ws_port }
228- logger .info ("小程序自动化已连接(长连接模式) | 端口: {}" , ws_port )
256+ logger .info ("小程序自动化已连接 | 端口: {} | 第{}次尝试 " , ws_port , i + 1 )
229257 return
230- except Exception :
231- continue
232-
233- # 如果循环结束还没连接成功
234- if self ._bridge_proc .poll () is not None :
235- stdout = self ._bridge_proc .stdout .read ().decode ("utf-8" , errors = "replace" ) if self ._bridge_proc .stdout else ""
236- stderr = self ._bridge_proc .stderr .read ().decode ("utf-8" , errors = "replace" ) if self ._bridge_proc .stderr else ""
237- raise RuntimeError (f"桥接服务器启动失败:\n { stdout } \n { stderr } " )
258+ else :
259+ logger .warning ("connect返回失败: {}(第{}次)" , data .get ("error" , "" ), i + 1 )
260+ except Exception as e :
261+ logger .warning ("connect请求异常: {}(第{}次)" , e , i + 1 )
262+ await asyncio .sleep (3 )
263+
264+ self ._kill_bridge ()
265+ raise RuntimeError (
266+ f"桥接服务器connect命令5次均失败。WS端口{ ws_port } 已确认打开,"
267+ f"但automator连接失败。请确认开发者工具已打开项目。"
268+ )
238269
239- raise RuntimeError ("桥接服务器启动超时(30秒),请确认微信开发者工具已安装并开启服务端口" )
270+ def _kill_bridge (self ) -> None :
271+ """清理桥接服务器进程。"""
272+ if self ._bridge_proc and self ._bridge_proc .poll () is None :
273+ self ._bridge_proc .terminate ()
274+ try :
275+ self ._bridge_proc .wait (timeout = 3 )
276+ except Exception :
277+ self ._bridge_proc .kill ()
278+ self ._bridge_proc = None
240279
241280 async def close (self ) -> None :
242281 """关闭小程序自动化连接。"""
0 commit comments