房判分測(cè)試工具)
本菜鳥記錄一下項(xiàng)目的機(jī)房判分測(cè)試工具如有不完善的地方歡迎指正基于 CDP 協(xié)議的 WebView2 自動(dòng)化測(cè)試工具用于批量驗(yàn)證學(xué)生端考試流程。目錄1. 項(xiàng)目概述2. 技術(shù)架構(gòu)3. 配置文件4. 打包部署5. CDP 調(diào)試協(xié)議6. WebSocket 客戶端7. CDP 命令封裝8. 進(jìn)程管理9. 主測(cè)試流程10. 異常處理與容錯(cuò)11. 常見問(wèn)題排查1. 項(xiàng)目概述1.1 背景在智能測(cè)評(píng)系統(tǒng)的機(jī)房部署場(chǎng)景中需要在100 臺(tái)客戶機(jī)上驗(yàn)證學(xué)生端ExamStudent.exe的完整考試流程是否正常運(yùn)行。人工逐臺(tái)操作效率極低因此開發(fā)了本自動(dòng)化測(cè)試工具。1.2 功能定位本工具通過(guò)CDPChrome DevTools Protocol協(xié)議控制基于 WebView2 渲染的學(xué)生端桌面應(yīng)用模擬真實(shí)用戶的完整考試操作流程啟動(dòng) → 登錄 → 選擇任務(wù) → 進(jìn)入考試 → 交卷 → 輸入驗(yàn)證碼 → 確認(rèn)提交1.3 核心特性特性說(shuō)明零依賴純 Python 標(biāo)準(zhǔn)庫(kù)實(shí)現(xiàn)無(wú)需 pip install 任何第三方包免安裝運(yùn)行通過(guò) PyInstaller 打包為單文件 exe雙擊即可運(yùn)行自動(dòng)發(fā)現(xiàn)自動(dòng)查找 ExamStudent.exe支持常見路徑搜索 全盤搜索 GUI 選擇CDP 自動(dòng)化手寫 WebSocket 客戶端通過(guò) CDP 協(xié)議控制 WebView2 頁(yè)面配置持久化首次運(yùn)行自動(dòng)生成 config.json后續(xù)直接讀取測(cè)試報(bào)告每次運(yùn)行生成 JSON 格式的測(cè)試報(bào)告1.4 文件結(jié)構(gòu)判分測(cè)試/├── exam_test.py # 主腳本約 1235 行├── config.json # 運(yùn)行配置文件├── build.bat # PyInstaller 打包腳本├── 判分測(cè)試工具.exe # 打包產(chǎn)物dist 目錄下└── test_report_*.json # 測(cè)試報(bào)告自動(dòng)生成2. 技術(shù)架構(gòu)2.1 技術(shù)棧Python 3.9純標(biāo)準(zhǔn)庫(kù)├── socket / struct / base64 → 手寫 WebSocket 客戶端├── urllib.request → HTTP 請(qǐng)求CDP 端口探測(cè)├── json → 配置讀寫 / CDP 消息序列化├── subprocess → 進(jìn)程管理啟動(dòng)/殺死 ExamStudent.exe├── os / sys / time → 系統(tǒng)交互 / 路徑處理 / 流程控制├── tkinter → 文件選擇對(duì)話框GUI 降級(jí)方案└── PyInstaller → 打包為單文件 exe2.2 架構(gòu)圖┌─────────────────────┐│ 判分測(cè)試工具.exe ││ ││ ┌───────────────┐ │ env: WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS│ │ setup_cdp_env │──┼──────────────────────────────────┐│ └───────────────┘ │ ││ ┌───────────────┐ │ ▼│ │ launch_with │──┼──? subprocess.Popen ──? ExamStudent.exe│ │ _cdp() │ │ │ │ └───────────────┘ │ ▼│ ┌───────────────┐ │ WebView2 渲染引擎│ │ wait_cdp_ready│──┼──? HTTP :9222/json/list│ │ └───────────────┘ │ ▼│ ┌───────────────┐ │ CDP 端口 9222 開放│ │ connect_cdp() │──┼──? WebSocket 連接│ └───────────────┘ │ ││ ┌───────────────┐ │ ▼│ │ test_student()│──┼──? DOM / Runtime / Input 命令│ └───────────────┘ ││ ┌───────────────┐ ││ │ main() │──┼──? config.json / test_report.json│ └───────────────┘ │└─────────────────────┘2.3 核心模塊關(guān)系exam_test.py ├── find_exe() # 自動(dòng)查找學(xué)生端可執(zhí)行文件├── load_config() # 配置加載與持久化├── WebSocketClient # 純標(biāo)準(zhǔn)庫(kù) WebSocket 客戶端│ ├── _handshake() # HTTP Upgrade 握手│ ├── send_json() # 發(fā)送 JSON 消息帶 WebSocket 幀封裝│ └── recv_json() # 接收并解析 JSON 消息├── CDP # Chrome DevTools Protocol 封裝│ ├── send() # 發(fā)送 CDP 命令并等待響應(yīng)│ ├── enable_domains() # 啟用 DOM / Runtime / Page 域│ ├── query_selector() # CSS 選擇器查詢│ ├── set_value() # 設(shè)置輸入框值兼容 Vue/React 響應(yīng)式│ ├── eval_js() # 執(zhí)行 JavaScript 表達(dá)式│ ├── wait_selector() # 等待元素出現(xiàn)│ └── dump_dom() # 導(dǎo)出 DOM 樹調(diào)試用├── setup_cdp_env() # 設(shè)置 CDP 環(huán)境變量os.environ setx├── launch_with_cdp() # 啟動(dòng)學(xué)生端并注入 CDP 環(huán)境變量├── wait_cdp_ready() # 輪詢等待 CDP 端口就緒├── connect_cdp() # 建立 WebSocket 連接├── test_student() # 單個(gè)學(xué)生的完整測(cè)試流程19 步└── main() # 入口函數(shù)3. 配置文件3.1 config.json 結(jié)構(gòu){exe_path:C:/Program Files/智能測(cè)評(píng)系統(tǒng)/ExamStudent.exe,password:123456,wait_seconds:5,cdp_port:9222,students:[cs001,cs002,cs003]}3.2 配置字段說(shuō)明字段類型默認(rèn)值說(shuō)明exe_pathstring自動(dòng)查找學(xué)生端可執(zhí)行文件的絕對(duì)路徑passwordstring123456登錄密碼所有賬號(hào)統(tǒng)一密碼wait_secondsint5進(jìn)入考試后的等待秒數(shù)模擬答題時(shí)間cdp_portint9222WebView2 CDP 調(diào)試端口studentsarray[cs002]待測(cè)試的學(xué)生賬號(hào)列表3.3 自動(dòng)查找策略find_exe()按以下優(yōu)先級(jí)查找 ExamStudent.exe常見安裝路徑7 個(gè)預(yù)設(shè)路徑覆蓋 C/D 盤的 Program Files全盤搜索限制深度 4 層避免遍歷過(guò)深GUI 文件選擇tkinter 文件對(duì)話框作為最終降級(jí)方案查找結(jié)果會(huì)自動(dòng)保存到config.json下次運(yùn)行直接讀取。3.4 PyInstaller 路徑處理ifgetattr(sys,frozen,False):SCRIPT_DIRos.path.dirname(sys.executable)# 打包后exe 所在目錄else:SCRIPT_DIRos.path.dirname(os.path.abspath(file))# 開發(fā)時(shí)腳本所在目錄CONFIG_FILEos.path.join(SCRIPT_DIR,config.json)注意打包后__file__指向_MEI臨時(shí)目錄必須用sys.executable獲取 exe 所在路徑。4. 打包部署4.1 打包腳本 build.batecho off chcp 65001 nul cd /d %~dp0 set PYTHON_EXED:\py\venv\Scripts\python.exe :: 清理舊構(gòu)建 if exist build rd /s /q build if exist dist rd /s /q dist if exist 判分測(cè)試工具.spec del /q 判分測(cè)試工具.spec :: 安裝 PyInstaller 并打包 %PYTHON_EXE% -m pip install pyinstaller -q %PYTHON_EXE% -m PyInstaller --onefile --console --name 判分測(cè)試工具 --clean ^ --hidden-import tkinter ^ --hidden-import tkinter.filedialog ^ exam_test.py :: 復(fù)制配置文件 copy /Y config.json dist\config.json nul 214.2 打包要點(diǎn)要點(diǎn)說(shuō)明必須cd /d %~dp0雙擊 bat 時(shí)工作目錄可能是 system32不切換會(huì)導(dǎo)致 PyInstaller 報(bào)錯(cuò)必須清理舊構(gòu)建build/dist/.spec文件不清理可能產(chǎn)生緩存問(wèn)題--hidden-import tkinterPyInstaller 無(wú)法自動(dòng)檢測(cè) tkinter 的動(dòng)態(tài)導(dǎo)入需顯式聲明--onefile --console單文件 控制臺(tái)模式方便查看運(yùn)行日志Python 路徑當(dāng)前配置為D:\py\venv\Scripts\python.exe需根據(jù)實(shí)際環(huán)境修改4.3 部署方式打包完成后將dist/目錄下的文件復(fù)制到目標(biāo)機(jī)器dist/├── 判分測(cè)試工具.exe # 主程序└── config.json # 配置文件運(yùn)行方式直接雙擊判分測(cè)試工具.exe即可不需要管理員權(quán)限。5. CDP 調(diào)試協(xié)議5.1 原理ExamStudent.exe 是基于 Tauri 框架打包的桌面應(yīng)用內(nèi)部使用WebView2Chromium 內(nèi)核渲染 UI。WebView2 支持通過(guò)環(huán)境變量開啟 Chrome DevTools ProtocolCDP調(diào)試端口。5.2 環(huán)境變量設(shè)置defsetup_cdp_env(cdp_port):dbg_arg--remote-debugging-portstr(cdp_port)os.environ[WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS]dbg_arg os.environ[WEBVIEW2_BROWSER_EXECUTABLE_ARGS]dbg_arg os.system(setx WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS dbg_arg nul 21)os.system(setx WEBVIEW2_BROWSER_EXECUTABLE_ARGS dbg_arg nul 21)方法作用范圍持久性說(shuō)明os.environ當(dāng)前進(jìn)程及子進(jìn)程進(jìn)程結(jié)束即失效普通運(yùn)行時(shí)有效setx寫入用戶注冊(cè)表永久生效確保管理員模式等場(chǎng)景也能讀取5.3 啟動(dòng)流程setup_cdp_env(9222) ← 先寫環(huán)境變量os.environ setx 注冊(cè)表↓kill_existing() ← 殺掉舊的 ExamStudent.exe↓launch_with_cdp(exe_path) ← subprocess.Popen 啟動(dòng)env 繼承環(huán)境變量↓ExamStudent.exe 啟動(dòng)↓WebView2 讀取 WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS↓開啟 --remote-debugging-port9222↓wait_cdp_ready(9222) ← 輪詢 HTTP http://127.0.0.1:9222/json/list↓ connect_cdp(pages) ← 解析 webSocketDebuggerUrl建立 WebSocket 連接5.4 重要限制?? 不要以管理員身份運(yùn)行管理員模式下WebView2 的子進(jìn)程msedgewebview2.exe可能以降低的完整性級(jí)別運(yùn)行導(dǎo)致環(huán)境變量傳遞斷裂CDP 端口無(wú)法開啟。普通用戶模式下所有進(jìn)程在同一完整性級(jí)別中等環(huán)境變量正常傳遞。6. WebSocket 客戶端6.1 設(shè)計(jì)說(shuō)明由于項(xiàng)目要求零第三方依賴WebSocket 客戶端完全使用 Python 標(biāo)準(zhǔn)庫(kù)socket、struct、base64手寫實(shí)現(xiàn)。6.2 核心類 WebSocketClientclassWebSocketClient:definit(self,host,port,path):self.socksocket.socket(socket.AF_INET,socket.SOCK_STREAM)self.sock.settimeout(30)self.sock.connect((host,port))self._handshake(host,port,path)# RFC 6455 握手self._msg_id06.3 握手流程遵循 RFC 6455 規(guī)范客戶端 → 服務(wù)端:GET /devtools/page/xxx HTTP/1.1Host: 127.0.0.1:9222Upgrade: websocketConnection: UpgradeSec-WebSocket-Key: base64隨機(jī)16字節(jié)Sec-WebSocket-Version: 13服務(wù)端 → 客戶端: HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: SHA1(key magic)6.4 幀協(xié)議操作實(shí)現(xiàn)發(fā)送構(gòu)造 WebSocket 幀0x81文本幀F(xiàn)IN 0x80接收解析幀頭 → 讀取長(zhǎng)度7bit / 16bit / 64bit→ 處理掩碼 → 按 opcode 分發(fā)Ping/Pong收到opcode0x9Ping自動(dòng)回復(fù) Pong0x8A關(guān)閉發(fā)送opcode0x8Close幀 關(guān)閉 socket6.5 消息格式CDP 消息遵循 JSON-RPC 風(fēng)格// 請(qǐng)求 {id: 1, method: DOM.getDocument}// 響應(yīng) {id: 1, result: {root: {nodeId: 1, ...}}}// 事件推送 {method: Page.loadEventFired, params: {...}}7. CDP 命令封裝7.1 CDP 類class CDP: def init(self, ws): self.ws ws def send(self, method, paramsNone, timeout15): 發(fā)送 CDP 命令等待匹配的 id 響應(yīng) msg_id self.ws.send_json(method, params) while True: msg self.ws.recv_json(timeout) if msg and msg.get(id) msg_id: return msg if msg and method in msg: # 跳過(guò)事件推送 continue7.2 核心方法一覽方法CDP 命令功能enable_domains()DOM.enableRuntime.enablePage.enable啟用必要的 CDP 域query_selector(sel)DOM.getDocumentDOM.querySelectorCSS 選擇器查詢返回 nodeIdset_value(sel, val)DOM.focusRuntime.evaluate設(shè)置輸入框值兼容 Vue/Reacteval_js(expr)Runtime.evaluate執(zhí)行 JS 表達(dá)式返回值click(sel)Runtime.evaluate點(diǎn)擊元素wait_selector(sel, t)輪詢query_selector等待元素出現(xiàn)超時(shí) t 秒dump_dom(depth)Runtime.evaluate導(dǎo)出 DOM 樹結(jié)構(gòu)調(diào)試用7.3 Vue/React 輸入框兼容WebView2 頁(yè)面使用 Vue/Element UI直接修改input.value不會(huì)觸發(fā)響應(yīng)式更新。需要使用原生settervarnativeSetObject.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,value).set;nativeSet.call(el,新值);el.dispatchEvent(newEvent(input,{bubbles:true}));el.dispatchEvent(newEvent(change,{bubbles:true}));7.4 鼠標(biāo)事件雙重保障對(duì)于關(guān)鍵操作如雙擊任務(wù)行同時(shí)使用兩種機(jī)制確保觸發(fā)JS 模擬element.dispatchEvent(new MouseEvent(dblclick, ...))CDP InputInput.dispatchMouseEvent底層渲染級(jí)別的鼠標(biāo)事件第一層JS 模擬 cdp.eval_js(element.dispatchEvent(new MouseEvent(dblclick, ...)))第二層CDP Input更底層繞過(guò) JS 事件系統(tǒng) cdp.send(Input.dispatchMouseEvent,{type:mousePressed,x:x,y:y,...})cdp.send(Input.dispatchMouseEvent,{type:mouseReleased,x:x,y:y,...})8. 進(jìn)程管理8.1 清理舊進(jìn)程defkill_existing():os.system(taskkill /F /IM ExamStudent.exe nul 21)time.sleep(2)使用nul 21抑制輸出避免進(jìn)程不存在時(shí)報(bào)錯(cuò)。等待 2 秒確保進(jìn)程完全退出、端口釋放。8.2 啟動(dòng)學(xué)生端deflaunch_with_cdp(exe_path,cdp_port):envos.environ.copy()procsubprocess.Popen([exe_path],envenv)returnproc關(guān)鍵點(diǎn)subprocess.Popen必須顯式傳遞envenv否則子進(jìn)程無(wú)法繼承環(huán)境變量。8.3 等待 CDP 就緒def wait_cdp_ready(cdp_port, timeout30): while time.time() end: try: resp urllib.request.urlopen( http://127.0.0.1: str(cdp_port) /json/list, timeout3 ) data json.loads(resp.read().decode(utf-8)) if data: return data # 返回頁(yè)面列表 except Exception: pass time.sleep(1) return None每秒輪詢一次最多等待 30 秒。返回的頁(yè)面列表包含webSocketDebuggerUrl用于建立 WebSocket 連接。8.4 連接 CDPdefconnect_cdp(pages):forpageinpages:ifpage.get(type)page:ws_urlpage.get(webSocketDebuggerUrl,)breakparsedurlparse(ws_url)wsWebSocketClient(parsed.hostname,parsed.port,parsed.path)returnCDP(ws)9. 主測(cè)試流程9.1 流程總覽test_student()函數(shù)包含19 個(gè)步驟模擬完整的考試操作步驟 0: 設(shè)置 CDP 環(huán)境變量步驟 1: 清理舊進(jìn)程步驟 2: 啟動(dòng)學(xué)生端帶 CDP步驟 3: 等待 CDP就緒 步驟 4: 連接 WebSocket步驟 5: 等待登錄頁(yè)加載步驟 6: 輸入賬號(hào)密碼步驟 7: 點(diǎn)擊登錄步驟 7.5: 處理未完成考試彈窗步驟 8: 雙擊練習(xí)圖標(biāo) 步驟9: 等待任務(wù)列表 步驟10: 雙擊編號(hào)為 1 的任務(wù)行步驟 11: 點(diǎn)擊信息確認(rèn)步驟 12: 點(diǎn)擊開始考試步驟 13-14: 等待考試進(jìn)行步驟 15: 點(diǎn)擊交卷步驟 16: Enter 確認(rèn)交卷步驟 17: 讀取驗(yàn)證碼步驟 18: 輸入驗(yàn)證碼步驟 19: 點(diǎn)擊確定完成交卷9.2 登錄流程步驟 5-7等待登錄頁(yè) cdp.wait_selector(input,20)多選擇器嘗試賬號(hào)輸入框 input_selectors[input[typetext],input[placeholder*賬號(hào)],input[placeholder*用戶名],input[nameusername],input[nameaccount],input]多選擇器嘗試密碼輸入框 pwd_selectors[input[typepassword],input[placeholder*密碼],input[namepassword]]使用原生 setter 設(shè)置值兼容 Vue 響應(yīng)式 cdp.set_value(selector,username)cdp.set_value(selector,password)文本匹配點(diǎn)擊登錄按鈕 cdp.eval_js( (function() { var btns document.querySelectorAll(button, .el-button, ...); for (var i 0; i btns.length; i) { var t (btns[i].innerText || btns[i].textContent || ).trim(); if (t 登錄 || t 登 錄) { btns[i].click(); return clicked; } } return not_found; })() )9.3 彈窗處理步驟 7.5登錄后可能遇到未完成考試彈窗自動(dòng)檢測(cè)并處理檢測(cè)彈窗 unfinishedcdp.eval_js( (function() { var all document.querySelectorAll(*); for (var i 0; i all.length; i) { var t (all[i].innerText || ).trim(); if (t.indexOf(未完成) 0 || t.indexOf(未完成的考試) 0) return detected; } return none; })() )點(diǎn)擊結(jié)束考試按鈕ifstr(unfinished)detected:cdp.eval_js(... 查找并點(diǎn)擊 結(jié)束考試 按鈕 ...)9.4 任務(wù)選擇步驟 8-10雙擊練習(xí)圖標(biāo) cdp.eval_js( var all document.querySelectorAll(*); for (var i 0; i all.length; i) { var t (el.innerText || ).trim(); if (t 練習(xí)) { el.dispatchEvent(new MouseEvent(dblclick, {bubbles: true})); return practice_dblclicked; } } )等待表格數(shù)據(jù)加載輪詢 td 有內(nèi)容的數(shù)量 td_countcdp.eval_js( var cells document.querySelectorAll(td); var count 0; for (var i 0; i cells.length; i) { if ((cells[i].textContent || ).trim().length 0) count; } return count; )雙擊編號(hào)為1的任務(wù)行JS模擬CDP Input 雙重保障9.5 交卷流程步驟 15-19點(diǎn)擊交卷 → Enter確認(rèn) → 讀取驗(yàn)證碼 → 輸入驗(yàn)證碼 → 點(diǎn)擊確定驗(yàn)證碼讀取策略四輪降級(jí)輪次策略說(shuō)明第 1 輪紅色/橙色背景的 4 位數(shù)字匹配backgroundColor中的 red/orange/rgb(2xx…)第 2 輪白色文字的 4 位數(shù)字匹配color中的 white/#fff第 3 輪內(nèi)聯(lián)樣式含 background/color匹配style屬性第 4 輪任何葉子節(jié)點(diǎn)的 4 位數(shù)字兜底排除 input 和無(wú)子元素的節(jié)點(diǎn)驗(yàn)證碼輸入策略三級(jí)降級(jí)級(jí)別方法說(shuō)明第 1 級(jí)Input.insertTextCDP 底層直接插入文本最可靠第 2 級(jí)Input.dispatchKeyEvent逐字符模擬鍵盤輸入第 3 級(jí)JSnativeSet賦值直接操作 DOM 的 value 屬性10. 異常處理與容錯(cuò)10.1 異常捕獲結(jié)構(gòu)deftest_student(config,username,index,total):try:# ... 19 步測(cè)試流程 ...return{username:username,status:success,msg:交卷成功,log:log}exceptExceptionase:return{username:username,status:fail,msg:str(e),log:log}finally:# 關(guān)閉 WebSocket 連接ifcdp:cdp.ws.close()# 清理環(huán)境變量os.environ.pop(WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS,None)os.environ.pop(WEBVIEW2_BROWSER_EXECUTABLE_ARGS,None)time.sleep(2)10.2 多選擇器降級(jí)對(duì)于每個(gè)關(guān)鍵 UI 元素都提供多個(gè) CSS 選擇器嘗試python賬號(hào)輸入框6個(gè)選擇器 input_selectors[input[typetext],input[placeholder*賬號(hào)],input[placeholder*用戶名],input[nameusername],input[nameaccount],input# 最終兜底]任務(wù)列表11個(gè)選擇器 task_selectors[table,.el-table,.task-list,[class*task],.el-card,.el-table__body,tr,.list-item,[class*paper],[class*exam],.ant-tablepy]10.3 重試機(jī)制場(chǎng)景重試策略雙擊練習(xí)圖標(biāo)最多重試 30 次每次間隔 3 秒等待表格數(shù)據(jù)最多輪詢 20 次每次間隔 0.5 秒讀取驗(yàn)證碼最多重試 10 次每次間隔 0.5 秒等待 CDP 就緒最多等待 30 秒每秒輪詢等待元素出現(xiàn)wait_selector超時(shí) 20-30 秒10.4 日志記錄每個(gè)步驟的操作和結(jié)果都記錄到log數(shù)組最終輸出到控制臺(tái)和測(cè)試報(bào)告log[]log.append(清理舊進(jìn)程...)log.append(啟動(dòng) ExamStudent.exe (CDP 端口: 9222)...)log.append(CDP 已就緒 (2 個(gè)頁(yè)面))log.append(已連接 WebSocket)log.append(輸入賬號(hào): cs002)log.append( 賬號(hào)輸入框: input[typetext])...11. 常見問(wèn)題排查11.1 CDP 端口未就緒現(xiàn)象等待 CDP 端口就緒一直超時(shí)報(bào)WinError 10061排查步驟powershell1.確認(rèn)進(jìn)程是否啟動(dòng) tasklist|findstrExamStudent2.確認(rèn)端口狀態(tài) netstat-ano|findstr92223.確認(rèn)環(huán)境變量setWEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS常見原因原因解決方案以管理員身份運(yùn)行不要用管理員運(yùn)行直接雙擊即可端口被其他進(jìn)程占用netstat -ano | findstr 9222查看占用進(jìn)程WebView2 運(yùn)行時(shí)未安裝安裝 WebView2 Runtime環(huán)境變量未生效檢查setx是否執(zhí)行成功重啟終端后再試11.2 打包后運(yùn)行報(bào)錯(cuò)錯(cuò)誤原因解決配置文件不存在: C:\...\_MEIxxx\config.json使用了__file__而非sys.executable確保sys.frozen判斷邏輯正確pip is not recognizedPython 不在 PATH 中build.bat 中使用完整路徑Do not run pyinstaller from system32bat 未切換目錄確保cd /d %~dp0打包后功能沒(méi)更新運(yùn)行的是舊 exe重新運(yùn)行 build.bat清理 build/dist11.3 UI 操作失敗現(xiàn)象原因解決未找到賬號(hào)輸入框頁(yè)面未加載完成增加wait_selector超時(shí)時(shí)間登錄按鈕未找到按鈕文本不匹配檢查是否有空格如登 錄任務(wù)行雙擊無(wú)效Tauri 不響應(yīng) JS 事件已使用 CDP Input 雙重保障驗(yàn)證碼讀取失敗頁(yè)面渲染延遲已實(shí)現(xiàn) 10 次重試 4 輪降級(jí)策略驗(yàn)證碼輸入失敗WebView2 輸入框特殊處理已實(shí)現(xiàn) 3 級(jí)降級(jí)insertText → 鍵盤 → JS賦值11.4 測(cè)試報(bào)告每次運(yùn)行自動(dòng)生成test_report_YYYYMMDD_HHMMSS.json{time:2026-07-15 14:30:00,total:1,success:1,fail:0,results:[{username:cs002,status:success,msg:交卷成功,log:[清理舊進(jìn)程...,啟動(dòng) ExamStudent.exe (CDP 端口: 9222)...,CDP 已就緒 (2 個(gè)頁(yè)面),已連接 WebSocket,...]}]}