XPath 選擇器

上篇講解 Selenium 的入門知識(shí)時(shí),挖了幾個(gè)坑,其中一個(gè)就是本文。

XPath 和 CSS 選擇器在 Web 自動(dòng)化測(cè)試中應(yīng)用非常廣泛,它們可以精準(zhǔn)定位元素。如果精通這兩個(gè)選擇器中的其中一種,就可以很好地進(jìn)行自動(dòng)化測(cè)試腳本的編寫。本文中,我將從 Web 自動(dòng)化測(cè)試的角度來介紹這兩個(gè)選擇器(所以與其無關(guān)的功能我可能就不講了,涉及到各自的術(shù)語的地方也不會(huì)太細(xì),畢竟真正搞懂并不容易)。

因?yàn)槠L(zhǎng),我就把這兩個(gè)選擇器分開說明。即使如此,內(nèi)容還是很復(fù)雜,還請(qǐng)大家耐心閱讀。

簡(jiǎn)介

XPath 是一門在 XML 文檔中查找信息的語言,可用來在 XML 文檔中對(duì)元素和屬性進(jìn)行遍歷。

HTML 可以視為一種不嚴(yán)格的 XML,所以也可以用 XPath 進(jìn)行元素的查詢。

了解元素

在了解這兩個(gè)選擇器之前,我們應(yīng)該了解 HTML 標(biāo)簽中元素之間的關(guān)系。

元素、屬性和值

我們先從一段簡(jiǎn)單的 HTML 標(biāo)簽開始:

<div class="ding-class ding-class1 ding-class2" id="ding-id">text</div>

上面一段中,整個(gè) div 標(biāo)簽是一個(gè)元素,div 是標(biāo)簽名。

該元素的屬性有兩個(gè):

  • 屬性名為 class,值為 ding-class ding-class1 ding-class2
  • 屬性名為 id,值為 ding-id

該元素的文本為 text。

元素間的關(guān)系

元素間的關(guān)系,主要有自身(self)、先輩(ancestor)/后代(descendant)、父(parent)子(child)、同胞(sibling)、前(preceding)后(following)。

這些關(guān)系從字面上應(yīng)該很好理解。我這邊主要說明一下先輩/后代與父子關(guān)系的區(qū)別:父子關(guān)系只包括直接從屬的關(guān)系;而先輩/后代關(guān)系不僅包括直接從屬,而且還包含間接從屬的關(guān)系。

我們看一個(gè)較為完整的簡(jiǎn)單的 HTML 代碼(之后的講解絕大多數(shù)都以這個(gè)為例):

<!DOCTYPE html>
<html id="an_4"><head>...</head><body id="an_3">
    <div id="an_2">
        <ul id="an_1">1
            <li id="si_1">1-1</li>
            <li id="si_2">1-2</li>
            <li id="test">1-3
                <ul id="de_1">1-3-1
                    <li id="de_2">1-3-1-1</li>
                    <li id="de_3">1-3-1-2</li>
                </ul>
                <ul id="de_4">1-3-2
                    <li id="de_5">1-3-2-1</li>
                    <li id="de_6">1-3-2-2</li>
                </ul>
                <ul id="de_7">1-3-3
                    <li id="de_8">1-3-3-1</li>
                </ul>
            </li>
            <li id="si_3">1-4</li>
            <li id="si_4">1-5</li>
        </ul>
    </div>
    <div>2</div>
    <div>3</div>
</body></html>

對(duì)于 idtestli 元素而言:

以該元素為例

它的父元素為 idan_1ul 元素;

它的先輩元素,除了父元素外,還有:

  • idan_2div 元素
  • idan_3body 元素
  • idan_4html 元素
父元素與先輩元素

它的子元素有:

  • idde_1ul 元素
  • idde_4ul 元素
  • idde_7ul 元素

它的后代元素,除了子元素外,還有:

  • idde_2li 元素
  • idde_3li 元素
  • idde_5li 元素
  • idde_6li 元素
  • idde_8li 元素
子元素與后代元素

