最近將watir更新到了1.9.1,忽然發現以前承諾的帶大家讀waitr源碼的”誇誇其談”還尚未實現,甚表歉意,暫且先說明一下watir定位(locate)元素的基本原來,聊以將功補過。
以下說明均以watir 1.9.1爲例。在這裏建議大家最好將watir升級到最新版本,因爲最新版本增加了對IE9的支持,儘管支持的不是很全面,但聊勝於無,優勢總是有的。
在watir的源碼中找到locator.rb文件。該文件一般位於your_disk:\Ruby192\lib\ruby\gems\1.9.1\gems\watir-1.9.1\lib\watir\目錄下。
locator文件定義了1個Locator類,這個類是定位對象的1個helper類。該類中如如下2個方法:
- normalize_specifiers!: 該方法的作用是構造specifiers,而specifiers正是定位對象所要用到的”標識”。
- matchwithspecifiers?: 該方法的作用是判斷元素是否符合specifiers所定義的特徵。如果符合,那麼這個元素肯定就是我們要找的目標元素了。
簡單看一下這2個方法的源碼
首先簡單介紹一下specifiers。specifiers看上去很陌生,但是在我們的watir腳本中,specifiers是無處不在的。考慮下面的代碼:
1
2
|
ie.div( :id
=> 'my_div' )
ie.link( :class
=> 'qq' ) |
在這2個方法裏面specifiers就是 :id => 'my_div'和:class => 'qq'。於是可以推斷specifiers應該是Hash對象,像這樣div(:id, 'my_div')非Hash形式的參數應該是會被轉成Hash的。
def
normalize_specifiers!(specifiers) specifiers. each
do |how, what|
# 遍歷所有的specifiers,於是我們能推斷出watir是支持多屬性識別的。 # 例如`div(:id => 1, :class => 'red')`
case
how # how就是:id, :class之類的元素屬性,what就是這些屬性的屬性值
when
:index # 當用index定位對象時候就將what轉成Integer類型
what = what.to_i
when
:url how =
:href # 如果how是url的話,那麼將url屬性變成href屬性
# 這就證明了url和href屬性的作用是相同的
when
:class # 如果how是class,就將class變成class_name,這個比較複雜,不解釋
how =
:class_name when
:caption # 可以看出caption就是value屬性
how =
:value when
:method # 可以看出method 實際上是form的method,也就是post或get
how =
:form_method end @specifiers [how] = what
# 按照上面的規則重新生成specifiers,並賦值給@specifiers
end end def
match_with_specifiers?(element) # 判斷element是否符合定位的條件
# 也就是說element的屬性是不是跟@specifiers定義的相同
# 如果相同,這個element當然是我們需要尋找的了
@specifiers . each
do |how, what|
# 遍歷specifiers next
if how == :index
# 如果how是index的話則跳過 return
false unless
match? element, how, what #如果match(element, how, what)爲false,則返回false
end return
true # 否則就返回true
end |
end 在這裏要重點說一下match方法,match方法有3個參數,element how what, 該方法一般在Locator的子類中定義,下面會具體講到。 match方法的作用就是如果element能夠被how what匹配上則返回true,否則false
由於Locator只是基類,所以更加具體的識別任務就交由其子類完成。
TaggedElementLocator是Locator的子類,其具體作用是根據所需定位元素的tag及users(腳本編寫者)提供的specifiers來定位頁面元素。
下面介紹一下TaggedElementLocator類的一些方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
class
TaggedElementLocator < Locator def
initialize(container, tag) #初始化時候指定了container和tag,其中tag就是定位的關鍵
@container
= container @tag
= tag end def
set_specifier(how, what) #處理了how和what,將how和what變成了Hash,印證了我們上面的猜測
if
how. class
== Hash and
what. nil ?
specifiers = how
else specifiers = {how => what}
end @specifiers
= { :index
=> 1 }
# 如果沒指定how和what,那麼默認認爲how是index,what是1,就是默認返回第1個元素
normalize_specifiers! specifiers
#調用了基類的方法,規整了specifiers並賦值給@specifiers
end def
each_element tag # 很關鍵的1個方法,其作用是根據tag值,並調用js來獲取頁面上所有的tag是給定值的ole對象,並封裝成Watir的Element對象
#在container上調用getElementsByTagName,這是js的dom操作代碼,不懂請google
@container .document.getElementsByTagName(tag). each
do |ole_element|
yield
Element. new (ole_element)
end end def
locate #比較複雜的方法 #作用實際就是找到頁面上所有的tag爲給定值的watir element,然後遍歷這些元素,如果這些元素能夠被specifiers匹配則可能返回
count =
0 each_element( @tag )
do |element|
next
unless match_with_specifiers?(element)
# 如果該element不匹配則繼續 count +=
1 return
element.ole_object if
count == @specifiers [ :index ]
#如果有多個元素滿足條件,則返回第index個 #默認情況下index是1,也就是返回第1個匹配
end
# elements # 於是我們可以寫出如下的代碼ie.div(:class => 'red', :index => 3),返回class是red的第3個div元素
nil end def
match?(element, how, what) #核心的匹配方法,用來判斷元素是否符合specifiers
begin method = element.method(how)
# 如果元素定義了element.how方法則獲取這個方法的binding #舉例來說如果元素是div,how是id,則返回Div#id這個方法的binding
rescue
NameError # 否則元素沒有定義element.how方法,則拋出下面的錯誤
raise
MissingWayOfFindingObjectException, "#{how} is an unknown way of finding a <#{@tag}> element (#{what})" end case
method.arity # 判斷element.how的參數個數
when
0 # 如果沒有參數,則直接調用how方法,將返回的結果跟what值進行比較
what.matches method.call
when
1 # 如果有1個參數則將what值作爲這個參數傳給how方法
method.call(what)
else raise
MissingWayOfFindingObjectException, # 其他情況拋出錯誤
"#{how} is an unknown way of finding a <#{@tag}> element (#{what})" end end
# 該方法的返回值是what.matches或者是element.how(what),一般都是boolean end |
元素的定位就是這麼簡單,可能看了上面的代碼大家都有些糊塗了。下面我們舉例說明一下元素的定位之旅。
假設有下面的方法
1
|
ie.div( :class
=> 'red' ,
:index =>
3 ) |
- 首先watir會生成這樣的1個@specifiers變量,其值爲{:class_name => 'red', :index => 3}
- 然後watir會遍歷頁面上(實際上是container上,這裏爲了簡單起見,簡化了一下)所有的tag是div的元素,並將這些ole元素封裝成了watir的element
- 最後在每一個element上調用how方法,在這個例子裏就是調用@specifiers中的class_name方法。爲什麼沒有調用index方法?因爲index是跳過的,詳見matchwith_specifiers?方法
- 如果element.how等於what(爲了理解又簡化了,專家見諒),也就是element.class_name = 'red'的話,則將看這個element是不是第index個,如果是則返回element的ole元素,元素定位之旅結束
在這裏我們可以看到waitr定位元素一般是通過遍歷頁面上所有與給定元素擁有相同tag的元素,並比較其屬性值的方式進行的。
其比較的方法和原理都很簡單。當然,如果任意1個元素都通過遍歷tag的方式進行的話,那麼watir的效率將是比較低下的。爲此watir也提供了快速定位的方法,這個我們以後再慢慢討論。