lib/firewatir/firefox.rb in firewatir-1.6.2 vs lib/firewatir/firefox.rb in firewatir-1.6.5
- old
+ new
@@ -1,1127 +1,1040 @@
-=begin rdoc
- This is FireWatir, Web Application Testing In Ruby using Firefox browser
-
- Typical usage:
- # include the controller
- require "firewatir"
-
- # go to the page you want to test
- ff = FireWatir::Firefox.start("http://myserver/mypage")
-
- # enter "Angrez" into an input field named "username"
- ff.text_field(:name, "username").set("Angrez")
-
- # enter "Ruby Co" into input field with id "company_ID"
- ff.text_field(:id, "company_ID").set("Ruby Co")
-
- # click on a link that has "green" somewhere in the text that is displayed
- # to the user, using a regular expression
- ff.link(:text, /green/)
-
- # click button that has a caption of "Cancel"
- ff.button(:value, "Cancel").click
-
- FireWatir allows your script to read and interact with HTML objects--HTML tags
- and their attributes and contents. Types of objects that FireWatir can identify
- include:
-
- Type Description
- =========== ===============================================================
- button <input> tags, with the type="button" attribute
- check_box <input> tags, with the type="checkbox" attribute
- div <div> tags
- form
- frame
- hidden hidden <input> tags
- image <img> tags
- label
- link <a> (anchor) tags
- p <p> (paragraph) tags
- radio radio buttons; <input> tags, with the type="radio" attribute
- select_list <select> tags, known informally as drop-down boxes
- span <span> tags
- table <table> tags
- text_field <input> tags with the type="text" attribute (a single-line
- text field), the type="text_area" attribute (a multi-line
- text field), and the type="password" attribute (a
- single-line field in which the input is replaced with asterisks)
-
- In general, there are several ways to identify a specific object. FireWatir's
- syntax is in the form (how, what), where "how" is a means of identifying
- the object, and "what" is the specific string or regular expression
- that FireWatir will seek, as shown in the examples above. Available "how"
- options depend upon the type of object, but here are a few examples:
-
- How Description
- ============ ===============================================================
- :id Used to find an object that has an "id=" attribute. Since each
- id should be unique, according to the XHTML specification,
- this is recommended as the most reliable method to find an
- object.
- :name Used to find an object that has a "name=" attribute. This is
- useful for older versions of HTML, but "name" is deprecated
- in XHTML.
- :value Used to find a text field with a given default value, or a
- button with a given caption
- :index Used to find the nth object of the specified type on a page.
- For example, button(:index, 2) finds the second button.
- Current versions of FireWatir use 1-based indexing, but future
- versions will use 0-based indexing.
- :xpath The xpath expression for identifying the element.
-
- Note that the XHTML specification requires that tags and their attributes be
- in lower case. FireWatir doesn't enforce this; FireWatir will find tags and
- attributes whether they're in upper, lower, or mixed case. This is either
- a bug or a feature.
-
- FireWatir uses JSSh for interacting with the browser. For further information on
- Firefox and DOM go to the following Web page:
-
- http://www.xulplanet.com/references/objref/
-
-=end
-
-module FireWatir
- include Watir::Exception
-
- class Firefox
-
- include FireWatir::Container
-
- # XPath Result type. Return only first node that matches the xpath expression.
- # More details: "http://developer.mozilla.org/en/docs/DOM:document.evaluate"
- FIRST_ORDERED_NODE_TYPE = 9
-
- # variable to check if firefox browser has been started or not. Currently this is
- # used only while starting firefox on windows. For other platforms you need to start
- # firefox manually.
- #@@firefox_started = false
-
- # Stack to hold windows.
- @@window_stack = Array.new
-
- # This allows us to identify the window uniquely and close them accordingly.
- @window_title = nil
- @window_url = nil
-
- # 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:
- # :waitTime - 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.
-
- # TODO: Start the firefox version given by user. For example
- # ff = FireWatir::Firefox.new("1.5.0.4")
- #
-
- def initialize(options = {})
- if(options.kind_of?(Integer))
- options = {:waitTime => options}
- end
-
- if(options[:profile])
- profile_opt = "-no-remote -P #{options[:profile]}"
- else
- profile_opt = ""
- end
-
- waitTime = options[:waitTime] || 2
-
- case RUBY_PLATFORM
- when /mswin/
- # Get the path to Firefox.exe using Registry.
- require 'win32/registry.rb'
- path_to_bin = ""
- Win32::Registry::HKEY_LOCAL_MACHINE.open('SOFTWARE\Mozilla\Mozilla Firefox') do |reg|
- keys = reg.keys
- reg1 = Win32::Registry::HKEY_LOCAL_MACHINE.open("SOFTWARE\\Mozilla\\Mozilla Firefox\\#{keys[0]}\\Main")
- reg1.each do |subkey, type, data|
- if(subkey =~ /pathtoexe/i)
- path_to_bin = data
- end
- end
- end
-
- when /linux/i
- path_to_bin = `which firefox`.strip
- when /darwin/i
- path_to_bin = '/Applications/Firefox.app/Contents/MacOS/firefox'
- when /java/
- raise "Not implemented: Create a browser finder in JRuby"
- end
- @t = Thread.new { system("#{path_to_bin} -jssh #{profile_opt}")}
- sleep waitTime
-
- set_defaults()
- get_window_number()
- set_browser_document()
- end
-
- #
- # Description:
- # Creates a new instance of Firefox. Loads the URL and return the instance.
- #
- # Input:
- # url - url of the page to be loaded.
- #
- # Output:
- # New instance of firefox browser with the given url loaded.
- #
- def self.start(url)
- ff = Firefox.new
- ff.goto(url)
- return ff
- end
-
- #
- # Description:
- # Gets the window number opened. Used internally by Firewatir.
- #
- def get_window_number()
- $jssh_socket.send("getWindows().length;\n", 0)
- @@current_window = read_socket().to_i - 1
-
- # Derek Berner 5/16/08
- # If at any time a non-browser window like the "Downloads" window
- # pops up, it will become the topmost window, so make sure we
- # ignore it.
- @@current_window = js_eval("getWindows().length").to_i - 1
- while js_eval("getWindows()[#{@@current_window}].getBrowser") == ''
- @@current_window -= 1;
- end
-
- # This will store the information about the window.
- #@@window_stack.push(@@current_window)
- #puts "here in get_window_number window number is #{@@current_window}"
- return @@current_window
- end
- private :get_window_number
-
- #
- # Description:
- # Loads the given url in the browser. Waits for the page to get loaded.
- #
- # Input:
- # url - url to be loaded.
- #
- def goto(url)
- #set_defaults()
- get_window_number()
- set_browser_document()
- # Load the given url.
- $jssh_socket.send("#{BROWSER_VAR}.loadURI(\"#{url}\");\n" , 0)
- read_socket()
-
- wait()
- end
-
- #
- # Description:
- # Loads the previous page (if there is any) in the browser. Waits for the page to get loaded.
- #
- def back()
- #set_browser_document()
- $jssh_socket.send("if(#{BROWSER_VAR}.canGoBack) #{BROWSER_VAR}.goBack();\n", 0)
- read_socket();
- wait()
- end
-
- #
- # Description:
- # Loads the next page (if there is any) in the browser. Waits for the page to get loaded.
- #
- def forward()
- #set_browser_document()
- $jssh_socket.send("if(#{BROWSER_VAR}.canGoForward) #{BROWSER_VAR}.goForward();\n", 0)
- read_socket();
- wait()
- end
-
- #
- # Description:
- # Reloads the current page in the browser. Waits for the page to get loaded.
- #
- def refresh()
- #set_browser_document()
- $jssh_socket.send("#{BROWSER_VAR}.reload();\n", 0)
- read_socket();
- wait()
- end
-
- #
- # Description:
- # This function creates a new socket at port 9997 and sets the default values for instance and class variables.
- # Generatesi UnableToStartJSShException if cannot connect to jssh even after 3 tries.
- #
- def set_defaults(no_of_tries = 0)
- # JSSH listens on port 9997. Create a new socket to connect to port 9997.
- begin
- $jssh_socket = TCPSocket::new(MACHINE_IP, "9997")
- $jssh_socket.sync = true
- read_socket()
- rescue
- no_of_tries += 1
- retry if no_of_tries < 3
- 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"
- end
- @error_checkers = []
- end
- private :set_defaults
-
- def set_slow_speed
- @typingspeed = DEFAULT_TYPING_SPEED
- @defaultSleepTime = DEFAULT_SLEEP_TIME
- end
- private :set_slow_speed
-
- #
- # Description:
- # Sets the document, window and browser variables to point to correct object in JSSh.
- #
- def set_browser_document
- # Get the window in variable WINDOW_VAR.
- # Get the browser in variable BROWSER_VAR.
- jssh_command = "var #{WINDOW_VAR} = getWindows()[#{@@current_window}];"
- jssh_command += " var #{BROWSER_VAR} = #{WINDOW_VAR}.getBrowser();"
- # Get the document and body in variable DOCUMENT_VAR and BODY_VAR respectively.
- jssh_command += "var #{DOCUMENT_VAR} = #{BROWSER_VAR}.contentDocument;"
- jssh_command += "var #{BODY_VAR} = #{DOCUMENT_VAR}.body;"
-
- $jssh_socket.send("#{jssh_command}\n", 0)
- read_socket()
-
- # Get window and window's parent title and url
- $jssh_socket.send("#{DOCUMENT_VAR}.title;\n", 0)
- @window_title = read_socket()
- $jssh_socket.send("#{DOCUMENT_VAR}.URL;\n", 0)
- @window_url = read_socket()
- end
- private :set_browser_document
-
- #
- # Description:
- # Closes the window.
- #
- def close()
- #puts "current window number is : #{@@current_window}"
- # Derek Berner 5/16/08
- # Try to join thread only if there is exactly one open window
- if js_eval("getWindows().length").to_i == 1
- $jssh_socket.send(" getWindows()[0].close(); \n", 0)
- @t.join if @t != nil
- #sleep 5
- else
- # Check if window exists, because there may be the case that it has been closed by click event on some element.
- # For e.g: Close Button, Close this Window link etc.
- window_number = find_window("url", @window_url)
-
- # If matching window found. Close the window.
- if(window_number > 0)
- $jssh_socket.send(" getWindows()[#{window_number}].close();\n", 0)
- read_socket();
- end
-
- #Get the parent window url from the stack and return that window.
- #@@current_window = @@window_stack.pop()
- @window_url = @@window_stack.pop()
- @window_title = @@window_stack.pop()
- # Find window with this url.
- window_number = find_window("url", @window_url)
- @@current_window = window_number
- set_browser_document()
- end
- end
-
- #
- # Description:
- # 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.
- #
- def attach(how, what)
- window_number = find_window(how, what)
-
- if(window_number == 0)
- raise NoMatchingWindowFoundException.new("Unable to locate window, using #{how} and #{what}")
- elsif(window_number > 0)
- # Push the window_title and window_url of parent window. So that when we close the child window
- # appropriate handle of parent window is returned back.
- @@window_stack.push(@window_title)
- @@window_stack.push(@window_url)
-
- @@current_window = window_number.to_i
- set_browser_document()
- end
- self
- end
-
- #
- # Description:
- # Finds a Firefox browser window with a given title or url.
- #
- def find_window(how, what)
- jssh_command = "getWindows().length;";
- $jssh_socket.send("#{jssh_command}\n", 0)
- @@total_windows = read_socket()
- #puts "total windows are : " + @@total_windows.to_s
-
- jssh_command = "var windows = getWindows(); var window_number = 0;var found = false;
- for(var i = 0; i < windows.length; i++)
- {
- var attribute = '';
- if(\"#{how}\" == \"url\")
- {
- attribute = windows[i].getBrowser().contentDocument.URL;
- }
- if(\"#{how}\" == \"title\")
- {
- attribute = windows[i].getBrowser().contentDocument.title;
- }"
- if(what.class == Regexp)
- # Construct the regular expression because we can't use it directly by converting it to string.
- # If reg ex is /Google/i then its string conversion will be (?i-mx:Google) so we can't use it.
- # Construct the regular expression again from the string conversion.
- oldRegExp = what.to_s
- newRegExp = "/" + what.source + "/"
- flags = oldRegExp.slice(2, oldRegExp.index(':') - 2)
-
- for i in 0..flags.length do
- flag = flags[i, 1]
- if(flag == '-')
- break;
- else
- newRegExp << flag
- end
- end
-
- jssh_command += "var regExp = new RegExp(#{newRegExp});
- found = regExp.test(attribute);"
- else
- jssh_command += "found = (attribute == \"#{what}\");"
- end
-
- jssh_command += "if(found)
- {
- window_number = i;
- break;
- }
- }
- window_number;"
-
- jssh_command.gsub!(/\n/, "")
- #puts "jssh_command is : #{jssh_command}"
- $jssh_socket.send("#{jssh_command}\n", 0)
- window_number = read_socket()
- #puts "window number is : " + window_number.to_s
-
- return window_number.to_i
- end
- private :find_window
-
- #
- # Description:
- # Matches the given text with the current text shown in the browser.
- #
- # Input:
- # target - Text to match. Can be a string or regex
- #
- # Output:
- # Returns the index if the specified text was found.
- # Returns matchdata object if the specified regexp was found.
- #
- def contains_text(target)
- #puts "Text to match is : #{match_text}"
- #puts "Html is : #{self.text}"
- case target
- when Regexp
- self.text.match(target)
- when String
- self.text.index(target)
- else
- raise ArgumentError, "Argument #{target} should be a string or regexp."
- end
- end
-
- #
- # Description:
- # Returns the url of the page currently loaded in the browser.
- #
- # Output:
- # URL of the page.
- #
- def url()
- @window_url
- end
-
- #
- # Description:
- # Returns the title of the page currently loaded in the browser.
- #
- # Output:
- # Title of the page.
- #
- def title()
- @window_title
- end
-
- #
- # Description:
- # Returns the html of the page currently loaded in the browser.
- #
- # Output:
- # HTML shown on the page.
- #
- def html()
- $jssh_socket.send("var htmlelem = #{DOCUMENT_VAR}.getElementsByTagName('html')[0]; htmlelem.innerHTML;\n", 0)
- #$jssh_socket.send("#{BODY_VAR}.innerHTML;\n", 0)
- result = read_socket()
- return "<html>" + result + "</html>"
- end
-
- #
- # Description:
- # Returns the text of the page currently loaded in the browser.
- #
- # Output:
- # Text shown on the page.
- #
- def text()
- $jssh_socket.send("#{BODY_VAR}.textContent;\n", 0)
- return read_socket().strip
- end
-
- #
- # Description:
- # Maximize the current browser window.
- #
- def maximize()
- $jssh_socket.send("#{WINDOW_VAR}.maximize();\n", 0)
- read_socket()
- end
-
- #
- # Description:
- # Minimize the current browser window.
- #
- def minimize()
- $jssh_socket.send("#{WINDOW_VAR}.minimize();\n", 0)
- read_socket()
- end
-
- #
- # Description:
- # Waits for the page to get loaded.
- #
- def wait(last_url = nil)
- #puts "In wait function "
- isLoadingDocument = ""
- start = Time.now
-
- while isLoadingDocument != "false"
- isLoadingDocument = js_eval("#{BROWSER_VAR}=#{WINDOW_VAR}.getBrowser(); #{BROWSER_VAR}.webProgress.isLoadingDocument;")
- #puts "Is browser still loading page: #{isLoadingDocument}"
-
- # Derek Berner 5/16/08
- # Raise an exception if the page fails to load
- if (Time.now - start) > 300
- raise "Page Load Timeout"
- end
- end
- # Derek Berner 5/16/08
- # 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.
- $jssh_socket.send("#{BROWSER_VAR}.contentDocument.URL;\n", 0)
- url = read_socket()
-
- if(url != 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.
- jssh_command = "var wait = -1; var meta = null; meta = #{BROWSER_VAR}.contentDocument.getElementsByTagName('meta');
- if(meta != null)
- {
- var doc_url = #{BROWSER_VAR}.contentDocument.URL;
- for(var i=0; i< meta.length;++i)
- {
- var content = meta[i].content;
- var regex = new RegExp(\"^refresh$\", \"i\");
- if(regex.test(meta[i].httpEquiv))
- {
- var arrContent = content.split(';');
- var redirect_url = null;
- if(arrContent.length > 0)
- {
- if(arrContent.length > 1)
- redirect_url = arrContent[1];
-
- if(redirect_url != null)
- {
- regex = new RegExp(\"^.*\" + redirect_url + \"$\");
- if(!regex.test(doc_url))
- {
- wait = arrContent[0];
- }
- }
- break;
- }
- }
- }
- }
- wait;"
- #puts "command in wait is : #{jssh_command}"
- jssh_command = jssh_command.gsub(/\n/, "")
- $jssh_socket.send("#{jssh_command}; \n", 0)
- wait_time = read_socket();
- #puts "wait time is : #{wait_time}"
- begin
- wait_time = wait_time.to_i
- if(wait_time != -1)
- sleep(wait_time)
- # Call wait again. In case there are multiple redirects.
- $jssh_socket.send("#{BROWSER_VAR} = #{WINDOW_VAR}.getBrowser(); \n",0)
- read_socket()
- wait(url)
- end
- rescue
- end
- end
- set_browser_document()
- run_error_checks()
- return self
- end
-
- # Add an error checker that gets called on every page load.
- #
- # * checker - a Proc object
- def add_checker(checker)
- @error_checkers << checker
- end
-
- # Disable an error checker
- #
- # * checker - a Proc object that is to be disabled
- def disable_checker(checker)
- @error_checkers.delete(checker)
- end
-
- # Run the predefined error checks. This is automatically called on every page load.
- def run_error_checks
- @error_checkers.each { |e| e.call(self) }
- end
-
-
- #def jspopup_appeared(popupText = "", wait = 2)
- # winHelper = WindowHelper.new()
- # return winHelper.hasPopupAppeared(popupText, wait)
- #end
-
- #
- # Description:
- # Redefines the alert and confirm methods on the basis of button to be clicked.
- # This is done so that JSSh doesn't get blocked. You should use click_no_wait method before calling this function.
- #
- # Typical Usage:
- # ff.button(:id, "button").click_no_wait
- # ff.click_jspopup_button("OK")
- #
- # Input:
- # button - JavaScript button to be clicked. Values can be OK or Cancel
- #
- #def click_jspopup_button(button)
- # button = button.downcase
- # element = Element.new(nil)
- # element.click_js_popup(button)
- #end
-
- #
- # Description:
- # Tells FireWatir to click javascript button in case one comes after performing some action on an element. Matches
- # text of pop up with one if supplied as parameter. If text matches clicks the button else stop script execution until
- # pop up is dismissed by manual intervention.
- #
- # Input:
- # button - JavaScript button to be clicked. Values can be OK or Cancel
- # waitTime - Time to wait for pop up to come. Not used just for compatibility with Watir.
- # userInput - Not used just for compatibility with Watir
- # text - Text that should appear on pop up.
- #
- def startClicker(button, waitTime = 1, userInput = nil, text = nil)
- jssh_command = "var win = #{BROWSER_VAR}.contentWindow;"
- if(button =~ /ok/i)
- jssh_command += "var popuptext = '';
- var old_alert = win.alert;
- var old_confirm = win.confirm;
- win.alert = function(param) {"
- if(text != nil)
- jssh_command += "if(param == \"#{text}\") {
- popuptext = param;
- return true;
- }
- else {
- popuptext = param;
- win.alert = old_alert;
- win.alert(param);
- }"
- else
- jssh_command += "popuptext = param; return true;"
- end
- jssh_command += "};
- win.confirm = function(param) {"
- if(text != nil)
- jssh_command += "if(param == \"#{text}\") {
- popuptext = param;
- return true;
- }
- else {
- win.confirm = old_confirm;
- win.confirm(param);
- }"
- else
- jssh_command += "popuptext = param; return true;"
- end
- jssh_command += "};"
-
- elsif(button =~ /cancel/i)
- jssh_command = "var old_confirm = win.confirm;
- win.confirm = function(param) {"
- if(text != nil)
- jssh_command += "if(param == \"#{text}\") {
- popuptext = param;
- return false;
- }
- else {
- win.confirm = old_confirm;
- win.confirm(param);
- }"
- else
- jssh_command += "popuptext = param; return false;"
- end
- jssh_command += "};"
- end
- jssh_command.gsub!(/\n/, "")
- #puts "jssh command sent for js pop up is : #{jssh_command}"
- $jssh_socket.send("#{jssh_command}\n", 0)
- read_socket()
- end
-
- #
- # Description:
- # Returns text of javascript pop up in case it comes.
- #
- # Output:
- # Text shown in javascript pop up.
- #
- def get_popup_text()
- $jssh_socket.send("popuptext;\n", 0)
- return_value = read_socket()
- # reset the variable
- $jssh_socket.send("popuptext = '';\n", 0)
- read_socket()
- return return_value
- end
-
- #
- # Description:
- # Returns the document element of the page currently loaded in the browser.
- #
- # Output:
- # Document element.
- #
- def document
- Document.new("#{DOCUMENT_VAR}")
- end
-
- #
- # Description:
- # Returns the first element that matches the xpath query.
- #
- # Input:
- # Xpath expression or query.
- #
- # Output:
- # Element matching the xpath query.
- #
- def element_by_xpath(xpath)
- temp = Element.new(nil, self)
- element_name = temp.element_by_xpath(self, xpath)
- return element_factory(element_name)
- end
-
- #
- # Description:
- # Factory method to create object of correct Element class while using XPath to get the element.
- #
- def element_factory(element_name)
- jssh_type = Element.new(element_name,self).element_type
- #puts "jssh type is : #{jssh_type}" # DEBUG
- candidate_class = jssh_type =~ /HTML(.*)Element/ ? $1 : ''
- #puts candidate_class # DEBUG
- if candidate_class == 'Input'
- $jssh_socket.send("#{element_name}.type;\n", 0)
- input_type = read_socket().downcase.strip
- puts input_type # DEBUG
- firewatir_class = input_class(input_type)
- else
- firewatir_class = jssh2firewatir(candidate_class)
- end
-
- #puts firewatir_class # DEBUG
- klass = FireWatir.const_get(firewatir_class)
-
- if klass == Element
- klass.new(element_name,self)
- elsif klass == CheckBox
- klass.new(self,:jssh_name,element_name,["checkbox"])
- elsif klass == Radio
- klass.new(self,:jssh_name,element_name,["radio"])
- else
- klass.new(self,:jssh_name,element_name)
- end
- end
- private :element_factory
-
- #
- # Description:
- # Get the class name for element of input type depending upon its type like checkbox, radio etc.
- #
- def input_class(input_type)
- hash = {
- 'select-one' => 'SelectList',
- 'select-multiple' => 'SelectList',
- 'text' => 'TextField',
- 'password' => 'TextField',
- 'textarea' => 'TextField',
- # TODO when there's no type, it's a TextField
- 'file' => 'FileField',
- 'checkbox' => 'CheckBox',
- 'radio' => 'Radio',
- 'reset' => 'Button',
- 'button' => 'Button',
- 'submit' => 'Button',
- 'image' => 'Button'
- }
- hash.default = 'Element'
-
- hash[input_type]
- end
- private :input_class
-
- #
- # Description:
- # Converts element type returned by JSSh like HTMLDivElement to its corresponding class in Firewatir.
- #
- def jssh2firewatir(candidate_class)
- hash = {
- 'Div' => 'Div',
- 'Button' => 'Button',
- 'Frame' => 'Frame',
- 'Span' => 'Span',
- 'Paragraph' => 'P',
- 'Label' => 'Label',
- 'Form' => 'Form',
- 'Image' => 'Image',
- 'Table' => 'Table',
- 'TableCell' => 'TableCell',
- 'TableRow' => 'TableRow',
- 'Select' => 'SelectList',
- 'Link' => 'Link',
- 'Anchor' => 'Link' # FIXME is this right?
- #'Option' => 'Option' #Option uses a different constructor
- }
- hash.default = 'Element'
- hash[candidate_class]
- end
- private :jssh2firewatir
-
- #
- # Description:
- # Returns the array of elements that matches the xpath query.
- #
- # Input:
- # Xpath expression or query.
- #
- # Output:
- # Array of elements matching xpath query.
- #
- def elements_by_xpath(xpath)
- element = Element.new(nil, self)
- elem_names = element.elements_by_xpath(self, xpath)
- elem_names.inject([]) {|elements,name| elements << element_factory(name)}
- end
-
- #
- # Description:
- # Show all the forms available on the page.
- #
- # Output:
- # Name, id, method and action of all the forms available on the page.
- #
- def show_forms
- forms = Document.new(self).get_forms()
- count = forms.length
- puts "There are #{count} forms"
- for i in 0..count - 1 do
- puts "Form name: " + forms[i].name
- puts " id: " + forms[i].id
- puts " method: " + forms[i].attribute_value("method")
- puts " action: " + forms[i].action
- end
- end
- alias showForms show_forms
-
- #
- # Description:
- # Show all the images available on the page.
- #
- # Output:
- # Name, id, src and index of all the images available on the page.
- #
- def show_images
- images = Document.new(self).get_images
- puts "There are #{images.length} images"
- index = 1
- images.each do |l|
- puts "image: name: #{l.name}"
- puts " id: #{l.id}"
- puts " src: #{l.src}"
- puts " index: #{index}"
- index += 1
- end
- end
- alias showImages show_images
-
- #
- # Description:
- # Show all the links available on the page.
- #
- # Output:
- # Name, id, href and index of all the links available on the page.
- #
- def show_links
- links = Document.new(self).get_links
- puts "There are #{links.length} links"
- index = 1
- links.each do |l|
- puts "link: name: #{l.name}"
- puts " id: #{l.id}"
- puts " href: #{l.href}"
- puts " index: #{index}"
- index += 1
- end
- end
- alias showLinks show_links
-
- #
- # Description:
- # Show all the divs available on the page.
- #
- # Output:
- # Name, id, class and index of all the divs available on the page.
- #
- def show_divs
- divs = Document.new(self).get_divs
- puts "There are #{divs.length} divs"
- index = 1
- divs.each do |l|
- puts "div: name: #{l.name}"
- puts " id: #{l.id}"
- puts " class: #{l.className}"
- puts " index: #{index}"
- index += 1
- end
- end
- alias showDivs show_divs
-
- #
- # Description:
- # Show all the tables available on the page.
- #
- # Output:
- # Id, row count, column count (only first row) and index of all the tables available on the page.
- #
- def show_tables
- tables = Document.new(self).get_tables
- puts "There are #{tables.length} tables"
- index = 1
- tables.each do |l|
- puts "table: id: #{l.id}"
- puts " rows: #{l.row_count}"
- puts " columns: #{l.column_count}"
- puts " index: #{index}"
- index += 1
- end
- end
- alias showTables show_tables
-
- #
- # Description:
- # Show all the pre elements available on the page.
- #
- # Output:
- # Id, name and index of all the pre elements available on the page.
- #
- def show_pres
- pres = Document.new(self).get_pres
- puts "There are #{pres.length} pres"
- index = 1
- pres.each do |l|
- puts "pre: id: #{l.id}"
- puts " name: #{l.name}"
- puts " index: #{index}"
- index += 1
- end
- end
- alias showPres show_pres
-
- #
- # Description:
- # Show all the spans available on the page.
- #
- # Output:
- # Name, id, class and index of all the spans available on the page.
- #
- def show_spans
- spans = Document.new(self).get_spans
- puts "There are #{spans.length} spans"
- index = 1
- spans.each do |l|
- puts "span: name: #{l.name}"
- puts " id: #{l.id}"
- puts " class: #{l.className}"
- puts " index: #{index}"
- index += 1
- end
- end
- alias showSpans show_spans
-
- #
- # Description:
- # Show all the labels available on the page.
- #
- # Output:
- # Name, id, for and index of all the labels available on the page.
- #
- def show_labels
- labels = Document.new(self).get_labels
- puts "There are #{labels.length} labels"
- index = 1
- labels.each do |l|
- puts "label: name: #{l.name}"
- puts " id: #{l.id}"
- puts " for: #{l.for}"
- puts " index: #{index}"
- index += 1
- end
- end
- alias showLabels show_labels
-
- #
- # Description:
- # Show all the frames available on the page. Doesn't show nested frames.
- #
- # Output:
- # Name, and index of all the frames available on the page.
- #
- def show_frames
- jssh_command = "var frameset = #{WINDOW_VAR}.frames;
- var elements_frames = new Array();
- for(var i = 0; i < frameset.length; i++)
- {
- var frames = frameset[i].frames;
- for(var j = 0; j < frames.length; j++)
- {
- elements_frames.push(frames[j].frameElement);
- }
- }
- elements_frames.length;"
-
- jssh_command.gsub!("\n", "")
- $jssh_socket.send("#{jssh_command};\n", 0)
- length = read_socket().to_i
-
- puts "There are #{length} frames"
-
- frames = Array.new(length)
- for i in 0..length - 1 do
- frames[i] = Frame.new(self, :jssh_name, "elements_frames[#{i}]")
- end
-
- for i in 0..length - 1 do
- puts "frame: name: #{frames[i].name}"
- puts " index: #{i+1}"
- end
- end
- alias showFrames show_frames
-
- # 5/16/08 Derek Berner
- # Wrapper method to send JS commands concisely,
- # and propagate errors
- def js_eval(str)
- #puts "JS Eval: #{str}"
- $jssh_socket.send("#{str};\n",0)
- value = read_socket()
- if md=/^(\w+)Error:(.*)$/.match(value)
- eval "class JS#{md[1]}Error\nend"
- raise (eval "JS#{md[1]}Error"), md[2]
- end
- #puts "Value: #{value}"
- value
- end
-
- end # Class Firefox
-
- #
- # Module for handling the Javascript pop-ups. Not in use currently, will be available in future.
- # Use ff.startClicker() method for clicking javascript pop ups. Refer to unit tests on how to handle
- # javascript pop up (unittests/javascript_test.rb)
- #module Dialog
- # # Class for handling javascript popup. Not in use currently, will be available in future. See unit tests on how to handle
- # # javascript pop up (unittests/javascript_test.rb).
- # class JSPopUp
- # include Container
- #
- # def has_appeared(text)
- # require 'socket'
- # sleep 4
- # shell = TCPSocket.new("localhost", 9997)
- # read_socket(shell)
- # #jssh_command = "var url = #{DOCUMENT_VAR}.URL;"
- # jssh_command = "var length = getWindows().length; var win;length;\n"
- # #jssh_command += "for(var i = 0; i < length; i++)"
- # #jssh_command += "{"
- # #jssh_command += " win = getWindows()[i];"
- # #jssh_command += " if(win.opener != null && "
- # #jssh_command += " win.title == \"[JavaScript Application]\" &&"
- # #jssh_command += " win.opener.document.URL == url)"
- # #jssh_command += " {"
- # #jssh_command += " break;"
- # #jssh_command += " }"
- # #jssh_command += "}"
- #
- # #jssh_command += " win.title;\n";
- # #jssh_command += "var dialog = win.document.childNodes[0];"
- # #jssh_command += "vbox = dialog.childNodes[1].childNodes[1];"
- # #jssh_command += "vbox.childNodes[1].childNodes[0].childNodes[0].textContent;\n"
- # puts jssh_command
- # shell.send("#{jssh_command}", 0)
- # jstext = read_socket(shell)
- # puts jstext
- # return jstext == text
- # end
- # end
- #end
-
-end
+=begin rdoc
+ This is FireWatir, Web Application Testing In Ruby using Firefox browser
+
+ Typical usage:
+ # include the controller
+ require "firewatir"
+
+ # go to the page you want to test
+ ff = FireWatir::Firefox.start("http://myserver/mypage")
+
+ # enter "Angrez" into an input field named "username"
+ ff.text_field(:name, "username").set("Angrez")
+
+ # enter "Ruby Co" into input field with id "company_ID"
+ ff.text_field(:id, "company_ID").set("Ruby Co")
+
+ # click on a link that has "green" somewhere in the text that is displayed
+ # to the user, using a regular expression
+ ff.link(:text, /green/)
+
+ # click button that has a caption of "Cancel"
+ ff.button(:value, "Cancel").click
+
+ FireWatir allows your script to read and interact with HTML objects--HTML tags
+ and their attributes and contents. Types of objects that FireWatir can identify
+ include:
+
+ Type Description
+ =========== ===============================================================
+ button <input> tags, with the type="button" attribute
+ check_box <input> tags, with the type="checkbox" attribute
+ div <div> tags
+ form
+ frame
+ hidden hidden <input> tags
+ image <img> tags
+ label
+ link <a> (anchor) tags
+ p <p> (paragraph) tags
+ radio radio buttons; <input> tags, with the type="radio" attribute
+ select_list <select> tags, known informally as drop-down boxes
+ span <span> tags
+ table <table> tags
+ text_field <input> tags with the type="text" attribute (a single-line
+ text field), the type="text_area" attribute (a multi-line
+ text field), and the type="password" attribute (a
+ single-line field in which the input is replaced with asterisks)
+
+ In general, there are several ways to identify a specific object. FireWatir's
+ syntax is in the form (how, what), where "how" is a means of identifying
+ the object, and "what" is the specific string or regular expression
+ that FireWatir will seek, as shown in the examples above. Available "how"
+ options depend upon the type of object, but here are a few examples:
+
+ How Description
+ ============ ===============================================================
+ :id Used to find an object that has an "id=" attribute. Since each
+ id should be unique, according to the XHTML specification,
+ this is recommended as the most reliable method to find an
+ object.
+ :name Used to find an object that has a "name=" attribute. This is
+ useful for older versions of HTML, but "name" is deprecated
+ in XHTML.
+ :value Used to find a text field with a given default value, or a
+ button with a given caption
+ :index Used to find the nth object of the specified type on a page.
+ For example, button(:index, 2) finds the second button.
+ Current versions of FireWatir use 1-based indexing, but future
+ versions will use 0-based indexing.
+ :xpath The xpath expression for identifying the element.
+
+ Note that the XHTML specification requires that tags and their attributes be
+ in lower case. FireWatir doesn't enforce this; FireWatir will find tags and
+ attributes whether they're in upper, lower, or mixed case. This is either
+ a bug or a feature.
+
+ FireWatir uses JSSh for interacting with the browser. For further information on
+ Firefox and DOM go to the following Web page:
+
+ http://www.xulplanet.com/references/objref/
+
+=end
+
+module FireWatir
+ include Watir::Exception
+
+ class Firefox
+ include FireWatir::Container
+
+ # XPath Result type. Return only first node that matches the xpath expression.
+ # More details: "http://developer.mozilla.org/en/docs/DOM:document.evaluate"
+ FIRST_ORDERED_NODE_TYPE = 9
+
+ # 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:
+ # :waitTime - 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.
+
+ def initialize(options = {})
+ if(options.kind_of?(Integer))
+ options = {:waitTime => options}
+ 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)
+ jssh_down = false
+ begin
+ set_defaults()
+ rescue Watir::Exception::UnableToStartJSShException
+ jssh_down = true
+ end
+
+ if current_os == :macosx && !%x{ps x | grep firefox-bin | grep -v grep}.empty?
+ raise "Firefox is running without -jssh" if jssh_down
+ open_window unless options[:suppress_launch_process]
+ elsif not options[:suppress_launch_process]
+ launch_browser(options)
+ end
+
+ set_defaults()
+ get_window_number()
+ set_browser_document()
+ end
+
+ def inspect
+ '#<%s:0x%x url=%s title=%s>' % [self.class, hash*2, url.inspect, title.inspect]
+ end
+
+
+ # Launches firebox browser
+ # options as .new
+
+ def launch_browser(options = {})
+
+ if(options[:profile])
+ profile_opt = "-no-remote -P #{options[:profile]}"
+ else
+ profile_opt = ""
+ end
+
+ bin = path_to_bin()
+ @t = Thread.new { system("#{bin} -jssh #{profile_opt}") }
+ sleep options[:waitTime] || 2
+
+ end
+ private :launch_browser
+
+ # Creates a new instance of Firefox. Loads the URL and return the instance.
+ # Input:
+ # url - url of the page to be loaded.
+ def self.start(url)
+ ff = Firefox.new
+ ff.goto(url)
+ return ff
+ end
+
+ # Gets the window number opened.
+ # Currently, this returns the most recently opened window, which may or may
+ # not be the current window.
+ def get_window_number()
+ # If at any time a non-browser window like the "Downloads" window
+ # pops up, it will become the topmost window, so make sure we
+ # ignore it.
+ window_count = js_eval("getWindows().length").to_i - 1
+ while js_eval("getWindows()[#{window_count}].getBrowser") == ''
+ window_count -= 1;
+ end
+
+ # now correctly handles instances where only browserless windows are open
+ # opens one we can use if count is 0
+
+ if window_count < 0
+ open_window
+ window_count = 1
+ end
+ @window_index = window_count
+ end
+ private :get_window_number
+
+ # Loads the given url in the browser. Waits for the page to get loaded.
+ def goto(url)
+ get_window_number()
+ set_browser_document()
+ js_eval "#{browser_var}.loadURI(\"#{url}\")"
+ wait()
+ end
+
+ # Loads the previous page (if there is any) in the browser. Waits for the page to get loaded.
+ def back()
+ js_eval "if(#{browser_var}.canGoBack) #{browser_var}.goBack()"
+ wait()
+ end
+
+ # Loads the next page (if there is any) in the browser. Waits for the page to get loaded.
+ def forward()
+ js_eval "if(#{browser_var}.canGoForward) #{browser_var}.goForward()"
+ wait()
+ end
+
+ # Reloads the current page in the browser. Waits for the page to get loaded.
+ def refresh()
+ js_eval "#{browser_var}.reload()"
+ wait()
+ end
+
+ # Executes the given JavaScript string
+ def execute_script(source)
+ result = js_eval source.to_s
+ wait()
+
+ result
+ end
+
+ private
+ # This function creates a new socket at port 9997 and sets the default values for instance and class variables.
+ # Generatesi UnableToStartJSShException if cannot connect to jssh even after 3 tries.
+ def set_defaults(no_of_tries = 0)
+ # JSSH listens on port 9997. Create a new socket to connect to port 9997.
+ begin
+ $jssh_socket = TCPSocket::new(MACHINE_IP, "9997")
+ $jssh_socket.sync = true
+ read_socket()
+ rescue
+ no_of_tries += 1
+ retry if no_of_tries < 3
+ 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"
+ end
+ @error_checkers = []
+ end
+
+ # Sets the document, window and browser variables to point to correct object in JSSh.
+ def set_browser_document
+ # Add eventlistener for browser window so that we can reset the document back whenever there is redirect
+ # or browser loads on its own after some time. Useful when you are searching for flight results etc and
+ # page goes to search page after that it goes automatically to results page.
+ # Details : http://zenit.senecac.on.ca/wiki/index.php/Mozilla.dev.tech.xul#What_is_an_example_of_addProgressListener.3F
+ jssh_command = "var listObj = new Object();"; # create new object
+ jssh_command << "listObj.wpl = Components.interfaces.nsIWebProgressListener;"; # set the web progress listener.
+ jssh_command << "listObj.QueryInterface = function(aIID) {
+ if (aIID.equals(listObj.wpl) ||
+ aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ };" # set function to locate the object via QueryInterface
+ jssh_command << "listObj.onStateChange = function(aProgress, aRequest, aFlag, aStatus) {
+ if (aFlag & listObj.wpl.STATE_STOP) {
+ if ( aFlag & listObj.wpl.STATE_IS_NETWORK ) {
+ #{document_var} = #{browser_var}.contentDocument;
+ #{body_var} = #{document_var}.body;
+ }
+ }
+ };" # add function to be called when window state is change. When state is STATE_STOP &
+ # STATE_IS_NETWORK then only everything is loaded. Now we can reset our variables.
+ jssh_command.gsub!(/\n/, "")
+ js_eval jssh_command
+
+ jssh_command = "var #{window_var} = getWindows()[#{@window_index}];"
+ jssh_command << "var #{browser_var} = #{window_var}.getBrowser();"
+ # Add listener create above to browser object
+ jssh_command << "#{browser_var}.addProgressListener( listObj,Components.interfaces.nsIWebProgress.NOTIFY_STATE_WINDOW );"
+ jssh_command << "var #{document_var} = #{browser_var}.contentDocument;"
+ jssh_command << "var #{body_var} = #{document_var}.body;"
+ js_eval jssh_command
+
+ @window_title = js_eval "#{document_var}.title"
+ @window_url = js_eval "#{document_var}.URL"
+ end
+
+ public
+ def window_var
+ "window"
+ end
+ #private
+ def browser_var
+ "browser"
+ end
+ def document_var # unfinished
+ "document"
+ end
+ def body_var # unfinished
+ "body"
+ end
+
+ public
+ # Closes the window.
+ def close
+
+ if js_eval("getWindows().length").to_i == 1
+ js_eval("getWindows()[0].close()")
+
+ if current_os == :macosx
+ %x{ osascript -e 'tell application "Firefox" to quit' }
+ end
+
+ # wait for the app to close properly
+ @t.join if @t
+ else
+ # Check if window exists, because there may be the case that it has been closed by click event on some element.
+ # For e.g: Close Button, Close this Window link etc.
+ window_number = find_window(:url, @window_url)
+
+ # If matching window found. Close the window.
+ if window_number > 0
+ js_eval "getWindows()[#{window_number}].close()"
+ end
+
+ end
+ end
+
+ # 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.
+ def attach(how, what)
+
+ $stderr.puts("warning: #{self.class}.attach is experimental") if $VERBOSE
+ window_number = find_window(how, what)
+
+ if(window_number.nil?)
+ raise NoMatchingWindowFoundException.new("Unable to locate window, using #{how} and #{what}")
+ elsif(window_number >= 0)
+ @window_index = window_number
+ set_browser_document()
+ end
+ self
+ end
+
+ # 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.
+ # Watir::Browser.attach(:url, 'http://www.google.com')
+ # Watir::Browser.attach(:title, 'Google')
+ def self.attach how, what
+ br = new :suppress_launch_process => true # don't create window
+ br.attach(how, what)
+ br
+ end
+
+ # loads up a new window in an existing process
+ # Watir::Browser.attach() with no arguments passed the attach method will create a new window
+ # this will only be called one time per instance we're only ever going to run in 1 window
+
+ def open_window
+
+ if @opened_new_window
+ return @opened_new_window
+ end
+
+ jssh_command = "var windows = getWindows(); var window = windows[0];
+ window.open();
+ var windows = getWindows(); var window_number = windows.length - 1;
+ window_number;"
+
+ window_number = js_eval(jssh_command).to_i
+ @opened_new_window = window_number
+ return window_number if window_number >= 0
+ end
+ private :open_window
+
+ # return the window index for the browser window with the given title or url.
+ # how - :url or :title
+ # what - string or regexp
+ # Start searching windows in reverse order so that we attach/find the latest opened window.
+ def find_window(how, what)
+ jssh_command = "var windows = getWindows(); var window_number = false; var found = false;
+ for(var i = windows.length - 1; i >= 0; i--)
+ {
+ var attribute = '';
+ if(typeof(windows[i].getBrowser) != 'function')
+ {
+ continue;
+ }
+ var browser = windows[i].getBrowser();
+ if(!browser)
+ {
+ continue;
+ }
+ if(\"#{how}\" == \"url\")
+ {
+ attribute = browser.contentDocument.URL;
+ }
+ if(\"#{how}\" == \"title\")
+ {
+ attribute = browser.contentDocument.title;
+ }"
+ if(what.class == Regexp)
+ jssh_command << "var regExp = new RegExp(#{what.inspect});
+ found = regExp.test(attribute);"
+ else
+ jssh_command << "found = (attribute == \"#{what}\");"
+ end
+
+ jssh_command << "if(found)
+ {
+ window_number = i;
+ break;
+ }
+ }
+ window_number;"
+ window_number = js_eval(jssh_command).to_s
+ return window_number == 'false' ? nil : window_number.to_i
+ end
+ private :find_window
+
+ #
+ # Description:
+ # Matches the given text with the current text shown in the browser.
+ #
+ # Input:
+ # target - Text to match. Can be a string or regex
+ #
+ # Output:
+ # Returns the index if the specified text was found.
+ # Returns matchdata object if the specified regexp was found.
+ #
+ def contains_text(target)
+ #puts "Text to match is : #{match_text}"
+ #puts "Html is : #{self.text}"
+ case target
+ when Regexp
+ self.text.match(target)
+ when String
+ self.text.index(target)
+ else
+ raise TypeError, "Argument #{target} should be a string or regexp."
+ end
+ end
+
+ # Returns the url of the page currently loaded in the browser.
+ def url
+ @window_url = js_eval "#{document_var}.location"
+ end
+
+ # Returns the title of the page currently loaded in the browser.
+ def title
+ @window_title = js_eval "#{document_var}.title"
+ end
+
+ # Returns the Status of the page currently loaded in the browser from statusbar.
+ #
+ # Output:
+ # Status of the page.
+ #
+ def status
+ js_status = js_eval("#{window_var}.status")
+ js_status.empty? ? js_eval("#{WINDOW_VAR}.XULBrowserWindow.statusText;") : js_status
+ end
+
+
+ # Returns the html of the page currently loaded in the browser.
+ def html
+ result = js_eval("var htmlelem = #{document_var}.getElementsByTagName('html')[0]; htmlelem.innerHTML")
+ return "<html>" + result + "</html>"
+ end
+
+ # Returns the text of the page currently loaded in the browser.
+ def text
+ js_eval("#{body_var}.textContent").strip
+ end
+
+ # Maximize the current browser window.
+ def maximize()
+ js_eval "#{window_var}.maximize()"
+ end
+
+ # Minimize the current browser window.
+ def minimize()
+ js_eval "#{window_var}.minimize()"
+ end
+
+ # Waits for the page to get loaded.
+ def wait(last_url = nil)
+ #puts "In wait function "
+ isLoadingDocument = ""
+ start = Time.now
+
+ while isLoadingDocument != "false"
+ isLoadingDocument = js_eval("#{browser_var}=#{window_var}.getBrowser(); #{browser_var}.webProgress.isLoadingDocument;")
+ #puts "Is browser still loading page: #{isLoadingDocument}"
+
+ # Raise an exception if the page fails to load
+ if (Time.now - start) > 300
+ 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 = js_eval("#{browser_var}.contentDocument.URL")
+
+ if(url != 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.
+ jssh_command = "var wait = -1; var meta = null; meta = #{browser_var}.contentDocument.getElementsByTagName('meta');
+ if(meta != null)
+ {
+ var doc_url = #{browser_var}.contentDocument.URL;
+ for(var i=0; i< meta.length;++i)
+ {
+ var content = meta[i].content;
+ var regex = new RegExp(\"^refresh$\", \"i\");
+ if(regex.test(meta[i].httpEquiv))
+ {
+ var arrContent = content.split(';');
+ var redirect_url = null;
+ if(arrContent.length > 0)
+ {
+ if(arrContent.length > 1)
+ redirect_url = arrContent[1];
+
+ if(redirect_url != null)
+ {
+ regex = new RegExp(\"^.*\" + redirect_url + \"$\");
+ if(!regex.test(doc_url))
+ {
+ wait = arrContent[0];
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ wait;"
+ wait_time = js_eval(jssh_command).to_i
+ begin
+ if(wait_time != -1)
+ sleep(wait_time)
+ # Call wait again. In case there are multiple redirects.
+ js_eval "#{browser_var} = #{window_var}.getBrowser()"
+ wait(url)
+ end
+ rescue
+ end
+ end
+ set_browser_document()
+ run_error_checks()
+ return self
+ end
+
+ # Add an error checker that gets called on every page load.
+ # * checker - a Proc object
+ def add_checker(checker)
+ @error_checkers << checker
+ end
+
+ # Disable an error checker
+ # * checker - a Proc object that is to be disabled
+ def disable_checker(checker)
+ @error_checkers.delete(checker)
+ end
+
+ # Run the predefined error checks. This is automatically called on every page load.
+ def run_error_checks
+ @error_checkers.each { |e| e.call(self) }
+ end
+
+
+ #def jspopup_appeared(popupText = "", wait = 2)
+ # winHelper = WindowHelper.new()
+ # return winHelper.hasPopupAppeared(popupText, wait)
+ #end
+
+ #
+ # Description:
+ # Redefines the alert and confirm methods on the basis of button to be clicked.
+ # This is done so that JSSh doesn't get blocked. You should use click_no_wait method before calling this function.
+ #
+ # Typical Usage:
+ # ff.button(:id, "button").click_no_wait
+ # ff.click_jspopup_button("OK")
+ #
+ # Input:
+ # button - JavaScript button to be clicked. Values can be OK or Cancel
+ #
+ #def click_jspopup_button(button)
+ # button = button.downcase
+ # element = Element.new(nil)
+ # element.click_js_popup(button)
+ #end
+
+ #
+ # Description:
+ # Tells FireWatir to click javascript button in case one comes after performing some action on an element. Matches
+ # text of pop up with one if supplied as parameter. If text matches clicks the button else stop script execution until
+ # pop up is dismissed by manual intervention.
+ #
+ # Input:
+ # button - JavaScript button to be clicked. Values can be OK or Cancel
+ # waitTime - Time to wait for pop up to come. Not used just for compatibility with Watir.
+ # userInput - Not used just for compatibility with Watir
+ # text - Text that should appear on pop up.
+ #
+ def startClicker(button, waitTime = 1, userInput = nil, text = nil)
+ jssh_command = "var win = #{browser_var}.contentWindow;"
+ if(button =~ /ok/i)
+ jssh_command << "var popuptext = '';
+ var old_alert = win.alert;
+ var old_confirm = win.confirm;
+ win.alert = function(param) {"
+ if(text != nil)
+ jssh_command << "if(param == \"#{text}\") {
+ popuptext = param;
+ return true;
+ }
+ else {
+ popuptext = param;
+ win.alert = old_alert;
+ win.alert(param);
+ }"
+ else
+ jssh_command << "popuptext = param; return true;"
+ end
+ jssh_command << "};
+ win.confirm = function(param) {"
+ if(text != nil)
+ jssh_command << "if(param == \"#{text}\") {
+ popuptext = param;
+ return true;
+ }
+ else {
+ win.confirm = old_confirm;
+ win.confirm(param);
+ }"
+ else
+ jssh_command << "popuptext = param; return true;"
+ end
+ jssh_command << "};"
+
+ elsif(button =~ /cancel/i)
+ jssh_command = "var old_confirm = win.confirm;
+ win.confirm = function(param) {"
+ if(text != nil)
+ jssh_command << "if(param == \"#{text}\") {
+ popuptext = param;
+ return false;
+ }
+ else {
+ win.confirm = old_confirm;
+ win.confirm(param);
+ }"
+ else
+ jssh_command << "popuptext = param; return false;"
+ end
+ jssh_command << "};"
+ end
+ js_eval jssh_command
+ end
+
+ #
+ # Description:
+ # Returns text of javascript pop up in case it comes.
+ #
+ # Output:
+ # Text shown in javascript pop up.
+ #
+ def get_popup_text()
+ return_value = js_eval "popuptext"
+ # reset the variable
+ js_eval "popuptext = ''"
+ return return_value
+ end
+
+ # Returns the document element of the page currently loaded in the browser.
+ def document
+ Document.new(self)
+ end
+
+ # Returns the first element that matches the given xpath expression or query.
+ def element_by_xpath(xpath)
+ temp = Element.new(nil, self)
+ element_name = temp.element_by_xpath(self, xpath)
+ return element_factory(element_name)
+ end
+
+ # Return object of correct Element class while using XPath to get the element.
+ def element_factory(element_name)
+ jssh_type = Element.new(element_name,self).element_type
+ #puts "jssh type is : #{jssh_type}" # DEBUG
+ candidate_class = jssh_type =~ /HTML(.*)Element/ ? $1 : ''
+ #puts candidate_class # DEBUG
+ if candidate_class == 'Input'
+ input_type = js_eval("#{element_name}.type").downcase.strip
+ firewatir_class = input_class(input_type)
+ else
+ firewatir_class = jssh2firewatir(candidate_class)
+ end
+
+ #puts firewatir_class # DEBUG
+ klass = FireWatir.const_get(firewatir_class)
+
+ if klass == Element
+ klass.new(element_name,self)
+ elsif klass == CheckBox
+ klass.new(self,:jssh_name,element_name,["checkbox"])
+ elsif klass == Radio
+ klass.new(self,:jssh_name,element_name,["radio"])
+ else
+ klass.new(self,:jssh_name,element_name)
+ end
+ end
+ private :element_factory
+
+ # Return the class name for element of input type depending upon its type like checkbox, radio etc.
+ def input_class(input_type)
+ hash = {
+ 'select-one' => 'SelectList',
+ 'select-multiple' => 'SelectList',
+ 'text' => 'TextField',
+ 'password' => 'TextField',
+ 'textarea' => 'TextField',
+ # TODO when there's no type, it's a TextField
+ 'file' => 'FileField',
+ 'checkbox' => 'CheckBox',
+ 'radio' => 'Radio',
+ 'reset' => 'Button',
+ 'button' => 'Button',
+ 'submit' => 'Button',
+ 'image' => 'Button'
+ }
+ hash.default = 'Element'
+
+ hash[input_type]
+ end
+ private :input_class
+
+ # For a provided element type returned by JSSh like HTMLDivElement,
+ # returns its corresponding class in Firewatir.
+ def jssh2firewatir(candidate_class)
+ hash = {
+ 'Div' => 'Div',
+ 'Button' => 'Button',
+ 'Frame' => 'Frame',
+ 'Span' => 'Span',
+ 'Paragraph' => 'P',
+ 'Label' => 'Label',
+ 'Form' => 'Form',
+ 'Image' => 'Image',
+ 'Table' => 'Table',
+ 'TableCell' => 'TableCell',
+ 'TableRow' => 'TableRow',
+ 'Select' => 'SelectList',
+ 'Link' => 'Link',
+ 'Anchor' => 'Link' # FIXME is this right?
+ #'Option' => 'Option' #Option uses a different constructor
+ }
+ hash.default = 'Element'
+ hash[candidate_class]
+ end
+ private :jssh2firewatir
+
+ #
+ # Description:
+ # Returns the array of elements that matches the xpath query.
+ #
+ # Input:
+ # Xpath expression or query.
+ #
+ # Output:
+ # Array of elements matching xpath query.
+ #
+ def elements_by_xpath(xpath)
+ element = Element.new(nil, self)
+ elem_names = element.elements_by_xpath(self, xpath)
+ elem_names.inject([]) {|elements,name| elements << element_factory(name)}
+ end
+
+ #
+ # Description:
+ # Show all the forms available on the page.
+ #
+ # Output:
+ # Name, id, method and action of all the forms available on the page.
+ #
+ def show_forms
+ forms = Document.new(self).get_forms()
+ count = forms.length
+ puts "There are #{count} forms"
+ for i in 0..count - 1 do
+ puts "Form name: " + forms[i].name
+ puts " id: " + forms[i].id
+ puts " method: " + forms[i].attribute_value("method")
+ puts " action: " + forms[i].action
+ end
+ end
+ alias showForms show_forms
+
+ #
+ # Description:
+ # Show all the images available on the page.
+ #
+ # Output:
+ # Name, id, src and index of all the images available on the page.
+ #
+ def show_images
+ images = Document.new(self).get_images
+ puts "There are #{images.length} images"
+ index = 1
+ images.each do |l|
+ puts "image: name: #{l.name}"
+ puts " id: #{l.id}"
+ puts " src: #{l.src}"
+ puts " index: #{index}"
+ index += 1
+ end
+ end
+ alias showImages show_images
+
+ #
+ # Description:
+ # Show all the links available on the page.
+ #
+ # Output:
+ # Name, id, href and index of all the links available on the page.
+ #
+ def show_links
+ links = Document.new(self).get_links
+ puts "There are #{links.length} links"
+ index = 1
+ links.each do |l|
+ puts "link: name: #{l.name}"
+ puts " id: #{l.id}"
+ puts " href: #{l.href}"
+ puts " index: #{index}"
+ index += 1
+ end
+ end
+ alias showLinks show_links
+
+ #
+ # Description:
+ # Show all the divs available on the page.
+ #
+ # Output:
+ # Name, id, class and index of all the divs available on the page.
+ #
+ def show_divs
+ divs = Document.new(self).get_divs
+ puts "There are #{divs.length} divs"
+ index = 1
+ divs.each do |l|
+ puts "div: name: #{l.name}"
+ puts " id: #{l.id}"
+ puts " class: #{l.className}"
+ puts " index: #{index}"
+ index += 1
+ end
+ end
+ alias showDivs show_divs
+
+ #
+ # Description:
+ # Show all the tables available on the page.
+ #
+ # Output:
+ # Id, row count, column count (only first row) and index of all the tables available on the page.
+ #
+ def show_tables
+ tables = Document.new(self).get_tables
+ puts "There are #{tables.length} tables"
+ index = 1
+ tables.each do |l|
+ puts "table: id: #{l.id}"
+ puts " rows: #{l.row_count}"
+ puts " columns: #{l.column_count}"
+ puts " index: #{index}"
+ index += 1
+ end
+ end
+ alias showTables show_tables
+
+ #
+ # Description:
+ # Show all the pre elements available on the page.
+ #
+ # Output:
+ # Id, name and index of all the pre elements available on the page.
+ #
+ def show_pres
+ pres = Document.new(self).get_pres
+ puts "There are #{pres.length} pres"
+ index = 1
+ pres.each do |l|
+ puts "pre: id: #{l.id}"
+ puts " name: #{l.name}"
+ puts " index: #{index}"
+ index += 1
+ end
+ end
+ alias showPres show_pres
+
+ #
+ # Description:
+ # Show all the spans available on the page.
+ #
+ # Output:
+ # Name, id, class and index of all the spans available on the page.
+ #
+ def show_spans
+ spans = Document.new(self).get_spans
+ puts "There are #{spans.length} spans"
+ index = 1
+ spans.each do |l|
+ puts "span: name: #{l.name}"
+ puts " id: #{l.id}"
+ puts " class: #{l.className}"
+ puts " index: #{index}"
+ index += 1
+ end
+ end
+ alias showSpans show_spans
+
+ #
+ # Description:
+ # Show all the labels available on the page.
+ #
+ # Output:
+ # Name, id, for and index of all the labels available on the page.
+ #
+ def show_labels
+ labels = Document.new(self).get_labels
+ puts "There are #{labels.length} labels"
+ index = 1
+ labels.each do |l|
+ puts "label: name: #{l.name}"
+ puts " id: #{l.id}"
+ puts " for: #{l.for}"
+ puts " index: #{index}"
+ index += 1
+ end
+ end
+ alias showLabels show_labels
+
+ #
+ # Description:
+ # Show all the frames available on the page. Doesn't show nested frames.
+ #
+ # Output:
+ # Name, and index of all the frames available on the page.
+ #
+ def show_frames
+ jssh_command = "var frameset = #{window_var}.frames;
+ var elements_frames = new Array();
+ for(var i = 0; i < frameset.length; i++)
+ {
+ var frames = frameset[i].frames;
+ for(var j = 0; j < frames.length; j++)
+ {
+ elements_frames.push(frames[j].frameElement);
+ }
+ }
+ elements_frames.length;"
+
+ length = js_eval(jssh_command).to_i
+
+ puts "There are #{length} frames"
+
+ frames = Array.new(length)
+ for i in 0..length - 1 do
+ frames[i] = Frame.new(self, :jssh_name, "elements_frames[#{i}]")
+ end
+
+ for i in 0..length - 1 do
+ puts "frame: name: #{frames[i].name}"
+ puts " index: #{i+1}"
+ end
+ end
+ alias showFrames show_frames
+
+ private
+
+ def path_to_bin
+ path = case current_os()
+ when :windows
+ path_from_registry
+ when :macosx
+ path_from_spotlight
+ when :linux
+ `which firefox`.strip
+ end
+
+ raise "unable to locate Firefox executable" if path.nil? || path.empty?
+
+ path
+ end
+
+ def current_os
+ return @current_os if defined?(@current_os)
+
+ platform = RUBY_PLATFORM =~ /java/ ? java.lang.System.getProperty("os.name") : RUBY_PLATFORM
+
+ @current_os = case platform
+ when /mingw32|mswin|windows/i
+ :windows
+ when /darwin|mac os/i
+ :macosx
+ when /linux/i
+ :linux
+ end
+ end
+
+ def path_from_registry
+ raise NotImplementedError, "(need to know how to access windows registry on JRuby)" if RUBY_PLATFORM =~ /java/
+ require 'win32/registry.rb'
+ lm = Win32::Registry::HKEY_LOCAL_MACHINE
+ lm.open('SOFTWARE\Mozilla\Mozilla Firefox') do |reg|
+ reg1 = lm.open("SOFTWARE\\Mozilla\\Mozilla Firefox\\#{reg.keys[0]}\\Main")
+ if entry = reg1.find { |key, type, data| key =~ /pathtoexe/i }
+ return entry.last
+ end
+ end
+ end
+
+ def path_from_spotlight
+ ff = %x[mdfind 'kMDItemCFBundleIdentifier == "org.mozilla.firefox"']
+ ff = ff.empty? ? '/Applications/Firefox.app' : ff.split("\n").first
+
+ "#{ff}/Contents/MacOS/firefox-bin"
+ end
+
+ end # Firefox
+end # FireWatir