akephalos2 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/MIT_LICENSE +20 -0
  2. data/README.md +287 -0
  3. data/bin/akephalos +102 -0
  4. data/lib/akephalos/capybara.rb +343 -0
  5. data/lib/akephalos/client/cookies.rb +73 -0
  6. data/lib/akephalos/client/filter.rb +120 -0
  7. data/lib/akephalos/client.rb +192 -0
  8. data/lib/akephalos/configuration.rb +49 -0
  9. data/lib/akephalos/console.rb +32 -0
  10. data/lib/akephalos/cucumber.rb +6 -0
  11. data/lib/akephalos/htmlunit/ext/confirm_handler.rb +18 -0
  12. data/lib/akephalos/htmlunit/ext/http_method.rb +30 -0
  13. data/lib/akephalos/htmlunit.rb +36 -0
  14. data/lib/akephalos/node.rb +192 -0
  15. data/lib/akephalos/page.rb +113 -0
  16. data/lib/akephalos/remote_client.rb +93 -0
  17. data/lib/akephalos/server.rb +79 -0
  18. data/lib/akephalos/version.rb +3 -0
  19. data/lib/akephalos.rb +19 -0
  20. data/vendor/html-unit/apache-mime4j-0.6.jar +0 -0
  21. data/vendor/html-unit/commons-codec-1.4.jar +0 -0
  22. data/vendor/html-unit/commons-collections-3.2.1.jar +0 -0
  23. data/vendor/html-unit/commons-io-2.0.1.jar +0 -0
  24. data/vendor/html-unit/commons-lang-2.6.jar +0 -0
  25. data/vendor/html-unit/commons-logging-1.1.1.jar +0 -0
  26. data/vendor/html-unit/cssparser-0.9.5.jar +0 -0
  27. data/vendor/html-unit/htmlunit-2.9.jar +0 -0
  28. data/vendor/html-unit/htmlunit-core-js-2.9.jar +0 -0
  29. data/vendor/html-unit/httpclient-4.1.2.jar +0 -0
  30. data/vendor/html-unit/httpcore-4.1.2.jar +0 -0
  31. data/vendor/html-unit/httpmime-4.1.2.jar +0 -0
  32. data/vendor/html-unit/nekohtml-1.9.15.jar +0 -0
  33. data/vendor/html-unit/sac-1.3.jar +0 -0
  34. data/vendor/html-unit/serializer-2.7.1.jar +0 -0
  35. data/vendor/html-unit/xalan-2.7.1.jar +0 -0
  36. data/vendor/html-unit/xercesImpl-2.9.1.jar +0 -0
  37. data/vendor/html-unit/xml-apis-1.3.04.jar +0 -0
  38. metadata +127 -0
