Skip to content

Commit 646bafc

Browse files
committed
feat: Playwright根因分析+异常检测器误报修复
1. _extract_playwright_root_cause(): 从Playwright详细报错中提取关键根因 - intercepts pointer events CSS层级遮挡提示 - element is not visible 元素不可见提示 - strict mode violation 选择器匹配多个元素提示 - 等7种常见Playwright根因模式 2. Bug描述附加根因分析 - 引擎端:Bug.description追加根因分析 - 插件端:_formatBugText显示根因分析和修复建议 - 编程AI能看到真正原因(如navbar遮挡sidebar) 3. 异常检测器误报修复 - AnomalyDetector新增suppress_error_text()方法 - assert_text通过后标记验证文本为蓝本预期 - _check_error_elements跳过蓝本故意验证的错误文本 - 解决故意测试登录失败场景时误报.error的问题
1 parent df4e381 commit 646bafc

3 files changed

Lines changed: 68 additions & 6 deletions

File tree

extension/src/sidebarProvider.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -352,14 +352,21 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
352352
const step = bug.step_number ? ` (步骤#${bug.step_number})` : "";
353353
lines.push(`${i + 1}. [${(bug.severity as string || "medium").toUpperCase()}] ${cat ? cat + " " : ""}${bug.title}${step}`);
354354
if (bug.description) {
355-
// 包含修复建议时多显示几行
356355
const desc = bug.description as string;
357-
const firstLine = desc.split("\n")[0].substring(0, 150);
356+
const firstLine = desc.split("\n")[0].substring(0, 200);
358357
lines.push(` ${firstLine}`);
359-
if (desc.includes("💡 修复建议")) {
358+
// 显示根因分析(关键!编程AI需要看到Playwright的真正报错原因)
359+
if (desc.includes("🔍 根因分析")) {
360+
const rootCause = desc.split("� 根因分析: ")[1];
361+
if (rootCause) {
362+
lines.push(` 🔍 根因分析: ${rootCause.split("\n")[0].substring(0, 200)}`);
363+
}
364+
}
365+
// 显示修复建议
366+
if (desc.includes("�💡 修复建议")) {
360367
const suggestion = desc.split("💡 修复建议")[1];
361368
if (suggestion) {
362-
lines.push(` 💡 修复建议${suggestion.split("\n")[0].substring(0, 150)}`);
369+
lines.push(` 💡 修复建议${suggestion.split("\n")[0].substring(0, 200)}`);
363370
}
364371
}
365372
}

src/testing/anomaly_detector.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,16 @@ def __init__(self, page: Page) -> None:
9898
self._network_errors: list[dict] = []
9999
self._monitoring = False
100100
self._reported_errors: set[str] = set() # 已报告的异常指纹,避免重复
101+
self._suppress_error_texts: set[str] = set() # 蓝本故意验证的错误文本,检测时跳过
102+
103+
def suppress_error_text(self, text: str) -> None:
104+
"""标记某段文本为蓝本预期的错误提示,异常检测时跳过。
105+
106+
当assert_text步骤故意验证错误提示(如'用户名或密码错误')并通过时,
107+
调用此方法避免异常检测器误报。
108+
"""
109+
if text:
110+
self._suppress_error_texts.add(text.strip())
101111

102112
def start_monitoring(self) -> None:
103113
"""开始监控控制台错误和网络失败。在测试开始前调用一次。"""
@@ -265,6 +275,9 @@ async def _check_error_elements(self, report: AnomalyReport) -> None:
265275
text = (await el.text_content() or "").strip()[:200]
266276
if not text:
267277
continue
278+
# 跳过蓝本故意验证的错误文本(如assert_text验证'用户名或密码错误')
279+
if any(suppress in text for suppress in self._suppress_error_texts):
280+
continue
268281
report.anomalies.append(Anomaly(
269282
anomaly_type=AnomalyType.ERROR_ELEMENT,
270283
severity=AnomalySeverity.MEDIUM,

src/testing/blueprint_runner.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,11 @@ async def _execute_step(
520520
duration_seconds=elapsed,
521521
error_message=f"文本断言失败: 预期'{expected_value}',实际'{text}'",
522522
), bug
523+
else:
524+
# assert_text通过:告诉异常检测器这个文本是蓝本预期的,
525+
# 避免异常检测器看到.error元素就误报(如故意测试登录失败场景)
526+
if self._anomaly_detector and expected_value:
527+
self._anomaly_detector.suppress_error_text(expected_value)
523528

524529
elif step_def.action == "assert_visible":
525530
try:
@@ -608,15 +613,25 @@ async def _execute_step(
608613

609614
except (BrowserActionError, BrowserNavigationError) as e:
610615
elapsed = time.time() - start
611-
logger.info(" ⚠ 步骤{} error | {:.1f}秒 | {}", step_num, elapsed, str(e)[:80])
616+
# 提取Playwright关键根因(如"intercepts pointer events"、"element is not visible")
617+
err_detail = str(e)
618+
root_cause = self._extract_playwright_root_cause(err_detail)
619+
if root_cause:
620+
logger.info(" ⚠ 步骤{} error | {:.1f}秒 | {} | 根因: {}", step_num, elapsed, str(e)[:80], root_cause)
621+
else:
622+
logger.info(" ⚠ 步骤{} error | {:.1f}秒 | {}", step_num, elapsed, str(e)[:80])
612623
# 操作类步骤失败(选择器找不到/超时)生成Bug,让AI中枢有机会介入恢复
613624
err_bug = None
614625
if step_def.action in ("click", "fill", "select"):
626+
# 构建完整描述:基本错误 + 根因(如果有)
627+
bug_desc = err_detail
628+
if root_cause:
629+
bug_desc += f"\n\n🔍 根因分析: {root_cause}"
615630
err_bug = BugReport(
616631
severity=BugSeverity.MEDIUM,
617632
category="操作失败",
618633
title=f"步骤{step_num}操作失败: {step_def.action} {target or ''}",
619-
description=str(e),
634+
description=bug_desc,
620635
location=target or "",
621636
reproduction=desc,
622637
screenshot_path=None,
@@ -643,6 +658,33 @@ async def _execute_step(
643658
error_message=str(e),
644659
), None
645660

661+
@staticmethod
662+
def _extract_playwright_root_cause(error_text: str) -> Optional[str]:
663+
"""从Playwright错误信息中提取关键根因,让编程AI能看到真正问题。
664+
665+
Playwright的错误信息很长(含Call log),关键线索藏在后面。
666+
例如 "intercepts pointer events" 说明有元素遮挡了点击目标。
667+
"""
668+
import re
669+
# 已知的Playwright关键根因模式
670+
patterns = [
671+
(r"<([^>]+)>\s*intercepts pointer events", "CSS层级遮挡: <{0}>元素挡住了点击目标,检查z-index或position"),
672+
(r"element is not visible", "目标元素不可见(display:none或visibility:hidden)"),
673+
(r"element is outside of the viewport", "目标元素在视口外,需要先滚动到可见区域"),
674+
(r"element is not enabled", "目标元素被禁用(disabled属性)"),
675+
(r"waiting for selector.*did not resolve to any element", "选择器在页面中找不到任何匹配元素"),
676+
(r"Element is not an <input>", "目标元素不是输入框,不能用fill操作(可能是<select>下拉框,应用select操作)"),
677+
(r"strict mode violation.*resolved to (\d+) elements", "选择器匹配到多个元素({0}个),需要更精确的选择器"),
678+
]
679+
for pattern, template in patterns:
680+
match = re.search(pattern, error_text, re.IGNORECASE)
681+
if match:
682+
try:
683+
return template.format(*match.groups()) if match.groups() else template
684+
except (IndexError, KeyError):
685+
return template
686+
return None
687+
646688
async def _check_anomalies(self, step_num: int, page_url: str) -> list[BugReport]:
647689
"""执行蓝本外异常检测,将发现的异常转为BugReport。"""
648690
if not self._anomaly_detector:

0 commit comments

Comments
 (0)