徹底學會Selenium元素定位

2022-10-24 06:01:06

轉載請註明出處❤️

作者:測試蔡坨坨

原文連結:caituotuo.top/63099961.html


你好,我是測試蔡坨坨。

最近收到不少初學UI自動化測試的小夥伴私信,對於元素的定位還是有些頭疼,總是定位不到元素,以及不知道用哪種定位方式更好。

其實UI自動化測試的本質就是將手工測試的一系列動作轉化成機器自動執行,可以簡單概括為五大步驟:定位元素 - 操作元素 - 模擬頁面動作 - 斷言結果 - 生成報告。所以很多同學在學習時,都是以元素定位作為入門導向,好的開始就是成功的一半。因此,本篇將詳細介紹Selenium八大元素定位方法,以及在自動化測試框架中如何對元素定位方法進行二次封裝,最後會給出一些在定位元素時的經驗總結。

注意:本文出現的程式碼範例均以 Python3.10 + Selenium4.5.0 為準,由於網上大多數教學都是Selenium3,Selenium4相比於Selenium3會有一些新的語法,如果你還不瞭解Selenium4,推薦先閱讀往期文章「Selenium 4 有哪些不一樣?」。

Selenium八大元素定位

所謂八大元素定位方式就是id、name、class_name、tag_name、link_text、partial_link_text、xpath、css_selector。

在介紹定位方式之前先來說一下定位工具,以Chrome瀏覽器為例,使用F12或右鍵檢查進入開發者工具。

ID

通過元素的id屬性定位,一般情況下id在當前頁面中是唯一的。使用id選擇器的前提條件是元素必須要有id屬性。由於id值一般是唯一的,因此當元素存在id屬性值時,優先使用id方式定位元素。

例如:下面的這個input標籤的id屬性值為kw

<input type="text" class="s_ipt" name="wd" id="kw" maxlength="100" autocomplete="off">

語法:

driver.find_element(By.ID, "id屬性值")

舉慄:

# author: 測試蔡坨坨
# datetime: 2022/10/22 19:08
# function: id定位

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
driver.find_element(By.ID, "kw").send_keys("測試蔡坨坨")
driver.find_element(By.ID, "su").click()
time.sleep(3)
driver.quit()

NAME

通過元素的name屬性來定位。name定位方式使用的前提條件是元素必須有name屬性。由於元素的name屬性值可能存在重複,所以必須確定其能夠代表目標元素唯一性後,方可使用。

當頁面內有多個元素的特徵值相同時,定位元素的方法執行時只會預設獲取第一個符合要求的特徵對應的元素。

例如:下面的這個input標籤的name屬性值為wd

<input type="text" class="s_ipt" name="wd" id="kw" maxlength="100" autocomplete="off">

語法:

driver.find_element(By.NAME, "name屬性值")

舉慄:

# author: 測試蔡坨坨
# datetime: 2022/10/22 19:23
# function: name定位

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
driver.find_element(By.NAME, "wd").send_keys("測試蔡坨坨")
driver.find_element(By.ID, "su").click()
time.sleep(3)
driver.quit()

CLASS_NAME

通過元素的class屬性來定位,class屬性一般為多個值。使用class定位方式的前提條件是元素必須要有class屬性。

雖然方法名是class_name,但是我們要找的是class屬性。

例如:下面這個input標籤的class屬性值為but1

<input class="but1" type="text" name="key" placeholder="請輸入你要查詢的關鍵字" value="">

語法:

driver.find_element(By.CLASS_NAME, "class屬性值")

舉慄:

# author: 測試蔡坨坨
# datetime: 2022/10/22 19:31
# function: class定位

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
# 開啟電商網站
driver.get("http://127.0.0.1")
driver.maximize_window()

# 搜尋方塊中輸入 鞋子
driver.find_element(By.CLASS_NAME, "but1").send_keys("鞋子")
# 點選搜尋
driver.find_element(By.CLASS_NAME, "but2").click()

