智能座舱项目,车机大屏加仪表加副驾屏,版本迭代快得像坐火箭。每两周一个版本,发版前必做全量回归

那时候我组三个人,手工点一遍所有主流程,最少三天。点完手都麻了,结果还漏了几个冷门路径

我一度以为,自动化能救我们。可真做起来才发现,座舱的UI自动化比手机App难出一个量级

元素ID两周一变,CAN信号时序卡得死死的,多屏来回切。传统脚本写起来费劲,维护起来更费劲——往往改定位器的时间,比手工点还要久

直到我把 Cursor 和大模型塞进工作流,让 AI 来写、来修、来分析脚本。事情才真正拐了个弯


一、传统智能座舱UI自动化的瓶颈

为什么说智能座舱自动化难?

自动化要”点“按钮,得先”找到“按钮。在程序世界里,界面上每个元素——按钮、文本框、图标——在代码里都有一个专属的身份标识,叫 resource-id。你可以把它理解成元素的”身份证号”。脚本就靠这个号码去定位:”我要点那个com.xxx.ac.btn_temp 的按钮”。号对了就中;号变了,脚本就报错了

麻烦就麻烦在,车机系统 App 的 resource-id 特别爱变。手机 App 一般比较稳定,但车机是大厂深度定制的,版本一升级,整套页面可能重构。上一版叫 A,下一版叫 B,脚本里记的还是 A,跑去一点——”查无此号”,直接报错

问答

问:那能不能不用“身份证号”,改用按钮上的文字找?

答:可以,但文字也可能改(比如”空调”改成”HVAC”),而且同屏可能有两个一样文字的按钮,照样抓不准。所以定位这件事,从来都是座舱自动化的头号难题。

① 元素定位脆弱:版本一更新脚本就报错

手机App用 Appium,resource-id 一般挺稳。但车机系统应用不一样

我们的车机是大厂定制的系统,每个版本重构一遍页面,连控件的 resource-id 都重新生成。比如空调按钮,上个版本叫 com.xxx.ac.btn_temp,下个版本变成 com.xxx.hvac.temp_btn

你写的二十条定位器,一次版本发布能挂一半。修定位器这件事,本身就成了测试的主要工作量

② CAN信号耦合:UI操作要同步验证信号层

座舱测试有个手机测试没有的硬需求:你点了屏幕上的空调按钮,得同时看 CAN 总线上有没有对应的报文出来

UI 层和信号层是耦合的。传统 Appium 只认屏幕,它根本感知不到 CAN 总线。你要在用例里手动接 python-can 去监听报文,代码量和耦合度直接翻倍

真实事故:有一次发版,空调开关在 UI 上看是亮的,但 CAN 报文迟迟没发。Appium 用例”绿”了,实际车上的压缩机根本没启动。后来靠人工在 CANoe 上抓包才发现问题。这说明——只测 UI 不测信号,座舱自动化就是半截子

③ 脚本维护成本高:修改用例比手工测试还慢

这是最要命的。一个迭代周期,新功能开发加旧功能回归,你要改用例、改定位器、改信号映射

我算过一笔账:我们组维护 300 条左右的核心用例,每个两周迭代平均有 60 条要动。一个人改定位器加调试,要花一天半。这还没算新用例编写

结果就是:自动化的维护成本,悄悄超过了手工测试本身

瓶颈
具体表现
对回归的影响
元素定位脆弱
resource-id 两周一变,XPath 长串易碎
脚本失效率高,每次发布要大面积返修
CAN信号耦合
UI 操作需同步验证总线报文
Appium 单测 UI 漏掉信号层缺陷
脚本维护成本
改定位器+改用例耗时大
维护时间超过手工测试,ROI 走低

二、AI能做什么?Cursor + 大模型的3层能力

我是在一次技术分享上,听到有人用 Cursor 写前端代码。当时我就想:测试脚本能不能让 AI 来写?

试了三个月,我把 AI 在座舱自动化里的价值,归纳成三层。一层比一层深

