#!/bin/bash # iOS 控制用例批量:每个用例前用 WDA terminate/launch 复位到首页,跑完记录结果。失败不阻塞。 # 对齐 Android run-p0-all 的 CONTROL,iOS 适配:① 复位走 WDA(无 adb)② 不做 BLE/WiFi 切换双跑(iOS 无 adb svc, # 控制走当前连接即可)③ plug 在 iOS 改名为 "Plug JP",PLUG_CTRL_DEVICE 用子串 "Plug"。 # 设备依赖:控制类用例依赖添加阶段(run-ios-adds.sh)已加的设备 + 账号保留的 摄像头/门铃。 # 注:本 iOS 账号当前无 Lock/Blind Tilt 实体 → lock_card/blind_tilt 多半无设备(失败隔离,不影响其它)。 # WDA 须已在 localhost:8100。 set -u cd "$(dirname "$0")/.." export PLATFORM=ios SOAK_MODE=1 SKIP_PROTO_NET=1 VITEST_RETRY=1 # 控制阶段:单个 it 失败自动重跑一次(对齐 Android) export LOCK_DEVICE="Lock 6D" LOCK_PRO_DEVICE="Lock Pro DE" BT_DEVICE="Blind Tilt 42" # PLUG_CTRL_DEVICE 不导出:plug 控制走 getDeviceName('plug') → 注册表实际名(iOS 当前 "Plug EU"),不写死 BUNDLE=com.wohand.wohand LOG=reports/ios-controls.log : > "$LOG" # WDA 健康检查 + 恢复:/status 不通则重启 iproxy 端口转发(覆盖过夜端口转发断开;设备上 WDA 进程仍在即恢复)。 ensure_wda(){ for t in 1 2 3; do curl -s --max-time 5 http://localhost:8100/status 2>/dev/null | grep -q '"ready"' && return 0 echo "[wda] /status 不通,重启 iproxy(第 $t 次)..." | tee -a "$LOG" pkill -f "iproxy 8100" 2>/dev/null; sleep 1 local udid; udid=$(idevice_id -l 2>/dev/null | head -1) nohup iproxy 8100 8100 -u "$udid" >/dev/null 2>&1 & sleep 5 done curl -s --max-time 5 http://localhost:8100/status 2>/dev/null | grep -q '"ready"' && return 0 echo "[wda] !! WDA 仍不可达(设备上 WDA 进程可能已退出,需手动重启 WDA)" | tee -a "$LOG"; return 1 } reset_home(){ ensure_wda || return 1 local sid sid=$(curl -s --max-time 5 http://localhost:8100/status 2>/dev/null | grep -oE '"sessionId" *: *"[^"]*"' | sed 's/.*"\([^"]*\)"$/\1/') [ -z "$sid" ] && { echo "[reset] 无 WDA session(WDA 掉了?)" | tee -a "$LOG"; return 1; } curl -s -X POST "http://localhost:8100/session/$sid/wda/apps/terminate" -H 'Content-Type: application/json' -d "{\"bundleId\":\"$BUNDLE\"}" >/dev/null 2>&1 sleep 1 curl -s -X POST "http://localhost:8100/session/$sid/wda/apps/launch" -H 'Content-Type: application/json' -d "{\"bundleId\":\"$BUNDLE\"}" >/dev/null 2>&1 sleep 3 } # 设备控制(两轮:BLE/WiFi 各跑一遍,分别打 PROTO=ble/wifi 标签)。action_panel 作为前置只跑一次。 CTRL=( "meter|npx vitest run tests/meter/meter_control.test.ts" "meter_alert_cal|METER_DEVICE='Meter 0B' npx vitest run tests/meter/meter_alert_calibration.test.ts" "meterplus_alert_cal|METER_DEVICE='Meter Plus C9' METER_ALERT_STEP=UZCtZRPy METER_CALT_STEP=9mheLMDJ METER_CALH_STEP=GSZv8BYm npx vitest run tests/meter/meter_alert_calibration.test.ts" "outdoormeter_alert_cal|METER_DEVICE='Outdoor Meter 6A' METER_ALERT_STEP=QwnZJARA METER_CALT_STEP=Ncg7BDeR METER_CALH_STEP=k6cqKqtZ npx vitest run tests/meter/meter_alert_calibration.test.ts" "meterpro_alert_cal|METER_DEVICE='Meter Pro (CO2 Monitor) 6E' METER_ALERT_STEP=JggzNXDo METER_CALT_STEP=L967RGHa METER_CALH_STEP=8Wzaj5pB npx vitest run tests/meter/meter_alert_calibration.test.ts" "bot_card|npx vitest run tests/bot/bot_card.test.ts" "lock_card|npx vitest run tests/lock/lock_card.test.ts" "curtain|npx vitest run tests/curtain/curtain_control.test.ts" "curtain3|CURTAIN_DEVICE='Curtain3 Auto' npx vitest run tests/curtain/curtain_control.test.ts" "curtain3_2025|CURTAIN_DEVICE='Curtain3 2025 Auto' npx vitest run tests/curtain/curtain_control.test.ts" "plug_jp|PLUG_CTRL_DEVICE='Plug JP' npx vitest run tests/plug/plug_mini_control.test.ts" "plug_us|PLUG_CTRL_DEVICE='Plug US' npx vitest run tests/plug/plug_mini_control.test.ts" "plug_eu|PLUG_CTRL_DEVICE='Plug EU' npx vitest run tests/plug/plug_mini_control.test.ts" "blind_tilt|npx vitest run tests/curtain/blind_tilt_control.test.ts" "humidifier2|npx vitest run tests/humidifier/humidifier2_card.test.ts" "air_purifier|npx vitest run tests/air_purifier/air_purifier_card.test.ts" "light_eave|LIGHT_REG=eaveLight LIGHT_CARD='Permanent Outdoor Lights' npx vitest run tests/ceiling_light/eave_light_control.test.ts" "light_color_bulb|LIGHT_REG=colorBulb LIGHT_CARD='Color Bulb' npx vitest run tests/ceiling_light/eave_light_control.test.ts" "light_ceiling_pro|LIGHT_REG=ceilingLight LIGHT_CARD='Ceiling Light Pro' npx vitest run tests/ceiling_light/eave_light_control.test.ts" "light_color_floor|LIGHT_REG=colorFloorLamp LIGHT_CARD='RGBICWW Floor Lamp' npx vitest run tests/ceiling_light/eave_light_control.test.ts" "light_floor|LIGHT_REG=floorLamp LIGHT_CARD='RGBWW Floor Lamp' npx vitest run tests/ceiling_light/eave_light_control.test.ts" "light_strip|LIGHT_REG=stripLight LIGHT_CARD='Strip Light SB' npx vitest run tests/ceiling_light/eave_light_control.test.ts" "light_strip3|LIGHT_REG=stripLight3 LIGHT_CARD='RGBWW Strip Light 3' npx vitest run tests/ceiling_light/eave_light_control.test.ts" "light_neon|LIGHT_REG=neonLight LIGHT_CARD='RGBIC Neon Rope Light' npx vitest run tests/ceiling_light/eave_light_control.test.ts" "light_candle|LIGHT_REG=candleLamp LIGHT_CARD='Candle Warmer Lamp' npx vitest run tests/ceiling_light/eave_light_control.test.ts" # ===== 功能页控制(进设备详情页;iOS 用 tapElement+原地校验,见 reference-ios-feature-control-fixes) ===== "feat_lock|npx vitest run tests/lock/lock_control.test.ts" "feat_curtain|npx vitest run tests/curtain/curtain_feature_control.test.ts" "feat_curtain3|CURTAIN_DEVICE='Curtain3 Auto' npx vitest run tests/curtain/curtain_feature_control.test.ts" "feat_curtain3_2025|CURTAIN_DEVICE='Curtain3 2025 Auto' npx vitest run tests/curtain/curtain_feature_control.test.ts" "feat_blind_tilt|npx vitest run tests/curtain/blind_tilt_feature_control.test.ts" "feat_plug|npx vitest run tests/plug/plug_feature_control.test.ts" "feat_air_purifier|npx vitest run tests/air_purifier/air_purifier_feature_control.test.ts" "feat_humidifier2|npx vitest run tests/humidifier/humidifier2_feature_control.test.ts" "feat_color_bulb|npx vitest run tests/color_bulb/color_bulb_feature_control.test.ts" "feat_relay_switch|npx vitest run tests/switch/relay_switch_feature_control.test.ts" "feat_relay_card|npx vitest run tests/switch/relay_switch_card.test.ts" "feat_relay_2pm|npx vitest run tests/switch/relay_switch_2pm_control.test.ts" "feat_light_eave|LIGHT_CARD='Permanent Outdoor Lights' npx vitest run tests/ceiling_light/light_feature_control.test.ts" "feat_light_floor_c|LIGHT_CARD='RGBICWW Floor Lamp' npx vitest run tests/ceiling_light/light_feature_control.test.ts" "feat_light_floor|LIGHT_CARD='RGBWW Floor Lamp' npx vitest run tests/ceiling_light/light_feature_control.test.ts" "feat_light_strip3|LIGHT_CARD='RGBWW Strip Light 3' npx vitest run tests/ceiling_light/light_feature_control.test.ts" "feat_light_neon|LIGHT_CARD='RGBIC Neon Rope Light' npx vitest run tests/ceiling_light/light_feature_control.test.ts" "feat_light_ceiling|LIGHT_CARD='Ceiling Light Pro' npx vitest run tests/ceiling_light/light_feature_control.test.ts" "feat_light_candle|LIGHT_CARD='Candle Warmer Lamp' npx vitest run tests/ceiling_light/light_feature_control.test.ts" "camera_stream|STREAM_HOLD_MS=60000 npx vitest run tests/camera/camera_stream.test.ts" ) # TAIL:keypad 闭环 + 平台 + 账号(只跑一次,不分协议) TAIL=( "keypad_lock_passcode|npx vitest run tests/keypad/keypad_lock_passcode.test.ts" "feedback|npx vitest run tests/platform/feedback.test.ts" "rn_plugin|npx vitest run tests/platform/rn_plugin.test.ts" "create_room|npx vitest run tests/platform/create_room.test.ts" "message_center|npx vitest run tests/platform/message_center.test.ts" "scene_operation|npx vitest run tests/platform/scene_operation.test.ts" "third_party_login|npx vitest run tests/auth/third_party_login.test.ts" "eu_login|npx vitest run tests/auth/eu_login.test.ts" "register|npx vitest run tests/auth/register.test.ts" "forgot_password|npx vitest run tests/auth/forgot_password.test.ts" "delete_account|npx vitest run tests/auth/delete_account.test.ts" "login|npx vitest run tests/auth/login.test.ts" ) echo "===== $(date '+%F %T') iOS 控制批量开始 =====" | tee -a "$LOG" # 跑单条:复位→执行→打印结果;含 "✗ [FAIL]" 返回 1(SKIP/无设备不算失败,不重试)。 run_ctrl_one() { local name="$1" cmd="$2" echo "----- $(date +%T) [$name] -----" | tee -a "$LOG" reset_home eval "$cmd" > /tmp/ios-ctrl-one.log 2>&1 grep -E '✓ \[PASS\]|✗ \[FAIL\]|○ \[SKIP\]|结果:|找不到|未找到|读不到|not found|cannot|诊断|Unable to perform' /tmp/ios-ctrl-one.log | grep -ivE "deprecat" | tail -12 | tee -a "$LOG" if grep -q '✗ \[FAIL\]' /tmp/ios-ctrl-one.log; then { echo "========== FAIL: [$name] $cmd @ $(date '+%F %T') =========="; cat /tmp/ios-ctrl-one.log; echo; } >> reports/ios-ctrl-fail.log return 1 fi return 0 } # 跑一轮 CTRL 清单,失败收集后重试一轮。$1=协议标签(打进日志,供 gen-p0-report 按 PROTO 拆分)。 run_ctrl_pass() { local label="$1" entry name cmd local failed=() for entry in "${CTRL[@]}"; do name="${entry%%|*}"; cmd="${entry#*|}" # BLE 轮跳过摄像头拉流:拉流依赖 WiFi,BLE 轮 WiFi 已关 → 必失败,白等无意义。仅 WiFi 轮测拉流。 if [ "$label" = "ble" ] && [ "$name" = "camera_stream" ]; then echo "----- [skip][ble] camera_stream(拉流需 WiFi,BLE 轮跳过)-----" | tee -a "$LOG"; continue fi run_ctrl_one "[$label]$name" "$cmd" || failed+=("$entry") done if [ ${#failed[@]} -gt 0 ]; then echo "===== [$label] 控制失败 ${#failed[@]} 个,重试一轮 =====" | tee -a "$LOG" for entry in "${failed[@]}"; do name="${entry%%|*}"; cmd="${entry#*|}" run_ctrl_one "[$label]${name}-retry" "$cmd" && echo "[CTRL-RETRY-OK][$label] $name" | tee -a "$LOG" || echo "[CTRL-RETRY-GIVEUP][$label] $name" | tee -a "$LOG" done fi } # 前置:打开 Action Panel Settings 所有开关(只跑一次) run_ctrl_one "action_panel" "npx vitest run tests/platform/action_panel_settings.test.ts" || true if [ -n "${NO_PROTO_SPLIT:-}" ]; then # ===== 单轮:不分 BLE/WiFi,保持当前 radio 态(两端 WiFi+蓝牙都开),控制清单只跑一遍 ===== echo "===== 控制:单轮(不分协议, radio 保持当前态) =====" | tee -a "$LOG" run_ctrl_pass all else # ===== BLE 轮:WiFi 关(蓝牙保持开)===== echo "----- BLE 控制前:WiFi 关闭(蓝牙保持开)-----" | tee -a "$LOG" WIFI_TARGET=off npx ts-node scripts/ios-set-wifi.ts 2>&1 | grep -iE "iOS-WiFi|快捷|翻遍" | tee -a "$LOG" export PROTO=ble echo "===== 控制 PROTO=ble(WiFi 已关) =====" | tee -a "$LOG" run_ctrl_pass ble # ===== WiFi 轮:蓝牙关 + WiFi 开 ===== echo "----- WiFi 控制前:关蓝牙 + 开 WiFi(纯 WiFi)-----" | tee -a "$LOG" npx ts-node scripts/ios-toggle-bt.ts 2>&1 | grep -iE "iOS-BT|快捷" | tee -a "$LOG" # 蓝牙 开→关 WIFI_TARGET=on npx ts-node scripts/ios-set-wifi.ts 2>&1 | grep -iE "iOS-WiFi|快捷|翻遍" | tee -a "$LOG" export PROTO=wifi echo "===== 控制 PROTO=wifi(WiFi 已开, 蓝牙已关) =====" | tee -a "$LOG" run_ctrl_pass wifi unset PROTO # 恢复:蓝牙开回来(WiFi 已开),供 TAIL 平台/账号用例 echo "----- 恢复蓝牙(开)-----" | tee -a "$LOG" npx ts-node scripts/ios-toggle-bt.ts 2>&1 | grep -iE "iOS-BT|快捷" | tee -a "$LOG" # 蓝牙 关→开 fi # ===== TAIL:平台 + 账号(只跑一次,不分协议)===== if [ -z "${NO_PROTO_SPLIT:-}" ]; then echo "----- 平台 + 账号阶段 -----" | tee -a "$LOG" for entry in "${TAIL[@]}"; do name="${entry%%|*}"; cmd="${entry#*|}" run_ctrl_one "$name" "$cmd" || true done else echo "----- NO_PROTO_SPLIT=1:跳过平台/账号 TAIL(只跑控制)-----" | tee -a "$LOG" fi echo "===== $(date '+%F %T') iOS 控制完成 =====" | tee -a "$LOG" # 汇总 iOS 全量(添加+控制)逐用例报告 + BLE/WiFi 成功率 TS=$(date +%Y%m%d-%H%M%S) npx ts-node scripts/gen-combined.ts "iOSAll_$TS" >> "$LOG" 2>&1 \ && echo "iOS 逐用例报告: reports/iOSAll_${TS}_*.html" | tee -a "$LOG" \ || echo "报告生成失败(见 $LOG)" | tee -a "$LOG" python3 scripts/gen-p0-report.py reports/ios-controls.log reports/P0_iOS_Ctrl_真实.md 2>&1 | tail -2 | tee -a "$LOG" # 含 BLE/WiFi 成功率表