watir是如何定位測試對象的

最近將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也提供了快速定位的方法,這個我們以後再慢慢討論。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章