2023年10月

某天安全工程师反馈我们的某个网站里包含违规内容,需要立即处理,否则整个主域名有被封禁的风险,主域名下有上千个子域名,涉及业务非常广,真要封禁非同小可,我们不敢怠慢,立即处理。初步排查,域名确实指向了某个违规的网站,但网站不是我们的,应该是项目下线,主机销毁IP释放,但域名并没有及时删除,新的用户被分配了同样的IP,然后这个IP指向了违规的网站,间接的域名也就指向了违规的网站。删除域名解析,问题解决,但隐藏在这个问题背后的是我们对域名管理的失控,鉴于此,域名管理系统势在必行,这篇文章就来介绍下我们域名管理系统是如何设计的,究竟做了哪些个事情

域名管理系统整体上包含三个部分,分别是查询、变更、巡检,接下来分别介绍下这三个部分的内容

查询

首先是查询,我们要有完善的域名数据库,包含所有的域名信息,能清晰的知道我们有哪些域名,这些域名在哪里注册、在哪里管理、当前状态如何、什么时间过期,以及每个域名下的记录数量等等域名基础数据

除了域名的数据也要能够获取全量的解析记录数据,包含域名记录、类型、线路、记录值、状态、TTL时间等解析记录相关的基础数据。这些数据我们可以通过域名提供商的API获取到,难度不大

当有了这些基础域名解析数据后,就可以通过
多云系统
获取到域名解析所对应的基础资源,可能是云主机、可能是负载均衡、可能是CDN、可能是DDOS、也可能是WAF,但无论是什么,我们都可以通过详尽的基础资源数据库来匹配。这一点对于我们做域名盘点非常有用,如果能匹配到资源且资源的状态正常的话那大概率域名依然在使用,如果匹配不到资源或者是匹配到资源已非正常使用状态,那么域名也大概率不在使用了

当然这里可以展示所有域名解析相关的数据,而不仅仅是域名解析关联的资源,例如后边会提到的域名变更记录数据、域名标记情况或是域名解析HTTPS数据等等,丰富域名解析关联数据,让域名解析不再孤立

变更

查询没有问题了,那我们接下来把域名的整个生命周期管理也给纳入了进来,域名作为系统的入口,其重要性不言而喻,域名的错误变更将会直接导致服务的中断或故障,所以所有域名的变更都要有二次确认的机制,在这个过程中我们引入了工单,所有变更都从工单开始

新建/启用/停止/编辑/删除操作直接触发工单流程,用户提交,域名管理员二次确认,没有问题审批通过,则自动触发域名变更的操作。当然在确认的过程中一样可以展示域名关联的信息,例如关联资源、标记状态,给管理者充足的信息来帮助确认是否应该给与申请通过

当然对于一些测试之类非核心域名也支持跳过审批自动处理,或者是根据具体的动作来选择跳过审批过程,例如默认新建域名无需审批,这个要根据具体的域名来配置了,充分考虑安全性和灵活性

前文多次提到标记和盘点,所谓盘点就是对我们所有使用的域名做一次全面的梳理,确定哪些域名记录有在使用,而哪些域名记录已经不用了,这里通过系统判断加人工确认的方式来实现,例如之前说的,如果域名解析没有绑定资源或是域名绑定资源已过期,那多半域名解析已经不在使用了,这时候可以标记域名解析状态为已下线,等待后续确认删除

域名变更管理流程上线后也可以跟域名记录标记关联起来做自动标记,用户新申请域名解析申请通过创建成功后,则直接标记域名记录为域名解析的申请用户即可,这样域名盘点工作量将大大降低

巡检

域名全量数据有了,能做饭便捷的查询使用,同时也做到了从申请到删除的全生命周期管理,除此之外关于域名还有一部分工作就是日常的巡检,巡检包含域名临期提醒、域名解析绑定资源下线提醒、域名证书扫描及临期提醒等

域名临期提醒,根据域名的到期时间提前提醒域名管理员续费,保证域名可用

域名解析绑定资源下线提醒,域名绑定资源下线大概率域名解析也要随之下线的,文章开头的案例就是因为域名解析未及时下线引发的问题,在域名绑定资源下线时及时提醒域名解析所有者暂停或删除域名解析能有效防止此类问题的发生