@@ -0,0 +1,192 @@
1
+ require 'akephalos/configuration'
2
+
3
+ if RUBY_PLATFORM != "java"
4
+ require 'akephalos/remote_client'
5
+ Akephalos::Client = Akephalos::RemoteClient
6
+ else
7
+ require 'akephalos/htmlunit'
8
+ require 'akephalos/htmlunit/ext/http_method'
9
+ require 'akephalos/htmlunit/ext/confirm_handler'
10
+
11
+ require 'akephalos/page'
12
+ require 'akephalos/node'
13
+
14
+ require 'akephalos/client/cookies'
15
+ require 'akephalos/client/filter'
16
+
17
+ module Akephalos
18
+
19
+ # Akephalos::Client wraps HtmlUnit's WebClient class. It is the main entry
20
+ # point for all interaction with the browser, exposing its current page and
21
+ # allowing navigation.
22
+ class Client
23
+
24
+ # @return [Akephalos::Page] the current page
25
+ attr_reader :page
26
+
27
+ # @return [HtmlUnit::BrowserVersion] the configured browser version
28
+ attr_reader :browser_version
29
+
30
+ # @return [true/false] whether to raise errors on javascript failures
31
+ attr_reader :validate_scripts
32
+
33
+ # @return [true/false] whether to ignore insecure ssl certificates
34
+ attr_reader :use_insecure_ssl
35
+
36
+ # @return ["trace" / "debug" / "info" / "warn" / "error" or "fatal"] which points the htmlunit log level
37
+ attr_reader :htmlunit_log_level
38
+
39
+ # The default configuration options for a new Client.
40
+ DEFAULT_OPTIONS = {
41
+ :browser => :firefox_3_6,
42
+ :validate_scripts => true,
43
+ :use_insecure_ssl => false,
44
+ :htmlunit_log_level => 'fatal'
45
+ }
46
+
47
+ # Map of browser version symbols to their HtmlUnit::BrowserVersion
48
+ # instances.
49
+ BROWSER_VERSIONS = {
50
+ :ie6 => HtmlUnit::BrowserVersion::INTERNET_EXPLORER_6,
51
+ :ie7 => HtmlUnit::BrowserVersion::INTERNET_EXPLORER_7,
52
+ :ie8 => HtmlUnit::BrowserVersion::INTERNET_EXPLORER_8,
53
+ :firefox_3 => HtmlUnit::BrowserVersion::FIREFOX_3,
54
+ :firefox_3_6 => HtmlUnit::BrowserVersion::FIREFOX_3_6
55
+ }
56
+
57
+ # @param [Hash] options the configuration options for this client
58
+ #
59
+ # @option options [Symbol] :browser (:firefox_3_6) the browser version (
60
+ # see BROWSER_VERSIONS)
61
+ #
62
+ # @option options [true, false] :validate_scripts (true) whether to raise
63
+ # errors on javascript errors
64
+ def initialize(options = {})
65
+ process_options!(options)
66
+
67
+ @_client = java.util.concurrent.FutureTask.new do
68
+
69
+ if @http_proxy.nil? or @http_proxy_port.nil?
70
+ client = HtmlUnit::WebClient.new(browser_version)
71
+ else
72
+ client = HtmlUnit::WebClient.new(browser_version, @http_proxy, @http_proxy_port)
73
+ end
74
+
75
+ client.setThrowExceptionOnFailingStatusCode(false)
76
+ client.setAjaxController(HtmlUnit::NicelyResynchronizingAjaxController.new)
77
+ client.setCssErrorHandler(HtmlUnit::SilentCssErrorHandler.new)
78
+ client.setThrowExceptionOnScriptError(validate_scripts)
79
+ client.setUseInsecureSSL(use_insecure_ssl)
80
+
81
+ Filter.new(client)
82
+ client
83
+ end
84
+ Thread.new { @_client.run }
85
+ end
86
+
87
+ # Visit the requested URL and return the page.
88
+ #
89
+ # @param [String] url the URL to load
90
+ # @return [Page] the loaded page
91
+ def visit(url)
92
+ begin
93
+ client.getPage(url)
94
+ rescue Exception => e
95
+ raise e unless e.message == 'java.lang.NullPointerException: null'
96
+ end
97
+ page
98
+ end
99
+
100
+ # @return [Cookies] the cookies for this session
101
+ def cookies
102
+ @cookies ||= Cookies.new(client.getCookieManager)
103
+ end
104
+
105
+ # @return [String] the current user agent string
106
+ def user_agent
107
+ @user_agent || client.getBrowserVersion.getUserAgent
108
+ end
109
+
110
+ # Set the User-Agent header for this session. If :default is given, the
111
+ # User-Agent header will be reset to the default browser's user agent.
112
+ #
113
+ # @param [:default] user_agent the default user agent
114
+ # @param [String] user_agent the user agent string to use
115
+ def user_agent=(user_agent)
116
+ if user_agent == :default
117
+ @user_agent = nil
118
+ client.removeRequestHeader("User-Agent")
119
+ else
120
+ @user_agent = user_agent
121
+ client.addRequestHeader("User-Agent", user_agent)
122
+ end
123
+ end
124
+
125
+ # @return [Page] the current page
126
+ def page
127
+ self.page = client.getCurrentWindow.getTopWindow.getEnclosedPage
128
+ @page
129
+ end
130
+
131
+ # Update the current page.
132
+ #
133
+ # @param [HtmlUnit::HtmlPage] _page the new page
134
+ # @return [Page] the new page
135
+ def page=(_page)
136
+ if @page != _page
137
+ @page = Page.new(_page)
138
+ end
139
+ @page
140
+ end
141
+
142
+ # @return [true, false] whether javascript errors will raise exceptions
143
+ def validate_scripts?
144
+ !!validate_scripts
145
+ end
146
+
147
+ # @return [true, false] whether to ignore insecure ssl certificates
148
+ def use_insecure_ssl?
149
+ !!use_insecure_ssl
150
+ end
151
+
152
+ # Merges the DEFAULT_OPTIONS with those provided to initialize the Client
153
+ # state, namely, its browser version, whether it should
154
+ # validate scripts, and htmlunit log level.
155
+ #
156
+ # @param [Hash] options the options to process
157
+ def process_options!(options)
158
+ options = DEFAULT_OPTIONS.merge(options)
159
+
160
+ @browser_version = BROWSER_VERSIONS.fetch(options.delete(:browser))
161
+ @validate_scripts = options.delete(:validate_scripts)
162
+ @use_insecure_ssl = options.delete(:use_insecure_ssl)
163
+ @htmlunit_log_level = options.delete(:htmlunit_log_level)
164
+ @http_proxy = options.delete(:http_proxy)
165
+ @http_proxy_port = options.delete(:http_proxy_port)
166
+
167
+ java.lang.System.setProperty("org.apache.commons.logging.simplelog.defaultlog", @htmlunit_log_level)
168
+ end
169
+
170
+ # Confirm or cancel the dialog, returning the text of the dialog
171
+ def confirm_dialog(confirm = true, &block)
172
+ handler = HtmlUnit::ConfirmHandler.new
173
+ handler.handleConfirmValue = confirm
174
+ client.setConfirmHandler(handler)
175
+ yield if block_given?
176
+ return handler.text
177
+ end
178
+
179
+ private
180
+
181
+ # Call the future set up in #initialize and return the WebCLient
182
+ # instance.
183
+ #
184
+ # @return [HtmlUnit::WebClient] the WebClient instance
185
+ def client
186
+ @client ||= @_client.get.tap do |client|
187
+ client.getCurrentWindow.getHistory.ignoreNewPages_.set(true)
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,49 @@
1
+ module Akephalos
2
+
3
+ @configuration = {}
4
+
5
+ class << self
6
+ # @return [Hash] the configuration
7
+ attr_accessor :configuration
8
+ end
9
+
10
+ module Filters
11
+ # @return [Array] all defined filters
12
+ def filters
13
+ configuration[:filters] ||= []
14
+ end
15
+
16
+ # Defines a new filter to be tested by Akephalos::Filter when executing
17
+ # page requests. An HTTP method and a regex or string to match against the
18
+ # URL are required for defining a filter.
19
+ #
20
+ # You can additionally pass the following options to define how the
21
+ # filtered request should respond:
22
+ #
23
+ # :status (defaults to 200)
24
+ # :body (defaults to "")
25
+ # :headers (defaults to {})
26
+ #
27
+ # If we define a filter with no additional options, then, we will get an
28
+ # empty HTML response:
29
+ #
30
+ # Akephalos.filter :post, "http://example.com"
31
+ # Akephalos.filter :any, %r{http://.*\.com}
32
+ #
33
+ # If you instead, say, wanted to simulate a failure in an external system,
34
+ # you could do this:
35
+ #
36
+ # Akephalos.filter :post, "http://example.com",
37
+ # :status => 500, :body => "Something went wrong"
38
+ #
39
+ # @param [Symbol] method the HTTP method to match
40
+ # @param [RegExp, String] regex URL matcher
41
+ # @param [Hash] options response values
42
+ def filter(method, regex, options = {})
43
+ regex = Regexp.new(Regexp.escape(regex)) if regex.is_a?(String)
44
+ filters << {:method => method, :filter => regex, :status => 200, :body => "", :headers => {}}.merge!(options)
45
+ end
46
+ end
47
+
48
+ extend Filters
49
+ end
@@ -0,0 +1,32 @@
1
+ # Begin a new Capybara session, by default connecting to localhost on port
2
+ # 3000.
3
+ def session
4
+ Capybara.app_host ||= "http://localhost:3000"
5
+ @session ||= Capybara::Session.new(:akephalos)
6
+ end
7
+ alias page session
8
+
9
+ module Akephalos
10
+ # Simple class for starting an IRB session.
11
+ class Console
12
+
13
+ # Start an IRB session. Tries to load irb/completion, and also loads a
14
+ # .irbrc file if it exists.
15
+ def self.start
16
+ require 'irb'
17
+
18
+ begin
19
+ require 'irb/completion'
20
+ rescue Exception
21
+ # No readline available, proceed anyway.
22
+ end
23
+
24
+ if ::File.exists? ".irbrc"
25
+ ENV['IRBRC'] = ".irbrc"
26
+ end
27
+
28
+ IRB.start
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,6 @@
1
+ require 'capybara/cucumber'
2
+
3
+ Before('@akephalos') do
4
+ Capybara.current_driver = :akephalos
5
+ end
6
+
@@ -0,0 +1,18 @@
1
+ # Reopen com.gargoylesoftware.htmlunit.ConfirmHandler to provide an interface to
2
+ # confirm a dialog and capture its message
3
+ module HtmlUnit
4
+ module ConfirmHandler
5
+
6
+ # Boolean - true for ok, false for cancel
7
+ attr_accessor :handleConfirmValue
8
+
9
+ # last confirmation's message
10
+ attr_reader :text
11
+
12
+ # handleConfirm will be called by htmlunit on a confirm, so store the message.
13
+ def handleConfirm(page, message)
14
+ @text = message
15
+ return handleConfirmValue.nil? ? true : handleConfirmValue
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,30 @@
1
+ module HtmlUnit
2
+ # Reopen HtmlUnit's HttpMethod class to add convenience methods.
3
+ class HttpMethod
4
+
5
+ # Loosely compare HttpMethod with another object, accepting either an
6
+ # HttpMethod instance or a symbol describing the method. Note that :any is a
7
+ # special symbol which will always return true.
8
+ #
9
+ # @param [HttpMethod] other an HtmlUnit HttpMethod object
10
+ # @param [Symbol] other a symbolized representation of an http method
11
+ # @return [true/false]
12
+ def ===(other)
13
+ case other
14
+ when HttpMethod
15
+ super
16
+ when :any
17
+ true
18
+ when :get
19
+ self == self.class::GET
20
+ when :post
21
+ self == self.class::POST
22
+ when :put
23
+ self == self.class::PUT
24
+ when :delete
25
+ self == self.class::DELETE
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,36 @@
1
+ require "pathname"
2
+ require "java"
3
+
4
+ dependency_directory = $:.detect { |path| Dir[File.join(path, 'html-unit/htmlunit-*.jar')].any? }
5
+
6
+ raise "Could not find htmlunit/htmlunit-VERSION.jar in load path:\n [ #{$:.join(",\n ")}\n ]" unless dependency_directory
7
+
8
+ Dir[File.join(dependency_directory, "html-unit/*.jar")].each do |jar|
9
+ require jar
10
+ end
11
+
12
+ java.lang.System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog")
13
+ java.lang.System.setProperty("org.apache.commons.logging.simplelog.defaultlog", "fatal")
14
+ java.lang.System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true")
15
+
16
+ # Container module for com.gargoylesoftware.htmlunit namespace.
17
+ module HtmlUnit
18
+ java_import "com.gargoylesoftware.htmlunit.BrowserVersion"
19
+ java_import "com.gargoylesoftware.htmlunit.History"
20
+ java_import "com.gargoylesoftware.htmlunit.HttpMethod"
21
+ java_import 'com.gargoylesoftware.htmlunit.ConfirmHandler'
22
+ java_import "com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController"
23
+ java_import "com.gargoylesoftware.htmlunit.SilentCssErrorHandler"
24
+ java_import "com.gargoylesoftware.htmlunit.WebClient"
25
+ java_import "com.gargoylesoftware.htmlunit.WebResponseData"
26
+ java_import "com.gargoylesoftware.htmlunit.WebResponseImpl"
27
+
28
+ # Container module for com.gargoylesoftware.htmlunit.util namespace.
29
+ module Util
30
+ java_import "com.gargoylesoftware.htmlunit.util.NameValuePair"
31
+ java_import "com.gargoylesoftware.htmlunit.util.WebConnectionWrapper"
32
+ end
33
+
34
+ # Disable history tracking
35
+ History.field_reader :ignoreNewPages_
36
+ end
@@ -0,0 +1,192 @@
1
+ module Akephalos
2
+
3
+ # Akephalos::Node wraps HtmlUnit's DOMNode class, providing a simple API for
4
+ # interacting with an element on the page.
5
+ class Node
6
+ # @param [HtmlUnit::DOMNode] node
7
+ def initialize(node)
8
+ @nodes = []
9
+ @_node = node
10
+ end
11
+
12
+ # @return [true, false] whether the element is checked
13
+ def checked?
14
+ if @_node.respond_to?(:isChecked)
15
+ @_node.isChecked
16
+ else
17
+ !! self[:checked]
18
+ end
19
+ end
20
+
21
+ # @return [String] inner text of the node
22
+ # Returns a textual representation of this element that represents what would
23
+ # be visible to the user if this page was shown in a web browser.
24
+ # For example, a single-selection select element would return the currently
25
+ # selected value as text.
26
+ # Note: This will cleanup/reduce whitespace
27
+ def text
28
+ @_node.asText
29
+ end
30
+
31
+ # Returns the raw text content of this node and its descendants...
32
+ def text_content
33
+ @_node.getTextContent
34
+ end
35
+
36
+ # Returns a string representation of the XML document from this element and
37
+ # all it's children (recursively). The charset used is the current page encoding.
38
+ def xml
39
+ @_node.asXml
40
+ end
41
+
42
+ # Return the value of the node's attribute.
43
+ #
44
+ # @param [String] name attribute on node
45
+ # @return [String] the value of the named attribute
46
+ # @return [nil] when the node does not have the named attribute
47
+ def [](name)
48
+ @_node.hasAttribute(name.to_s) ? @_node.getAttribute(name.to_s) : nil
49
+ end
50
+
51
+ # Return the value of a form element. If the element is a select box and
52
+ # has "multiple" declared as an attribute, then all selected options will
53
+ # be returned as an array.
54
+ #
55
+ # @return [String, Array<String>] the node's value
56
+ def value
57
+ case tag_name
58
+ when "select"
59
+ if self[:multiple]
60
+ selected_options.map { |option| option.value }
61
+ else
62
+ selected_option = @_node.selected_options.first
63
+ selected_option ? Node.new(selected_option).value : nil
64
+ end
65
+ when "option"
66
+ self[:value] || text
67
+ when "textarea"
68
+ @_node.getText
69
+ else
70
+ self[:value]
71
+ end
72
+ end
73
+
74
+ # Set the value of the form input.
75
+ #
76
+ # @param [String] value
77
+ def value=(value)
78
+ case tag_name
79
+ when "textarea"
80
+ @_node.setText("")
81
+ type(value)
82
+ when "input"
83
+ if file_input?
84
+ @_node.setValueAttribute(value)
85
+ else
86
+ @_node.setValueAttribute("")
87
+ type(value)
88
+ end
89
+ end
90
+ end
91
+
92
+ # Types each character into a text or input field.
93
+ #
94
+ # @param [String] value the string to type
95
+ def type(value)
96
+ value.each_char do |c|
97
+ @_node.type(c)
98
+ end
99
+ end
100
+
101
+ # @return [true, false] whether the node allows multiple-option selection (if the node is a select).
102
+ def multiple_select?
103
+ !self[:multiple].nil?
104
+ end
105
+
106
+ # @return [true, false] whether the node is a file input
107
+ def file_input?
108
+ tag_name == "input" && @_node.getAttribute("type") == "file"
109
+ end
110
+
111
+
112
+ # Unselect an option.
113
+ #
114
+ # @return [true, false] whether the unselection was successful
115
+ def unselect
116
+ @_node.setSelected(false)
117
+ end
118
+
119
+ # Return the option elements for a select box.
120
+ #
121
+ # @return [Array<Node>] the options
122
+ def options
123
+ @_node.getOptions.map { |node| Node.new(node) }
124
+ end
125
+
126
+ # Return the selected option elements for a select box.
127
+ #
128
+ # @return [Array<Node>] the selected options
129
+ def selected_options
130
+ @_node.getSelectedOptions.map { |node| Node.new(node) }
131
+ end
132
+
133
+ # Fire a JavaScript event on the current node. Note that you should not
134
+ # prefix event names with "on", so:
135
+ #
136
+ # link.fire_event('mousedown')
137
+ #
138
+ # @param [String] JavaScript event name
139
+ def fire_event(name)
140
+ @_node.fireEvent(name)
141
+ end
142
+
143
+ # @return [String] the node's tag name
144
+ def tag_name
145
+ @_node.getNodeName
146
+ end
147
+
148
+ # @return [true, false] whether the node is visible to the user accounting
149
+ # for CSS.
150
+ def visible?
151
+ @_node.isDisplayed
152
+ end
153
+
154
+ # @return [true, false] whether the node is selected to the user accounting
155
+ # for CSS.
156
+ def selected?
157
+ if @_node.respond_to?(:isSelected)
158
+ @_node.isSelected
159
+ else
160
+ !! self[:selected]
161
+ end
162
+ end
163
+
164
+ # Click the node and then wait for any triggered JavaScript callbacks to
165
+ # fire.
166
+ def click
167
+ begin
168
+ @_node.click
169
+ @_node.getPage.getEnclosingWindow.getJobManager.waitForJobs(1000)
170
+ @_node.getPage.getEnclosingWindow.getJobManager.waitForJobsStartingBefore(1000)
171
+ rescue Exception => e
172
+ raise e unless e.message == 'java.lang.NullPointerException: null'
173
+ end
174
+ end
175
+
176
+ # Search for child nodes which match the given XPath selector.
177
+ #
178
+ # @param [String] selector an XPath selector
179
+ # @return [Array<Node>] the matched nodes
180
+ def find(selector)
181
+ nodes = @_node.getByXPath(selector).map { |node| Node.new(node) }
182
+ @nodes << nodes
183
+ nodes
184
+ end
185
+
186
+ # @return [String] the XPath expression for this node
187
+ def xpath
188
+ @_node.getCanonicalXPath
189
+ end
190
+ end
191
+
192
+ end
@@ -0,0 +1,113 @@
1
+ module Akephalos
2
+
3
+ # Akephalos::Page wraps HtmlUnit's HtmlPage class, exposing an API for
4
+ # interacting with a page in the browser.
5
+ class Page
6
+ # @param [HtmlUnit::HtmlPage] page
7
+ def initialize(page)
8
+ @nodes = []
9
+ @_page = page
10
+ end
11
+
12
+ # Search for nodes which match the given XPath selector.
13
+ #
14
+ # @param [String] selector an XPath selector
15
+ # @return [Array<Node>] the matched nodes
16
+ def find(selector)
17
+ nodes = current_frame.getByXPath(selector).map { |node| Node.new(node) }
18
+ @nodes << nodes
19
+ nodes
20
+ end
21
+
22
+ # Return the page's source, including any JavaScript-triggered DOM changes.
23
+ #
24
+ # @return [String] the page's modified source
25
+ def modified_source
26
+ current_frame.asXml
27
+ end
28
+
29
+ # Return the page's source as returned by the web server.
30
+ #
31
+ # @return [String] the page's original source
32
+ def source
33
+ current_frame.getWebResponse.getContentAsString
34
+ end
35
+
36
+ # @return [Hash{String => String}] the page's response headers
37
+ def response_headers
38
+ headers = current_frame.getWebResponse.getResponseHeaders.map do |header|
39
+ [header.getName, header.getValue]
40
+ end
41
+ Hash[*headers.flatten]
42
+ end
43
+
44
+ # @return [Integer] the response's status code
45
+ def status_code
46
+ current_frame.getWebResponse.getStatusCode
47
+ end
48
+
49
+ # Execute the given block in the context of the frame specified.
50
+ #
51
+ # @param [String] frame_id the frame's id
52
+ # @return [true] if the frame is found
53
+ # @return [nil] if the frame is not found
54
+ def within_frame(frame_id)
55
+ return unless @current_frame = find_frame(frame_id)
56
+ yield
57
+ true
58
+ ensure
59
+ @current_frame = nil
60
+ end
61
+
62
+ # @return [String] the current page's URL.
63
+ def current_url
64
+ current_frame.getWebResponse.getRequestSettings.getUrl.toString
65
+ end
66
+
67
+ # Execute JavaScript against the current page, discarding any return value.
68
+ #
69
+ # @param [String] script the JavaScript to be executed
70
+ # @return [nil]
71
+ def execute_script(script)
72
+ current_frame.executeJavaScript(script)
73
+ nil
74
+ end
75
+
76
+ # Execute JavaScript against the current page and return the results.
77
+ #
78
+ # @param [String] script the JavaScript to be executed
79
+ # @return the result of the JavaScript
80
+ def evaluate_script(script)
81
+ current_frame.executeJavaScript(script).getJavaScriptResult
82
+ end
83
+
84
+ # Compare this page with an HtmlUnit page.
85
+ #
86
+ # @param [HtmlUnit::HtmlPage] other an HtmlUnit page
87
+ # @return [true, false]
88
+ def ==(other)
89
+ @_page == other
90
+ end
91
+
92
+ private
93
+
94
+ # Return the current frame. Usually just @_page, except when inside of the
95
+ # within_frame block.
96
+ #
97
+ # @return [HtmlUnit::HtmlPage] the current frame
98
+ def current_frame
99
+ @current_frame || @_page
100
+ end
101
+
102
+ # @param [String] id the frame's id
103
+ # @return [HtmlUnit::HtmlPage] the specified frame
104
+ # @return [nil] if no frame is found
105
+ def find_frame(id)
106
+ frame = @_page.getFrames.find do |frame|
107
+ frame.getFrameElement.getAttribute("id") == id
108
+ end
109
+ frame.getEnclosedPage if frame
110
+ end
111
+ end
112
+
113
+ end