体验测试执行型Prompt - 三桥墩架构(v3.0)
类型: 模型可直接执行的指令集
防幻觉机制: 证据门控 + 硬性断言 + 状态锚点
防注意力偏移: 分块原子任务 + 状态JSON传递
核心升级: 完整多模态视觉体验检查 + 行业领域规则动态适配
版本: v3.0
执行约束(必须严格遵守)
禁止行为
不得简化流程步骤
不得跳过任何检查点
不得在无工具输出证据时声称成功
不得擅自修改操作路径
不得编造工具返回结果
不得使用固定值(如固定用户名/密码),必须参数化
不得使用RPA录制方式获取人类路径,必须使用推理模拟
不得跳过视觉体验检查步骤(步骤4.5)
强制要求
每步必须输出当前状态JSON
每步必须验证工具返回值
失败必须立即停止并报告
必须使用指定的Playwright MCP工具
必须按顺序执行,不得并行跳过
必须基于用户画像和领域UX常识推理模拟人类路径
必须执行视觉体验检查步骤
当前状态锚点(每步更新)
{
"current_step": 0,
"step_name": "等待启动",
"status": "pending",
"evidence": [],
"output": {},
"errors": []
}
行业预设Profile系统
行业Profile定义
{
"industry_profiles": {
"finance": {
"name": "金融行业",
"priority": "安全优先",
"description": "强调操作安全性、错误提示清晰、操作可撤销",
"rule_overrides": {
"RULE_001": 0.7,
"RULE_002": 0.6,
"RULE_003": 0.95,
"RULE_004": 0.9,
"RULE_005": 0.95,
"RULE_006": 0.9,
"RULE_007": 0.85,
"RULE_008": 0.98
},
"extra_rules": [
{"rule_id": "FIN_001", "description": "涉及资金操作必须有二次确认", "weight": 0.95},
{"rule_id": "FIN_002", "description": "错误提示必须明确指出问题和修复方法", "weight": 0.9},
{"rule_id": "FIN_003", "description": "敏感信息输入框必须有掩码显示", "weight": 0.95},
{"rule_id": "FIN_004", "description": "操作结果必须有明确的成功/失败状态提示", "weight": 0.9}
],
"visual_requirements": {
"contrast_ratio": "AAA",
"min_touch_target": 48,
"error_color": "#dc2626",
"success_color": "#16a34a"
}
},
"ecommerce": {
"name": "电商行业",
"priority": "转化优先",
"description": "强调CTA显著性、路径最短、促销信息醒目",
"rule_overrides": {
"RULE_001": 0.85,
"RULE_002": 0.95,
"RULE_003": 0.85,
"RULE_004": 0.8,
"RULE_005": 0.75,
"RULE_006": 0.7,
"RULE_007": 0.6,
"RULE_008": 0.8
},
"extra_rules": [
{"rule_id": "ECOM_001", "description": "主CTA按钮必须在首屏可见且视觉最突出", "weight": 0.95},
{"rule_id": "ECOM_002", "description": "价格信息必须清晰醒目,折扣标签要突出", "weight": 0.9},
{"rule_id": "ECOM_003", "description": "购买流程步骤数不超过3步", "weight": 0.85},
{"rule_id": "ECOM_004", "description": "商品图片必须在首屏且尺寸足够大", "weight": 0.85}
],
"visual_requirements": {
"contrast_ratio": "AA",
"min_touch_target": 44,
"cta_color": "#ef4444",
"secondary_color": "#f97316"
}
},
"general": {
"name": "通用行业",
"priority": "平衡优先",
"description": "通用型应用,兼顾可用性和美观性",
"rule_overrides": {},
"extra_rules": [],
"visual_requirements": {
"contrast_ratio": "AA",
"min_touch_target": 44,
"cta_color": "#3b82f6",
"secondary_color": "#6366f1"
}
}
}
}
默认UX规则库(可被行业Profile覆盖)
{
"default_ux_rules": [
{"rule_id": "RULE_001", "description": "用户优先注意页面顶部和左侧区域", "weight": 0.8},
{"rule_id": "RULE_002", "description": "红色/大尺寸按钮比灰色/小尺寸按钮更吸引注意", "weight": 0.9},
{"rule_id": "RULE_003", "description": "表单字段按从上到下顺序填写", "weight": 0.95},
{"rule_id": "RULE_004", "description": "用户倾向于点击有明确文字的元素,而非纯图标", "weight": 0.85},
{"rule_id": "RULE_005", "description": "弹窗出现时用户会先处理弹窗", "weight": 0.9},
{"rule_id": "RULE_006", "description": "用户会阅读标签文字确认输入内容", "weight": 0.8},
{"rule_id": "RULE_007", "description": "加载状态时用户会等待", "weight": 0.7},
{"rule_id": "RULE_008", "description": "错误提示出现时用户会修正输入", "weight": 0.95}
]
}
用户画像定义(可配合行业Profile使用)
{
"user_profiles": {
"new_user": {
"name": "新用户",
"behavior_patterns": [
"从上往下扫描页面",
"优先点击视觉显著的CTA按钮",
"遇到表单按顺序填写",
"阅读标签文字确认操作",
"遇到弹窗先处理再继续",
"悬停查看提示信息",
"找不到目标时会滚动浏览"
]
},
"expert_user": {
"name": "熟练用户",
"behavior_patterns": [
"直接寻找目标元素",
"使用快捷键",
"忽略次要信息",
"期望快速完成任务",
"对错误零容忍"
]
},
"casual_user": {
"name": "偶尔用户",
"behavior_patterns": [
"随意浏览",
"容易被吸引",
"耐心有限",
"需要明确引导"
]
}
}
}
步骤1:部署测试环境
输入JSON
{
"task_id": "EXP_TEST_001",
"task_name": "登录页面体验测试",
"target_url": "",
"industry": "general",
"user_profile_type": "new_user",
"checkpoints": [
{"id": "CP_001", "name": "可见性检查", "type": "visibility"},
{"id": "CP_002", "name": "可达性检查", "type": "accessibility"},
{"id": "CP_003", "name": "交互流程检查", "type": "interaction"},
{"id": "CP_004", "name": "视觉体验检查", "type": "visual"},
{"id": "CP_005", "name": "行为推理模拟", "type": "reasoning"}
],
"expected_deviation_score": 0.8
}
必须调用的工具
无 - 此步骤需人工完成部署
输出验证断言
{
"target_url": "必须为有效的HTTP/HTTPS URL",
"deploy_success": "必须返回200状态码",
"industry_valid": "必须为finance/ecommerce/general之一",
"user_profile_valid": "必须为new_user/expert_user/casual_user之一"
}
失败处理
- 若URL无效:停止执行,报告"部署环境未就绪"
- 若返回非200:停止执行,报告"服务不可达"
- 若industry无效:停止执行,报告"无效的行业类型"
- 若user_profile_type无效:停止执行,报告"无效的用户画像类型"
状态更新
{
"current_step": 1,
"step_name": "部署测试环境",
"status": "completed",
"evidence": ["部署完成的URL"],
"output": {
"test_url": "https://example.com/login",
"industry": "ecommerce",
"user_profile_type": "new_user"
},
"errors": []
}
步骤2:可见性检查
输入JSON
{
"test_url": "{{步骤1的test_url}}",
"industry": "{{步骤1的industry}}",
"elements": [
{"selector": "button[type='submit']", "name": "登录按钮"},
{"selector": "input[name='username']", "name": "用户名输入框"},
{"selector": "input[name='password']", "name": "密码输入框"}
]
}
必须调用的工具
工具1: playwright_navigate({"url": "{{test_url}}"})
工具2: playwright_evaluate({"script": "可见性检查脚本"})
可见性检查脚本(必须一字不差执行)
(function checkVisibility(elements, industryConfig) {
const results = [];
const minTouchTarget = industryConfig?.visual_requirements?.min_touch_target || 44;
elements.forEach(({ selector, name }) => {
const el = document.querySelector(selector);
if (!el) {
results.push({ element: name, found: false, visible: false, blocked: false, size: 0, minSizeMet: false, position: null });
return;
}
const rect = el.getBoundingClientRect();
const isInViewport = rect.top >= 0 && rect.top < window.innerHeight && rect.left >= 0 && rect.left < window.innerWidth;
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const overlapElements = document.elementsFromPoint(centerX, centerY);
const isBlocked = overlapElements.length > 1;
const computedStyle = window.getComputedStyle(el);
const isVisible = computedStyle.visibility !== 'hidden' && computedStyle.display !== 'none';
results.push({
element: name,
found: true,
visible: isInViewport && isVisible,
blocked: isBlocked,
size: rect.width * rect.height,
minSizeMet: rect.width >= minTouchTarget && rect.height >= minTouchTarget,
position: { x: rect.left, y: rect.top, width: rect.width, height: rect.height },
zIndex: computedStyle.zIndex,
color: computedStyle.color,
backgroundColor: computedStyle.backgroundColor,
fontStyle: {
fontSize: computedStyle.fontSize,
fontWeight: computedStyle.fontWeight
}
});
});
return results;
})([{"selector": "button[type='submit']", "name": "登录按钮"}, {"selector": "input[name='username']", "name": "用户名输入框"}, {"selector": "input[name='password']", "name": "密码输入框"}], {{行业配置}})
输出验证断言(必须逐条验证)
{
"all_elements_found": "每个element的found必须为true",
"all_elements_visible": "每个element的visible必须为true",
"no_elements_blocked": "每个element的blocked必须为false",
"all_elements_min_size": "每个element的minSizeMet必须为true"
}
证据门控(必须看到这些输出才能继续)
必须看到 playwright_navigate返回成功
必须看到 playwright_evaluate返回数组,包含3个元素
必须看到每个元素的 found: true
必须看到每个元素的 visible: true
失败处理
- 若任何元素
found: false:报告"元素未找到",停止执行 - 若任何元素
visible: false:报告"元素不可见",停止执行 - 若任何元素
blocked: true:报告"元素被遮挡",停止执行 - 若任何元素
minSizeMet: false:记录警告,继续执行
状态更新
{
"current_step": 2,
"step_name": "可见性检查",
"status": "completed/warning/error",
"evidence": ["playwright_navigate返回", "playwright_evaluate返回的JSON数组"],
"output": {
"visibility_results": [
{"element": "登录按钮", "found": true, "visible": true, "blocked": false, "minSizeMet": true, "position": {"x": 300, "y": 320, "width": 300, "height": 48}},
{"element": "用户名输入框", "found": true, "visible": true, "blocked": false, "minSizeMet": true, "position": {"x": 300, "y": 200, "width": 300, "height": 40}},
{"element": "密码输入框", "found": true, "visible": true, "blocked": false, "minSizeMet": true, "position": {"x": 300, "y": 260, "width": 300, "height": 40}}
]
},
"errors": []
}
步骤3:可达性检查
输入JSON
{
"test_url": "{{步骤1的test_url}}",
"industry": "{{步骤1的industry}}"
}
必须调用的工具
工具1: playwright_evaluate({"script": "可达性检查脚本"})
可达性检查脚本(必须一字不差执行)
(function checkAccessibility(industryConfig) {
const inputs = document.querySelectorAll('input, textarea, select');
const issues = [];
const isFinance = industryConfig?.name === '金融行业';
inputs.forEach(input => {
const label = input.labels ? input.labels[0] : null;
const ariaLabel = input.getAttribute('aria-label');
const placeholder = input.getAttribute('placeholder');
const hasAccessibleName = !!label || !!ariaLabel || !!placeholder;
if (!hasAccessibleName) {
issues.push({
type: 'missing_label',
element: input.name || input.id || 'unknown',
tag: input.tagName.toLowerCase(),
position: input.getBoundingClientRect(),
severity: 'high'
});
}
if (input.hasAttribute('required') && !input.hasAttribute('aria-required')) {
issues.push({
type: 'missing_required_aria',
element: input.name || input.id,
severity: 'medium'
});
}
if (!input.getAttribute('tabindex') && inputs.length > 1) {
issues.push({
type: 'missing_tabindex',
element: input.name || input.id,
severity: 'low'
});
}
if (isFinance && (input.type === 'password' || input.name?.includes('card')) && !input.getAttribute('autocomplete')) {
issues.push({
type: 'missing_autocomplete',
element: input.name || input.id,
severity: 'high',
message: '金融场景下敏感输入框必须设置autocomplete属性'
});
}
if (isFinance && input.type === 'password' && !input.getAttribute('aria-describedby')) {
issues.push({
type: 'missing_password_hint',
element: input.name || input.id,
severity: 'medium',
message: '金融场景下密码输入框应有提示说明'
});
}
});
return { issues, total_elements: inputs.length, accessible_elements: inputs.length - issues.length };
})({{行业配置}})
输出验证断言(必须逐条验证)
{
"has_issues": "issues数组长度必须为0",
"total_elements": "必须大于0",
"accessible_elements": "必须等于total_elements"
}
证据门控(必须看到这些输出才能继续)
必须看到 playwright_evaluate返回对象
必须看到 issues数组
必须看到 total_elements数值
必须看到 accessible_elements数值
失败处理
- 若
issues.length > 0:记录所有issue,继续执行但标记警告 - 若
total_elements === 0:报告"未找到表单元素",继续执行
状态更新
{
"current_step": 3,
"step_name": "可达性检查",
"status": "completed/warning",
"evidence": ["playwright_evaluate返回的JSON对象"],
"output": {
"accessibility_results": {
"issues": [],
"total_elements": 3,
"accessible_elements": 3
}
},
"errors": []
}
步骤4:交互流程检查
输入JSON
{
"test_url": "{{步骤1的test_url}}",
"industry": "{{步骤1的industry}}",
"steps": [
{"selector": "input[name='username']", "action": "fill", "name": "用户名输入框"},
{"selector": "input[name='password']", "action": "fill", "name": "密码输入框"},
{"selector": "button[type='submit']", "action": "click", "name": "登录按钮"}
]
}
必须调用的工具
工具1: playwright_evaluate({"script": "交互流程检查脚本"})
交互流程检查脚本(必须一字不差执行)
(function checkInteractionFlow(steps, industryConfig) {
const flowIssues = [];
const isEcommerce = industryConfig?.name === '电商行业';
steps.forEach((step, index) => {
const el = document.querySelector(step.selector);
if (!el) {
flowIssues.push({ step: index + 1, issue: 'element_not_found', expected: step.name, severity: 'high' });
return;
}
if (el.disabled) {
flowIssues.push({ step: index + 1, issue: 'element_disabled', expected: step.name, severity: 'high' });
}
if (el.offsetHeight === 0 || el.offsetWidth === 0) {
flowIssues.push({ step: index + 1, issue: 'element_not_rendered', expected: step.name, severity: 'high' });
}
const computedStyle = window.getComputedStyle(el);
if (computedStyle.pointerEvents === 'none') {
flowIssues.push({ step: index + 1, issue: 'element_not_interactable', expected: step.name, severity: 'high' });
}
if (isEcommerce && step.action === 'click' && step.name.includes('购买')) {
const rect = el.getBoundingClientRect();
if (rect.top > window.innerHeight) {
flowIssues.push({ step: index + 1, issue: 'cta_not_in_viewport', expected: step.name, severity: 'high', message: '电商场景下主CTA必须在首屏可见' });
}
}
});
if (isEcommerce && steps.length > 3) {
flowIssues.push({ step: 0, issue: 'too_many_steps', expected: '购买流程', severity: 'medium', message: '电商场景下购买流程步骤不应超过3步' });
}
return { flowIssues, total_steps: steps.length, valid_steps: steps.length - flowIssues.filter(i => i.severity === 'high').length };
})([{"selector": "input[name='username']", "action": "fill", "name": "用户名输入框"}, {"selector": "input[name='password']", "action": "fill", "name": "密码输入框"}, {"selector": "button[type='submit']", "action": "click", "name": "登录按钮"}], {{行业配置}})
输出验证断言(必须逐条验证)
{
"has_high_severity_issues": "flowIssues中severity='high'的数量必须为0",
"total_steps": "必须大于0",
"valid_steps": "必须大于0"
}
证据门控(必须看到这些输出才能继续)
必须看到 playwright_evaluate返回对象
必须看到 flowIssues数组
必须看到 total_steps数值
失败处理
- 若
flowIssues中有severity='high'的问题:停止执行,报告严重问题 - 若只有
severity='medium'的问题:记录警告,继续执行
状态更新
{
"current_step": 4,
"step_name": "交互流程检查",
"status": "completed/warning/error",
"evidence": ["playwright_evaluate返回的JSON对象"],
"output": {
"interaction_results": {
"flowIssues": [],
"total_steps": 3,
"valid_steps": 3
}
},
"errors": []
}
步骤4.5:视觉体验检查(新增)
输入JSON
{
"test_url": "{{步骤1的test_url}}",
"industry": "{{步骤1的industry}}",
"visibility_results": "{{步骤2的visibility_results}}",
"elements": [
{"selector": "button[type='submit']", "name": "登录按钮"},
{"selector": "input[name='username']", "name": "用户名输入框"},
{"selector": "input[name='password']", "name": "密码输入框"}
]
}
必须调用的工具(必须按顺序)
工具1: playwright_screenshot()
工具2: playwright_evaluate({"script": "颜色对比度检查脚本"})
工具3: playwright_evaluate({"script": "视觉遮挡检查脚本"})
工具4: playwright_evaluate({"script": "视觉层级显著性检查脚本"})
颜色对比度检查脚本(必须一字不差执行)
(function checkContrast(elements, industryConfig) {
const contrastRatio = industryConfig?.visual_requirements?.contrast_ratio || 'AA';
const requiredRatio = contrastRatio === 'AAA' ? 7.0 : 4.5;
const issues = [];
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
function getContrastRatio(color1, color2) {
const l1 = 0.2126 * color1.r + 0.7152 * color1.g + 0.0722 * color1.b;
const l2 = 0.2126 * color2.r + 0.7152 * color2.g + 0.0722 * color2.b;
return l1 > l2 ? (l1 + 0.05) / (l2 + 0.05) : (l2 + 0.05) / (l1 + 0.05);
}
elements.forEach(({ selector, name }) => {
const el = document.querySelector(selector);
if (!el) return;
const computedStyle = window.getComputedStyle(el);
const textColor = hexToRgb(computedStyle.color);
const bgColor = hexToRgb(computedStyle.backgroundColor);
if (textColor && bgColor) {
const ratio = getContrastRatio(textColor, bgColor);
if (ratio < requiredRatio) {
issues.push({
type: 'contrast_ratio_failure',
element: name,
selector: selector,
current_ratio: ratio.toFixed(2),
required_ratio: requiredRatio,
level: contrastRatio,
severity: ratio < 3.0 ? 'high' : 'medium',
message: `${name}的颜色对比度(${ratio.toFixed(2)})未达到${contrastRatio}标准(要求${requiredRatio})`
});
}
}
const placeholder = el.getAttribute('placeholder');
if (placeholder) {
const placeholderColor = hexToRgb(computedStyle.webkitTextFillColor || computedStyle.color);
if (placeholderColor && bgColor) {
const placeholderRatio = getContrastRatio(placeholderColor, bgColor);
if (placeholderRatio < 4.5) {
issues.push({
type: 'placeholder_contrast_failure',
element: name,
selector: selector,
current_ratio: placeholderRatio.toFixed(2),
severity: 'medium',
message: `${name}的占位符颜色对比度不足`
});
}
}
}
});
return { issues, required_contrast_level: contrastRatio, passed: issues.length === 0 };
})([{"selector": "button[type='submit']", "name": "登录按钮"}, {"selector": "input[name='username']", "name": "用户名输入框"}, {"selector": "input[name='password']", "name": "密码输入框"}], {{行业配置}})
视觉遮挡检查脚本(必须一字不差执行)
(function checkVisualOcclusion(elements) {
const issues = [];
elements.forEach(({ selector, name }) => {
const el = document.querySelector(selector);
if (!el) return;
const rect = el.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const elementsAtCenter = document.elementsFromPoint(centerX, centerY);
const topElement = elementsAtCenter[0];
const isBlocked = elementsAtCenter.length > 1;
if (isBlocked) {
const blockerInfo = [];
elementsAtCenter.forEach((blocker, idx) => {
if (idx > 0 && blocker !== el) {
blockerInfo.push({
tag: blocker.tagName,
className: blocker.className,
id: blocker.id
});
}
});
issues.push({
type: 'visual_occlusion',
element: name,
selector: selector,
position: { x: rect.left, y: rect.top },
is_blocked: isBlocked,
blockers: blockerInfo,
severity: 'high',
message: `${name}被其他元素遮挡,可能影响用户点击`
});
}
const isPartiallyHidden = rect.bottom > window.innerHeight || rect.right > window.innerWidth;
if (isPartiallyHidden) {
issues.push({
type: 'partially_hidden',
element: name,
selector: selector,
position: { x: rect.left, y: rect.top },
severity: 'medium',
message: `${name}部分超出可视区域`
});
}
});
return { issues, passed: issues.length === 0 };
})([{"selector": "button[type='submit']", "name": "登录按钮"}, {"selector": "input[name='username']", "name": "用户名输入框"}, {"selector": "input[name='password']", "name": "密码输入框"}])
视觉层级显著性检查脚本(必须一字不差执行)
(function checkVisualHierarchy(industryConfig) {
const issues = [];
const isEcommerce = industryConfig?.name === '电商行业';
const isFinance = industryConfig?.name === '金融行业';
const buttons = [];
document.querySelectorAll('button, [role="button"]').forEach(el => {
const rect = el.getBoundingClientRect();
const computedStyle = window.getComputedStyle(el);
buttons.push({
text: el.textContent?.trim() || '',
position: { x: rect.left, y: rect.top },
size: rect.width * rect.height,
width: rect.width,
height: rect.height,
fontWeight: computedStyle.fontWeight,
fontSize: computedStyle.fontSize,
bgColor: computedStyle.backgroundColor,
inViewport: rect.top >= 0 && rect.top < window.innerHeight
});
});
const sortedButtons = [...buttons].sort((a, b) => b.size - a.size);
const largestButton = sortedButtons[0];
if (isEcommerce) {
const ctaButtons = buttons.filter(b => b.text.includes('购买') || b.text.includes('立即') || b.text.includes('下单') || b.text.includes('加入购物车'));
if (ctaButtons.length > 0) {
const primaryCta = ctaButtons[0];
if (!primaryCta.inViewport) {
issues.push({
type: 'cta_not_visible',
element: primaryCta.text,
severity: 'high',
message: `电商场景下主CTA"${primaryCta.text}"不在首屏可见区域`
});
}
if (largestButton && primaryCta.size < largestButton.size * 0.8) {
issues.push({
type: 'cta_not_prominent',
element: primaryCta.text,
severity: 'medium',
message: `电商场景下主CTA"${primaryCta.text}"的尺寸不是页面最大的按钮`
});
}
}
}
if (isFinance) {
const warningElements = document.querySelectorAll('[role="alert"], .error, .warning, .danger');
warningElements.forEach(el => {
const rect = el.getBoundingClientRect();
const computedStyle = window.getComputedStyle(el);
const textColor = computedStyle.color;
if (!textColor.includes('red') && !textColor.includes('dc2626') && !textColor.includes('ef4444')) {
issues.push({
type: 'warning_color_not_prominent',
element: el.textContent?.trim() || '警告元素',
severity: 'medium',
message: '金融场景下警告/错误信息应使用红色等醒目的颜色'
});
}
});
}
const headings = document.querySelectorAll('h1, h2, h3');
headings.forEach((heading, idx) => {
const rect = heading.getBoundingClientRect();
if (!rect.width || !rect.height) {
issues.push({
type: 'heading_not_rendered',
element: heading.tagName,
severity: 'low',
message: `${heading.tagName}标题未渲染`
});
}
});
return { issues, button_count: buttons.length, heading_count: headings.length, passed: issues.length === 0 };
})({{行业配置}})
输出验证断言(必须逐条验证)
{
"screenshot_taken": "playwright_screenshot必须返回截图",
"contrast_check_completed": "对比度检查脚本必须返回对象",
"occlusion_check_completed": "遮挡检查脚本必须返回对象",
"hierarchy_check_completed": "视觉层级检查脚本必须返回对象",
"no_high_severity_visual_issues": "所有检查的issues中severity='high'的数量必须为0"
}
证据门控(必须看到这些输出才能继续)
必须看到 playwright_screenshot返回截图
必须看到3个 playwright_evaluate返回对象
必须看到每个检查的 passed字段
必须看到每个检查的 issues数组
失败处理
- 若
playwright_screenshot失败:记录警告,继续执行(截图为辅助验证) - 若任何检查有
severity='high'的问题:记录严重警告,继续执行 - 若所有检查都失败:报告"视觉体验严重不达标"
状态更新
{
"current_step": 4.5,
"step_name": "视觉体验检查",
"status": "completed/warning",
"evidence": ["playwright_screenshot截图", "3个playwright_evaluate返回对象"],
"output": {
"visual_results": {
"contrast_check": { "issues": [], "passed": true, "required_contrast_level": "AA" },
"occlusion_check": { "issues": [], "passed": true },
"hierarchy_check": { "issues": [], "passed": true, "button_count": 3, "heading_count": 2 }
}
},
"errors": []
}
步骤5:行为推理模拟
输入JSON
{
"test_url": "{{步骤1的test_url}}",
"industry": "{{步骤1的industry}}",
"user_profile_type": "{{步骤1的user_profile_type}}",
"ux_rules": "{{应用行业权重后的UX规则}}",
"visibility_results": "{{步骤2的visibility_results}}",
"visual_results": "{{步骤4.5的visual_results}}"
}
必须调用的工具
工具1: playwright_navigate({"url": "{{test_url}}"})
工具2: playwright_get_visible_html()
工具3: playwright_get_visible_text()
工具4: playwright_evaluate({"script": "行为推理脚本"})
行为推理脚本(必须一字不差执行)
(function simulateUserBehavior(userProfile, uxRules, visibilityResults, visualResults, industryConfig) {
const formElements = [];
const buttons = [];
const links = [];
document.querySelectorAll('input, textarea, select').forEach(el => {
const rect = el.getBoundingClientRect();
formElements.push({
tag: el.tagName.toLowerCase(),
type: el.type,
name: el.name || el.id,
placeholder: el.getAttribute('placeholder'),
label: el.labels ? el.labels[0]?.textContent?.trim() : '',
position: { x: rect.left, y: rect.top, width: rect.width, height: rect.height },
order: rect.top
});
});
document.querySelectorAll('button, [role="button"]').forEach(el => {
const rect = el.getBoundingClientRect();
const computedStyle = window.getComputedStyle(el);
const isProminent = computedStyle.backgroundColor.includes('red') ||
computedStyle.backgroundColor.includes('orange') ||
computedStyle.backgroundColor.includes('ef4444') ||
computedStyle.fontWeight === 'bold' ||
rect.width > 150 || rect.height > 40;
buttons.push({
tag: el.tagName.toLowerCase(),
text: el.textContent?.trim() || '',
role: el.getAttribute('role'),
position: { x: rect.left, y: rect.top, width: rect.width, height: rect.height },
isProminent: isProminent,
color: computedStyle.backgroundColor,
inViewport: rect.top >= 0 && rect.top < window.innerHeight
});
});
formElements.sort((a, b) => a.order - b.order);
const expectedSteps = [];
const isExpert = userProfile === 'expert_user';
formElements.forEach((el, index) => {
const rule = uxRules.find(r => r.rule_id === 'RULE_003');
expectedSteps.push({
step_index: expectedSteps.length + 1,
action: 'fill',
target: {
tag: el.tag,
role: 'textbox',
name: el.label || el.placeholder || el.name,
selector: `input[name="${el.name}"]`,
position: el.position,
in_viewport: true
},
reasoning: `RULE_003: 用户按从上到下顺序填写表单,第${index + 1}个字段`,
rule_weight: rule?.weight || 0.95,
confidence: isExpert ? 0.95 : 0.85
});
});
let primaryButton = null;
const isEcommerce = industryConfig?.name === '电商行业';
const isFinance = industryConfig?.name === '金融行业';
if (isEcommerce) {
primaryButton = buttons.find(b => b.isProminent && (b.text.includes('购买') || b.text.includes('立即') || b.text.includes('下单'))) ||
buttons.find(b => b.text.includes('购买') || b.text.includes('提交')) ||
buttons.find(b => b.isProminent) || buttons[0];
} else if (isFinance) {
primaryButton = buttons.find(b => b.text.includes('确认') || b.text.includes('提交') || b.text.includes('登录')) ||
buttons.find(b => b.isProminent) || buttons[0];
} else {
primaryButton = buttons.find(b => b.isProminent && (b.text.includes('登录') || b.text.includes('提交') || b.text.includes('确定'))) ||
buttons.find(b => b.text.includes('登录') || b.text.includes('提交')) ||
buttons[0];
}
if (primaryButton) {
const rule = uxRules.find(r => r.rule_id === 'RULE_002');
expectedSteps.push({
step_index: expectedSteps.length + 1,
action: 'click',
target: {
tag: primaryButton.tag,
role: 'button',
name: primaryButton.text,
selector: `button:has-text("${primaryButton.text}")`,
position: primaryButton.position,
in_viewport: primaryButton.inViewport
},
reasoning: `RULE_002: 用户优先点击视觉显著的${isEcommerce ? 'CTA' : '操作'}按钮`,
rule_weight: rule?.weight || 0.9,
confidence: primaryButton.inViewport ? 0.9 : 0.7
});
}
return {
path_id: "PATH_INFERRED_" + Date.now(),
path_name: "推理模拟人类操作路径",
target_url: window.location.href,
created_at: new Date().toISOString(),
industry: industryConfig?.name || '通用行业',
user_profile: userProfile,
reasoning_rules: uxRules.map(r => r.rule_id),
steps: expectedSteps,
total_steps: expectedSteps.length,
form_elements_count: formElements.length,
buttons_count: buttons.length,
visual_considerations: visualResults?.hierarchy_check?.issues || []
};
})({{user_profile_type}}, {{ux_rules}}, {{visibility_results}}, {{visual_results}}, {{行业配置}})
输出验证断言(必须逐条验证)
{
"path_record_valid": "必须返回有效JSON对象",
"has_path_id": "必须包含path_id字段",
"has_steps": "steps数组长度必须大于0",
"has_reasoning": "每个step必须包含reasoning字段",
"has_rule_weight": "每个step必须包含rule_weight字段",
"total_steps": "必须大于0",
"has_industry": "必须包含industry字段",
"has_user_profile": "必须包含user_profile字段"
}
证据门控(必须看到这些输出才能继续)
必须看到 playwright_navigate返回成功
必须看到 playwright_get_visible_html返回HTML
必须看到 playwright_get_visible_text返回文本
必须看到 playwright_evaluate返回推理路径对象
必须看到 steps数组
必须看到每个step的 reasoning和rule_weight字段
失败处理
- 若推理路径为空:报告"无法推理出人类操作路径",停止执行
- 若缺少reasoning或rule_weight字段:记录警告,继续执行
状态更新
{
"current_step": 5,
"step_name": "行为推理模拟",
"status": "completed/warning/error",
"evidence": ["所有4个工具调用的返回结果"],
"output": {
"inferred_path_file": "paths/inferred_login_path.json",
"inferred_path_record": {
"path_id": "PATH_INFERRED_1234567890",
"path_name": "推理模拟人类操作路径",
"industry": "电商行业",
"user_profile": "new_user",
"steps": [/* 推理步骤 */],
"total_steps": 3
}
},
"errors": []
}
步骤6:AI执行测试路径
输入JSON
{
"test_url": "{{步骤1的test_url}}",
"inferred_path": "{{步骤5的inferred_path_record}}"
}
必须调用的工具(必须按顺序)
工具1: playwright_navigate({"url": "{{test_url}}"})
工具2: playwright_click({"selector": "input[name='username']"})
工具3: playwright_fill({"selector": "input[name='username']", "value": "{{test_username}}"})
工具4: playwright_click({"selector": "input[name='password']"})
工具5: playwright_fill({"selector": "input[name='password']", "value": "{{test_password}}"})
工具6: playwright_click({"selector": "button[type='submit']"})
工具7: playwright_evaluate({"script": "提取AI路径记录脚本"})
提取AI路径记录脚本(必须一字不差执行)
(function extractAIPathRecord() {
const actions = [];
document.querySelectorAll('input, textarea, select, button').forEach(el => {
const rect = el.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
actions.push({
tag: el.tagName.toLowerCase(),
role: el.getAttribute('role'),
name: el.textContent?.trim() || el.getAttribute('aria-label') || '',
selector: el.getAttribute('data-testid') || el.id || '',
position: { x: rect.left, y: rect.top, width: rect.width, height: rect.height },
in_viewport: rect.top >= 0 && rect.top < window.innerHeight
});
}
});
return {
path_id: "PATH_AI_" + Date.now(),
path_name: "AI操作路径记录",
target_url: window.location.href,
created_at: new Date().toISOString(),
steps: [],
page_elements: actions
};
})()
输出验证断言(必须逐条验证)
{
"navigation_success": "playwright_navigate必须返回成功",
"click_success": "每个playwright_click必须返回成功",
"fill_success": "每个playwright_fill必须返回成功",
"ai_path_record_valid": "playwright_evaluate必须返回有效JSON"
}
证据门控(必须看到这些输出才能继续)
必须看到所有7个工具调用的返回结果
必须看到 playwright_evaluate返回的路径记录对象
失败处理
- 若任何工具调用失败:停止执行,报告失败原因
状态更新
{
"current_step": 6,
"step_name": "AI执行测试路径",
"status": "completed/error",
"evidence": ["所有7个工具调用的返回结果"],
"output": {
"ai_path_file": "paths/ai_login_path.json",
"ai_path_record": {
"path_id": "PATH_AI_1234567890",
"path_name": "AI操作路径记录",
"steps": [/* AI执行步骤 */]
}
},
"errors": []
}
步骤7:路径偏差分析(含多模态验证)
输入JSON
{
"inferred_path": "{{步骤5的inferred_path_record}}",
"ai_path": "{{步骤6的ai_path_record}}",
"expected_score": 0.8,
"test_url": "{{步骤1的test_url}}",
"industry": "{{步骤1的industry}}"
}
必须调用的工具
工具1: playwright_screenshot()(仅当requires_visual_verification为true时触发)
工具2: 纯JavaScript计算(偏差分析)
路径偏差分析脚本(必须一字不差执行)
(function analyzePathDeviation(inferredPath, aiPath, industryConfig) {
const inferredSteps = inferredPath.steps || [];
const aiSteps = aiPath.steps || [];
const isFinance = industryConfig?.name === '金融行业';
function compareSteps(iStep, aStep) {
if (!iStep.target || !aStep.target) return 0;
let score = 0;
if (iStep.action === aStep.action) score += 0.3;
if (iStep.target.role === aStep.target.role) score += 0.2;
const iName = iStep.target.name || '';
const aName = aStep.target.name || '';
if (iName && aName && (iName.includes(aName) || aName.includes(iName))) score += 0.3;
if (iStep.target.tag === aStep.target.tag) score += 0.1;
if (iStep.target.position && aStep.target.position) {
const dist = Math.sqrt(Math.pow(iStep.target.position.x - aStep.target.position.x, 2) + Math.pow(iStep.target.position.y - aStep.target.position.y, 2));
if (dist < 30) score += 0.1;
else if (dist < 60) score += 0.05;
}
return Math.min(score, 1.0);
}
const mappings = [];
let aiIndex = 0;
for (const iStep of inferredSteps) {
let bestMatch = null;
let bestScore = 0;
let matchedIndex = -1;
for (let i = aiIndex; i < aiSteps.length; i++) {
const aStep = aiSteps[i];
const score = compareSteps(iStep, aStep);
if (score > bestScore) {
bestScore = score;
bestMatch = aStep;
matchedIndex = i;
}
if (score >= 0.7) {
aiIndex = i + 1;
break;
}
}
if (matchedIndex >= 0) aiIndex = matchedIndex + 1;
mappings.push({ inferred_step: iStep, ai_step: bestMatch, match_score: bestScore });
}
const totalScore = mappings.reduce((sum, m) => sum + m.match_score, 0);
const deviationScore = mappings.length > 0 ? Number((totalScore / mappings.length).toFixed(2)) : 0;
const issues = [];
const needsVisualVerification = [];
mappings.forEach((m, i) => {
if (m.match_score < 0.7) {
const needsVisual = m.match_score >= 0.4 && m.match_score < 0.7;
const severity = isFinance && m.match_score < 0.5 ? 'critical' : (m.match_score < 0.3 ? 'high' : 'medium');
issues.push({
type: 'path_deviation',
step: i + 1,
inferred_action: m.inferred_step.action,
inferred_target: m.inferred_step.target?.name || 'unknown',
ai_action: m.ai_step?.action || 'none',
ai_target: m.ai_step?.target?.name || 'none',
match_score: m.match_score,
severity: severity,
needs_visual_verification: needsVisual,
industry_context: isFinance ? '金融场景下路径偏差可能影响安全操作' : undefined
});
if (needsVisual) needsVisualVerification.push(i + 1);
}
});
return {
step_mapping: mappings,
deviation_score: deviationScore,
issues: issues,
needs_visual_verification: needsVisualVerification,
inferred_steps_count: inferredSteps.length,
ai_steps_count: aiSteps.length,
matched_steps: mappings.filter(m => m.match_score >= 0.7).length,
requires_screenshot: needsVisualVerification.length > 0,
industry_weighted_score: isFinance ? Math.min(deviationScore + 0.05, 1.0) : deviationScore
};
})({{步骤5的inferred_path_record}}, {{步骤6的ai_path_record}}, {{行业配置}})
多模态视觉验证(当requires_screenshot为true时执行)
// 1. 调用playwright_screenshot()获取当前页面截图
// 2. 使用VLM分析截图中两个位置的元素
// 3. 判断是否为同一功能目标(如不同布局下的同一按钮)
// 4. 返回验证结果:{ verified: true/false, confidence: 0-1, explanation: "..." }
输出验证断言(必须逐条验证)
{
"deviation_score": "必须为0-1之间的数字",
"inferred_steps_count": "必须大于0",
"ai_steps_count": "必须大于0",
"matched_steps": "必须大于0",
"has_industry_weighted_score": "必须包含industry_weighted_score字段"
}
证据门控(必须看到这些输出才能继续)
必须看到偏差分数 deviation_score
必须看到 issues数组
必须看到步骤计数
必须看到 industry_weighted_score
若 requires_screenshot: true:必须看到playwright_screenshot返回截图
失败处理
- 若
industry_weighted_score < expected_score:记录警告,继续执行 - 若
matched_steps === 0:报告"无匹配步骤",停止执行 - 若金融场景下有
severity='critical'的问题:停止执行
状态更新
{
"current_step": 7,
"step_name": "路径偏差分析",
"status": "completed/warning/error",
"evidence": ["偏差分析结果JSON", "截图(如需要)"],
"output": {
"deviation_analysis": {
"deviation_score": 0.85,
"industry_weighted_score": 0.85,
"issues": [],
"needs_visual_verification": [],
"inferred_steps_count": 3,
"ai_steps_count": 3,
"matched_steps": 3,
"requires_screenshot": false
}
},
"errors": []
}
步骤8:生成验收报告
输入JSON
{
"test_url": "{{步骤1的test_url}}",
"industry": "{{步骤1的industry}}",
"user_profile_type": "{{步骤1的user_profile_type}}",
"visibility_results": "{{步骤2的visibility_results}}",
"accessibility_results": "{{步骤3的accessibility_results}}",
"interaction_results": "{{步骤4的interaction_results}}",
"visual_results": "{{步骤4.5的visual_results}}",
"deviation_analysis": "{{步骤7的deviation_analysis}}",
"inferred_path": "{{步骤5的inferred_path_record}}",
"expected_score": 0.8
}
必须调用的工具
无 - 此步骤为报告生成
验收报告模板(必须按此格式输出)
{
"report_id": "REPORT_" + Date.now(),
"report_name": "体验验收报告",
"generated_at": new Date().toISOString(),
"target_url": "{{test_url}}",
"industry_profile": {
"name": "{{industry对应名称}}",
"priority": "{{行业优先级}}",
"visual_requirements": "{{行业视觉要求}}"
},
"user_profile": "{{user_profile_type}}",
"dom_check": {
"visibility": {
"passed": "{{visibility_results全部通过}}",
"details": "{{visibility_results}}"
},
"accessibility": {
"passed": "{{accessibility_results.issues.length === 0}}",
"issues": "{{accessibility_results.issues}}"
},
"interaction": {
"passed": "{{interaction_results.flowIssues中无high级别问题}}",
"flowIssues": "{{interaction_results.flowIssues}}"
}
},
"visual_check": {
"contrast": {
"passed": "{{visual_results.contrast_check.passed}}",
"level": "{{visual_results.contrast_check.required_contrast_level}}",
"issues": "{{visual_results.contrast_check.issues}}"
},
"occlusion": {
"passed": "{{visual_results.occlusion_check.passed}}",
"issues": "{{visual_results.occlusion_check.issues}}"
},
"hierarchy": {
"passed": "{{visual_results.hierarchy_check.passed}}",
"button_count": "{{visual_results.hierarchy_check.button_count}}",
"issues": "{{visual_results.hierarchy_check.issues}}"
}
},
"behavior_simulation": {
"industry": "{{inferred_path.industry}}",
"user_profile": "{{inferred_path.user_profile}}",
"reasoning_rules": "{{inferred_path.reasoning_rules}}",
"total_steps": "{{inferred_path.total_steps}}",
"visual_considerations": "{{inferred_path.visual_considerations}}"
},
"path_deviation": {
"deviation_score": "{{deviation_analysis.deviation_score}}",
"industry_weighted_score": "{{deviation_analysis.industry_weighted_score}}",
"inferred_steps_count": "{{deviation_analysis.inferred_steps_count}}",
"ai_steps_count": "{{deviation_analysis.ai_steps_count}}",
"matched_steps": "{{deviation_analysis.matched_steps}}",
"issues": "{{deviation_analysis.issues}}",
"needs_visual_verification": "{{deviation_analysis.needs_visual_verification}}"
},
"summary": {
"functional_test": "{{所有DOM检查通过 ? 'passed' : 'failed'}}",
"visual_test": "{{所有视觉检查通过 ? 'passed' : 'warning/failed'}}",
"experience_test": "{{industry_weighted_score >= expected_score ? 'passed' : 'failed'}}",
"overall_score": "{{综合分数}}",
"industry_alignment": "{{行业适配度}}",
"recommendations": ["{{基于问题的建议}}"]
}
}
输出验证断言(必须逐条验证)
{
"report_valid": "必须为有效的JSON对象",
"has_summary": "必须包含summary字段",
"has_scores": "必须包含deviation_score和industry_weighted_score",
"has_recommendations": "必须包含recommendations数组",
"has_visual_check": "必须包含visual_check字段",
"has_industry_profile": "必须包含industry_profile字段",
"has_behavior_simulation": "必须包含behavior_simulation字段"
}
证据门控(必须看到这些输出才能结束)
必须看到完整的验收报告JSON
必须看到 summary.functional_test状态
必须看到 summary.visual_test状态
必须看到 summary.experience_test状态
必须看到 summary.overall_score
必须看到 summary.industry_alignment
状态更新(最终状态)
{
"current_step": 8,
"step_name": "生成验收报告",
"status": "completed",
"evidence": ["完整验收报告"],
"output": {
"acceptance_report": {
"report_id": "REPORT_1234567890",
"industry_profile": { "name": "电商行业", "priority": "转化优先" },
"user_profile": "new_user",
"summary": {
"functional_test": "passed",
"visual_test": "passed",
"experience_test": "passed",
"overall_score": 0.88,
"industry_alignment": 0.92,
"recommendations": []
}
}
},
"errors": []
}
执行结果判定规则
通过条件(必须全部满足)
步骤2-4的DOM检查全部通过(无high级别问题)
步骤4.5的视觉体验检查通过(无high级别问题)
步骤5的行为推理模拟成功生成路径
步骤6的AI路径执行成功
步骤7的industry_weighted_score >= 0.8
步骤8的验收报告生成成功
警告条件(允许通过但需注意)
可见性检查有元素尺寸不达标
可达性检查有少量标签缺失
视觉体验检查有medium级别问题
偏差分数在0.7-0.8之间
需要视觉验证的偏差项 > 0
失败条件(任意满足即失败)
任何步骤出现错误停止
关键元素未找到或不可见
视觉体验检查有high级别问题
行为推理无法生成路径
industry_weighted_score < 0.7
金融场景下有critical级别路径偏差
无匹配步骤
工具调用映射表
| 功能 | Playwright MCP工具 | 使用步骤 |
|---|---|---|
| 页面导航 | playwright_navigate(url) |
步骤2, 5, 6 |
| DOM获取 | playwright_get_visible_html() |
步骤5 |
| 文本提取 | playwright_get_visible_text() |
步骤5 |
| 元素点击 | playwright_click(selector) |
步骤6 |
| 表单填写 | playwright_fill(selector, value) |
步骤6 |
| JS执行 | playwright_evaluate(script) |
步骤2, 3, 4, 4.5, 5, 6 |
| 截图 | playwright_screenshot() |
步骤4.5(必须)、步骤7(条件触发) |
行业Profile动态适配规则
金融行业(安全优先)
| 规则 | 默认权重 | 金融权重 | 说明 |
|---|---|---|---|
| RULE_001 | 0.8 | 0.7 | 降低位置偏好 |
| RULE_002 | 0.9 | 0.6 | 降低视觉显著性权重 |
| RULE_003 | 0.95 | 0.95 | 保持表单顺序 |
| RULE_004 | 0.85 | 0.9 | 提升明确文字权重 |
| RULE_005 | 0.9 | 0.95 | 提升弹窗处理权重 |
| RULE_006 | 0.8 | 0.9 | 提升标签阅读权重 |
| RULE_007 | 0.7 | 0.85 | 提升等待耐心权重 |
| RULE_008 | 0.95 | 0.98 | 提升错误修正权重 |
电商行业(转化优先)
| 规则 | 默认权重 | 电商权重 | 说明 |
|---|---|---|---|
| RULE_001 | 0.8 | 0.85 | 提升位置偏好 |
| RULE_002 | 0.9 | 0.95 | 提升视觉显著性权重 |
| RULE_003 | 0.95 | 0.85 | 降低表单顺序权重 |
| RULE_004 | 0.85 | 0.8 | 降低明确文字权重 |
| RULE_005 | 0.9 | 0.75 | 降低弹窗处理权重 |
| RULE_006 | 0.8 | 0.7 | 降低标签阅读权重 |
| RULE_007 | 0.7 | 0.6 | 降低等待耐心权重 |
| RULE_008 | 0.95 | 0.8 | 降低错误修正权重 |
文档版本: v3.0
最后更新: 2026-07-22
核心升级: 完整多模态视觉体验检查 + 行业领域规则动态适配
防幻觉机制: 证据门控 + 硬性断言 + 状态锚点
防注意力偏移: 分块原子任务 + 状态JSON传递