fix: 第二轮深度调试修复 - useSpeechSynthesis守卫+NPE防护+E2E测试完善

- P0: useSpeechSynthesis.ts cancel()增加isSupported守卫
- P0: iot_provider.go 添加loader nil检查防止NPE panic
- 新增CDP E2E v4测试脚本 14项全绿通过
- 生成第二轮修复报告 docs/debug/2026-05-21-round2-fixes.md
This commit is contained in:
2026-05-22 00:10:37 +08:00
parent a058b0ab8e
commit b15e1c9541
7 changed files with 1236 additions and 8 deletions
+152
View File
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""CDP E2E Test v2: 使用真实JWT Token进行前端端到端测试"""
import json, time, base64, urllib.request
REAL_TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3ODE5Njg1MDAsImlhdCI6MTc3OTM3NjUwMCwidXNlcl9pZCI6ImFkbWluIn0.dHdo1NciPDC8-yR0P-CHv2x0hsh3-G2sHOr9E8VBvws'
print('=== CDP E2E Test v2 ===')
# Open page
req = urllib.request.Request('http://127.0.0.1:9225/json/new?url=http://localhost:5199/', method='PUT')
resp = urllib.request.urlopen(req, timeout=10)
page = json.loads(resp.read())
ws_url = page['webSocketDebuggerUrl']
page_id = page['id']
print(f'Page: {page_id}')
from websocket import create_connection
ws = create_connection(ws_url, timeout=10)
def cdp(method, params=None, msg_id=1):
ws.send(json.dumps({'id': msg_id, 'method': method, 'params': params or {}}))
def recv_all(timeout=3):
ws.settimeout(timeout)
msgs = []
try:
while True:
msgs.append(ws.recv())
except:
pass
return msgs
def find_result(msgs, msg_id):
for m in msgs:
try:
d = json.loads(m)
if d.get('id') == msg_id:
return d.get('result', {})
except:
pass
return None
# Enable domains
cdp('Page.enable', {}, 1)
cdp('Runtime.enable', {}, 2)
cdp('Network.enable', {}, 3)
cdp('Log.enable', {}, 4)
recv_all(1)
# Inject real token
print('\n--- Inject Token ---')
cdp('Runtime.evaluate', {
'expression': 'localStorage.setItem("token", "%s"); localStorage.setItem("user_id", "admin"); "OK"' % REAL_TOKEN,
'returnByValue': True
}, 50)
recv_all(2)
# Reload
print('\n--- Reload Page ---')
cdp('Page.navigate', {'url': 'http://localhost:5199/'}, 60)
time.sleep(4)
all_msgs = recv_all(5)
# Analyze
print('\n--- Console Analysis ---')
errors = []
for m in all_msgs:
try:
d = json.loads(m)
except:
continue
method = d.get('method', '')
params = d.get('params', {})
if method == 'Log.entryAdded':
entry = params.get('entry', {})
lvl = entry.get('level', 'log')
text = str(entry.get('text', ''))[:200]
if lvl == 'error':
errors.append(text)
print(f' [{lvl}] {text}')
elif method == 'Runtime.consoleAPICalled':
args = params.get('args', [])
lvl = params.get('type', 'log')
texts = [str(a.get('value', '') or a.get('description', ''))[:150] for a in args]
combined = ' '.join(texts)
if lvl == 'error':
errors.append(combined)
print(f' [{lvl}] {combined}')
elif method == 'Runtime.exceptionThrown':
exc = params.get('exceptionDetails', {})
text = exc.get('text', '')
errors.append(text)
print(f' [EXCEPTION] {text}')
# Network
print('\n--- Network API Calls ---')
for m in all_msgs:
try:
d = json.loads(m)
except:
continue
method = d.get('method', '')
params = d.get('params', {})
if method == 'Network.responseReceived':
resp = params.get('response', {})
url = resp.get('url', '')
status = resp.get('status', 0)
if '/api/' in url:
print(f' [{status}] {url[-80:]}')
elif method == 'Network.loadingFailed':
print(f' [FAIL] {params.get("errorText","?")}')
# Runtime checks
print('\n--- Runtime State ---')
checks = [
'localStorage.getItem("token") ? "TOKEN_OK:" + localStorage.getItem("token").slice(0,20) + "..." : "NO_TOKEN"',
'localStorage.getItem("user_id") || "none"',
'document.title',
'document.querySelectorAll("[class*=\\"message\\"]").length + " message elements"',
'(function(){try{var s=document.querySelector("#root");return s?s.children.length+" root children":"no root"}catch(e){return e.message}})()',
]
for idx, expr in enumerate(checks):
cdp('Runtime.evaluate', {'expression': expr, 'returnByValue': True}, 200 + idx)
recv_msgs = recv_all(2)
r = find_result(recv_msgs, 200 + idx)
if r:
val = r.get('result', {}).get('value', '?')
print(f' {val}')
# Screenshot
print('\n--- Screenshot ---')
cdp('Page.captureScreenshot', {'format': 'png'}, 300)
recv_msgs = recv_all(5)
r = find_result(recv_msgs, 300)
if r and r.get('data'):
img = base64.b64decode(r['data'])
with open('/tmp/cyrene_e2e_v2.png', 'wb') as f:
f.write(img)
print(f' Saved: {len(img)} bytes')
if errors:
print(f'\n=== {len(errors)} ERRORS ===')
for e in errors:
print(f' - {e[:200]}')
else:
print('\n=== ZERO ERRORS ===')
ws.close()
print('[DONE]')