从功能断言到体验验收:基于 MCP 的三桥墩 AI Agent 测试架构 v3.0》,绝对会被官方加精!

体验测试执行型Prompt - 三桥墩架构(v3.0)

类型: 模型可直接执行的指令集
防幻觉机制: 证据门控 + 硬性断言 + 状态锚点
防注意力偏移: 分块原子任务 + 状态JSON传递
核心升级: 完整多模态视觉体验检查 + 行业领域规则动态适配
版本: v3.0


:locked: 执行约束(必须严格遵守)

禁止行为

  • :cross_mark: 不得简化流程步骤
  • :cross_mark: 不得跳过任何检查点
  • :cross_mark: 不得在无工具输出证据时声称成功
  • :cross_mark: 不得擅自修改操作路径
  • :cross_mark: 不得编造工具返回结果
  • :cross_mark: 不得使用固定值(如固定用户名/密码),必须参数化
  • :cross_mark: 不得使用RPA录制方式获取人类路径,必须使用推理模拟
  • :cross_mark: 不得跳过视觉体验检查步骤(步骤4.5)

强制要求

  • :white_check_mark: 每步必须输出当前状态JSON
  • :white_check_mark: 每步必须验证工具返回值
  • :white_check_mark: 失败必须立即停止并报告
  • :white_check_mark: 必须使用指定的Playwright MCP工具
  • :white_check_mark: 必须按顺序执行,不得并行跳过
  • :white_check_mark: 必须基于用户画像和领域UX常识推理模拟人类路径
  • :white_check_mark: 必须执行视觉体验检查步骤

:clipboard: 当前状态锚点(每步更新)

{
  "current_step": 0,
  "step_name": "等待启动",
  "status": "pending",
  "evidence": [],
  "output": {},
  "errors": []
}

:bullseye: 行业预设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": [
        "随意浏览",
        "容易被吸引",
        "耐心有限",
        "需要明确引导"
      ]
    }
  }
}

:rocket: 步骤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": []
}

:rocket: 步骤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"
}

证据门控(必须看到这些输出才能继续)

  • :white_check_mark: 必须看到 playwright_navigate 返回成功
  • :white_check_mark: 必须看到 playwright_evaluate 返回数组,包含3个元素
  • :white_check_mark: 必须看到每个元素的 found: true
  • :white_check_mark: 必须看到每个元素的 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": []
}

:rocket: 步骤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"
}

证据门控(必须看到这些输出才能继续)

  • :white_check_mark: 必须看到 playwright_evaluate 返回对象
  • :white_check_mark: 必须看到 issues 数组
  • :white_check_mark: 必须看到 total_elements 数值
  • :white_check_mark: 必须看到 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": []
}

:rocket: 步骤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"
}

证据门控(必须看到这些输出才能继续)

  • :white_check_mark: 必须看到 playwright_evaluate 返回对象
  • :white_check_mark: 必须看到 flowIssues 数组
  • :white_check_mark: 必须看到 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": []
}

:rocket: 步骤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"
}

证据门控(必须看到这些输出才能继续)

  • :white_check_mark: 必须看到 playwright_screenshot 返回截图
  • :white_check_mark: 必须看到3个 playwright_evaluate 返回对象
  • :white_check_mark: 必须看到每个检查的 passed 字段
  • :white_check_mark: 必须看到每个检查的 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": []
}

:rocket: 步骤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字段"
}

证据门控(必须看到这些输出才能继续)

  • :white_check_mark: 必须看到 playwright_navigate 返回成功
  • :white_check_mark: 必须看到 playwright_get_visible_html 返回HTML
  • :white_check_mark: 必须看到 playwright_get_visible_text 返回文本
  • :white_check_mark: 必须看到 playwright_evaluate 返回推理路径对象
  • :white_check_mark: 必须看到 steps 数组
  • :white_check_mark: 必须看到每个step的 reasoningrule_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": []
}

:rocket: 步骤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"
}

证据门控(必须看到这些输出才能继续)

  • :white_check_mark: 必须看到所有7个工具调用的返回结果
  • :white_check_mark: 必须看到 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": []
}

:rocket: 步骤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字段"
}

证据门控(必须看到这些输出才能继续)

  • :white_check_mark: 必须看到偏差分数 deviation_score
  • :white_check_mark: 必须看到 issues 数组
  • :white_check_mark: 必须看到步骤计数
  • :white_check_mark: 必须看到 industry_weighted_score
  • :white_check_mark: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": []
}

:rocket: 步骤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字段"
}

证据门控(必须看到这些输出才能结束)

  • :white_check_mark: 必须看到完整的验收报告JSON
  • :white_check_mark: 必须看到 summary.functional_test 状态
  • :white_check_mark: 必须看到 summary.visual_test 状态
  • :white_check_mark: 必须看到 summary.experience_test 状态
  • :white_check_mark: 必须看到 summary.overall_score
  • :white_check_mark: 必须看到 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": []
}

:bar_chart: 执行结果判定规则

通过条件(必须全部满足)

  1. :white_check_mark: 步骤2-4的DOM检查全部通过(无high级别问题)
  2. :white_check_mark: 步骤4.5的视觉体验检查通过(无high级别问题)
  3. :white_check_mark: 步骤5的行为推理模拟成功生成路径
  4. :white_check_mark: 步骤6的AI路径执行成功
  5. :white_check_mark: 步骤7的industry_weighted_score >= 0.8
  6. :white_check_mark: 步骤8的验收报告生成成功

警告条件(允许通过但需注意)

  1. :warning: 可见性检查有元素尺寸不达标
  2. :warning: 可达性检查有少量标签缺失
  3. :warning: 视觉体验检查有medium级别问题
  4. :warning: 偏差分数在0.7-0.8之间
  5. :warning: 需要视觉验证的偏差项 > 0

失败条件(任意满足即失败)

  1. :cross_mark: 任何步骤出现错误停止
  2. :cross_mark: 关键元素未找到或不可见
  3. :cross_mark: 视觉体验检查有high级别问题
  4. :cross_mark: 行为推理无法生成路径
  5. :cross_mark: industry_weighted_score < 0.7
  6. :cross_mark: 金融场景下有critical级别路径偏差
  7. :cross_mark: 无匹配步骤

:link: 工具调用映射表

功能 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(条件触发)

:bullseye: 行业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传递

4 个赞

能不能多一点总结不要纯的AI呀

1 个赞

告别"机械通过":AI Agent 体验测试的三桥墩架构与实战心法

当 AI 能完成点击、填表、跳转,然后报告"测试通过"——但用户依然觉得难用,问题出在哪里?
本文从认知心理学和人机交互的视角,拆解一套让 AI Agent 真正"像人一样"评估产品体验的方法论。


一、核心观点速览

在深入细节之前,先理解三件事:

① 传统 AI 测试的根本缺陷:它只验证"功能对不对",不验证"体验好不好"。

绝大多数 AI Agent 做自动化测试时,走的是 DOM 选择器路线:找到按钮 → 点击 → 检查页面是否跳转。这条链路能回答"这个功能能不能用",但回答不了"用户能不能顺畅地用"。按钮被遮挡了?间距太小手指点不准?这些真正影响体验的问题,DOM 选择器根本看不见。

② 体验测试的本质:让 AI 具备"模拟人类认知路径"的能力。

好的体验测试不是更多的检查点,而是一个完整的推理过程:人类用户打开页面先看哪里?看到什么会点击?从哪个字段开始填表?做错了什么会收到反馈——这些才是体验的核心。AI 必须模拟这条路径,才能发现真正的体验断裂点。

③ "三桥墩架构"的核心理念:从静态检查走向动态模拟。

用三个递进的层次,从"看得见、点得到"的基础层,到"真人录屏"的基准层,再到"AI 模拟人类"的推理层,逐层逼近真实的用户体验。


二、三桥墩架构:让 AI Agent 真正"理解"体验

桥墩 1:DOM 结构化检查——“看得见、摸得到、够得到”

这是体验测试的地基。听起来简单,但做起来有讲究。

三个必须检查的维度:

可见性:元素是否真的出现在视口内?是否被其他元素遮挡?尺寸是否足够大(尤其是移动端,44px 是推荐的最小触控目标)?这些问题在代码 review 时很难发现,只有在运行时才能检测到。

可达性:表单控件是否都有对应的标签(label)?必填项是否标记了 aria-required?这些不仅是无障碍设计的要求,也是体验质量的基石——缺少标签的输入框会让用户犹豫"这个框是填什么的?"

交互流程完整性:关键操作按钮是否可点击?是否被禁用?位置是否合理?这里的逻辑是:如果用户无法完成一个连贯的操作序列,体验的地基就不存在。

一句话总结:桥墩 1 确保的是"物理可达性"——用户理论上能使用这个界面。

