Class: Vapir::Firefox

Inherits:
Browser
  • Object
show all
Includes:
Firefox::Window, ModalDialogContainer, PageContainer
Defined in:
lib/vapir-firefox/firefox.rb,
lib/vapir-firefox/version.rb

Defined Under Namespace

Modules: Container, ModalDialogContainer, PageContainer, RadioCheckboxCommon Classes: Area, Button, CheckBox, Dd, Div, Dl, Dt, Em, FileField, Form, Frame, H1, H2, H3, H4, H5, H6, Hidden, Image, InputElement, Label, Li, Link, Map, ModalDialog, ModalDialogDocument, Ol, Option, P, Pre, Radio, SelectList, Span, Strong, TBody, Table, TableCell, TableRow, TextField, Ul

Constant Summary collapse

VERSION =
'1.7.0'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ModalDialogContainer

#modal_dialog, #modal_dialog!

Methods included from PageContainer

#containing_object, #document_element, #execute_script, #html, #page_container, #title, #url

Methods included from Container

#element_by_xpath, #element_object_by_xpath, #element_objects_by_xpath, #elements_by_xpath, #extra_for_contained

Constructor Details

#initialize(options = {}) ⇒ Firefox

Description:

Starts the firefox browser. 
On windows this starts the first version listed in the registry.

Input:

options  - Hash of any of the following options:
  :wait_time - Time to wait for Firefox to start. By default it waits for 2 seconds.
              This is done because if Firefox is not started and we try to connect
              to jssh on port 9997 an exception is thrown.
  :profile  - The Firefox profile to use. If none is specified, Firefox will use
              the last used profile.
  :suppress_launch_process - do not create a new firefox process. Connect to an existing one.

TODO: Start the firefox version given by user.



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/vapir-firefox/firefox.rb', line 139

