三、Poco控件的定位方式详解

1. 前言

本文将详细讲解Poco控件定位的各种方式,利用这些方法可以帮助我们编写出目标控件的定位脚本。我们在IDE录制的poco脚本,常见的都是类似 poco("star_single").click()这样的脚本,其中 poco("star_single") 这块就属于Poco控件定位脚本啦。

2. 三种定位选择器

Poco控件最基本的3种定位选择器分别是:

  • 基本选择器
  • 相对选择器
  • 空间顺序选择器

1)基本选择器

在poco实例后加一对括号,我们就可以进行元素选择了。选择器会遍历所有元素,将满足给定条件的元素都选出来并返回。

括号里的参数就是所给定的条件,用属性名值对表示,其中第一个参数表示 节点名 ,就像 poco("star_single") 。后面还可以跟着一些可选参数,均表示 节点的属性及预期的属性值

  1. from poco.drivers.unity3d import UnityPoco
  2. poco = UnityPoco()
  3. poco("star_single",type="Image")

图片

2)相对选择器

如果直接用节点属性(或者说仅仅使用基本选择器)没法选出你所想要的元素时,你还可以通过元素之间的渲染层级关系进行选择,例如父子关系、兄弟关系、祖先后代关系等等:

  1. poco("plays").child("playBasic").offspring("star_single")

图片

更多关于父子关系、兄弟关系的方法API可以到这个页面上查看:https://poco.readthedocs.io/zh\_CN/latest/source/poco.proxy.html?highlight=offspring#poco.proxy.UIObjectProxy.offspring

3)空间顺序选择器

按照序号(顺序)进行选择总是按照空间排布顺序,先从左往右,再像之前那样一行一行从上到下。如下图所示,我们利用选择器选中了很多个 type="Text" 的元素,然后再利用索引顺序逐个选中单个元素:

  1. name0 = poco("Content").child(type="Text")[0].get_name()
  2. name1 = poco("Content").child(type="Text")[1].get_name()
  3. name2 = poco("Content").child(type="Text")[2].get_name()
  4. print(name0+" "+name1+" "+name2)

图片

索引选择有个特例,一旦进行选择后,如果元素的位置发生了变化,那么下标序号仍然是按照选择的那一瞬间所确定的值。即,如果选择时①号元素现在去到了③号的位置,那么还是要用 poco(...)[0] 来访问,而不是 poco(...)[2]

如果选择了之后,某个元素消失了(从界面中移除或者隐藏了),那么如果再访问那个元素则可能会发生异常,其余的元素仍可继续访问。

3. 利用正则表达式匹配控件

其实除了上述3种常规一点的定位方式之外,还有一种同学们比较少见,但是非常好用的定位方式,那就是 用正则表达式来匹配控件 ,如下述示例这样:

  1. # select the UI element(s) which text attribute matches the pattern '^close.*$'
  2. poco = Poco(...)
  3. arb_close_btn = poco(textMatches='^close.*$')

1)1个简单的例子

我们从1个简单的例子来了解下正则表达式匹配poco控件的用法:

图片

这是淘宝APP的icon控件,利用基本选择器,我们可以使用类似 poco(text="手机淘宝") 这样的方式来定位到这个淘宝的icon控件。

换成正则表达式来匹配这个控件的话,我们可以使用如下方式来进行匹配:

  1. poco(textMatches="能匹配到手机淘宝的正则表达式")

能匹配到“手机淘宝”的正则表达式有很多,例如 .*淘宝 ,这个就是能匹配到“手机淘宝”的1个正则表达式,所以点击手机淘宝icon控件的语句我们可以写成如下的形式:

  1. poco(textMatches=".*淘宝").click()

除了使用 textMatches 以外,同理我们还可以使用 nameMatches 来匹配控件的name属性等:

图片

利用基本选择器定位图中控件,我们可以这么编写定位脚本:

  1. poco(name="com.netease.cloudmusic:id/portalTitle",text="每日推荐")