桥墩 2:人类基线路径——“真人是怎么用的?”

如果说桥墩 1 回答"能不能用",桥墩 2 回答"人类是怎么用的"。

核心思路:不要让 AI 自己想怎么操作,而是请一个真实用户操作一遍,把每一步都记录下来——点击了什么、填了什么、等了多久、页面是什么状态。这条路径就是"人类基线"。

为什么要录基线?

因为 AI 天生带有"最优路径"倾向——它会选择最短的 DOM 路径、最快的完成方式,但真实用户的操作不是这样的。用户会先扫一眼页面、会按顺序填表、会被大按钮吸引、会在弹窗出现时先处理弹窗再继续。AI 如果不了解这条"人类路径",即使功能全对,体验也会和真实场景差很远。

录什么?

每一步的动作类型(fill / click / navigate)、操作目标(元素名、标签文本)、精确位置(x, y 坐标)、时间戳(操作间隔)、页面状态(URL、标题、滚动位置)。这些信息构成了一个完整的"人类操作快照",后续所有 AI 行为都要和这条路径做比对。

一句话总结:桥墩 2 建立的是"行为基准线"——AI 的所有行为都要以真实用户的行为为参照。

桥墩 3:路径偏差分析——“AI 做得像不像人?”

有了人类基线,下一步就是让 AI 执行同样的任务,然后比对两条路径的偏差。

比对什么?

五个维度的加权评分:

维度 权重 核心问题
动作类型匹配 30% 人类点击,AI 也点击了吗?
元素角色匹配 20% 人类操作的是 button,AI 操作的也是 button 吗?
元素名称匹配 30% 人类找的是"登录"按钮,AI 找到的也是"登录"按钮吗?
元素标签匹配 10% 人类操作的 tag,AI 操作的是同一个 tag 吗?
位置距离 10% AI 点击的位置和人类点击的位置差了多远?

偏差评分的意义

  • 0.8 以上:体验合格。AI 的操作路径与人类高度一致,说明产品在 AI 视角下也是易用的。
  • 0.6-0.8:体验预警。存在中等偏差,需要重点检查偏差步骤的设计是否合理。
  • 0.6 以下:体验不合格。AI 的操作路径与人类严重偏离,这通常意味着产品存在体验设计缺陷——比如主要操作按钮不够突出、表单布局混乱、或者引导逻辑断裂。

一句话总结:桥墩 3 完成的是"体验度量"——用一个可量化的偏差分数,回答"这个产品的体验到底好不好"。


三、高级心法:让测试"活"起来的三个关键升级

升级 1:行业 Profile——不同行业,体验标准不一样

金融行业的登录页和电商行业的登录页,体验要求完全不同。这不是主观感受,而是客观事实:

金融行业(安全优先)

  • 密码框必须有掩码显示
  • 涉及资金的操作必须有二次确认
  • 错误提示必须明确指出"哪里错了、怎么改"
  • 视觉要求更高(对比度 AAA 级),因为金融场景下误操作的成本极高

电商行业(转化优先)

  • 主 CTA 按钮必须在首屏可见且视觉最突出
  • 购买流程步骤数不能超过 3 步
  • 价格信息必须醒目,折扣标签要突出
  • 视觉对比度要求稍低(AA 级),但转化效率优先级更高

通用行业(平衡优先)

  • 兼顾可用性和美观性,走中间路线

这意味着,没有"放之四海而皆准"的体验标准。测试系统必须能根据行业类型自动切换检查规则和权重,才能给出真正有意义的评估。

升级 2:用户画像——不同用户,操作路径不一样

新用户、熟练用户、偶尔用户——他们在同一个页面上的操作路径完全不同:

用户类型 典型行为
新用户 从上往下扫页面 → 被最显著的按钮吸引 → 按顺序填表 → 遇到弹窗先处理
熟练用户 直奔目标元素 → 使用快捷键 → 忽略次要信息 → 期望一步到位
偶尔用户 随意浏览 → 容易被促销信息吸引 → 耐心有限 → 需要清晰引导

测试价值:同一个产品,针对不同用户画像做体验测试,可能得出完全不同的结论。一个页面对新用户可能"体验极佳"(引导清晰),但对熟练用户可能"体验一般"(路径太长)。合理的做法是用主流目标用户的画像做基准测试,再用其他画像做补充验证。

升级 3:证据门控——每一步都必须"看见"证据