这正是 AI 生成脚本的核心原理。你不用自己敲 Python,你只要说:”打开空调,调到 22 度,验证仪表同步显示。”大模型就能理解你的意思,吐出一段 uiautomator2 的操作代码。它不一定一次写对,但骨架和逻辑基本成型,你改改就能跑。大模型把”写脚本”这件事,从纯手写变成了”描述需求 + 校对“

问答

问:大模型会自己点车机屏幕吗?

答:不会,它只会写代码。真正去点屏幕的是 uiautomator2 这类框架,大模型是”写操作指南的人”,框架是”照着指南动手的人”。两者配合,才跑得起来

微信公众号二维码图片引自微信公众号,扫码关注阅读原文

 AI 三层能力架构(生成 → 修复 → 分析)

① AI生成脚本:描述场景直接出代码

第一层最简单。你用自然语言描述一个测试场景,比如”打开空调,设温度到22度,验证仪表同步显示”,AI 直接给你生成 Python + uiautomator2 的操作代码

我刚开始还不信,手写一条空调用例要半小时,AI 三十秒出初稿。当然初稿不能直接用,但改改就能跑,省掉的是最枯燥的样板代码

② AI修复脚本:元素定位失败自动重定位

第二层是救命的。脚本跑挂了,传统做法是你去看报错、去抓 DOM、去改 XPath。现在你把报错截图和 dump 出来的 XML 丢给 AI,它告诉你”按钮 resource-id 变了”,并给出新的定位器

③ AI分析失败:报告自动归类原因

第三层是进阶。一次回归跑完,几十条失败。AI 把失败日志吃进去,帮你分成三类:环境抖动、元素变更、真实 Bug

这一步省掉的是”人肉看日志”的大量时间。真实 Bug 被自动挑出来优先处理,环境类失败直接忽略重跑

任务类型
纯手写耗时
AI 辅助耗时
提速
生成单条UI用例
约 30 分钟
约 5 分钟(改稿)
约 6 倍
修复定位失败
约 15 分钟
约 2 分钟
约 7 倍
失败日志归类
约 40 分钟
约 3 分钟
约 13 倍
全量回归执行
手工 3 天
AI跑+自愈 4 小时
约 10 倍

小结:三层能力叠加,不是简单加法。生成省了写代码的时间,修复省了维护的时间,分析省了排查的时间。三者合起来,才把回归从三天压到四小时


三、环境搭建:工具链准备

光有想法不行,工具链得先立起来。我列一份我们组现在在用的清单。

先讲清楚这套工具链的”主力”——uiautomator2。它是 Google 提供的 Android UI 自动化框架,简单说,就是一套能让你用 Python 代码远程操控安卓界面的工具:点哪里、输什么字、往哪个方向滑,全都能用几行代码搞定

在车机这个场景,uiautomator2 更轻、更稳。uiautomator2 直接走 adb 通道,跟车机的契合度更高,装的环境也简单。

① Cursor 配置

Cursor 是主力编辑器。关键是那个 .cursorrules 文件——它告诉 AI 我们的车机测试规范、用什么 API、CAN 信号怎么约定

② Python 环境

  • uiautomator2
    控制车机屏幕,定位元素、点击、输入
  • python-can
    监听 CAN 总线报文,验证信号层
  • pytest
    用例组织和执行
  • Allure
    出报告,给研发和产品查看分析
# 一键装好依赖(建议用虚拟环境)pip install uiautomator2 python-can pytest allure-pytest# 初始化 uiautomator2 的手机端 agent(车机需先 adb 连上)python -m uiautomator2 init

③ 车机连接

车机连电脑有两路:一路是 adb,控制屏幕;一路是 CAN,接 CANoe 或者 PCAN 硬件

# 1) adb 连车机(WiFi 调试,车机和电脑同一网段)adb connect 192.168.1.50:5555adb devices          # 确认设备在线# 2) CAN 接口(以 PCAN-USB 为例,python-can 配置)# windows 下需安装 PCAN-Basic 驱动,channel 按硬件填
连不上?先看这几点:车机是否开了”USB调试/网络调试”;PCAN 驱动是否装对;防火墙有没有挡 5555 端口。我这踩过坑,车机默认只开 MTP,得进工程菜单手动开调试

