发布新日志

  • Ruby语言入门(24)- 内部类 - Thread

    2013-03-18 15:17:47

    Thread使得并行编程成为可能。线程是一种共享内存空间并可同时运行的控制流。目前Ruby解释器采用时间片轮转法来控制线程,所以使用线程后并不会提高程序的运行速度。

    启动程序时生成的线程被称作主线程。若主线程因为某些原因而终止运行时,子线程和整个程序也会终止运行。发生异常时,会被送往主线程。

    若启动线程时指定的块结束运行时,该线程也将终结。块的正常结束和因异常等引起的非正常结束都代表快的终结。

    当线程内发生异常,而且没有被rescue捕到时,该线程会被停掉,而且不发出任何警告。如果这时有其它线程因Thread#join而等待这个线程,会在等待的线程中再次引发相同的异常。

    通过下列方法可以保证在线程因发生异常而终止工作时,中断解释器:
    $DEBUG的值设为真(调试模式)。用-d选项来启动ruby解释器也可以取得相同的效果。
    使用Thread.abort_on_exception来设置标识。
    使用Thread#abort_on_exception来设定特定线程的标识。

    线程的状态
    可以使用Object#inspect或Thread#status来查看线程的状态。 状态有:
    run (运行 or 可运行状态)
    sleep (挂起状态)
    aborting (终止处理中)
    dead (终止状态)

    Thread.abort_on_exception
    Thread.abort_on_exception = newstate
    若其值为真的话,一旦某线程因异常而终止时,整个解释器就会被中断

    Thread.critical
    Thread.critical = newstate
    当其值为真时,将不会进行线程切换

    Thread.current
    返回当前运行中的线程

    Thread.exit
    终止当前线程的运行。

    Thread.kill(thread)
    终止指定线程的运行。若该线程已经终止,则不作任何动作。

    Thread.list
    返回处于运行状态或挂起状态的活线程的数组。

    Thread.main
    返回主线程

    Thread.new([arg, ...]) { ... }
    Thread.start([arg, ...]) { ... }
    Thread.fork([arg, ...]) { ... }
    生成线程,并开始对块进行计算.

    Thread.pass
    将运行权交给其他线程. 它不会改变运行中的线程的状态,而是将控制权交给其他可运行的线程

    Thread.stop
    将当前线程挂起,直到其他线程使用run方法再次唤醒该线程.

    self[name]
    取出线程内与name相对应的固有数据.

    self[name] = val
    将线程内与name相对应的固有数据的值设为val

    abort_on_exception
    abort_on_exception = newstate
    它返回布尔值,在赋值形式中,它返回右边的newstate。

    alive?
    若线程是"活"的,就返回true.

    exit
    kill
    terminate 
    终止线程的运行.

    group 
    返回线程所属的ThreadGroup对象.

    join
    join(limit) 
    挂起当前线程,直到self线程终止运行为止. 若self因异常而终止时, 将会当前线程引发同样的异常.


    key?(name)
    若与name相对应的线程固有数据已经被定义的话,就返回true

    keys 
    以数组的形式返回与线程固有数据取得关联的索引.

    priority
    返回线程的优先度. 优先度的默认值为0. 该值越大则优先度越高.

    priority = val
    设定线程的优先度

    raise([error_type,][message][,traceback])
    在该线程内强行引发异常.

    run
    重新启动被挂起(stop)的线程.

    safe_level
    返回self 的安全等级.

    status
    使用字符串"run"、"sleep"或"aborting" 来表示活线程的状态.

    stop?
    若线程处于终止状态(dead)或被挂起(stop)时,返回true.

    value
    一直等到self线程终止运行(等同于join)后,返回该线程的块的返回值.

    wakeup
    把被挂起(stop)的线程的状态改为可执行状态(run).


  • Ruby语言入门(23)- 内部类 -Struct

    2013-03-18 14:44:23

    Struct为结构体类。由Struct.new生成该类的子类。在子类中使用new方法就可以生成构造体。构造体子类中定义了构造体成员的访问方法。

    Struct.new([name,] member ... )
    生成并返回一个名为name的Struct 类的子类。子类中定义了访问结构体成员的方法.结构体名name将成为Struct的类常数名,所以必须以大写字母开始。

    Struct::XXX.new(value,...)
    Struct::XXX[value,...]
    生成并返回结构体对象。参数将成为结构体的初始值。

    Child = Struct.new("Child", :name, :age) //由Struct.new生成该类的子类。
    child1 = Child.new("xixi", 5) //在子类中使用new方法就可以生成构造体。
    child1.age  //构造体子类中定义了构造体成员的访问方法。
    #=> 5
    child2.name
    #=>xixi

    self[nth]
    返回结构体的第nth个成员的值。
    child1[1]
    #=> 5  //child1[1] --> child1.age

    self[nth]=value
    将结构体的第nth个成员的值设定为value,并返回value值。
    child1[1]=11
    child1[1]
    #=> 11

    each {|value| ... }
    依次对结构体的各个成员进行迭代操作。
    child1.each {|v| puts v}
    #=> 
    xixi
    11

    each_pair {|member, value| ... } 
    在结构体中,依次将某成员的名称和值传给块进行迭代操作。
    child1.each_pair{|m,v| p m,v}
    #=> 
    :name
    xixi
    :age
    11

    length/size
    返回结构体的成员数量。
    child1.legth
    #=>2

    members
    以数组形式返回结构体的成员名(字符串)。
    child1.members
    #=>[:name, :age]

    values/to_a
    将结构体的成员的值存入数组,并返回它。
    child1.values
    #=>["xixi", 11]

    values_at(member_1, ... member_n)
    以数组的形式返回参数(数值、字符串或Symbol)所指成员的值。
    child1.values_at(1)
    #=> 11


  • 使用Ruby对XML进行操作

    2013-03-06 15:55:49

    Ruby是用REXML库对XML文件进行解析,路径是: rexml/document 

    所有方法全部包含在模块 module REXML 中,所以在文件头部引用的时候使用如下格式:
    require 'rexml/document'
    include REXML

    首先我们要打开指定的XML文件:
    xmlDoc=File.new('c:\\nodes.xml')
    xmlFile=Document.new(xmlDoc)

    new
    Document类的构造方法,参数可以为一个xml文件的路径,或者一个IO对象。

    puts xmlFile.root

    输出为:
    <root>Root Node<Child1 att1='attvalue1' att2='attvalue2'>Son<Child2>grandson</Child2></Child1></root>

    root
    返回一个element类型的对象,是该xml的根元素。 注意,这时候不是document对象了,是一个element对象。

    puts xmlFile.root.document

    输出为:
    <?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
    <root>Root Node<Child1 att1='attvalue1' att2='attvalue2'>Son<Child2>grandson</Child2></Child1></root>

    document
    返回包含element类型对象的文档, 注意,这时候是document对象了。

    puts xmlFile.root.get_text.value
    puts xmlFile.root.get_text
    puts xmlFile.root.text
    puts xmlFile.root.texts

    输出均为:
    Root Node

    text: 返回第一个text element的string值,为String类
    texts:返回的所有text element的集合,为Array类
    get_text:返回的是一个REXML::Text对象
    value:返回REXML::Text对象的值

    puts  XPath.first(xmlFile, "//Child1" )

    输出为:
    <Child1 att1='attvalue1' att2='attvalue2'>Son<Child2>grandson</Child2></Child1>

    使用XPath找到第一个满足Child1的节点,类型为element

    XPath.each(xmlFile, "//") { |element| puts element }

    返回所有的element

    puts xmlFile.get_elements("//Child2")

    输出为:
    <Child2>grandson</Child2>

    使用xpath查找匹配的element

    xmlFile.root.xpath

    输出为:
    /root

    显示element的xpath,可以看到具体的xpath路径

    puts xmlFile.root.elements[1]

    输出为:
    <Child1 att1='attvalue1' att2='attvalue2'>Son<Child2>grandson</Child2></Child1>

    puts xmlFile.root.elements[1].elements[1]

    输出为:
    <Child2>grandson</Child2>

    以上都是利用index定位element,注意index都是从1开始的,没有0



  • Ruby利用封装模块来创建XML文档

    2013-03-06 10:49:54

    我们来看看怎么通过定制可复用的函数来创建xml文档:

    #coding: utf-8
    require 'win32ole'

    #创建xml文件
    $xmlDoc = WIN32OLE.new('Msxml2.DOMDocument.3.0')

    #创建Root节点
    $Root=$xmlDoc.createElement("RootNode")
    $xmlDoc.appendChild $Root

    #创建新的节点,并作为指定节点child加入
    def newXmlNode(newNodeName, newNodeValue, parentNode)
      $newnode = $xmlDoc.createElement(newNodeName) 
      currentNode=parentNode.appendChild($newnode)
      currentNode.text = newNodeValue
      return $newnode
    end 

    #创建指定节点的属性
    def addNodeAttribute(xmlNodeName,newAttribute, newAttributeValue)
      attrs = $xmlDoc.createAttribute(newAttribute)
      attrs.value=newAttributeValue
      xmlNodeName.setAttributeNode(attrs)
    end

    #创建一个子节点,作为Root的子节点加入
    newnode1=newXmlNode('Child1','Son',$Root)
    #给指定的子节点添加属性1
    addNodeAttribute(newnode1,'att1','attvalue1')
    #创建一个子节点,并作为newnode1的子节点加入
    newnode2=newXmlNode('Child2','grandson',newnode1)
    #给指定的子节点继续添加属性2
    addNodeAttribute(newnode1,'att2','attvalue2')
    #保存xml文件
    $xmlDoc.save('C:/nodes.xml')

    运行结果如下:
  • 使用Ruby生成XML文件

    2013-03-05 18:40:16

    下文为一个ruby生成多层xml文档的实例:

    require 'win32ole'

    #创建xml文件
    xmlDoc=WIN32OLE.new("MSXML2.DOMDocument")

    #设置Root
    Root=xmlDoc.createElement("Root")
    xmlDoc.appendChild Root

    #创建各层子元素

    #层1
    bc1=xmlDoc.createElement("child_1")
      bc1Attribute=xmlDoc.createAttribute("Level-1")
      bc1Attribute.text="1"
      bc1.text="1"
      bc1.setAttributeNode bc1Attribute
    #层2
    bc2=xmlDoc.createElement("child_2")
      bc2Attribute=xmlDoc.createAttribute("Leve-2")
      bc2.setAttributeNode bc2Attribute
      bc2Attribute.text="2"
      bc2.text="2"
    #层3
    bc3=xmlDoc.createElement("child_3")
      bc3.text="3"
    #层4-1
    bc41=xmlDoc.createElement("child_4_1")
      bc41.text="4-1"
    #层4-2
    bc42=xmlDoc.createElement("child_4_2")
      bc42.text="4-2"
      
    #加入各自父节点下
      bc3.appendChild bc41
      bc3.appendChild bc42
      bc2.appendChild bc3
      bc1.appendChild bc2
      Root.appendChild bc1


    #创建 XML processing instruction 并把它加到根元素之前
    header=xmlDoc.createProcessingInstruction("xml","version='1.0'")
    xmlDoc.insertBefore header,xmlDoc.childNodes(0)
    #文件保存
    xmlDoc.Save "c:\\test.xml"

    执行结果如下:
  • 使用excel进行Data Driven的实例

    2013-03-05 17:28:02

    基于ruby的watir web driver

    # encoding: utf-8
    #要求支持中文
    require 'win32ole'
    #支持excel文件操作
    require 'watir-webdriver'
    #watri web driver支持

    #打开数据文件,读入用户名和密码信息
    excel = WIN32OLE::new('excel.Application')
    workbook = excel.Workbooks.Open('d:\rubycode\test.xlsx')
    worksheet = workbook.Worksheets('test')
    worksheet.Activate
    @username = worksheet.Range('a1').Value
    @password = worksheet.Range('b1').Value
    excel.Quit

    #使用Watir 进行自动化测试
    $BASE_URL="http://10.32.148.243:8080"
    $browser=Watir::Browser.new:ie
    $browser.goto($BASE_URL+'/parkinglot/login')
    Watir::Wait.until {$browser.text.include?('停车场管理系统登录')} 

    #从数据文件中得出的测试数据
    $browser.text_field(:name, "username").set(@username)
    $browser.text_field(:name, "password").set('1234')

    $browser.div(:class, "login_btn").click
    Watir::Wait.until {$browser.text.include?('Copyright')} 

    很简单的一个示例
  • Watir Web Driver 对excel 文件的操作

    2013-03-05 15:56:23

    一般我们在Watir中实现data driven时,是依靠ruby的file函数。下面是如何使用excel文件进行data-driven的。

    打开文件需要首先 
    require 'win32ole'

    然后打开文件
    excel = WIN32OLE::new('excel.Application')
    workingbook = excel.Workbooks.Open('d:\rubycode\test.xlsx')

    然后选择需要的sheet

    #依据sheet的名称选择
    workingsheet = workingbook.Worksheet('test') 

    #或者依据顺序号选择
    workingsheet = workingbook.Worksheets(1) 

    #激活可以用select
    workingsheet.Select
    #也可以用Activate
    workingsheet.Actviate

    然后选择你需要的数据

    #第一个cell的值
    workingsheet.Range('a1').Value

    #第一列的1-10的值
    worksheet.Range('a1:a3').Value

    #第一行的a-g的值
    worksheet.Range('a1:b1:c1:d1:e1:f1:g1').Value
    或者
    worksheet.Range('a1:g1').Value

    #任意cell的值
    worksheet.Range('a1:b2:c3:d4:e5:f2:g9').Value

    #第一列第一个到第三列第三个,即a1,a2,a3,b1,b2,b3,c1,c2,c3 
    worksheet.Range('a1:c3').Value

    #判断第一列的非空行数(注意,返回的是第一个空cell)
    line=1
    cells='a1'
    while worksheet.Range(cells).Value
    line=line+1
    cells='a'+line.to_s
    end

    #列出第一列的非空cell
    line=line-1
    cells='a'+line.to_s

    if cells == 'a1'
    rows='a1'
    else
    rows='a1:'+cells
    end
    celldata = worksheet.Range(rows).Value 
    puts cell data

    写入数据

    #单个数据
    worksheet.Range('e2').Value= 'test' 
    #数组值
    worksheet.Range('a5:c5').Value = ['more', 'than', 'one'] 

    然后存储

    workbook.Close(1)
    #对应的是第一个按键
     
    workbook.SaveAs 'mytest.xls'
    # 默认路径是"My Documents"

    关闭文件
    excel.Quit

    注意,操作时不要打开目标文件。

    更多的关于excel的操作,可以参考:
    http://msdn.microsoft.com/zh-cn/library/ff841127(v=office.14).aspx


  • Watir Webdriver的editor处理

    2013-01-28 15:00:47

    很多网站使用了WYSIWYG Editor来输入文本,比如51testing这样的。Watir内置了一些方法可以对其进行处理:

    一种是先查找到editor所在的iframe,然后使用send_keys的方法来发送字符串,需要注意的是,包含该iframe的窗口必须是在所有的窗口最上方.

    另外一种方式是书写一段javascript语句,通过对browser进行内容输入,这种方法最常见

    例如CKEditor

    require 'watir-webdriver'
    b = Watir::Browser.new :firefox
    b.goto 'http://ckeditor.com/demo'
    b.execute_script("CKEDITOR.instances['editor1'].setData('hello world');")
    b.frame(:title => 'Rich text editor, editor1, press ALT 0 for help.').send_keys 'hello world again'

    这个例子就是向iframe直接通过send_keys发送字符

    而使用TinyMCE Editor的例子,就是执行一段javascript语句

    require 'watir-webdriver'
    b = Watir::Browser.new
    b.goto 'http://tinymce.moxiecode.com/tryit/full.php'
    b.execute_script("tinyMCE.get('content').execCommand('mceSetContent',false, 'hello world' );")
    b.frame(:id => "content_ifr").send_keys 'hello world again'

  • Watir Webdriver支持的特殊键

    2013-01-28 14:02:03

    Watir Webdriver可以向element发送特殊的key值,可以有如下几种方式:

    向browser发送Enter键,等于在当前页面敲击回车,作用于当前焦点处:
    browser.send_keys :enter

    向指定的element发送多个键值:
    browser.element.send_keys [:control'a'], :backspace

    也可以overide一些元素的click事件:
    browser.element.click(:shift:control)

    可以指定的key值有:
    :null
    :cancel cancel键
    :help help键
    :backspace 退格键
    :tab 制表符键
    :clear 清除键
    :return 回车符键
    :enter 回车键
    :shift 右shift键
    :left_shift 左shift键
    :control 右ctrl键
    :left_control 左ctrl键
    :alt 右Alt键
    :left_alt 左Alt键
    :pause 暂停键
    :escape Esc键
    :space 空格键
    :page_up PgUp键
    :page_down PgDn键
    :end End键
    :home Home键
    :left 左移
    :arrow_left 左移键
    :up 上移
    :arrow_up 上移键
    :right 右移
    :arrow_right 右移键
    :down 下移
    :arrow_down 下移键
    :insert 插入键
    :delete 删除键
    :semicolon 分号键
    :equals 等号键
    :numpad0 数字0键
    :numpad1 数字1键
    :numpad2 数字2键
    :numpad3 数字3键
    :numpad4 数字4键
    :numpad5 数字5键
    :numpad6 数字6键
    :numpad7 数字7键
    :numpad8 数字8键
    :numpad9 数字9键
    :multiply *号键
    :add +号键
    :separator |键
    :subtract -键
    :decimal .键
    :divide /键
    :f1 F1键
    :f2 F2键
    :f3 F3键
    :f4 F4键
    :f5 F5键
    :f6 F6键
    :f7 F7键
    :f8 F8键
    :f9 F9键
    :f10 F10键
    :f11 F11键
    :f12 F12键
    :meta windows键(windows平台)
    :command 等同:meta键,别名
  • Watir Webdriver的Page Object应用(2)

    2013-01-28 13:41:26

    如何去创建一个Page Object,下面有一些建议:

    尽量给每一个page页面创建一个Page Object
    如果一个页面包含过多的业务逻辑,你可以考虑将它创建为多个Page Object
    把element的详细处理都封装起来
    对于测试代码里,永远不要直接去操作element对象,或者操作browser。
    如果使用Rspec或者Cucumber,在define_step里,不要有直接操作的代码
    Page Object的目标是当页面发生变化是,你不需要去修改测试代码,而是修改后面的object层
    不要在Page Ojbect中包含Assertion,断言应该仍在在代码层实现

    我们来讨论一个实例:


    browser = Watir::Browser.new
    browser.goto "http://example.com/login"

    browser.text_field(:name => "user").set "Mom"
    browser.text_field(:name => "pass").set "s3cr3t"
    browser.button(:id => "login").click

    Watir::Wait.until { browser.title == "Your Profile" }
    browser.div(:id => "logged-in").should exist

    这是一个简单的例子,完全按照workflow去操作一个个element对象

    如果将其Page Object化,那么看其起来是这个样子的:

    site = Site.new(Watir::Browser.new)

    login_page = site.login_page.open
    user_page = login_page.login_as "Mom", "s3cr3t"

    user_page.should be_logged_in

    请注意一下对应关系


    为了实现Page Obejcts化,我们需要将详细的步骤,封装起来:

    class BrowserContainer
      def initialize(browser)
        @browser = browser
      end
    end

    class Site < BrowserContainer
      def login_page
        @login_page = LoginPage.new(@browser)
      end

      def user_page
        @user_page = UserPage.new(@browser)
      end

      def close
        @browser.close
      end
    end # Site

    class LoginPage < BrowserContainer
      URL = "http://example.com/login"

      def open
        @browser.goto URL
        self
      end

      def login_as(user, pass)
        user_field.set user
        password_field.set pass

        login_button.click

        next_page = UserPage.new(@browser)
        Watir::Wait.until { next_page.loaded? }

        next_page
      end

      private

      def user_field
        @browser.text_field(:name => "user")
      end

      def password_field
        @browser.text_field(:name => "pass")
      end

      def login_button
        @browser.button(:id => "login")
      end
    end # LoginPage

    class UserPage < BrowserContainer
      def logged_in?
        logged_in_element.exists?
      end

      def loaded?
        @browser.title == "Your Profile"
      end

      private

      def logged_in_element
        @browser.div(:id => "logged-in")
      end
    end 

    从上面代码可以看出,Page Objects化之后,我们的测试代码都是按照封装好的去书写,例如,将手工测试用例map到自动化测试步骤(Cucumber),在代码更迭之后,我们只需要去修改后面封装的对应代码,从而避免如下的问题发生:

    1. 漏掉一部分代码没有更新
    2. 更新错误
    3. 打乱了原有的代码结构
    4. 重复代码过多
    5. 定位错误困难

    当完成Page Object化之后,跟其他的工具集成就很简单了,例如,在cucumber中我们可以在初始化测试环境中加入一段代码:

    require "watir-webdriver"
    require "/path/to/site"

    module SiteHelper
      def site
        @site ||= (
          Site.new(Watir::Browser.new(:firefox))
        )
      end
    end

    World(SiteHelper)

    这样,得到一个更加合理优化的代码结构。

    而且匹配过程变更加清晰明了:

     Given /I have successfully logged in/ do
       login_page = site.login_page.open

       user_page = login_page.login_as "Mom", "s3cr3t"
       user_page.should be_logged_in
     end

  • Watir Webdriver的Page Object应用(1)

    2013-01-28 10:43:18

    Page Objects是一种设计模式,用来将页面上的对象进行模块化。通过消除重复的对象,建立起一个抽象基类,从而使得你在开发浏览器自动化测试脚本时有更好的维护性和更强的健壮性。

    Page Objects可以从两个方面来看待:
    从测试开发人员的角度来看, 一个Page Object可以看作一个服务。
    从开发人员的角度来看, 一个Page Object可以看作良好结构的页面对象。

    对于Page Objects来说,一定要理解,它是提供一个服务的对象,而不用更详细的研究它的方法以及结构的细节。

    我们来举一个简单的例子,比如一个Web Mail的收件页面, 我们可以把它想象成一个服务,能够提供写信,读信,展示来信的主题等功能,对于我们测试来说,它这些功能是怎么实现的不是我们考虑的范围。

    对于Page Objects来说,返回值应该是其他的Page Objects,这意味着我们是在不同的Page Objects中进行数据交互。

    我们用login page来做个例子


    public class LoginPage {
        public HomePage loginAs(String username, String password) {
            // ... 成功的login
        }
        
        public LoginPage loginAsExpectingError(String username, String password) {
            //  ...失败的login
        }
        
        public String getErrorMessage() {
            // 判断一下错误的内容
        }
    }

    从上面我们可以看到,不但要考虑成功的login到下一个page,也得考虑失败的login是转入那个page,还是在page上显示错误信息。考虑的都是page这个整体需要处理的行为。


    另外,我们应该考虑的是,我们的测试应该是着重判断page的状态,拿inbox举例子:

    public void testMessagesAreReadOrUnread() {
        Inbox inbox = new Inbox(driver);
        inbox.assertMessageWithSubjectIsUnread("I like cheese");
        inbox.assertMessageWithSubjectIsNotUnread("I'm not fond of tofu");
    }

    我们的断言,是放在page的状态上,也可以这么写:

    public void testMessagesAreReadOrUnread() {
        Inbox inbox = new Inbox(driver);
        assertTrue(inbox.isMessageWithSubjectIsUnread("I like cheese"));
        assertFalse(inbox.isMessageWithSubjectIsUnread("I'm not fond of tofu"));
    }

    当然,我们也要先通过webdriver来判断我们所在的页面是对的,或者说我们要操作的Page Object是已经出现了

    public class LoginPage {
        private final WebDriver driver;

        public LoginPage(WebDriver driver) {
            this.driver = driver;

            // 判断我们在正确的页面上
            if (!"Login".equals(driver.getTitle())) {
                //如果不在,那就报错
                throw new IllegalStateException("This is not the login page");
            }
        }

        // 理论上讲,login Page Object能提供Login As的服务 
        public HomePage loginAs(String username, String password) {
            // 在这里是唯一的详细操作步骤
            driver.findElement(By.id("username")).sendKeys(username);
            driver.findElement(By.id("passwd")).sendKeys(password);
            driver.findElement(By.id("login")).submit();
             
            //返回对象应该是一个新的Page Object
            return new HomePage(driver);
        }
    }



  • 使用Watir Webdriver对页面性能进行测试

    2013-01-28 10:34:23

    Watir Webdriver自带一个gem  Watir-WebDriver-Performance, 可以对页面的性能进行一些简单的测试。

    方法如下:

    require 'watir-webdriver'
    require 'watir-webdriver-performance'
     
    b = Watir::Browser.new :chrome
     
    10.times do
      b.goto 'http://watir.com'
      load_secs = b.performance.summary[:response_time]/1000
      puts "Load Time: #{load_secs} seconds."
    end 

    得到的结果如下:
    Load Time: 3.701 seconds.
    Load Time: 0.694 seconds.
    Load Time: 1.874 seconds.
    Load Time: 1.721 seconds.
    Load Time: 2.096 seconds.
    Load Time: 0.823 seconds.
    Load Time: 2.362 seconds.
    Load Time: 1.008 seconds.
    Load Time: 1.761 seconds.
    Load Time: 2.066 seconds.

    可以用的方法除了response_time之外,还有:
    :summary
    :navigation
    :memory
    :timing

    注意,目前只能支持chrome和IE9的版本
  • Watir Webdriver 对JS Dialog的处理

    2013-01-28 10:20:52

    Watir webdriver内置了如何处理javascript. dialog的方法,以及从dialog获得所需值的方法。

    # 判断alert是否存在
    browser.alert.exists?
     
    # 获得alert的值
    browser.alert.text
     
    # 关闭alert
    browser.alert.ok
    browser.alert.close

    # 接受confirm
    browser.alert.ok
     
    # 取消confirm
    browser.alert.close

    # 输入内容到prompt
    browser.alert.set "Prompt answer"
     
    # 接受prompt
    browser.alert.ok
     
    # 取消prompt
    browser.alert.close

    如果上述方法无效,我们还有一些替代的方法:

    # 对于alert,overide从而使其不返回任何值
    browser.execute_script("window.alert = function() {}")
     
    # 返回用户在prompt输入的值
    browser.execute_script("window.prompt = function() {return 'my name'}")
     
    # 返回空值,用于模拟点击prompt的cancel
    browser.execute_script("window.prompt = function() {return null}")
     
    # 返回true,用于模拟模拟点击confirm的ok
    browser.execute_script("window.confirm = function() {return true}")
     
    # 返回false,用于模拟点击confirm的cancel
    browser.execute_script("window.confirm = function() {return false}")
     
    # 对于离开popup不返回任何值
    browser.execute_script("window.onbeforeunload = null")

  • Watir Webdriver生成文件型log的实例

    2013-01-27 17:26:38

    我们想获得很好的log日志,这需要我们动手去写,这里有个生成文件log的实例:

    首先我们有个主log生成器, logfactory.rb

    require 'logger'


    # default logger
    class LoggerFactory
      
      # start logger
      def LoggerFactory.start_default_logger(fileNamePrefix)
        
        # time = Time.now.strftime("%m %d %Y %H %M %s")  
     time = Time.now.strftime("%Y%m%d%H%M%S") 

        # logger = CoreLogger.new(File.join(File.dirname(__FILE__), "#{fileNamePrefix}_#{time}.txt") ,logs to keep, maxlogsize)
      logger = CoreLogger.new(File.join(File.expand_path(".") + "\\logs\\", "#{fileNamePrefix}_#{time}.txt") ,2, 1000000)
        return logger 
      end  
    end 



    class  CoreLogger < Logger
      
      # extend the logger, overide it
      def initialize(fileName, logsToKeep, maxLogSize) 
        super(fileName , logsToKeep, maxLogSize) 
        self.level = Logger::INFO   # set to INFO level
        self.datetime_format = "%d-%b-%Y %H:%M:%S" 
        self.info("Logger starting...")          
      end 
      
      #overloaded "log" from logger.rb to something more intuitive.
      def log(message) 
        puts "log #{message}\n"         #optional. comment out if you don't want to see logging in the console
        info(message)                   #calls info in logger.rb -- would be good to use different logging levels
      end 
    end  

    然后在我们的case中引入:

    require 'logfactory'

    就可以进行使用了:


    def login_action
    # Navigate to target URL
    $browser.goto(TEST_SITE)
    $logger.log("Passed: Step " + $stepcounter.to_s() + ": Navigate to URL:" + TEST_SITE)
    $stepcounter=$stepcounter + 1


    # wait till the user name element show up
    Watir::Wait.until {$browser.text.include? "USER NAME:"} 
       
       
    # enter user name
        $browser.text_field(:name, "UserName").set(LOGINID)
        $logger.log("Passed: Step " + $stepcounter.to_s() + ": Enter '" + LOGINID + "' in the User Name text field")
        $stepcounter=$stepcounter + 1
        
        # enter user password
        $browser.text_field(:name, "UserPassword").set(PASSWORD)
        $logger.log("Passed: Step " + $stepcounter.to_s() + ": Enter '" + PASSWORD + "' in the Password text field")
    $stepcounter=$stepcounter + 1
        
        # click Login button
        $browser.button(:value, "Login").click
        $logger.log("Passed: Step " + $stepcounter.to_s() + ": Click the 'Login' button")
    $stepcounter=$stepcounter + 1

    # if the duplicate login session pop up show up
    if $browser.button(:value, 'OK').exist? # Yes, pop up is here
    $browser.button(:value, 'OK').click # Click OK button
    $logger.log("Passed: Step " + $stepcounter.to_s() + ": click the 'OK' button")
    $stepcounter=$stepcounter + 1
    end


    # check login sucessfully or not
    # Watir::Wait.until {$browser.title.include?('Home')}
        $browser.div(:id, "content").wait_until_present

    if $browser.div(:id, "content").text.include?"Home" 
    $logger.log("Passed: Step " + $stepcounter.to_s() + ": Login successfully")
    $stepcounter=$stepcounter + 1
    else
    $logger.log("Failed: Step " + $stepcounter.to_s() + ": Login failed")
    $stepcounter=$stepcounter + 1
    end
     end
       
      def logout_action
     
      # click the logout link
        $browser.link(:text, "Log Out").click
        $logger.log("Passed: Step " + $stepcounter.to_s() + ": Click the 'Log Out' link")

          # close the browser
          $browser.close
      end

    生成的结果如:
    # Logfile created on 2012-11-21 16:17:52 +0800 by logger.rb/31641
    I, [21-Nov-2012 16:17:52#5096]  INFO -- : Logger starting...
    I, [21-Nov-2012 16:17:52#5096]  INFO -- : 
    I, [21-Nov-2012 16:17:54#5096]  INFO -- : ------------------------------------------
    I, [21-Nov-2012 16:17:54#5096]  INFO -- : ## Beginning of test case 01            ##
    I, [21-Nov-2012 16:17:54#5096]  INFO -- : ------------------------------------------
    I, [21-Nov-2012 16:17:55#5096]  INFO -- : Passed: Step 1000: Navigate to URL:http://10.32.152.113:8080/
    I, [21-Nov-2012 16:17:57#5096]  INFO -- : Passed: Step 1001: Enter 'test@gmail.com' in the User Name text field
    I, [21-Nov-2012 16:17:58#5096]  INFO -- : Passed: Step 1002: Enter 'password123' in the Password text field
    I, [21-Nov-2012 16:17:58#5096]  INFO -- : Passed: Step 1003: Click the 'Login' button
    I, [21-Nov-2012 16:17:59#5096]  INFO -- : Passed: Step 1004: click the 'OK' button
    I, [21-Nov-2012 16:18:00#5096]  INFO -- : Passed: Step 1005: Login successfully
    I, [21-Nov-2012 16:18:00#5096]  INFO -- : ------------------------------------------
    I, [21-Nov-2012 16:18:00#5096]  INFO -- : ## End of test case 01                  ##
    I, [21-Nov-2012 16:18:00#5096]  INFO -- : ------------------------------------------


  • Watir webdriver一些常用的方法

    2013-01-27 17:06:22

    使用Test Unit的方式组织测试脚本,只有使用这种架构,才能进行assert

    require "test/unit"
    require "watir-webdriver"
      

    class TC_myTest < Test::Unit::TestCase

      def testcase1

        $browser=selenium.Browser.new(chrome)  
        $browser.goto('http://10.32.148.243:8080/parkinglot/')
        assert($browser.element(:text, 'floor Manage').click)
      end
    end


    最好有些常规的功能在所有的code之前,和最后运行

    # 在所有case运行之前进行一些操作
    def setup
      $browser = 'chrome' if $browser.nil?
      $site = 'http://test.localhost' if $site.nil?
     
      if $headless
        require 'headless'
        $headless = Headless.new
        $headless.start
      end
      
     if $browser == 'chrome'
        $b = Watir::Browser.new :chrome
      elsif $browser == 'firefox'
        $b = Watir::Browser.new :ff
      elsif $browser == 'ie'
        $b = Watir::Browser.new :ie
      end
     
      $b.goto $site
    end
     
    # 关闭所有的窗口
    def teardown
      $b.close
      if $headless
          $headless.destroy
      end
    end


    通过上面的代码,会自动运行系统的default设定的浏览器,一直其设定的default的URL

    当然, 使用参数化的过程来选择浏览器,也更加专业:

    ARGV.each { |arg|
        if arg.downcase.include? 'chrome'
            $browser = 'chrome'
        elsif arg.downcase.include? 'firefox'
            $browser = 'firefox'
        elsif arg.downcase.include? 'ff'
            $browser = 'firefox'
        elsif arg.downcase.include? 'ie'
            $browser = 'ie'
        elsif arg.downcase.include? 'headless'
            $headless = true
        end}

    我们喜欢在错误发生的时候有更详细的信息,但是不可能一直盯着跑,那就在错误发生时,拍个screenshot,以便我们回头来查看

    time = Time.new
    $b.driver.save_screenshot(File.dirname(__FILE__) + '/screenshots/' + @method_name + '_' + time.strftime('%Y%m%d_%H%M%S') + '.png');

    实际上,我们可以用的assert语句还有 assert_true, assert_false和assert_equal,我们判断返回值是否等于我们的期望值的时候,可以这么写:

    assert_equal 'Click Me', $b.text_field(:name, 'click1').value

    我们在组织case的时候,最好引入模块化,或者层次化,这样能搞好的整理我们的代码,例如:


    def form_register_page
      $b.text_field(:name, 'organization_name').set('Magic/More Magic')
      $b.text_field(:name, 'question_38').set('As mentioned above, we make magic and more magic.')
      $b.text_field(:name, 'question_39').set('People who like magic and more magic, as opposed to less magic.')
      $b.link(:id=> 'show-more').click
      $b.text_field(:name, 'question_41').set('Im putting stuff into question 41')
      $b.text_field(:name, 'question_45').set('Im putting stuff into question 45')
    end

    对于一个form里的所有操作,我们都可以封装到一个方法里,更加易读。


    对于time out,是个老大难问题,我们最好能够多处理一下,这里就是个很好的实例:
    def load_link(waittime)
      begin
        Timeout::timeout(waittime)  do
        yield
      end
      rescue Timeout::Error => e
        puts "Page load timed out: #{e}"
        retry
      end
    end
     
    def browse_to_new_project
    load_link(30){ $b.goto $site + "/designtourney/projects/new" }
    end
     
    def click_logo_design
    load_link(30){ $b.link(:class, 'logo-design').click }
    end

    虽然默认的log已经足够使用,但是可读性不是很好,我们可以设置一个更加丰富的log文件来处理error:

    module Test
      module Unit
        class TestSuite
          alias :old_run :run
          def run(result, &progress_block)
            old_run(result, &progress_block)
            File.open('errors.log', 'w'){|f|
              result.faults.each{|err|
                case err
                  when Test::Unit::Error, Test::Unit::Failure
                    f << err.test_name
                    f << "\n"
                  #not in log file
                  when Test::Unit::Pending, Test::Unit::Notification, Test::Unit::Omission
                  end
              }
            }
          end
        end
      end
    end

    有时候,尽管出错了,但是错误的原因完全是个意外,甚至是不可知的,我们想再试试怎么办:

    # create string of all args
    args = ""
    ARGV.each { |arg| args+=" "+arg }
    f = File.open("errors.log") or die "Unable to open file..."
     
    # start with an empty array
    errors=[]
    f.each_line {|line|
      errors.push line
    }
     
    if errors.length > 0
      puts 'Attempting to resolve errors'
      try = 1
      while try <= 3
        puts "Try number: "+try.to_s
        errors.each_with_index{|name, i|
          test = /(.+?)\((.+?)\)/.match(name)
          if system "ruby \""+test[2]+".rb"+args+"\""
            errors[i] = false
          end
        }
        errors.delete(false)
        if errors.length == 0
          puts 'All errors resolved successfully!'
          break
        end
        try+=1
      end
      File.open('errors.log', 'w'){|f|
        errors.each{|error|
          f << error
          f << "\n"
        }
      }
      if errors.length != 0
        puts 'Errors unresolved'
      end
    else
      puts 'There are no errors in errors.log'
    end

    我们等于要把log中的error都再次过滤一下,从而使得有些可以避免的error跑到我们的视野中,搞的我们花费了大量时间去处理一个完全的孤立的意外。

    把上面所有的都整理起来,就是一个你可以作为参考的良好实例:

    require "rubygems"
    gem "test-unit"
    require "test/unit"
    require "watir-webdriver"
     
    # check arguments for browser or headless specification
    ARGV.each { |arg|
        if arg.downcase.include? 'chrome'
            $browser = 'chrome'
        elsif arg.downcase.include? 'firefox'
            $browser = 'firefox'
        elsif arg.downcase.include? 'ff'
            $browser = 'firefox'
        elsif arg.downcase.include? 'ie'
            $browser = 'ie'
        elsif arg.downcase.include? 'headless'
            $headless = true
        end}
     
    module Test
      module Unit
        class TestSuite
          alias :old_run :run
          def run(result, &progress_block)
            old_run(result, &progress_block)
            File.open('errors.log', 'w'){|f|
              result.faults.each{|err|
                case err
                  when Test::Unit::Error, Test::Unit::Failure
                    f << err.test_name
                    f << "\n"
                  #not in log file
                  when Test::Unit::Pending, Test::Unit::Notification, Test::Unit::Omission
                  end
              }
            }
          end
        end
      end
    end
     
    class TestExample < Test::Unit::TestCase
      # setup is run before every test
      def setup
        $browser = 'chrome' if $browser.nil?
        $site = 'http://test.localhost' if $site.nil?
     
        if $headless
          require 'headless'
          $headless = Headless.new
          $headless.start
        end
        if $browser == 'chrome'
          $b = Watir::Browser.new :chrome
        elsif $browser == 'firefox'
          $b = Watir::Browser.new :ff
        elsif $browser == 'ie'
          $b = Watir::Browser.new :ie
        end
     
        $timeout_length = 30
     
        load_link($timeout_length){ $b.goto $site }
      end
     
      # teardown is run after every test
      def teardown
        # take screenshot of end of test, useful for failures/errors
        time = Time.new
        $b.driver.save_screenshot(File.dirname(__FILE__) + '/screenshots/' + @method_name + '_' + time.strftime('%Y%m%d_%H%M%S') + '.png');
        $b.close
        if $headless
            $headless.destroy
        end
      end
     
      def browse_to_new_project
        load_link($timeout_length){ $b.goto $site + "/designtourney/projects/new" }
      end
     
      def click_logo_design
        load_link($timeout_length){ $b.link(:class, 'logo-design').click }
      end
     
      def form_fill_first_page
        $b.text_field(:name, 'organization_name').set('Magic/More Magic')
        $b.text_field(:name, 'question_38').set('As mentioned above, we make magic and more magic.')
        $b.text_field(:name, 'question_39').set('People who like magic and more magic, as opposed to less magic.')
        $b.link(:id=> 'show-more').click
        $b.text_field(:name, 'question_41').set('Im putting stuff into question 41')
        $b.text_field(:name, 'question_45').set('Im putting stuff into question 45')
      end
     
      def first_page_asserts type = 'regular'
        assert_equal 'Magic/More Magic', $b.text_field(:name, 'organization_name').value
        assert_equal 'As mentioned above, we make magic and more magic.', $b.text_field(:name, 'question_38').value
        assert_equal 'People who like magic and more magic, as opposed to less magic.', $b.text_field(:name,'question_39').value
        assert_equal 'Im putting stuff into question 41', $b.text_field(:name,'question_41').value
      end
     
      def wait_for_ajax
        $b.div(:id, 'ajax-loader').wait_while_present
      end
     
      def load_link(waittime)
        begin
          Timeout::timeout(waittime)  do
          yield
        end
        rescue Timeout::Error => e
          puts "Page load timed out: #{e}"
          retry
        end
      end
     
      def test_save_for_later
        browse_to_new_project
     
        click_logo_design
     
        form_fill_first_page
        $b.link(:class, 'save').click
        wait_for_ajax
     
        assert_true $b.div(:id, 'fallr').visible?
     
        browse_to_new_project
     
        $b.div(:id, 'fallr').wait_until_present
        $b.wait_until{ $b.execute_script('return $(\'#fallr-wrapper\').is(\':animated\')') == false }
        sleep 0.5
        $b.link(:id, 'fallr-button-yes').click
        $b.div(:id, 'fallr-overlay').wait_while_present
     
        # These assertions make sure the stuff for the first page is still all there
        first_page_asserts
      end
    end
  • Watir Webdriver处理新pop up的窗口

    2013-01-27 16:54:08

    其实很简单,就是使用该窗口即可,就是切换activity而已。

    下面的实例就是,切换到新的弹出窗口:annoying popup上,然后关闭这个窗口

    browser.window(:title => "annoying popup").use do
      browser.button(:id => "close").click
    end

    更多的操作方式是在windows switch里描述的:


    通过URL来定位窗口, 下例url为'/closable.html'
    w = browser.window(:url => /closeable\.html/).use


    通过title来定位:
    w = browser.window(:title => "closeable window").use

    通过index定位:
    w = browser.window(:index => 1).use

    关闭窗口:
    browser.window(:title => "closeable window").close

    返回title
    titles = browser.windows.map { |e| e.title }
    titles.size.should == 2
    titles.sort.should == ["window switching", "closeable window"].sort


  • Watir Webdriver对下载的处理

    2013-01-27 16:41:03

    Watir Webdriver可以对下载进行处理,最简单的方式不是对其进行管理,而是沉默处理,压根不弹出窗口才最合适。

    在ff中,处理的方式是:
    download_directory = "#{Dir.pwd}/downloads"
    download_directory.gsub!("/", "\\") if Selenium::WebDriver::Platform.windows?
     
    profile = Selenium::WebDriver::Firefox::Profile.new
    profile['browser.download.folderList'] = 2 # custom location
    profile['browser.download.dir'] = download_directory
    profile['browser.helperApps.neverAsk.saveToDisk'] = "text/csv,application/pdf"
     
    b = Watir::Browser.new :firefox, :profile => profile

    这样,所有的下载文件直接就到指定的文件夹里,而且是隐式处理,同理chrome也可以这样处理:

    download_directory = "#{Dir.pwd}/downloads"
    download_directory.gsub!("/", "\\") if  Selenium::WebDriver::Platform.windows?
     
    profile = Selenium::WebDriver::Chrome::Profile.new
    profile['download.prompt_for_download'] = false
    profile['download.default_directory'] = download_directory
     
    b = Watir::Browser.new :chrome, :profile => profile


    获取更多的option信息,ff可以在空白页面里查看:about:config

  • Watir Webdriver对browser的certificate的操作

    2013-01-27 16:26:35

    对于弹出非trust的certificate进行处理

    ie,可以当alert窗口进行直接处理

    对于ff来说,直接屏蔽即可

    profile = Selenium::WebDriver::Firefox::Profile.new
    profile.assume_untrusted_certificate_issuer = false
    b = Watir::Browser.new :firefox, :profile => profile


    同理 chrome也可以屏蔽,不做验证

    Watir::Browser.new :chrome, :switches => ['--ignore-certificate-errors']


  • Watir WebDriver对于用户验证的处理

    2013-01-27 16:13:53

    有一些网站,在访问的时候,就会先弹出一个验证窗口,要求你输入用户名和密码。

    一般来说,常见的网站都是采用默认的验证方式,也就是说,你只需要在URL里附带user/passwod就行,例如:

    require 'watir-webdriver'
    b = Watir::Browser.new :firefox
    b.goto 'http://admin:password@192.168.0.1'

    但是,有些网站采用更严格的验证方式,比如NTLM,这时候需要验证的是proxy。

    简单的快速解决方法,就是手工的进入该网站一次,这样验证方式就存在当前的profile里了,对于ff来说,就直接使用默认的profile即可:

    require 'watir-webdriver'
    b = Watir::Browser.new :firefox, :profile => 'default'
    b.goto 'http://192.168.0.1'

    当然复杂的也不会太复杂,就安装一个addon就可以解决:

    profile = Selenium::WebDriver::Firefox::Profile.from_name 'WatirWebDriver'
    profile.add_extension 'autoauth-2.1-fx+fn.xpi'
    b = Watir::Browser.new :firefox, :profile => profile
    b.goto 'http://192.168.0.1'


    总之,基本策略就是:

    1. 使用ff的profile manager创建一个profile
    2. 把需要的验证信息,加入profile
    3. 在测试中,指定使用这个profile
    4. 添加AutoAuth插件,一劳永逸

    这个方法 目前也就对ff和chrome可用,ie的话,手工配置吧
  • Watir WebDriver的wait应用

    2013-01-27 15:59:00

    watir提供的wait方法有:

    Watir::Wait.until { ... }:  
    等待你指定的block出现变为true

    object.when_present.set:    
    当对象出现时,你可以做点你set的什么

    object.wait_until_present:  
    纯粹的等待对象的出现,什么都不干

    object.wait_while_present:
    纯的等待直到对象消失


    对于动态页面来说,尤其是有很多Ajax代码的页面,需要使用wait来进行等待,但是watir的wait是等待页面完成之后就结束了,实际上Ajax很多代码要在页面load之后还在加载,这样的话,就导致等待无效,真正有效的是Wait_while_present

    我们可以自己添加一个简单的方法进行处理:

    def wait_for_ajax
      browser.div(:id, 'ajax-loader').wait_while_present
    end

    默认的等待时间是30秒,你可以通过参数去设定时间长度:

    b.select_list(:id => 'entry_1').wait_until_present(100)
    等待一百秒

    下面是一些简单的例子

    require 'watir-webdriver'
    b = Watir::Browser.start 'bit.ly/watir-webdriver-demo'
    b.select_list(:id => 'entry_1').wait_until_present
    b.text_field(:id => 'entry_0').when_present.set 'your name'
    b.button(:value => 'Submit').click
    b.button(:value => 'Submit').wait_while_present
    Watir::Wait.until { b.text.include? 'Thank you' }


    你甚至可以使用 Implicit waits 来设置最长的等待时间,例如:
    require 'watir-webdriver'
    b = Watir::Browser.new
    b.driver.manage.timeouts.implicit_wait = 3 #3 seconds

    implicit wait的最长的等待时间意味着3秒之内,只要找到对象,就不再等待,直接进入下一步,而wait,要一直等到规定的时间超时才进行下一步。
1464/8<12345678>
Open Toolbar