AI Agent 最容易出问题的地方就是"幻觉"——声称操作成功了但实际没执行、声称找到了元素但实际不存在。解决这个问题的核心机制叫做证据门控

  • 每一步操作必须亲眼看见工具返回的结果
  • 没有工具输出的证据,就不允许声称成功
  • 任何一步失败,立即停止整个流程并报告

具体来说,每一步都要验证:

  • playwright_navigate 返回成功吗?
  • playwright_evaluate 返回的结果里,目标元素的 found 字段是 true 吗?
  • 每个检查项的断言都通过了吗?

这个机制从根本上切断了 AI 的"幻觉通道"——没有实际的、可验证的工具输出,就不承认任何成功。


四、体验测试的七个步骤:从部署到验收

把三桥墩架构串起来,就是一条完整的七步工作流:

步骤 1  →  部署测试环境(人工完成)
步骤 2  →  可见性检查(桥墩 1)
步骤 3  →  可达性检查(桥墩 1)
步骤 4  →  交互流程检查(桥墩 1)
步骤 4.5 →  视觉体验检查(新增:对比度、遮挡、层级显著性)
步骤 5  →  行为推理模拟(桥墩 2:推理人类操作路径)
步骤 6  →  AI 执行测试路径
步骤 7  →  路径偏差分析(桥墩 3:比对偏差、计算分数)
步骤 8  →  生成验收报告

为什么在步骤 4 和步骤 5 之间插入 4.5?

这是 v3.0 的核心升级——视觉体验检查。在完成 DOM 层面的结构检查后,增加一个多模态视觉层的检查,覆盖三个维度:

  1. 颜色对比度:文本与背景的对比度是否达到行业标准(金融 AAA、电商 AA)
  2. 视觉遮挡:关键元素是否被其他组件遮挡
  3. 视觉层级显著性:主要 CTA 按钮是否是页面上最突出的按钮

这个步骤填补了 DOM 检查和行为推理之间的空白——有些元素 DOM 结构正确,但视觉上不够突出,用户就是找不到。


五、实战建议:从 0 到 1 搭建体验测试体系

适合什么团队?

  • 有前端产品、希望提升用户体验质量的团队
  • 已经有传统自动化测试、但想更进一步的测试工程师
  • 想在 AI Agent 方向落地实际价值的开发者

最小可行方案(MVP)

如果想快速验证,不需要一次搞全所有功能,建议分三阶段:

第一阶段:桥墩 1 先行

  • 先搭建 DOM 结构化检查框架(可见性 + 可达性 + 交互流程)
  • 用 Playwright MCP 作为核心工具
  • 输出结构化的 JSON 检查结果

第二阶段:引入行业 Profile

  • 选一个目标行业(建议先从自己团队的产品所在行业开始)
  • 配置行业规则权重
  • 体验"同一个页面,不同行业得出不同评估结论"的效果

第三阶段:接入行为推理

  • 实现人类路径推理逻辑
  • 计算路径偏差分数
  • 生成完整体验验收报告

避坑指南

不要让 AI 自己决定操作路径。 让 AI 先推理"人类会怎么做",再执行。这是体验测试和普通自动化测试的本质区别。

不要忽略证据门控。 任何没有工具输出证据的"通过"都是幻觉。严格执行每一步的断言验证。

不要只用一种用户画像。 至少用两种画像做测试(比如新用户 + 熟练用户),才能全面评估体验。


六、总结:从"功能验证"到"体验度量"

AI Agent 体验测试的核心转变可以用一句话概括:

功能验证回答"能不能用",体验度量回答"好不好用"。

三桥墩架构的价值在于,它把这个模糊的"好不好用"变成了可量化、可比较、可追溯的具体指标。通过 DOM 检查确保物理可达性,通过人类基线建立行为参照,通过偏差分析量化体验质量——三层递进,最终让 AI Agent 具备了"像用户一样思考"的能力。

在 AI Agent 快速发展的今天,能不能做好体验测试,决定了产品是"机械可用"还是"真正好用"。而建立一套可持续迭代的体验测试体系,正是从"能用"走向"好用"的第一步。


关联文章

4 个赞

AI 测体验,为啥总不准?用三招搞定"看起来好"的假象

你有没有过这种经历:一个 APP 功能明明能跑通,但用起来就是别扭——按钮藏得深、填表顺序乱、关键提示看不见。AI 测了说"没问题",但你自己用着就是不爽。

