akephalos2-stable 2.1.1.1
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 +365 -0
- data/bin/akephalos +110 -0
- data/lib/akephalos.rb +16 -0
- data/lib/akephalos/capybara.rb +348 -0
- data/lib/akephalos/client.rb +192 -0
- data/lib/akephalos/client/cookies.rb +73 -0
- data/lib/akephalos/client/filter.rb +120 -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.rb +31 -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_downloader.rb +38 -0
- data/lib/akephalos/node.rb +187 -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
- metadata +158 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
module Akephalos
|
2
|
+
class Client
|
3
|
+
# Interface for working with HtmlUnit's CookieManager, providing a basic
|
4
|
+
# API for manipulating the cookies in a session.
|
5
|
+
class Cookies
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
# @param [HtmlUnit::CookieManager] cookie manager
|
9
|
+
def initialize(cookie_manager)
|
10
|
+
@cookie_manager = cookie_manager
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [name] the cookie name
|
14
|
+
# @return [Cookie] the cookie with the given name
|
15
|
+
# @return [nil] when no cookie is found
|
16
|
+
def [](name)
|
17
|
+
cookie = @cookie_manager.getCookie(name)
|
18
|
+
Cookie.new(cookie) if cookie
|
19
|
+
end
|
20
|
+
|
21
|
+
# Clears all cookies for this session.
|
22
|
+
def clear
|
23
|
+
@cookie_manager.clearCookies
|
24
|
+
end
|
25
|
+
|
26
|
+
# Iterator for all cookies in the current session.
|
27
|
+
def each
|
28
|
+
@cookie_manager.getCookies.each do |cookie|
|
29
|
+
yield Cookie.new(cookie)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Remove the cookie from the session.
|
34
|
+
#
|
35
|
+
# @param [Cookie] the cookie to remove
|
36
|
+
def delete(cookie)
|
37
|
+
@cookie_manager.removeCookie(cookie.native)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [true, false] whether there are any cookies
|
41
|
+
def empty?
|
42
|
+
!any?
|
43
|
+
end
|
44
|
+
|
45
|
+
class Cookie
|
46
|
+
|
47
|
+
attr_reader :domain, :expires, :name, :path, :value
|
48
|
+
|
49
|
+
# @param [HtmlUnit::Cookie] the cookie
|
50
|
+
def initialize(cookie)
|
51
|
+
@_cookie = cookie
|
52
|
+
@domain = cookie.getDomain
|
53
|
+
@expires = cookie.getExpires
|
54
|
+
@name = cookie.getName
|
55
|
+
@path = cookie.getPath
|
56
|
+
@value = cookie.getValue
|
57
|
+
@secure = cookie.isSecure
|
58
|
+
end
|
59
|
+
|
60
|
+
def secure?
|
61
|
+
!!@secure
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [HtmlUnit::Cookie] the native cookie object
|
65
|
+
# @api private
|
66
|
+
def native
|
67
|
+
@_cookie
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Akephalos
|
2
|
+
class Client
|
3
|
+
|
4
|
+
# Akephalos::Client::Filter extends HtmlUnit's WebConnectionWrapper to
|
5
|
+
# enable filtering outgoing requests generated interally by HtmlUnit.
|
6
|
+
#
|
7
|
+
# When a request comes through, it will be tested against the filters
|
8
|
+
# defined in Akephalos.filters and return a mock response if a match is
|
9
|
+
# found. If no filters are defined, or no filters match the request, then
|
10
|
+
# the response will bubble up to HtmlUnit for the normal request/response
|
11
|
+
# cycle.
|
12
|
+
class Filter < HtmlUnit::Util::WebConnectionWrapper
|
13
|
+
# Filters an outgoing request, and if a match is found, returns the mock
|
14
|
+
# response.
|
15
|
+
#
|
16
|
+
# @param [WebRequest] request the pending HTTP request
|
17
|
+
# @return [WebResponse] when the request matches a defined filter
|
18
|
+
# @return [nil] when no filters match the request
|
19
|
+
def filter(request)
|
20
|
+
if filter = find_filter(request)
|
21
|
+
start_time = Time.now
|
22
|
+
headers = filter[:headers].map do |name, value|
|
23
|
+
HtmlUnit::Util::NameValuePair.new(name.to_s, value.to_s)
|
24
|
+
end
|
25
|
+
response = HtmlUnit::WebResponseData.new(
|
26
|
+
filter[:body].to_s.to_java_bytes,
|
27
|
+
filter[:status],
|
28
|
+
HTTP_STATUS_CODES.fetch(filter[:status], "Unknown"),
|
29
|
+
headers
|
30
|
+
)
|
31
|
+
HtmlUnit::WebResponse.new(response, request, Time.now - start_time)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Searches for a filter which matches the request's HTTP method and url.
|
36
|
+
#
|
37
|
+
# @param [WebRequest] request the pending HTTP request
|
38
|
+
# @return [Hash] when a filter matches the request
|
39
|
+
# @return [nil] when no filters match the request
|
40
|
+
def find_filter(request)
|
41
|
+
Akephalos.filters.find do |filter|
|
42
|
+
request.http_method === filter[:method] && request.url.to_s =~ filter[:filter]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# This method is called by WebClient when a page is requested, and will
|
47
|
+
# return a mock response if the request matches a defined filter or else
|
48
|
+
# return the actual response.
|
49
|
+
#
|
50
|
+
# @api htmlunit
|
51
|
+
# @param [WebRequest] request the pending HTTP request
|
52
|
+
# @return [WebResponse]
|
53
|
+
def getResponse(request)
|
54
|
+
filter(request) || super
|
55
|
+
end
|
56
|
+
|
57
|
+
# Map of status codes to their English descriptions.
|
58
|
+
HTTP_STATUS_CODES = {
|
59
|
+
100 => "Continue",
|
60
|
+
101 => "Switching Protocols",
|
61
|
+
102 => "Processing",
|
62
|
+
200 => "OK",
|
63
|
+
201 => "Created",
|
64
|
+
202 => "Accepted",
|
65
|
+
203 => "Non-Authoritative Information",
|
66
|
+
204 => "No Content",
|
67
|
+
205 => "Reset Content",
|
68
|
+
206 => "Partial Content",
|
69
|
+
207 => "Multi-Status",
|
70
|
+
300 => "Multiple Choices",
|
71
|
+
301 => "Moved Permanently",
|
72
|
+
302 => "Found",
|
73
|
+
303 => "See Other",
|
74
|
+
304 => "Not Modified",
|
75
|
+
305 => "Use Proxy",
|
76
|
+
306 => "Switch Proxy",
|
77
|
+
307 => "Temporary Redirect",
|
78
|
+
400 => "Bad Request",
|
79
|
+
401 => "Unauthorized",
|
80
|
+
402 => "Payment Required",
|
81
|
+
403 => "Forbidden",
|
82
|
+
404 => "Not Found",
|
83
|
+
405 => "Method Not Allowed",
|
84
|
+
406 => "Not Acceptable",
|
85
|
+
407 => "Proxy Authentication Required",
|
86
|
+
408 => "Request Timeout",
|
87
|
+
409 => "Conflict",
|
88
|
+
410 => "Gone",
|
89
|
+
411 => "Length Required",
|
90
|
+
412 => "Precondition Failed",
|
91
|
+
413 => "Request Entity Too Large",
|
92
|
+
414 => "Request-URI Too Long",
|
93
|
+
415 => "Unsupported Media Type",
|
94
|
+
416 => "Requested Range Not Satisfiable",
|
95
|
+
417 => "Expectation Failed",
|
96
|
+
418 => "I'm a teapot",
|
97
|
+
421 => "There are too many connections from your internet address",
|
98
|
+
422 => "Unprocessable Entity",
|
99
|
+
423 => "Locked",
|
100
|
+
424 => "Failed Dependency",
|
101
|
+
425 => "Unordered Collection",
|
102
|
+
426 => "Upgrade Required",
|
103
|
+
449 => "Retry With",
|
104
|
+
450 => "Blocked by Windows Parental Controls",
|
105
|
+
500 => "Internal Server Error",
|
106
|
+
501 => "Not Implemented",
|
107
|
+
502 => "Bad Gateway",
|
108
|
+
503 => "Service Unavailable",
|
109
|
+
504 => "Gateway Timeout",
|
110
|
+
505 => "HTTP Version Not Supported",
|
111
|
+
506 => "Variant Also Negotiates",
|
112
|
+
507 => "Insufficient Storage",
|
113
|
+
509 => "Bandwidth Limit Exceeded",
|
114
|
+
510 => "Not Extended",
|
115
|
+
530 => "User access denied"
|
116
|
+
}.freeze
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
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,31 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require "java"
|
3
|
+
|
4
|
+
Dir["#{Dir.pwd}/.akephalos/#{ENV['htmlunit_version']}/*.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,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,38 @@
|
|
1
|
+
module HtmlUnit
|
2
|
+
def self.download_htmlunit(version)
|
3
|
+
if not version_exist?(version)
|
4
|
+
puts "Installing HTMLUnit #{version} at .akephalos/#{version}/"
|
5
|
+
Dir.mkdir(".akephalos") unless File.exists?(".akephalos")
|
6
|
+
Dir.mkdir(".akephalos/#{version}") unless File.exists?(".akephalos/#{version}")
|
7
|
+
download(version)
|
8
|
+
unzip(version)
|
9
|
+
remove_cache(version)
|
10
|
+
else
|
11
|
+
puts "Using HTMLUnit #{version}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.version_exist?(version)
|
16
|
+
File.exist?(".akephalos/#{version}/htmlunit-#{version}.jar")
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.unzip(version)
|
20
|
+
`tar xzf htmlunit-#{version}.zip`
|
21
|
+
`mv -f htmlunit-2.10-SNAPSHOT htmlunit-2.10 > /dev/null 2>&1`
|
22
|
+
`cp -r htmlunit-#{version}/lib/ .akephalos/#{version}/`
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.download(version)
|
26
|
+
if version == "2.10"
|
27
|
+
%x[curl -L -o htmlunit-2.10.zip http://build.canoo.com/htmlunit/artifacts/htmlunit-2.10-SNAPSHOT-with-dependencies.zip]
|
28
|
+
elsif version == '2.9'
|
29
|
+
%x[curl -L -o htmlunit-2.9.zip http://sourceforge.net/projects/htmlunit/files/htmlunit/2.9/htmlunit-2.9-bin.zip]
|
30
|
+
else
|
31
|
+
%x[curl -L -O http://sourceforge.net/projects/htmlunit/files/htmlunit/#{version}/htmlunit-#{version}.zip]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.remove_cache(version)
|
36
|
+
`rm -rf htmlunit-#{version} htmlunit-#{version}.zip`
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,187 @@
|
|
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
|
+
@_node.click
|
168
|
+
@_node.getPage.getWebClient.waitForBackgroundJavaScriptStartingBefore(3000)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Search for child nodes which match the given XPath selector.
|
172
|
+
#
|
173
|
+
# @param [String] selector an XPath selector
|
174
|
+
# @return [Array<Node>] the matched nodes
|
175
|
+
def find(selector)
|
176
|
+
nodes = @_node.getByXPath(selector).map { |node| Node.new(node) }
|
177
|
+
@nodes << nodes
|
178
|
+
nodes
|
179
|
+
end
|
180
|
+
|
181
|
+
# @return [String] the XPath expression for this node
|
182
|
+
def xpath
|
183
|
+
@_node.getCanonicalXPath
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|