ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

WebUI自动化必学:HTML与CSS定位实战指南

WebUI自动化必学:HTML与CSS定位实战指南 做 WebUI 自动化这些年面试过不少人我几乎都会问同一个问题“给你一个页面的 HTML 源码你能不能在十秒内判断出该用什么方式定位元素”能当场给出清晰答案的并不多。很多人用 Selenium 或 Playwright 写脚本很熟练测试用例也跑得通但页面只要一改版脚本就倒一片改定位器改到怀疑人生。根子大多不在框架用法上而在 HTML 和 CSS 的底子不够扎实。这篇文章要聊的就是 WebUI 自动化绕不开的 HTML 与 CSS 知识。它不是什么高深的前端理论而是一套“自动化视角下的页面解读方法”怎么从 DOM 结构里找到可靠节点怎么用 CSS 选择器写出稳定的定位表达式怎么分辨一个元素到底是真的能点击还是被某些样式挡住了。无论你是刚接触自动化测试的新手还是被动态元素、iframe、伪元素折磨过的老手这篇文章都能给你一套可以直接落地的分析思路和排查方法论。1. 自动化测试为什么绕不开 HTML 和 CSS1.1 元素定位的本质一切脚本都在跟页面结构打交道WebUI 自动化最核心的动作无非是三件事找到元素、操作元素、断言结果。而“找到元素”这件事本质上就是在和 HTML 打交道。你写的driver.find_element(By.ID, username)底层做的是在浏览器的 DOM 树中查找 id 属性为 username 的那个节点你写的page.locator(.btn-primary).click()底层是根据 CSS 选择器.btn-primary在文档里做元素匹配。框架只是封装了查找逻辑真正决定查找成败的是你对页面结构理解得有多深。我常跟团队里的小朋友说一句话自动化脚本是你写给页面看的“说明书”但页面只认 HTML 结构这一种语言。你会不会写 XPath能不能分辨div和span的嵌套关系知不知道input的type属性会如何影响定位方式——这些才是脚本稳定性的分水岭。同样一个登录按钮有人用//button[contains(text(),登录)]写得干干净净有人却靠复制浏览器生成的超长绝对路径一跑就废原因就在这里。1.2 HTML/CSS 底子好坏直接决定脚本的稳定性很多人以为 CSS 只是管样式的跟自动化关系不大这是最大的误解。一个元素能不能被点击、会不会被判定为可见、文本断言为什么总是对不上背后很多时候都是 CSS 在“捣鬼”。举个最常见的例子一个按钮设置了display: noneSelenium 的is_displayed()会返回 False你直接点它就会报ElementClickInterceptedException但同样是隐藏如果用的是opacity: 0某些情况下元素依然占位、依然可以接收点击断言结果就完全不一样。再比如页面上的元素被一个position: fixed的浮层盖住了你的脚本用element.click()点击时浏览器会告诉你“元素不可点击”因为实际被点击的目标是那个浮层而不是你想要的按钮。这种问题不深入理解 CSS 的盒模型和定位机制光靠调等待时间是永远调不好的。所以我会把 HTML 和 CSS 当作自动化测试的“内功”来看待。框架 API 是招式页面的结构理解力是内力。招式可以看文档速成内力不行必须一点一点攒。2. HTML 核心知识自动化视角下的页面骨架2.1 标准文档结构和 DOM 树先知道页面是怎么长出来的随便打开任何一个网页的源码基本结构都是这样的!DOCTYPE html html langzh-cn head meta charsetutf-8 title页面标题/title link relstylesheet hrefstyle.css /head body div idapp button classbtn-login登录/button /div /body /html!DOCTYPE html是文档类型声明告诉浏览器用标准模式渲染html是整个文档的根节点head里放的是元信息、样式表和脚本用户看不见body里才是真正渲染到屏幕上的内容。自动化脚本操作的绝大多数元素都在body里但head也不是完全无关比如某些断言需要读取页面标题或 meta 信息时就用得上。浏览器拿到这份 HTML 后会把它解析成一棵“树”也就是 DOM 树Document Object Model。树上的每个标签都是一个节点节点之间有父子、兄弟关系。html是根head和body是它的子节点body下面又挂着各种div、button、a。自动化定位的过程说穿了就是在这棵树上找节点。理解 DOM 树对写定位器的最大帮助是让你学会“顺着结构找元素”。很多时候元素本身没有 id 也没有 class但它有一个有名字的父节点或者它和某个固定节点存在稳定的兄弟关系这时候你就可以用层级关系去定位它。我见过不少人一遇到这种元素就死磕 XPath 写绝对路径其实如果脑子里有 DOM 树的结构感用div.form-item input或者//div[contains(class,form-item)]//input这种相对路径就能轻松解决。2.2 高频标签和属性自动化最常用的映射关系做 WebUI 自动化你需要重点掌握的高频标签其实就那么十几个远不需要像前端开发一样把 HTML 规范全背下来。我按使用频率排个序div和span无语义块级/行内容器页面上绝大多数结构都由它们撑起来。a超链接定位时常用它的href属性或链接文本来判断是不是你要找的那个。input表单项老大type属性决定了它是文本框、密码框、单选框、复选框还是隐藏域。button按钮很多场景下用它的文本内容定位最直接。form表单容器回车提交时有用但它本身很少作为定位目标。select和option下拉框Selenium 有专门的Select类处理。ul、ol、li列表结构菜单和导航栏的主力军。table、tr、td表格数据虽然老派但在后台管理系统里依然常见。iframe内嵌页面遇到它必须先切换上下文否则元素死活找不到。label表单字段的文本标签有时候点 label 也能触发对应控件的交互。属性方面和定位关系最密切的是这几个id页面内应唯一是定位的“第一优先”依据。class一个元素可以挂多个 class比如classbtn btn-primary定位时建议只用最标签化的那个比如.btn-primary。name传统表单元素常用在 Selenium 里有专门的find_element(By.NAME, ...)。type决定了input的具体行为也是属性选择器的高频用法。value表单控件的当前值断言输入是否正确时经常读到。placeholder输入框的提示文案定位时也常用。>input typetext idusername classform-control nameuserName placeholder请输入用户名那你可以有至少四种定位方式定位方式写法示例稳定性评估ID 定位find_element(By.ID, username)最稳优先用Name 定位find_element(By.NAME, userName)稳表单元素常见CSS 选择器#username/input[nameuserName]/.form-control简洁高效XPath//input[idusername]灵活但容易写啰嗦很多初学者会纠结到底学 XPath 还是 CSS我的建议是两个都要会但策略不同能用 CSS 就用 CSS因为浏览器原生支持querySelector解析快、写法简洁需要靠文本定位比如按钮上的“登录”二字或者需要从子元素反向找父元素时才用 XPath。这个选择逻辑后面在实战章节会展开讲。还有一个很重要的认知id 是 Selenium 的By.ID、XPath 和 CSS 三种方式都能定位的“万能属性”但 id 并不是每个元素都有。现代前端框架React、Vue 等渲染出的页面id 常常是动态生成的今天叫app-root_123明天可能就变成app-root_456。这时候死磕 id 反而会踩坑反而是一段稳定的div.form-item input层级结构更靠谱。这就是为什么我说 HTML 功底决定定位器的质量上限。3. CSS 核心知识看得见的样式决定看不见的可操作性3.1 三种样式引入方式和优先级定位和渲染都逃不开它CSS 有三种引入方式自动化测试人员至少要知道它们的区别因为这会直接影响你对“这个元素为什么长这样”的判断。外部样式表用link relstylesheet href...引入这是生产环境最常见的方式样式全在一个.css文件里内部样式表是写在style标签里的通常用于单页面的定制样式内联样式是直接写在元素style属性上的比如div styledisplay:none优先级最高自动化调试时最容易被它迷惑。三种方式叠加时浏览器按照一套优先级规则来决定最终生效的样式内联样式 id 选择器 class 选择器 标签选择器!important可以强行提升某一行的权重。这套规则对自动化的意义在哪里它帮你快速定位“元素为什么不可见/不可点”。比如你在开发者工具里看到一个元素明明没有隐藏样式但就是is_displayed()为 False这时候就该往它的样式来源上排查是不是外层容器的 class 里有display: none是不是媒体查询在某些屏幕尺寸下把它藏了而不是盯着元素本身看。3.2 影响元素可见性与可点击性的关键属性这是 CSS 里对自动化最重要的一个板块。我列一份“遮挡与隐藏手法清单”这些情况我都实际踩过坑display: none元素完全不占据空间DOM 里存在但不可见不可交互is_displayed()为 False。visibility: hidden元素占位但不可见同样不可交互。opacity: 0元素完全透明但仍占位仍可能接收事件。width: 0; height: 0或overflow: hidden元素被压缩成看不见的零尺寸。position: fixed/absolute配合高z-index一个浮层把目标元素盖住点击事件会被截胡。pointer-events: none元素自身对鼠标事件“免疫”点击会穿透到它下面的元素。前两种情况是“真不可见”后四种是“看起来不可见但实际占坑”或者“占坑但挡路”。自动化脚本报错时你会看到各种不同的表现NoSuchElementException说明根本找不到节点ElementNotInteractableException说明找到了但不可交互ElementClickInterceptedException说明被其他东西挡住了。看一眼异常类型再对照上面这张表排查方向立马就有了。在 Selenium 里is_displayed()的底层判定逻辑是元素必须有非零的宽高且它的visibility不是hidden且所有祖先节点都没有display: none。Playwright 的可见性判定更严格一些要求元素有非空的包围盒bounding box并且visibility是visible。理解这些底层逻辑之后你就不会被is_displayed()返回 True 但依然点不到元素这种奇怪问题困住了——前者只说明“可渲染”后者是“可交互”两码事。3.3 伪类与伪元素自动化的“隐形暗礁”伪类和伪元素是新手最容易忽略、老手也偶尔翻车的地方。伪类表示元素的某种状态比如:hover鼠标悬停、:focus获得焦点、:disabled禁用、:checked选中它们不会出现在 DOM 里但会改变元素的样式和行为。自动化断言时偶尔会遇到明明页面上按钮是灰色的不可点状态你读出来 class 也没有变化其实它是通过:disabled伪类控制的元素本身没有disabled属性。伪元素更隐蔽::before和::after可以在元素内容的前后插入额外内容。很多 CSS 图标、工具提示、必填星号都是用伪元素做的。它们不在 DOM 树中但确实参与渲染。这带来一个经典问题你用 Selenium 的element.text去断言一段文字断言结果总是不对打开开发者工具却明明看得到文字。原因很可能就是这段文字是通过::before的content属性渲染出来的而不是元素的文本节点内容。遇到这种情况我的建议是不要在框架层面硬扛而是调整断言策略——要么断言其他真实存在于 DOM 中的文本要么和前端同事确认好约定把关键信息放到真实元素里。伪元素适合做视觉装饰不适合承载业务流程的关键数据这个认知在自动化项目里很重要。4. 实战从页面源码到稳定定位器的一套完整方法4.1 打开开发者工具的姿势从 Elements 面板开始理论说再多不如实操一把。拿到任何一个页面打开浏览器开发者工具F12切到 Elements元素面板这是自动化测试人员的主战场。这里面有几个必须练熟的操作点击左上角的箭头图标然后在页面上移动鼠标点选元素Elements 面板会自动定位到对应的 DOM 节点同时右侧的 Styles 标签页会展示这个元素的所有样式来源。这个操作解决的是“嘴上说要找的元素到底是哪个 DOM 节点”的问题比盯着源码来回看高效得多。在 Elements 面板里按 CtrlF 可以打开一个搜索框这个框支持输入 XPath 或 CSS 选择器进行实时匹配。我写定位器时几乎必用这个功能先在搜索框里敲一版表达式按回车看它高亮了几个元素如果高亮的是一个且只有那一个再拿去做脚本。这个“先验证后写代码”的习惯能帮你省掉大量调脚本的时间。另外右键点击 DOM 节点有 Copy Copy selector 和 Copy XPath 两个选项。很多新手直接复制出来用这是大忌——浏览器生成的路径通常是类似#main div.container div.row div:nth-child(3) div.card button这种绝对路径中间任何一个结构变化都会让它失效。我一般只把复制的表达式当作“参考线索”手动改成相对定位。4.2 XPath 与 CSS Selector 的选择逻辑到底什么时候用 XPath什么时候用 CSS我给团队定的一个简单规则能解决大部分问题场景推荐方案原因有 id 或稳定的 classCSS Selector语义清晰、性能好需要按文本内容定位XPathCSS 不支持按文本匹配需要从子元素找父元素XPathCSS 没有“从下往上”的路径需要匹配属性的一部分两者均可CSS 用^、*、$XPath 用starts-with和contains元素结构复杂且都无特征XPath 优先可以通过中间节点建立相对路径举个具体的例子现在要定位一个“确认删除”按钮它在弹窗里弹窗里的登记文本是“此操作不可恢复”div classmodal-footer button classbtn-default取消/button button classbtn-danger确认删除/button /div最稳的写法是div.modal-footer button.btn-dangerCSS 一步到位。但如果是这样div classmodal-footer button classbtn-default取消/button button确认删除/button /div后面的按钮没有独立 class此时 CSS 只能用div.modal-footer button:last-child牺牲一点语义换稳定性或者干脆上 XPath//div[contains(class,modal-footer)]//button[contains(text(),确认删除)]。按文本定位在中文页面里太好用了这是 XPath 不可替代的价值。还有一点值得提醒XPath 里的contains和text()组合有个容易翻车的小细节。.//button[contains(text(),确认)]只能匹配直接文本节点如果按钮内部嵌套了span文本就不在 button 的直接子级里要用.//button[contains(.,确认)]用.匹配当前节点的全部文本内容。两类写法看起来差不多实际效果天差地别。4.3 写定位器时的自查清单经过无数个被线上 bug 反复捶打的夜晚我总结了一份写定位器的自查清单每次写完都过一遍能很大程度上避免返工这个表达式是“我写的”还是“浏览器生成的”如果是复制的能不能简化成相对路径表达式里有没有依赖动态 id、动态 class、动态索引有的话必须换方案。能不能用>
返回列表