这篇文章就解决这个问题。不讲代码,讲逻辑。


一、先说一个残酷的事实

现在绝大多数 AI 做测试,干的活就像一个"盲人摸象"的质检员:

  • 能确认按钮存在(DOM 选择器找到的)
  • 能完成点击、跳转、填表
  • 然后报告一句:“测试通过!”

但它看不见的事情多了去:

  • 按钮是不是被其他东西挡住了?
  • 两个输入框之间的间距够不够大?手指会不会同时碰到两个?
  • 用户会不会先看到红色大按钮,忘了下面还有一个?
  • 这个页面加载慢,用户会不会以为卡住了?

简单说就是:AI 能测"功能对不对",测不了"体验好不好"。


二、三招搞定体验测试

怎么让 AI 也能像人一样感受体验?用三个层层递进的办法:

第一招

3 个赞

状态JSON怎么传递的

1 个赞

状态 JSON 是怎么"接力"传递的

核心思路:一个 JSON 接力棒

整个测试流程就像 接力赛跑 ,状态 JSON 就是那根接力棒。每跑完一棒(一个步骤),就把接力棒交个下一个人(下一个步骤),棒上记着: 跑到第几棒了、上一棒的人跑了什么、成绩怎么样、有没有问题 。

状态 JSON 长什么样

每一步结束后,都会生成这样一个 JSON:

{
  "current_step": 2,
  "step_name": "可见性检查",
  "status": "completed",
  "evidence": ["playwright_navigate返回
  ", "playwright_evaluate返回的JSON数组
  "],
  "output": {
    "visibility_results": [...]
  },
  "errors": []
}

字段含义:

  • current_step :当前跑到第几步(从1开始)
  • step_name :这一步叫什么名字(如"可见性检查"、“可达性检查”)
  • status :这一步的结果( completed 完成、 warning 有警告、 error 失败)
  • evidence :做了哪些事、看到了什么证据(记录所有工具调用的返回结果)
  • output :这一步产出的具体数据(比如可见性检查的结果、路径记录等)
  • errors :这一步遇到的问题列表

怎么传给下一步

这是关键。每一步的 输入 JSON 里,都会引用上一步的 output 数据 ,用占位符标记:

这里的 {{步骤1的test_url}} 就是引用——步骤1的 output 里生成了 test_url ,步骤2直接拿来用,不需要重新获取。

整条链路的传递关系是这样的:

这样做的好处

  1. 上下文不丢失 :每个步骤的输入都明确告诉你"需要什么、从哪来",不会出现"突然不知道 URL 是什么"的情况

  2. 证据可追溯 :每一步的 evidence 字段记录了所有工具调用的原始返回值。如果最后报告出了问题,可以回溯到每一步的原始证据,查清楚是哪一步出的错

  3. 出错就停 :如果某一步 status 是 error ,后面的步骤就不该继续了——因为接力棒已经掉了,继续跑也没意义

  4. 不重复干活 :步骤 1 已经拿到了 URL,步骤 2 就不需要再拿一次。步骤 2 已经检查了可见性,步骤 4.5 就不需要再检查。数据从上往下流,一次获取,多次使用

一句话总结

状态 JSON 的传递机制就是: 每干一件事,记一次笔记,把笔记传给下一个人,下一个人接着记。所有数据单向流动、不回头、不重复

状态机捏,runtime and log,这可是自进化的base

从工程实现的角度看,这个观察是准确的——状态锚点确实像状态机、证据门控确实像 runtime log、历史 JSON 积累确实能做自进化 base。但这套设计的 出发点 不是做一套通用状态系统,而是 对抗 AI 的三个天生缺陷 :注意力衰减、幻觉、偷懒捷径。状态机和日志是实现手段,对抗 AI 缺陷才是设计目的。

监控商品价格自动下单?

让人和AI认知对齐,前端怎么进行测试。

这是一份非常高价值且完整的实践总结,差不多摸到了AI CI/CD的天花板

可以说,其他人再也不需要走这条路了,因为这条路已经到头了,是一条死路,这是一条——

想通过完善法律来实现无犯罪的理想主义道路

原因在于LLM既当裁判员又当运动员,这就跟“自查自纠”效果是一样的,有一定效果,但会演化出更先进的漏洞,护栏永远都有老鼠洞。

真正的解决方案是引入第三方审计工具

需要让经典计算机来给AI反馈,不允许AI绕过

被举报?:grinning_face:

为什么