它的同胞元素中,

  • 在它之前的同胞元素有:
    • idsi_1li 元素
    • idsi_2li 元素
  • 在它之后的同胞元素有:
    • idsi_3li 元素
    • idsi_4li 元素
同胞元素

基本選取元素方式

絕對(duì)路徑與相對(duì)路徑

首先,XPath 有絕對(duì)路徑和相對(duì)路徑之分。如果路徑以 / 開頭,則為絕對(duì)路徑,否則為相對(duì)路徑。

絕對(duì)路徑是相對(duì)于整個(gè) HTML 代碼而言的。

相對(duì)路徑只能在特定情況下使用,如在 Selenium 中,對(duì)于 WebElement 實(shí)例使用:

a = driver.find_element(By.XPATH, '//div[@id="content_left"]')
b = a.find_elements(By.XPATH, 'div[not(@tpl="recommend_list")]//h3')

上面的代碼中,a 使用的是絕對(duì)路徑,b 使用的是相對(duì)路徑。

標(biāo)簽

如果要查詢某個(gè)標(biāo)簽名的元素,直接輸入標(biāo)簽名即可。如 div、p

全體

表示全體的符號(hào)為 *,為選取之前的元素下的所有元素,不管什么標(biāo)簽都可以。

如果使用絕對(duì)路徑查詢?nèi)w:

/*

一般會(huì)選擇 html 元素。

屬性

在標(biāo)簽后加上方括號(hào) [],方括號(hào)內(nèi)寫這個(gè)元素的屬性,表示選取符合該條件的元素。

下標(biāo)

在屬性中寫上數(shù)字,表示選取第幾個(gè)元素。

注意:XPath 中的下標(biāo)從 1 開始,與絕大多數(shù)編程語言不一樣!

如:

div[3]

表示選取第三個(gè) div 元素——更確切地說,是路徑下 div 元素中的第三個(gè)。

參數(shù)

在參數(shù)名前加上 @,寫在屬性里,表示選取具有該參數(shù)的元素。如:

div[@id]

表示選取具有 id 屬性的 div 元素。

@參數(shù)名=值 表示選取參數(shù)為某個(gè)值的元素,如:

li[@id="test"]

表示選取 id 屬性為 testli 元素。

文本值用單引號(hào)、雙引號(hào)包裹都可以,只要統(tǒng)一就行,看個(gè)人習(xí)慣。不過,如果你要將其應(yīng)用于編程,最好用你包裹字符串用的引號(hào)不一樣的引號(hào),免去轉(zhuǎn)義的麻煩:

driver.find_element(By.XPATH, '//div[@id="content_left"]')
driver.find_element(By.XPATH, "http://div[@id='content_left']")

子元素與后代元素

要查詢?cè)?a(父元素)下的子元素 b,則可以在父元素后接上 /,再接上子元素:

a/b

這種方法可以一直做下去。

如果要查詢?cè)?a(先輩元素)下的后代元素 b,則接上 //

a//b

// 很多情況下用在一段 XPath 語句的開頭,這樣可以直接定位元素,免去寫元素前面的 /html/body/... 這樣無用的層級(jí),節(jié)約開發(fā)時(shí)間,增加易讀性。比如示例中定位 idtestli 元素,可以直接寫:

//li[@id="test"]

而不用寫成:

/html/body/div[1]/ul/li[@id="test"]

當(dāng)然,前提是能夠定位準(zhǔn)確。

父元素

查詢?cè)氐母冈?,可以?.. 表示。如:

//li[@id="test"]/../li[1]

選取的是示例中 idtestli 元素的父元素的第一個(gè) li 子元素,也就是 idsi_1 的元素。

聰明的你應(yīng)該發(fā)現(xiàn)了,XPath 和文件路徑的表達(dá)方式很像——所以有時(shí)候我們就叫“XPath 路徑”;也能想到, . 表示自己。

運(yùn)算與常用函數(shù)

基本運(yùn)算

XPath 語法中支持加減乘除(+ - * div)、取余(mod)、比較(= != < > <= >=)、邏輯(and or not())運(yùn)算。

注意,XPath 中的除法為 div 代替,取余用 mod,等于只需要寫一個(gè)等號(hào)。邏輯運(yùn)算的“與”和“或”分別用 andor 連接,而“非”使用 not() 函數(shù)表示。

如:

//li[not(@index div @alt < 6)]

表示選取不符合“index 屬性除以 alt 屬性的結(jié)果小于 6”這個(gè)條件的 li 元素。如果一個(gè) li 元素的這兩個(gè)屬性不能進(jìn)行運(yùn)算(如其中一個(gè)為文本),或者是缺少這兩個(gè)屬性中的任意一個(gè),也會(huì)被選取。如果應(yīng)用于下面的 HTML 代碼:

<div>
  <ul>
    <li index="2" alt="1">1</li>
    <li index="12" alt="2">2</li>
    <li index="24" alt="2">3</li>
    <li index="d" alt="e">4</li>
    <li index="2" alt="f">5</li>
    <li index="e" alt="1">6</li>
    <li index="2" alt="1">7</li>
    <li index="e" alt="e">8</li>
    <li index="2">9</li>
    <li index="e">10</li>
    <li alt="1">11</li>
    <li alt="e">12</li>
    <li>13</li>
  </ul>
</div>

則文字為 1、7 的 li 元素不會(huì)被選取,其他的會(huì)被選取。

可以使用圓括號(hào)提高運(yùn)算優(yōu)先級(jí),不過使用情況比較有限,基本上是包裹整個(gè)路徑,或者在屬性內(nèi)使用。

選取第幾個(gè)元素

前面我們說了如何選取第幾個(gè)元素,現(xiàn)在說點(diǎn)高級(jí)的。

選取最后一個(gè)元素,在方括號(hào)中用 last()。如:

//li[@id="test"]/../li[last()]

選取的是示例中 idtestli 元素的父元素的最后一個(gè) li 子元素,也就是 idsi_4 的元素。

如果要選取倒數(shù)第幾個(gè)元素,也可以如法炮制,如:

//li[@id="test"]/../li[last()-1]

選取的是示例中 idtestli 元素的父元素的倒數(shù)第二個(gè) li 子元素,也就是 idsi_3 的元素。

方括號(hào)中還可以使用 position() 函數(shù)表示被選取元素的序號(hào),可與限定條件一起使用。如:

//li[@id="test"]/../li[position()<3]

選取的是示例中 idtestli 元素的父元素的前兩個(gè) li 子元素,也就是 idsi_1、si_2 的元素。

選取多個(gè)路徑

多個(gè)路徑可以通過使用 | 來連接,表示選擇滿足任意一個(gè)路徑的條件的元素。如:

//ul[@id="de_1"] | //li[@id="si_3"]

選取的是示例中 idde_1ul 元素,以及 idsi_3li 元素。

文字

text() 函數(shù)表示元素的文字。該功能在 Web 自動(dòng)化測(cè)試時(shí),較常用于定義屬性。如:

//li[text()="1-3-3-1"]

選取的是文本為 1-3-3-1li 元素,即 idde_8li 元素。

注意:該函數(shù)取的文本不包括后代元素的文本,且如果有換行、空格和縮進(jìn),也會(huì)算進(jìn)去。如果換行和縮進(jìn)之間被其他元素隔開,也會(huì)被分隔為兩個(gè)部分。比如對(duì)示例執(zhí)行:

//li[@id="test"]/text()

實(shí)際上選取了四處:

> $x('//li[@id="test"]/text()')
(4) [text, text, text, text]
> $x('//li[@id="test"]/text()')[0].textContent
"1-3\n                "
> $x('//li[@id="test"]/text()')[1].textContent
"\n                "
> $x('//li[@id="test"]/text()')[2].textContent
"\n                "
> $x('//li[@id="test"]/text()')[3].textContent
"\n            "

因此,如果執(zhí)行下面的語句,選取不到任何內(nèi)容:

/li[@text="1-3"]

包含與連接,以及選取特定 class 的內(nèi)容

contains(a, b) 函數(shù)表示在 a 中包含 b。

在選取帶 class 屬性的值時(shí),會(huì)遇到一個(gè)元素的 class 屬性有多個(gè)的情況:

<div class="ding-class">text</div>
<div class="ding-class bing-class">text</div>
<div class="bing-class ding-class ping-class">text</div>

現(xiàn)在需要選取 class 屬性帶有 ding-classdiv 元素,如果使用

//div[@class="ding-class"]

只會(huì)選取第一個(gè)元素。

如果使用如下的語句,就能夠選取這三個(gè)元素:

//div[contains(@class, "ding-class")]

表示 class 屬性包含 ding-classdiv 元素。

但是,這還不夠,比如這種情況:

<div class="ading-class">text</div>
<div class="ding-class-1 x-class">text</div>

使用上面的語句,也會(huì)把這兩個(gè)元素選中,這不是我們想要的。

我們有一個(gè)取巧的方法,在兩個(gè)查詢文本左右都加上空格。給 ding-class 加上空格簡(jiǎn)單,在字符串兩邊加空格就可以了;但是給 @class 加空格,就要用到 concat() 函數(shù)。

concat(a, b, ...) 函數(shù)表示連接括號(hào)里面的字符串。

對(duì)于上面的需求,只要把代碼寫為:

//div[contains(concat(" ", @class, " "), " ding-class ")]

這樣就能夠精確選取了。

以某字符串開頭

starts-with(a, b) 函數(shù)表示 ab 開頭。由于和包含很像,就不贅述了。

對(duì)于同胞元素、先輩元素等情況,我們可以利用一個(gè)稱之為“軸”的概念。

軸的比較完整的用法如下:

  之前的元素/軸::標(biāo)簽[條件或下標(biāo)]

表示之前的元素的軸中,符合條件或下標(biāo)的標(biāo)簽。

對(duì)于 Web 自動(dòng)化測(cè)試,相關(guān)的軸有以下:

  • parent:父元素(相當(dāng)于..
  • ancestor:先輩元素
  • ancestor-or-self:先輩元素與自身
  • child:子元素
  • descendant:后輩元素(相當(dāng)于 //
  • descendant-or-self:后輩元素與自身
  • preceding:之前的元素
  • following:之后的元素
  • preceding-sibling:之前的同胞元素
  • following-sibling:之后的同胞元素

主要是后面兩個(gè)比較常用。

直接說還是比較難懂,還是看一下示例吧。

還是之前的例子,使用如下的語句:

//li[@id="test"]/preceding-sibling::*

表示選取示例中 idtestli 元素之前的所有同胞元素,也就是 idsi_1si_2li 元素。

不過有一點(diǎn)需要注意,如果涉及到下標(biāo),是根據(jù)該元素的位置,從近到遠(yuǎn)排序的,而不是按照代碼中的順序排序!如:

//li[@id="test"]/preceding-sibling::*[1]

選取的是示例中 idsi_2li 元素,因?yàn)樗x idtestli 元素最近。

書寫選擇器文本的訣竅

善用工具

一般的瀏覽器的開發(fā)人員工具都提供了對(duì)這兩個(gè)選擇器的支持,可以參照之前的文章,在瀏覽器中調(diào)試選擇器文本。

通過開發(fā)人員工具查詢?cè)?/div>

避免使用自動(dòng)生成、層級(jí)過多、多處使用下標(biāo)的嵌套

由于 Web 框架可能會(huì)變動(dòng)層級(jí)和元素,這樣的查詢語句很容易查詢不到想要的元素。建議使用一些標(biāo)志性的特征來定位。如:

//*[@id="post-XPath"]/div[1]/main/div[2]/div/div[2]/div/p
//h3[@id="root-node"]/../div[@class="body"]/p

后者明顯比前者要好。

課件

本文課件地址:https://static.a4ding.com/doc/auto_test/0002_xpath_selector.pdf

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容