6.1 常用定位方法讲解
对象定位是自动化测试中很关键的一步,也可以说是最关键的一步,毕竟你对象都没定位那么你想操作也不行。所以本章节的知识我希望大家多动手去操作,不要仅仅只是书本上的知识,毕竟这个我只能够举例说明。下面我们来看我们常用的一些定位方式。
6.1.1 ID定位
无论是在web自动化还是app自动化中id都是唯一的,可能有的小伙伴看到这里会有疑问,因为有的资料说是通过name定位是唯一的,为什么你这里是id呢,其实这个在之前是不冲突的,但是如果你用的是appium较新版本是不行的,在新版本中name定位被去掉了,所以在以后的定位中不会有name定位了,通常情况下我们也更喜欢用id进行定位。这里可能刚学的小伙伴会有疑问,有的时候你的应用为什么没有id,或者说在这个手机上有但是另外的手机上没有。1、开发没有添加。2、android版本是4.4以下的。
我们直接看下面这张图片吧
上面图片中左边部分用红色圈出来的对象的id我们在右边的属性中可以看到,他的id我同样是用红色圈出,如果我们需要对“手机号/邮箱”这个输入框进行输入信息,我们只需操作右边的id就行,下面我们直接看代码。
driver.find_element_by_id("cn.com.open.mooc:id/account_edit").send_keys("111111")
通过上面的代码我们能够直接在用户信息输入框中输入用户信息111111。可能对于无基础的人来说这里会又点儿迷糊,这个driver是哪里来的,driver在我们配置启动的时就已经初始化,我们只需要调用他的方法find_element_by_id。如果你的ide有自动补全功能,那么你在输入后面的方法时会发现一个问题,为什么还有一个find_elements_by_id呢?这个在后面我们会讲解,下来可以思考一下。
6.1.2 className定位
在实际工作中className定位用得相对而言会比较少。当你经常去看class时你会发现很多的className是一样的,你没有办法对其进行唯一定位,下面我们看下面两张图片
我们可以仔细看一下这两张图片中手机号、密码两个输入框中的className都是一样的,如果在这种情况下你使用
driver.find_element_by_class_name("android.widget.EditText").send_keys("111111")
这种方式去定位,你会发现你永远定位不了密码栏,这是为什么呢?因为在设计的时候如果你查找的元素在页面有多个,系统会自动给你选择第一个,所以你永远操作不了后面的,那如何解决这种问题呢?我们看后面讲解。
6.1.3 xpath定位
xpath定位在web自动化中是最常见的,而且也是最有效的,使用xpath定位避免了找不到元素导致报错的问题,但是在app中使用xpath定位是一件很low的事情。为什么这么说呢?因为在作者的经历中只要遇见使用xpath定位元素他的反应就会比较慢,自动化的目的是为了提高效率,但是使用xpath后会降低效率,所以这里说很 low。但很多时候我们不得不去了解,下面我们大概讲解一下。首先我们要熟悉一下web的xpath定位。
讲web的xpath之前大家先装一下fireFox浏览器,再在浏览器中安装fireBug以及FirePath两个插件。如下图:
在自动化或者学习xpath时这两个插件是必不可少的,这里我们直接讲xpath,我们来看下面一张图片理解一下
用红色圈出有虚线的输入框我们看一下xFirePath给我们的定位,在定位的xpath中显示的是“.//*[@id='kw']”,这个是什么意思呢?我们来一步一步讲解。1、//* 选取文档中的所有元素 。2、@id='kw'] 匹配属性为id且值为kw的节点。这里有的小伙伴可能不是很理解,说这里直接使用id进行定位就行。其实也是,但是当没有这个属性的时候呢?我们看下面这张图片
name定位无效的情况下,当你看到这张图片的时候如果你不用xpath怎么定位呢?有一些抓狂的感觉吧。小伙伴可以尝试着自己使用xpath进行定位,可能有一些人发现xpath中定位不是很明白了,为什么呢?.//*[@id='u1']/a[4] 在这个xpath中我们没有像之前那样思路清晰了他多了一些层级关系,这个后面我们会仔细讲。这个xpath中首先第一步1、@id='u1'和之前的一样匹配属性为id值为ul的节点,然后再在他的下面进行定位2、/a[4] 意思就是从根节点下选取第四个a元素。这样一步一步解析是否更加容易理解了呢?下面我们看一下在xpath定位中经常用到的一些语法,下来大家多多练习。
这个是我们经常用到的,而且是最基础的知识,只有这些没有办法完成很多古怪的需求,那么就有更难的,下面我们看下面的列表
上面这些知识都是在http://www.w3school.com.cn/xpath/xpath_examples.asp 里面,大家下来后一定要多看,多练习。
下面我们直接看在app中xpath的使用
在上面两张图片中我们能够清除的看见他们的id、className都是一样的,这样的情况下不用层级定位方式我们只能够采用xpath来进行定位,首先根据前面web的学习大家可以思考一下该怎么定位。我们直接看代码
driver.find_element_by_xpath("//android.widget.TextView[@text='JavaScript']").click()
在xpath里面我们的语法是这样“//android.widget.TextView[@text='JavaScript']”,这个和我们之前web的xpath一样,意思是查找所有节点中节点为android.widget.TextView (这里使用的是className,也可以使用id,系统会依次去找)并且他的text属性值为JavaScript,这样是否更容易理解呢?下来多练习。这样的定位方式不推荐,效率很慢。
6.2 层级定位
6.2.1 什么是层级定位
在前面的章节中我们已经提到了层级定位,只是不知道具体怎么操作而已。在很多的自动化中如果只是靠简单的定位是没有办法完成自动化的,就像刚xpath定位一样,有的元素的id、name、className都是一样的,xpath定位效率低下,这个时候我们大多数都会采用层级定位。
6.2.2 项目中层级定位如何运用
下面我们举一个简单的例子来理解层级定位。
从上面的图片我们可以看出id为cn.com.open.mooc:id/rv_child的节点下面包含了很多的android.widget.FrameLayout
从这张图片我们不难看出,如果我们要定位这个元素我们是没办法去定位的,这种情况我们大多数使用的是层级定位以及xpath,这里我们来看如何使用层级定位。
首先我们可以看出两幅图的结构上的区别,第二幅图元素他是在第一幅图里面的,这里我们称第一幅图id为cn.com.open.mooc:id/rv_child的节点为第二幅图元素的父节点,我们只需要先通过id定位到父节点,然后再从父节点往下面进行定位就好。现在你可以练习一下,看和我的结果一样吗?看代码:
element = driver.find_element_by_id("cn.com.open.mooc:id/rv_child") element.find_element_by_class_name("android.widget.FrameLayout").click
按照思维我们的代码会是上面的结果,但是你去运行会发现不报错,可也不会点击,这个是为什么呢?我们看下面的图片
在父节点下的所有子节点他的className都是“android.widget.FrameLayout”,这种情况下他怎么去点击操作呢?所以在这种情况下会引发一个新的定位问题,就是我们接下来要讲的List定位。
6.3 List定位
List故名思义就是一个列表,在python里面也有list这一个说法,如果你不是很理解什么是list,这里暂且理解为一个数组或者说一个集合。首先一个list是一个集合,那么他的个数也就成了不确定性,所以这里需要用复数,所以在我们定位时我们不能够接着用find_element_by_id等等定位方式了,我们需要用他的复数形式find_elements_by_id,所有的定位方式都一样需要采用复数加s。这里我们接着上面的案例讲,如何使用list定位想定位的元素。首先看一下图片:
我们查看图片可以知道我们能够很轻松的通过id定位到整个父节点,我们接下来需要做的事定位这个父节点下所有的“android.widget.RelativeLayout”节点,同样的首先我们看一张图:
这里我们需要直接使用定位复数的方法来操作,直接看代码:
element = driver.find_element_by_id("cn.com.open.mooc:id/rv_child") elements = element.find_elements_by_class_name("android.widget.RelativeLayout")
通过上面的代码我们直接定位了cn.com.open.mooc:id/rv_child 父节点下的所有android.widget.RelativeLayout子节点,现在我们需要怎么去操作这个子节点了,这里有两种方法:
1、前面我们讲了List你可以理解为一个数组或者一个集合,这里定位的所有子节点最后就成了个list,如果我们要访问这个list里面的某一个元素我们可以像访问数组中的数据一样通过下标访问。最后的代码就是下面这个样子:
element = driver.find_element_by_id("cn.com.open.mooc:id/rv_child") elements = element.find_elements_by_class_name("android.widget.RelativeLayout") elements[1].click();
上面的代码最后的结果是选择了JavaScript这个标签页面,然后点击进入。
备注:如果初学者不理解是如何通过下标访问的,这里说一下下标是从0开始,如果要访问list i中的第一个元素结果就是i[0],这部分知识会在后面python基础中会讲到。
2、如果你要访问List里面的元素,那么我们是否可以通过for循环语句来依次访问呢?这个在自动化中会经常用到。下面你可以通过这个思路自己去实战一下,看能否达到预期效果。下面看我的代码:
element = driver.find_element_by_id("cn.com.open.mooc:id/rv_child") elements = element.find_elements_by_class_name("android.widget.RelativeLayout") for ele in elements: ele.click()
看上面的代码,我们通过循环去访问这个list里面的每一个元素,因为每次循环得到的都是其中一个元素,那么我们只需要在这个元素上加上你想要的操作即可,所以我们这里可以直接点击进去。
如果你动手做到这里会发现一个问题,你进入到第一个标签后没一会儿系统就会报错,为什么呢?你也可以试着去解决这个问题,后面我们会讲解这块儿知识。
6.4 内嵌H5定位
6.4.1 hybrid定位思考
在web自动化中我们会遇见frame的问题,在遇见这些内嵌的标签后我们需要做的就是切换窗口,那么在app自动化测试也有类似的情况就是我们经常看见的内嵌html,在我们原生的app中增加一个由html做成的页面。大家可以思考一下这种情况怎么操作。
6.4.2 hybrid常见定位问题分析
首先我们看一下下面一张图片:
通过右边的结构图我们能够清晰的看见整个页面就是一个webview,无论从什么角度来定位我们都不能够很好的进行,如果这个时候我们需要操作页面的元素就需要通过切换contexts来完成。但是在讲这个知识点之前大家先按照网上的知识来试一下处理这个页面,看能否成功。下面先说大家会遇见的问题:
1、可能你看到有的文章显示我们不需要通过切换contexts就能够完成定位,这样的情况有,但是那种情况作者只在微博登录、qq登录等第三方登录时遇见过,如果不是这样的情况而像上面的情况就没办法通过类似的方法进行完成,所以我希望读者遇见这种情况时自己动手去操作,看什么方式更加适合自己的项目。
2、需要切换contexts那么就需要获取页面的所有contexts,此时你通过官网或者其他文章的知识通过下面的方法来获取,可能会报错,这种情况关系不大。
webview = self.driver.contexts print webview
如果你通过上面的代码来调试但是却报错,但是其他资料却没问题时你也不要着急,这里你需要确定两件事情:(1)、app打包的时候需要开启webview 的debug属性setWebContentDebuggingEnabled(true),这个直接让开发加上就好。一般情况是开启的,毕竟他们也要调试。(2)、你用很多手机去调试,发现有一些可以有一些不可以,但是你用模拟器却都可以,根据官方给出的答案是这个时候你需要去将手机root,然后再试。目前作者遇见了这两种情况,第二种我也是调试了很久才找到原因。
6.4.3 hybrid定位讲解
这两个问题解决后那么定位webview就轻松搞定,直接看代码:
webview = driver.contexts driver.switch_to.context(webview[1]) driver.find_element_by_link_text('PHP').click()
对于初学者对于上面的代码可能不是很理解,下面我们看一下日志:
大家这里不用管我执行代码和之前的区别(多了一个self),我们看下面控制台的输出,输出的是一个list,前面说过list和数组类似,在这个list里面有两个元素“NATIVE_APP”,“WEBVIEW_cn_com_open_mooc”,第一个元素是我们原生的app的contexts,后面的则是我们的webview的context,所以我们需要获取webview的context时只需要通过这个list的下表来进行访问。
我们获取到webview的context后只需要通过driver.switch_to.context()进行切换就好。当切换后我们就可以像定位web一样进行定位。
看下面一张图片我们通过浏览器将h5页面打开:
通过上面的图片我们就能够很轻松的像web一样进行定位,也就可以使用web的一些定位方式。看到这里是不是觉得解决了一个难题呢?动手去吧。
6.4.4 hybrid问题实战
通过前面的学习我相信你已经有了一些实战能力,这里给大家提一个问题,我们获取到的contexts每次一定是两个吗?如果不是两个那么我们上面的脚本是不是就没办法用了呢?可以思考一下这里怎么解决,在看我们下面的思路以及解决方案。
无论在什么页面我们去获取contexts时他无论有几个但是他的类型是不是肯定都是一个list呢?既然是list那么我们是否可以取到里面的每一个值,然后把每一个值进行判断,只要找出我们的webview就可以了呢?下面看代码:
#获取当前页面所有的contexts webview = driver.contexts #在获取到的contexts list里面去挨个循环 for context in webview: #判断循环中单个的context是否是webview,如果是就进行切换,并且跳出循环 if 'WEBVIEW' in context: driver.switch_to.context(context) break driver.find_element_by_link_text('PHP').click()
通过上面的代码我们是否完美的解决了内嵌H5的定位问题呢?动手吧
6.5 滑动定位
6.5.1 滑动定位方式
在app自动化中我们经常会遇见一个问题,我们需要查找的元素不在当前可展示的屏幕,至于在什么地方我们不知道,如果这个时候我们一直使用在当前页面查找,那么系统就会报错,为了解决这个问题我们就需要使用滑动查找。
首先的思路是我们在需要查找对象的页面查找一下该元素,判断该元素是否在当前页面,如果该元素不在该页面那么我们就需要去互动屏幕,到我们的下一屏幕,然后再进行查找,依次类推到找到为止。
6.5.2 滑动定位思路分析
方式我们有了,那么我们就需要知道实现这个功能应该有哪些点。下面跟着我一起来分析一下:
1、需要查找的元素我们是不是需要知道是什么呢?这个需要先确定
2、我们需要找的页面是在我们的当前页面的上方还是下方还是左方还是右方,我们不能确定,那么我们是否需要确定我们需要滑动的方向?
3、元素和方向有了,但是你知道我们每次需要滑动屏幕的多少吗?那么我们是否需要先去获取屏幕的大小,然后针对不同的方向去计算一个滑动的值呢?
万事具备只欠东风,去按照这个思路动手练习一吧。
6.5.3 滑动定位实战
一、根据上面的思路我们能首先来确定我们需要查找的元素,看下面图片:
我们要找实战推荐后面的“换一换”按钮,然后进行点击。首先我们查看他的定位信息
最后我们查找元素的定位信息代码如下:
self.driver.find_element_by_id('cn.com.open.mooc:id/tv_replace').click()
对于有一定基础的人可能会觉得这个很low,但是有没有思考过一个问题,我们可以通过这个代码去执行,在没有这按钮的时候却会报错,也就没有办法执行下去了,那么需要怎么处理呢?所以这个时候我们需要有一些python的容错知识,即使我们的代码执行出错了,那么也要让他按照我们的意思执行下去。try.......except.......,这个就是我们python中的容错处理 ,下面我们看添加后的代码:
try: self.driver.find_element_by_id('cn.com.open.mooc:id/tv_replace').click() except Exception,e: print e
try的意思就是告诉编译器试着去执行他下面这一段代码,如果报错了,那么你就把except里面的错误信息打印出来。
二、有了元素现在我们需要知道的是不是就是该怎么滑动界面了呢?首先我们看一下下面这张图片:
在我们使用app的过程中存在上面几种滑动情况,我们把整个界面看作为一个坐标系(x,y),如果我们需要往上滑动,那么我们是不是就是x轴不动,y轴从下往上动呢?往下就是x轴不动,y轴从上往下呢?同理左右滑动是不是就是应该y轴不动x轴左右滑动呢?可以好好去体会一下,脑海中有个画面。
在appium中滑动我们所需要使用的方法就是swipe函数,至于往哪个方向滑动就是看我们里面的x,y的值,如果我们需要下往上滑动那么我们就应该是:
self.driver.swipe(x1,y1,x1,y2,t)
上面的代码x轴的值不变,y轴的值进行了变化,所以是沿着上下进行滑动的,从y2滑动到了y1点。t代表的是多少时间完成这个动作,或者说这个时间持续多久。
备注:这里需要注意的是屏幕的x,y的值是从左上角开始取的,左上角为(0,0),右下角是最大。
三、上面滑动的方法看着是好用,但是我们不可能每次都去填写一个坐标,那样太low,所以我们需要获取屏幕大小,直接看代码:
x = self.driver.get_window_size()['width'] y = self.driver.get_window_size()['height']
上面的代码就是我们获取到的x,y轴。通过思路我们的代码都有了,下面我们要做的就是对原来的代码进行修改,进行一个封装。下面看代码,这个暂时看不懂没关系,到后面我们学了python'基础就能够看懂了。先思路,然后了解。
#获取屏幕大小 def getSize(self): x = self.driver.get_window_size()['width'] y = self.driver.get_window_size()['height'] return (x,y) #向左滑动 def swipeLeft(self,t): l=self.getSize() x1=int(l[0]*0.9) y1=int(l[1]*0.5) x2=int(l[0]*0.1) self.driver.swipe(x1,y1,x2,y1,t) #向右滑动 def swipeRight(self,t): l=self.getSize() x1=int(l[0]*0.25) y1=int(l[1]*0.5) x2=int(l[0]*0.75) self.driver.swipe(x1,y1,x2,y1,t) #向上滑动 def swipeUp(self,t): l=self.getSize() x1=int(l[0]*0.5) y1=int(l[1]*0.8) y2=int(l[1]*0.4) self.driver.swipe(x1,y1,x1,y2,t) time.sleep(5) #向下滑动 def swipeDown(self,t): l=self.getSize() x1=int(l[0]*0.5) y1=int(l[1]*0.25) y2=int(l[1]*0.75) self.driver.swipe(x1,y1,x1,y2,t) #查找元素,没找到滑动 def findLocal(self): x = 1 while x==1: if self.fact() == 1: self.swipeUp(2000) time.sleep(3) self.fact() else: print "找到了" x=2 #递归 def fact(self): n =1 try: self.driver.find_element_by_id('cn.com.open.mooc:id/tv_replace').click() except Exception,e: return n
通过查看上面代码的整个逻辑就是1、首先去查找元素,如果找到了我就直接点击。2、如果没有找到元素那么我就往上滑动(这里可以自己选择),滑动后再次进行查找,如果找到就点击,没有找到继续滑动。动手动手,这里知识点很重要!虽然后面会有一些替代方法,但是思路、算法很重要。