Skip to content

Commit 0fad63a

Browse files
committed
fix: Flutter移动端测试6项关键修复 - 95%通过率
1 parent 49e21fe commit 0fad63a

10 files changed

Lines changed: 775 additions & 397 deletions

File tree

extension/src/engineClient.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,18 @@ export class EngineClient {
208208
return this._post<{ session_id: string; message: string; device?: Record<string, unknown> }>("/api/v1/mobile/session/create", params);
209209
}
210210

211+
/** 手机测试环境一键检测(设备+Appium) */
212+
async mobilePrecheck(): Promise<{
213+
ok: boolean;
214+
device_ok: boolean;
215+
appium_ok: boolean;
216+
message: string;
217+
device_message: string;
218+
appium_message: string;
219+
}> {
220+
return this._get("/api/v1/mobile/precheck");
221+
}
222+
211223
/** 检查小程序开发者工具状态 */
212224
async getMiniprogramDevtoolsStatus(): Promise<{ found: boolean; path?: string; message?: string }> {
213225
return this._get<{ found: boolean; path?: string; message?: string }>("/api/v1/miniprogram/devtools/status");

extension/src/sidebarProvider.ts

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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]*"detail"\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

flutter-demo/testpilot/auth.testpilot.json

Lines changed: 0 additions & 161 deletions
This file was deleted.

0 commit comments

Comments
 (0)