欢迎光临
我们一直在努力

Alpamayo-R1-10B实操手册:WebUI界面元素XPath定位与自动化测试脚本编写

Alpamayo-R1-10B实操手册:WebUI界面元素XPath定位与自动化测试脚本编写

1. 引言

如果你正在使用Alpamayo-R1-10B这个强大的自动驾驶视觉-语言-动作模型,可能会遇到这样的场景:需要频繁测试不同驾驶指令下的轨迹预测效果,或者想要批量验证模型在各种路况下的表现。手动在WebUI界面上点点点,不仅效率低下,还容易出错。

这时候,自动化测试就成了你的得力助手。想象一下,写个脚本就能自动上传图片、输入指令、点击推理、保存结果,整个过程一气呵成,是不是很酷?

本文将带你从零开始,手把手教你如何定位Alpamayo-R1-10B WebUI的界面元素,并编写实用的自动化测试脚本。无论你是想进行回归测试、批量验证,还是构建自己的测试流水线,这篇文章都能给你清晰的指引。

2. 理解Alpamayo-R1-10B WebUI界面结构

在开始编写自动化脚本之前,我们得先搞清楚WebUI的界面长什么样,各个元素都在哪里。这就像你要操作一个机器,总得先知道各个按钮和开关的位置吧。

2.1 WebUI整体布局

Alpamayo-R1-10B的WebUI界面主要分为几个功能区:

┌─────────────────────────────────────────┐
│ 🚗 Alpamayo-R1 Autonomous Driving VLA │
├─────────────────────────────────────────┤
│ Model Status │
│ ⚠️ Model not loaded… │
│ [🔄 Load Model] │
├─────────────────────────────────────────┤
│ 📷 Input Data │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Front│ │Left │ │Right│ │
│ └─────┘ └─────┘ └─────┘ │
│ Driving Prompt: │
│ [Navigate through…] │
│ Top-p ◆───────● Temperature │
│ Num Samples ◆───● │
│ [🚀 Start Inference] │
├─────────────────────────────────────────┤
│ 📊 Inference Results │
│ Reasoning │ Trajectory Plot │
└─────────────────────────────────────────┘