④ 目录结构规范

目录结构

ivitest/      # 项目根

├── .cursorrules      # AI 行为说明书(核心)

├── pages/      # POM 页面对象

│   ├── base_page.py

│   ├── hvac_page.py      # 空调页

│   └── cluster_page.py   # 仪表页

├── cases/      # pytest 用例

├── can/      # python-can 封装 + DBC 文件

│   ├── dbc/xxx.dbc

│   └── can_listener.py

├── data/      # Excel/JSON 测试数据

├── reports/      # Allure 结果

└── conftest.py


四、AI生成脚本实战:5步从需求到可运行用例

下面来真的。我们以”空调控制页测试”为例,走完整的五步

场景是:打开空调 → 设温度 22° → 验证仪表同步显示 22°这条用例要把 UI 操作和 CAN 信号验证绑在一起

微信公众号二维码图片引自微信公众号,扫码关注阅读原文
从需求到可运行用例的5步流程

正式走五步前,先讲一个关键概念:POM 模式(Page Object Model,页面对象模型)。它是自动化里常用的一种代码组织方式

啥意思?你把每个界面,封装成一个”类”。界面上的按钮,变成这个类的”属性”;对按钮的操作(点它、读它的值),变成类的”方法”。比如”空调页”就是一个类,里面有”开空调””设温度”这些方法。以后界面改了——比如按钮从减号改成下拉,你只在这个类里改一处,所有用到它的脚本都不用动

打个比方:POM 就像给”空调页面”做了一支遥控器模板。遥控器上有开关键、温度键,脚本只管按”开关键”。哪天车机换版本,遥控器外形变了,你只换这支遥控器(改一个类),按遥控器的那些脚本一行都不用改。这就是它省维护成本的秘密
微信公众号二维码图片引自微信公众号,扫码关注阅读原文
POM 封装:把界面变成一个类

Step1:给 Cursor 描述测试场景

我在 Cursor 的聊天框里直接写:

# 我要测空调控制页:

# 1. 打开空调主开关

# 2. 把温度设为 22 度

# 3. 验证仪表盘通过 CAN 报文同步显示目标温度 22

# 用 uiautomator2 操作 UI,用 python-can 监听信号,生成 POM 结构代码

Step2:AI 生成页面操作代码

AI 给了我空调页的 Page 类。核心部分(已加注释,可直接跑):

# pages/hvac_page.pyimport uiautomator2 as u2class HvacPage:    """空调控制页,封装所有 UI 操作"""    def __init__(self, d: u2.Device):        self.d = d    def open_ac(self):        # 优先用 resource-id 定位,稳定        self.d(resourceId="com.xxx.hvac:id/ac_main_switch").click()        # 状态机等待:等开关真正变成开启态        self.d(resourceId="com.xxx.hvac:Id/ac_main_switch").wait(timeout=3)    def set_temp(self, temp: int):        # 温度减到 22:先点减号若干次,再读当前值校验        current = int(self.d(resourceId="com.xxx.hvac:id/temp_value").get_text())        while current > temp:            self.d(resourceId="com.xxx.hvac:id/temp_minus").click()            current = int(self.d(resourceId="com.xxx.hvac:id/temp_value").get_text())            if current <= temp:                break    def get_temp_display(self) -> int:        return int(self.d(resourceId="com.xxx.hvac:id/temp_value").get_text())

Step3:AI 生成 CAN 信号验证代码

这一步是关键,把信号层接上。AI 按 DBC 文件生成了监听代码:

# can/can_listener.pyimport canclass CanListener:    """监听空调目标温度信号"""    def __init__(self, channel="PCAN_USBBUS1", bitrate=500000):        self.bus = can.interface.Bus(channel=channel,                                     bustype="pcan",                                     bitrate=bitrate)    def wait_target_temp(self, expect: int, timeout=5) -> bool:        # 按 DBC 解析:报文 ID 0x3A0,信号名 HVAC_TargetTemp        msg = self.bus.recv(timeout=timeout)        while msg is not None:            if msg.arbitration_id == 0x3A0:                # 信号起始位 8,长度 8bit,偏移 0,分辨率 0.5                raw = (msg.data[1] >> 0) & 0xFF                temp = raw * 0.5                if abs(temp - expect) < 0.6:                    return True            msg = self.bus.recv(timeout=timeout)        return False    def shutdown(self):        self.bus.shutdown()

Step4:AI 生成 pytest 用例 + 断言

最后让 AI 把前面两块拼成一条完整用例(带断言)

# cases/test_hvac.pyimport pytestimport uiautomator2 as u2from pages.hvac_page import HvacPagefrom can.can_listener import CanListenerdef test_ac_set_temp_22():    d = u2.connect("192.168.1.50:5555")    hvac = HvacPage(d)    can = CanListener()    hvac.open_ac()              # 开空调    hvac.set_temp(22)          # 设 22 度    ui_temp = hvac.get_temp_display()    assert ui_temp == 22, f"UI显示温度异常: {ui_temp}"    # 关键:验证 CAN 信号层同步    ok = can.wait_target_temp(22, timeout=5)    assert ok, "仪表未通过CAN同步目标温度22°"    can.shutdown()

Step5:运行验证 + AI 自动修复定位失败

第一次跑,大概率挂。我们的车机版本迭代把 temp_minus 改成了 temp_down。Cursor 捕获到 NoSuchElementError,我把截图和 hierarchy 丢回去,它自动把定位器改成新的,重跑通过

踩坑故事:刚开始我让 AI 一次性生成所有代码,结果它把温度按钮的定位写成了坐标 (540, 980)。车机分辨率一变,全错。后来我在 .cursorrules 里硬性禁用坐标,这事再没发生过

五、.cursorrules配置:让AI懂车机测试

这是全文我最想强调的一节:.cursorrules 就是写给 AI 的”团队规范”。你不写,AI 就按它自己的默认习惯来,生成的代码跟我们项目八竿子打不着

我贴一份我们迭代了十几个版本的配置,核心就五块

先解释下 .cursorrules 到底是个啥。Cursor 是一个 AI 编辑器,你在里面写代码时,AI 会实时帮你补全、生成。而 .cursorrules 是一个放在项目根目录的纯文本规则文件,专门写给这个 AI 看的”工作手册”

问答:
问:.cursorrules 是编程高手才懂的高深配置吗?
答:完全不是。它就是一段大白话指令,你用中文写”请用 uiautomator2,别用坐标”就行。门槛很低,但效果很大
微信公众号二维码图片引自微信公众号,扫码关注阅读原文
.cursorrules 五大配置模块

# .cursorrules —— 座舱UI自动化专用

# ① 技术栈声明

你正在编写车载座舱 UI 自动化测试代码。

技术栈固定为:uiautomator2(UI控制)+ python-can(信号监听)

+ pytest(用例)+ Allure(报告)。不要引入其他框架。

# ② 元素定位规范

– 优先使用 resource-id 定位,示例:d(resourceId=”com.xxx:Id/xxx”)

– 次选 XPath 作为 fallback,禁止在 XPath 里写太长链式

– 严禁使用屏幕坐标 (x, y) 点击,分辨率会变

– 操作前用 wait(timeout=3) 做状态等待,不要 sleep 写死

# ③ CAN信号约定

– DBC 文件路径:can/dbc/vehicle.dbc

– 信号命名遵循 DBC:如 HVAC_TargetTemp、AC_Status

– CAN 接口默认 PCAN_USBBUS1,bitrate 500000

– 任何 UI 操作若对应信号,必须写 CAN 校验断言

# ④ 代码风格(POM分层)

– 每个页面一个 Page 类,继承 BasePage

– 页面类只封装操作,不写断言

– 断言只在 cases/ 下的 pytest 用例里写

– 函数/变量用 snake_case,类用 CamelCase

# ⑤ 车机特殊处理

