在医院做护士的朋友跟我说过一句玩笑话:“测试别人的代码,就像护理一个不听话的病人,你得先了解他的脾气秉性。”这话放在响应式布局的自动化验证上,简直贴切得不行。我接手过不少前端和全栈项目,每次功能测试跑得欢,一到不同分辨率下页面就“变形”:明明桌面端完美居中,到了平板就错位;手机上一顿操作猛如虎,结果按钮被底部栏挡住,点都点不到。你要说发现不了吧,也不是,人工缩放浏览器一个个试,眼都快花了。后来我引入了 Galen Framework,才算是把这块硬骨头啃了下来。这篇文章,我就把从选型、写 spec、集成 CI 到排查各种“灵异事件”的全流程实操经验,原原本本摊开来讲。
Galen Framework 在响应式布局自动化验证里解决的核心问题,说白了就是“用结构化的规则来描述布局,让机器替你检查每个元素该在哪、该多大、该不该显示”。它不像 Selenium 那样擅长模拟用户操作流,它的专长在于“看版式”——用一套叫 Galen Spec 的语法,把设计师和验收标准翻译成能自动跑起来的断言。适合谁?适合那些被响应式页面回归测试折磨得头疼的测试开发工程师、前端质量保障同学,以及所有需要在多个视口下反复确认页面“没走样”的团队。
1. 为什么是 Galen Framework:响应式验证的核心痛点与选型逻辑
先说清楚一个事:市面上做视觉回归的工具不少,比如 Percy、Applitools、backstopjs,都挺优秀。但 Galen Framework 的切入点和它们不一样,不同在哪?它验证的不是“像素长什么样”,而是“元素之间的空间关系”。这刚好命中了响应式布局测试的要害。
1.1 响应式布局测试到底难在哪
你要是只用 Selenium 做自动化,会发现绝大多数断言都在验证“行为”,比如点击按钮后弹窗出现、输入后文案更新。但“布局对不对”这件事,Selenium 很难给出直观判断。它知道一个元素是否存在,却很难回答“这个导航栏在 1366px 宽的屏幕上,是不是应该贴左边 20px、高度不小于 50px、而且不能和主内容区重叠”。
手工测试呢?每次改版、每次调 CSS、每次新增组件,都要在几个标准分辨率下肉眼过一遍。短时间还能忍,周期一长,比如一个月迭代两三个版本,人就会麻。而且人眼对于“左边多了 2px”这种细微偏移,基本是免疫的——看着差不多就放过了。但这些问题一旦累积,在不同设备上的体验差距会越来越大。
另一个痛点是:响应式布局的断点越来越多。以前就桌面、平板、手机三档,现在还有大屏桌面 1920、小屏笔记本 1366、iPad 竖屏 768、iPhone SE 的 375、折叠屏展开等一堆奇奇怪怪的宽度。每个断点下,布局规则可能都不一样。如果你用的是截图对比类工具,几张图做像素级 diff,稍微有点文字渲染差异、字体加载时序不同,就会产生一堆没有意义的 red diff,维护成本极高。
Galen Framework 绕开这个问题的方式是:不比较截图,比较几何关系。它在页面里把每个关键元素抽象成一个个 object,然后你告诉它“在宽度为 1024px 的视口下,header 应该高 80~100px,并且居中”“侧边栏应该和主内容区相邻,且自身宽度不小于 30% 的容器宽度”。它内部会调用浏览器 API 去测量这些元素的真实 bounding box,再和你的规则去比。这比两眼一抹黑地靠像素比对,要稳定和精确得多。
1.2 Galen 擅长什么,不擅长什么
任何工具都有自己的能力边界。Galen 擅长的是验证几何布局——元素的位置、尺寸、可见性、对齐方式、间距关系、重叠与否。它也能做页面跳转、点击、输入文本等基本交互,但它的交互能力相对 Selenium 要单薄不少。所以我在项目里通常是让 Galen 负责“版式验证”,用 Selenium/Appium 负责“复杂用户流测试”,两者互补而不是替代。
还有个容易踩的认知误区:Galen 不是视觉回归工具,它不做像素级截图比对。虽然它有截图功能,但那是用来给人看的现场回执,不是用来做 diff 的。如果你的诉求是“确保两版页面的每个像素看起来完全一致”,那应该去看 Applitools 或者 Percy。Galen 解决的是“在不同视口下,页面元素是否按照设计稿的几何约束排布”。这两种诉求听着接近,实际是两码事。
从我个人的选型经验来看,Galen 的另一个隐性优势是:它让验收标准变得“可读”。spec 文件写出来之后,产品经理、设计师都能参与 review——因为里面就是近乎自然语言的规则描述。对比一些用 Node 脚本或者 Java 代码写布局断言的方案,Galen 的 spec 文件几乎零学习成本,这个价值在跨团队协作时非常突出。
2. 核心语法入门:Spec 语言是 Galen 的灵魂
Galen Framework 的规范文件(.spec 后缀)是整套实践的核心资产。它不复杂,只要你理解几个核心概念:对象声明、布局规则、设备标签、页面对象。我们逐个拆开讲,每个都有对应的实际用法。
2.1 一条 spec 小试牛刀:对象声明与布局规则
对象声明用来告诉 Galen,页面上哪个元素是你关注的节点。语法很简单:
@objects header .header main-content #main-section footer .footer第一列是自定义的别名,第二列是 CSS 选择器。之后在规则里引用的时候,用别名而不是选择器,这样 spec 文件能保持较高的可读性。如果你用 Galen Pages 组件,对象声明甚至可以放到独立文件里复用。
有了对象之后,就该写布局规则了。Galen 的核心规则大约十几条,常见的有visible、hidden、width、height、inside、near、centered、left-of、right-of、above、below、left-edge等等。它们可以组合出非常丰富的布局断言。举个例子:
= 首页桌面端布局验证 = header visible height 80px to 100px centered inside viewport horizontally main-content visible below header 20px to 40px width 800px to 1200px footer visible near: bottom 0px inside viewport这段规则翻译成人话就是:header 要显示,高度在 80 到 100 像素之间,水平居中;主内容区在 header 下方 20~40 像素,宽度区间 800~1200;footer 贴住视口底部。看到没?这里面没有一张参考图,全是几何关系。同一个元素规则你可以用“范围”而不是“精确值”去约束,比如height 80px to 100px。这是刻意的,因为真实浏览器里字体渲染、缩放比例都会带来几个像素的浮动,只有傻瓜才把断言写到像素级精确。用范围断言,能避开很多环境因素带来的假失败。
不少人刚开始用 Galen 时容易犯一个错误:把对象声明里写了很多选择器,规则也写得很全,但忽略了“这个元素在某个断点下可能根本不存在”。比如移动端隐藏了侧边栏,DOM 里压根没有这个节点,visible规则就会失败。这时候你需要用hidden或者根据设备标签来区分规则,这正是下一节要讲的。
2.2 device 标签与三端布局规则
Galen 里最常用的组织方式,是给不同视口宽度打标签,然后在 spec 里用@on区块对不同标签分别断言。你可以在运行测试时通过命令行参数或代码指定视口尺寸,并同时指定它对应的标签。举个例子:
galen check homepage.spec --url "http://example.com" --size "1366x768" --device "desktop" galen check homepage.spec --url "http://example.com" --size "768x1024" --device "tablet" galen check homepage.spec --url "http://example.com" --size "375x667" --device "mobile"在 spec 文件里,你可以这样写:
@on desktop header height 80px to 100px centered inside viewport horizontally @on mobile header height 60px to 80px width 100% of viewport # 移动端隐藏侧边栏 sidebar hidden这里的@on就是在告诉 Galen:“当测试运行时指定的 device 标签是 desktop,就走第一组规则;是 mobile,就走第二组规则。”你完全可以自定义标签名,甚至同一个尺寸下挂多个标签,比如@on desktop high-res这类组合。这在项目里非常实用,因为不同业务页面可能对断点的定义不完全一致。
实际操作中我强烈建议你给标准设备尺寸建一张表,并且写进团队的 README 文档。因为如果每个人跑测试用的尺寸都不一样,那这个自动化体系很快就形同虚设。我自己在项目里维护了一套基准,长这样:
| 设备标签 | 视口宽度 | 典型设备 | 说明 |
|---|---|---|---|
| mobile-sm | 320px | 老款小屏手机 | 最小值边界 |
| mobile | 375px | iPhone SE/iPhone X | 主力手机档 |
| mobile-lg | 425px | 大屏手机 | 手机横屏的临界 |
| tablet | 768px | iPad 竖屏 | 平板竖屏 |
| desktop | 1366px | 主流笔记本 | 桌面默认档 |
| desktop-lg | 1920px | 大屏显示器 | 宽屏回归 |
每一次跑全量回归,这些尺寸都会跑一遍。虽然时间长了点,但换来的是“布局崩了,第一个知道的永远是自动化,而不是客户”。
2.3 用 Galen Pages 管理复杂页面
如果你测试的页面比较多,且同一套对象在多个 spec 文件里都要用,那我建议你别把对象声明重复贴在每个 spec 里。Galen 的解决方案是 Galen Pages——有点类似 Selenium 里的 Page Object 模式。它把对象声明和 spec 规则拆分,在 spec 文件顶部用@@引入,结构会清爽很多。
@@page /pages/main.page在main.page文件里:
@page MainPage header .header main-content #main-section footer .footer sidebar .sidebar nav-menu .nav-menu这样任何 spec 文件都可以引用 MainPage 里的对象。如果页面结构变了,只需要改一个地方。要注意,Galen Pages 里的 CSS 选择器是相对你正在测试的页面来说的。如果你的站点是多页面的,最好按页面维度去组织 Pages 文件。比如/pages/home.page、/pages/product-list.page、/pages/checkout.page,分类清晰,后边维护成本低。
Galen Pages 还有个隐藏好处:它支持继承。你可以在一个基础 Page 里统一定义全局通用的组件(比如 header、footer),然后在具体的业务页面里覆盖或者追加。这样能大大减少重复声明,尤其适合那些头部底部统一的站点。不过也别过度抽象,页面之间差异大的就别硬套继承,否则到了后期,一个 Page 文件里塞满条件分支,比不用还难维护。
3. 全流程实操:从环境搭建到 CI 集成的落地闭环
聊完理论基础,我们进正题,看看一个完整的落地流程是怎么搭起来的。我以 Java + Maven + TestNG 这套组合为例,这也是 Galen 官方支持最完善的一条路。当然你也可以用 Python 或者直接命令行,但 Java 生态的集成度最高,文档也最全。
3.1 环境准备与工程结构
第一步,在pom.xml里引入 Galen 的 Java 依赖。需要 JDK 8+,Selenium Java 依赖要和你浏览器版本匹配。我一般是这样配:
<dependency> <groupId>net.galensframework</groupId> <artifactId>galen-java-support</artifactId> <version>2.4.4</version> </dependency>配完之后,工程结构通常长这样:
project-root/ ├── pom.xml ├── galen.config ├── src/ │ ├── main/ │ │ └── java/ │ │ └── com/example/tests/ │ │ ├── HomePageLayoutTest.java │ │ └── BaseTest.java │ └── test/ │ └── resources/ │ ├── specs/ │ │ ├── homepage.spec │ │ └── listing.spec │ └── pages/ │ ├── home.page │ └── listing.page └── reports/spec 和 page 文件放在测试资源里,Java 测试类负责加载页面、设置视口尺寸、调用 Galen 的 API 去执行 spec。这个结构在中小规模项目里完全够用,也方便后续和其他测试框架融合。
3.2 编写第一个响应式验证测试
核心 Java 代码其实不走 Selenium 那套复杂的 expected conditions,你只要让 WebDriver 加载页面,然后调用checkLayout就行。我贴一段我实际一直在用的 BaseTest 变体:
import com.galenframework.api.Galen; import com.galenframework.reports.GalenTestInfo; import com.galenframework.reports.HtmlReport; import com.galenframework.reports.model.LayoutReport; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import java.io.IOException; import java.util.Arrays; import java.util.LinkedList; import java.util.List; public class HomePageLayoutTest { private WebDriver driver; private List<GalenTestInfo> tests = new LinkedList<>(); @BeforeClass public void setUp() { ChromeOptions options = new ChromeOptions(); // 无头方式跑适合 CI,本地调试可以去掉 options.addArguments("--headless=new"); options.addArguments("--window-size=1366,768"); driver = new ChromeDriver(options); } @Test public void homePage_desktop_layout() throws IOException { driver.get("http://example.com"); LayoutReport layoutReport = Galen.checkLayout(driver, "specs/homepage.spec", Arrays.asList("desktop")); GalenTestInfo testInfo = GalenTestInfo.fromString("HomePage desktop layout"); testInfo.getReports().add(layoutReport); tests.add(testInfo); } @Test public void homePage_mobile_layout() throws IOException { driver.manage().window().setSize(new org.openqa.selenium.Dimension(375, 667)); driver.get("http://example.com"); LayoutReport layoutReport = Galen.checkLayout(driver, "specs/homepage.spec", Arrays.asList("mobile")); GalenTestInfo testInfo = GalenTestInfo.fromString("HomePage mobile layout"); testInfo.getReports().add(layoutReport); tests.add(testInfo); } @AfterClass public void tearDown() throws IOException { HtmlReport htmlReport = new HtmlReport("reports/" + System.currentTimeMillis()); htmlReport.apply(tests); driver.quit(); } }这里Galen.checkLayout是核心 API,接受 WebDriver 实例、spec 文件路径和一组标签。标签就是 spec 里@on后面带的那个名字。每次调用返回一个LayoutReport,里面有所有规则校验的通过/失败明细。它的好处是即使某条规则失败,也不会抛异常打断整个测试——它会收集完所有失败点,最后统一在报告里展示。
注意一个细节:每个@Test方法我都显式设置了窗口尺寸,而不是依赖基类里的默认值。因为 TestNG 的执行顺序不保证,如果一个测试改了尺寸没改回来,下一个测试可能就在错误的视口下跑,这会直接导致 spec 里的@on标签错配。要么每个测试都显式设尺寸,要么在 tearDown 里重置,两者必须至少做一样。
关于视口尺寸还有一个容易搞混的概念:浏览器窗口大小和视口大小不完全相等。Chrome 的window-size参数指的是整个浏览器窗口,包含标签栏、书签栏这些,真正的页面视口会小几十像素。Galen 做布局验证时关心的是视口(viewport)而非窗口。如果你指定的尺寸和实际视口差太多,spec 里的width 100% of viewport这类规则就会产生偏差。最稳妥的办法是启动浏览器后先读一遍driver.manage().window().getSize()并打印出来,确认实际视口再往下走。
3.3 galen.config 调参与报告生成
Galen 的配置文件galen.config写在工程根目录,里面能调一堆全局项。我最常用的是下面这些:
galen.remote.screenshots = fullPage galen.browser.timeout = 60000 galen.default.page.url = http://example.com galen.reporting.html = reports galen.reporting.screenshot.full.page = false逐条解释一下:
galen.remote.screenshots = fullPage:允许 Galen 在测试时截取整页截图,方便失败时回看现场。galen.browser.timeout = 60000:页面加载或者元素查找的超时时间,默认 30 秒。碰到图片多、接口慢的业务页面,60 秒更稳妥。galen.default.page.url:如果在调用 spec 时不写 URL,Galen 会默认加载这个地址,适合固定环境调试。galen.reporting.html:HTML 报告输出目录,不写的话默认在当前工程目录生成reports文件夹。galen.reporting.screenshot.full.page:如果设成 false,截图只保留首屏;响应式验证时首屏截图往往足够定位问题,整页截图会拖慢速度。
关于galen.reporting.screenshot.full.page = false我特别提一句:很多人为了“看得全”开了整页截图,结果移动端长列表页面光截图就要多出好几秒,而且生成的文件体积巨大,CI 上传 artifacts 时能把人急死。你要知道,Galen 的主要产物是 Validation 结果文本,截图只是为了辅助排查,优先级没那么高。按需开整页截图才合理。
报告大概长什么样?Galen 会生成一个带缩略图的 HTML 页面,每个被测页面一张卡片,点进去能看到每条规则的 pass/fail 状态。失败项会标注实际测量值和期望范围。如果你是刚接触 Galen,我的建议是:让报告里的失败项和你的 spec 断言一一对应,这样领导问起来,你直接贴报告链接,比任何口播都有说服力。
3.4 接入 CI 持续回归
本地能跑通只是第一步,真正的价值在持续集成里。我习惯的做法是:每天凌晨跑一遍全量 Galen suite,所有设备和页面组合全跑;白天提交代码时用增量模式只跑 diff 影响到的页面。这个策略在 Jenkins 里用两个 job 实现,代码改动频繁时白天 job 反馈速度极快,晚上全量兜底。
接入 Jenkins 的核心是:Maven 跑 TestNG 时能输出标准 surefire 报告,Galen 的 HTML 报告在reports目录。Jenkins 里配好 archive artifacts,把报告上传到服务器,那么每次构建完,团队成员就能直接点开网页看结果。
命令行层面,我一般这样触发:
mvn test -Dtest=HomePageLayoutTest -Dgalen.config=galen.config同时把 Chrome 和 chromedriver 的版本锁进一个环境变量或 Dockerfile。这里有个血泪教训:chromedriver 和 Chrome 版本不匹配,整个 suite 会在一半的时候疯狂超时,报错信息又不明显,排查起来极其痛苦。最好用 Docker 镜像把浏览器、驱动、测试代码一起固化,每次 CI 用同一个镜像跑,从根上消灭环境漂移。
如果你用的是 Playwright 做其他测试,也别急着把 Galen 换掉。Galen 的定位不是 E2E runner,它在布局验证这个细分场景里做得又专又稳。两个框架完全可以共存:Playwright 跑交互流程,最后在关键页面节点上调用一下 Galen 的 spec;或者反过来,Galen 负责布局,Playwright 负责动作。我见过不少团队把两者结合得很好。
4. 问题排查与避坑经验:那些文档里不会写的细节
Galen 上手不难,但真正走进项目里,你会遇到一堆官方文档不会告诉你的“隐形门槛”。这一节我列几个最常见的坑,每一个都是我实际踩过、并且带着团队一起解决过的。
4.1 动态内容导致验证飘红
最常见的问题是:页面上有动态文案、广告位、异步加载的推荐位,导致元素高度或位置不稳定。比如你在 spec 里写死width 100% to 80%,但某个 A/B 实验把 banner 加高了 20px,主内容区被往下推,一条below header 20px to 40px的断言立刻失败。
对这种问题的解法不是把断言范围无限放宽,而是把动态区域隔离在外。我惯用的招数有几种:
- 给动态区域一个独立对象,只验证它的可见性和大致位置,不验证精确尺寸。
- 用
@groups分组,把强断言和弱断言分开。比如@on desktop下分critical和sanity组,CRITICAL 的规则失败就阻断发布,sanity 的规则失败只告警提醒。 - 针对异步渲染的内容,在运行测试前用
waitForElement这样的命令等它加载完,再执行 layout 验证。
要提醒的是,Galen 的断言本身不带“重试”机制。它测量的是“此刻页面的状态”,如果你的页面还在加载中,那这一刻测量出来的值本身就是无效的。所以规范做法是:先显式等待关键元素就位,再调checkLayout。你可以在 Java 代码里用 Selenium 的WebDriverWait来实现,并不复杂。
4.2 滚动与视口:截图的“隐雷”
Galen 在测量布局时,默认只测量元素在当前视口内的可见部分。如果一个元素在页面下方,需要滚动才能看到,那么inside viewport这类规则就可能因为“测量不到完整几何信息”而失败。这里常驻着一个争议点:inside viewport和inside screen在 Galen 的语义里不一样。前者对应浏览器可视区,后者对应整页文档。你要验证底部 footer 是否贴底,在大多数页面上应该用near: bottom 0px inside screen而非inside viewport——除非你确定 footer 首屏可见。
由于截图默认截取整个页面时,Galen 会先把页面滚动到顶部再裁长图,但这个行为在某些站点上会触发懒加载。最典型的就是图片懒加载——首屏外的图片在滚动后才加载,截图时机一旦没控制好,报告里就会看到一堆图片区域是空白。我建议的做法是:在setUp里先打开页面,主动执行一次滚动到底再滚回顶部,让所有懒加载内容就位,然后再开始跑 spec。代价是每个页面多一点时间,换来的是稳定的测量结果。
4.3 浏览器差异与 Selenium 版本兼容
Galen 对 WebDriver 的依赖比较重,版本敏感度也比一般工具高。Firefox 的 geckodriver 升级、Chrome 的 DevTools 协议变更,都可能让 Galen 的截图和尺寸测量出现偏差。我自己遇到过好几次:同一个 spec,本地 Chrome 全绿,CI 的 Firefox 挂了一半,看报告都是元素尺寸不对。后来逐条排查,发现是 Firefox 的默认字体渲染和滚动条宽度策略和 Chrome 不同,导致容器实际可用宽度少了一截。
解决方案是“分层治理”:
- 核心断言必须跨浏览器稳定,尽量用结构关系(比如
left-of、above),少用像素级精确断言。 - 针对不同浏览器,允许在 spec 里用
@on desktop firefox这类多级标签做差异化校准。比如 Firefox 的滚动条占 15px,就在 firefox 标签下给容器宽度多留 20px 余量。 - CI 里尽量固定浏览器版本和驱动版本,不要用
latest。在 Dockerfile 里锁死版本号,别让镜像漂移吃掉你的时间。
这又回到选型那个话题:Galen 的精细化控制能力强,但这份能力的代价是你得懂一点跨浏览器渲染差异。你不能指望一套 spec 在所有浏览器上毫无差别地通过——这不符合浏览器生态的现实。你的目标应当是:核心布局语义在所有浏览器上都正确,细节像素差异在可接受范围内。
4.4 对象找不到的定位排查
Galen 报 “Cannot find object” 是仅次于断言失败的常见事故。通常情况下,原因是 CSS 选择器没写对,或者页面结构和预期不一致。但也存在比较隐蔽的情形:元素在某个视口下是display:none的,甚至没进 DOM,而对象声明里还留着它。
我的排查套路是这样:
- 打开对应页面的浏览器控制台,直接执行
document.querySelectorAll("你的选择器"),确认返回结果数量。 - 如果返回 0 个,看是不是动态渲染还没完成;如果返回多个,确认你要测的是不是第一个。
- 如果元素在但不可见,检查 CSS 里是否被
display:none或visibility:hidden影响。Galen 的对象测量依赖真实可见性,隐藏元素在布局验证里没意义。 - 最后检查 spec 里是否引用了正确的 Page 文件,对象别名是否和
@page里声明的完全一致,大小写也别大意。
还有一个容易忽略的点:如果你在@objects里自定义了对象名,又在 Galen Pages 里定义了同名的对象,后者会覆盖前者。规范里应该统一管理,不要两种方式混着写。我在一个项目里吃过这个亏:一个 header 对象在 spec 里定义为.global-header,后来又在一个 Page 文件里加了同名但不同选择器的 header,结果测试时某些用例用了这个、某些用了那个,报错定位花了一晚上才发现。
5. 响应式自动化验证之后的思考:它到底给团队带来了什么价值
说了这么多实操,最后想聊一点工具之外的东西。响应式布局自动化验证这件事,表面上看是“用工具替代人工反复拖拽窗口”,但深一层看,它改变的其实是团队对“完成”的定义。
没有这套机制之前,前端同事说“我调好了,各个分辨率都看过”,你只能相信他,或者自己再花半小时人工复核。有了 Galen 的 spec 文件之后,“完成”变成了一组可执行的断言集合:桌面端导航不折叠、移动端菜单展开后不遮挡正文、footer 永远贴在安全区里。哪一条没过,代码就还没到位,没有模糊地带。
与此同时,spec 文件本身也成了一个不断进化的“设计契约”。每当 UI 调整,设计师改规范、前端改代码、测试改 spec,这三者之间天然形成了一种同步机制。比起靠群聊里的截图和口口相传,这种以代码为载体的协作粒度要可靠得多。
我个人的体会还有一个:Galen 虽然名字里带 Framework,但它其实更像一个“布局规则的编译器”。你给它规则,它帮你编译成可执行的检查。它的价值不在于工具本身有多炫,而在于它逼着你把“看起来还行”这种模糊标准,翻译成“高 80~100px、距顶部 20px、水平居中”这种精确语言。而这个翻译的过程,本身就能帮团队澄清很多潜在的需求分歧——比如设计稿里没写清楚断点处的间距是多少,产品经理和前端各执一词,那就来写一条 spec 规则,白纸黑字定下来。
最后分享一个实操里的小技巧,每次上新项目,我都会尽早把 Galen 接进 dev 环境的 smoke test,而不是等功能稳定了再做。原因很简单:布局问题就像慢性病,拖得越久,修复成本越高。改动一多,你根本不知道是哪次提交把间距搞坏的。有了自动化,至少能第一时间拉警报。哪怕最初只有两个页面、五条规则,也好过什么都没有。先让轮子转起来,再慢慢往上加页面和断言,是这条路最平滑的打开方式。