def initialize(options = {})
  if(options.kind_of?(Integer))
    options = {:wait_time => options}
    Kernel.warn "DEPRECATION WARNING: #{self.class.name}.new takes an options hash - passing a number is deprecated. Please use #{self.class.name}.new(:wait_time => #{options[:wait_time]})\n(called from #{caller.map{|c|"\n"+c}})"
  end
  options=handle_options(options, {:wait_time => 20}, [:attach, :goto, :binary_path])
  if options[:binary_path]
    @binary_path=options[:binary_path]
  end
  
  # check for jssh not running, firefox may be open but not with -jssh
  # if its not open at all, regardless of the :suppress_launch_process option start it
  # error if running without jssh, we don't want to kill their current window (mac only)
  begin
    jssh_socket(:reset_if_dead => true).assert_socket
  rescue JsshError
    # here we're going to assume that since it's not connecting, we need to launch firefox. 
    if options[:attach]
      raise Vapir::Exception::NoBrowserException, "cannot attach using #{options[:attach].inspect} - could not connect to Firefox with JSSH"
    else
      launch_browser
      # if we just launched a the browser process, attach to the window
      # that opened when we did that. 
      # but if options[:attach] is explicitly given as false (not nil), 
      # take that to mean we don't want to attach to the window launched 
      # when the process starts. 
      unless options[:attach]==false
        options[:attach]=[:title, //]
      end
    end
    ::Waiter.try_for(options[:wait_time], :exception => Vapir::Exception::NoBrowserException.new("Could not connect to the JSSH socket on the browser after #{options[:wait_time]} seconds. Either Firefox did not start or JSSH is not installed and listening.")) do
      begin
        jssh_socket(:reset_if_dead => true).assert_socket
        true
      rescue JsshUnableToStart
        false
      end
    end
  end
  @browser_jssh_objects = jssh_socket.object('{}').store_rand_object_key(@@firewatir_jssh_objects) # this is an object that holds stuff for this browser 
  
  if options[:attach]
    attach(*options[:attach])
  else
    open_window
  end
  set_browser_document
  set_defaults
  if options[:goto]
    goto(options[:goto])
  end
end

Instance Attribute Details

#body_objectObject (readonly)

Returns the value of attribute body_object.



360
361
362
# File 'lib/vapir-firefox/firefox.rb', line 360

def body_object
  @body_object
end

#browser_objectObject (readonly)

Returns the value of attribute browser_object.



358
359
360
# File 'lib/vapir-firefox/firefox.rb', line 358

def browser_object
  @browser_object
end

#browser_window_objectObject (readonly)

Returns the value of attribute browser_window_object.



356
357
358
# File 'lib/vapir-firefox/firefox.rb', line 356

def browser_window_object
  @browser_window_object
end

#content_window_objectObject (readonly)

Returns the value of attribute content_window_object.



357
358
359
# File 'lib/vapir-firefox/firefox.rb', line 357

def content_window_object
  @content_window_object
end

#document_objectObject (readonly)

Returns the value of attribute document_object.



359
360
361
# File 'lib/vapir-firefox/firefox.rb', line 359

def document_object
  @document_object
end

Class Method Details

.attach(how, what) ⇒ Object

Class method to return a browser object if a window matches for how and what. Window can be referenced by url or title. The second argument can be either a string or a regular expression. Vapir::Browser.attach(:url, ‘www.google.com’) Vapir::Browser.attach(:title, ‘Google’)



447
448
449
# File 'lib/vapir-firefox/firefox.rb', line 447

def self.attach how, what
  new(:attach => [how, what])
end

.browser_window_objectsObject



482
483
484
485
486
487
488
# File 'lib/vapir-firefox/firefox.rb', line 482

def self.browser_window_objects
  window_objects=[]
  each_browser_window_object do |window_object|
    window_objects << window_object
  end
  window_objects
end

.eachObject



467
468
469
470
471
# File 'lib/vapir-firefox/firefox.rb', line 467

def self.each
  each_browser_window_object do |win|
    yield self.attach(:jssh_object, win)
  end
end

.each_browser_window_objectObject



473
474
475
476
477
478
479
480
481
# File 'lib/vapir-firefox/firefox.rb', line 473

def self.each_browser_window_object
  mediator=jssh_socket.Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(jssh_socket.Components.interfaces.nsIWindowMediator)
  enumerator=mediator.getEnumerator("navigator:browser")
  while enumerator.hasMoreElements
    win=enumerator.getNext
    yield win
  end
  nil
end

.each_window_objectObject



489
490
491
492
493
494
495
496
497
# File 'lib/vapir-firefox/firefox.rb', line 489

def self.each_window_object
  mediator=jssh_socket.Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(jssh_socket.Components.interfaces.nsIWindowMediator)
  enumerator=mediator.getEnumerator(nil)
  while enumerator.hasMoreElements
    win=enumerator.getNext
    yield win
  end
  nil
end

.initialize_jssh_socketObject



100
101
102
103
104
105
106
107
108
# File 'lib/vapir-firefox/firefox.rb', line 100

def self.initialize_jssh_socket
  # if it already exists and is not nil, then we are clobbering an existing one, presumably dead. but a new socket will not have any objects of the old one, so warn 
  if class_variable_defined?('@@jssh_socket') && @@jssh_socket 
    Kernel.warn "WARNING: JSSH_SOCKET RESET: resetting jssh socket. Any active javascript references will not exist on the new socket!"
  end
  @@jssh_socket=JsshSocket.new
  @@firewatir_jssh_objects=@@jssh_socket.object("Vapir").assign({})
  @@jssh_socket
end

.jssh_socket(options = {}) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/vapir-firefox/firefox.rb', line 109

def self.jssh_socket(options={})
  if options[:reset] || !(class_variable_defined?('@@jssh_socket') && @@jssh_socket)
    initialize_jssh_socket
  end
  if options[:reset_if_dead]
    begin
      @@jssh_socket.assert_socket
    rescue JsshConnectionError
      initialize_jssh_socket
    end
  end
  @@jssh_socket
end

.start(url) ⇒ Object

Creates a new instance of Firefox. Loads the URL and return the instance. Input:

url - url of the page to be loaded.


231
232
233
# File 'lib/vapir-firefox/firefox.rb', line 231

def self.start(url)
  new(:goto => url)
end

.window_objectsObject



498
499
500
501
502
503
504
# File 'lib/vapir-firefox/firefox.rb', line 498

def self.window_objects
  window_objects=[]
  each_window_object do |window_object|
    window_objects << window_object
  end
  window_objects
end

Instance Method Details

#add_checker(checker) ⇒ Object

Add an error checker that gets called on every page load.

  • checker - a Proc object



607
608
609
# File 'lib/vapir-firefox/firefox.rb', line 607

def add_checker(checker)
  @error_checkers << checker
end

#attach(how, what) ⇒ Object

Used for attaching pop up window to an existing Firefox window, either by url or title.

ff.attach(:url, 'http://www.google.com')
ff.attach(:title, 'Google')

Output:

Instance of newly attached window.


427
428
429
430
431
432
433
434
435
436
437
438
439
440
# File 'lib/vapir-firefox/firefox.rb', line 427

def attach(how, what)
  @browser_window_object = case how
  when :jssh_object
    what
  else
    find_window(how, what)
  end
  
  unless @browser_window_object
    raise Exception::NoMatchingWindowFoundException.new("Unable to locate window, using #{how} and #{what}")
  end
  set_browser_document
  self
end

#backObject

Loads the previous page (if there is any) in the browser. Waits for the page to get loaded.



244
245
246
247
248
249
250
251
# File 'lib/vapir-firefox/firefox.rb', line 244

def back
  if browser_object.canGoBack
    browser_object.goBack
  else
    raise Vapir::Exception::NavigationException, "Cannot go back!"
  end
  wait
end

#browserObject



204
205
206
# File 'lib/vapir-firefox/firefox.rb', line 204

def browser
  self
end

#closeObject

Closes the window.



368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/vapir-firefox/firefox.rb', line 368

def close
  assert_exists
  begin
    browser_window_object.close
    # TODO/fix timeout; this shouldn't be a hard-coded magic number. 
    ::Waiter.try_for(32, :exception => Exception::WindowFailedToCloseException.new("The browser window did not close")) do
      !exists?
    end
    @@jssh_socket.assert_socket
  rescue JsshConnectionError # the socket may disconnect when we close the browser, causing the JsshSocket to complain 
    @@jssh_socket=nil
  end
  @browser_window_object=@browser_object=@document_object=@content_window_object=@body_object=nil
@launched_browser_process=false #TODO/FIX: check here if we originally launched the browser process
  if @launched_browser_process && @@jssh_socket
    quit_browser(:force => false)
  end
end

#close_allObject

Closes all firefox windows by quitting the browser



388
389
390
# File 'lib/vapir-firefox/firefox.rb', line 388

def close_all
  quit_browser(:force => false)
end

#disable_checker(checker) ⇒ Object

Disable an error checker

  • checker - a Proc object that is to be disabled



613
614
615
# File 'lib/vapir-firefox/firefox.rb', line 613

def disable_checker(checker)
  @error_checkers.delete(checker)
end

#exists?Boolean

Returns:

  • (Boolean)


208
209
210
211
# File 'lib/vapir-firefox/firefox.rb', line 208

def exists?
  # jssh_socket may be nil if the window has closed 
  jssh_socket && browser_window_object && jssh_socket.object('getWindows()').to_js_array.include(browser_window_object)
end

#forwardObject

Loads the next page (if there is any) in the browser. Waits for the page to get loaded.



254
255
256
257
258
259
260
261
# File 'lib/vapir-firefox/firefox.rb', line 254

def forward
  if browser_object.canGoForward
    browser_object.goForward
  else
    raise Vapir::Exception::NavigationException, "Cannot go forward!"
  end
  wait
end

#goto(url) ⇒ Object

Loads the given url in the browser. Waits for the page to get loaded.



237
238
239
240
241
# File 'lib/vapir-firefox/firefox.rb', line 237

def goto(url)
  assert_exists
  browser_object.loadURI url
  wait
end

#jssh_socket(options = nil) ⇒ Object



122
123
124
# File 'lib/vapir-firefox/firefox.rb', line 122

def jssh_socket(options=nil)
  options ? self.class.jssh_socket(options) : @@jssh_socket
end

#maximizeObject

Maximize the current browser window.



543
544
545
# File 'lib/vapir-firefox/firefox.rb', line 543

def maximize()
  browser_window_object.maximize
end

#minimizeObject

Minimize the current browser window.



548
549
550
# File 'lib/vapir-firefox/firefox.rb', line 548

def minimize()
  browser_window_object.minimize
end

#mozilla_window_class_nameObject

true

end
def firefox_is_running?
  self.class.firefox_is_running?
end


200
201
202
# File 'lib/vapir-firefox/firefox.rb', line 200

def mozilla_window_class_name
  'MozillaUIWindowClass'
end

#quit_browser(options = {}) ⇒ Object

quits the browser. quit_browser(:force => true) will force the browser to quit.



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# File 'lib/vapir-firefox/firefox.rb', line 394

def quit_browser(options={})
  options=handle_options(options, :force => false)
  # from https://developer.mozilla.org/en/How_to_Quit_a_XUL_Application
  appStartup= jssh_socket.Components.classes['@mozilla.org/toolkit/app-startup;1'].getService(jssh_socket.Components.interfaces.nsIAppStartup)
  quitSeverity = options[:force] ? jssh_socket.Components.interfaces.nsIAppStartup.eForceQuit : jssh_socket.Components.interfaces.nsIAppStartup.eAttemptQuit
  begin
    appStartup.quit(quitSeverity)
    ::Waiter.try_for(8, :exception => Exception::WindowFailedToCloseException.new("The browser did not quit")) do
      @@jssh_socket.assert_socket # this should error, going up past the waiter to the rescue block above 
      false
    end
  rescue JsshConnectionError
    @@jssh_socket=nil
  end
  # TODO/FIX: poll to wait for the process itself to finish? the socket closes (which we wait for 
  # above) before the process itself has exited, so if Firefox.new is called between the socket 
  # closing and the process exiting, Firefox pops up with:
  #  Close Firefox
  #  A copy of Firefox is already open. Only one copy of Firefox can be open at a time.
  #  [OK]
  # until that's implemented, just wait for an arbitrary amount of time. (ick)
  sleep 2

  @browser_window_object=@browser_object=@document_object=@content_window_object=@body_object=nil
  nil
end

#refreshObject

Reloads the current page in the browser. Waits for the page to get loaded.



264
265
266
267
# File 'lib/vapir-firefox/firefox.rb', line 264

def refresh
  browser_object.reload
  wait
end

#run_error_checksObject

Run the predefined error checks. This is automatically called on every page load.



618
619
620
# File 'lib/vapir-firefox/firefox.rb', line 618

def run_error_checks
  @error_checkers.each { |e| e.call(self) }
end

#startClicker(*args) ⇒ Object

Raises:

  • (NotImplementedError)


623
624
625
# File 'lib/vapir-firefox/firefox.rb', line 623

def startClicker(*args)
  raise NotImplementedError, "startClicker is gone. Use Firefox#modal_dialog.click_button (generally preceded by a Element#click_no_wait)"
end

#statusObject

Returns the Status of the page currently loaded in the browser from statusbar.

Output:

Status of the page.


532
533
534
535
# File 'lib/vapir-firefox/firefox.rb', line 532

def status
  #content_window_object.status
  browser_window_object.XULBrowserWindow.statusText
end

#textObject

Returns the text of the page currently loaded in the browser.



538
539
540
# File 'lib/vapir-firefox/firefox.rb', line 538

def text
  body_object.textContent
end

#updated_atObject



362
363
364
# File 'lib/vapir-firefox/firefox.rb', line 362

def updated_at
  Time.at(@updated_at_epoch_ms.val/1000.0)+@updated_at_offset
end

#wait(options = {}) ⇒ Object

Waits for the page to get loaded.



553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
# File 'lib/vapir-firefox/firefox.rb', line 553

def wait(options={})
  return unless exists?
  unless options.is_a?(Hash)
    raise ArgumentError, "given options should be a Hash, not #{options.inspect} (#{options.class})\nold conflicting arguments of no_sleep or last_url are gone"
  end
  options={:sleep => false, :last_url => nil, :timeout => 120}.merge(options)
  started=Time.now
  while browser_object.webProgress.isLoadingDocument
    sleep 0.1
    if Time.now - started > options[:timeout]
      raise "Page Load Timeout"
    end
  end

  # If the redirect is to a download attachment that does not reload this page, this
  # method will loop forever. Therefore, we need to ensure that if this method is called
  # twice with the same URL, we simply accept that we're done.
  url= document_object.URL

  if(url != options[:last_url])
    # Check for Javascript redirect. As we are connected to Firefox via JSSh. JSSh
    # doesn't detect any javascript redirects so check it here.
    # If page redirects to itself that this code will enter in infinite loop.
    # So we currently don't wait for such a page.
    # wait variable in JSSh tells if we should wait more for the page to get loaded
    # or continue. -1 means page is not redirected. Anyother positive values means wait.
    metas=document_object.getElementsByTagName 'meta'
    wait_time=metas.to_array.map do |meta|
      return_time=true
      return_time &&= meta.httpEquiv =~ /\Arefresh\z/i 
      return_time &&= begin
        content_split=meta.content.split(';')
        content_split[1] && content_split[1] !~ /\A\s*url=#{Regexp.escape(url)}\s*\z/ # if there is no url, or if the url is the current url, it's just a reload, not a redirect; don't wait. 
      end
      return_time ? content_split[0].to_i : nil
    end.compact.max
    
    if wait_time
      if wait_time > (options[:timeout] - (Time.now - started)) # don't wait longer than what's left in the timeout would for any other timeout. 
        raise "waiting for a meta refresh would take #{wait_time} seconds but remaining time before timeout is #{options[:timeout] - (Time.now - started)} seconds - giving up"
      end
      sleep(wait_time)
      wait(:last_url => url, :timeout => options[:timeout] - (Time.now - started))
    end
  end
  ::Waiter.try_for(options[:timeout] - (Time.now - started), :exception => "Waiting for requests in progress to complete timed out.") do
    @requests_in_progress.length<=@browser_jssh_objects[:unmatched_stopped_requests_count]
  end
  run_error_checks
  return self
end