域名证书扫描及临期提醒,域名解析数据有了,我们就可以自动扫描域名解析是否有开启HTTPS,以及所使用的证书和证书到期时间等,无需任何配置即可对相关数据进行自动扫描,遇到问题及时提醒,最大程度保证域名及证书安全

基础数据完善之后,自动化的巡检能帮我们发现问题及风险,我们可以及时处理和修复。域名系统虽无复杂的功能,但却是非常的实用,切实的帮助我们解决问题,堵住风险,谁能说这不是有一个好的系统呢

在上篇文章
别再用 float 布局了,flex 才是未来!
中,我们聊到 Flex 布局才是目前主流的布局方式。在文章最后,我们还贴了一个案例,并且还浅浅地讲解了一下。

有些小伙伴说,这讲解得太粗了,要是能够再深入讲解一下,顺便把代码分享分享就好了。那么,今天我们就继续来扒一扒这个项目的布局实现。

大体框架实现

这个项目是我在 CodePen 上找到的一个项目,地址是:
Glassmorphism Creative Cloud App Redesign
,其页面如下图所示。

-w1327

从上图可以看得出来,其布局还是非常清晰明了的。其最外层包括一个顶部的导航栏和一个下面的内容区域,用 html 描述大致是下面的代码。

<div class="app">
    <div class="header"</div> 
    <div class="wrapper">/div>
</div>

如上面代码所示的布局,我们用 Flex 布局来写,大致就是如下代码所示。

.app {
  display: flex;
  flex-direction: column;
  background-color: var(--theme-bg-color);
  max-width: 1250px;
  max-height: 860px;
  width: 100%;
  height: 90vh;
  overflow: hidden;
  position: relative;
  font-size: 15px;
  font-weight: 500;
  border-radius: 14px;
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
}

.header {
  display: flex;
  height: 58px;
  padding: 0 30px;
  background-color: black;
}

.wrapper {
  display: flex;
  flex: 1;
  background-color: red;
  overflow: hidden;
}

加了上面的样式代码之后,整体的效果就变成了如下图的样式。

-w6666666

此时去拖动窗口大小,会发现红色背景的内容部分是会自动改变高度的。

接下来,我们继续分析剩下的内容。

对于导航栏而言,我们也可以把它看成是一个 Flex 容器,其内部划分为 4 个元素。我加上这部分的代码内容之后,整体的 html 代码如下所示。

<div class="app">
  <div class="header">
    <div class="menu-circle"></div>
    <div class="header-menu"></div>
    <div class="search-bar"></div>
    <div class="header-profile"></div>
  </div>
  <div class="wrapper">
    
    </div>
  </div>
</div>

此时,我们再加上导航栏这部分的 CSS 样式,如下代码所示。


.menu-circle {
  flex-shrink: 0;
  width: 100px;
  margin-right: 50px;
  background-color: gray;
}

.header-menu {
  flex-shrink: 0;
  width: 400px;
  margin-right: 50px;
  background-color: gray;
}

.search-bar {
  flex-shrink: 0;
  width: 200px;
  margin-right: 50px;
  background-color: gray;
}

.header-profile {
  flex-shrink: 0;
  width: 100px;
  margin-right: 50px;
  background-color: gray;
}

加上之后,其效果图如下图所示。

-w6666667

到这里,我想你应该会发现,使用 Flex 布局其实就是一层层把内容划分,然后设置好合适的 flex 属性,布局变得非常简单了。这里我就不继续讲解其他区域的布局代码了,我直接将我最终完成的一个布局草稿图给出来,如下图所示。

-w1093

我们在实际要做的时候,就是这样一点点去将需要弄的区域做出来,接下来就是填上所需要的内容,包括文字、图标、颜色等等信息。上面我自己练习布局的 HTML 和 CSS 代码都在 CodePen 上,有兴趣的可以参考下:
CodePen Home Flex 布局项目实战

纸上得来终觉浅,虽然 Flex 布局简单,但也有非常多的实现细节需要去琢磨。这里我就不事无巨细地讲解所有的样式实现了,我将摘取 5 个比较常见的样式实现来一步步讲解如何实现它们。这 5 个例子的代码都放在了 CodePen 上,感兴趣的可以自己看看:
CSS最佳实践 - CodePen

细节实现

扁平化按钮

首先,我们将上面的例子整理一下,作为我们的第一个 CSS 最佳实践。

要实现如下图所示的扁平化按钮,应该怎么写呢?

-w163

实现思路

使用 padding 属性控制按钮文字与边框的距离。

实现步骤

1、首先,使用 button 元素来作为按钮的 html 元素。
2、接着,使用 padding 属性来控制按钮文字与上下左右的距离。
3、最后,设置按钮文字、背景颜色、背景圆角、边框、鼠标手势属性。

整体实现代码:

<div>
  <h1>1. 扁平化图标的实现</h1>
  <div>
    <button class="content-btn">Start free trial</button>
  </div>
</div>
.content-btn {
    padding: 8px 26px;
    border: none;
    border-radius: 20px;
    color: #fff;
    background-color: #3a6df0;
    cursor: pointer;
}

带图标的菜单

一个菜单,左边有一个图标,如下图所示,如何实现?

-w164

核心思路

使用 flex 布局设置菜单项。将图标与文字放在同一个层级,使用 flex 布局对齐图标和文字,设置 svg 图标的大小。

实现步骤

首先,使用 a 属性表示一个菜单,外层包一个 div 容器,如下代码所示。

<div class="side-menu">
  <a href=""></a>
  <a href=""></a>
</div>

接着,每一个 a 元素表示一个菜单。在菜单里面,图标与菜单文字在同一层,如下代码所示。

<div class="side-menu">
  <a href="">
    <svg></svg>带图标的菜单1
  </a>
  <a href="">
    <svg></svg>带图标的菜单2
  </a>
</div>

接着,构造好 html 层次之后,可以构思 CSS 布局。菜单项(.side-menu)所在容器使用 flex 布局。

.side-menu {
  display: flex;
  flex-direction: column;
  white-space: no-wrap;
}

最后,单个菜单(.side-menu a)内部则也使用 flex 布局,同时设置垂直居中对齐,让图标和文字对齐。此外,还为图标设置大小、悬浮显示背景颜色等。相关 CSS 代码如下所示。

.side-menu a {
    display: flex;
    align-items: center;
    font-weight: 400;
    font-size: 14px;
    text-decoration: none;
    padding: 10px;
    color: #000;
    /* 设置宽度 */
    width: 150px;
}

.side-menu a:hover {
  background-color: rgba(12 15 25 / 30%);
  border-radius: 6px;
}

.side-menu svg {
  width: 16px;
  margin-right: 8px;
}

完整代码见:
CSS最佳实践 - 3、 带图标的菜单 - CodePen

图标上的红点提醒

对于许多应用来说,会通过红点或者未读数量来提醒用户,那么如何实现类似于下图的提醒呢?

-w194

核心思路

使用 relative 或 absolute 布局让红点飘到右上角。其他的样式思路包括:使用 border-radius 画一个圆;使用 flex 布局使数字上下左右居中。

实现步骤

首先,在菜单后面加上 span 标签,填入对应的内容,如下代码所示。

带图标的菜单<span class="notification-number updates">3</span>

接着,画出圆圈以及背景颜色,以及字体大小颜色,如下代码所示。

.notification-number {
  width: 16px;
  height:16px;
  background-color: #3a6df0;
  border-radius: 50%;
  font-size:10px;
  color: #fff;
}

接着,使用 flex 布局设置圆圈和字体的上下左右居中对齐。

.notification-number {
  display: flex;
  align-items: center;
  justify-content: center;
}

最后,使用相对布局调整图形图标位置。

.notification-number {
  position: relative;
  top: -6px;
  right: -6px;
}

到这里,图标上的红点提示就完成了。上面这种实现方式是使用 relative 来实现的,完整代码见:
CSS最佳实践 - 4、图标上的红点提醒(relative实现) - CodePen

实际上,我们也可以使用 absolute 对齐的方式来实现,其完整代码见:
CSS最佳实践 - 5、图标上的红点提醒(absolute实现) - CodePen

这两种的区别在于:它们偏移的参考对象不同。对于 relative 而言,其相对于其父级容器偏移。但是 absolute 则是相对于最近的非 static 定位祖先元素的偏移。

搜索表单

