最近将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也提供了快速定位的方法,这个我们以后再慢慢讨论。