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 │ └─────────────────────────────────────────┘ 

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

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

2.2 关键界面元素识别

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

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

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

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

3.1 打开开发者工具

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

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 关键要点总结

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

7.2 最佳实践建议

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

7.3 下一步学习方向

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

  1. 集成到CI/CD流水线:把自动化测试脚本集成到Jenkins、GitHub Actions等工具中
  2. 分布式测试:在多台机器上并行运行测试,提高测试效率
  3. 测试数据管理:建立测试数据集,覆盖更多场景
  4. 性能基准测试:建立性能基准,监控模型性能变化
  5. 可视化测试报告:生成HTML格式的测试报告,更直观地展示结果

自动化测试不是一劳永逸的,随着WebUI的更新和模型的变化,你需要不断维护和优化测试脚本。但投入是值得的,它能帮你节省大量手动测试时间,确保模型质量,让你更专注于算法和模型的优化。

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


获取更多AI镜像

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