Appium python自動化測試系列之元素的定位(六)
?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、如果沒有找到元素那麽我就往上滑動(這裏可以自己選擇),滑動後再次進行查找,如果找到就點擊,沒有找到繼續滑動。動手動手,這裏知識點很重要!雖然後面會有一些替代方法,但是思路、算法很重要。
Appium python自動化測試系列之元素的定位(六)