Watir是如何定位页面上的测试对象的----watir源码倾情解读

上一篇 / 下一篇  2011-07-11 22:03:10 / 个人分类: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是无处不在的。考虑下面的代码:

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类的一些方法:

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

元素的定位就是这么简单,可能看了上面的代码大家都有些糊涂了。下面我们举例说明一下元素的定位之旅。

假设有下面的方法

ie.div(:class => 'red', :index => 3)
  • 首先watir会生成这样的1个@specifiers变量,其值为{:class_name => 'red', :index => 3}
  • 然后watir会遍历页面上(实际上是container上,这里为了简单起见,简化了一下)所有的tag是
    的元素,并将这些ole元素封装成了watir的element
  • 最后在每一个element上调用how方法,在这个例子里就是调用@specifiers中的classname方法。为什么没有调用index方法?因为index是跳过的,详见matchwith_specifiers?方法
  • 如果element.how等于what(为了理解又简化了,专家见谅),也就是element.class_name = 'red'的话,则将看这个element是不是第index个,如果是则返回element的ole元素,元素定位之旅结束

在这里我们可以看到waitr定位元素一般是通过遍历页面上所有与给定元素拥有相同tag的元素,并比较其属性值的方式进行的。

其比较的方法和原理都很简单。当然,如果任意1个元素都通过遍历tag的方式进行的话,那么watir的效率将是比较低下的。为此watir也提供了快速定位的方法,这个我们以后再慢慢讨论。


TAG: Ruby ruby Watir watir

 

评分:0

我来说两句

我的栏目

日历

« 2024-03-20  
     12
3456789
10111213141516
17181920212223
24252627282930
31      

数据统计

  • 访问量: 5573
  • 日志数: 7
  • 建立时间: 2009-06-22
  • 更新时间: 2011-10-21

RSS订阅

Open Toolbar