发布新日志

  • 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,要一直等到规定的时间超时才进行下一步。
  • Watir WedDriver支持的浏览器操作

    2013-01-27 15:26:02

    Watir Webdriver的工作方式是:

    Watir 脚本 -> Browser Driver -> Browser ->Screen (or headless)

    目前支持的Browser Driver 有IEDriver, ChromeDriver, FFDriver,SafariDriver, HeadlessDriver

    我们来看一下对各个browser的支持:

    Chrome

    标准创建
    browser = Watir::Browser.new :chrome

    带profile的创建,下例为设置不弹出download窗口,并设置下载默认路径

    profile = Selenium::WebDriver::Chrome::Profile.new
    profile['download.prompt_for_download'] = false
    profile['download.default_directory'] = "/path/to/dir"

    browser = Watir::Browser.new :chrome, :profile => profile

    带switch的创建,下例为忽略证书错误,关闭popup窗口弹出,禁止自动翻译
    browser = Watir::Browser.new :chrome, :switches => %w[--ignore-certificate-errors --disable-popup-blocking --disable-translate]

    设置代理
    browser = Watir::Browser.new :chrome, :switches => %w[--proxy-server=myproxy.com:8080]

    Firefox

    标准创建
    browser = Watir::Browser.new :firefox

    使用默认的profile
    browser = Watir::Browser.new :firefox, :profile => 'default'

    设置所需的profile
    profile = Selenium::WebDriver::Firefox::Profile.new
    profile['browser.download.dir'] = "/tmp/webdriver-downloads"
    profile['browser.download.folderList'] = 2
    profile['browser.helperApps.neverAsk.saveToDisk'] = "application/pdf"
     
    browser = Watir::Browser.new :firefox, :profile => profile

    禁止native event,该方法主要是window使用默认的一个较低level的交互方法来与webdrive进行交互,有时候会引起莫名的错误,可以禁止:
    profile = Selenium::WebDriver::Firefox::Profile.new
    profile.native_events = false
    browser = Watir::Browser.new :firefox, :profile => profile

    设置代理服务
    profile = Selenium::WebDriver::Firefox::Profile.new
    profile.proxy = Selenium::WebDriver::Proxy.new :http => 'myproxy.com:8080:, :ssl => 'myproxy.com:8080'

    browser = Watir::Browser.new :firefox, :profile => profile

    设置使用addon,例如:firebug
    profile = Selenium::WebDriver::Firefox::Profile.new
    profile.add_extension "../path/to/firebug.xpi"

    browser = Watir::Browser.new :firefox, :profile => profile

    IE

    标准创建
    browser = Watir::Browser.new :firefox

    IE使用默认配置,也就是说,你必须手工配置IE来满足你的需要。

    Safari

    标准创建
    browser = Watir::Browser.new :safari

    safari现在支持还不够完善,有待进一步更新。

    Headless

    Headless不是浏览器,他是ruby对Xvfb的封装,从而使得能headless的linux上运行一个图形界面程序。

    Xvfb or X virtual framebuffer 是一种将所有的图形化操作在内存中隐式运行,而不是在屏幕上显式运行的一种服务器。

    举个简单的例子
    require 'watir-webdriver'
    require 'headless'

    headless = Headless.new
    headless.start

    browser = Watir::Browser.start 'www.google.com'
    puts b.title
    b.close

    headless.destroy

    通过先生成headless,我们就能在非图形界面的操作系统上,运行一个图形界面的程序。


  • Watir Webdriver的mobile测试

    2013-01-27 14:52:21

    实际上,Watir Webdriver可以测试的仍然是Site,不是APP,可以用于测试mobile device访问的mobile site的方式有三种:

    1. 在一个真实的设备上,利用内置的browser进行测试
    2. 在一个仿真的设备上,利用内置的browser进行测试
    3. 在pc机上,将browser设置为mobile browser进行测试

    对于在真实设备上进行测试,速度会相对比较慢,而且iOS还需要额外的费用。

    而且还需要在iOs和Android的设备上先安装相应的driver。

    所以最简单的方法,就是利用webdriver-user-agent gem 进行模拟测试

    方法如下:

    require 'watir-webdriver'
    require 'webdriver-user-agent'
    driver = UserAgent.driver(:browser => :chrome, :agent => :iphone, :orientation => :landscape)
    browser = Watir::Browser.new driver
    browser.goto 'tiffany.com'
    browser.url.should == 'http://m.tiffany.com/International.aspx'


    我们可以看到,最关键的就是:

    driver = UserAgent.driver(:browser => :chrome, :agent => :iphone, :orientation => :landscape)

    这个步骤,指明了你要使用的浏览器类型,仿真器类型,和视图方式。

    该gem可以支持的浏览器有ff和chrome
    可以支持的仿真器类型有:iphone, ipad, android_phone, 和android_tablet
    支持的视图方式有两种,水平和竖直。 (portrait and landscape)

  • Selenium Webdriver和Watir Webdriver的详细对比

    2013-01-27 14:33:13

    我们来看一些具体的API,来对Selenium的WebDriver和Watir的Webdirver进行更加直观的了解


    1. 生成一个新的browser

    Selenium:

    driver=Selenium::WebDriver.for:firefox 

    Watir:

    driver=Watir::Browser.new:firefox


    2. 转向指定的页面

    Selenium:

    driver.get 'http://www.baidu.com'

    Watir:

    driver.goto 'http://www.baidu.com'


    3. 查找元素

    Selenium:

    element = driver.find_element(:id, "coolestWidgetEvah")

    Watir:

    element = driver.element(:id, "coolestWidgetEvah")


    4. 执行一段js代码

    Selenium:

    element = driver.execute_script("return $('.cheese')[0]")

    Watir:

    element = driver.fire_event("return $('.cheese')[0]")

    5. Alert 操作

    Selenium:

    alert = driver.switch_to.alert
    alert.ok

    Watir:

    browser.alert.ok


  • Selenium Web Driver 和 Watir Web Driver

    2013-01-27 14:19:25

    Watir Webdriver不仅仅是Watir的升级,实际上它是对Selenium WebDriver的API进行的二次封装。从而使得其API能够更好的符合Ruby语言的规范,和更加简单明确的方法应用。

    可以这么说,如果使用Ruby进行WebDriver开发,Watir WebDriver更加好用,更加简洁,更加利于新手的学习。 

     我们看一下Watir Webdriver和Selenium Webdriver的一个简单例子,从而更好的理解封装后的简便化:

    Selenium WebDriver

    require 'rubygems' 
    require 'selenium-webdriver' 
    driver = Selenium::WebDriver.for :firefox 
    driver.get "http://google.com" 
    element = driver.find_element :name => "q" 
    element.send_keys "Cheese!" 
    element.submit

    Watir WebDriver

    require 'rubygems' 
    require 'watir-webdriver' 
    driver = Watir::Browser.new:firefox 
    driver.goto "http://google.com" 
    element = driver.element(:name => "q") 
    element.send_keys "Cheese!" 
    element.submit

    从上面的语法可知,我们能从Watir Driver中明确的知道,我们要创建一个Browser的实例,指定browser的type。比selenium的构造方法更加明了。

    我们转向到想去的页面,goto比get更加容易理解。

    我们根据element的name在页面上查找,可以随时改变属性为:id,:value等等,更加便于我们二次封装。

    其他的基本上类似。所以说,在功能一致的情况下,一个更加便于阅读和理解的代码,对于初学者来说 更加容易去掌握,这更加符合ruby语言当初的初衷:让学习开发语言不再痛苦。
  • Selenium 2 简介

    2013-01-27 14:17:09

    selenium2 包括两部分:Selenium IDE和Selenium WebDriver。

    Selenium IDE作为Firefox的一个插件,以录制-回放的方式进行工作。主要是用来快速创建一个bug的回归测试脚本,

    或者辅助自动化测试来进行一下探索性测试。

    而Selenium WebDriver主要是用于创建一个强大而稳定的基于浏览器的回归测试的测试集。 甚至能够进行多种测试环境

    中进行分布式测试。

    Selenium WebDriver是RC的继承者,虽然在Selenium2中,我们仍然可以使用Rc,但是实际上,RC已经被正式的宣布不再

    进行更新。我们可以把Selenium2看作是如下三者的集合体:已有的RC+新的WebDriverAPI+二者共同使用的Selenium 

    Server。

    而且对于WebDriver和RC共同使用的Selenium Server在Selenium2中已经内置了网格支持。

    Selenium WebDriver的革新之处就是提供了一套WebDriver的API,从而使得我们可以在本地服务器或者远程服务器上像

    真实用户那样对浏览器进行操作。

    Selenium WebDriver的出现的目的是为了解决RC的一些限制。从而能够更好的支持动态页面,例如在不重载页面就更新

    了该页面一些元素的情况。

    Selenium WebDriver是通过直接调用浏览器的自有方法来达到驱动浏览器的目的。而RC的方式是将javaScript代码“注入

    ”到当前的浏览器中,当浏览器加载时,就将“注入”的javascript代码同时加载,从而这些“注入”的javascript驱动浏览

    器进行各种行为。简而言之,webdriver是直接驱动浏览器本身,RC是通过我们植入的代码,间接的驱动浏览器。

    如果你的Webdriver目的就是对一套固定的测试环境进行测试,是不需要使用Selenium Server的。只有在如下的情况下

    ,才会使用:
    1. 使用selenium-Grid将你的测试用例分布在不同的测试环境中。
    2. 你需要连接一个remote server来使用你当前环境下没有的浏览器。
    3. 你不想使用Java bindings(Python,C#或者Ruby),而是想使用HTMLUnitDriver的时候。
  • Cucumber的tags

    2013-01-25 13:55:01

    cucumber提供了一个简单有效的标志方法来帮你更好的整理你的测试流程

    使用起来很简单,只需要在代码行上加上你想要的标志,例如 

    在login.feature里添加@init, @major, @authenticate:
     @init
      Feature:  .  .  .
      .  .  .
      @major @authenticate
      Scenario:  A user should authenticate before accessing any resource.
         Given I do have a user named "testuser"
          When the user visits the login page
             And the user named "testuser" authenticates successfully
         Then I should see .  .  .
             .   .  .
    比如,我们想做一个初始化的流程的测试,只需要运行那些带有@init的feature即可:
    $ cucumber --profile=my_profile --tags=@init 
    也可以用:
    $ rake cucumber:init

    或者指定功能区域的authenticate相关的场景
    $ cucumber --profile=my_profile --tags=@authenticate features/login


    或者指定的功能级别:
    $ cucumber --tags=@major #没有指定profile的时候,会使用默认的profile

    对于rake的方式来说,想使用指定的tag,必须在config/cucumber.yml中预先定义才行。

    我们也可以指定执行@tag的index,例如下例就是指执行拥有第三个@major标签的场景
    $ cucumber --tags=@major:3 features/log

    我们可以使用反选符号~,例如下例就是指执行除了含有@major标签的场景之外的所有场景

    $ cucumber --tags=~@major features/log

  • Cucumber的 Step Definitions

    2013-01-25 13:09:09

    Cucumber的step deifne过程,就是通过正则表达式,将feature里的文本步骤转义为可执行的代码。

    好的步骤定义的标准是:

    匹配串尽可能的短
    最好能同时匹配正向和反向的条件
    一个步骤最多两个匹配参数
    用于匹配的参数名称最好简单明确
    每个步骤最好不要超过10行的处理,否则进行二次封装
    最好不要去引用其他的步骤


    在step define文件中,没有次序的概念,Given,When,Then可以任意行定义。

    甚至结构也可以是嵌套的, 例如:

    Given /some "(.*)" action/  do |act|
       .  .  .
    end

    Then /some "(.*)" action/  do |act|
       .  .  .
    end

    When /in an invoiced non-shipped situation/ do
      Given "some \"invoiced\" action" 
      Then "some \"non-shipped\" action"
      .  .  .
    end

    我们在上例可以看到, 在when里直接包含了已经预先定义好的Given和Then,这是允许的。但是最好不要这么做。

    事实上我们为了避免这种看起来很混淆的代码,我们一般用step来取代这些关键词,上面的例子就变为了:

    When /in an invoiced non-shipped situation/ do
      step("some \"invoiced\" action") 
      step %Q("some \"non-shipped\" action")
      .  .  .
    end


    %Q是用来去除多余的“符号

    对于多个step,我们可以用steps块来定义, 上例就变为:

    When /in an invoiced non-shipped situation/ do
      steps %Q{
      Given "some \"invoiced\" action" 
      Then "some \"non-shipped\" action"
      .  .  .
      }
    end



  • Cucumber的关键字

    2013-01-25 12:44:59

    Cucumber的关键字有如下

         Feature 功能
         Background 背景
         Scenario 场景
         Scenario outline 场景大纲
         Example 例子

    Given 假如
    And   而且
    ....
    Then  当
    And   并且
    ...  
    But  但是
    And  并且
    ...
    Then 那么
    And  并且
    ...

    我们在Step里面没有对应feature, Scenario, Scenario outline, example的匹配定义。

    对应background的是:
    Before do
    end

    After do
    end

    Before会在所有的场景前执行,After会在所有的场景后执行


    Given对应的是“假如”(Given)和“并且”(Given后的And)

    Given / / do |action|

    end

    But对应的是“但是”(But)和“而且”(But后的And)

    But / / do |action|

    end

    When对应的是“当”(When)和“而且”(When后的And)

    When // do |op|

    end

    When对应的是“那么”(Then)和“而且”(Then后的And)

    Then // do |result|
     
    end

  • Cucumber的目录结构

    2013-01-25 12:26:35

    Cucumber的目录结构很简单,我们举个简单的例子:

    features
    ├── step_definitions
    └── support
        └── env.rb
    你所定义的feature文件位于features目录下,你可以定义多个feature文件,例如一个user story一个。

    在step_definintions目录下,定义steps的map文件

    在lib目录下,定义map用的自动化测试脚本

    然后support目录下的env.rb,用于在执行feature前,进行你的环境初始化,例如,生成一个@browser=Watir::Browser.new:chrome. 并且初始化一些全局变量。


    实际上,我们能够用更复杂的方式来组织我们的目录结构,只要你保证所有的文件都在features文件夹里,任意指定即可。

    例如,使用功能区域组织我们的目录结构:

    |-- features
    |   |-- entities
    |   |   |-- entity.feature
    |   |   `-- step_definitions
    |   |       |-- anything.rb
    |   |       `-- entity_steps.rb
    |   |-- locations
    |   |   |-- location.feature
    |   |   `-- step_definitions
    |   |       `-- location_steps.rb
    |   |-- sites
    |   |   `-- step_definitions
    |   |-- step_definitions
    |   |   |-- local_assert_steps.rb
    |   |   |-- local_crud_response_steps.rb
    |   |   |-- local_email_steps.rb
    |   |   |-- local_file_steps.rb
    |   |   |-- local_script_steps.rb
    |   |   |-- local_steps.rb
    |   |   |-- local_web_steps.rb
    `   |   `-- local_xml_file_steps.rb   
        `-- support
            |-- env.rb
            |-- local_env.rb
            `-- local_transforms.rb

    或者用模块层次组织我们的目录结构:

    |-- features
    |   |-- models
    |   |   `-- entities
    |   |       |-- entity.feature
    |   |       `-- step_definitions
    |   |           |-- anything.rb
    |   |           `-- entity_steps.rb
    |   |-- views
    |   |   |-- entity_new
    |   |   `-- step_definitions
    |   |       `-- entity_new_steps.rb
    |   |-- step_definitions
    `   |   `-- local_steps.rb
        `-- support
            |-- env.rb
            |-- local_env.rb
            `-- local_transforms.rb

1826/10<12345678910>
Open Toolbar