– 多屏切换:主驾屏/副驾屏/仪表,切换后必须重新 wait

– 权限弹窗:首次进入可能弹”允许”对话框,需自动点掉

– 状态机等待:车机响应有延迟,用显式等待而非固定 sleep

– 禁止生成会修改车机系统设置的破坏性代码


六、AI自愈机制:脚本挂了自己修

自愈,是我觉得 AI 给座舱自动化带来的最大变革。传统脚本一挂,人就得上。AI 自愈让脚本在断网前自己把命续上

讲自愈前,得先弄懂”元素定位失败”是啥。回到前面说的 resource-id:脚本上次记的是”空调按钮在第 3 排第 2 个”(或者说,身份证号是某个值)。它下次跑,就按这个老地址去找

结果这次界面改版了,按钮挪到了第 4 排。脚本还按老地址一摸——空的,找不到。这就是”元素定位失败”,传统脚本会直接报错退出,然后等你来救

生活类比:就像你常去一家店,记着”招牌在进门左转第二家”。某天店主把招牌挪到了右转第三家。你照老记忆走,扑了个空。传统脚本的做法是:空手回来跟你喊”没找到”。AI 自愈的做法是:站在店门口,扫一眼现在的布局,发现招牌挪了,自己走过去确认新位置,顺便把你的”记忆”更新成新地址,下次直接去对地方

整个机制跑起来是这样:

微信公众号二维码图片引自微信公众号,扫码关注阅读原文
AI 自愈机制流程(异常 → 分析 → 修复)

自愈异常处理代码

核心是一个装饰器/重试逻辑。定位失败后,自动把现场交给 AI:

# utils/self_heal.pyimport functools, tracebackfrom utils.ai_fix import ask_ai_for_locator   # 封装对 Cursor/大模型的调用def self_heal(max_retry=2):    def decor(func):        def wrapper(*args, **kwargs):            for i in range(max_retry):                try:                    return func(*args, **kwargs)                except Exception as e:                    # 1) 截图 + dump 当前页面 XML                    d = args[0].d                    d.screenshot(f"heal_{func.__name__}_{i}.png")                    xml = d.dump_hierarchy()                    # 2) 把现场喂给 AI,拿新定位器                    new_loc = ask_ai_for_locator(str(e), xml)                    # 3) 用新定位器重写本次操作并重试                    if new_loc:                        args[0].apply_locator(new_loc)            raise RuntimeError(f"自愈失败: {func.__name__}")        return wrapper    return decor

三个真实修复案例

案例1:版本升级后 resource-id 变更:

某次发布后,音乐卡片的 id 从 music_card 变成 media_card。AI 对比 XML,发现文本仍是”音乐”,推断控件被改名,给出新 locator,用例复活

案例2:弹窗遮挡:

进导航页先弹”同意隐私”对话框。AI 在截图里看到遮挡层,生成”先点允许再继续”的逻辑。比我们手动加弹窗处理稳多了

案例3:页面加载慢超时:

冷启动车机,首屏要 5 秒才出。原脚本 wait(3) 不够。AI 分析 XML 发现目标节点缺失,建议把等待改成”等元素出现为止”的显式等待,超时设 10 秒

效果:接入自愈后,单次回归因定位失效导致的”假失败”,从平均 18 条降到 2 条以内。修复不再占用人工,半夜跑回归也不用心跳了

七、CI/CD集成与数据驱动

脚本能自跑了,下一步是让它进流水线,每次提交自动跑。我们用的 Jenkins

先补个概念:CI/CD 是两套连在一起的做法。CI 是持续集成(Continuous Integration),意思是开发人员一提交代码,系统就自动把代码拉下来、自动构建、自动跑测试。CD 是持续交付(Continuous Delivery),在 CI 基础上,把通过测试的产物自动部署出去

放到座舱测试里,这套流程就是:你(或 Jenkins)一触发,系统自动连上车机,把全部用例跑一遍,自动出报告,再让 AI 把挂了的用例分分类、分析下原因。人不用守在旁边点,也不用自己挨个看日志。整条回归从”人手驱动”变成了”流水线驱动”