如下图所示的搜索框,如何实现?

-w235

核心思路

使用背景图以及位置偏移设置搜索放大镜图标。

实现步骤

首先,整理出 html 元素层级。

<div class="search-bar">
    <input type="text" placeholder="Search">
</div>

接着,设置搜索框大小以及背景,还有文字字体颜色。

.search-bar input {
  height: 40px;
  width: 150px;
  padding: 0 20px 0 40px;
  background-color: #14162b;
  border-radius: 5px;
  border: none;
  font-family: "Poppins", sans-serif;
  font-size: 15px;
  font-weight: 500;
  color: #fff;
}

最后,设置背景图以及位置。

.search-bar input {
  background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 56.966 56.966' fill='%23717790c7'%3e%3cpath d='M55.146 51.887L41.588 37.786A22.926 22.926 0 0046.984 23c0-12.682-10.318-23-23-23s-23 10.318-23 23 10.318 23 23 23c4.761 0 9.298-1.436 13.177-4.162l13.661 14.208c.571.593 1.339.92 2.162.92.779 0 1.518-.297 2.079-.837a3.004 3.004 0 00.083-4.242zM23.984 6c9.374 0 17 7.626 17 17s-7.626 17-17 17-17-7.626-17-17 7.626-17 17-17z'/%3e%3c/svg%3e");
  background-size: 14px;
  background-repeat: no-repeat;
  background-position: 16px 48%;
}

完整代码见:
CSS最佳实践 - 6. 搜索表单 - CodePen

顶部菜单

要实现如下图所示的顶部菜单效果,应该如何实现呢?

20231017211747

核心思路

使用 padding 设置菜单项带下,使用 flex 布局排列菜单项。

实现步骤

首先,写好 html 结构,使用 a 元素来实现,如下代码所示。

<div class="menu">
<a class="is-active" href="#">首页</a>
<a href="#">投资者关系</a>
<a href="#">企业社会责任</a>
<a href="#">加入我们</a>
</div>

接着,设置菜单项的样式,用 padding 撑开并设置字体大小,如下代码所示。

.menu a {
  display: inline-block;
  padding: 20px 30px;
  text-decoration: none;
  color: gray;
}

接着,设置激活状态下的菜单项以及鼠标悬浮下的菜单项效果。

.menu a.is-active,
.menu a:hover {
  color: black;
  border-bottom: 2px solid black;
}

最后,在顶层容器设置 flex 布局,这样每个菜单项之间就不会有间隙。

