vapir-firefox 1.7.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,687 @@
1
+ =begin rdoc
2
+ This is Vapir, Web Application Testing In Ruby using Firefox browser
3
+
4
+ Typical usage:
5
+ # include the controller
6
+ require "vapir-firefox"
7
+
8
+ # go to the page you want to test
9
+ ff = Vapir::Firefox.start("http://myserver/mypage")
10
+
11
+ # enter "Angrez" into an input field named "username"
12
+ ff.text_field(:name, "username").set("Angrez")
13
+
14
+ # enter "Ruby Co" into input field with id "company_ID"
15
+ ff.text_field(:id, "company_ID").set("Ruby Co")
16
+
17
+ # click on a link that has "green" somewhere in the text that is displayed
18
+ # to the user, using a regular expression
19
+ ff.link(:text, /green/)
20
+
21
+ # click button that has a caption of "Cancel"
22
+ ff.button(:value, "Cancel").click
23
+
24
+ Vapir allows your script to read and interact with HTML objects--HTML tags
25
+ and their attributes and contents. Types of objects that Vapir can identify
26
+ include:
27
+
28
+ Type Description
29
+ =========== ===============================================================
30
+ button <input> tags, with the type="button" attribute
31
+ check_box <input> tags, with the type="checkbox" attribute
32
+ div <div> tags
33
+ form
34
+ frame
35
+ hidden hidden <input> tags
36
+ image <img> tags
37
+ label
38
+ link <a> (anchor) tags
39
+ p <p> (paragraph) tags
40
+ radio radio buttons; <input> tags, with the type="radio" attribute
41
+ select_list <select> tags, known informally as drop-down boxes
42
+ span <span> tags
43
+ table <table> tags
44
+ text_field <input> tags with the type="text" attribute (a single-line
45
+ text field), the type="text_area" attribute (a multi-line
46
+ text field), and the type="password" attribute (a
47
+ single-line field in which the input is replaced with asterisks)
48
+
49
+ In general, there are several ways to identify a specific object. Vapir's
50
+ syntax is in the form (how, what), where "how" is a means of identifying
51
+ the object, and "what" is the specific string or regular expression
52
+ that Vapir will seek, as shown in the examples above. Available "how"
53
+ options depend upon the type of object, but here are a few examples:
54
+
55
+ How Description
56
+ ============ ===============================================================
57
+ :id Used to find an object that has an "id=" attribute. Since each
58
+ id should be unique, according to the XHTML specification,
59
+ this is recommended as the most reliable method to find an
60
+ object.
61
+ :name Used to find an object that has a "name=" attribute. This is
62
+ useful for older versions of HTML, but "name" is deprecated
63
+ in XHTML.
64
+ :value Used to find a text field with a given default value, or a
65
+ button with a given caption
66
+ :index Used to find the nth object of the specified type on a page.
67
+ For example, button(:index, 2) finds the second button.
68
+ Current versions of Vapir use 1-based indexing, but future
69
+ versions will use 0-based indexing.
70
+ :xpath The xpath expression for identifying the element.
71
+
72
+ Note that the XHTML specification requires that tags and their attributes be
73
+ in lower case. Vapir doesn't enforce this; Vapir will find tags and
74
+ attributes whether they're in upper, lower, or mixed case. This is either
75
+ a bug or a feature.
76
+
77
+ Vapir uses JSSh for interacting with the browser. For further information on
78
+ Firefox and DOM go to the following Web page:
79
+
80
+ http://www.xulplanet.com/references/objref/
81
+
82
+ =end
83
+
84
+ require 'vapir-common/waiter'
85
+ require 'vapir-common/browser'
86
+ require 'vapir-firefox/window'
87
+ require 'vapir-firefox/modal_dialog'
88
+ require 'vapir-firefox/jssh_socket'
89
+ require 'vapir-firefox/container'
90
+ require 'vapir-firefox/page_container'
91
+
92
+ module Vapir
93
+ include Vapir::Exception
94
+
95
+ class Firefox < Browser
96
+ include Firefox::PageContainer
97
+ include Firefox::Window
98
+ include Firefox::ModalDialogContainer
99
+
100
+ def self.initialize_jssh_socket
101
+ # 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
102
+ if class_variable_defined?('@@jssh_socket') && @@jssh_socket
103
+ Kernel.warn "WARNING: JSSH_SOCKET RESET: resetting jssh socket. Any active javascript references will not exist on the new socket!"
104
+ end
105
+ @@jssh_socket=JsshSocket.new
106
+ @@firewatir_jssh_objects=@@jssh_socket.object("Vapir").assign({})
107
+ @@jssh_socket
108
+ end
109
+ def self.jssh_socket(options={})
110
+ if options[:reset] || !(class_variable_defined?('@@jssh_socket') && @@jssh_socket)
111
+ initialize_jssh_socket
112
+ end
113
+ if options[:reset_if_dead]
114
+ begin
115
+ @@jssh_socket.assert_socket
116
+ rescue JsshConnectionError
117
+ initialize_jssh_socket
118
+ end
119
+ end
120
+ @@jssh_socket
121
+ end
122
+ def jssh_socket(options=nil)
123
+ options ? self.class.jssh_socket(options) : @@jssh_socket
124
+ end
125
+
126
+ # Description:
127
+ # Starts the firefox browser.
128
+ # On windows this starts the first version listed in the registry.
129
+ #
130
+ # Input:
131
+ # options - Hash of any of the following options:
132
+ # :wait_time - Time to wait for Firefox to start. By default it waits for 2 seconds.
133
+ # This is done because if Firefox is not started and we try to connect
134
+ # to jssh on port 9997 an exception is thrown.
135
+ # :profile - The Firefox profile to use. If none is specified, Firefox will use
136
+ # the last used profile.
137
+ # :suppress_launch_process - do not create a new firefox process. Connect to an existing one.
138
+ # TODO: Start the firefox version given by user.
139
+ def initialize(options = {})
140
+ if(options.kind_of?(Integer))
141
+ options = {:wait_time => options}
142
+ 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}})"
143
+ end
144
+ options=handle_options(options, {:wait_time => 20}, [:attach, :goto, :binary_path])
145
+ if options[:binary_path]
146
+ @binary_path=options[:binary_path]
147
+ end
148
+
149
+ # check for jssh not running, firefox may be open but not with -jssh
150
+ # if its not open at all, regardless of the :suppress_launch_process option start it
151
+ # error if running without jssh, we don't want to kill their current window (mac only)
152
+ begin
153
+ jssh_socket(:reset_if_dead => true).assert_socket
154
+ rescue JsshError
155
+ # here we're going to assume that since it's not connecting, we need to launch firefox.
156
+ if options[:attach]
157
+ raise Vapir::Exception::NoBrowserException, "cannot attach using #{options[:attach].inspect} - could not connect to Firefox with JSSH"
158
+ else
159
+ launch_browser
160
+ # if we just launched a the browser process, attach to the window
161
+ # that opened when we did that.
162
+ # but if options[:attach] is explicitly given as false (not nil),
163
+ # take that to mean we don't want to attach to the window launched
164
+ # when the process starts.
165
+ unless options[:attach]==false
166
+ options[:attach]=[:title, //]
167
+ end
168
+ end
169
+ ::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
170
+ begin
171
+ jssh_socket(:reset_if_dead => true).assert_socket
172
+ true
173
+ rescue JsshUnableToStart
174
+ false
175
+ end
176
+ end
177
+ end
178
+ @browser_jssh_objects = jssh_socket.object('{}').store_rand_object_key(@@firewatir_jssh_objects) # this is an object that holds stuff for this browser
179
+
180
+ if options[:attach]
181
+ attach(*options[:attach])
182
+ else
183
+ open_window
184
+ end
185
+ set_browser_document
186
+ set_defaults
187
+ if options[:goto]
188
+ goto(options[:goto])
189
+ end
190
+ end
191
+
192
+ # def self.firefox_is_running?
193
+ # TODO/FIX: implement!
194
+ # true
195
+ # end
196
+ # def firefox_is_running?
197
+ # self.class.firefox_is_running?
198
+ # end
199
+
200
+ def mozilla_window_class_name
201
+ 'MozillaUIWindowClass'
202
+ end
203
+
204
+ def browser
205
+ self
206
+ end
207
+
208
+ def exists?
209
+ # jssh_socket may be nil if the window has closed
210
+ jssh_socket && browser_window_object && jssh_socket.object('getWindows()').to_js_array.include(browser_window_object)
211
+ end
212
+
213
+ # Launches firebox browser
214
+ # options as .new
215
+
216
+ def launch_browser(options = {})
217
+ if(options[:profile])
218
+ profile_opt = "-no-remote -P #{options[:profile]}"
219
+ else
220
+ profile_opt = ""
221
+ end
222
+
223
+ bin = path_to_bin()
224
+ @t = Thread.new { system("#{bin} -jssh #{profile_opt}") }
225
+ end
226
+ private :launch_browser
227
+
228
+ # Creates a new instance of Firefox. Loads the URL and return the instance.
229
+ # Input:
230
+ # url - url of the page to be loaded.
231
+ def self.start(url)
232
+ new(:goto => url)
233
+ end
234
+
235
+
236
+ # Loads the given url in the browser. Waits for the page to get loaded.
237
+ def goto(url)
238
+ assert_exists
239
+ browser_object.loadURI url
240
+ wait
241
+ end
242
+
243
+ # Loads the previous page (if there is any) in the browser. Waits for the page to get loaded.
244
+ def back
245
+ if browser_object.canGoBack
246
+ browser_object.goBack
247
+ else
248
+ raise Vapir::Exception::NavigationException, "Cannot go back!"
249
+ end
250
+ wait
251
+ end
252
+
253
+ # Loads the next page (if there is any) in the browser. Waits for the page to get loaded.
254
+ def forward
255
+ if browser_object.canGoForward
256
+ browser_object.goForward
257
+ else
258
+ raise Vapir::Exception::NavigationException, "Cannot go forward!"
259
+ end
260
+ wait
261
+ end
262
+
263
+ # Reloads the current page in the browser. Waits for the page to get loaded.
264
+ def refresh
265
+ browser_object.reload
266
+ wait
267
+ end
268
+
269
+ private
270
+ # This function creates a new socket at port 9997 and sets the default values for instance and class variables.
271
+ # Generatesi UnableToStartJSShException if cannot connect to jssh even after 3 tries.
272
+ def set_defaults(no_of_tries = 0)
273
+ @error_checkers = []
274
+ end
275
+
276
+ # Sets the document, window and browser variables to point to correct object in JSSh.
277
+ def set_browser_document
278
+ unless browser_window_object
279
+ raise "Window must be set (using open_window or attach) before the browser document can be set!"
280
+ end
281
+ @browser_object=@browser_jssh_objects[:browser]= ::Waiter.try_for(2, :exception => Vapir::Exception::NoMatchingWindowFoundException.new("The browser could not be found on the specified Firefox window!")) do
282
+ if browser_window_object.respond_to?(:getBrowser)
283
+ browser_window_object.getBrowser
284
+ end
285
+ end
286
+
287
+ # the following are not stored elsewhere; the ref will just be to attributes of the browser, so that updating the
288
+ # browser (in javascript) will cause all of these refs to reflect that as well
289
+ @document_object=browser_object.contentDocument
290
+ @content_window_object=browser_object.contentWindow
291
+ # note that browser_window_object.content is the same thing, but simpler to refer to stuff on browser_object since that is updated by the nsIWebProgressListener below
292
+ @body_object=document_object.body
293
+ @browser_jssh_objects[:requests_in_progress]=[]
294
+ @requests_in_progress=@browser_jssh_objects[:requests_in_progress].to_array
295
+ @browser_jssh_objects[:unmatched_stopped_requests_count]=0
296
+
297
+ @updated_at_epoch_ms=@browser_jssh_objects.attr(:updated_at_epoch_ms).assign_expr('new Date().getTime()')
298
+ @updated_at_offset=Time.now.to_f-jssh_socket.value_json('new Date().getTime()')/1000.0
299
+
300
+ # Add eventlistener for browser window so that we can reset the document back whenever there is redirect
301
+ # or browser loads on its own after some time. Useful when you are searching for flight results etc and
302
+ # page goes to search page after that it goes automatically to results page.
303
+ # Details : http://zenit.senecac.on.ca/wiki/index.php/Mozilla.dev.tech.xul#What_is_an_example_of_addProgressListener.3F
304
+ @browser_jssh_objects[:listener_object]={}
305
+ listener_object=@browser_jssh_objects[:listener_object]
306
+ listener_object[:QueryInterface]=jssh_socket.object(
307
+ "function(aIID)
308
+ { if(aIID.equals(Components.interfaces.nsIWebProgressListener) || aIID.equals(Components.interfaces.nsISupportsWeakReference) || aIID.equals(Components.interfaces.nsISupports))
309
+ { return this;
310
+ }
311
+ throw Components.results.NS_NOINTERFACE;
312
+ }")
313
+ listener_object[:onStateChange]= jssh_socket.object(
314
+ "function(aWebProgress, aRequest, aStateFlags, aStatus)
315
+ { var requests_in_progress=#{@requests_in_progress.ref};
316
+ if(aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
317
+ { #{@updated_at_epoch_ms.ref}=new Date().getTime();
318
+ #{browser_object.ref}=#{browser_window_object.ref}.getBrowser();
319
+ var matched=false;
320
+ for(var i=0; i<requests_in_progress.length; i+=1)
321
+ { if(requests_in_progress[i].request==aRequest)
322
+ // TODO/FIX: this doesn't seem to work reliably - possibly related to redirects?
323
+ // workaround is to just check if there are as many unmatched stop requests as requests
324
+ // in progress.
325
+ // but this ought to be fixed to correctly match STATE_STOP requests to previously-
326
+ // encountered STATE_START requests.
327
+ { requests_in_progress.splice(i, 1);
328
+ matched=true;
329
+ break;
330
+ }
331
+ }
332
+ if(!matched)
333
+ { #{@browser_jssh_objects.attr(:unmatched_stopped_requests_count).ref}++; //.push({webProgress: aWebProgress, request: aRequest, stateFlags: aStateFlags, status: aStatus});
334
+ // count any stop requests that we fail to match so that we can compare that count to the number of unmatched start requests.
335
+ }
336
+ }
337
+ if(aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_START)
338
+ { requests_in_progress.push({webProgress: aWebProgress, request: aRequest, stateFlags: aStateFlags, status: aStatus});
339
+ }
340
+ // the below was kind of a hack to get rid of any requests which
341
+ // are done but were not matched to a STATE_STOP request.
342
+ // it doesn't seem to work very well, so commented.
343
+ /*
344
+ for(var i=0; i<requests_in_progress.length; ++i)
345
+ { var request_in_progress=requests_in_progress[i];
346
+ if(request_in_progress.request.loadGroup.activeCount==0)
347
+ { requests_in_progress.splice(i, 1);
348
+ --i;
349
+ }
350
+ }*/
351
+ }")
352
+ browser_object.addProgressListener(listener_object)
353
+ end
354
+
355
+ public
356
+ attr_reader :browser_window_object
357
+ attr_reader :content_window_object
358
+ attr_reader :browser_object
359
+ attr_reader :document_object
360
+ attr_reader :body_object
361
+
362
+ def updated_at
363
+ Time.at(@updated_at_epoch_ms.val/1000.0)+@updated_at_offset
364
+ end
365
+
366
+ public
367
+ # Closes the window.
368
+ def close
369
+ assert_exists
370
+ begin
371
+ browser_window_object.close
372
+ # TODO/fix timeout; this shouldn't be a hard-coded magic number.
373
+ ::Waiter.try_for(32, :exception => Exception::WindowFailedToCloseException.new("The browser window did not close")) do
374
+ !exists?
375
+ end
376
+ @@jssh_socket.assert_socket
377
+ rescue JsshConnectionError # the socket may disconnect when we close the browser, causing the JsshSocket to complain
378
+ @@jssh_socket=nil
379
+ end
380
+ @browser_window_object=@browser_object=@document_object=@content_window_object=@body_object=nil
381
+ @launched_browser_process=false #TODO/FIX: check here if we originally launched the browser process
382
+ if @launched_browser_process && @@jssh_socket
383
+ quit_browser(:force => false)
384
+ end
385
+ end
386
+
387
+ # Closes all firefox windows by quitting the browser
388
+ def close_all
389
+ quit_browser(:force => false)
390
+ end
391
+
392
+ # quits the browser.
393
+ # quit_browser(:force => true) will force the browser to quit.
394
+ def quit_browser(options={})
395
+ options=handle_options(options, :force => false)
396
+ # from https://developer.mozilla.org/en/How_to_Quit_a_XUL_Application
397
+ appStartup= jssh_socket.Components.classes['@mozilla.org/toolkit/app-startup;1'].getService(jssh_socket.Components.interfaces.nsIAppStartup)
398
+ quitSeverity = options[:force] ? jssh_socket.Components.interfaces.nsIAppStartup.eForceQuit : jssh_socket.Components.interfaces.nsIAppStartup.eAttemptQuit
399
+ begin
400
+ appStartup.quit(quitSeverity)
401
+ ::Waiter.try_for(8, :exception => Exception::WindowFailedToCloseException.new("The browser did not quit")) do
402
+ @@jssh_socket.assert_socket # this should error, going up past the waiter to the rescue block above
403
+ false
404
+ end
405
+ rescue JsshConnectionError
406
+ @@jssh_socket=nil
407
+ end
408
+ # TODO/FIX: poll to wait for the process itself to finish? the socket closes (which we wait for
409
+ # above) before the process itself has exited, so if Firefox.new is called between the socket
410
+ # closing and the process exiting, Firefox pops up with:
411
+ # Close Firefox
412
+ # A copy of Firefox is already open. Only one copy of Firefox can be open at a time.
413
+ # [OK]
414
+ # until that's implemented, just wait for an arbitrary amount of time. (ick)
415
+ sleep 2
416
+
417
+ @browser_window_object=@browser_object=@document_object=@content_window_object=@body_object=nil
418
+ nil
419
+ end
420
+
421
+ # Used for attaching pop up window to an existing Firefox window, either by url or title.
422
+ # ff.attach(:url, 'http://www.google.com')
423
+ # ff.attach(:title, 'Google')
424
+ #
425
+ # Output:
426
+ # Instance of newly attached window.
427
+ def attach(how, what)
428
+ @browser_window_object = case how
429
+ when :jssh_object
430
+ what
431
+ else
432
+ find_window(how, what)
433
+ end
434
+
435
+ unless @browser_window_object
436
+ raise Exception::NoMatchingWindowFoundException.new("Unable to locate window, using #{how} and #{what}")
437
+ end
438
+ set_browser_document
439
+ self
440
+ end
441
+
442
+ # Class method to return a browser object if a window matches for how
443
+ # and what. Window can be referenced by url or title.
444
+ # The second argument can be either a string or a regular expression.
445
+ # Vapir::Browser.attach(:url, 'http://www.google.com')
446
+ # Vapir::Browser.attach(:title, 'Google')
447
+ def self.attach how, what
448
+ new(:attach => [how, what])
449
+ end
450
+
451
+ # loads up a new window in an existing process
452
+ # Vapir::Browser.attach() with no arguments passed the attach method will create a new window
453
+ # this will only be called one time per instance we're only ever going to run in 1 window
454
+
455
+ def open_window
456
+ begin
457
+ @browser_window_name="firewatir_window_%.16x"%rand(2**64)
458
+ end while jssh_socket.value_json("$A(getWindows()).detect(function(win){return win.name==#{@browser_window_name.to_jssh}}) ? true : false")
459
+ watcher=jssh_socket.Components.classes["@mozilla.org/embedcomp/window-watcher;1"].getService(jssh_socket.Components.interfaces.nsIWindowWatcher)
460
+ # nsIWindowWatcher is used to launch new top-level windows. see https://developer.mozilla.org/en/Working_with_windows_in_chrome_code
461
+
462
+ @browser_window_object=@browser_jssh_objects[:browser_window]=watcher.openWindow(nil, 'chrome://browser/content/browser.xul', @browser_window_name, 'resizable', nil)
463
+ return @browser_window_object
464
+ end
465
+ private :open_window
466
+
467
+ def self.each
468
+ each_browser_window_object do |win|
469
+ yield self.attach(:jssh_object, win)
470
+ end
471
+ end
472
+
473
+ def self.each_browser_window_object
474
+ mediator=jssh_socket.Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(jssh_socket.Components.interfaces.nsIWindowMediator)
475
+ enumerator=mediator.getEnumerator("navigator:browser")
476
+ while enumerator.hasMoreElements
477
+ win=enumerator.getNext
478
+ yield win
479
+ end
480
+ nil
481
+ end
482
+ def self.browser_window_objects
483
+ window_objects=[]
484
+ each_browser_window_object do |window_object|
485
+ window_objects << window_object
486
+ end
487
+ window_objects
488
+ end
489
+ def self.each_window_object
490
+ mediator=jssh_socket.Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(jssh_socket.Components.interfaces.nsIWindowMediator)
491
+ enumerator=mediator.getEnumerator(nil)
492
+ while enumerator.hasMoreElements
493
+ win=enumerator.getNext
494
+ yield win
495
+ end
496
+ nil
497
+ end
498
+ def self.window_objects
499
+ window_objects=[]
500
+ each_window_object do |window_object|
501
+ window_objects << window_object
502
+ end
503
+ window_objects
504
+ end
505
+
506
+ # return the window jssh object for the browser window with the given title or url.
507
+ # how - :url or :title
508
+ # what - string or regexp
509
+ #
510
+ # Start searching windows in reverse order so that we attach/find the latest opened window.
511
+ def find_window(how, what)
512
+ orig_how=how
513
+ hows={ :title => proc{|content_window| content_window.title },
514
+ :URL => proc{|content_window| content_window.location.href },
515
+ }
516
+ how=hows.keys.detect{|h| h.to_s.downcase==orig_how.to_s.downcase}
517
+ raise ArgumentError, "how should be one of: #{hows.keys.inspect} (was #{orig_how.inspect})" unless how
518
+ found_win=nil
519
+ self.class.each_browser_window_object do |win|
520
+ found_win=win if Vapir::fuzzy_match(hows[how].call(win.getBrowser.contentDocument),what)
521
+ # we don't break here if found_win is set because we want the last match if there are multiple.
522
+ end
523
+ return found_win
524
+ end
525
+ private :find_window
526
+
527
+ # Returns the Status of the page currently loaded in the browser from statusbar.
528
+ #
529
+ # Output:
530
+ # Status of the page.
531
+ #
532
+ def status
533
+ #content_window_object.status
534
+ browser_window_object.XULBrowserWindow.statusText
535
+ end
536
+
537
+ # Returns the text of the page currently loaded in the browser.
538
+ def text
539
+ body_object.textContent
540
+ end
541
+
542
+ # Maximize the current browser window.
543
+ def maximize()
544
+ browser_window_object.maximize
545
+ end
546
+
547
+ # Minimize the current browser window.
548
+ def minimize()
549
+ browser_window_object.minimize
550
+ end
551
+
552
+ # Waits for the page to get loaded.
553
+ def wait(options={})
554
+ return unless exists?
555
+ unless options.is_a?(Hash)
556
+ raise ArgumentError, "given options should be a Hash, not #{options.inspect} (#{options.class})\nold conflicting arguments of no_sleep or last_url are gone"
557
+ end
558
+ options={:sleep => false, :last_url => nil, :timeout => 120}.merge(options)
559
+ started=Time.now
560
+ while browser_object.webProgress.isLoadingDocument
561
+ sleep 0.1
562
+ if Time.now - started > options[:timeout]
563
+ raise "Page Load Timeout"
564
+ end
565
+ end
566
+
567
+ # If the redirect is to a download attachment that does not reload this page, this
568
+ # method will loop forever. Therefore, we need to ensure that if this method is called
569
+ # twice with the same URL, we simply accept that we're done.
570
+ url= document_object.URL
571
+
572
+ if(url != options[:last_url])
573
+ # Check for Javascript redirect. As we are connected to Firefox via JSSh. JSSh
574
+ # doesn't detect any javascript redirects so check it here.
575
+ # If page redirects to itself that this code will enter in infinite loop.
576
+ # So we currently don't wait for such a page.
577
+ # wait variable in JSSh tells if we should wait more for the page to get loaded
578
+ # or continue. -1 means page is not redirected. Anyother positive values means wait.
579
+ metas=document_object.getElementsByTagName 'meta'
580
+ wait_time=metas.to_array.map do |meta|
581
+ return_time=true
582
+ return_time &&= meta.httpEquiv =~ /\Arefresh\z/i
583
+ return_time &&= begin
584
+ content_split=meta.content.split(';')
585
+ 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.
586
+ end
587
+ return_time ? content_split[0].to_i : nil
588
+ end.compact.max
589
+
590
+ if wait_time
591
+ if wait_time > (options[:timeout] - (Time.now - started)) # don't wait longer than what's left in the timeout would for any other timeout.
592
+ 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"
593
+ end
594
+ sleep(wait_time)
595
+ wait(:last_url => url, :timeout => options[:timeout] - (Time.now - started))
596
+ end
597
+ end
598
+ ::Waiter.try_for(options[:timeout] - (Time.now - started), :exception => "Waiting for requests in progress to complete timed out.") do
599
+ @requests_in_progress.length<=@browser_jssh_objects[:unmatched_stopped_requests_count]
600
+ end
601
+ run_error_checks
602
+ return self
603
+ end
604
+
605
+ # Add an error checker that gets called on every page load.
606
+ # * checker - a Proc object
607
+ def add_checker(checker)
608
+ @error_checkers << checker
609
+ end
610
+
611
+ # Disable an error checker
612
+ # * checker - a Proc object that is to be disabled
613
+ def disable_checker(checker)
614
+ @error_checkers.delete(checker)
615
+ end
616
+
617
+ # Run the predefined error checks. This is automatically called on every page load.
618
+ def run_error_checks
619
+ @error_checkers.each { |e| e.call(self) }
620
+ end
621
+
622
+
623
+ def startClicker(*args)
624
+ raise NotImplementedError, "startClicker is gone. Use Firefox#modal_dialog.click_button (generally preceded by a Element#click_no_wait)"
625
+ end
626
+
627
+ private
628
+
629
+ def path_to_bin
630
+ path = @binary_path || begin
631
+ case current_os
632
+ when :windows
633
+ path_from_registry
634
+ when :macosx
635
+ path_from_spotlight
636
+ when :linux
637
+ `which firefox`.strip
638
+ end
639
+ end
640
+ raise "unable to locate Firefox executable" if path.nil? || path.empty?
641
+ path
642
+ end
643
+
644
+ def current_os
645
+ @current_os ||= begin
646
+ platform= RUBY_PLATFORM =~ /java/ ? java.lang.System.getProperty("os.name") : RUBY_PLATFORM
647
+ case platform
648
+ when /mswin|windows|mingw32/i
649
+ :windows
650
+ when /darwin|mac os/i
651
+ :macosx
652
+ when /linux/i
653
+ :linux
654
+ else
655
+ raise "Unidentified platform #{platform}"
656
+ end
657
+ end
658
+ end
659
+
660
+ def path_from_registry
661
+ raise NotImplementedError, "(need to know how to access windows registry on JRuby)" if RUBY_PLATFORM =~ /java/
662
+ require 'win32/registry'
663
+ lm = ::Win32::Registry::HKEY_LOCAL_MACHINE
664
+ lm.open('SOFTWARE\Mozilla\Mozilla Firefox') do |reg|
665
+ reg1 = lm.open("SOFTWARE\\Mozilla\\Mozilla Firefox\\#{reg.keys[0]}\\Main")
666
+ if entry = reg1.find { |key, type, data| key =~ /pathtoexe/i }
667
+ return entry.last
668
+ end
669
+ end
670
+ end
671
+
672
+ def path_from_spotlight
673
+ ff = %x[mdfind 'kMDItemCFBundleIdentifier == "org.mozilla.firefox"']
674
+ ff = ff.empty? ? '/Applications/Firefox.app' : ff.split("\n").first
675
+
676
+ "#{ff}/Contents/MacOS/firefox-bin"
677
+ end
678
+
679
+ private
680
+ def base_element_class
681
+ Firefox::Element
682
+ end
683
+ def browser_class
684
+ Firefox
685
+ end
686
+ end # Firefox
687
+ end # Vapir