imseng-capybara-webkit 0.12.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/.gitignore +21 -0
- data/.rspec +2 -0
- data/Appraisals +7 -0
- data/CONTRIBUTING.md +47 -0
- data/ChangeLog +70 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/NEWS.md +36 -0
- data/README.md +114 -0
- data/Rakefile +65 -0
- data/bin/Info.plist +22 -0
- data/capybara-webkit.gemspec +28 -0
- data/extconf.rb +2 -0
- data/gemfiles/1.0.gemfile +7 -0
- data/gemfiles/1.0.gemfile.lock +70 -0
- data/gemfiles/1.1.gemfile +7 -0
- data/gemfiles/1.1.gemfile.lock +70 -0
- data/lib/capybara/driver/webkit/browser.rb +164 -0
- data/lib/capybara/driver/webkit/connection.rb +120 -0
- data/lib/capybara/driver/webkit/cookie_jar.rb +55 -0
- data/lib/capybara/driver/webkit/node.rb +118 -0
- data/lib/capybara/driver/webkit/socket_debugger.rb +43 -0
- data/lib/capybara/driver/webkit/version.rb +7 -0
- data/lib/capybara/driver/webkit.rb +136 -0
- data/lib/capybara/webkit/matchers.rb +37 -0
- data/lib/capybara/webkit.rb +13 -0
- data/lib/capybara-webkit.rb +1 -0
- data/lib/capybara_webkit_builder.rb +68 -0
- data/spec/browser_spec.rb +173 -0
- data/spec/capybara_webkit_builder_spec.rb +37 -0
- data/spec/connection_spec.rb +54 -0
- data/spec/cookie_jar_spec.rb +48 -0
- data/spec/driver_rendering_spec.rb +80 -0
- data/spec/driver_resize_window_spec.rb +59 -0
- data/spec/driver_spec.rb +1552 -0
- data/spec/integration/driver_spec.rb +20 -0
- data/spec/integration/session_spec.rb +137 -0
- data/spec/self_signed_ssl_cert.rb +42 -0
- data/spec/spec_helper.rb +46 -0
- data/src/Body.h +12 -0
- data/src/ClearCookies.cpp +15 -0
- data/src/ClearCookies.h +11 -0
- data/src/Command.cpp +19 -0
- data/src/Command.h +31 -0
- data/src/CommandFactory.cpp +37 -0
- data/src/CommandFactory.h +16 -0
- data/src/CommandParser.cpp +76 -0
- data/src/CommandParser.h +33 -0
- data/src/Connection.cpp +71 -0
- data/src/Connection.h +37 -0
- data/src/ConsoleMessages.cpp +10 -0
- data/src/ConsoleMessages.h +12 -0
- data/src/CurrentUrl.cpp +68 -0
- data/src/CurrentUrl.h +16 -0
- data/src/Evaluate.cpp +84 -0
- data/src/Evaluate.h +22 -0
- data/src/Execute.cpp +16 -0
- data/src/Execute.h +12 -0
- data/src/Find.cpp +19 -0
- data/src/Find.h +13 -0
- data/src/FrameFocus.cpp +66 -0
- data/src/FrameFocus.h +28 -0
- data/src/GetCookies.cpp +20 -0
- data/src/GetCookies.h +14 -0
- data/src/Header.cpp +18 -0
- data/src/Header.h +11 -0
- data/src/Headers.cpp +10 -0
- data/src/Headers.h +12 -0
- data/src/IgnoreSslErrors.cpp +12 -0
- data/src/IgnoreSslErrors.h +12 -0
- data/src/JavascriptInvocation.cpp +14 -0
- data/src/JavascriptInvocation.h +19 -0
- data/src/NetworkAccessManager.cpp +29 -0
- data/src/NetworkAccessManager.h +19 -0
- data/src/NetworkCookieJar.cpp +101 -0
- data/src/NetworkCookieJar.h +15 -0
- data/src/Node.cpp +14 -0
- data/src/Node.h +13 -0
- data/src/NullCommand.cpp +10 -0
- data/src/NullCommand.h +11 -0
- data/src/PageLoadingCommand.cpp +46 -0
- data/src/PageLoadingCommand.h +40 -0
- data/src/Render.cpp +18 -0
- data/src/Render.h +12 -0
- data/src/RequestedUrl.cpp +12 -0
- data/src/RequestedUrl.h +12 -0
- data/src/Reset.cpp +29 -0
- data/src/Reset.h +15 -0
- data/src/ResizeWindow.cpp +16 -0
- data/src/ResizeWindow.h +12 -0
- data/src/Response.cpp +24 -0
- data/src/Response.h +15 -0
- data/src/Server.cpp +24 -0
- data/src/Server.h +21 -0
- data/src/SetCookie.cpp +16 -0
- data/src/SetCookie.h +11 -0
- data/src/SetProxy.cpp +22 -0
- data/src/SetProxy.h +11 -0
- data/src/Source.cpp +18 -0
- data/src/Source.h +19 -0
- data/src/Status.cpp +12 -0
- data/src/Status.h +12 -0
- data/src/UnsupportedContentHandler.cpp +32 -0
- data/src/UnsupportedContentHandler.h +18 -0
- data/src/Url.cpp +12 -0
- data/src/Url.h +12 -0
- data/src/Visit.cpp +12 -0
- data/src/Visit.h +12 -0
- data/src/WebPage.cpp +239 -0
- data/src/WebPage.h +58 -0
- data/src/body.cpp +10 -0
- data/src/capybara.js +315 -0
- data/src/find_command.h +29 -0
- data/src/main.cpp +33 -0
- data/src/webkit_server.pro +85 -0
- data/src/webkit_server.qrc +5 -0
- data/templates/Command.cpp +10 -0
- data/templates/Command.h +12 -0
- data/webkit_server.pro +4 -0
- metadata +298 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class Capybara::Driver::Webkit
|
4
|
+
class Browser
|
5
|
+
def initialize(connection)
|
6
|
+
@connection = connection
|
7
|
+
end
|
8
|
+
|
9
|
+
def visit(url)
|
10
|
+
command "Visit", url
|
11
|
+
end
|
12
|
+
|
13
|
+
def header(key, value)
|
14
|
+
command("Header", key, value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def find(query)
|
18
|
+
command("Find", query).split(",")
|
19
|
+
end
|
20
|
+
|
21
|
+
def reset!
|
22
|
+
command("Reset")
|
23
|
+
end
|
24
|
+
|
25
|
+
def body
|
26
|
+
command("Body")
|
27
|
+
end
|
28
|
+
|
29
|
+
def source
|
30
|
+
command("Source")
|
31
|
+
end
|
32
|
+
|
33
|
+
def status_code
|
34
|
+
command("Status").to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
def console_messages
|
38
|
+
command("ConsoleMessages").split("\n").map do |messages|
|
39
|
+
parts = messages.split("|", 3)
|
40
|
+
{ :source => parts.first, :line_number => Integer(parts[1]), :message => parts.last }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def error_messages
|
45
|
+
console_messages.select do |message|
|
46
|
+
message[:message] =~ /Error:/
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def response_headers
|
51
|
+
Hash[command("Headers").split("\n").map { |header| header.split(": ") }]
|
52
|
+
end
|
53
|
+
|
54
|
+
def url
|
55
|
+
command("Url")
|
56
|
+
end
|
57
|
+
|
58
|
+
def requested_url
|
59
|
+
command("RequestedUrl")
|
60
|
+
end
|
61
|
+
|
62
|
+
def current_url
|
63
|
+
command("CurrentUrl")
|
64
|
+
end
|
65
|
+
|
66
|
+
def frame_focus(frame_id_or_index=nil)
|
67
|
+
if frame_id_or_index.is_a? Fixnum
|
68
|
+
command("FrameFocus", "", frame_id_or_index.to_s)
|
69
|
+
elsif frame_id_or_index
|
70
|
+
command("FrameFocus", frame_id_or_index)
|
71
|
+
else
|
72
|
+
command("FrameFocus")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def ignore_ssl_errors
|
77
|
+
command("IgnoreSslErrors")
|
78
|
+
end
|
79
|
+
|
80
|
+
def command(name, *args)
|
81
|
+
@connection.puts name
|
82
|
+
@connection.puts args.size
|
83
|
+
args.each do |arg|
|
84
|
+
@connection.puts arg.to_s.bytesize
|
85
|
+
@connection.print arg.to_s
|
86
|
+
end
|
87
|
+
check
|
88
|
+
read_response
|
89
|
+
end
|
90
|
+
|
91
|
+
def evaluate_script(script)
|
92
|
+
json = command('Evaluate', script)
|
93
|
+
JSON.parse("[#{json}]").first
|
94
|
+
end
|
95
|
+
|
96
|
+
def execute_script(script)
|
97
|
+
command('Execute', script)
|
98
|
+
end
|
99
|
+
|
100
|
+
def render(path, width, height)
|
101
|
+
command "Render", path, width, height
|
102
|
+
end
|
103
|
+
|
104
|
+
def set_cookie(cookie)
|
105
|
+
command "SetCookie", cookie
|
106
|
+
end
|
107
|
+
|
108
|
+
def clear_cookies
|
109
|
+
command "ClearCookies"
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_cookies
|
113
|
+
command("GetCookies").lines.map{ |line| line.strip }.select{ |line| !line.empty? }
|
114
|
+
end
|
115
|
+
|
116
|
+
def set_proxy(options = {})
|
117
|
+
options = default_proxy_options.merge(options)
|
118
|
+
command("SetProxy", options[:host], options[:port], options[:user], options[:pass])
|
119
|
+
end
|
120
|
+
|
121
|
+
def clear_proxy
|
122
|
+
command("SetProxy")
|
123
|
+
end
|
124
|
+
|
125
|
+
def resize_window(width, height)
|
126
|
+
command("ResizeWindow", width.to_i, height.to_i)
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def check
|
132
|
+
result = @connection.gets
|
133
|
+
result.strip! if result
|
134
|
+
|
135
|
+
if result.nil?
|
136
|
+
raise WebkitNoResponseError, "No response received from the server."
|
137
|
+
elsif result != 'ok'
|
138
|
+
raise WebkitInvalidResponseError, read_response
|
139
|
+
end
|
140
|
+
|
141
|
+
result
|
142
|
+
end
|
143
|
+
|
144
|
+
def read_response
|
145
|
+
response_length = @connection.gets.to_i
|
146
|
+
if response_length > 0
|
147
|
+
response = @connection.read(response_length)
|
148
|
+
response.force_encoding("UTF-8") if response.respond_to?(:force_encoding)
|
149
|
+
response
|
150
|
+
else
|
151
|
+
""
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def default_proxy_options
|
156
|
+
{
|
157
|
+
:host => "localhost",
|
158
|
+
:port => "0",
|
159
|
+
:user => "",
|
160
|
+
:pass => ""
|
161
|
+
}
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'timeout'
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
class Capybara::Driver::Webkit
|
6
|
+
class Connection
|
7
|
+
WEBKIT_SERVER_START_TIMEOUT = 15
|
8
|
+
|
9
|
+
attr_reader :port
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@socket_class = options[:socket_class] || TCPSocket
|
13
|
+
@stdout = options.has_key?(:stdout) ?
|
14
|
+
options[:stdout] :
|
15
|
+
$stdout
|
16
|
+
start_server
|
17
|
+
connect
|
18
|
+
end
|
19
|
+
|
20
|
+
def puts(string)
|
21
|
+
@socket.puts string
|
22
|
+
end
|
23
|
+
|
24
|
+
def print(string)
|
25
|
+
@socket.print string
|
26
|
+
end
|
27
|
+
|
28
|
+
def gets
|
29
|
+
@socket.gets
|
30
|
+
end
|
31
|
+
|
32
|
+
def read(length)
|
33
|
+
@socket.read(length)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def start_server
|
39
|
+
pipe = fork_server
|
40
|
+
@port = discover_port(pipe)
|
41
|
+
@stdout_thread = Thread.new do
|
42
|
+
Thread.current.abort_on_exception = true
|
43
|
+
forward_stdout(pipe)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def fork_server
|
48
|
+
server_path = File.expand_path("../../../../../bin/webkit_server", __FILE__)
|
49
|
+
pipe, @pid = server_pipe_and_pid(server_path)
|
50
|
+
register_shutdown_hook
|
51
|
+
pipe
|
52
|
+
end
|
53
|
+
|
54
|
+
def kill_process(pid)
|
55
|
+
if RUBY_PLATFORM =~ /mingw32/
|
56
|
+
Process.kill(9, pid)
|
57
|
+
else
|
58
|
+
Process.kill("INT", pid)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def register_shutdown_hook
|
63
|
+
@owner_pid = Process.pid
|
64
|
+
at_exit do
|
65
|
+
if Process.pid == @owner_pid
|
66
|
+
kill_process(@pid)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def server_pipe_and_pid(server_path)
|
72
|
+
cmdline = [server_path]
|
73
|
+
pipe = IO.popen(cmdline.join(" "))
|
74
|
+
[pipe, pipe.pid]
|
75
|
+
end
|
76
|
+
|
77
|
+
def discover_port(read_pipe)
|
78
|
+
return unless IO.select([read_pipe], nil, nil, WEBKIT_SERVER_START_TIMEOUT)
|
79
|
+
((read_pipe.first || '').match(/listening on port: (\d+)/) || [])[1].to_i
|
80
|
+
end
|
81
|
+
|
82
|
+
def forward_stdout(pipe)
|
83
|
+
while pipe_readable?(pipe)
|
84
|
+
line = pipe.readline
|
85
|
+
if @stdout
|
86
|
+
@stdout.write(line)
|
87
|
+
@stdout.flush
|
88
|
+
end
|
89
|
+
end
|
90
|
+
rescue EOFError
|
91
|
+
end
|
92
|
+
|
93
|
+
def connect
|
94
|
+
Timeout.timeout(5) do
|
95
|
+
while @socket.nil?
|
96
|
+
attempt_connect
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def attempt_connect
|
102
|
+
@socket = @socket_class.open("127.0.0.1", @port)
|
103
|
+
rescue Errno::ECONNREFUSED
|
104
|
+
end
|
105
|
+
|
106
|
+
if !defined?(RUBY_ENGINE) || (RUBY_ENGINE == "ruby" && RUBY_VERSION <= "1.8")
|
107
|
+
# please note the use of IO::select() here, as it is used specifically to
|
108
|
+
# preserve correct signal handling behavior in ruby 1.8.
|
109
|
+
# https://github.com/thibaudgg/rb-fsevent/commit/d1a868bf8dc72dbca102bedbadff76c7e6c2dc21
|
110
|
+
# https://github.com/thibaudgg/rb-fsevent/blob/1ca42b987596f350ee7b19d8f8210b7b6ae8766b/ext/fsevent/fsevent_watch.c#L171
|
111
|
+
def pipe_readable?(pipe)
|
112
|
+
IO.select([pipe])
|
113
|
+
end
|
114
|
+
else
|
115
|
+
def pipe_readable?(pipe)
|
116
|
+
!pipe.eof?
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'webrick'
|
2
|
+
|
3
|
+
# A simple cookie jar implementation.
|
4
|
+
# Does not take special cookie attributes
|
5
|
+
# into account like expire, max-age, httponly, secure
|
6
|
+
class Capybara::Driver::Webkit::CookieJar
|
7
|
+
attr_reader :browser
|
8
|
+
|
9
|
+
def initialize(browser)
|
10
|
+
@browser = browser
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](*args)
|
14
|
+
cookie = find(*args)
|
15
|
+
cookie && cookie.value
|
16
|
+
end
|
17
|
+
|
18
|
+
def find(name, domain = nil, path = "/")
|
19
|
+
# we are sorting by path size because more specific paths take
|
20
|
+
# precendence
|
21
|
+
cookies.sort_by { |c| -c.path.size }.find { |c|
|
22
|
+
c.name.downcase == name.downcase &&
|
23
|
+
(!domain || valid_domain?(c, domain)) &&
|
24
|
+
(!path || valid_path?(c, path))
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def valid_domain?(cookie, domain)
|
31
|
+
ends_with?(("." + domain).downcase,
|
32
|
+
normalize_domain(cookie.domain).downcase)
|
33
|
+
end
|
34
|
+
|
35
|
+
def normalize_domain(domain)
|
36
|
+
domain = "." + domain unless domain[0,1] == "."
|
37
|
+
domain
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid_path?(cookie, path)
|
41
|
+
starts_with?(path, cookie.path)
|
42
|
+
end
|
43
|
+
|
44
|
+
def ends_with?(str, suffix)
|
45
|
+
str[-suffix.size..-1] == suffix
|
46
|
+
end
|
47
|
+
|
48
|
+
def starts_with?(str, prefix)
|
49
|
+
str[0, prefix.size] == prefix
|
50
|
+
end
|
51
|
+
|
52
|
+
def cookies
|
53
|
+
browser.get_cookies.map { |c| WEBrick::Cookie.parse_set_cookie(c) }
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
class Capybara::Driver::Webkit
|
2
|
+
class Node < Capybara::Driver::Node
|
3
|
+
NBSP = "\xC2\xA0"
|
4
|
+
NBSP.force_encoding("UTF-8") if NBSP.respond_to?(:force_encoding)
|
5
|
+
|
6
|
+
def text
|
7
|
+
invoke("text").gsub(NBSP, ' ').gsub(/\s+/u, ' ').strip
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](name)
|
11
|
+
value = invoke("attribute", name)
|
12
|
+
if name == 'checked' || name == 'disabled'
|
13
|
+
value == 'true'
|
14
|
+
else
|
15
|
+
value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def value
|
20
|
+
if multiple_select?
|
21
|
+
self.find(".//option").select(&:selected?).map(&:value)
|
22
|
+
else
|
23
|
+
invoke "value"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def set(value)
|
28
|
+
invoke "set", value
|
29
|
+
end
|
30
|
+
|
31
|
+
def select_option
|
32
|
+
invoke "selectOption"
|
33
|
+
end
|
34
|
+
|
35
|
+
def unselect_option
|
36
|
+
select = find("ancestor::select").first
|
37
|
+
if select.multiple_select?
|
38
|
+
invoke "unselectOption"
|
39
|
+
else
|
40
|
+
raise Capybara::UnselectNotAllowed
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def click
|
45
|
+
invoke "click"
|
46
|
+
end
|
47
|
+
|
48
|
+
def drag_to(element)
|
49
|
+
invoke 'dragTo', element.native
|
50
|
+
end
|
51
|
+
|
52
|
+
def tag_name
|
53
|
+
invoke "tagName"
|
54
|
+
end
|
55
|
+
|
56
|
+
def visible?
|
57
|
+
invoke("visible") == "true"
|
58
|
+
end
|
59
|
+
|
60
|
+
def selected?
|
61
|
+
invoke("selected") == "true"
|
62
|
+
end
|
63
|
+
|
64
|
+
def checked?
|
65
|
+
self['checked']
|
66
|
+
end
|
67
|
+
|
68
|
+
def disabled?
|
69
|
+
self['disabled']
|
70
|
+
end
|
71
|
+
|
72
|
+
def path
|
73
|
+
invoke "path"
|
74
|
+
end
|
75
|
+
|
76
|
+
def submit(opts = {})
|
77
|
+
invoke "submit"
|
78
|
+
end
|
79
|
+
|
80
|
+
def trigger(event)
|
81
|
+
invoke "trigger", event
|
82
|
+
end
|
83
|
+
|
84
|
+
def find(xpath)
|
85
|
+
invoke("findWithin", xpath).split(',').map do |native|
|
86
|
+
self.class.new(driver, native)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def invoke(name, *args)
|
91
|
+
if allow_unattached_nodes? || attached?
|
92
|
+
browser.command "Node", name, native, *args
|
93
|
+
else
|
94
|
+
raise Capybara::Driver::Webkit::NodeNotAttachedError
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def allow_unattached_nodes?
|
99
|
+
!automatic_reload?
|
100
|
+
end
|
101
|
+
|
102
|
+
def automatic_reload?
|
103
|
+
Capybara.respond_to?(:automatic_reload) && Capybara.automatic_reload
|
104
|
+
end
|
105
|
+
|
106
|
+
def attached?
|
107
|
+
browser.command("Node", "isAttached", native) == "true"
|
108
|
+
end
|
109
|
+
|
110
|
+
def browser
|
111
|
+
driver.browser
|
112
|
+
end
|
113
|
+
|
114
|
+
def multiple_select?
|
115
|
+
self.tag_name == "select" && self["multiple"] == "multiple"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Wraps the TCP socket and prints data sent and received. Used for debugging
|
2
|
+
# the wire protocol. You can use this by passing a :socket_class to Browser.
|
3
|
+
class Capybara::Driver::Webkit
|
4
|
+
class SocketDebugger
|
5
|
+
def self.open(host, port)
|
6
|
+
real_socket = TCPSocket.open(host, port)
|
7
|
+
new(real_socket)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(socket)
|
11
|
+
@socket = socket
|
12
|
+
end
|
13
|
+
|
14
|
+
def read(length)
|
15
|
+
received @socket.read(length)
|
16
|
+
end
|
17
|
+
|
18
|
+
def puts(line)
|
19
|
+
sent line
|
20
|
+
@socket.puts(line)
|
21
|
+
end
|
22
|
+
|
23
|
+
def print(content)
|
24
|
+
sent content
|
25
|
+
@socket.print(content)
|
26
|
+
end
|
27
|
+
|
28
|
+
def gets
|
29
|
+
received @socket.gets
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def sent(content)
|
35
|
+
Kernel.puts " >> " + content.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
def received(content)
|
39
|
+
Kernel.puts " << " + content.to_s
|
40
|
+
content
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require "capybara"
|
2
|
+
require "capybara/driver/webkit/version"
|
3
|
+
require "capybara/driver/webkit/node"
|
4
|
+
require "capybara/driver/webkit/connection"
|
5
|
+
require "capybara/driver/webkit/browser"
|
6
|
+
require "capybara/driver/webkit/socket_debugger"
|
7
|
+
require "capybara/driver/webkit/cookie_jar"
|
8
|
+
|
9
|
+
class Capybara::Driver::Webkit
|
10
|
+
class WebkitInvalidResponseError < StandardError
|
11
|
+
end
|
12
|
+
|
13
|
+
class WebkitNoResponseError < StandardError
|
14
|
+
end
|
15
|
+
|
16
|
+
class NodeNotAttachedError < Capybara::ElementNotFound
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :browser
|
20
|
+
|
21
|
+
def initialize(app, options={})
|
22
|
+
@app = app
|
23
|
+
@options = options
|
24
|
+
@rack_server = Capybara::Server.new(@app)
|
25
|
+
@rack_server.boot if Capybara.run_server
|
26
|
+
@browser = options[:browser] || Browser.new(Connection.new(options))
|
27
|
+
@browser.ignore_ssl_errors if options[:ignore_ssl_errors]
|
28
|
+
end
|
29
|
+
|
30
|
+
def current_url
|
31
|
+
browser.current_url
|
32
|
+
end
|
33
|
+
|
34
|
+
def requested_url
|
35
|
+
browser.requested_url
|
36
|
+
end
|
37
|
+
|
38
|
+
def visit(path)
|
39
|
+
browser.visit(url(path))
|
40
|
+
end
|
41
|
+
|
42
|
+
def find(query)
|
43
|
+
browser.find(query).map { |native| Node.new(self, native) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def source
|
47
|
+
browser.source
|
48
|
+
end
|
49
|
+
|
50
|
+
def body
|
51
|
+
browser.body
|
52
|
+
end
|
53
|
+
|
54
|
+
def header(key, value)
|
55
|
+
browser.header(key, value)
|
56
|
+
end
|
57
|
+
|
58
|
+
def execute_script(script)
|
59
|
+
value = browser.execute_script script
|
60
|
+
value.empty? ? nil : value
|
61
|
+
end
|
62
|
+
|
63
|
+
def evaluate_script(script)
|
64
|
+
browser.evaluate_script script
|
65
|
+
end
|
66
|
+
|
67
|
+
def console_messages
|
68
|
+
browser.console_messages
|
69
|
+
end
|
70
|
+
|
71
|
+
def error_messages
|
72
|
+
browser.error_messages
|
73
|
+
end
|
74
|
+
|
75
|
+
def response_headers
|
76
|
+
browser.response_headers
|
77
|
+
end
|
78
|
+
|
79
|
+
def status_code
|
80
|
+
browser.status_code
|
81
|
+
end
|
82
|
+
|
83
|
+
def resize_window(width, height)
|
84
|
+
browser.resize_window(width, height)
|
85
|
+
end
|
86
|
+
|
87
|
+
def within_frame(frame_id_or_index)
|
88
|
+
browser.frame_focus(frame_id_or_index)
|
89
|
+
begin
|
90
|
+
yield
|
91
|
+
ensure
|
92
|
+
browser.frame_focus
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def within_window(handle)
|
97
|
+
raise Capybara::NotSupportedByDriverError
|
98
|
+
end
|
99
|
+
|
100
|
+
def wait?
|
101
|
+
true
|
102
|
+
end
|
103
|
+
|
104
|
+
def wait_until(*args)
|
105
|
+
end
|
106
|
+
|
107
|
+
def reset!
|
108
|
+
browser.reset!
|
109
|
+
end
|
110
|
+
|
111
|
+
def has_shortcircuit_timeout?
|
112
|
+
false
|
113
|
+
end
|
114
|
+
|
115
|
+
def render(path, options={})
|
116
|
+
options[:width] ||= 1000
|
117
|
+
options[:height] ||= 10
|
118
|
+
|
119
|
+
browser.render path, options[:width], options[:height]
|
120
|
+
end
|
121
|
+
|
122
|
+
def server_port
|
123
|
+
@rack_server.port
|
124
|
+
end
|
125
|
+
|
126
|
+
def cookies
|
127
|
+
@cookie_jar ||= CookieJar.new(browser)
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def url(path)
|
133
|
+
@rack_server.url(path)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Capybara
|
2
|
+
module Webkit
|
3
|
+
module RspecMatchers
|
4
|
+
RSpec::Matchers.define :have_errors do |expected|
|
5
|
+
match do |actual|
|
6
|
+
actual = resolve(actual)
|
7
|
+
actual.error_messages.any?
|
8
|
+
end
|
9
|
+
|
10
|
+
failure_message_for_should do |actual|
|
11
|
+
"Expected Javascript errors, but there were none."
|
12
|
+
end
|
13
|
+
|
14
|
+
failure_message_for_should_not do |actual|
|
15
|
+
actual = resolve(actual)
|
16
|
+
"Expected no Javascript errors, got:\n#{error_messages_for(actual)}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def error_messages_for(obj)
|
20
|
+
obj.error_messages.map do |m|
|
21
|
+
" - #{m[:message]}"
|
22
|
+
end.join("\n")
|
23
|
+
end
|
24
|
+
|
25
|
+
def resolve(actual)
|
26
|
+
if actual.respond_to? :page
|
27
|
+
actual.page.driver
|
28
|
+
elsif actual.respond_to? :driver
|
29
|
+
actual.driver
|
30
|
+
else
|
31
|
+
actual
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "capybara"
|
2
|
+
require "capybara/driver/webkit"
|
3
|
+
|
4
|
+
Capybara.register_driver :webkit do |app|
|
5
|
+
Capybara::Driver::Webkit.new(app)
|
6
|
+
end
|
7
|
+
|
8
|
+
Capybara.register_driver :webkit_debug do |app|
|
9
|
+
connection = Capybara::Driver::Webkit::Connection.new(
|
10
|
+
:socket_class => Capybara::Driver::Webkit::SocketDebugger)
|
11
|
+
browser = Capybara::Driver::Webkit::Browser.new(connection)
|
12
|
+
Capybara::Driver::Webkit.new(app, :browser => browser)
|
13
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "capybara/webkit"
|