而换成正则表达式的匹配方式,则可以这么实现:

  1. poco(nameMatches=".*portalTitle",textMatches=".*推荐")

2)简洁高效的正则匹配定位

在编写层次特别深的一些定位脚本时,使用正则表达式来匹配,会非常简洁高效:

图片

如上图所示,我们希望获取当前页面所有歌曲的详细介绍信息,如果使用之前介绍的定位方式,脚本可能如下:

  1. for i in poco("com.netease.cloudmusic:id/pagerListview").child("com.netease.cloudmusic:id/musicListItemContainer"):
  2. info = i.child("com.netease.cloudmusic:id/songNameAndInfoArea").offspring("com.netease.cloudmusic:id/songInfo")
  3. print(info.get_text())

图片

可以看到,利用基本选择器和相对选择器写出来的定位脚本,看起来非常繁琐,而且我们还需要非常 精确地了解其中的层级关系 ,否则定位脚本就很容易出错。

那么我们试试换成正则表达式的定位方式:仔细观察UI树发现,这些歌曲信息的控件名都是一样的,所以只要我们写1个正则表达式,匹配到这一批相同的控件名,就相当于定位到了当前页面所有的歌曲信息控件,接下来就可以利用poco遍历,逐一获取控件的text属性了:

  1. for i in poco(nameMatches="com.*?songInfo"):
  2. print(i.get_text())

图片

除了最常见的 textMatchesnameMatchestypeMatches ,其实大部分的属性都可以用这种方式来传递正则表达式,只要能够用 poco(xx=预期属性值) 来选择的控件,就可以用 poco(xxMatches=预期属性值的正则表达式) 来进行匹配定位。

3)检测正则表达式是否匹配的工具

如果同学们需要正则表达式的上手教程,我们推荐大家阅读这篇比较经典的 正则表达式30分钟入门文章https://deerchao.cn/tutorials/regex/regex.htm

另外,当同学们撰写了1个正则表达式,想知道它是否能到匹配到预期目标时,我们还可以简单地在线测试下匹配结果,比如使用这个 检测正则表达式是否匹配的网站https://tool.oschina.net/regex/

图片

4. Poco定位脚本的常见问题

1)区分控件定位脚本与控件操作脚本

很多同学在录制/编写完Poco的定位脚本之后,就直接运行了,结果发现设备画面并没有任何反应,这是因为poco控件的定位脚本仅仅起到筛选控件的作用而已,我们需要在后面加上预期的控件操作,比如点击click(),才会真正对这个控件执行某些操作:

  1. # poco控件定位脚本
  2. poco("star_single")
  3. # poco控件操作脚本
  4. poco("star_single").click()

2)不推荐的Poco定位方式

从上文中我们可以知道,Poco控件的定位方式有很多种,但是我们通常不推荐使用相对选择器和控件顺序选择器进行定位,因为复杂的层级关系加上空间索引顺序,很容易出现运行效率差,或者因为索引值变化而导致出现找不到控件的问题。

除非是必要情况,我们一般不要选择这种定位方式。

3)推荐的Poco定位方式

我们非常推荐多使用基本选择器和正则匹配表达式来定位控件:

举个例子,假设我们想要点击网易云音乐首页的“每日推荐”控件,利用文本属性编写定位脚本的话,将非常简洁,效率也会更高,不用管它复杂的层级关系以及空间索引顺序:

图片

触类旁通,使用1个节点的某个属性值,不仅仅指text属性,还可以是name属性或者其它属性,只要它在当前页面是唯一的,那么我们就可以利用这个属性来定位到我们的控件;甚至 借助节点的多个属性值来进行精准定位控件,都是可以的 。我们非常建议大家多使用这种方式来编写我们的Poco定位脚本。

至于推荐使用正则表达式来匹配控件的原因,上文我们已经提到过了,在部分场景下,使用正则匹配表达式定位控件,脚本将非常高效而且简洁。