注意:如果class name是一個複合類(存在多個屬性值,每個屬性值以空格隔開),則只能使用其中的任意一個屬性值進行定位,但是不建議這麼做,因為可能會定位到多個元素。

例如:下面這個標籤的class屬性值為bg s_btn btn_h btnhover

<input class="bg s_btn btn_h btnhover" type="text" name="key">

則只能使用複合類的任意一個單詞去定位:

driver.find_element(By.CLASS_NAME,"bg") # 正確示範

driver.find_element(By.CLASS_NAME,"bg s_btn btn_h btnhover") # 錯誤示範 NoSuchElementException

TAG_NAME

通過元素的標籤名稱來定位,例如input標籤、button標籤、a標籤等。

由於存在大量標籤,並且重複性高,因此必須確定其能夠代表目標元素唯一性後,方可使用。如果頁面中存在多個相同標籤,預設返回第一個標籤元素。一般情況下標籤重複性過高,要精確定位,都不會選擇tag_name定位方式。

語法:

driver.find_element(By.TAG_NAME, "標籤名稱")

舉慄:

driver.find_element(By.TAG_NAME, "input")

定位超連結標籤。只能使用精準匹配(即a標籤的全部文字內容),該方法只針對超連結元素(a 標籤),並且需要輸入超連結的全部文字資訊。

例如:下面這個a標籤的全部文字內容為聯絡客服

<a href="http://XXX">聯絡客服</a>

語法:

driver.find_element(By.LINK_TEXT, "a標籤的全部文字內容")

舉慄:

# author: 測試蔡坨坨
# datetime: 2022/10/22 20:27
# function: link_text定位

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("http://127.0.0.1")
driver.maximize_window()

# 點選聯絡客服
driver.find_element(By.LINK_TEXT, "聯絡客服").click()

定位超連結標籤,與LINK_TEXT不同的是它可以使用精準或模糊匹配,也就是a標籤的部分文字內容,如果使用模糊匹配最好使用能代表唯一的關鍵詞,如果有多個元素,預設返回第一個。

例如:下面這個a標籤的全部文字內容為「聯絡客服」,模糊匹配就可以使用a標籤的部分文字內容,比如聯絡、客服、聯、服……

<a href="http://XXX">聯絡客服</a>

語法:

driver.find_element(By.PARTIAL_LINK_TEXT, "a標籤的部分文字內容")

舉慄:

# author: 測試蔡坨坨
# datetime: 2022/10/22 20:34
# function: partial_link_text定位器

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("http://127.0.0.1")
driver.maximize_window()
# 點選聯絡客服
driver.find_element(By.PARTIAL_LINK_TEXT, "聯絡").click()

XPATH

定義

XML Path Language 的簡稱,用於解析XML和HTML。(不僅可以解析XML還可以解析HTML,因為HTML與XML是非常相像的,XML多用於傳輸和儲存資料,側重於資料,HTML多用於顯示資料並關注資料的外觀)

Xpath策略有多種,無論使用哪一種策略,定位的方法都是同一個,不同策略只決定方法的引數的寫法。

Xpath不僅可以用於Selenium,還適用於Appium,是一個萬能的定位方式。

Xpath有一個缺點,就是速度比較慢,比CSS_SELECT要慢很多,因為Xpath是從頭到尾一點一點去遍歷。

絕對路徑

從最外層元素到指定元素之間所有經過元素層級的路徑 ,絕對路徑是以/html根節點開始,使用 / 來分割元素層級的語法,比如:/html/body/div[2]/div/div[2]/div[1]/form/input[1](因為會有多個div標籤,所以用索引的方式定位div[2],且XPath的下標是從1開始的,例如:/bookstore/bool[1]表示選取屬於bookstore子元素的第一個book元素,除了用數位索引外,還可以用last()、position()函數來表達索引,例如:/bookstore/book[last()]表示選取屬於bookstore子元素的最後一個book元素,/bookstore/book[last()-1]表示選取屬於bookstore子元素的倒數第二個book元素,/bookstore/book[position()❤️]表示選取最前面的兩個屬於bookstore元素的子元素的book元素)

由於絕對路徑對頁面結構要求比較嚴格,因此不建議使用絕對路徑。

語法:

driver.find_element(By.XPATH, "/html開頭的絕對路徑")

舉慄:

# author: 測試蔡坨坨
# datetime: 2022/10/23 11:13
# function: xpath絕對路徑

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
# 開啟電商網站
driver.get("http://127.0.0.1")
driver.maximize_window()

# 絕對路徑
# 搜尋方塊輸入 阿迪達斯
# XPath的下標是從1開始的
driver.find_element(By.XPATH, "/html/body/div[2]/div/div[2]/div[1]/form/input[1]").send_keys("阿迪達斯")
# 點選搜尋
driver.find_element(By.XPATH, "/html/body/div[2]/div/div[2]/div[1]/form/input[2]").click()

driver.quit()
相對路徑

匹配任意層級的元素,不限制元素的位置 ,相對路徑是以 // 開始, // 後面跟元素名稱,不知元素名稱時可以使用 * 號代替,在實際應用中推薦使用相對路徑。

語法:

driver.find_element(By.XPATH, "//input")

driver.find_element(By.XPATH, "//*")

舉慄:

# author: 測試蔡坨坨
# datetime: 2022/10/23 12:35
# function: xpath相對路徑

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
# 開啟電商網站
driver.get("http://127.0.0.1")
driver.maximize_window()

# 相對路徑
# XPath相對路徑以 // 開頭
# 搜尋方塊輸入 鞋子
driver.find_element(By.XPATH, "//input[@class='but1']").send_keys("鞋子")
# 點選搜尋按鈕
driver.find_element(By.XPATH, "//*[@class='but2']").click()

使用瀏覽器開發者工具直接複製xpath路徑值(偷懶的方法,不推薦在學習的時候使用):

通過元素屬性定位
單個屬性

使用目標元素的任意一個屬性和屬性值(需保證唯一性)。

注意:

使用 XPath 策略,建議先在瀏覽器開發者工具中根據策略語法,組裝策略值,測試驗證後再放入程式碼中使用。

目標元素的有些屬性和屬性值可能存在多個相同特徵的元素,需注意唯一性。

語法:

driver.find_element(By.XPATH, "//標籤名[@屬性='屬性值']")

driver.find_element(By.XPATH, "//*[@屬性='屬性值']")

比如:下面這個input標籤的placeholder屬性的屬性值為「請輸入你要查詢的關鍵字」

<input class="but1" type="text" name="key" placeholder="請輸入你要查詢的關鍵字">

舉慄:

# author: 測試蔡坨坨
# datetime: 2022/10/23 17:27
# function: 單個屬性

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("http://127.0.0.1")
driver.maximize_window()

# 通過單個屬性匹配
driver.find_element(By.XPATH, "//input[@placeholder='請輸入你要查詢的關鍵字']").send_keys("測試蔡坨坨")
多個屬性

通過多個屬性和屬性值進行匹配,解決單個屬性和屬性值無法定位元素唯一性的問題。

多個屬性可由多個 and 連線,每一個屬性都要以 @ 開頭,可以根據需求使用更多屬性值。

語法:

driver.find_element(By.XPATH, "//標籤名[@屬性1='屬性值1' and @屬性2='屬性值2']")

driver.find_element(By.XPATH, "//*[@屬性1='屬性值1' and @屬性2='屬性值2']")

比如:下面這個input標籤的class屬性的屬性值為"but1",placeholder屬性的屬性值為"請輸入你要查詢的關鍵字"

<input class="but1" type="text" name="key" placeholder="請輸入你要查詢的關鍵字" value="">

舉慄:

# author: 測試蔡坨坨
# datetime: 2022/10/23 17:38
# function: 多個屬性匹配

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("http://127.0.0.1")
driver.maximize_window()

# 通過多個屬性匹配
driver.find_element(By.XPATH, "//input[@class='but1' and @placeholder='請輸入你要查詢的關鍵字']").send_keys("測試蔡坨坨")
通過屬性模糊匹配

通過屬性值的部分內容進行匹配。

語法:

driver.find_element(By.XPATH, "//標籤名[contains(@屬性,'屬性值的部分內容')]")

driver.find_element(By.XPATH, "//*[contains(@屬性,'屬性值的部分內容')]")

比如:下面這個input標籤的placeholder屬性的屬性值為"請輸入你要查詢的關鍵字",模糊匹配就可以是"請輸入"

<input class="but1" type="text" name="key" placeholder="請輸入你要查詢的關鍵字">

舉慄:

# author: 測試蔡坨坨
# datetime: 2022/10/23 17:41
# function: contains模糊匹配

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("http://127.0.0.1")
driver.maximize_window()

# 通過contains模糊匹配屬性值
driver.find_element(By.XPATH, "//input[contains(@placeholder,'請輸入')]").send_keys("測試蔡坨坨")
starts-with屬性值以XX開頭

語法:

driver.find_element(By.XPATH, "//標籤名[starts-with(@屬性,'屬性值的開頭部分')]")

driver.find_element(By.XPATH, "//*[starts-with(@屬性,'屬性值的開頭部分')]")

比如:下面這個input標籤的placeholder屬性的屬性值以"請輸入"開頭

<input class="but1" type="text" name="key" placeholder="請輸入你要查詢的關鍵字">

舉慄:

# author: 測試蔡坨坨
# datetime: 2022/10/23 18:01
# function: starts-with定位屬性值以xxx開頭的元素

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("http://127.0.0.1")
driver.maximize_window()

driver.find_element(By.XPATH, "//input[starts-with(@placeholder,'請輸入')]").send_keys("測試蔡坨坨")
文字值定位

通過標籤的文字值進行定位,定位文字值等於XX的元素,一般適用於p標籤、a標籤。

語法:

driver.find_element(By.XPATH, "//*[text()='文字資訊']")

比如:下面這個a標籤的文字資訊為"免費註冊"

<a href="http://127.0.0.1/register">免費註冊</a>

舉慄:

# author: 測試蔡坨坨
# datetime: 2022/10/23 17:41
# function: text()文字資訊定位

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("http://127.0.0.1")
driver.maximize_window()

driver.find_element(By.XPATH, "//*[text()='免費註冊']").click()

CSS_SELECTOR

通過CSS選擇器語法定位元素。

適用於Selenium和Appium,但是需要注意的是,原生的app控制元件不支援CSS_SELECTOR,只支援Xpath。

Selenium框架官方推薦使用CSS定位,因為CSS定位效率高於XPATH。

CSS是一種標示語言,控制元素的顯示樣式,就必須找到元素,在CSS標示語言中找元素使用CSS選擇器。

CSS的選擇策略也多很多種,但是無論選擇哪一種選擇策略都是用同一種定位方法。

定位方法:

driver.find_element(By.CSS_SELECTOR, "CSS選擇策略")
絕對路徑

以html開始,使用 > 或 空格 分隔,與XPATH一樣,CSS_SELECTOR的下標也是從1開始。

driver.find_element(By.CSS_SELECTOR, "html>body>div>div>div>div>form>input:nth-child(1)").send_keys("測試蔡坨坨") # 使用>分隔

driver.find_element(By.CSS_SELECTOR, "html body div div div div form input:nth-child(1)").send_keys("測試蔡坨坨") # 使用空格分隔

driver.find_element(By.CSS_SELECTOR, "html>body>div>div div div form input:nth-child(1)").send_keys("測試蔡坨坨")  # 使用 空格 + > 分隔
相對路徑

不以html開頭,以CSS選擇器開頭,比如標id選擇器、class選擇器等。

舉慄:

driver.find_element(By.CSS_SELECTOR, "input.but1").send_keys("測試蔡坨坨")
id選擇器

語法:# 開頭表示id選擇器

driver.find_element(By.CSS_SELECTOR, "標籤#id屬性值")

舉慄:

driver.find_element(By.CSS_SELECTOR, "i#cart_num").click()
class選擇器

語法:. 開頭表示class選擇器,或者使用[class='class屬性值']

如果具有多個屬性值的class,則需要傳入全部的屬性值

driver.find_element(By.CSS_SELECTOR, ".class屬性值")

driver.find_element(By.CSS_SELECTOR, "[class='class屬性值']")

舉慄:

driver.find_element(By.CSS_SELECTOR, ".but2").click()

driver.find_element(By.CSS_SELECTOR, "[class='but2']").click()
屬性選擇器
單個屬性

語法:

driver.find_element(By.CSS_SELECTOR, "標籤名[屬性='屬性值']")

driver.find_element(By.CSS_SELECTOR, "[屬性='屬性值']")

舉慄:

driver.find_element(By.CSS_SELECTOR, "input[placeholder='請輸入你要查詢的關鍵字']").send_keys("測試蔡坨坨")

driver.find_element(By.CSS_SELECTOR, "[placeholder='請輸入你要查詢的關鍵字']").send_keys("測試蔡坨坨")
多個屬性

語法:注意與xpath的區別

driver.find_element(By.CSS_SELECTOR, "標籤名[屬性1='屬性值1'][屬性2='屬性值2']")

舉慄:

driver.find_element(By.CSS_SELECTOR, "input[name='key'][class='but1']").send_keys("測試蔡坨坨")
模糊匹配
driver.find_element(By.CSS_SELECTOR, "[屬性^='開頭的字母']") # 獲取指定屬性以指定字母開頭的元素
driver.find_element(By.CSS_SELECTOR, "[屬性$='結束的字母']") # 獲取指定屬性以指定字母結束的元素
driver.find_element(By.CSS_SELECTOR, "[屬性*='包含的字母']") # 獲取指定屬性包含指定字母的元素
標籤選擇器

語法:

driver.find_element(By.CSS_SELECTOR, "標籤名") # 例如:input、button
層級關係

父子層級關係:父層級策略 > 子層級策略 (也可以使用空格連線上下層級)

祖輩後代層級關係:祖輩策略 後代策略

> 與 空格 的區別:大於號必須為子元素,空格則不用

first-child

第一個子元素

<div class="help">
	<a href="http://127.0.0.1">首頁</a> 
	<a href="http://127.0.0.1/buy">我的訂單</a> 
	<a href="http://127.0.0.1//help">聯絡客服</a>
</div>
driver.find_element(By.CSS_SELECTOR, ".help>a:first-child").click() # 首頁
last-child

最後一個子元素

driver.find_element(By.CSS_SELECTOR, ".help>a:last-child").click() # 聯絡客服
nth-last-child()

倒序

driver.find_element(By.CSS_SELECTOR, ".help>a:nth-last-child(2)").click()  # 我的訂單
nth-child()

正序

driver.find_element(By.CSS_SELECTOR, ".help>a:nth-child(3)").click()  # 聯絡客服
乾兒子和親兒子

若一個標籤下有多個同級標籤,雖然這些同級標籤的 tag name 不一樣,但是他們是放在一起排序的。

# author: 測試蔡坨坨
# datetime: 2022/10/23 20:20
# function: css_selector 不區分乾兒子和親兒子

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
driver.maximize_window()

# css_selector 不區分乾兒子和親兒子,
# 若一個標籤下有多個同級標籤,雖然這些同級標籤的tag name不一樣,但是他們是放在一起排序的
# 開啟百度,在搜尋方塊中輸入 測試蔡坨坨 ,點選百度一下
driver.find_element(By.CSS_SELECTOR, "form#form>span:nth-child(8)>input").send_keys("測試蔡坨坨")
driver.find_element(By.CSS_SELECTOR, "form#form>span:nth-child(9)>input").click()