简单来说,界面从上到下依次是:

  • 模型状态区:显示模型是否加载,有个"Load Model"按钮
  • 输入数据区:三个摄像头图片上传区域、驾驶指令输入框、参数滑块
  • 推理按钮:那个显眼的"Start Inference"按钮
  • 结果展示区:推理过程和轨迹可视化结果
  • 2.2 关键界面元素识别

    要自动化操作这个界面,我们需要重点关注以下几个元素:

    • 模型加载按钮:点击后才能进行推理
    • 图片上传区域:三个独立的文件上传组件
    • 指令输入框:输入自然语言驾驶指令的地方
    • 参数滑块:Top-p、Temperature、Samples三个滑块
    • 推理按钮:触发模型推理的核心按钮
    • 结果展示区域:获取推理结果和轨迹图

    3. 使用浏览器开发者工具定位元素

    现在我们来实际操作一下,看看怎么找到这些元素的XPath。别担心,这个过程其实很简单,就像在网页上"右键检查"一样。

    3.1 打开开发者工具

  • 在浏览器中打开Alpamayo-R1-10B的WebUI(通常是http://localhost:7860)
  • 按F12键或者右键选择"检查",打开开发者工具
  • 点击左上角的箭头图标(选择元素工具)
  • 3.2 定位具体元素

    让我们一个个来定位关键元素:

    模型加载按钮:

    • 把鼠标移到"🔄 Load Model"按钮上
    • 右键点击,选择"检查"
    • 在Elements面板中,右键点击对应的HTML元素
    • 选择"Copy" → "Copy XPath"

    你可能会得到类似这样的XPath:

    //button[contains(text(), 'Load Model')]
    //button[contains(@class, 'gr-button') and contains(text(), 'Load Model')]

    驾驶指令输入框:

    • 找到"Driving Prompt"下面的输入框
    • 同样右键检查,复制XPath
    • 常见的XPath可能是:

    //textarea[contains(@placeholder, 'Navigate through')]
    //textarea[@class='gr-textarea']

    推理按钮:

    • 找到"🚀 Start Inference"按钮
    • 复制XPath:

    //button[contains(text(), 'Start Inference')]
    //button[@id='start-inference-btn']

    3.3 验证XPath是否正确

    找到XPath后,我们可以在开发者工具的Console面板中验证一下:

    // 测试XPath是否能找到元素
    $x("//button[contains(text(), 'Load Model')]")

    // 如果返回数组长度大于0,说明找到了元素
    // 可以进一步查看元素属性
    $x("//button[contains(text(), 'Load Model')]")[0].click() // 模拟点击

    4. 编写Python自动化测试脚本

    理论讲完了,现在我们来点实际的。我将带你一步步编写一个完整的自动化测试脚本。

    4.1 环境准备

    首先,确保你的Python环境中有必要的库:

    pip install selenium webdriver-manager

    如果你需要处理图片,还可以安装:

    pip install pillow opencv-python

    4.2 基础脚本框架

    我们先创建一个基础的自动化脚本:

    #!/usr/bin/env python3
    """
    Alpamayo-R1-10B WebUI自动化测试脚本
    作者:技术博客
    日期:2025-02-05
    """

    import time
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.chrome.service import Service
    from webdriver_manager.chrome import ChromeDriverManager
    import logging

    # 配置日志
    logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s – %(levelname)s – %(message)s'
    )
    logger = logging.getLogger(__name__)

    class AlpamayoWebUITester:
    """Alpamayo-R1-10B WebUI自动化测试类"""

    def __init__(self, webui_url="http://localhost:7860"):
    """
    初始化测试器

    Args:
    webui_url: WebUI访问地址
    """
    self.webui_url = webui_url
    self.driver = None
    self.wait = None

    def setup_driver(self):
    """设置WebDriver"""
    logger.info("正在启动Chrome浏览器…")

    # Chrome选项配置
    chrome_options = webdriver.ChromeOptions()
    chrome_options.add_argument('–no-sandbox')
    chrome_options.add_argument('–disable-dev-shm-usage')
    chrome_options.add_argument('–disable-gpu')
    chrome_options.add_argument('–window-size=1920,1080')

    # 启动浏览器
    service = Service(ChromeDriverManager().install())
    self.driver = webdriver.Chrome(service=service, options=chrome_options)
    self.wait = WebDriverWait(self.driver, 30) # 最长等待30秒

    logger.info("浏览器启动成功")

    def teardown(self):
    """清理资源"""
    if self.driver:
    logger.info("正在关闭浏览器…")
    self.driver.quit()
    logger.info("浏览器已关闭")

    def open_webui(self):
    """打开WebUI页面"""
    logger.info(f"正在打开WebUI: {self.webui_url}")
    self.driver.get(self.webui_url)

    # 等待页面加载完成
    self.wait.until(
    EC.presence_of_element_located((By.TAG_NAME, "body"))
    )
    logger.info("WebUI页面加载完成")

    # 等待页面标题出现
    try:
    self.wait.until(
    EC.presence_of_element_located(
    (By.XPATH, "//*[contains(text(), 'Alpamayo-R1')]")
    )
    )
    logger.info("检测到Alpamayo-R1页面标题")
    except:
    logger.warning("未找到页面标题,但页面可能已加载")

    4.3 定位关键元素的XPath

    接下来,我们定义所有关键元素的XPath。这里我提供一些通用的定位策略:

    class AlpamayoWebUITester:
    # … 之前的代码 …

    # 定义元素定位器
    ELEMENT_LOCATORS = {
    # 模型状态相关
    'model_status': "//div[contains(@class, 'status') and contains(text(), 'Model')]",
    'load_model_btn': "//button[contains(text(), 'Load Model')]",

    # 图片上传区域
    'front_camera_upload': "//input[@type='file' and contains(@aria-label, 'Front')]",
    'left_camera_upload': "//input[@type='file' and contains(@aria-label, 'Left')]",
    'right_camera_upload': "//input[@type='file' and contains(@aria-label, 'Right')]",

    # 驾驶指令输入
    'driving_prompt': "//textarea[contains(@placeholder, 'Navigate')]",

    # 参数滑块
    'top_p_slider': "//input[@type='range' and contains(@aria-label, 'Top-p')]",
    'temperature_slider': "//input[@type='range' and contains(@aria-label, 'Temperature')]",
    'samples_slider': "//input[@type='range' and contains(@aria-label, 'Samples')]",

    # 推理按钮
    'inference_btn': "//button[contains(text(), 'Start Inference')]",

    # 结果区域
    'reasoning_result': "//div[contains(@class, 'reasoning') or contains(@class, 'output')]",
    'trajectory_plot': "//img[contains(@alt, 'trajectory') or contains(@src, 'plot')]",

    # 加载状态
    'loading_indicator': "//div[contains(@class, 'loading') or contains(@class, 'spinner')]",
    }

    def find_element(self, element_name, timeout=10):
    """
    查找页面元素

    Args:
    element_name: 元素名称,对应ELEMENT_LOCATORS中的键
    timeout: 查找超时时间(秒)

    Returns:
    WebElement对象或None
    """
    if element_name not in self.ELEMENT_LOCATORS:
    logger.error(f"未找到元素定义: {element_name}")
    return None

    xpath = self.ELEMENT_LOCATORS[element_name]
    try:
    element = WebDriverWait(self.driver, timeout).until(
    EC.presence_of_element_located((By.XPATH, xpath))
    )
    logger.debug(f"找到元素: {element_name}")
    return element
    except Exception as e:
    logger.error(f"查找元素失败 {element_name}: {e}")
    return None

    def wait_for_element_visible(self, element_name, timeout=10):
    """
    等待元素可见

    Args:
    element_name: 元素名称
    timeout: 等待超时时间(秒)

    Returns:
    WebElement对象或None
    """
    if element_name not in self.ELEMENT_LOCATORS:
    return None

    xpath = self.ELEMENT_LOCATORS[element_name]
    try:
    element = WebDriverWait(self.driver, timeout).until(
    EC.visibility_of_element_located((By.XPATH, xpath))
    )
    return element
    except:
    return None

    4.4 核心操作函数

    现在我们来编写具体的操作函数:

    class AlpamayoWebUITester:
    # … 之前的代码 …

    def load_model(self):
    """加载模型"""
    logger.info("正在加载模型…")

    # 查找加载按钮
    load_btn = self.find_element('load_model_btn')
    if not load_btn:
    logger.error("未找到模型加载按钮")
    return False

    # 点击加载按钮
    try:
    load_btn.click()
    logger.info("已点击模型加载按钮")

    # 等待模型加载完成
    time.sleep(2) # 给一点反应时间

    # 检查模型状态
    for i in range(60): # 最多等待60秒
    status_element = self.find_element('model_status')
    if status_element:
    status_text = status_element.text
    if "loaded" in status_text.lower() or "success" in status_text.lower():
    logger.info("模型加载成功")
    return True
    elif "error" in status_text.lower() or "fail" in status_text.lower():
    logger.error("模型加载失败")
    return False

    logger.info(f"等待模型加载… ({i+1}/60)")
    time.sleep(1)

    logger.warning("模型加载超时")
    return False

    except Exception as e:
    logger.error(f"加载模型时出错: {e}")
    return False

    def upload_images(self, front_img=None, left_img=None, right_img=None):
    """
    上传摄像头图片

    Args:
    front_img: 前视摄像头图片路径
    left_img: 左侧摄像头图片路径
    right_img: 右侧摄像头图片路径

    Returns:
    bool: 是否上传成功
    """
    logger.info("开始上传图片…")

    upload_results = []

    # 上传前视摄像头图片
    if front_img:
    try:
    front_input = self.find_element('front_camera_upload')
    if front_input:
    front_input.send_keys(front_img)
    logger.info(f"已上传前视摄像头图片: {front_img}")
    upload_results.append(True)
    else:
    logger.warning("未找到前视摄像头上传区域")
    upload_results.append(False)
    except Exception as e:
    logger.error(f"上传前视摄像头图片失败: {e}")
    upload_results.append(False)

    # 上传左侧摄像头图片
    if left_img:
    try:
    left_input = self.find_element('left_camera_upload')
    if left_input:
    left_input.send_keys(left_img)
    logger.info(f"已上传左侧摄像头图片: {left_img}")
    upload_results.append(True)
    else:
    logger.warning("未找到左侧摄像头上传区域")
    upload_results.append(False)
    except Exception as e:
    logger.error(f"上传左侧摄像头图片失败: {e}")
    upload_results.append(False)

    # 上传右侧摄像头图片
    if right_img:
    try:
    right_input = self.find_element('right_camera_upload')
    if right_input:
    right_input.send_keys(right_img)
    logger.info(f"已上传右侧摄像头图片: {right_img}")
    upload_results.append(True)
    else:
    logger.warning("未找到右侧摄像头上传区域")
    upload_results.append(False)
    except Exception as e:
    logger.error(f"上传右侧摄像头图片失败: {e}")
    upload_results.append(False)

    # 等待图片上传完成
    time.sleep(2)

    return all(upload_results) if upload_results else False

    def set_driving_prompt(self, prompt):
    """
    设置驾驶指令

    Args:
    prompt: 驾驶指令文本

    Returns:
    bool: 是否设置成功
    """
    logger.info(f"设置驾驶指令: {prompt}")

    try:
    prompt_input = self.find_element('driving_prompt')
    if not prompt_input:
    logger.error("未找到驾驶指令输入框")
    return False

    # 清空原有内容
    prompt_input.clear()
    time.sleep(0.5)

    # 输入新指令
    prompt_input.send_keys(prompt)
    logger.info("驾驶指令设置完成")
    return True

    except Exception as e:
    logger.error(f"设置驾驶指令失败: {e}")
    return False

    def set_parameters(self, top_p=None, temperature=None, samples=None):
    """
    设置推理参数

    Args:
    top_p: Top-p值 (0.0-1.0)
    temperature: 温度值 (0.0-2.0)
    samples: 采样数量 (1-6)

    Returns:
    bool: 是否设置成功
    """
    logger.info("设置推理参数…")

    try:
    # 设置Top-p
    if top_p is not None:
    top_p_slider = self.find_element('top_p_slider')
    if top_p_slider:
    # 通过JavaScript设置滑块值
    self.driver.execute_script(
    f"arguments[0].value = {top_p}; arguments[0].dispatchEvent(new Event('input'));",
    top_p_slider
    )
    logger.info(f"设置Top-p为: {top_p}")

    # 设置Temperature
    if temperature is not None:
    temp_slider = self.find_element('temperature_slider')
    if temp_slider:
    self.driver.execute_script(
    f"arguments[0].value = {temperature}; arguments[0].dispatchEvent(new Event('input'));",
    temp_slider
    )
    logger.info(f"设置Temperature为: {temperature}")

    # 设置Samples
    if samples is not None:
    samples_slider = self.find_element('samples_slider')
    if samples_slider:
    self.driver.execute_script(
    f"arguments[0].value = {samples}; arguments[0].dispatchEvent(new Event('input'));",
    samples_slider
    )
    logger.info(f"设置Samples为: {samples}")

    time.sleep(1) # 等待参数生效
    return True

    except Exception as e:
    logger.error(f"设置参数失败: {e}")
    return False

    def start_inference(self, wait_for_result=True, timeout=60):
    """
    开始推理

    Args:
    wait_for_result: 是否等待推理完成
    timeout: 等待超时时间(秒)

    Returns:
    tuple: (推理是否成功, 推理结果文本, 轨迹图路径)
    """
    logger.info("开始推理…")

    try:
    # 查找推理按钮
    inference_btn = self.find_element('inference_btn')
    if not inference_btn:
    logger.error("未找到推理按钮")
    return False, None, None

    # 点击推理按钮
    inference_btn.click()
    logger.info("已点击推理按钮")

    if not wait_for_result:
    return True, None, None

    # 等待推理完成
    logger.info("等待推理完成…")

    # 检查是否有加载指示器
    loading_element = self.find_element('loading_indicator')
    if loading_element:
    # 等待加载完成
    for i in range(timeout):
    try:
    # 检查加载指示器是否消失
    if not loading_element.is_displayed():
    logger.info("推理加载完成")
    break
    except:
    # 元素可能已从DOM中移除
    break

    if i % 5 == 0:
    logger.info(f"等待推理中… ({i+1}/{timeout})")
    time.sleep(1)

    # 等待结果出现
    time.sleep(3) # 给结果一些渲染时间

    # 获取推理结果文本
    reasoning_text = None
    reasoning_element = self.find_element('reasoning_result')
    if reasoning_element:
    reasoning_text = reasoning_element.text
    logger.info(f"获取到推理结果: {reasoning_text[:100]}…")

    # 获取轨迹图(如果有)
    trajectory_path = None
    plot_element = self.find_element('trajectory_plot')
    if plot_element:
    # 截图保存轨迹图
    timestamp = time.strftime("%Y%m%d_%H%M%S")
    trajectory_path = f"trajectory_{timestamp}.png"
    plot_element.screenshot(trajectory_path)
    logger.info(f"轨迹图已保存: {trajectory_path}")

    return True, reasoning_text, trajectory_path

    except Exception as e:
    logger.error(f"推理过程中出错: {e}")
    return False, None, None

    4.5 完整的测试用例示例

    现在,让我们把这些功能组合起来,创建一个完整的测试用例:

    class AlpamayoWebUITester:
    # … 之前的代码 …

    def run_basic_test(self, test_images=None, prompt=None):
    """
    运行基础测试用例

    Args:
    test_images: 测试图片路径字典,格式为:
    {'front': 'path/to/front.jpg', 'left': 'path/to/left.jpg', 'right': 'path/to/right.jpg'}
    prompt: 驾驶指令,默认为"Navigate through the intersection safely"

    Returns:
    dict: 测试结果
    """
    logger.info("=" * 50)
    logger.info("开始运行基础测试用例")
    logger.info("=" * 50)

    # 设置默认值
    if prompt is None:
    prompt = "Navigate through the intersection safely"

    if test_images is None:
    # 使用默认测试图片(这里需要你提供实际的图片路径)
    test_images = {
    'front': 'test_images/front_view.jpg',
    'left': 'test_images/left_view.jpg',
    'right': 'test_images/right_view.jpg'
    }

    test_results = {
    'success': False,
    'steps': [],
    'reasoning': None,
    'trajectory_image': None,
    'errors': []
    }

    try:
    # 步骤1: 打开WebUI
    logger.info("[步骤1] 打开WebUI页面")
    self.open_webui()
    test_results['steps'].append('打开WebUI页面 ✓')
    time.sleep(2)

    # 步骤2: 加载模型
    logger.info("[步骤2] 加载模型")
    if self.load_model():
    test_results['steps'].append('加载模型 ✓')
    else:
    test_results['steps'].append('加载模型 ✗')
    test_results['errors'].append('模型加载失败')
    return test_results

    # 步骤3: 上传图片
    logger.info("[步骤3] 上传摄像头图片")
    upload_success = self.upload_images(
    front_img=test_images.get('front'),
    left_img=test_images.get('left'),
    right_img=test_images.get('right')
    )

    if upload_success:
    test_results['steps'].append('上传图片 ✓')
    else:
    test_results['steps'].append('上传图片 ✗')
    test_results['errors'].append('图片上传失败')
    # 继续执行,可能部分图片上传成功

    # 步骤4: 设置驾驶指令
    logger.info("[步骤4] 设置驾驶指令")
    if self.set_driving_prompt(prompt):
    test_results['steps'].append('设置驾驶指令 ✓')
    else:
    test_results['steps'].append('设置驾驶指令 ✗')
    test_results['errors'].append('驾驶指令设置失败')

    # 步骤5: 设置参数(使用默认值)
    logger.info("[步骤5] 设置推理参数")
    if self.set_parameters(top_p=0.98, temperature=0.6, samples=1):
    test_results['steps'].append('设置推理参数 ✓')
    else:
    test_results['steps'].append('设置推理参数 ✗')
    test_results['errors'].append('参数设置失败')

    # 步骤6: 开始推理
    logger.info("[步骤6] 开始推理")
    inference_success, reasoning_text, trajectory_path = self.start_inference()

    if inference_success:
    test_results['steps'].append('开始推理 ✓')
    test_results['reasoning'] = reasoning_text
    test_results['trajectory_image'] = trajectory_path
    test_results['success'] = True

    logger.info("推理成功完成!")
    logger.info(f"推理结果: {reasoning_text[:200] if reasoning_text else '无'}")
    logger.info(f"轨迹图保存至: {trajectory_path}")
    else:
    test_results['steps'].append('开始推理 ✗')
    test_results['errors'].append('推理过程失败')

    # 步骤7: 截图保存结果
    logger.info("[步骤7] 保存测试结果截图")
    timestamp = time.strftime("%Y%m%d_%H%M%S")
    screenshot_path = f"test_result_{timestamp}.png"
    self.driver.save_screenshot(screenshot_path)
    test_results['screenshot'] = screenshot_path
    logger.info(f"测试结果截图已保存: {screenshot_path}")

    except Exception as e:
    logger.error(f"测试过程中发生异常: {e}")
    test_results['errors'].append(f"异常: {str(e)}")

    logger.info("=" * 50)
    logger.info("测试用例执行完成")
    logger.info(f"成功: {test_results['success']}")
    logger.info(f"错误数: {len(test_results['errors'])}")
    logger.info("=" * 50)

    return test_results

    def run_batch_tests(self, test_cases):
    """
    运行批量测试

    Args:
    test_cases: 测试用例列表,每个用例为字典格式:
    {
    'name': '测试用例名称',
    'images': {'front': 'path', 'left': 'path', 'right': 'path'},
    'prompt': '驾驶指令',
    'params': {'top_p': 0.98, 'temperature': 0.6, 'samples': 1}
    }

    Returns:
    list: 所有测试用例的结果
    """
    logger.info(f"开始批量测试,共 {len(test_cases)} 个用例")

    all_results = []

    for i, test_case in enumerate(test_cases, 1):
    logger.info(f"\\n{'='*60}")
    logger.info(f"运行测试用例 {i}/{len(test_cases)}: {test_case.get('name', f'TestCase_{i}')}")
    logger.info(f"{'='*60}")

    # 设置参数
    params = test_case.get('params', {})

    # 执行测试
    result = self.run_basic_test(
    test_images=test_case.get('images'),
    prompt=test_case.get('prompt')
    )

    # 添加测试用例信息
    result['test_case'] = test_case.get('name', f'TestCase_{i}')
    result['test_index'] = i

    all_results.append(result)

    # 等待一段时间再进行下一个测试
    time.sleep(2)

    # 生成测试报告
    self.generate_test_report(all_results)

    return all_results

    def generate_test_report(self, test_results):
    """生成测试报告"""
    logger.info("\\n" + "="*60)
    logger.info("测试报告摘要")
    logger.info("="*60)

    total_tests = len(test_results)
    passed_tests = sum(1 for r in test_results if r['success'])
    failed_tests = total_tests – passed_tests

    logger.info(f"总测试用例数: {total_tests}")
    logger.info(f"通过数: {passed_tests}")
    logger.info(f"失败数: {failed_tests}")
    logger.info(f"通过率: {passed_tests/total_tests*100:.1f}%")

    if failed_tests > 0:
    logger.info("\\n失败的测试用例:")
    for result in test_results:
    if not result['success']:
    logger.info(f" – {result.get('test_case', '未知用例')}")
    for error in result.get('errors', []):
    logger.info(f" 错误: {error}")

    # 保存详细报告到文件
    report_path = f"test_report_{time.strftime('%Y%m%d_%H%M%S')}.txt"
    with open(report_path, 'w', encoding='utf-8') as f:
    f.write("Alpamayo-R1-10B WebUI自动化测试报告\\n")
    f.write(f"生成时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\\n")
    f.write("="*60 + "\\n\\n")

    f.write(f"总测试用例数: {total_tests}\\n")
    f.write(f"通过数: {passed_tests}\\n")
    f.write(f"失败数: {failed_tests}\\n")
    f.write(f"通过率: {passed_tests/total_tests*100:.1f}%\\n\\n")

    f.write("详细测试结果:\\n")
    f.write("-"*60 + "\\n")

    for result in test_results:
    f.write(f"\\n测试用例: {result.get('test_case', '未知')}\\n")
    f.write(f"状态: {'通过' if result['success'] else '失败'}\\n")
    f.write(f"执行步骤:\\n")
    for step in result.get('steps', []):
    f.write(f" {step}\\n")

    if result.get('errors'):
    f.write(f"错误信息:\\n")
    for error in result['errors']:
    f.write(f" – {error}\\n")

    if result.get('reasoning'):
    f.write(f"推理结果: {result['reasoning'][:200]}…\\n")

    f.write("-"*40 + "\\n")

    logger.info(f"详细测试报告已保存至: {report_path}")

    4.6 主程序入口

    最后,我们创建一个主程序来运行测试:

    def main():
    """主函数"""
    print("Alpamayo-R1-10B WebUI自动化测试脚本")
    print("="*50)

    # 创建测试器实例
    tester = AlpamayoWebUITester(webui_url="http://localhost:7860")

    try:
    # 设置WebDriver
    tester.setup_driver()

    # 示例1: 运行单个测试用例
    print("\\n1. 运行单个测试用例")
    result = tester.run_basic_test(
    prompt="Turn left at the intersection and maintain safe distance"
    )

    print(f"测试结果: {'成功' if result['success'] else '失败'}")

    # 示例2: 运行批量测试(需要准备测试数据)
    run_batch = input("\\n是否运行批量测试? (y/n): ").lower() == 'y'

    if run_batch:
    print("\\n2. 运行批量测试")

    # 这里可以定义你的测试用例
    test_cases = [
    {
    'name': '直行通过路口',
    'prompt': 'Navigate through the intersection safely',
    'params': {'top_p': 0.98, 'temperature': 0.6, 'samples': 1}
    },
    {
    'name': '左转路口',
    'prompt': 'Turn left at the intersection',
    'params': {'top_p': 0.95, 'temperature': 0.7, 'samples': 1}
    },
    {
    'name': '跟车行驶',
    'prompt': 'Follow the vehicle ahead',
    'params': {'top_p': 0.98, 'temperature': 0.5, 'samples': 1}
    },
    {
    'name': '变道右转',
    'prompt': 'Merge into the right lane and prepare to turn right',
    'params': {'top_p': 0.99, 'temperature': 0.8, 'samples': 2}
    }
    ]

    # 注意:这里需要你提供实际的图片路径
    # 为了演示,我们使用相同的图片路径
    for test_case in test_cases:
    test_case['images'] = {
    'front': 'test_images/front_view.jpg',
    'left': 'test_images/left_view.jpg',
    'right': 'test_images/right_view.jpg'
    }

    batch_results = tester.run_batch_tests(test_cases)

    # 显示批量测试结果
    success_count = sum(1 for r in batch_results if r['success'])
    print(f"\\n批量测试完成: {success_count}/{len(batch_results)} 个用例通过")

    # 示例3: 自定义测试
    run_custom = input("\\n是否运行自定义测试? (y/n): ").lower() == 'y'

    if run_custom:
    print("\\n3. 自定义测试")

    # 这里可以添加你的自定义测试逻辑
    custom_prompt = input("请输入驾驶指令 (回车使用默认): ")
    if not custom_prompt:
    custom_prompt = "Navigate through the intersection safely"

    custom_top_p = input("请输入Top-p值 (0.0-1.0, 回车使用0.98): ")
    custom_temp = input("请输入Temperature值 (0.0-2.0, 回车使用0.6): ")
    custom_samples = input("请输入Samples值 (1-6, 回车使用1): ")

    params = {}
    if custom_top_p:
    params['top_p'] = float(custom_top_p)
    if custom_temp:
    params['temperature'] = float(custom_temp)
    if custom_samples:
    params['samples'] = int(custom_samples)

    print(f"\\n开始自定义测试…")
    print(f"指令: {custom_prompt}")
    print(f"参数: {params}")

    # 运行测试
    tester.set_parameters(**params)
    tester.set_driving_prompt(custom_prompt)
    success, reasoning, trajectory = tester.start_inference()

    if success:
    print(f"测试成功!")
    if reasoning:
    print(f"推理结果: {reasoning[:200]}…")
    if trajectory:
    print(f"轨迹图: {trajectory}")
    else:
    print("测试失败")

    except KeyboardInterrupt:
    print("\\n用户中断测试")
    except Exception as e:
    print(f"测试过程中发生错误: {e}")
    import traceback
    traceback.print_exc()
    finally:
    # 清理资源
    tester.teardown()
    print("\\n测试完成,资源已清理")

    if __name__ == "__main__":
    main()

    5. 高级技巧与最佳实践

    掌握了基础脚本编写后,我们来看看一些高级技巧和最佳实践。

    5.1 处理动态加载的元素

    有时候页面元素是动态加载的,我们需要更智能的等待策略:

    def wait_for_element_with_retry(self, element_name, max_retries=3, timeout=10):
    """
    带重试的元素等待

    Args:
    element_name: 元素名称
    max_retries: 最大重试次数
    timeout: 每次等待超时时间

    Returns:
    WebElement对象或None
    """
    for attempt in range(max_retries):
    try:
    element = self.wait_for_element_visible(element_name, timeout)
    if element:
    logger.info(f"在第 {attempt+1} 次尝试中找到元素: {element_name}")
    return element
    except Exception as e:
    logger.warning(f"第 {attempt+1} 次尝试查找元素失败: {e}")

    if attempt < max_retries – 1:
    logger.info(f"等待 {timeout} 秒后重试…")
    time.sleep(timeout)

    logger.error(f"经过 {max_retries} 次尝试仍未找到元素: {element_name}")
    return None

    5.2 处理iframe和弹窗

    如果WebUI中有iframe或弹窗,需要特殊处理:

    def switch_to_iframe(self, iframe_locator):
    """
    切换到iframe

    Args:
    iframe_locator: iframe的定位器

    Returns:
    bool: 是否切换成功
    """
    try:
    iframe = self.find_element(iframe_locator)
    if iframe:
    self.driver.switch_to.frame(iframe)
    logger.info("已切换到iframe")
    return True
    except Exception as e:
    logger.error(f"切换iframe失败: {e}")

    return False

    def switch_to_default_content(self):
    """切换回主文档"""
    try:
    self.driver.switch_to.default_content()
    logger.info("已切换回主文档")
    return True
    except Exception as e:
    logger.error(f"切换回主文档失败: {e}")
    return False

    5.3 错误处理与重试机制

    自动化测试中,错误处理非常重要:

    def safe_click(self, element_name, max_retries=2):
    """
    安全的点击操作,带重试机制

    Args:
    element_name: 元素名称
    max_retries: 最大重试次数

    Returns:
    bool: 是否点击成功
    """
    for attempt in range(max_retries):
    try:
    element = self.find_element(element_name)
    if element and element.is_enabled() and element.is_displayed():
    # 使用JavaScript点击,避免元素遮挡等问题
    self.driver.execute_script("arguments[0].click();", element)
    logger.info(f"成功点击元素: {element_name}")
    return True
    else:
    logger.warning(f"元素不可点击: {element_name}, 尝试 {attempt+1}/{max_retries}")
    except Exception as e:
    logger.warning(f"点击元素失败: {element_name}, 错误: {e}, 尝试 {attempt+1}/{max_retries}")

    if attempt < max_retries – 1:
    time.sleep(1) # 等待后重试

    logger.error(f"经过 {max_retries} 次尝试仍无法点击元素: {element_name}")
    return False

    5.4 性能监控与日志记录

    添加性能监控和详细的日志记录:

    import time
    from functools import wraps

    def time_it(func):
    """计时装饰器"""
    @wraps(func)
    def wrapper(*args, **kwargs):
    start_time = time.time()
    result = func(*args, **kwargs)
    end_time = time.time()
    elapsed = end_time – start_time

    # 获取类名和方法名
    class_name = args[0].__class__.__name__ if args else 'Unknown'
    logger.info(f"{class_name}.{func.__name__} 执行时间: {elapsed:.2f}秒")

    return result
    return wrapper

    class AlpamayoWebUITester:
    # … 之前的代码 …

    @time_it
    def load_model(self):
    # 原有的load_model方法
    pass

    @time_it
    def start_inference(self, wait_for_result=True, timeout=60):
    # 原有的start_inference方法
    pass

    5.5 配置管理

    将配置信息提取到单独的文件中:

    # config.py
    CONFIG = {
    'webui_url': 'http://localhost:7860',
    'timeouts': {
    'page_load': 30,
    'element_wait': 10,
    'model_load': 60,
    'inference': 120
    },
    'retry_settings': {
    'max_retries': 3,
    'retry_delay': 2
    },
    'screenshot_settings': {
    'save_screenshots': True,
    'screenshot_dir': 'screenshots',
    'screenshot_format': 'png'
    },
    'logging': {
    'level': 'INFO',
    'format': '%(asctime)s – %(name)s – %(levelname)s – %(message)s',
    'file': 'alpamayo_test.log'
    }
    }

    # 测试用例配置
    TEST_CASES = [
    {
    'name': '基础直行测试',
    'description': '测试车辆直行通过路口的基本功能',
    'prompt': 'Navigate through the intersection safely',
    'params': {
    'top_p': 0.98,
    'temperature': 0.6,
    'samples': 1
    },
    'expected': {
    'has_reasoning': True,
    'has_trajectory': True,
    'keywords': ['intersection', 'safe', 'proceed']
    }
    },
    # 更多测试用例…
    ]

    6. 实际应用场景

    掌握了自动化测试脚本的编写后,我们来看看在实际工作中怎么用。

    6.1 回归测试

    每次模型更新或WebUI修改后,运行自动化测试确保核心功能正常:

    def run_regression_test():
    """运行回归测试套件"""
    tester = AlpamayoWebUITester()

    try:
    tester.setup_driver()

    # 定义回归测试用例
    regression_cases = [
    {
    'name': '模型加载测试',
    'test_func': tester.load_model,
    'expected': True
    },
    {
    'name': '图片上传测试',
    'test_func': lambda: tester.upload_images(
    front_img='test_images/test_front.jpg'
    ),
    'expected': True
    },
    {
    'name': '基础推理测试',
    'test_func': lambda: tester.run_basic_test(
    prompt='Navigate through the intersection safely'
    )['success'],
    'expected': True
    }
    ]

    results = []
    for case in regression_cases:
    logger.info(f"运行回归测试: {case['name']}")
    try:
    result = case['test_func']()
    passed = result == case['expected']
    results.append({
    'name': case['name'],
    'passed': passed,
    'result': result
    })
    logger.info(f" -> {'通过' if passed else '失败'}")
    except Exception as e:
    logger.error(f"测试失败: {e}")
    results.append({
    'name': case['name'],
    'passed': False,
    'error': str(e)
    })

    # 生成回归测试报告
    passed_count = sum(1 for r in results if r['passed'])
    total_count = len(results)

    logger.info(f"\\n回归测试完成: {passed_count}/{total_count} 通过")

    return results

    finally:
    tester.teardown()

    6.2 批量验证不同驾驶场景

    测试模型在各种驾驶指令下的表现:

    def test_different_scenarios():
    """测试不同驾驶场景"""
    scenarios = [
    {
    'name': '城市道路直行',
    'prompt': 'Drive straight on the urban road at 30 km/h',
    'description': '测试城市道路直行场景'
    },
    {
    'name': '高速公路变道',
    'prompt': 'Change to the left lane on the highway',
    'description': '测试高速公路变道场景'
    },
    {
    'name': '路口右转',
    'prompt': 'Turn right at the intersection',
    'description': '测试路口右转场景'
    },
    {
    'name': '跟车行驶',
    'prompt': 'Follow the car ahead with safe distance',
    'description': '测试跟车行驶场景'
    },
    {
    'name': '避让行人',
    'prompt': 'Slow down and yield to pedestrians at the crosswalk',
    'description': '测试避让行人场景'
    }
    ]

    tester = AlpamayoWebUITester()

    try:
    tester.setup_driver()
    tester.open_webui()
    tester.load_model()

    scenario_results = []

    for scenario in scenarios:
    logger.info(f"\\n测试场景: {scenario['name']}")
    logger.info(f"描述: {scenario['description']}")
    logger.info(f"指令: {scenario['prompt']}")

    # 设置驾驶指令
    tester.set_driving_prompt(scenario['prompt'])

    # 开始推理
    success, reasoning, trajectory = tester.start_inference()

    # 分析结果
    result_analysis = analyze_reasoning(reasoning, scenario['prompt'])

    scenario_results.append({
    'scenario': scenario['name'],
    'prompt': scenario['prompt'],
    'success': success,
    'reasoning_length': len(reasoning) if reasoning else 0,
    'has_trajectory': trajectory is not None,
    'analysis': result_analysis
    })

    # 保存场景特定结果
    if success:
    save_scenario_result(scenario, reasoning, trajectory)

    time.sleep(2) # 场景间等待

    # 生成场景测试报告
    generate_scenario_report(scenario_results)

    finally:
    tester.teardown()

    def analyze_reasoning(reasoning_text, prompt):
    """分析推理结果"""
    if not reasoning_text:
    return "无推理结果"

    analysis = {
    'contains_decision': 'decision' in reasoning_text.lower(),
    'contains_action': any(word in reasoning_text.lower()
    for word in ['turn', 'slow', 'stop', 'accelerate', 'change']),
    'matches_prompt': any(word in reasoning_text.lower()
    for word in prompt.lower().split()),
    'reasoning_length': len(reasoning_text.split())
    }

    return analysis

    6.3 性能测试

    测试模型推理的响应时间和资源使用:

    import psutil
    import GPUtil

    def monitor_performance(duration=60):
    """
    监控系统性能

    Args:
    duration: 监控时长(秒)

    Returns:
    dict: 性能数据
    """
    performance_data = {
    'cpu_percent': [],
    'memory_percent': [],
    'gpu_utilization': [],
    'gpu_memory': []
    }

    start_time = time.time()

    while time.time() – start_time < duration:
    # CPU使用率
    cpu_percent = psutil.cpu_percent(interval=1)
    performance_data['cpu_percent'].append(cpu_percent)

    # 内存使用率
    memory_percent = psutil.virtual_memory().percent
    performance_data['memory_percent'].append(memory_percent)

    # GPU使用情况(如果可用)
    try:
    gpus = GPUtil.getGPUs()
    if gpus:
    gpu = gpus[0] # 假设使用第一个GPU
    performance_data['gpu_utilization'].append(gpu.load * 100)
    performance_data['gpu_memory'].append(gpu.memoryUtil * 100)
    except:
    pass

    time.sleep(1)

    # 计算平均值
    avg_data = {
    'avg_cpu': sum(performance_data['cpu_percent']) / len(performance_data['cpu_percent']),
    'avg_memory': sum(performance_data['memory_percent']) / len(performance_data['memory_percent']),
    'max_cpu': max(performance_data['cpu_percent']),
    'max_memory': max(performance_data['memory_percent'])
    }

    if performance_data['gpu_utilization']:
    avg_data['avg_gpu_util'] = sum(performance_data['gpu_utilization']) / len(performance_data['gpu_utilization'])
    avg_data['avg_gpu_memory'] = sum(performance_data['gpu_memory']) / len(performance_data['gpu_memory'])

    return avg_data

    def run_performance_test():
    """运行性能测试"""
    tester = AlpamayoWebUITester()

    try:
    tester.setup_driver()
    tester.open_webui()

    # 测试1: 模型加载性能
    logger.info("测试模型加载性能…")
    load_start = time.time()
    tester.load_model()
    load_time = time.time() – load_start
    logger.info(f"模型加载时间: {load_time:.2f}秒")

    # 测试2: 推理性能
    logger.info("测试推理性能…")

    # 监控推理期间的性能
    import threading

    performance_results = []
    monitor_thread = None

    def monitor_during_inference():
    nonlocal performance_results
    performance_results = monitor_performance(duration=30)

    # 开始监控
    monitor_thread = threading.Thread(target=monitor_during_inference)
    monitor_thread.start()

    # 执行推理
    inference_start = time.time()
    success, reasoning, trajectory = tester.start_inference(timeout=30)
    inference_time = time.time() – inference_start

    # 等待监控线程结束
    monitor_thread.join()

    logger.info(f"推理时间: {inference_time:.2f}秒")
    logger.info(f"平均CPU使用率: {performance_results.get('avg_cpu', 0):.1f}%")
    logger.info(f"平均内存使用率: {performance_results.get('avg_memory', 0):.1f}%")

    if 'avg_gpu_util' in performance_results:
    logger.info(f"平均GPU使用率: {performance_results['avg_gpu_util']:.1f}%")
    logger.info(f"平均GPU内存使用率: {performance_results['avg_gpu_memory']:.1f}%")

    # 保存性能报告
    save_performance_report({
    'load_time': load_time,
    'inference_time': inference_time,
    'performance_metrics': performance_results
    })

    finally:
    tester.teardown()

    7. 总结

    通过本文的学习,你应该已经掌握了Alpamayo-R1-10B WebUI自动化测试的核心技能。让我们回顾一下重点:

    7.1 关键要点总结

  • 界面元素定位是基础:使用浏览器开发者工具找到准确的XPath,这是自动化测试的第一步
  • Selenium是强大工具:提供了丰富的API来模拟用户操作,从点击按钮到上传文件都能搞定
  • 健壮的脚本需要错误处理:网络延迟、元素加载慢、页面变化都需要考虑,重试机制很重要
  • 实际应用场景多样:不仅可以用作功能测试,还能用于回归测试、性能测试、批量验证等
  • 7.2 最佳实践建议

    • 保持定位器更新:WebUI界面可能会变化,定期检查更新XPath
    • 添加充分的等待:模型加载和推理需要时间,合理的等待策略能提高脚本稳定性
    • 详细的日志记录:好的日志能帮你快速定位问题
    • 模块化设计:把功能拆分成小函数,方便维护和重用
    • 定期运行测试:建立自动化测试流水线,确保模型质量

    7.3 下一步学习方向

    如果你已经掌握了基础,可以进一步探索:

  • 集成到CI/CD流水线:把自动化测试脚本集成到Jenkins、GitHub Actions等工具中
  • 分布式测试:在多台机器上并行运行测试,提高测试效率
  • 测试数据管理:建立测试数据集,覆盖更多场景
  • 性能基准测试:建立性能基准,监控模型性能变化
  • 可视化测试报告:生成HTML格式的测试报告,更直观地展示结果
  • 自动化测试不是一劳永逸的,随着WebUI的更新和模型的变化,你需要不断维护和优化测试脚本。但投入是值得的,它能帮你节省大量手动测试时间,确保模型质量,让你更专注于算法和模型的优化。

    现在,拿起你学到的工具,开始为你的Alpamayo-R1-10B项目构建自动化测试吧!


    获取更多AI镜像

    想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

    赞(0)
    未经允许不得转载:171主机测评 » Alpamayo-R1-10B实操手册:WebUI界面元素XPath定位与自动化测试脚本编写
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址