(appium+python)UI自動化_04_appium元素定位_基礎定位


轉自文章:https://www.cnblogs.com/Mushishi_xu/p/7685966.html

6.1 常用定位方法講解

對象定位是自動化測試中很關鍵的一步,也可以說是最關鍵的一步,畢竟你對象都沒定位那么你想操作也不行。所以本章節的知識我希望大家多動手去操作,不要僅僅只是書本上的知識,畢竟這個我只能夠舉例說明。下面我們來看我們常用的一些定位方式。

6.1.1 ID定位

無論是在web自動化還是app自動化中id都是唯一的,可能有的小伙伴看到這里會有疑問,因為有的資料說是通過name定位是唯一的,為什么你這里是id呢,其實這個在之前是不沖突的,但是如果你用的是appium較新版本是不行的,在新版本中name定位被去掉了,所以在以后的定位中不會有name定位了,通常情況下我們也更喜歡用id進行定位。這里可能剛學的小伙伴會有疑問,有的時候你的應用為什么沒有id,或者說在這個手機上有但是另外的手機上沒有。1、開發沒有添加。2、android版本是4.4以下的。

我們直接看下面這張圖片吧

上面圖片中左邊部分用紅色圈出來的對象的id我們在右邊的屬性中可以看到,他的id我同樣是用紅色圈出,如果我們需要對“手機號/郵箱”這個輸入框進行輸入信息,我們只需操作右邊的id就行,下面我們直接看代碼。

1
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都是一樣的,如果在這種情況下你使用

1
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的學習大家可以思考一下該怎么定位。我們直接看代碼

1
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定位到父節點,然后再從父節點往下面進行定位就好。現在你可以練習一下,看和我的結果一樣嗎?看代碼:

 

1
2
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”節點,同樣的首先我們看一張圖:

這里我們需要直接使用定位復數的方法來操作,直接看代碼:

1
2
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里面的某一個元素我們可以像訪問數組中的數據一樣通過下標訪問。最后的代碼就是下面這個樣子:

1
2
3
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循環語句來依次訪問呢?這個在自動化中會經常用到。下面你可以通過這個思路自己去實戰一下,看能否達到預期效果。下面看我的代碼:

1
2
3
4
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,此時你通過官網或者其他文章的知識通過下面的方法來獲取,可能會報錯,這種情況關系不大。

1
2
webview  =  self .driver.contexts
print  webview

  

如果你通過上面的代碼來調試但是卻報錯,但是其他資料卻沒問題時你也不要着急,這里你需要確定兩件事情:(1)、app打包的時候需要開啟webview 的debug屬性setWebContentDebuggingEnabled(true),這個直接讓開發加上就好。一般情況是開啟的,畢竟他們也要調試。(2)、你用很多手機去調試,發現有一些可以有一些不可以,但是你用模擬器卻都可以,根據官方給出的答案是這個時候你需要去將手機root,然后再試。目前作者遇見了這兩種情況,第二種我也是調試了很久才找到原因。

6.4.3 hybrid定位講解

這兩個問題解決后那么定位webview就輕松搞定,直接看代碼:

1
2
3
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就可以了呢?下面看代碼:

1
2
3
4
5
6
7
8
9
#獲取當前頁面所有的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 滑動定位實戰

一、根據上面的思路我們能首先來確定我們需要查找的元素,看下面圖片:

我們要找實戰推薦后面的“換一換”按鈕,然后進行點擊。首先我們查看他的定位信息

最后我們查找元素的定位信息代碼如下:

1
self .driver.find_element_by_id( 'cn.com.open.mooc:id/tv_replace' ).click()

  對於有一定基礎的人可能會覺得這個很low,但是有沒有思考過一個問題,我們可以通過這個代碼去執行,在沒有這按鈕的時候卻會報錯,也就沒有辦法執行下去了,那么需要怎么處理呢?所以這個時候我們需要有一些python的容錯知識,即使我們的代碼執行出錯了,那么也要讓他按照我們的意思執行下去。try.......except.......,這個就是我們python中的容錯處理 ,下面我們看添加后的代碼:

1
2
3
4
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的值,如果我們需要下往上滑動那么我們就應該是:

 

1
self .driver.swipe(x1,y1,x1,y2,t)

  

上面的代碼x軸的值不變,y軸的值進行了變化,所以是沿着上下進行滑動的,從y2滑動到了y1點。t代表的是多少時間完成這個動作,或者說這個時間持續多久。

備注:這里需要注意的是屏幕的x,y的值是從左上角開始取的,左上角為(0,0),右下角是最大。

三、上面滑動的方法看着是好用,但是我們不可能每次都去填寫一個坐標,那樣太low,所以我們需要獲取屏幕大小,直接看代碼:

1
2
=  self .driver.get_window_size()[ 'width' ]
=  self .driver.get_window_size()[ 'height' ]

  上面的代碼就是我們獲取到的x,y軸。通過思路我們的代碼都有了,下面我們要做的就是對原來的代碼進行修改,進行一個封裝。下面看代碼,這個暫時看不懂沒關系,到后面我們學了python'基礎就能夠看懂了。先思路,然后了解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#獲取屏幕大小
         
def  getSize( self ):
   =  self .driver.get_window_size()[ 'width' ]
   =  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 ):
   =  1
   while  x = = 1 :
     if  self .fact()  = =  1 :
       self .swipeUp( 2000 )
       time.sleep( 3 )
       self .fact()
     else :
       print  "找到了"
       x = 2
             
         
         
#遞歸
def  fact( self ):
   = 1
   try :
     self .driver.find_element_by_id( 'cn.com.open.mooc:id/tv_replace' ).click()
   except  Exception,e:
     return  n

  通過查看上面代碼的整個邏輯就是1、首先去查找元素,如果找到了我就直接點擊。2、如果沒有找到元素那么我就往上滑動(這里可以自己選擇),滑動后再次進行查找,如果找到就點擊,沒有找到繼續滑動。動手動手,這里知識點很重要!雖然后面會有一些替代方法,但是思路、算法很重要。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM