做过Web/App测试的人,应该都遇到过这种场景:
接口是200。
DOM存在。
按钮能点。
自动化Case全部通过。
结果产品打开页面看了一眼:
**“这个按钮都被挡住一半了,你们没测出来?”**
测试工程师再打开自动化报告:
```text
login_test PASS
checkout_test PASS
payment_test PASS
```
全部绿得很漂亮。
但真实页面里:
按钮确实被遮挡了。
文字确实截断了。
金额确实重叠了。
这就是传统UI自动化一直存在的一个天然盲区:
> **它很擅长判断“元素在不在、能不能点”,却不一定知道“用户最终看到的页面到底对不对”。**
而8月21日,DeepSeek上线首个V4系列多模态视觉理解实验模型 **V4-Flash-Vision-Exp**。
它真正值得测试工程师关注的,不只是“DeepSeek也能看图了”。
而是:
**截图,开始有机会成为AI测试里一个真正低成本、可工程化的输入。**
---
## 一、先说一个大家都遇到过、但很少想深的问题
假设你现在负责一个商城登录页面。
传统Playwright代码可能是这样:
```python
from playwright.sync_api import expect
def test_login(page):
page.goto("https://test.example.com/login")
page.get_by_label("用户名").fill("tester")
page.get_by_label("密码").fill("123456")
page.get_by_role(
"button",
name="登录"
).click()
expect(page).to_have_url(
"https://test.example.com/home"
)
```
这段代码能证明什么?
很简单:
**用户能够完成登录流程。**
但是假设页面因为CSS问题,变成了这样:
```text
用户名 [____________]
密码 [____________]
登 录 登 录 登...
```
按钮文字被截断。
或者移动端出现:
```text
┌────────────────────┐
│ │
│ 提交订单 │
│ │
████████ 固定底栏 █████
```
提交按钮有一部分被Fixed Footer挡住。
但DOM里:
按钮存在。
Playwright也能定位。
甚至脚本仍然能成功执行:
```python
page.get_by_role(
"button",
name="提交订单"
).click()
```
最后Case:
```text
PASS
```
可真实用户看到的页面:
已经出了问题。
---
## 二、所以面试官问“UI自动化能不能覆盖所有UI问题”,别只回答“不能”
这道题很多初级测试工程师都会遇到。
最常见的回答是:
> 不能,有些样式问题自动化测不到。
没错。
但如果面试里只回答到这里,基本还是停留在表面。
更深一层其实是:
> **传统UI自动化主要验证DOM、交互行为和业务状态,而用户看到的是浏览器最终渲染结果,两者并不是同一个测试对象。**
换句话说:
```text
DOM正确
≠
页面一定正确
```
再进一步:
```text
按钮可点击
≠
用户一定能正常看到
```
以及:
```text
文字存在
≠
文字没有发生截断
```
很多UI Bug,本质上不是“元素不存在”。
而是:
**元素之间的视觉关系错了。**
这也是为什么视觉回归测试一直有价值。
---
## 三、那传统视觉回归为什么一直让测试工程师又爱又恨?
Playwright其实很早就支持截图断言。
比如:
```javascript
await expect(page).toHaveScreenshot(
'login-page.png'
)
```
如果页面变化,就会生成Diff。
还可以允许一定误差:
```javascript
await expect(page).toHaveScreenshot(
'login-page.png',
{
maxDiffPixelRatio: 0.01
}
)
```
也可以把动态区域Mask掉:
```javascript
await expect(page).toHaveScreenshot({
mask: [
page.getByTestId('avatar'),
page.getByTestId('timestamp')
]
})
```
看起来已经很完善。
但真正落地以后,很多团队会遇到一个痛点:
**误报太多。**
字体渲染变化。
浏览器版本变化。
时间戳变化。
头像变化。
广告位变化。
一个像素位置轻微偏移。
最后:
```text
100条视觉Case
48 FAILED
```
测试工程师人工打开48张Diff图。
发现:
**47张其实不是Bug。**
这就是传统Pixel Diff最大的问题:
> **它知道哪里“不一样”,却不知道这个“不一样”到底重不重要。**
---
## 四、这正是多模态模型开始有价值的地方
以前视觉回归更像:
```text
页面截图
↓
Pixel Diff
↓
PASS / FAIL
```
现在可以多加一层:
```text
页面截图
↓
视觉变化检测
↓
DeepSeek Vision
↓
语义判断
↓
风险分级
```
最重要的一点是:
**不要让视觉大模型完全替代Pixel Diff。**
更合理的思路应该是:
> **Pixel负责找不同,视觉模型负责理解“这个不同有没有业务意义”。**
举个最简单的例子。
基线图:
```text
订单金额:¥999
```
新页面:
```text
订单金额:¥999
```
只是字体位置偏了2像素。
传统视觉Diff:
```text
FAILED
```
但其实业务完全正常。
反过来:
基线:
```text
确认支付 ¥999
```
新页面:
```text
确认支付 ¥99
```
可能只少了一个数字。
像素变化比例不一定很大。
但这是非常严重的业务Bug。
所以:
**像素差异大小,不等于业务风险大小。**
这恰恰是多模态视觉模型可以补上的能力。
---
## 五、最小实战:截图直接交给视觉模型分析
第一步还是Playwright。
```python
from playwright.sync_api import sync_playwright
def capture_page():
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(
viewport={
"width": 1440,
"height": 900
}
)
page.goto(
"https://test.example.com/login"
)
page.screenshot(
path="login.png",
full_page=True
)
browser.close()
```
接下来,把截图变成模型可以消费的输入。
```python
import base64
def image_to_base64(path):
with open(path, "rb") as f:
return base64.b64encode(
f.read()
).decode("utf-8")
```
然后给视觉模型一个明确的测试任务。
例如:
```text
你是一名UI测试工程师。
请检查截图中是否存在:
1. 文本截断
2. 控件重叠
3. 按钮被遮挡
4. 输入框显示异常
5. 间距严重错误
6. 文案显示不完整
7. 影响正常操作的视觉问题
请返回JSON结构。
```
我们期待模型给出的不是一句:
> “页面看起来有问题。”
而是更结构化的结果:
```json
{
"passed": false,
"issues": [
{
"type": "button_occlusion",
"element": "提交订单按钮",
"severity": "high",
"description": "按钮底部区域被固定导航遮挡",
"impact": "可能影响移动端用户点击"
}
]
}
```
这时候测试报告的价值就完全不同了。
过去:
```text
Pixels differ: 18273
```
现在:
```text
提交订单按钮存在遮挡风险
严重级别:High
可能影响:移动端点击
```
对测试工程师来说,显然后者更接近真实工作。
---
## 六、但这里马上又会出现一道更重要的面试题
面试官:
> **视觉模型说按钮被遮挡了,你直接提Bug吗?**
答案一定应该是:
**不能。**
因为大模型也是概率模型。
它可能:
误判。
漏判。
幻觉。
所以真正的AI视觉测试绝对不能设计成:
```text
模型说有Bug
↓
自动提Bug
```
而应该变成:
```text
视觉模型发现异常
↓
程序继续取证
↓
确定性规则验证
↓
风险分级
↓
输出缺陷报告
```
例如模型说:
> “提交按钮可能超出可视区域。”
那我们继续用Playwright取Bounding Box。
```python
button = page.get_by_role(
"button",
name="提交订单"
)
box = button.bounding_box()
viewport = page.viewport_size
print(box)
print(viewport)
```
假设得到:
```text
button y = 780
button height = 70
viewport height = 800
```
那么就可以计算:
```python
button_bottom = (
box["y"] + box["height"]
)
if button_bottom > viewport["height"]:
raise AssertionError(
"提交按钮超出可视区域"
)
```
这时候证据链就完整了。
模型负责:
**发现问题。**
程序负责:
**证明问题。**
---
## 七、这才是多模态模型进入测试的正确姿势
很多人看到视觉模型,会自然想到:
> 以后是不是可以不用写UI自动化了?
我反而觉得完全不是。
真正合理的组合应该是:
```text
Playwright
+
截图
+
Pixel Diff
+
视觉模型
+
确定性断言
```
其中:
Playwright负责执行。
Pixel Diff负责低成本筛查。
视觉模型负责语义理解。
传统断言负责最终事实验证。
这几个东西不是互相替代关系。
而是:
**互相补盲区。**
尤其对于测试开发工程师来说,真正值得研究的不是:
“DeepSeek能不能看懂一张图。”
而是:
> **如何把视觉能力接进原来的自动化测试工程体系里。**
---
## 写在最后
过去UI自动化最擅长回答的是:
> **“这个按钮存在吗?”**
以后我们有机会继续往前问一步:
> **“用户真正看到的按钮,到底是不是正常的?”**
这两个问题看起来差别不大。
背后其实是两类完全不同的测试能力。
以前:
```text
Selector
+
Action
+
Assert
```
接下来可能越来越多变成:
```text
Screenshot
+
Vision Model
+
Semantic Assertion
+
Evaluator
```
但有一条原则始终不会变:
**不要把所有判断权交给大模型。**
让AI去做它擅长的:
看。
理解。
发现异常。
让程序继续负责:
取事实。
做计算。
验状态。
给证据。
真正有价值的AI视觉测试,不是:
**“大模型帮测试工程师看截图。”**
而是:
> **让视觉模型发现过去自动化根本不知道应该看的问题,再用传统测试工具证明它到底是不是Bug。**
这可能才是DeepSeek视觉模型对测试开发真正值得关注的地方。
而下一篇,我们会进入一个更复杂、更接近真实业务的问题:
**接口正确、数据库正确、DOM也正确,为什么电商结算页依然可能出现P1级Bug?**
那才是多模态视觉测试真正开始体现业务价值的地方。