元素定位二次封裝

在之前的文章中我們介紹過UI自動化測試框架,可參考往期文章「五分鐘搞懂 POM 設計模式」。

框架中的base_page模組對Selenium一些常用的API進行二次封裝,其中就有對find_element的封裝。

base_page.py:

class BasePage(object):
    def __init__(self, driver):
        self.logger = GetLogger().get_logger()
        self.driver = driver

    def wait_ele_visible_(self, loc, loc_doc, times=3, poll_frequency=0.5):
        """
        等待元素可見
        :param loc: 元素定位
        :param loc_doc: 元素描述
        :param times: 最長等待時間
        :param poll_frequency: 輪詢頻率,呼叫 until 或 until_not方法中的間隔時間,預設為0.5秒
        :return:
        """
        try:
            @do_time
            def fun():
                WebDriverWait(self.driver, times, poll_frequency).until(EC.visibility_of_element_located(loc))

            self.logger.info("等待【{}】元素【{}】出現,耗時:{}豪秒".format(loc_doc, loc, fun()))
        except Exception:
            self.logger.error("等待【{}】元素【{}】出現失敗".format(loc_doc, loc))
            raise

    def find_element_(self, loc, loc_doc):
        """
        查詢元素
        :param loc: 元素定位 例如:login_icon_loc = (By.ID, "sb_form_q")
        :param loc_doc: 對元素的描述
        :return:
        """
        try:
            self.logger.info("開始查詢【{}】元素【{}】".format(loc_doc, loc))
            self.wait_ele_visible_(loc, loc_doc)
            return self.driver.find_element(*loc)
        except Exception:
            self.logger.error("查詢【{}】元素【{}】失敗".format(loc_doc, loc))
            raise

元素定位總結

  • 首先考慮id定位,id定位是效率最高的

    一般情況下id屬性在當前頁面是唯一的。

    在實際企業專案中,可能需要前端同學的配合,保證元素唯一屬性命名規則。所有可操作元素,例如輸入框、點選按鈕等均需要加id欄位,並且id欄位的命名為元素含義的英文;若當前頁面存在兩個或多個一樣的元素,則第二個開始命名為id=username2,以此類推;多層級元素一般最外層定義即可。

  • 如果沒有id,再選擇xpath,一般使用相對路徑

  • css_selector比xpath更加穩定

    為什麼說css_selector比xpath更穩定?因為我們通過Chrome瀏覽器的開發者工具可以看出藍色線代表DOM出現,紅色線代表圖片等資源已載入完,如果用xpath定位元素,其實是在DOM出現的時候進行查詢,而當你使用css_selector進行元素定位的時候,它會等待圖片資源載入完成後進行查詢,也就是紅線的位置,所以css_selector比xpath更穩定,當你使用xpath定位不到元素時,不妨嘗試使用css_selector。

  • tag_name使用頻率最低

  • 儘量不要用href屬性、純數位的屬性(純數位可能是個動態值)去定位

  • 對於Toast提示框,很快消失的提示框,可以點選 開發者工具-sources中的暫停鍵 後再去定位

  • 新增適當的等待時間,避免等待時間不夠,元素還未載入出來

  • 多視窗時需考慮視窗控制程式碼是否還處在上一個視窗,導致無法定位新視窗的元素,是否需要切換視窗控制程式碼

  • iframe/frame,這是個常見的定位不到元素的原因,frame中實際上是嵌入了另一個頁面,而webdriver每次只能在一個頁面識別,因此需要先定位到相應的frame,再對那個頁面裡的元素進行定位

  • 如果使用xpath或css_selector,請在瀏覽器開發者工具中偵錯測試正確後再寫入程式碼中