前言
UI自動化的學習,個人認爲應該分五步走:環境搭建、元素定位、特殊場景處理、框架設計與搭建、測試平臺開發。第一步的環境搭建其實沒什麼難度,都是固定的套路。今天就來到了第二步的元素定位,可以說元素定位是整個UI自動化的基本功。
我查閱了大量的資料,在動手實踐的基礎上,整理總結了此文。
常用定位方式
衆所周知,Selenium提供了8種定位方式:
- id:根據id定位,是最常用的定位方式,因爲id具有唯一性,定位準確快捷
- name:通過元素的【名稱】屬性定位,name會存在不唯一的情況
- className:class 屬性定義了元素的類名
- tagName:通過標籤命定位,一般不建議使用
- linkText:專用於定位超鏈接元素(即a標籤),需要完全匹配超鏈接的內容
- partialLinkText:同樣用於定位超鏈接元素,但可以模糊匹配超鏈接的內容
- xpath:根據元素路徑進行定位,分爲絕對路徑和相對路徑
- cssSelector:selenium官方推薦的元素定位方式,比xpath效率更高,但需要掌握一些css基礎
下面以百度搜索框爲例,進行定位方式的實踐練習
在Java中,selenium封裝了獲取元素的兩個函數,區別在於前者會獲得一個元素,後者獲取一系列(1個或多個)元素的集合:
// 獲取某個元素
WebElement findElement(By var1);
// 獲取元素的集合
List<WebElement> findElements(By var1);
1 id定位
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class ElementTest {
public static void main(String[] args) {
// 指定瀏覽器驅動的路徑
String driverPath = "E:/source/driver/chromedriver_80_2.exe";
System.setProperty("webdriver.chrome.driver", driverPath);
// 創建一個chrome driver
WebDriver driver = new ChromeDriver();
// 訪問百度
driver.get("https://www.baidu.com");
// id定位元素
try {
driver.findElement(By.id("kw")).sendKeys("測試");
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
} finally {
driver.quit();
}
}
}
2 name定位
根據元素的name標籤定位元素,name屬性的值是可重複的。
元素定位之外的相同代碼省略,下同。
// name定位元素
try {
driver.findElement(By.name("wd")).sendKeys("測試");
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
} finally {
driver.quit();
}
3 className
很多人可能對前端的class這個概念缺乏瞭解,這裏簡單描述一下。class屬性一般是對元素進行樣式描述,它有兩種定義方式:
- 定義在HTML文件的【head】標籤的【style】標籤內
- 定義在專門的css文件中,用【link】標籤對該css文件進行引用
一個元素可以引用多個class,一個class也可以被多個元素引用,見下面示例代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>測試頁面</title>
<link rel="stylesheet" href="../../layui/css/layui.css">
<style>
.colkey {
font-size: 15px;
font-weight: 600;
text-align: left;
width: 75px;
padding: 9px 0 9px 10px;
}
.content {
margin: 0 10px 5px 25px
}
</style>
</head>
<body>
<div class="colkey">測試1</div>
<div class="colkey content">測試2</div>
<div class="content">測試3</div>
</body>
</html>
關於class的知識,感興趣的可以自己去多瞭解前端相關內容。使用className去定位元素,其實並不是非常好的一種定位方式,原因是一個className可能被多個元素所擁有,難以保證元素定位的唯一性。
// className定位元素
try {
driver.findElement(By.className("s_ipt")).sendKeys("測試");
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
} finally {
driver.quit();
}
4 tagName定位
所謂tag就是元素的標籤,比如head、form、div等等,通過tagName去定位元素更加難以保證定位的唯一性。
在定位百度搜索框這個案例中,有兩種思路,具體見代碼:
// tagName定位元素
try {
// 思路1:獲取所有input標籤的元素,再根據索引獲取目標元素
driver.findElements(By.tagName("input")).get(7).sendKeys("測試");
// 思路2:先定位到父級元素,再通過tagName定位目標元素
driver.findElement(By.className("s_ipt_wr")).findElement(By.tagName("input")).sendKeys("測試");
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
} finally {
driver.quit();
}
5 linkText定位
linkText定位專用於超鏈接元素,即a標籤包裹的元素,例如:
<a href="http://www.w3school.com.cn">W3School</a>
上面就是一個典型a標籤元素,href指向目標url,"W3School"是該元素的value,我們可以用該內容定位這個a標籤元素。
下面以百度搜索csdn並跳轉csdn官網爲例,演示linkText定位:
// tagName定位元素
try {
driver.findElement(By.id("kw")).sendKeys("csdn");
driver.findElement(By.id("su")).click();
// 隱式等待頁面加載完成
driver.manage().timeouts().implicitlyWait(20, TimeUnit.SECONDS);
// 精確匹配
driver.findElement(By.linkText("CSDN博客")).click();
// 模糊匹配
driver.findElement(By.partialLinkText("IT技術社區")).click();
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
} finally {
driver.quit();
}
上面的6種定位方式,使用起來很簡單,但缺陷在於很多元素用這些方式無法定位,接下來就是兩種幾乎能定位到所有元素的定位方式:xpath和css selector。這兩種方式內容非常多,又比較深奧,如果有人不想努力了,倒是有偷懶方法,在目標元素上右鍵,Copy selector
(獲取css selector表達式)和Copy XPaht
(獲取xpath表達式):
6 xpath定位
所謂xpath,即根據元素的路徑進行定位。更多xpath的知識請見:w3school
6.1 路徑匹配
xpath定位最常用的就是路徑定位了,具體又分爲絕對路徑和相對路徑。
路徑匹配有以下幾個符號:
- 用
/
表示節點路徑,如/A/B/C
表示節點A的子節點B的子節點C
,/
表示根節點。 - 用
//
表示所有路徑以//
後指定的子路徑結尾的元素,如//D
表示所有的D元素;如果是//C/D
表示所有父節點爲C
的D
元素。 - 用
*
表示路徑的通配符,如/A/B/C/*
表示A
元素下的B
元素下的C
元素下的所有子元素。
6.1.1 絕對路徑
絕對路徑也稱全路徑,是指從根路徑出發,逐層定位,例如:
By.xpath("html/body/div/form/span/input")
以上面的百度搜索框爲例,絕對路徑:
By.xpath("/html/body/div[1]/div[1]/div[5]/div/div/form/span[1]/input")
絕對路徑繁瑣冗長,而且極易受前端結構變動影響,所以強烈不推薦使用。
6.1.2 相對路徑
即相對於上下文節點的路徑,使用雙斜槓,例如:
By.xpath("//input//div")
相對路徑更加實用,一般我們難以直接定位到一個目標元素時,可以先定位到一個能準確定位到的上級元素,再從此上級元素出發,通過元素之間的層級關係定位到目標元素。
例如,定位百度搜索框(當然百度搜索框本身是能定位到的,這裏是爲了演示):
By.xpath("//*[@id='form']/span/input")
當一個元素下有多個同類型元素時,僅憑路徑匹配就行不通了,又因爲對於每一個元素,它的各個子元素都是有序的,所以通過索引就能準確定位到目標元素:
/A/B/C[1]
表示A
元素下的B
元素下的C
元素下的第一個子元素。/A/B/C[last()]
表示A
元素下的B
元素下的C
元素下最後一個子元素。/A/B/C[position()>2]
表示A
元素下的B
元素下的C
元素下的位置號大於2的元素。
例如:
By.xpath("//form[2]")
通過相對路徑定位元素,其核心思想在於,當目標元素不能直接定位時,先找到一個能直接定位到的元素,我稱之爲錨點元素,再通過目標元素與錨點元素之間的位置關係進行定位。
具體又可分爲,通過上下級節點(或父子節點)和同級節點(或兄弟節點)兩種方式。
示例代碼:
<html>
<body>
<div id="parent">
<div id="A"> old brother</div>
<div id="B"> child</div>
<div id="C"> litter brother</div>
</div>
</body>
</html>
以上面代碼爲例:
1、通過父級節點查找子級節點
By.xpath("//div[@id='parent']/div[2]")
2、通過子級節點查找父級節點
By.xpath("//div[@id='B']/..")
3、通過兄弟節點定位
By.xpath("//div[@id='B']/../div[1]")
另外根據兄弟節點的相對位置關係進行定位,其他的常用表達式:
E/following-sibling::F
:獲取和E
元素同級且位於其後的F
元素E/following-sibling::F[n]
:獲取和E
元素同級且位於其後的第n個F
元素preceding-sibling::F
:獲取和E
元素同級且位於其前的F
元素preceding-sibling::F[n]
:獲取和E
元素同級且位於其前的第n個F
元素
下面用一個示例進行演示:
頁面代碼如下,可以發現目標元素所屬的tbody
標籤只有一個動態id,顯然難以直接定位。但在目標元素所在的tbody上面,有一個可以通過id
直接定位到的tbody
,我稱之爲錨點元素。錨點元素和目標元素所在tbody在同級,這時候就很適合用兄弟元素的方式去定位。
具體代碼如下:
By.xpath("//*[@id='separatorline']/following-sibling::tbody[2]/tr/th/a[3]")).click();
6.2 屬性匹配
在xpath中可以使用屬性和屬性的值來定位元素,使用屬性定位時要以@
開頭(下面form
僅爲示例,也可以爲div
、input
等)
//form[@id]
:表示所有具有屬性id
的form
元素。//form[@*]
:表示所有具有屬性的form
元素。//form[not(@*)]
:表示所有不具有屬性的form
元素。//form[@id="myId"]
:表示id
值爲myId
的form
元素。
另外還有屬性模糊匹配方法,功能更加強大:
//form[start-with(@id,'myId')]
:表示所有屬性id
的值以myId
開頭的form
元素。//form[ends-with(@id,'myId')]
:表示所有屬性id
的值以myId
結尾的form
元素。//input[contains(@id,'myId')]
:表示所有屬性id
的值包含myId
的form
元素。//a[contains(text(),'貼吧')]
:表示超鏈接元素的文本內容爲貼吧的a
元素
在實際定位中,常常是上面三種方式結合起來進行定位。
當然,另外還有使用布爾邏輯計算定位,例如:
By.xpath("//div[@id='myId' or @name='myName']")
雙條件同時過濾,例如:
By.xpath("//div[@id='myId'][@name='myName'")
7 cssSelector定位
更詳細內容請見:W3C
css元素選擇器
選擇input
類型的元素:
By.cssSelector("input")
7.1 css類與id選擇器
id選擇器以 #
來定義,class類選擇器以一個.
顯示,有以下幾種例子:
- 選擇
id
爲myId
的元素:By.cssSelector("#myId")
- 選擇
id
爲myId
的input
元素:By.cssSelector("input#myId")
- 選擇class爲a的元素:
By.cssSelector(".a")
- 選擇class爲a、b的元素:
By.cssSelector(".a.b")
- 選擇class爲a的input元素:
By.cssSelector("input.a")
7.2 css屬性選擇器
- 選擇有屬性有屬性
maxlength
的input
元素By.cssSelector(“input[maxlength]”)
- 屬性
maxlength
的值精確等於255
的input
元素(完全相等,區分大小寫)By.cssSelector(“input[maxlength=‘255’]”)
- 屬性
class
的值以空格隔開,其中一項等於fm
(區分大小寫)input
元素:By.cssSelector(“input[class~=‘fm’]”)
- 屬性
class
的值以bar
開頭的div
元素By.cssSelector(“div[class^=‘bar’]”)
- 屬性
class
的值以bar
結尾的div
元素By.cssSelector(“div[class$=‘bar’]”)
- 屬性
name
的值包含myName
的form
元素By.cssSelector(“form[name*=‘myName’]”)
- 屬性
class
有一個以“soutu”開頭的用連字符分隔的值列表(從左邊)的span
元素:<span class="soutu-btn"></span>
By.cssSelector(“span[class|=‘soutu’]”)
- 屬性
type
等於hidden
且屬性name
等於ch
且屬性class
爲bg
的input
元素By.cssSelector("input[type='hidden'][name='ch'].bg")
7.3 css子元素選擇器
何爲子元素?以下面代碼中的form
元素爲基準,span
元素和a
元素是它的子元素,但input
元素不是。
代碼示例2:
<form id="myForm" class="fm">
<span class="bg s_ipt_wr quickdelete-wrap">
<input id="kw" name="wd" class="s_ipt" value="" maxlength="255">
</span>
<span class="bg s_ipt_wr quickdelete-wrap">
<a href="javascript:;" id="quickdelete" title="清空" class="quickdelete"></a>
</form>
子元素的標誌符號是>,下面是幾個示例。
form
元素的子元素中的span
元素:By.cssSelector("form#myForm > span")
- 屬性
id
爲form
的form
元素的子元素span
元素的子元素input
元素(實現百度搜索框定位):By.cssSelector("form#form > span > input")
7.4 css後代元素定位
後代元素與子元素的區別是,A元素的子元素的子元素,也是A的後代元素。同樣以前文代碼2中的form
元素爲基準,span
元素、a
元素和input
元素都是它的後代元素。
後代元素的標誌符號是空格,例如同樣定位百度搜索框,用後代元素方式:
By.cssSelector("form#form input[name='wd']")
7.5 css相鄰兄弟選擇器
相鄰兄弟選擇器(Adjacent sibling selector)可選擇緊接在另一元素後的元素,且二者有相同父元素。例如,屬性id
爲myId
的form元素的後代div
元素的span
子元素的相鄰的第一個弟弟元素input
:
By.cssSelector("form#myId div>span+input")
7.6 css僞類選擇器
這種選擇器,要求目標元素必須有父級元素,且符合位置匹配條件,具體如下:
- E:nth-child(n)和E:nth-last-child(n):兩者的區別是前者正序計數,後者倒序計數。其次,這兩個選擇器定位的元素要求必須在某個父級標籤內,且其父級標籤內對應索引n的元素的類型必須爲E,否則匹配失敗。以百度搜索框代碼爲例,span:nth-child(7)這樣是匹配失敗的,因爲form元素內第7個子元素是input類型元素,不是span類型。
- E:nth-of-type(n)和E:nth-last-of-type(n):兩者也是正序和倒序的區別。E:nth-of-type(n)與E:nth-child(n)的區別在於,前者匹配第n個E元素,後者匹配到第n個元素並判斷是否是E元素,不是則匹配失敗。
幾個示例如下:
- 屬性
class
爲s_ipt_wr
的span
元素的第2個子元素,且其類型爲input
的元素(位置和類型不對應則匹配失敗):By.cssSelector(“span[class~=‘s_ipt_wr’] > input:nth-child(2)”)
- 屬性
class
爲s_ipt_wr
的span
元素的倒數第3個子元素,且其類型爲input
的元素(位置和類型不對應則匹配失敗)By.cssSelector(“span[class~=‘s_ipt_wr’] > input:nth-last-child(3)”)
- 屬性
class
爲s_ipt_wr
的span
元素的第2個input
元素(該input
元素在所有子元素排第幾無所謂):By.cssSelector(“span[class~=‘s_ipt_wr’] > input:nth-of-type(2)”)
- 屬性
class
爲s_ipt_wr
的span
元素的倒數第3個input
元素(該input
元素在所有子元素排第幾無所謂):By.cssSelector(“span[class~=‘s_ipt_wr’] > input:nth-of-type(3)”)
- 屬性
class
爲s_ipt_wr
的span
元素的子元素中排在第一且爲input
類型的元素:By.cssSelector(“span[class~=‘s_ipt_wr’] > input:first-child”)
- 屬性
class
爲s_ipt_wr
的span
元素的子元素中排在最後一個且爲input
類型的元素:By.cssSelector(“span[class~=‘s_ipt_wr’] > input:last-child”)
- 屬性
class
爲s_ipt_wr
的span
元素的第一個input
類型的元素(該input
元素在所有子元素排第幾無所謂):By.cssSelector(“span[class~=‘s_ipt_wr’] > input:first-of-type”)
- 屬性
class
爲s_ipt_wr
的span
元素的最後一個input
類型的元素(該input
元素在所有子元素排第幾無所謂):By.cssSelector(“span[class~=‘s_ipt_wr’] > input:last-of-type”)
基本上常用的css selector定位方式都在下表中了: