firewatir 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/container.rb CHANGED
@@ -816,7 +816,11 @@ module Container
816
816
  # Test the index access.
817
817
  # puts doc[35].to_s
818
818
  end
819
-
819
+
820
+ def jssh_socket
821
+ $jssh_socket || @container.jssh_socket
822
+ end
823
+
820
824
  #
821
825
  # Description:
822
826
  # Reads the javascript execution result from the jssh socket.
@@ -827,9 +831,10 @@ module Container
827
831
  # Output:
828
832
  # The javascript execution result as string.
829
833
  #
830
- def read_socket(socket = $jssh_socket)
834
+ def read_socket(socket = jssh_socket)
831
835
  return_value = ""
832
836
  data = ""
837
+ receive = true
833
838
  #puts Thread.list
834
839
  s = nil
835
840
  while(s == nil) do
@@ -839,16 +844,22 @@ module Container
839
844
  for stream in s[0]
840
845
  data = stream.recv(1024)
841
846
  #puts "data is : #{data}"
842
- while(data[data.length - 3..data.length - 1] != "\n> ")
847
+ while(receive)
848
+ #while(data.length == 1024)
843
849
  return_value += data
844
- data = stream.recv(1024)
850
+ if(return_value.include?("\n> "))
851
+ receive = false
852
+ else
853
+ data = stream.recv(1024)
854
+ end
855
+ #puts "return_value is : #{return_value}"
845
856
  #puts "data length is : #{data.length}"
846
857
  end
847
858
  end
848
859
 
849
860
  # If received data is less than 1024 characters or for last data
850
861
  # we read in the above loop
851
- return_value += data
862
+ #return_value += data
852
863
 
853
864
  # Get the command prompt inserted by JSSH
854
865
  #s = Kernel.select([socket] , nil , nil, 0.3)
data/firewatir.rb CHANGED
@@ -255,6 +255,16 @@ module FireWatir
255
255
  def get_window_number()
256
256
  $jssh_socket.send("getWindows().length;\n", 0)
257
257
  @@current_window = read_socket().to_i - 1
258
+
259
+ # Derek Berner 5/16/08
260
+ # If at any time a non-browser window like the "Downloads" window
261
+ # pops up, it will become the topmost window, so make sure we
262
+ # ignore it.
263
+ @@current_window = js_eval("getWindows().length").to_i - 1
264
+ while js_eval("getWindows()[#{@@current_window}].getBrowser") == ''
265
+ @@current_window -= 1;
266
+ end
267
+
258
268
  # This will store the information about the window.
259
269
  #@@window_stack.push(@@current_window)
260
270
  #puts "here in get_window_number window number is #{@@current_window}"
@@ -329,6 +339,7 @@ module FireWatir
329
339
  retry if no_of_tries < 3
330
340
  raise UnableToStartJSShException, "Unable to connect to machine : #{MACHINE_IP} on port 9997. Make sure that JSSh is properly installed and Firefox is running with '-jssh' option"
331
341
  end
342
+ @error_checkers = []
332
343
  end
333
344
  private :set_defaults
334
345
 
@@ -368,7 +379,9 @@ module FireWatir
368
379
  #
369
380
  def close()
370
381
  #puts "current window number is : #{@@current_window}"
371
- if @@current_window == 0
382
+ # Derek Berner 5/16/08
383
+ # Try to join thread only if there is exactly one open window
384
+ if js_eval("getWindows().length").to_i == 1
372
385
  $jssh_socket.send(" getWindows()[0].close(); \n", 0)
373
386
  @t.join if @t != nil
374
387
  #sleep 5
@@ -576,70 +589,107 @@ module FireWatir
576
589
  # Description:
577
590
  # Waits for the page to get loaded.
578
591
  #
579
- def wait()
592
+ def wait(last_url = nil)
580
593
  #puts "In wait function "
581
594
  isLoadingDocument = ""
595
+ start = Time.now
596
+
582
597
  while isLoadingDocument != "false"
583
- $jssh_socket.send("#{BROWSER_VAR}=#{WINDOW_VAR}.getBrowser(); #{BROWSER_VAR}.webProgress.isLoadingDocument;\n" , 0)
584
- isLoadingDocument = read_socket()
598
+ isLoadingDocument = js_eval("#{BROWSER_VAR}=#{WINDOW_VAR}.getBrowser(); #{BROWSER_VAR}.webProgress.isLoadingDocument;")
585
599
  #puts "Is browser still loading page: #{isLoadingDocument}"
600
+
601
+ # Derek Berner 5/16/08
602
+ # Raise an exception if the page fails to load
603
+ if (Time.now - start) > 300
604
+ raise "Page Load Timeout"
605
+ end
586
606
  end
587
-
588
- # Check for Javascript redirect. As we are connected to Firefox via JSSh. JSSh
589
- # doesn't detect any javascript redirects so check it here.
590
- # If page redirects to itself that this code will enter in infinite loop.
591
- # So we currently don't wait for such a page.
592
- # wait variable in JSSh tells if we should wait more for the page to get loaded
593
- # or continue. -1 means page is not redirected. Anyother positive values means wait.
594
- jssh_command = "var wait = -1; var meta = null; meta = #{BROWSER_VAR}.contentDocument.getElementsByTagName('meta');
595
- if(meta != null)
596
- {
597
- var doc_url = #{BROWSER_VAR}.contentDocument.URL;
598
- for(var i=0; i< meta.length;++i)
607
+ # Derek Berner 5/16/08
608
+ # If the redirect is to a download attachment that does not reload this page, this
609
+ # method will loop forever. Therefore, we need to ensure that if this method is called
610
+ # twice with the same URL, we simply accept that we're done.
611
+ $jssh_socket.send("#{BROWSER_VAR}.contentDocument.URL;\n", 0)
612
+ url = read_socket()
613
+
614
+ if(url != last_url)
615
+ # Check for Javascript redirect. As we are connected to Firefox via JSSh. JSSh
616
+ # doesn't detect any javascript redirects so check it here.
617
+ # If page redirects to itself that this code will enter in infinite loop.
618
+ # So we currently don't wait for such a page.
619
+ # wait variable in JSSh tells if we should wait more for the page to get loaded
620
+ # or continue. -1 means page is not redirected. Anyother positive values means wait.
621
+ jssh_command = "var wait = -1; var meta = null; meta = #{BROWSER_VAR}.contentDocument.getElementsByTagName('meta');
622
+ if(meta != null)
599
623
  {
600
- var content = meta[i].content;
601
- var regex = new RegExp(\"^refresh$\", \"i\");
602
- if(regex.test(meta[i].httpEquiv))
603
- {
604
- var arrContent = content.split(';');
605
- var redirect_url = null;
606
- if(arrContent.length > 0)
607
- {
608
- if(arrContent.length > 1)
609
- redirect_url = arrContent[1];
610
-
611
- if(redirect_url != null)
612
- {
613
- regex = new RegExp(\"^.*\" + redirect_url + \"$\");
614
- if(!regex.test(doc_url))
615
- {
616
- wait = arrContent[0];
617
- }
618
- }
619
- break;
620
- }
621
- }
622
- }
623
- }
624
- wait;"
625
- #puts "command in wait is : #{jssh_command}"
626
- jssh_command = jssh_command.gsub(/\n/, "")
627
- $jssh_socket.send("#{jssh_command}; \n", 0)
628
- wait_time = read_socket();
629
- #puts "wait time is : #{wait_time}"
630
- begin
631
- wait_time = wait_time.to_i
632
- if(wait_time != -1)
633
- sleep(wait_time)
634
- # Call wait again. In case there are multiple redirects.
635
- $jssh_socket.send("#{BROWSER_VAR} = #{WINDOW_VAR}.getBrowser(); \n",0)
636
- read_socket()
637
- wait()
638
- end
639
- rescue
640
- end
624
+ var doc_url = #{BROWSER_VAR}.contentDocument.URL;
625
+ for(var i=0; i< meta.length;++i)
626
+ {
627
+ var content = meta[i].content;
628
+ var regex = new RegExp(\"^refresh$\", \"i\");
629
+ if(regex.test(meta[i].httpEquiv))
630
+ {
631
+ var arrContent = content.split(';');
632
+ var redirect_url = null;
633
+ if(arrContent.length > 0)
634
+ {
635
+ if(arrContent.length > 1)
636
+ redirect_url = arrContent[1];
637
+
638
+ if(redirect_url != null)
639
+ {
640
+ regex = new RegExp(\"^.*\" + redirect_url + \"$\");
641
+ if(!regex.test(doc_url))
642
+ {
643
+ wait = arrContent[0];
644
+ }
645
+ }
646
+ break;
647
+ }
648
+ }
649
+ }
650
+ }
651
+ wait;"
652
+ #puts "command in wait is : #{jssh_command}"
653
+ jssh_command = jssh_command.gsub(/\n/, "")
654
+ $jssh_socket.send("#{jssh_command}; \n", 0)
655
+ wait_time = read_socket();
656
+ #puts "wait time is : #{wait_time}"
657
+ begin
658
+ wait_time = wait_time.to_i
659
+ if(wait_time != -1)
660
+ sleep(wait_time)
661
+ # Call wait again. In case there are multiple redirects.
662
+ $jssh_socket.send("#{BROWSER_VAR} = #{WINDOW_VAR}.getBrowser(); \n",0)
663
+ read_socket()
664
+ wait(url)
665
+ end
666
+ rescue
667
+ end
668
+ end
641
669
  set_browser_document()
670
+ run_error_checks()
671
+ return self
642
672
  end
673
+
674
+ # Add an error checker that gets called on every page load.
675
+ #
676
+ # * checker - a Proc object
677
+ def add_checker(checker)
678
+ @error_checkers << checker
679
+ end
680
+
681
+ # Disable an error checker
682
+ #
683
+ # * checker - a Proc object that is to be disabled
684
+ def disable_checker(checker)
685
+ @error_checkers.delete(checker)
686
+ end
687
+
688
+ # Run the predefined error checks. This is automatically called on every page load.
689
+ def run_error_checks
690
+ @error_checkers.each { |e| e.call(self) }
691
+ end
692
+
643
693
 
644
694
  #def jspopup_appeared(popupText = "", wait = 2)
645
695
  # winHelper = WindowHelper.new()
@@ -1084,6 +1134,21 @@ module FireWatir
1084
1134
  end
1085
1135
  alias showFrames show_frames
1086
1136
 
1137
+ # 5/16/08 Derek Berner
1138
+ # Wrapper method to send JS commands concisely,
1139
+ # and propagate errors
1140
+ def js_eval(str)
1141
+ #puts "JS Eval: #{str}"
1142
+ $jssh_socket.send("#{str};\n",0)
1143
+ value = read_socket()
1144
+ if md=/^(\w+)Error:(.*)$/.match(value)
1145
+ eval "class JS#{md[1]}Error\nend"
1146
+ raise (eval "JS#{md[1]}Error"), md[2]
1147
+ end
1148
+ #puts "Value: #{value}"
1149
+ value
1150
+ end
1151
+
1087
1152
  end # Class Firefox
1088
1153
 
1089
1154
  #
@@ -0,0 +1,122 @@
1
+ if /linux/i.match(RUBY_PLATFORM)
2
+ require File.expand_path(File.join(File.dirname(__FILE__), 'x11'))
3
+
4
+ # Linux/X11 implementation of WinClicker.
5
+ # Not all functionality is present because of the differences between X11
6
+ # and Win32.
7
+ class WinClicker
8
+
9
+ def clickJavaScriptDialog(button="OK")
10
+ click_window_button(/The page/,button)
11
+ end
12
+
13
+ def clickJSDialog_Thread(button="OK")
14
+ puts "clickJSDialog_Thread Starting waiting..."
15
+ sleep 3
16
+ puts " clickJSDialog_Thread ... resuming"
17
+ n = 0
18
+ while n < 3
19
+ sleep 1
20
+ click_window_button(/The page/,button)
21
+ end
22
+ end
23
+
24
+ def clearSecurityAlertBox
25
+ click_window_button(/Unknown Authority/, "OK")
26
+ click_window_button(/Domain Name Mismatch/, "Cancel")
27
+ end
28
+
29
+ def clickWindowsButton(title, button, maxWaitTime=30)
30
+ start = Time.now
31
+ w = window_by_title(title)
32
+ until w || (Time.now - start > maxWaitTime)
33
+ sleep(2) # Window search is pretty CPU intensive, so relax the requirement
34
+ w = window_by_title(title)
35
+ end
36
+ unless w
37
+ puts "clickWindowsButton: Cant make window active in specified time ( " + maxWaitTime.to_s + ") - no handle"
38
+ return false
39
+ end
40
+ click_button(w,button)
41
+ end
42
+
43
+ private
44
+
45
+ # Since it's impossible to read the button text in X11 windows,
46
+ # we have to specify keystrokes for the button names given the title.
47
+ # TODO: A more elegant solution, or expand this list (to fill out popup text boxes for basic HTTP auth, perhaps).
48
+ @@window_keys = [
49
+ [/Unknown Authority/i, {'ok' => [:enter], 'cancel' => [:tab,:tab,:tab,:enter]}],
50
+ [/Domain Name Mismatch/i, {'ok' => [:tab, :enter], 'cancel' => [:enter]}],
51
+ [/Opening/i, {'ok' => [:sleep,:enter], 'cancel' => [:tab,:tab,:tab,:enter]}],
52
+ [/The page at .* says/i, {'ok' => [:enter], 'cancel' => [:tab,:enter]}]
53
+ ]
54
+
55
+ # Collection of all current firefox windows
56
+ def firefox_windows(w = nil)
57
+ collection = []
58
+ windows = nil
59
+ if w
60
+ windows = [w]
61
+ else
62
+ windows = X11::Display.instance.screens.collect{|s| s.root_window}
63
+ end
64
+ windows.each do |window|
65
+ if window.class == 'Gecko'
66
+ collection << window
67
+ end
68
+ window.children.each do |c|
69
+ collection << firefox_windows(c)
70
+ end
71
+ end
72
+ return collection.flatten.compact
73
+ end
74
+
75
+ def window_by_title(title,windows=nil)
76
+ pattern = nil
77
+ if title.is_a?(Regexp)
78
+ pattern = title
79
+ else
80
+ pattern = Regexp.compile(title,Regexp::IGNORECASE)
81
+ end
82
+ windows ||= X11::Display.instance.screens.collect{|s| s.root_window}
83
+ if window = windows.find{|w| w.class == 'Gecko' && pattern.match(w.name)}
84
+ return window
85
+ else
86
+ children = windows.reject{|w| w.class == 'Gecko'}.collect{|w| w.children}.flatten.compact
87
+ if children.length > 0
88
+ return window_by_title(pattern,children)
89
+ end
90
+ end
91
+ return nil
92
+ end
93
+
94
+ def keystrokes(window,button)
95
+ keys = @@window_keys.find{|wk| wk.first.match(window.name)}
96
+ if keys
97
+ return keys.last[button.downcase]
98
+ else
99
+ return false
100
+ end
101
+ end
102
+
103
+ def click_button(window, button)
104
+ keys = nil
105
+ if button.is_a?(Symbol)
106
+ keys = [button]
107
+ else
108
+ keys = keystrokes(window,button)
109
+ end
110
+ return unless keys
111
+ keys.each do |key|
112
+ if key == :sleep
113
+ @sleep_next = 1
114
+ next
115
+ end
116
+ window.send_key(key,@sleep_next)
117
+ @sleep_next = nil
118
+ end
119
+ end
120
+
121
+ end
122
+ end
data/firewatir/x11.rb ADDED
@@ -0,0 +1,192 @@
1
+ require 'dl/import'
2
+ require 'dl/struct'
3
+
4
+ require 'singleton'
5
+
6
+ module X11
7
+ class << self
8
+ include X11 # Do this so we can call imported libraries directly on X11
9
+ end
10
+
11
+ # Load the X11 library
12
+ extend DL::Importable
13
+ dlload "libX11.so"
14
+
15
+ # Import necessary functions from X11 here.
16
+ import("XOpenDisplay", "unsigned long", ["char*"])
17
+ import("XScreenCount", "int", ["unsigned long"])
18
+ import("XRootWindow", "unsigned long", ["unsigned long","int"])
19
+
20
+ import("XFree", "int", ["void*"])
21
+
22
+ import("XFetchName", "int", ["unsigned long","unsigned long","char**"])
23
+ import("XGetClassHint", "int", ["unsigned long","unsigned long","void*"])
24
+ import("XQueryTree", "int", ["unsigned long","unsigned long","unsigned long*","unsigned long*","unsigned long**","unsigned int*"])
25
+
26
+ import("XSetInputFocus", "int", ["unsigned long","unsigned long","int","long"])
27
+ import("XSendEvent", "int", ["unsigned long","unsigned long","int","long","void*"])
28
+ import("XFlush", "int", ["unsigned long"])
29
+
30
+ # Structs we will use in API calls.
31
+ # Pointer structs are necessary when the API uses a pointer parameter for a return value.
32
+ ULPPointer = struct [
33
+ "long* value"
34
+ ]
35
+ ULPointer = struct [
36
+ "long value"
37
+ ]
38
+ CPPointer = struct [
39
+ "char* value"
40
+ ]
41
+ UIPointer = struct [
42
+ "int value"
43
+ ]
44
+ # Info about window class
45
+ XClassHint = struct [
46
+ "char* res_name",
47
+ "char* res_class"
48
+ ]
49
+ # Event struct for key presses
50
+ XKeyEvent = struct [
51
+ "int type",
52
+ "long serial",
53
+ "int send_event",
54
+ "long display",
55
+ "long window",
56
+ "long root",
57
+ "long subwindow",
58
+ "long time",
59
+ "int x",
60
+ "int y",
61
+ "int x_root",
62
+ "int y_root",
63
+ "int state",
64
+ "int keycode",
65
+ "int same_screen"
66
+ ]
67
+
68
+ # End of library imports.
69
+
70
+ # X11 Display. Singleton -- assumes single display.
71
+ # Assumes the current display is the same as the one running FireFox.
72
+ # Represented by memory pointer (which we treat in-code as an unsigned long).
73
+ class Display
74
+ include Singleton
75
+
76
+ def initialize
77
+ @xdisplay = X11.xOpenDisplay("");
78
+ end
79
+
80
+ # Array of screens associated with this display.
81
+ def screens
82
+ nScreens = X11.xScreenCount(@xdisplay);
83
+ (0...nScreens).collect{|n| Screen.new(n,@xdisplay)}
84
+ end
85
+ end
86
+
87
+ # A display screen, for multi-monitor displays like mine ;-)
88
+ # Represented by display pointer and screen number.
89
+ class Screen
90
+ def initialize(screen_num,xdisplay)
91
+ @screen_num = screen_num
92
+ @xdisplay = xdisplay
93
+ end
94
+
95
+ # Root window containing all other windows in this screen.
96
+ def root_window
97
+ Window.new(X11.xRootWindow(@xdisplay,@screen_num),@screen_num,@xdisplay)
98
+ end
99
+ end
100
+
101
+ # An X11 Window (toplevel window, widget, applet, etc.)
102
+ # Represented by its XID, an unsigned long.
103
+ class Window
104
+ attr_reader :xid, :name, :class, :hint, :parent
105
+
106
+ def initialize(xid,screen_num,xdisplay,parent=nil)
107
+ @xid = xid
108
+ @screen_num = screen_num
109
+ @xdisplay = xdisplay
110
+ @parent = parent
111
+ load_standard
112
+ end
113
+
114
+ # Child windows
115
+ def children
116
+ tree[:children].collect{|c| Window.new(c,@screen_num,@xdisplay,self)}
117
+ end
118
+
119
+ # XID of parent window
120
+ def parent_xid
121
+ parent ? parent.xid : nil
122
+ end
123
+
124
+ # Send a key press to this window
125
+ def send_key(key=:enter,sleep=nil)
126
+ # TODO expand this list out, add support for shift, etc.
127
+ @@keys = {:enter => 36, :tab => 23} unless defined?@@keys
128
+ keycode = @@keys[key]
129
+ X11.xSetInputFocus(@xdisplay, @xid, 1, 0)
130
+ sleep(sleep) if sleep
131
+ e = create_key_event
132
+ e.keycode = keycode
133
+ e.type = 2 # press
134
+ X11.xSendEvent(@xdisplay,@xid,1,1,e)
135
+ e.type = 3 # release
136
+ X11.xSendEvent(@xdisplay,@xid,1,2,e)
137
+ X11.xFlush(@xdisplay)
138
+ end
139
+
140
+ private
141
+
142
+ # Retrieve this window's portion of the window tree
143
+ # Includes display root, parent, and children
144
+ def tree
145
+ tree = {:children => [], :parent => 0, :root => 0}
146
+ children = ULPPointer.malloc
147
+ root = ULPointer.malloc
148
+ parent = ULPointer.malloc
149
+ n = UIPointer.malloc
150
+ r=X11.xQueryTree(@xdisplay,@xid,root,parent,children,n)
151
+ tree[:parent] = parent.value
152
+ tree[:root] = root.value
153
+ tree[:children] = children.value.to_s(4*n.value).unpack("L*") if children.value
154
+ tree
155
+ end
156
+
157
+ # Load some standard attributes (name and class)
158
+ def load_standard
159
+ name = CPPointer.malloc
160
+ if X11.xFetchName(@xdisplay,@xid,name) != 0
161
+ @name = name.value.to_s
162
+ X11.xFree name.value
163
+ end
164
+ classHint = XClassHint.malloc
165
+ res = X11.xGetClassHint(@xdisplay,@xid,classHint)
166
+ if res != 0 then
167
+ @class = classHint.res_name.to_s
168
+ @hint = classHint.res_class.to_s
169
+ X11.xFree classHint.res_name
170
+ X11.xFree classHint.res_class
171
+ end
172
+ end
173
+
174
+ # Create an X11 Key Event for this window and set defaults
175
+ def create_key_event
176
+ ke = XKeyEvent.malloc
177
+ ke.serial = 0
178
+ ke.send_event = 1
179
+ ke.display = @xdisplay
180
+ ke.window = @xid
181
+ ke.subwindow = 0
182
+ ke.root = tree[:root]
183
+ ke.time = Time.now.sec
184
+ ke.state = 0
185
+ ke.same_screen = 0
186
+ return ke
187
+ end
188
+
189
+ end
190
+
191
+ end
192
+