#!/bin/bash # iOS 夜间添加稳定性 soak:循环【清空设备 → 逐个添加】到次日 09:00。 # 关键:iOS 走 WDA(8100),WDA 易掉 → 每轮检查并自动重起;每个设备添加前 terminate/launch 把 app 复位到首页 # (iOS navigateToAddPage 无回首页恢复)。计数沿用 SKIP 护栏(SKIP 不计 PASS)。 # nohup caffeinate -dimsu bash scripts/overnight-soak-ios.sh > reports/soak-ios.out 2>&1 & set -u cd "$(dirname "$0")/.." export PLATFORM=ios export SOAK_MODE=1 UDID=00008110-001A34303AE9801E BUNDLE=com.wohand.wohand DEADLINE=$(date -v+1d -v9H -v0M -v0S +%s 2>/dev/null) || DEADLINE=$(($(date +%s) + 50400)) TS=$(date +%Y%m%d-%H%M%S) mkdir -p reports LOG="reports/soak-ios-$TS.log" DETAIL="reports/soak-ios-$TS.detail.log" DEVICES=( "meter|tests/meter/meter_connect.test.ts|" "remote|tests/remote/remote_connect.test.ts|" "hub|tests/hub/hub_connect.test.ts|export HUB_DEVICE='HubMini 9I'" ) PASS=(); FAIL=(); SKIP=() for i in "${!DEVICES[@]}"; do PASS[$i]=0; FAIL[$i]=0; SKIP[$i]=0; done log(){ echo "[$(date '+%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; } wda_sid(){ curl -s --max-time 5 http://localhost:8100/status 2>/dev/null | grep -oE '[0-9A-F]{8}-[0-9A-F-]{27,}' | head -1; } wda_ready(){ curl -s --max-time 5 http://localhost:8100/status 2>/dev/null | grep -q '"ready" : true'; } ensure_wda(){ if wda_ready; then return 0; fi log " WDA 不在,重起 WDA..." pkill -f iproxy 2>/dev/null; sleep 1 curl -s --max-time 280 -X POST http://127.0.0.1:4723/session -H 'Content-Type: application/json' \ -d "{\"capabilities\":{\"alwaysMatch\":{\"platformName\":\"iOS\",\"appium:automationName\":\"XCUITest\",\"appium:udid\":\"$UDID\",\"appium:platformVersion\":\"26.5\",\"appium:deviceName\":\"iPhone\",\"appium:bundleId\":\"$BUNDLE\",\"appium:wdaLocalPort\":8100,\"appium:noReset\":true,\"appium:newCommandTimeout\":3600}}}" >>"$DETAIL" 2>&1 sleep 3 wda_ready && log " WDA 已就绪" || log " WDA 重起失败(本轮可能全失败)" } app_home(){ local sid; sid=$(wda_sid); [ -z "$sid" ] && return 0 curl -s --max-time 15 -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 --max-time 15 -X POST "http://localhost:8100/session/$sid/wda/apps/launch" -H 'Content-Type: application/json' -d "{\"bundleId\":\"$BUNDLE\"}" >/dev/null 2>&1 sleep 2 } names=""; for spec in "${DEVICES[@]}"; do names+="${spec%%|*} "; done log "===== iOS SOAK 开始,截止 $(date -r $DEADLINE '+%m-%d %H:%M' 2>/dev/null) ; 设备: $names=====" rm -f reports/.results.json ensure_wda cycle=0 while [ "$(date +%s)" -lt "$DEADLINE" ]; do cycle=$((cycle+1)) log "----- Cycle $cycle -----" ensure_wda app_home if RESET_DEVICES=1 npx ts-node scripts/reset-devices.ts >>"$DETAIL" 2>&1; then log " reset 完成"; else log " reset 异常(继续)"; fi for i in "${!DEVICES[@]}"; do spec="${DEVICES[$i]}"; name="${spec%%|*}"; rest="${spec#*|}"; file="${rest%%|*}"; envs="${rest#*|}" echo "[$(date '+%H:%M:%S')] >>> Cycle $cycle $name" >>"$DETAIL" ensure_wda; app_home # 每个设备添加前复位到首页(iOS 导航无回首页恢复) runlog=$(mktemp) ( eval "$envs"; npx vitest run "$file" ) >"$runlog" 2>&1; rc=$? cat "$runlog" >>"$DETAIL" if grep -q "\[SKIP\]" "$runlog"; then SKIP[$i]=$(( SKIP[$i] + 1 )); log " $name SKIP(设备已存在 → 不计通过)" elif [ $rc -eq 0 ] && grep -q "\[PASS\]" "$runlog"; then PASS[$i]=$(( PASS[$i] + 1 )); log " $name PASS" else FAIL[$i]=$(( FAIL[$i] + 1 )); log " $name FAIL (见 $DETAIL)" fi rm -f "$runlog" done tally=""; for i in "${!DEVICES[@]}"; do n="${DEVICES[$i]%%|*}"; tot=$(( PASS[$i] + FAIL[$i] + SKIP[$i] )); tally+="$n ${PASS[$i]}/${tot}(skip${SKIP[$i]}) "; done log " 累计通过率(pass/total,skip另计): $tally" done log "===== iOS SOAK 结束,共 $cycle 轮 =====" final=""; for i in "${!DEVICES[@]}"; do n="${DEVICES[$i]%%|*}"; tot=$(( PASS[$i] + FAIL[$i] + SKIP[$i] )); final+="$n=${PASS[$i]}/${tot}(skip${SKIP[$i]}) "; done log "最终: $final" log "生成汇总报告..." npx ts-node scripts/soak-report.ts "ios-$TS" >>"$DETAIL" 2>&1 && log "时间线汇总: reports/soak-report-ios-$TS.*" || log "时间线汇总生成失败" npx ts-node scripts/gen-combined.ts "SoakIOS_$TS" >>"$DETAIL" 2>&1 && log "逐用例报告: reports/SoakIOS_${TS}_*.html" || log "逐用例报告生成失败"