akephalos2 2.0.7-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT_LICENSE +20 -0
- data/README.md +330 -0
- data/bin/akephalos +102 -0
- data/lib/akephalos/capybara.rb +347 -0
- data/lib/akephalos/client/cookies.rb +73 -0
- data/lib/akephalos/client/filter.rb +120 -0
- data/lib/akephalos/client.rb +192 -0
- data/lib/akephalos/configuration.rb +49 -0
- data/lib/akephalos/console.rb +32 -0
- data/lib/akephalos/cucumber.rb +6 -0
- data/lib/akephalos/htmlunit/ext/confirm_handler.rb +18 -0
- data/lib/akephalos/htmlunit/ext/http_method.rb +30 -0
- data/lib/akephalos/htmlunit.rb +31 -0
- data/lib/akephalos/node.rb +192 -0
- data/lib/akephalos/page.rb +118 -0
- data/lib/akephalos/remote_client.rb +93 -0
- data/lib/akephalos/server.rb +79 -0
- data/lib/akephalos/version.rb +3 -0
- data/lib/akephalos.rb +19 -0
- data/vendor/html-unit/apache-mime4j-0.6.jar +0 -0
- data/vendor/html-unit/commons-codec-1.4.jar +0 -0
- data/vendor/html-unit/commons-collections-3.2.1.jar +0 -0
- data/vendor/html-unit/commons-io-2.0.1.jar +0 -0
- data/vendor/html-unit/commons-lang3-3.0.1.jar +0 -0
- data/vendor/html-unit/commons-logging-1.1.1.jar +0 -0
- data/vendor/html-unit/cssparser-0.9.6-20110829.205617-3.jar +0 -0
- data/vendor/html-unit/htmlunit-2.10-SNAPSHOT.jar +0 -0
- data/vendor/html-unit/htmlunit-core-js-2.9.jar +0 -0
- data/vendor/html-unit/httpclient-4.1.2.jar +0 -0
- data/vendor/html-unit/httpcore-4.1.2.jar +0 -0
- data/vendor/html-unit/httpmime-4.1.2.jar +0 -0
- data/vendor/html-unit/nekohtml-1.9.15.jar +0 -0
- data/vendor/html-unit/sac-1.3.jar +0 -0
- data/vendor/html-unit/serializer-2.7.1.jar +0 -0
- data/vendor/html-unit/xalan-2.7.1.jar +0 -0
- data/vendor/html-unit/xercesImpl-2.9.1.jar +0 -0
- data/vendor/html-unit/xml-apis-1.3.04.jar +0 -0
- 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_6 => HtmlUnit::BrowserVersion::FIREFOX_3_6
|
54
|
+
}
|
55
|
+
|
56
|
+
# @param [Hash] options the configuration options for this client
|
57
|
+
#
|
58
|
+
# @option options [Symbol] :browser (:firefox_3_6) the browser version (
|
59
|
+
# see BROWSER_VERSIONS)
|
60
|
+
#
|
61
|
+
# @option options [true, false] :validate_scripts (true) whether to raise
|
62
|
+
# errors on javascript errors
|
63
|
+
def initialize(options = {})
|
64
|
+
process_options!(options)
|
65
|
+
|
66
|
+
@_client = java.util.concurrent.FutureTask.new do
|
67
|
+
|
68
|
+
if @http_proxy.nil? or @http_proxy_port.nil?
|
69
|
+
client = HtmlUnit::WebClient.new(browser_version)
|
70
|
+
else
|
71
|
+
client = HtmlUnit::WebClient.new(browser_version, @http_proxy, @http_proxy_port)
|
72
|
+
end
|
73
|
+
|
74
|
+
client.setThrowExceptionOnFailingStatusCode(false)
|
75
|
+
client.setAjaxController(HtmlUnit::NicelyResynchronizingAjaxController.new)
|
76
|
+
client.setCssErrorHandler(HtmlUnit::SilentCssErrorHandler.new)
|
77
|
+
client.setThrowExceptionOnScriptError(validate_scripts)
|
78
|
+
client.setUseInsecureSSL(use_insecure_ssl)
|
79
|
+
client.setRefreshHandler(HtmlUnit::WaitingRefreshHandler.new)
|
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,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,31 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require "java"
|
3
|
+
|
4
|
+
Dir[File.dirname(__FILE__) + "/../.." + "/vendor/html-unit/*.jar"].each {|file| require file }
|
5
|
+
|
6
|
+
java.lang.System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog")
|
7
|
+
java.lang.System.setProperty("org.apache.commons.logging.simplelog.defaultlog", "fatal")
|
8
|
+
java.lang.System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true")
|
9
|
+
|
10
|
+
# Container module for com.gargoylesoftware.htmlunit namespace.
|
11
|
+
module HtmlUnit
|
12
|
+
java_import "com.gargoylesoftware.htmlunit.BrowserVersion"
|
13
|
+
java_import "com.gargoylesoftware.htmlunit.History"
|
14
|
+
java_import "com.gargoylesoftware.htmlunit.HttpMethod"
|
15
|
+
java_import 'com.gargoylesoftware.htmlunit.ConfirmHandler'
|
16
|
+
java_import "com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController"
|
17
|
+
java_import "com.gargoylesoftware.htmlunit.SilentCssErrorHandler"
|
18
|
+
java_import "com.gargoylesoftware.htmlunit.WebClient"
|
19
|
+
java_import "com.gargoylesoftware.htmlunit.WebResponseData"
|
20
|
+
java_import "com.gargoylesoftware.htmlunit.WebResponse"
|
21
|
+
java_import "com.gargoylesoftware.htmlunit.WaitingRefreshHandler"
|
22
|
+
|
23
|
+
# Container module for com.gargoylesoftware.htmlunit.util namespace.
|
24
|
+
module Util
|
25
|
+
java_import "com.gargoylesoftware.htmlunit.util.NameValuePair"
|
26
|
+
java_import "com.gargoylesoftware.htmlunit.util.WebConnectionWrapper"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Disable history tracking
|
30
|
+
History.field_reader :ignoreNewPages_
|
31
|
+
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,118 @@
|
|
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.getWebRequest.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/Integer] id the frame's id or index
|
103
|
+
# @return [HtmlUnit::HtmlPage] the specified frame
|
104
|
+
# @return [nil] if no frame is found
|
105
|
+
def find_frame(id_or_index)
|
106
|
+
if id_or_index.is_a? Fixnum
|
107
|
+
frame = @_page.getFrames.get(id_or_index) rescue nil
|
108
|
+
else
|
109
|
+
frame = @_page.getFrames.find do |frame|
|
110
|
+
frame.getFrameElement.getAttribute("id") == id_or_index
|
111
|
+
end
|
112
|
+
end
|
113
|
+
frame.getEnclosedPage if frame
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|