Xpath簡介
Xpath(Xml Path Language),即XML路徑語言,它是一種用來確定XML(標准通用標記語言的子集)文檔中某部分位置的語言。XPath基於XML的樹狀結構,提供在數據結構樹中找尋節點功能
Xpath用途
HTML文檔本身就是一個標准的XML頁面,所以在web測試項目中使用Xpath來定位元素,使用xpath幾乎可以定位到頁面上的任意元素。
Xpath語法
下面是常用的xpath的路徑表達式:
表達式 | 描述 |
/ | 從根節點選取。 |
// | 從匹配選擇的當前節點選擇文檔中的節點,而不考慮它們的位置。 |
@ | 選取屬性。 |
下面通過實際案例來實踐上面幾種表達式的用法:
上圖是某網頁的的登錄頁面,其HTML代碼如下(僅截取部分):
使用絕對路徑定位元素
我們使用絕對路徑定位用戶名輸入框,如下:
driver = webdriver.Firefox() driver.get('https://192.168.45.163/Terminal/logon.do') user=driver.find_element(By.XPATH, '/html/body/div[2]/div/div/div[1]/table/tbody/tr[2]/td[3]/input')
絕對路徑的表達式/開頭,XPath 的絕對路徑主要用標簽名的層級關系來定位元素的絕對路徑。最外層為html 語言,body 文本內,一級一級往下查找,如果一個層級下有多個相同的標簽名,那么就按上下順序確定是第幾個,div[2]表示第二個div 標簽。
使用相對路徑定位元素
下面示例使用相對路徑定位登錄界面的所有輸入框,說明下:該登錄框總計有5個input標簽元素,代碼如下:
inputs=driver.find_elements(By.XPATH, '//input') print len(inputs) for input in inputs: print input.tag_name input.send_keys("s")
說明:代碼目的是找到登錄頁面中的一組input標簽——>打印出總數——>循環打印出所有的input標簽的tag_name,並給每個輸入框賦值:s。
這里要注意以下幾點
1. 一般不推薦使用絕對路徑的寫法,因為一旦頁面結構發生變化,該路徑也隨之失效,必須重新寫。
2. 絕對路徑以單/號表示,而相對路徑則以//表示:
- 當xpath的路徑以/開頭時,表示讓Xpath解析引擎從文檔的根節點開始解析
- 當xpath路徑以//開頭時,則表示讓xpath引擎從文檔的任意符合的元素節點開始進行解析。
- 當/出現在xpath路徑中時,則表示尋找父節點的直接子節點
- 當//出現在xpath路徑中時,表示尋找父節點下任意符合條件的子節點,不管嵌套了多少層級
下面通過示例驗證2中的說明:
input=driver.find_elements(By.XPATH,'//tr//input') print len(input)
代碼輸出結果為:5
input=driver.find_elements(By.XPATH,'//tr/input') print len(input)
代碼輸出結果為:0
因為input標簽並非tr標簽的直接子節點,所以在路徑中使用/,定位的結果為0;而使用//則表示不限制為直接子節點,所以結果為5
利用元素屬性定位
下面通過示例說明利用屬性定位:
Id屬性定位
user=driver.find_element(By.XPATH, "//*[@id='inputUserName']") user.send_keys("guest")
如果不想指定標簽名也可以用星號(*)代替,上述方法表示通過id定位元素,該方法其實等同於id定位
user=driver.find_element(By.XPATH, "//input[@id='inputUserName']") user.send_keys("guest")
//表示當前頁面某個目錄下,input 表示定位元素的標簽名,[@id='inputUserName'] 表示這個元素的id 屬性值等於'inputUserName',同樣也可以使用其他元素的屬性值進行元素定位,下面示例通過type 和class 屬性值來定位:
Type屬性定位
pwd=driver.find_element(By.XPATH, "//input[@type='password']") pwd.send_keys("guest")
通過密碼輸入框的type屬性值來定位
Class屬性定位
code=driver.find_element(By.XPATH, "//input[@class='codeInput']") code.send_keys("guest")
通過驗證碼輸入框的class屬性來定位
name屬性定位
name=driver.find_element(By.XPATH, "//input[@name='userName']") name.send_keys("guest")
通過用戶名輸入框的name屬性來定位
value屬性定位
val=driver.find_element(By.XPATH, "//input[@value='資源下載']") val.click()
通過資源下載按鈕的vlaue屬性看來定位
層級與屬性結合定位
如果一個元素本身並沒有可以唯一標識這個元素的屬性值,我們可以找其上一級元素,如果它的上級有可以唯一標識屬性的值,也可以拿來使用,如下:通過定位到用戶輸入框的元素的父元素,來定位目標元素。
val=driver.find_element(By.XPATH,//div[@id='loginFormField']/descendant::input[1]") val.send_keys("ha")
注意,上面有用到xpath軸的只是,descendant是指定位到div的input子孫元素,后面的【1】是索引,表示符合條件的元素中的第一個
使用邏輯運算符
下面為w3c School中列出的可用在 XPath 表達式中的運算符:
運算符 |
描述 |
實例 |
返回值 |
| |
計算兩個節點集 |
//book | //cd |
返回所有擁有 book 和 cd 元素的節點集 |
+ |
加法 |
6 + 4 |
10 |
- |
減法 |
6-4 |
2 |
* |
乘法 |
6 * 4 |
24 |
div |
除法 |
8 div 4 |
2 |
= |
等於 |
price=9.80 |
如果 price 是 9.80,則返回 true |
如果 price 是 9.90,則返回 false |
|||
!= |
不等於 |
price!=9.80 |
如果 price 是 9.90,則返回 true |
如果 price 是 9.80,則返回 false |
|||
< |
小於 |
price<9.80 |
如果 price 是 9.00,則返回 true |
如果 price 是 9.90,則返回 false |
|||
<= |
小於或等於 |
price<=9.80 |
如果 price 是 9.00,則返回 true |
如果 price 是 9.90,則返回 false |
|||
> |
大於 |
price>9.80 |
如果 price 是 9.90,則返回 true |
如果 price 是 9.80,則返回 false |
|||
>= |
大於或等於 |
price>=9.80 |
如果 price 是 9.90,則返回 true |
如果 price 是 9.70,則返回 false |
|||
or |
或 |
price=9.80 or price=9.70 |
如果 price 是 9.80,則返回 true |
如果 price 是 9.50,則返回 false |
|||
and |
與 |
price>9.00 and price<9.90 |
如果 price 是 9.80,則返回 true |
如果 price 是 8.50,則返回 false |
|||
mod |
計算除法的余數 |
5 mod 2 |
1 |
具體示例如下:
find_element_by_xpath("//input[@id='kw' and @class='su']/span/input")
結合函數與屬性值匹配元素
W3CSchool中關於xpath介紹了很多函數的使用,具體見:http://www.w3school.com.cn/xpath/xpath_functions.asp
下面介紹三個函數的使用,函數具體功能如下:
函數 | 描述 |
fn:contains(string1,string2) | 如果 string1 包含 string2,則返回 true,否則返回false。 例子:contains('XML','XM') 結果:true。 |
fn:starts-with(string1,string2) | 如果 string1 以 string2 開始,則返回 true,否則返回false 例子:starts-with('XML','X') 結果:true |
fn:ends-with(string1,string2) | 如果 string1 以 string2 結尾,則返回 true,否則返回 false 例子:ends-with('XML','X') 結果:false |
下面通過示例實踐這三個函數在實際項目中的應用,下面定位登錄窗口的用戶名輸入框,使用三個函數分別如下:
val=driver.find_element(By.XPATH, "//div[contains(@id,'loginForm')]//input[1]") val.send_keys("contains") val=driver.find_element(By.XPATH, "//div[starts-with(@id,'login')]//input[1]") val.send_keys("starts-with") val=driver.find_element(By.XPATH, "//div[ends-with(@id,'Field')]//input[1]") val.send_keys("ends-with")
使用xpath軸來定位元素
Xpath軸
XPath軸(XPath Axes)可定義某個相對於當前節點的節點集
下表描述了XPath數據模型中的各個節點與上下文節點(context node)的關系(參考:http://www.cnblogs.com/gakusei/articles/1582152.html)
軸名稱 | 結果 |
ancestor | 選取當前節點的所有先輩(父、祖父等) |
ancestor-or-self | 選取當前節點的所有先輩(父、祖父等)以及當前節點本身 |
attribute | 選取當前節點的所有屬性 |
child | 選取當前節點的所有子元素 |
descendant | 選取當前節點的所有后代元素(子、孫等) |
descendant-or-self | 選取當前節點的所有后代元素(子、孫等)以及當前節點本身 |
following | 選取文檔中當前節點的結束標簽之后的所有節點 |
namespace | 選取當前節點的所有命名空間節點 |
parent | 選取當前節點的父節點 |
preceding | 選取文檔中當前節點的開始標簽之前的所有節點 |
preceding-sibling | 選取當前節點之前的所有同級節點 |
self | 選取當前節點 |
Child相當於默認節點,可以不寫,如上面的相對路徑中的xpath表達式:/html/body/div其實完整寫法應該是:/html/child::body/div
Xpath步(step)
上面描述了節點與節點之間的關系,在xpath中節點是通過沿着路徑 (path) 或者步 (steps) 來選取的,下面看看步(step)的內容:
步(step)包括:
- 軸(axis):定義所選節點與當前節點之間的樹關系
- 節點測試(node-test):識別某個軸內部的節點
- 謂語(predicate):更深入地提煉所選的節點集 用[]包括起來(零個或多個)
- 步的語法:軸名稱::節點測試[謂語]
實例參考下表
例子 | 結果 |
child::book | 選取所有屬於當前節點的子元素的 book 節點 |
attribute::lang | 選取當前節點的 lang 屬性 |
child::* | 選取當前節點的所有子元素 |
attribute::* | 選取當前節點的所有屬性 |
child::text() | 選取當前節點的所有文本子節點 |
child::node() | 選取當前節點的所有子節點 |
descendant::book | 選取當前節點的所有 book 后代 |
ancestor::book | 選擇當前節點的所有 book 先輩 |
ancestor-or-self::book | 選取當前節點的所有book先輩以及當前節點(假如此節點是book節點的話) |
child::*/child::price | 選取當前節點的所有 price 孫子--節點。 |
Xpath定位案例
下面同樣使用cccm的登錄框作為示范,使用前面xpath軸及步來定位輸入框部分內容
案例一:選取用戶名輸入框后的所有input節點,下面代碼返回的結果則是下圖中紅色部分的四個元素
users=driver.find_elements(By.XPATH, //*[@id='inputUserName']/following::input" ) for user in users: user.send_keys("xpathz")
案例二:當前節點為一個div,選取其所有子節點,而不關心節點是否是其直接子節點
users1=driver.find_elements(By.XPATH,//div[@id='loginFormField']//child::*") print len(users1)
該案例其實也可以理解為是當前節點的子孫節點,注意細節點,上述表達式child前使用了//而不是/,上述黃色背景部分的執行結果等同與下列代碼執行結果:
users1=driver.find_elements(By.XPATH,"//div[@id='loginFormField']/descendant::*"
下面通過案例來驗證/與//區別(該部分在前面相對路徑定位的章節中有介紹及案例驗證)此處使用xpath軸部分的案例再驗證下,如下圖:
圖一是//應用,所以有45個子元素,而圖二/,所以有1個子元素,並且值是圖一的直接子元素table。
案例三、屬性字段的應用,如下,通過定位到div后再定位到其子孫節點中的input元素,且name屬性為指定值
namez=driver.find_element(By.XPATH,//div[@id='loginFormField']//child::input[@name='userName']") namez.send_keys("zhou[x]")
參考資料
XPath Tutorial http://www.w3schools.com/xpath/default.asp
擴展資料
W3C XPath Recommendation: http://www.w3.org/TR/xpath/
XPath Tutorial: http://www.zvon.org/xxl/XPathTutorial/General/examples.html
DOM:http://www.w3school.com.cn/htmldom/dom_nodes.asp
Firefox插件,可以幫助你獲取頁面元素的XPath:XPath Checker、Firebug
Xpath相關文檔:http://www.cnblogs.com/gakusei/articles/1582152.html
文檔中有如下圖可以直觀的確認: