firewatir 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+