@@ -191,7 +191,13 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
191191
192192 this . _postMessage ( { command : "testResult" , data : report } ) ;
193193 } catch ( err : unknown ) {
194- const message = err instanceof Error ? err . message : String ( err ) ;
194+ const raw = err instanceof Error ? err . message : String ( err ) ;
195+ // 解析引擎返回的 JSON 错误详情(如 HTTP 400: {"detail":"Appium未就绪: ..."} )
196+ let message = raw ;
197+ try {
198+ const jsonMatch = raw . match ( / \{ [ \s \S ] * " d e t a i l " \s * : \s * " ( [ ^ " ] + ) " / ) ;
199+ if ( jsonMatch ) { message = jsonMatch [ 1 ] ; }
200+ } catch { /* 解析失败用原始信息 */ }
195201 this . _postMessage ( { command : "testError" , data : { error : message } } ) ;
196202 }
197203 }
@@ -283,6 +289,10 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
283289 md += `通过率: ${ ( r . pass_rate || 0 ) . toFixed ( 0 ) } % | Bug: ${ r . bug_count || 0 } \n\n` ;
284290 } ) ;
285291
292+ // 合并所有蓝本的 bugs 和 steps
293+ const allBugs = results . flatMap ( ( r ) => ( r as unknown as Record < string , unknown > ) . bugs as unknown [ ] || [ ] ) ;
294+ const allSteps = results . flatMap ( ( r ) => ( r as unknown as Record < string , unknown > ) . steps as unknown [ ] || [ ] ) ;
295+
286296 // 用最后一个报告的格式返回汇总
287297 const summary : TestReportResponse = {
288298 test_name : `批量测试(${ results . length } 个蓝本)` ,
@@ -294,6 +304,8 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
294304 pass_rate : passRate ,
295305 duration_seconds : totalDuration ,
296306 report_markdown : md ,
307+ bugs : allBugs ,
308+ steps : allSteps ,
297309 } as TestReportResponse ;
298310
299311 this . _postMessage ( { command : "testResult" , data : summary } ) ;
@@ -511,13 +523,14 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
511523- 断言失败时引擎会自动截图留证,蓝本不用额外写
512524- ❌ 禁止每个场景末尾都加 screenshot(浪费视觉大模型费用和时间)
513525
514- 【规则12:蓝本按功能模块拆分】
515- - 按业务功能模块拆分蓝本,不要把所有场景塞进一个文件
516- - 拆分原则:一个功能模块 = 一个蓝本文件,改了哪个功能就只跑哪个蓝本
517- * 例:auth(登录注册)、cart(购物车)、checkout(结算支付)、profile(个人中心)
526+ 【规则12:蓝本文件命名与拆分】
527+ - 命名规范:
528+ * 主蓝本(小项目/全量测试):testpilot/testpilot.json
529+ * 分模块蓝本(大项目按功能拆分):testpilot/<模块名>.testpilot.json
530+ - 拆分判断:页面≤3个用一个 testpilot.json;页面>3个按功能模块拆分
531+ * 例:auth.testpilot.json、cart.testpilot.json、checkout.testpilot.json
518532 * 跨页面的端到端流程归属到终点功能模块(如"加购→结算→下单"归入 checkout)
519- - 命名规范:testpilot/<模块名>.testpilot.json
520- - 每个蓝本独立可运行,场景开头都要从 navigate 开始
533+ - 每个蓝本独立可运行,非首页场景开头要从 navigate 开始
521534- 全量测试时通过 run_blueprint_batch 批量运行所有蓝本
522535
523536【规则13:蓝本增量修改(省Token)】
@@ -734,28 +747,14 @@ ${commonRules}`;
734747 const platform = ( msg . platform || "web" ) . toLowerCase ( ) ;
735748 try {
736749 if ( platform === "android" || platform === "ios" ) {
737- // 只检查设备是否连接,Session 由引擎在蓝本测试时自动创建
738- const devices = await this . _client . listMobileDevices ( ) ;
739- if ( ( devices . count || 0 ) === 0 ) {
740- this . _postMessage ( {
741- command : "platformPrecheckResult" ,
742- data : {
743- ok : false ,
744- platform,
745- message : "未检测到手机设备,请先连接手机并开启USB调试。" ,
746- } ,
747- } ) ;
748- return ;
749- }
750-
751- const first = devices . devices ?. [ 0 ] || { } ;
752- const model = String ( ( first as Record < string , unknown > ) . model || ( first as Record < string , unknown > ) . serial || "设备" ) ;
750+ // 一次性检测设备+Appium Server
751+ const check = await this . _client . mobilePrecheck ( ) ;
753752 this . _postMessage ( {
754753 command : "platformPrecheckResult" ,
755754 data : {
756- ok : true ,
755+ ok : check . ok ,
757756 platform,
758- message : `设备 ${ model } 已连接,Appium Session 将自动创建。` ,
757+ message : check . message ,
759758 } ,
760759 } ) ;
761760 return ;
@@ -824,6 +823,12 @@ ${commonRules}`;
824823 this . _postMessage ( { command : "connectDeviceResult" , data : { ok : false , message } } ) ;
825824 } ;
826825 try {
826+ // 0. 先检查引擎是否已连接(未连接则提示先启动引擎)
827+ const engineOk = await httpCheck ( "http://127.0.0.1:8900/api/v1/health" ) ;
828+ if ( ! engineOk ) {
829+ fail ( "引擎未启动,请先点击「启动引擎」按钮,再进行握手" ) ;
830+ return ;
831+ }
827832 // 1. 检查 adb 是否可用
828833 const adbVersion = await run ( "adb version" ) ;
829834 if ( ! adbVersion . includes ( "Android Debug Bridge" ) ) {
@@ -870,13 +875,10 @@ ${commonRules}`;
870875 return ;
871876 }
872877 }
873- // 5. 检查引擎
874- const engineOk = await httpCheck ( "http://127.0.0.1:8900/health" ) ;
875- const engineNote = engineOk ? "引擎就绪" : "引擎未运行(请点击启动引擎)" ;
876- // 6. 全部就绪
878+ // 5. 全部就绪
877879 this . _postMessage ( {
878880 command : "connectDeviceResult" ,
879- data : { ok : engineOk , message : `握手 ${ engineOk ? "成功" : "部分完成" } | 设备: ${ model } | Appium: 就绪 | ${ engineNote } ` } ,
881+ data : { ok : true , message : `握手成功 ✅ | 设备: ${ model } | Appium: 就绪 | 引擎: 就绪 ` } ,
880882 } ) ;
881883 } catch ( err : unknown ) {
882884 const message = err instanceof Error ? err . message : String ( err ) ;
@@ -1074,6 +1076,11 @@ ${commonRules}`;
10741076 </div>
10751077 <div style="display:flex;gap:4px;margin-top:2px">
10761078 <button id="btnDetectDevice" class="btn-secondary" style="font-size:10px;padding:3px 6px;flex:1">检测设备</button>
1079+ <button id="btnHandshake" class="btn-secondary hidden" style="font-size:10px;padding:3px 6px;flex:1">🤝 握手</button>
1080+ </div>
1081+ <div id="handshakeStatusRow" class="hidden" style="display:flex;align-items:center;gap:4px;margin-top:4px;font-size:10px">
1082+ <span id="handshakeIcon">⏳</span>
1083+ <span id="handshakeText" style="color:var(--muted);word-break:break-word">未握手</span>
10771084 </div>
10781085 </div>
10791086 <button id="btnLaunchEngine" class="hidden" style="background:#22c55e;margin-top:6px">🚀 一键启动引擎</button>
@@ -1301,6 +1308,21 @@ ${commonRules}`;
13011308 checkDeviceStatus();
13021309 });
13031310
1311+ // 握手按钮(检测设备+自动启动Appium)
1312+ document.getElementById("btnHandshake").addEventListener("click", () => {
1313+ const btn = document.getElementById("btnHandshake");
1314+ const row = document.getElementById("handshakeStatusRow");
1315+ const icon = document.getElementById("handshakeIcon");
1316+ const text = document.getElementById("handshakeText");
1317+ btn.disabled = true;
1318+ btn.textContent = "⏳ 握手中...";
1319+ row.classList.remove("hidden");
1320+ icon.textContent = "⏳";
1321+ text.textContent = "正在检测 Appium 环境...";
1322+ text.style.color = "var(--muted)";
1323+ vscode.postMessage({ command: "connectDevice", platform: getCurrentPlatform() });
1324+ });
1325+
13041326 // 刷新项目按钮
13051327 document.getElementById("btnRefreshProjects").addEventListener("click", () => {
13061328 vscode.postMessage({ command: "scanBlueprints" });
@@ -1498,21 +1520,45 @@ ${commonRules}`;
14981520 case "blueprintSelected": onBlueprintSelected(msg.data); break;
14991521 case "platformPrecheckResult": onPlatformPrecheckResult(msg.data); break;
15001522 case "deviceStatusResult": onDeviceStatusResult(msg.data); break;
1523+ case "connectDeviceResult": onConnectDeviceResult(msg.data); break;
15011524 }
15021525 });
15031526
15041527 function onDeviceStatusResult(data) {
15051528 const statusText = document.getElementById("deviceStatusText");
15061529 const statusIcon = document.getElementById("deviceStatusIcon");
1530+ const btnHandshake = document.getElementById("btnHandshake");
15071531 if (!data) return;
15081532 if (data.connected) {
15091533 statusText.textContent = (data.message || "设备已连接") + "(运行蓝本时自动连接)";
15101534 statusText.style.color = "var(--success,#22c55e)";
15111535 statusIcon.textContent = "✅";
1536+ btnHandshake.classList.remove("hidden");
15121537 } else {
15131538 statusText.textContent = data.message || "未检测到设备";
15141539 statusText.style.color = "var(--error,#ef4444)";
15151540 statusIcon.textContent = "❌";
1541+ btnHandshake.classList.add("hidden");
1542+ document.getElementById("handshakeStatusRow").classList.add("hidden");
1543+ }
1544+ }
1545+
1546+ function onConnectDeviceResult(data) {
1547+ const row = document.getElementById("handshakeStatusRow");
1548+ const icon = document.getElementById("handshakeIcon");
1549+ const text = document.getElementById("handshakeText");
1550+ const btn = document.getElementById("btnHandshake");
1551+ row.classList.remove("hidden");
1552+ btn.disabled = false;
1553+ btn.textContent = "🤝 握手";
1554+ if (data.ok) {
1555+ icon.textContent = "✅";
1556+ text.textContent = data.message || "握手成功";
1557+ text.style.color = "var(--success,#22c55e)";
1558+ } else {
1559+ icon.textContent = "❌";
1560+ text.textContent = data.message || "握手失败";
1561+ text.style.color = "var(--error,#ef4444)";
15161562 }
15171563 }
15181564
0 commit comments