① Jenkins pipeline

# Jenkinsfile(节选)pipeline {  agent any  stages {    stage('拉代码') { steps { git 'git@xxx:ivitest.git' } }    stage('连车机') { steps { sh 'adb connect 192.168.1.50:5555' } }    stage('跑pytest') {      steps { sh 'pytest cases/ --alluredir=reports' }    }    stage('AI分析失败') {      steps { sh 'python utils/ai_report.py reports' }    }  }  post {    always { sh 'allure generate reports -o allure-html' }  }}

最后一步 AI 分析失败,就是把 Allure 结果喂给大模型做归类,生成一段”今日失败归因”发到群里

② 数据驱动:用 JSON 管理测试数据

空调的温度组合、风量档位、模式切换,排列起来几十种。手写用例太傻。我们用 JSON 管理,让 AI 按模板批量生成:

# data/hvac_cases.json{  "cases": [    {"temp": 22, "fan": 2, "mode": "auto"},    {"temp": 26, "fan": 4, "mode": "face"},    {"temp": 18, "fan": 1, "mode": "foot"}  ]}# cases/test_hvac_data.py  —— pytest 参数化import json, pytestdata = json.load(open("data/hvac_cases.json"))@pytest.mark.parametrize("c", data["cases"])def test_hvac_matrix(c):    hvac.set_temp(c["temp"])    hvac.set_fan(c["fan"])    hvac.set_mode(c["mode"])    assert can.wait_target_temp(c["temp"])

③ 多台架并行执行

一台车机跑全量要 4 小时。我们后来接了三台架,用 pytest-xdist 分片,1.5 小时搞定。AI 生成的用例因为结构统一,分片几乎零冲突

并行注意点:多台架共用 CAN 分析仪时要小心,信号会串。我们给每台架配独立 PCAN 通道,靠 .cursorrules 里的 channel 约定区分


八、效果对比、避坑与行动清单

这是我们组接 AI 自动化半年后的对比:

微信公众号二维码图片引自微信公众号,扫码关注阅读原文
接入 AI 前后的关键指标对比(红=前,绿=后)
指标
传统方式
AI 辅助
全量回归耗时
3 天(手工)
4 小时(自动+自愈)
脚本维护时间
基线 100%
降约 70%
核心场景覆盖率
约 55%
约 82%

5个必须知道的坑

坑1:AI 生成的代码必须人工 review
它偶尔会”想当然”,比如把信号分辨率算错、把等待时间设太短。我们的规矩:AI 生成的代码合入前,组长过一遍
坑2:CAN 时序 AI 理解不准
哪些信号要先发、间隔多少毫秒,AI 经常拍脑袋。这块我都是把 DBC 和时序文档喂足,再人工核对
坑3:多屏操作 AI 容易混淆
主驾屏和副驾屏控件 id 可能重名,AI 有时点错屏。我在 .cursorrules 里强制要求写明目标屏
坑4:.cursorrules 要持续迭代
项目规范在变,配置不更新,AI 就会退化。把它当成代码一部分,跟着版本提交
坑5:AI 不适合写复杂状态机用例。比如”连续五次急刹后的仪表告警逻辑”,状态跳转太多,AI 容易漏分支。这种核心用例还是人写更稳

10条行动清单

  1. 装好 Cursor,建一个车机测试专用项目
  2. 写好第一版 .cursorrules,先把技术栈和禁用坐标写进去
  3. 用 uiautomator2 + python-can 跑通一条最小用例(含信号校验)
  4. 把这条用例的结构(POM)固定下来,作为 AI 的”模板”
  5. 加截图+XML 的自愈逻辑,让脚本能自己修定位
  6. 用 JSON/Excel 把测试数据抽出来,做参数化
  7. 接 Jenkins,每次提交自动跑回归
  8. 加一步 AI 失败归因,把报告发到团队群
  9. 多台架并行,把回归压到 2 小时内
  10. 每周复盘 AI 生成代码的失误,反哺 .cursorrules