普通视图
【Playwright学习笔记 06】用户视觉定位的方法
根据文本内容定位
Page/Locator 对象的 get_by_text 方法
比如,
如果要获取 所有 文本内容包含 11 的元素,就可以这样
elements = page.get_by_text('11').all()
![]()
![]()
![]()
如果,你希望包含的内容是以 11 结尾的,就可以使用正则表达式对象 作为参数,如下
import re
elements = page.get_by_text(re.compile("11$")).all()
![]()
根据 元素 role 定位
web应用现在有一种标准 称之为: ARIA (Accessible Rich Internet Applications)。
ARIA 根据web界面元素的用途,为这些元素定义了一套 角色( Role ) 信息,添加到页面中,
从而 让 残疾人士,或者 普通人在 某种环境下(比如夜里,太空中),不方便使用常规方法操作应用,使用辅助技术工具,来操作web应用的。
若想定位这个元素:
<div class="alert-message">
您已成功注册,很快您将收到一封确认电子邮件
</div>
![]()
直接可以根据如下代码 定位该元素
# 根据 role 定位
lc = page.get_by_role('alert')
# 打印元素文本
print(lc.inner_text())
![]()
html元素中,有些 特定语义元素(semantic elements)被ARIA规范认定为自身就包含 ARIA role 信息,并不需要我们明显的加上 ARIA role 属性设置,
比如
<progress value="75" max="100">75 %</progress>
![]()
就等于隐含了如下信息
<progress value="75" max="100"
role="progressbar"
aria-valuenow="75"
aria-valuemax="100">75 %</div>
![]()
所以,直接可以根据如下代码 定位该元素
# 根据 role 定位
lc = page.get_by_role('progressbar')
# 打印元素属性 value 的值
print(lc.get_attribute('value'))
![]()
再比如 search 类型的输入框,默认就有 searchbox role,
<input type="search">
![]()
ARIA Attribute
ARIA规范除了可以给元素添加 ARIA role ,还可以添加其它 ARIA属性(ARIA attributes) ,比如
<div role="heading" aria-level="1">白月黑羽标题1</div>
<div role="heading" aria-level="2">白月黑羽标题2</div>
![]()
aria-level 就是一个 ARIA 属性,表示 role 为 heading 时的 等级 信息
上面的定义,其实和下面的 html 元素 h1/h2 等价
<h1>白月黑羽标题1</h1>
<h2>白月黑羽标题2</h2>
![]()
上例中,h2 元素,隐含了 role="heading" aria-level="2" , 所以可以用下面代码定位
lc = page.get_by_role('heading',level=2)
print(lc.inner_text())
![]()
Accessible Name
只根据 ARIA role 和 ARIA属性 往往并不能唯一定位元素。
role定位最常见的组合是 ARIA role 和 Accessible Name
因为,Accessible Name 就像元素的 名字 一样,往往可以唯一定位。
html 元素标准属性 name 是浏览器内部的,用户看不到,比如
Accessible Name 不一样,它是元素界面可见的文本名,
<a name='link2byhy' href="https://www.byhy.net">白月黑羽教程</a>
![]()
比如上面的元素,暗含的 Accessible Name 值就是 白月黑羽教程 , 当然也暗含了 ARIA role 值为 link
所以,可以这样定位
lc = page.get_by_role('link',name='白月黑羽教程')
print(lc.click())
![]()
上面的写法, 只要 Accessible Name 包含 参数name 的字符串内容即可,而且大小写不分, 并不需要完全一致。
所以,这样也可以定位到
lc = page.get_by_role('link',name='白月黑羽')
![]()
如果你需要 Accessible Name 和 参数name 的内容完全一致,可以指定 exact=True ,如下
lc = page.get_by_role('link',name='白月黑羽', exact=True)
![]()
name值还可以通过正则表达式,进行较复杂的匹配规则,比如
lc = page.get_by_role('link',name=re.compile("^白月.*羽"))
![]()
我们这里说一些常见的:
<a> <td> <button> Accessible Name 值 就是其内部的文本内容。
<textarea> <input> 这些输入框,它们的 Accessible Name 值 是和他们关联的 的文本。
比如:
<label>
<input type="checkbox" /> Subscribe
</label>
![]()
这个 checkbox 的 Accessible Name 却是 Subscribe
一些元素 比如 <img> ,它的 Accessible Name 是其html 属性 alt 的值
比如
<img src="grape.jpg" alt="banana"/>
![]()
role定位代码复杂,建议使用codegen代码助手来完成
# CSS 学习笔记:彻底掌握 `:nth-child(n)` 伪类选择器
CSS 学习笔记:彻底掌握 :nth-child(n) 伪类选择器
一、一句话定义(先锚定认知)
:nth-child(n)是一个基于父元素子节点顺序的结构化伪类选择器,用于匹配:
“是其直接父元素的第n个子元素,且自身标签名(或类型)与选择器前缀一致” 的元素。
它不关心“这是第几个<li>”,而只认“这是父元素的第几个孩子”。
二、语法全解:从简单到强大
1️ 基础形式(最常用)
| 写法 | 含义 | 示例 |
|---|---|---|
:nth-child(1) |
第1个子元素 |
li:nth-child(1) → 列表第一个 <li>
|
:nth-child(odd) |
所有奇数位子元素(1,3,5…) |
tr:nth-child(odd) → 表格奇数行 |
:nth-child(even) |
所有偶数位子元素(2,4,6…) |
div:nth-child(even) → 偶数位 <div>
|
2️ 数学公式 an + b(万能通式)
-
n是从0开始的整数(n = 0, 1, 2, 3...) - 计算结果
an + b即为匹配的子元素位置(从 1 开始计数)
| 公式 | 展开(n=0,1,2,3…) | 实际匹配位置 | 常见用途 |
|---|---|---|---|
2n |
0, 2, 4, 6… → 忽略 0 | 2, 4, 6, 8… | 等价于 even
|
2n+1 |
1, 3, 5, 7… | 1, 3, 5, 7… | 等价于 odd
|
3n+1 |
1, 4, 7, 10… | 每隔 3 个选第 1 个 | 网格首列高亮 |
n+3 |
3, 4, 5, 6… | 从第 3 个开始所有子元素 | “跳过前两个” |
-n+3 |
3, 2, 1, 0… → 0 及负数无效 | 1, 2, 3 | 前三个元素(黄金公式 ✅) |
记忆口诀:
even/odd→ 看是否被 2 整除;n+X→ 从第 X 个起全部;-n+X→ 前 X 个;an+b→ 代入 n=0,1,2… 算出有效正整数即可。
三、核心机制(必读!90% 错误源于此)
正确逻辑(三步判定)
对页面中每一个目标元素(如每个 <li>),浏览器执行:
-
定位父元素 → 找到它的
parentNode(直接父容器); -
计算位置 → 查看它在父元素的
children列表中是第几个(从 1 开始计数,只算元素节点,忽略纯文本/注释); -
双重校验 → ① 位置符合
an+b公式;② 元素类型与选择器前缀一致(如li必须是<li>标签)→ 两者都满足才匹配。
常见误解(红色警报!)
| 误解 | 为什么错 | 正确做法 |
|---|---|---|
“:nth-child(3) 就是第三个 <li>” |
❌ 它找的是“第3个子元素”,不是“第3个 <li>” |
✅ 改用 li:nth-of-type(3)
|
| “父元素没写,就找不到父” | ❌ 每个元素天然有父节点(<body> 或其他),无需显式声明 |
✅ li:nth-child(odd) 会自动作用于所有 <li>,各自找爹 |
“隐藏元素(display:none)不参与计数” |
❌ :nth-child 基于 DOM 结构,与渲染无关 |
✅ 隐藏元素仍占位置;若需“视觉上第n个”,需 JS |
| “空白换行会影响序号” | ⚠️ 现代浏览器已优化:只计算元素节点(Element Nodes),忽略纯空白文本节点 | ✅ 安心写格式化 HTML,无需压缩成一行 |
四、实战对比::nth-child() vs :nth-of-type()
| 维度 | :nth-child(n) |
:nth-of-type(n) |
|---|---|---|
| 判定依据 | 元素在其父元素中的总子元素序号 | 元素在其父元素中同类型元素的序号 |
| 是否跨类型计数 | ✅ 是(<h2>, <p>, <div> 全部计入) |
❌ 否(只数 <p>,忽略 <h2>) |
| HTML 示例 | <ul><h2>T</h2><p>A</p><span>S</span><p>B</p></ul> |
同上 |
p:nth-child(2) |
✅ 匹配 <p>A</p>(它是第2个子元素) |
— |
p:nth-child(4) |
✅ 匹配 <p>B</p>(它是第4个子元素) |
— |
p:nth-of-type(1) |
— | ✅ 匹配 <p>A</p>(第一个 <p>) |
p:nth-of-type(2) |
— | ✅ 匹配 <p>B</p>(第二个 <p>) |
| 何时选用 | 需要“按物理位置布局”(如表格隔行、网格首行) | 需要“按标签语义筛选”(如第3个段落、最后2个列表项) |
快速决策树:
你想选「排第几的元素」→:nth-child()
你想选「第几个同类元素」→:nth-of-type()
五、无父类选择器的真实含义(深度澄清)
li:nth-child(odd) { background: #eee; }
它不是“没有父”,而是“不限定父”
- 浏览器会为每个
<li>独立执行:当前<li>.parentNode.children→ 找到它在其中的索引 → 判断是否为奇数 → 是则应用样式。 - 因此,它可能同时作用于:
-
<ul>下的<li> -
<ol>下的<li> -
<nav>中的<li>(语义化导航) - 甚至
<section>直接子级的<li>(HTML5 允许)
-
为什么可以这样写?
- CSS 是全局作用域,选择器描述的是“满足条件的元素集合”,不强制绑定父容器;
- 这种写法简洁高效,适合通用样式(如所有列表隔行变色);
- ⚠️ 但大型项目中建议增加命名空间约束以防冲突:
/* 推荐:模块化限定 */
.product-list li:nth-child(odd) { ... }
.nav-menu li:nth-child(odd) { ... }
/* 不推荐:全局污染风险 */
li:nth-child(odd) { ... } /* 可能意外影响第三方组件 */
六、经典应用场景(附可复制代码)
场景 1:表格隔行变色(最稳方案)
<table>
<tr><td>张三</td><td>85</td></tr>
<tr><td>李四</td><td>92</td></tr>
<tr><td>王五</td><td>78</td></tr>
</table>
/* 推荐:用 :nth-child,不受 thead/tfoot 干扰 */
table tr:nth-child(even) { background: #f9f9f9; }
table tr:nth-child(odd) { background: #fff; }
场景 2:网格布局首行加粗
<div class="grid">
<div>1</div><div>2</div><div>3</div>
<div>4</div><div>5</div><div>6</div>
</div>
/* 每行3列 → 首行:1,2,3 → 位置 1,2,3 → 用 -n+3 */
.grid div:nth-child(-n+3) { font-weight: bold; }
场景 3:导航菜单仅第1、3、5项有下划线
.nav-link:nth-child(2n+1) { border-bottom: 2px solid blue; }
七、调试技巧(开发必备)
| 方法 | 操作 | 作用 |
|---|---|---|
| 浏览器开发者工具 | 在 Elements 面板选中 <li> → 右侧 Styles 查看是否生效 → 展开父节点数子元素 |
✅ 验证实际位置 |
| Console 快速验证 | 输入 document.querySelector('li:nth-child(2)')
|
✅ 返回匹配的第一个元素 |
| 临时高亮所有匹配项 | *:nth-child(odd) { outline: 2px solid red !important; } |
✅ 可视化所有奇数位子元素(慎用) |
八、兼容性与注意事项
| 项目 | 说明 |
|---|---|
| ✅ 浏览器支持 | Chrome 4+ / Firefox 3.5+ / Safari 3.1+ / Edge 12+ / IE9+(IE8及以下不支持) |
| ⚠️ 服务端渲染(SSR)注意 | 若 HTML 由服务端生成,确保结构与客户端一致,否则样式错位 |
| 🚫 不能做的事 | • 无法基于内容(如文字包含“重要”)筛选• 无法选择“倒数第2个可见元素”(需 JS)• 无法跨父元素计数(如“整个页面第5个 <li>”) |
附录:速查记忆卡(打印/收藏版)
| 目标 | 写法 | 备注 |
|---|---|---|
| 第1个元素 | :nth-child(1) |
最简写法 |
| 奇数位 |
:nth-child(odd) 或 :nth-child(2n+1)
|
二选一 |
| 偶数位 |
:nth-child(even) 或 :nth-child(2n)
|
二选一 |
| 前3个 | :nth-child(-n+3) |
✅ 黄金公式 |
| 从第4个起所有 | :nth-child(n+4) |
✅ 黄金公式 |
| 每4个选第3个 | :nth-child(4n+3) |
如 3,7,11,15… |
| 仅最后一个 | :nth-last-child(1) |
倒序计数 |
最后送你一句心法:
:nth-child()不是“找第几个<p>”,而是“站在父元素门口,点名叫第n个进门的孩子——还得确认他是不是你要找的那位”。
理解了这个画面,你就永远不会再写错。
vue3+vite使用unocss和vant做移动端开发适配,使用lib-flexible适配RemToPx
lib-flexible设置根字号
npm i lib-flexible
//然后在mian.js中引入
uno.config.js设置
根目录创建uno.config.js文件,需要安装 npm i @unocss/preset-rem-to-px
import { defineConfig, presetWind3 } from 'unocss'
import presetRemToPx from '@unocss/preset-rem-to-px'
export default defineConfig({
presets: [
presetWind3(), //预设样式
presetRemToPx() // 重点,把预设的rem转成px,这样postCssPxToRem就可以转了 例如预设样式 text-base的font-size:1rem;经过转换变成16px,在经过postCssPxToRem转换成rem
]
})
这个是vite的配置
import postCssPxToRem from 'postcss-pxtorem'
import UnoCSS from 'unocss/vite'
export default defineConfig({
plugins: [
UnoCSS(),
],
css: {
postcss: {
plugins: [
postCssPxToRem({
rootValue: 37.5,
unitPrecision: 6,
minPixelValue: 1,
propList: ['*'],
mediaQuery: false,
})
]
}
}
)}
其他的按照vant文档正常使用就好了
【Playwright 学习笔记 05】Xpath选择
XPath (XML Path Language) 是由国际标准化组织W3C指定的,用来在 XML 和 HTML 文档中选择节点的语言。
目前主流浏览器 (chrome、firefox,edge,safari) 都支持XPath语法,xpath有多个版本,目前浏览器支持的是 xpath 1的语法。
语法介绍
xpath 语法中,整个HTML文档根节点用'/'表示
![]()
![]()
绝对路径选择
从根节点开始的,到某个节点,每层都依次写下来,每层之间用 / 分隔的表达式,就是某元素的 绝对路径
上面的xpath表达式 /html/body/div ,就是一个绝对路径的xpath表达式, 类似 css表达式 html>body>div
相对路径选择
有的时候,我们需要选择网页中某个元素, 不管它在什么位置 。
xpath需要前面加 // , 表示从当前节点往下寻找所有的后代元素,不管它在什么位置。
所以xpath表达式,应该这样写: //div
通配符
如果要选择所有div节点的所有直接子节点,可以使用表达式 //div/*
* 是一个通配符,对应任意节点名的元素
根据属性选择
Xpath 可以根据属性来选择元素。
根据属性来选择元素 是通过 这种格式来的 [@属性名='属性值']
注意:
- 属性名注意前面有个@
- 属性值一定要用引号, 可以是单引号,也可以是双引号
根据id属性选择
选择 id 为 west 的元素,可以这样 //*[@id='west'] (选择任意元素中含有id=west的)
根据其他属性
同样的道理,我们也可以利用其它的属性选择
比如选择 具有multiple属性的所有页面元素 ,可以这样 //*[@multiple]
属性值包含字符串
要选择 style属性值 包含 color 字符串的 页面元素 ,可以这样 //*[contains(@style,'color')]
要选择 style属性值 以 color 字符串 开头 的 页面元素 ,可以这样 //*[starts-with(@style,'color')]
要选择 style属性值 以 某个 字符串 结尾 的 页面元素 ,大家可以推测是 //*[ends-with(@style,'color')] , 但是,很遗憾,这是xpath 2.0 的语法 ,目前浏览器都不支持
按次序选择
某类型 第几个 子元素
比如要选择 p类型第2个的子元素,就是
//p[2]
![]()
第几个子元素
也可以选择第2个子元素,不管是什么类型,采用通配符
比如 选择父元素为div的第2个子元素,不管是什么类型
//div/*[2]
![]()
某类型 倒数第几个 子元素
当然也可以选取倒数第几个子元素
比如:
- 选取p类型倒数第1个子元素
//p[last()]
![]()
- 选取p类型倒数第2个子元素
//p[last()-1]
![]()
范围选择
xpath还可以选择子元素的次序范围。
比如
- 选取option类型第1到2个子元素
//option[position()<=2]
![]()
- 选择class属性为multi_choice的后3个子元素
//*[@class='multi_choice']/*[position()>=last()-2]
![]()
组选择、父节点、兄弟节点
组选择
xpath也有组选择, 是用 竖线 隔开多个表达式
比如,要选所有的option元素 和所有的 h4 元素,可以使用
//option | //h4
![]()
选择父节点
xpath可以选择父节点, 这是css做不到的。
某个元素的父节点用 /.. 表示
要选择 id 为 china 的节点的父节点,可以这样写 //*[@id='china']/..
兄弟节点选择
xpath也可以选择 后续 兄弟节点,用这样的语法 following-sibling::
比如,要选择 class 为 single_choice 的元素的所有后续兄弟节点 //*[@class='single_choice']/following-sibling::*
等同于CSS选择器 .single_choice ~ *
如果,要选择后续节点中的div节点, 就应该这样写 //*[@class='single_choice']/following-sibling::div
xpath还可以选择 前面的 兄弟节点,用这样的语法 preceding-sibling::
比如,要选择 class 为 single_choice 的元素的 所有 前面的兄弟节点,这样写
//*[@class='single_choice']/preceding-sibling::*
![]()