给自动化测试套上AI,测试跑得更快了——但脆弱的测试也崩得更快了。这不是AI的锅,是你脚下那层抽象本身就站不住。
生产力悖论的核心:AI放大的是底层抽象,不是你的期望
"AI生产力悖论"在这里有一个非常具体的含义:AI会放大它所依托的抽象层的一切特性——包括缺陷。 你把AI架在一个脆弱的抽象上,它不会帮你修补脆弱,它会把脆弱性以更高的速度和更大的规模扩散出去。
在测试自动化的语境下,这个"脆弱的抽象"就是DOM-centric(以DOM结构为中心)的测试范式。选择器写死、路径硬编码、依赖渲染树的特定层级——这些是DOM测试的常态,也是它的结构性软肋。当你用AI生成更多这样的测试、用AI自动修复选择器、用AI批量扩写用例,你得到的不是更可靠的测试,而是更多以同样方式崩塌的测试。
DOM测试为什么结构性脆弱
一个典型的问题场景:产品团队改了一个按钮的CSS类名,从btn-primary改成button--primary,或者把一个<div>换成了<button>。对用户来说什么都没变——按钮还在那里,还能点,功能一样。但你的测试套件可能几十条用例同时挂掉,因为它们全绑在那个类名或标签上。
这种脆弱不是偶发的bug,而是DOM抽象的本质属性。DOM是给浏览器渲染引擎用的内部结构,不是给测试用的稳定契约。前端框架的重构、组件库升级、无障碍改造,都会合法地改变DOM树,而用户行为和业务意图完全不受影响。
更具体地说,DOM测试有三层结构性风险:
- 选择器耦合:CSS选择器、XPath路径与实现细节绑定,任何重构都可能断裂。
- 状态依赖:测试依赖元素在DOM中的出现顺序、层级关系,而非用户可见的状态。
- 语义缺失:DOM节点没有"意图"信息——一个
<span>可能是按钮、标签、还是提示文字,测试无从区分,只能靠结构猜测。
当你让AI去"自动修复"这些断裂的选择器,AI找到的下一个选择器大概率同样脆弱——它只是在同一层抽象上换了一个锚点。
感知与意图:一个更抗变的抽象层
文章提出的方向是:放弃DOM作为测试的底层抽象,转向感知(perception)和意图(intent)。
- 感知:测试应该像用户一样"看到"界面——通过视觉特征、可访问性角色、可见文本,而不是通过DOM节点路径。
- 意图:测试应该表达"我要完成什么操作",而不是"我要点击第3个div下的第2个span"。
这个抽象层更抗变,因为它与用户实际体验对齐,而不是与内部实现耦合。按钮换了标签、列表换了组件、页面换了布局——只要用户还能看到同样的东西、完成同样的操作,测试就不应该挂掉。
实践:从DOM选择器到意图驱动测试
下面用Playwright演示两种写法的差异。先看DOM-centric的脆弱写法,再看基于感知与意图的写法。
脆弱的DOM写法
# ❌ 脆弱:绑定CSS类名和DOM层级
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://example-shop.com")
# 硬编码选择器——改个类名就全挂
page.locator("div.header > nav > ul > li:nth-child(2) > a.btn-primary").click()
# 依赖DOM顺序——加个筛选项就错位
page.locator("div.product-list > div:nth-child(3) > button.add-to-cart").click()
# 断言绑在内部属性上
assert page.locator("div.cart-summary > span.cart-count").inner_text() == "1"
browser.close()
任何一次前端重构——换组件、调布局、改BEM命名——这些测试都会批量失败,而用户操作完全没受影响。
感知与意图驱动的写法
# ✅ 抗变:基于可访问性角色、可见文本和用户意图
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://example-shop.com")
# 意图:我要导航到"Products"——用角色+文本,不关心DOM路径
page.get_by_role("link", name="Products").click()
# 意图:我要把第3个可见商品加入购物车——用角色定位
product_cards = page.get_by_role("article", name="Product card")
third_product = product_cards.nth(2)
third_product.get_by_role("button", name="Add to cart").click()
# 感知:断言购物车数量——读用户可见的文本,不读内部节点
cart_badge = page.get_by_role("status", name="Cart items count")
assert cart_badge.inner_text() == "1"
browser.close()
关键变化:
| 维度 | DOM写法 | 意图写法 |
|---|---|---|
| 定位方式 | CSS/XPath选择器 | ARIA角色 + 可见文本 |
| 耦合对象 | 内部DOM结构 | 用户感知的界面语义 |
| 抗重构能力 | 任何DOM变动都可能断裂 | 只要角色和文本不变就稳定 |
| 可读性 | 需要查DOM才能理解意图 | 读代码就知道在做什么 |
Playwright的get_by_role和get_by_text正是"感知与意图"在API层面的体现——它们映射的是无障碍树(accessibility tree),而不是DOM树。无障碍树是浏览器从DOM提炼出来的、面向用户和辅助技术的语义视图,天然更稳定。
用AI放大意图,而不是放大脆弱
如果你确实要用AI生成测试,把生成目标对准意图层:
# AI辅助生成意图测试的prompt模板(可用在任何LLM SDK)
import openai
def generate_intent_test(user_story: str, page_description: str) -> str:
prompt = f"""
你是一个测试工程师。根据以下用户故事和页面描述,生成Playwright意图驱动测试代码。
规则:
- 只用 get_by_role、get_by_label、get_by_text、get_by_test_id 定位元素
- 禁止使用 CSS 选择器或 XPath
- 每个操作必须表达用户意图,不要依赖DOM结构
- 断言只检查用户可见的内容
用户故事:{user_story}
页面描述(角色和可见文本):{page_description}
输出格式:可直接运行的Python代码
"""
response = openai.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
)
return response.choices[0].message.content
# 示例调用
test_code = generate_intent_test(
user_story="用户浏览商品列表,将第三个商品加入购物车,验证购物车数量更新",
page_description="导航栏有link角色'Products'; 商品卡片有article角色'Product card'; 每个卡片有button角色'Add to cart'; 购物车有status角色'Cart items count'"
)
print(test_code)
这样AI放大的是"意图抽象"——一个抗变的层。如果意图描述本身有缺陷,AI也会放大那个缺陷,但意图层的缺陷远比DOM选择器的缺陷更容易发现和修正,因为它与用户语言对齐,人能直接读懂。
落地考量与取舍
转向感知与意图不是零成本的,有几个现实问题需要正视:
团队需要学习无障碍语义。 get_by_role要求你理解ARIA角色体系——button、link、navigation、article、status等。这不是额外负担,而是写出可访问产品的必要知识,测试只是顺便受益。如果你的产品本身无障碍做得差(角色缺失、标签空洞),意图测试也会受阻——这恰恰说明产品语义层需要补强,而不是测试应该退回DOM。
并非所有场景都能纯意图驱动。 复杂的数据表格、动态图表、Canvas渲染——这些场景的可访问性支持参差不齐,有时需要data-testid作为补充锚点。data-testid是开发主动暴露的稳定契约,比CSS类名可靠得多,属于"显式意图"的一种。Playwright的get_by_testid就是为此设计的。
迁移是渐进的。 不需要一夜之间重写所有测试。新用例从意图写法开始,旧用例在重构时逐步替换。优先替换挂率最高的那批DOM测试——它们才是AI放大脆弱性时最危险的部分。
AI生成测试的约束必须写在prompt里。 如果你用AI批量生成测试,不加约束,它默认产出DOM选择器——因为那是训练数据中最常见的模式。必须在生成指令中明确禁止选择器、要求角色和文本定位,否则AI会忠实地把旧范式的脆弱性放大十倍。
测试自动化的下一步不是让AI跑更多DOM测试,而是换一个AI应该放大的抽象层。感知和意图与用户对齐,与实现解耦——这才是值得规模化的事情。