联系方式:15120035568 QQ(几乎24小时在线):252413619 邮箱:robin9257#hotmail.com
上一篇我详细的和大家分享了整个定位的一个流程,但没有分享元素到底是如何定位的,定位的原理是什么,今天和大家分享元素定位的细节,即watir的核心。
本次以下面这句话为举例,来描述定位原理:
require 'rubygems'
require 'watir'
ie=Watir::IE.new
ie.goto 'image.baidu.com'
ie.text_field(:class,'kw').exist?
这段话的功能就是输出class为kw的输入框是否存在
首先我们看下textfield这个类,根据上一节,我们知道,这个类继承自InputElement,而这个类有继承自Element类
TextFiled类定义在input_element.rb文件中,它的exist?函数来自父类的父类(Element),该函数定义在element.rb中。
exist?函数代码如下:
def exists?
begin
locate if defined?(locate)
rescue WIN32OLERuntimeError
@o = nil
end
@o ? true: false
end
alias :exist? :exists?
因此,该函数执行locate函数,locate函数定义在input_element.rb中,若@o这个对象不为空,则返回true,否则返回false,而@o是在locate函数中赋值的,看下代码:
def locate
@o = @container.locate_input_element(@how, @what, self.class::INPUT_TYPES)
end
OK,@container是类container的实例对象,因此,我们需要看看locate_input_element函数,该函数定义在container.rb中
def locate_input_element(how, what, types, value=nil, klass=nil)
puts document
case how
when :xpath
return element_by_xpath(what)
when :css
return element_by_css(what)
when :ole_object
return what
end
# else:
locator = InputElementLocator.new self, types
locator.specifier = [how, what, value]
# 这个document赋值很重要
locator.document = document
return locator.element if locator.fast_locate
# todo: restrict search to elements.getElementsByTag('INPUT'); faster
locator.elements = ole_inner_elements if locator.elements.nil?
locator.klass = klass if klass
locator.locate
end
代码中我进行了一下标注,其中传入的@how就是例子中的:class,watir 支持xpath定位,css定位暂时不支持。:ole_object会返回@what。当how==:class时,就会跳过case,直接到locator的实例化,并传入参数。
需要注意这句:
locator.document = document
有人可能会问,后面的这个document是什么东西,这个document是一个函数,而这个函数定义在ie-class.rb中,看下代码
# Return the current document
def document
return @ie.document
end
好,根据前几篇的知识,@ie是internetExploere.Application对象,而document就是@ie本身再带的一个属性,具体大家可以查阅:
http://msdn.microsoft.com/en-us/library/aa752052%28v=vs.85%29.aspx
接下来我们需要看看locator中的fast_locate函数,该函数返回true或false
在此之前,大家需要先了解locator中有一个变量@specifiers,他是一个hash结构,结构如下:
how => what #how可以是:id,:class等等
:index => 1 #如果定位后,有N个元素都符合定位的要求,则只要其中的第一个元素
:value => sth #sth就是任意东西
接下来贴fast_locate代码:
def fast_locate
# Searching through all elements returned by ole_inner_elements
# is *significantly* slower than IE's getElementById() and
# getElementsByName() calls when how is :id or :name. However
# IE doesn't match Regexps, so first we make sure what is a String.
# In addition, IE's getElementById() will also return an element
# where the :name matches, so we will only return the results of
# getElementById() if the matching element actually HAS a matching
# :id.
the_id = @specifiers[:id]
puts the_id
if the_id && the_id.class == String &&
@specifiers[:index] == 1 && @specifiers.length == 2
@element = @document.getElementById(the_id) rescue nil
# Return if our fast match really HAS a matching :id
return true if @element && @element.invoke('id') == the_id
end
the_name = @specifiers[:name]
if the_name && the_name.class == String
@elements = @document.getElementsByName(the_name) rescue nil
end
false
end
end
其中
the_id = @specifiers[:id]
这句是查询@how是不是:id,若是,则返回@what并赋值给the_id。若定位方式是用:id,且what是String类型,则可以用@doucment来定位,该变量就是InternetExploere.Application的document属性的返回,利用document.getElementById这个自带函数获取对象,将返回对象存储在@element中。后面的the_name和the_id原理一样,调用@document的getElementByName来获取对象。
总的来说,fast_locate的作用就是当定位用:id,:name时,直接用InternetExploere.Application的document属性中的getElementById和getElementByName获取。
结论:用:id 和:name 定位要比用其他方式定位快!这两种定位方式不是由watir提供的,而是有InternetExploere提供的
当没有用:id和:name定位时,用以下方法。
先介绍locator中的另一个变量:@elements这是一个数组,主要功能是保存多个html元素
从locate_input_element函数,我们可以得知,若用其他方法定位,则调用ole_inner_elements函数
locator.elements = ole_inner_elements if locator.elements.nil?
locator.klass = klass if klass
locator.locate
接下来看看ole_inner_elements函数,该函数定义在container.rb中
def ole_inner_elements
return document.body.all
end
他返回body中的所有元素!当页面很大时,这个会比较耗时耗内存。接下来执行locator中的locate函数,这个函数定义在locator.rb中,locate主要功能就是遍历所有元素,比较元素是否符合定位要求!
以下是代码:
def locate count = 0 puts '@klass:' puts @klass puts '@types' puts @types @elements.each do |object| if @klass == Element element = Element.new(object) else element = @klass.new(@container, @specifiers, nil) element.ole_object = object def element.locate; @o; end end catch :next_element do throw :next_element unless @types.include?(element.type) @specifiers.each do |how, what| next if how == :index unless match? element, how, what throw :next_element end end count += 1 throw :next_element unless count == @specifiers[:index] return object end end nil end
因此,我们可以得知,每个遍历的元素,第一步都是对这个HTML元素建立RUBY对象,这是一个耗时耗内存的时间!所以,为了测试方便,尽量为每个元素设置ID,个人觉得为每个元素设置ID是一件规范的事情,并不需要消耗前端开发者的时间。
因此,watir有以上两种定位方法,定位后,将元素保存在@o中,以后的操作都是对@o进行操作了。
总结:第三篇和第四篇我都在分享watir是如何定位元素的,其实总的来说都是在调用InternetExploere.Application中的方法,所以,大家现在应该觉得watir是一个比较简单而且比较容易理解的框架。
接下来一篇会介绍watir执行js的方法以及如何利用js作为一个接口,让ruby和html进行通信,还会介绍attach函数和鼠标键盘的使用。
I‘m Robin