.menu {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

完整代码见:
CSS最佳实践 - 7. 顶部菜单 - CodePen

关于 Flex 布局实战的分享就到此为止。希望这篇文章也能给你带来收获,让你更好掌握 CSS 布局技能。
如果这篇文章对你有帮助,记得一键三连支持我!

参考资料

APP攻防--反模拟器&反代理&反证书&真机逃逸&XP框架&Frida技术

APP抓包技术

关于APP抓包,使用burpsuite抓模拟器中的数据包,需要将模拟器中的网络设置代理,代理地址为burpsuite监听的地址与端口,要想成功获取https数据包,需要将burpsuite证书导入模拟器中,在Android7.0之后的系统需要将证书导入系统认证,具体操作参考
转载--逍遥模拟器与burpsuite抓包配置问题

反模拟器检测绕过

在对目标进行抓包渗透时,有时候会显示检测到模拟器,目标APP并不能正常运行。
image.png
面对反模拟器时,一般使用以下三种思路进行绕过

  • 使用真机进行测试
  • 配置模拟器参数

例如,配置模拟器中的手机号、运营商、手机型号、内存、硬盘等等,使得模拟器能够绕过目标APP的检测

  • 将APP的apk文件进行逆向,将检测代码进行删除后在进行重打包。这种方式需要看的懂代码

反代理检测绕过

在对一些APP进行抓包的时候,明明网络没有任何问题,但是就是显示网络不可用、网络不可达、数据异常,并且抓不到访问数据包,这时候要考虑APP是不是进行了代理检测。

  • 代理软件绕过
    • 很多软件只检测了模拟器中网络的高级设置是不是使用了高级代理设置,这时候可以尝试使用代理软件进行设置。例如
      Postern
      ,设置代理规则等即可转发流量,具体操作网上查询。
  • 主机逃逸
    • 由于我们的APP安装在模拟器中,相当于放在虚拟机中,APP检测代理也不会检测宿主机上的网络设置。这时候我们可以使用代理工具,比如在proxifier中设置代理规则,将模拟器进程的流量转发至burpsuite监听的端口上,这样就可以绕过代理检测。
  • APP逆向删除检测代理代码,重打包

主机逃逸方法:

  1. 想要设置转发,首先找到模拟器进程。

在proxifier中设置代理服务器,地址为burpsuite所监听的地址和端口
image.png

设置监听规则,将所有运行的进行流量全部拦截,同时运行模拟器。
image.png
查看进程名称,即可找到模拟器进程。
image.png

  1. 重新设置代理规则

将找到疑似模拟器的进程名称写入,动作改为转发至burpsuite,之后便可以绕过代理检测。
image.png

反证书检测绕过

证书检测有两种检测方式,分为单向认证或者双向认证。
绕过思路
分为四种思路:

  1. XP框架绕过单向认证
  2. Frida框架通杀脚本转发WireShark
  3. Frida框架配合脚本转发burpsuite
  4. 将APK进行逆向将证书提取安装至burpsuite

XP框架绕过单向认证

1.使用XP框架中模块绕过证书检测
安装XP模块:
夜深模拟器直接在应用市场安装即可
逍遥模拟器:
参考
模拟器xposed框架安装7.1-64教程

  1. 安装
    JustTrustMe模块

下载地址
JustTrustMe
下载apk之后将apk安装到模拟器即可

  1. 启动模块并重启模拟器

详细步骤可以参考
app渗透之Burp抓取app数据包

Frida框架通杀脚本转发WireShark

  • Hook技术

Hook 技术又叫做钩子函数,在系统没有调用该函数之前,钩子程序就先捕获该消息,钩子函数先得到控制权,这时钩子函数既可以加工处理(改变)该函数的执行行为,还可以强制结束消息的传递。简单来说,就是把系统的程序拉出来变成我们自己执行代码片段。
Frida就是Hook技术的一种框架

  • Frida框架

Firda 是一款易用的跨平Hook工具, Java层到Native层的Hook无所不能,是一种 动态 的插桩工具,可以插入代码到原生App的内存空间中,动态的去监视和修改行为,原生平台包括Win、Mac、Linux、Android、iOS全平台

  • r0capture脚本
    • 仅限安卓平台,测试安卓7、8、9、10、11、12、13 可用 ;
    • 无视所有证书校验或绑定,不用考虑任何证书的事情;
    • 通杀TCP/IP四层模型中的应用层中的全部协议;
    • 通杀协议包括:Http,WebSocket,Ftp,Xmpp,Imap,Smtp,Protobuf等等、以及它们的SSL版本;
    • 通杀所有应用层框架,包括HttpUrlConnection、Okhttp1/3/4、Retrofit/Volley等等;
    • 无视加固,不管是整体壳还是二代壳或VMP,不用考虑加固的事情;

Frida框架安装:

  1. 本地安装Frida
pip install frida
pip install frida-tools
  1. 模拟器中安装Frida服务端

安装之前首先确认本地Frida版本

frida --version
  1. 进入模拟器命令行查看模拟器版本及位数
abd.exe shell
getprop ro.product.cpu.abi

下载与本地Frida版本以及模拟器位数一致的服务端,Frida服务端下载地址链接
Frida

  1. 将下载解压后的Frida服务端放入模拟器中,开启服务端并设置转发
adb.exe push frida-server /data/local
adb.exe shell
cd /data/local/
chmod 777 frida-server
./frida-server
adb.exe forward tcp:27042 tcp:27042
  1. 判断连接是否成功
frida-ps -U
frida-ps -R

执行两条命令可以看到模拟器中的进程及开启转发成功,frida-ps -R看不到结果是因为没有做端口转发.
成功开启Frida服务并转发至宿主机效果如下
image.png
通杀脚本转发流量

  1. 下载通杀脚本

通杀脚本下载地址
r0capture

  1. 确认Frida服务端启动,并成功转发进程至宿主机
  2. 获取要截取目标APP数据包的包名

建议使用
APK资源提取器1.0
获取包名
使用效果图:
image.png

  1. 运行通杀脚本
python r0capture.py -U -f 包名/程序名 -p xxxx.pcap

运行效果或其他使用方法见项目说明
运行脚本之后,模拟器端会自动运行目标APP,下面进行调试即可

  1. 使用wireshark分析数据即可

Frida框架配合脚本转发burpsuite

  1. 设置模拟器网络代理,转发至burpsuite

  2. 下载Frida脚本
    FridaScripts

  3. 确认Frida服务端启动,并成功转发进程至宿主机

  4. 获取要截取目标APP数据包的包名

  5. 运行脚本文件

frida -U -f 包名 -l SSLUnpinning.js

这里项目中很多脚本都有不同功能,绕过双向证书使用SSLUnpinning.js文件。
运行脚本之后,模拟器端会自动运行目标APP,接下来目标APP的数据包即可正常转发至burpsuite。

反编译获取证书

反编译获取证书文件需要深厚的逆向知识以及代码能力,这里不再记录。

在软件开发领域中,源码依赖、接口依赖和服务依赖是三种不同类型的依赖关系,它们有不同的特点和优缺点。下面我会详细解释它们的区别和各自的优缺点

源码依赖(Source Code Dependency)

源码依赖是指一个软件项目依赖于其他软件项目的源代码。这通常发生在开源软件开发中,一个项目使用了另一个项目的源代码来构建自己的功能。依赖的是另外一个项目的源代码,所以关系更加紧密,代码交织在一起。通常用于较小的依赖或者有需要自定义修改的情况。

优点:

    • 可以更灵活地自定义和修改依赖项的代码。
    • 可以避免不必要的依赖库或模块,减小项目的体积。

缺点:

    • 维护和更新依赖的源代码可能会复杂,尤其是在源项目发生变化时。
    • 可能引入依赖项目的不兼容性问题。

接口依赖(Interface Dependency)

接口依赖是指一个软件模块或组件依赖于其他模块或组件的公共接口,而不是直接依赖其内部实现细节。这个接口包含一组函数、方法、类或API的定义,但不包括底层的源代码。

接口依赖通常对应于库文件,以库文件(如共享库、DLL等)的形式提供,供其他模块引用。不涉及直接包含其他项目的源代码。

优点:

    • 降低了耦合度,使得模块之间更容易替换和维护。
    • 可以实现模块的分离开发,不关心具体实现。

缺点:

    • 需要设计和维护稳定的接口,可能需要额外的工作。
    • 在运行时,需要确保接口的实现是可用的。

服务依赖(Service Dependency):

服务依赖是指一个软件系统依赖于外部的服务或API,通常通过网络进行通信。这种依赖形式常见于微服务架构中。

软件模块通过调用服务的API来实现特定功能,而服务通常以远程方式提供。服务依赖关系涉及到服务的网络地址或终端点,通常使用URL、RPC终端点、RESTful API等来描述服务的位置和通信方式。

优点:

    • 可以实现高度解耦,各个服务之间独立开发、部署和扩展。
    • 允许不同技术栈的服务相互协作。

缺点:

    • 需要处理网络通信的问题,如延迟、可用性和安全性。
    • 可能引入更多的复杂性和运维负担。

文章小结

总之,选择源码依赖、接口依赖或服务依赖取决于项目的需求和复杂性。通常,接口依赖和服务依赖更适合大型和分布式系统,以支持模块化和可扩展性,而源码依赖更适合小型项目或需要定制化的情况。在实际项目中,常常会根据具体情况来综合考虑这些依赖关系的使用。

软件依赖管理-源码依赖、接口依赖、服务依赖

相对定位历史

  • 2021-10-13 发布的 selenium 4.0 开始引入,selenium 3.X是没有的
implement relative locator for find_element (#9902)
  • 4.10维护了下
Improve near relative locator behavior (#11290)

其他都是文档、异常信息方面的处理

实例演示

D:\selenium\demo\relative.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>relative</title>
</head>
<body>
    DATE:<input id="date" type="text">
    USER:<input id="username" type="text"><br>
    CODE:<input id="code" type="text">
    PASS:<input id="password" type="text">
</body>
</html>

如下界面

实例代码

from selenium.webdriver.common.by import By
from selenium.webdriver.support.relative_locator import locate_with


from selenium import webdriver
from time import sleep
driver = webdriver.Chrome()
driver.get(r'D:\selenium\demo\relative.html')
ele_date = driver.find_element('id','date')
ele_code = driver.find_element('id','code')
ele_user = driver.find_element('id','username')
ele_password = driver.find_element('id','password')

driver.find_element(locate_with(By.CSS_SELECTOR, "input").above(ele_code)).send_keys('code aboe')
driver.find_element(locate_with(By.CSS_SELECTOR, "input").below(ele_user)).send_keys('user below')
driver.find_element(locate_with(By.CSS_SELECTOR, "input").to_left_of(ele_password)).send_keys('pass left')
driver.find_element(locate_with(By.CSS_SELECTOR, "input").to_right_of(ele_date)).send_keys('date right')
driver.find_element(locate_with(By.CSS_SELECTOR, "input").near(ele_code)).send_keys('code near')

执行效果

相关源码说明

find_element

在find_element的源码中有这么一段

    def find_element(self, by=By.ID, value=None) -> WebElement:
        if isinstance(by, RelativeBy):
            elements = self.find_elements(by=by, value=value)
            if not elements:
                raise NoSuchElementException(f"Cannot locate relative element with: {by.root}")
            return elements[0]

也就是说你传入的by不仅仅可以是下面这8个,还可以是RelativeBy对象

class By:
    """
    Set of supported locator strategies.
    """

    ID = "id"
    XPATH = "xpath"
    LINK_TEXT = "link text"
    PARTIAL_LINK_TEXT = "partial link text"
    NAME = "name"
    TAG_NAME = "tag name"
    CLASS_NAME = "class name"
    CSS_SELECTOR = "css selector"

那如果是RelativeBy对象的话,会去调用find_elements,
self.find_elements(by=by, value=value)

    def find_elements(self, by=By.ID, value=None) -> List[WebElement]:
    	if isinstance(by, RelativeBy):
            _pkg = '.'.join(__name__.split('.')[:-1])
            raw_function = pkgutil.get_data(_pkg, 'findElements.js').decode('utf8')
            find_element_js = f"return ({raw_function}).apply(null, arguments);"
            return self.execute_script(find_element_js, by.to_dict())

if语句下的2行代码就是在加载
findElements.js

最后两句就是构造一个js然后去执行它,细节就不追究了

RelativeBy

这个class位于
selenium\webdriver\support\relative_locator.py

class RelativeBy:
    def __init__(self, root: Dict[By, str] = None, filters: List = None):
        self.root = root
        self.filters = filters or []
        
    def above(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy":
        if not element_or_locator:
            raise WebDriverException("Element or locator must be given when calling above method")

        self.filters.append({"kind": "above", "args": [element_or_locator]})
        return self

这个类提供了6个实例方法:
above
below
to_left_of
to_right_of
near

可以看到RelativeBy对象的实例化需要2个参数,一个是root:dict类型,一个是filters : 列表类型

可以看到above这样的方法其实没做啥,关键是对self.filters的一个处理,增加一个对应kind(与方法同名)和args,这个args操作你要去参考的元素的定位器或WebElement

locate_with

在实例代码中,我们用到了locate_with这个函数,这个函数跟RelativeBy在同一个文件中

代码如下

def locate_with(by: By, using: str) -> "RelativeBy":
    assert by is not None, "Please pass in a by argument"
    assert using is not None, "Please pass in a using argument"
    return RelativeBy({by: using})

可以看到它确实是返回了一个RelativeBy的实例对象

而它的用法跟我们的find_element就一致了,唯一的不同就是参数名,这边是using,find_element是value

为何用它的另一方面原因是在RelativeBy的doc中这样的一段描述

        Example:
            lowest = driver.find_element(By.ID, "below")

            elements = driver.find_elements(locate_with(By.CSS_SELECTOR, "p").above(lowest))

说在最后

这东西我在工作中没有用过,因为它出生后我就进入了...

使用过一些常见去测试它的效果,并不理想,不过是在早期的版本中做的,现在不清楚是否好用一些

溯源的话应该可以追溯到js中吧,有空找下,或者哪个大佬知道的可以指点下