capybara-webkit 0.7.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +1 -6
- data/Gemfile.lock +15 -13
- data/README.md +9 -8
- data/Rakefile +3 -21
- data/capybara-webkit.gemspec +21 -18
- data/gemfiles/1.0.gemfile.lock +18 -13
- data/gemfiles/1.1.gemfile.lock +18 -13
- data/lib/capybara/driver/webkit.rb +20 -1
- data/lib/capybara/driver/webkit/browser.rb +49 -5
- data/lib/capybara/driver/webkit/cookie_jar.rb +55 -0
- data/lib/capybara/driver/webkit/node.rb +5 -1
- data/lib/capybara/driver/webkit/version.rb +7 -0
- data/lib/capybara/webkit/matchers.rb +39 -0
- data/lib/capybara_webkit_builder.rb +2 -1
- data/spec/browser_spec.rb +150 -2
- data/spec/cookie_jar_spec.rb +48 -0
- data/spec/driver_spec.rb +248 -16
- data/spec/self_signed_ssl_cert.rb +42 -0
- data/src/CommandFactory.cpp +31 -0
- data/src/CommandFactory.h +16 -0
- data/src/CommandParser.cpp +68 -0
- data/src/CommandParser.h +29 -0
- data/src/Connection.cpp +14 -83
- data/src/Connection.h +5 -8
- data/src/ConsoleMessages.cpp +11 -0
- data/src/ConsoleMessages.h +12 -0
- data/src/RequestedUrl.cpp +15 -0
- data/src/RequestedUrl.h +12 -0
- data/src/Reset.cpp +1 -0
- data/src/Server.cpp +2 -1
- data/src/Server.h +1 -1
- data/src/SetProxy.cpp +24 -0
- data/src/SetProxy.h +11 -0
- data/src/UnsupportedContentHandler.cpp +1 -0
- data/src/Visit.cpp +1 -1
- data/src/WebPage.cpp +28 -2
- data/src/WebPage.h +7 -0
- data/src/capybara.js +54 -11
- data/src/find_command.h +4 -0
- data/src/main.cpp +4 -1
- data/src/webkit_server.pro +10 -0
- metadata +67 -47
@@ -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,39 @@
|
|
1
|
+
module Capybara
|
2
|
+
module Webkit
|
3
|
+
module RspecMatchers
|
4
|
+
extend RSpec::Matchers::DSL
|
5
|
+
|
6
|
+
matcher :have_errors do |expected|
|
7
|
+
match do |actual|
|
8
|
+
actual = resolve(actual)
|
9
|
+
actual.error_messages.any?
|
10
|
+
end
|
11
|
+
|
12
|
+
failure_message_for_should do |actual|
|
13
|
+
"Expected Javascript errors, but there were none."
|
14
|
+
end
|
15
|
+
failure_message_for_should_not do |actual|
|
16
|
+
actual = resolve(actual)
|
17
|
+
"Expected no Javascript errors, got:\n#{error_messages_for(actual)}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def error_messages_for(obj)
|
21
|
+
obj.error_messages.map do |m|
|
22
|
+
" - #{m[:message]}"
|
23
|
+
end.join("\n")
|
24
|
+
end
|
25
|
+
|
26
|
+
def resolve(actual)
|
27
|
+
if actual.respond_to? :page
|
28
|
+
actual.page.driver
|
29
|
+
elsif actual.respond_to? :driver
|
30
|
+
actual.driver
|
31
|
+
else
|
32
|
+
actual
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "fileutils"
|
2
|
+
require "rbconfig"
|
2
3
|
|
3
4
|
module CapybaraWebkitBuilder
|
4
5
|
extend self
|
@@ -11,7 +12,7 @@ module CapybaraWebkitBuilder
|
|
11
12
|
def makefile
|
12
13
|
qmake_binaries = ['qmake', 'qmake-qt4']
|
13
14
|
qmake = qmake_binaries.detect { |qmake| system("which #{qmake}") }
|
14
|
-
case
|
15
|
+
case RbConfig::CONFIG['host_os']
|
15
16
|
when /linux/
|
16
17
|
system("#{qmake} -spec linux-g++")
|
17
18
|
when /freebsd/
|
data/spec/browser_spec.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'self_signed_ssl_cert'
|
2
3
|
require 'stringio'
|
3
4
|
require 'capybara/driver/webkit/browser'
|
5
|
+
require 'socket'
|
6
|
+
require 'base64'
|
4
7
|
|
5
8
|
describe Capybara::Driver::Webkit::Browser do
|
6
9
|
|
7
10
|
let(:browser) { Capybara::Driver::Webkit::Browser.new }
|
11
|
+
let(:browser_ignore_ssl_err) {
|
12
|
+
Capybara::Driver::Webkit::Browser.new(:ignore_ssl_errors => true)
|
13
|
+
}
|
8
14
|
|
9
15
|
describe '#server_port' do
|
10
16
|
subject { browser.server_port }
|
@@ -23,13 +29,155 @@ describe Capybara::Driver::Webkit::Browser do
|
|
23
29
|
new_browser.server_port.should_not == browser.server_port
|
24
30
|
end
|
25
31
|
end
|
26
|
-
|
32
|
+
|
27
33
|
it 'forwards stdout to the given IO object' do
|
28
34
|
io = StringIO.new
|
29
35
|
new_browser = Capybara::Driver::Webkit::Browser.new(:stdout => io)
|
30
36
|
new_browser.execute_script('console.log("hello world")')
|
31
37
|
sleep(0.5)
|
32
|
-
io.string.should
|
38
|
+
io.string.should include "hello world\n"
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'handling of SSL validation errors' do
|
42
|
+
before do
|
43
|
+
# set up minimal HTTPS server
|
44
|
+
@host = "127.0.0.1"
|
45
|
+
@server = TCPServer.new(@host, 0)
|
46
|
+
@port = @server.addr[1]
|
47
|
+
|
48
|
+
# set up SSL layer
|
49
|
+
ssl_serv = OpenSSL::SSL::SSLServer.new(@server, $openssl_self_signed_ctx)
|
50
|
+
|
51
|
+
@server_thread = Thread.new(ssl_serv) do |serv|
|
52
|
+
while conn = serv.accept do
|
53
|
+
# read request
|
54
|
+
request = []
|
55
|
+
until (line = conn.readline.strip).empty?
|
56
|
+
request << line
|
57
|
+
end
|
58
|
+
|
59
|
+
# write response
|
60
|
+
html = "<html><body>D'oh!</body></html>"
|
61
|
+
conn.write "HTTP/1.1 200 OK\r\n"
|
62
|
+
conn.write "Content-Type:text/html\r\n"
|
63
|
+
conn.write "Content-Length: %i\r\n" % html.size
|
64
|
+
conn.write "\r\n"
|
65
|
+
conn.write html
|
66
|
+
conn.close
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
after do
|
72
|
+
@server_thread.kill
|
73
|
+
@server.close
|
74
|
+
end
|
75
|
+
|
76
|
+
it "doesn't accept a self-signed certificate by default" do
|
77
|
+
lambda { browser.visit "https://#{@host}:#{@port}/" }.should raise_error
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'accepts a self-signed certificate if configured to do so' do
|
81
|
+
browser_ignore_ssl_err.visit "https://#{@host}:#{@port}/"
|
82
|
+
end
|
33
83
|
end
|
84
|
+
describe "forking" do
|
85
|
+
it "only shuts down the server from the main process" do
|
86
|
+
browser.reset!
|
87
|
+
pid = fork {}
|
88
|
+
Process.wait(pid)
|
89
|
+
expect { browser.reset! }.not_to raise_error
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe '#set_proxy' do
|
94
|
+
before do
|
95
|
+
@host = '127.0.0.1'
|
96
|
+
@user = 'user'
|
97
|
+
@pass = 'secret'
|
98
|
+
@url = "http://example.org/"
|
99
|
+
|
100
|
+
@server = TCPServer.new(@host, 0)
|
101
|
+
@port = @server.addr[1]
|
102
|
+
|
103
|
+
@proxy_requests = []
|
104
|
+
@proxy = Thread.new(@server, @proxy_requests) do |serv, proxy_requests|
|
105
|
+
while conn = serv.accept do
|
106
|
+
# read request
|
107
|
+
request = []
|
108
|
+
until (line = conn.readline.strip).empty?
|
109
|
+
request << line
|
110
|
+
end
|
34
111
|
|
112
|
+
# send response
|
113
|
+
auth_header = request.find { |h| h =~ /Authorization:/i }
|
114
|
+
if auth_header || request[0].split(/\s+/)[1] =~ /^\//
|
115
|
+
html = "<html><body>D'oh!</body></html>"
|
116
|
+
conn.write "HTTP/1.1 200 OK\r\n"
|
117
|
+
conn.write "Content-Type:text/html\r\n"
|
118
|
+
conn.write "Content-Length: %i\r\n" % html.size
|
119
|
+
conn.write "\r\n"
|
120
|
+
conn.write html
|
121
|
+
conn.close
|
122
|
+
proxy_requests << request if auth_header
|
123
|
+
else
|
124
|
+
conn.write "HTTP/1.1 407 Proxy Auth Required\r\n"
|
125
|
+
conn.write "Proxy-Authenticate: Basic realm=\"Proxy\"\r\n"
|
126
|
+
conn.write "\r\n"
|
127
|
+
conn.close
|
128
|
+
proxy_requests << request
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
browser.set_proxy(:host => @host,
|
134
|
+
:port => @port,
|
135
|
+
:user => @user,
|
136
|
+
:pass => @pass)
|
137
|
+
browser.visit @url
|
138
|
+
@proxy_requests.size.should == 2
|
139
|
+
@request = @proxy_requests[-1]
|
140
|
+
end
|
141
|
+
|
142
|
+
after do
|
143
|
+
@proxy.kill
|
144
|
+
@server.close
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'uses the HTTP proxy correctly' do
|
148
|
+
@request[0].should match /^GET\s+http:\/\/example.org\/\s+HTTP/i
|
149
|
+
@request.find { |header|
|
150
|
+
header =~ /^Host:\s+example.org$/i }.should_not be nil
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'sends correct proxy authentication' do
|
154
|
+
auth_header = @request.find { |header|
|
155
|
+
header =~ /^Proxy-Authorization:\s+/i }
|
156
|
+
auth_header.should_not be nil
|
157
|
+
|
158
|
+
user, pass = Base64.decode64(auth_header.split(/\s+/)[-1]).split(":")
|
159
|
+
user.should == @user
|
160
|
+
pass.should == @pass
|
161
|
+
end
|
162
|
+
|
163
|
+
it "uses the proxies' response" do
|
164
|
+
browser.body.should include "D'oh!"
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'uses original URL' do
|
168
|
+
browser.url.should == @url
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'uses URLs changed by javascript' do
|
172
|
+
browser.execute_script "window.history.pushState('', '', '/blah')"
|
173
|
+
browser.requested_url.should == 'http://example.org/blah'
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'is possible to disable proxy again' do
|
177
|
+
@proxy_requests.clear
|
178
|
+
browser.clear_proxy
|
179
|
+
browser.visit "http://#{@host}:#{@port}/"
|
180
|
+
@proxy_requests.size.should == 0
|
181
|
+
end
|
182
|
+
end
|
35
183
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'capybara/driver/webkit/cookie_jar'
|
3
|
+
|
4
|
+
describe Capybara::Driver::Webkit::CookieJar do
|
5
|
+
let(:browser) {
|
6
|
+
browser = double("Browser")
|
7
|
+
browser.stub(:get_cookies) { [
|
8
|
+
"cookie1=1; domain=.example.org; path=/",
|
9
|
+
"cookie1=2; domain=.example.org; path=/dir1/",
|
10
|
+
"cookie1=3; domain=.facebook.com; path=/",
|
11
|
+
"cookie2=4; domain=.sub1.example.org; path=/",
|
12
|
+
] }
|
13
|
+
browser
|
14
|
+
}
|
15
|
+
|
16
|
+
subject { Capybara::Driver::Webkit::CookieJar.new(browser) }
|
17
|
+
|
18
|
+
describe "#find" do
|
19
|
+
it "returns a cookie object" do
|
20
|
+
subject.find("cookie1", "www.facebook.com").domain.should == ".facebook.com"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns the right cookie for every given domain/path" do
|
24
|
+
subject.find("cookie1", "example.org").value.should == "1"
|
25
|
+
subject.find("cookie1", "www.facebook.com").value.should == "3"
|
26
|
+
subject.find("cookie2", "sub1.example.org").value.should == "4"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "does not return a cookie from other domain" do
|
30
|
+
subject.find("cookie2", "www.example.org").should == nil
|
31
|
+
end
|
32
|
+
|
33
|
+
it "respects path precedence rules" do
|
34
|
+
subject.find("cookie1", "www.example.org").value.should == "1"
|
35
|
+
subject.find("cookie1", "www.example.org", "/dir1/123").value.should == "2"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#[]" do
|
40
|
+
it "returns the first matching cookie's value" do
|
41
|
+
subject["cookie1", "example.org"].should == "1"
|
42
|
+
end
|
43
|
+
|
44
|
+
it "returns nil if no cookie is found" do
|
45
|
+
subject["notexisting"].should == nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/spec/driver_spec.rb
CHANGED
@@ -20,7 +20,7 @@ describe Capybara::Driver::Webkit do
|
|
20
20
|
p_id = "greeting"
|
21
21
|
msg = "hello"
|
22
22
|
iframe = "<iframe id=\"f\" src=\"/?iframe=true\"></iframe>"
|
23
|
-
end
|
23
|
+
end
|
24
24
|
body = <<-HTML
|
25
25
|
<html>
|
26
26
|
<head>
|
@@ -140,12 +140,16 @@ describe Capybara::Driver::Webkit do
|
|
140
140
|
@app = lambda do |env|
|
141
141
|
[200, {"Content-Type" => "text/css", "Content-Length" => body.length.to_s}, [body]]
|
142
142
|
end
|
143
|
+
subject.visit("/")
|
143
144
|
end
|
144
145
|
|
145
146
|
it "renders unsupported content types gracefully" do
|
146
|
-
subject.visit("/")
|
147
147
|
subject.body.should =~ /css/
|
148
148
|
end
|
149
|
+
|
150
|
+
it "sets the response headers with respect to the unsupported request" do
|
151
|
+
subject.response_headers["Content-Type"].should == "text/css"
|
152
|
+
end
|
149
153
|
end
|
150
154
|
|
151
155
|
context "hello app" do
|
@@ -219,9 +223,9 @@ describe Capybara::Driver::Webkit do
|
|
219
223
|
subject.current_url.should == "http://127.0.0.1:#{port}/hello/world?success=true"
|
220
224
|
end
|
221
225
|
|
222
|
-
it "
|
223
|
-
subject.visit("/hello
|
224
|
-
subject.current_url.should =~ /
|
226
|
+
it "does not double-encode URLs" do
|
227
|
+
subject.visit("/hello/world?success=%25true")
|
228
|
+
subject.current_url.should =~ /success=\%25true/
|
225
229
|
end
|
226
230
|
|
227
231
|
it "visits a page with an anchor" do
|
@@ -316,6 +320,40 @@ describe Capybara::Driver::Webkit do
|
|
316
320
|
end
|
317
321
|
end
|
318
322
|
|
323
|
+
context "console messages app" do
|
324
|
+
|
325
|
+
before(:all) do
|
326
|
+
@app = lambda do |env|
|
327
|
+
body = <<-HTML
|
328
|
+
<html>
|
329
|
+
<head>
|
330
|
+
</head>
|
331
|
+
<body>
|
332
|
+
<script type="text/javascript">
|
333
|
+
console.log("hello");
|
334
|
+
console.log("hello again");
|
335
|
+
oops
|
336
|
+
</script>
|
337
|
+
</body>
|
338
|
+
</html>
|
339
|
+
HTML
|
340
|
+
[200,
|
341
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
342
|
+
[body]]
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
it "collects messages logged to the console" do
|
347
|
+
subject.console_messages.first.should include :source, :message => "hello", :line_number => 6
|
348
|
+
subject.console_messages.length.should eq 3
|
349
|
+
end
|
350
|
+
|
351
|
+
it "logs errors to the console" do
|
352
|
+
subject.error_messages.length.should eq 1
|
353
|
+
end
|
354
|
+
|
355
|
+
end
|
356
|
+
|
319
357
|
context "form app" do
|
320
358
|
before(:all) do
|
321
359
|
@app = lambda do |env|
|
@@ -497,8 +535,13 @@ describe Capybara::Driver::Webkit do
|
|
497
535
|
body = <<-HTML
|
498
536
|
<html><body>
|
499
537
|
<form action="/" method="GET">
|
500
|
-
<input class="watch" type="
|
538
|
+
<input class="watch" type="email"/>
|
539
|
+
<input class="watch" type="number"/>
|
501
540
|
<input class="watch" type="password"/>
|
541
|
+
<input class="watch" type="search"/>
|
542
|
+
<input class="watch" type="tel"/>
|
543
|
+
<input class="watch" type="text"/>
|
544
|
+
<input class="watch" type="url"/>
|
502
545
|
<textarea class="watch"></textarea>
|
503
546
|
<input class="watch" type="checkbox"/>
|
504
547
|
<input class="watch" type="radio"/>
|
@@ -540,9 +583,11 @@ describe Capybara::Driver::Webkit do
|
|
540
583
|
%w{change blur}).flatten
|
541
584
|
end
|
542
585
|
|
543
|
-
|
544
|
-
|
545
|
-
|
586
|
+
%w(email number password search tel text url).each do | field_type |
|
587
|
+
it "triggers text input events on inputs of type #{field_type}" do
|
588
|
+
subject.find("//input[@type='#{field_type}']").first.set(newtext)
|
589
|
+
subject.find("//li").map(&:text).should == keyevents
|
590
|
+
end
|
546
591
|
end
|
547
592
|
|
548
593
|
it "triggers textarea input events" do
|
@@ -550,11 +595,6 @@ describe Capybara::Driver::Webkit do
|
|
550
595
|
subject.find("//li").map(&:text).should == keyevents
|
551
596
|
end
|
552
597
|
|
553
|
-
it "triggers password input events" do
|
554
|
-
subject.find("//input[@type='password']").first.set(newtext)
|
555
|
-
subject.find("//li").map(&:text).should == keyevents
|
556
|
-
end
|
557
|
-
|
558
598
|
it "triggers radio input events" do
|
559
599
|
subject.find("//input[@type='radio']").first.set(true)
|
560
600
|
subject.find("//li").map(&:text).should == %w(click change)
|
@@ -838,7 +878,7 @@ describe Capybara::Driver::Webkit do
|
|
838
878
|
[body]]
|
839
879
|
end
|
840
880
|
end
|
841
|
-
|
881
|
+
|
842
882
|
it "raises a webkit error for the requested url" do
|
843
883
|
make_the_server_go_away
|
844
884
|
expect {
|
@@ -853,7 +893,7 @@ describe Capybara::Driver::Webkit do
|
|
853
893
|
subject.browser.instance_variable_get(:@socket).unstub!(:puts)
|
854
894
|
subject.browser.instance_variable_get(:@socket).unstub!(:print)
|
855
895
|
end
|
856
|
-
|
896
|
+
|
857
897
|
def make_the_server_go_away
|
858
898
|
subject.browser.instance_variable_get(:@socket).stub!(:gets).and_return(nil)
|
859
899
|
subject.browser.instance_variable_get(:@socket).stub!(:puts)
|
@@ -943,6 +983,10 @@ describe Capybara::Driver::Webkit do
|
|
943
983
|
cookie["domain"].should include "127.0.0.1"
|
944
984
|
cookie["path"].should == "/"
|
945
985
|
end
|
986
|
+
|
987
|
+
it "allows reading access to cookies using a nice syntax" do
|
988
|
+
subject.cookies["cookie"].should == "abc"
|
989
|
+
end
|
946
990
|
end
|
947
991
|
|
948
992
|
context "with socket debugger" do
|
@@ -1014,6 +1058,98 @@ describe Capybara::Driver::Webkit do
|
|
1014
1058
|
end
|
1015
1059
|
end
|
1016
1060
|
|
1061
|
+
context "app with a lot of HTML tags" do
|
1062
|
+
before(:all) do
|
1063
|
+
@app = lambda do |env|
|
1064
|
+
body = <<-HTML
|
1065
|
+
<html>
|
1066
|
+
<head>
|
1067
|
+
<title>My eBook</title>
|
1068
|
+
<meta class="charset" name="charset" value="utf-8" />
|
1069
|
+
<meta class="author" name="author" value="Firstname Lastname" />
|
1070
|
+
</head>
|
1071
|
+
<body>
|
1072
|
+
<div id="toc">
|
1073
|
+
<table>
|
1074
|
+
<thead id="head">
|
1075
|
+
<tr><td class="td1">Chapter</td><td>Page</td></tr>
|
1076
|
+
</thead>
|
1077
|
+
<tbody>
|
1078
|
+
<tr><td>Intro</td><td>1</td></tr>
|
1079
|
+
<tr><td>Chapter 1</td><td class="td2">1</td></tr>
|
1080
|
+
<tr><td>Chapter 2</td><td>1</td></tr>
|
1081
|
+
</tbody>
|
1082
|
+
</table>
|
1083
|
+
</div>
|
1084
|
+
|
1085
|
+
<h1 class="h1">My first book</h1>
|
1086
|
+
<p class="p1">Written by me</p>
|
1087
|
+
<div id="intro" class="intro">
|
1088
|
+
<p>Let's try out XPath</p>
|
1089
|
+
<p class="p2">in capybara-webkit</p>
|
1090
|
+
</div>
|
1091
|
+
|
1092
|
+
<h2 class="chapter1">Chapter 1</h2>
|
1093
|
+
<p>This paragraph is fascinating.</p>
|
1094
|
+
<p class="p3">But not as much as this one.</p>
|
1095
|
+
|
1096
|
+
<h2 class="chapter2">Chapter 2</h2>
|
1097
|
+
<p>Let's try if we can select this</p>
|
1098
|
+
</body>
|
1099
|
+
</html>
|
1100
|
+
HTML
|
1101
|
+
[200,
|
1102
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
1103
|
+
[body]]
|
1104
|
+
end
|
1105
|
+
end
|
1106
|
+
|
1107
|
+
it "builds up node paths correctly" do
|
1108
|
+
cases = {
|
1109
|
+
"//*[contains(@class, 'author')]" => "/html/head/meta[2]",
|
1110
|
+
"//*[contains(@class, 'td1')]" => "/html/body/div[@id='toc']/table/thead[@id='head']/tr/td[1]",
|
1111
|
+
"//*[contains(@class, 'td2')]" => "/html/body/div[@id='toc']/table/tbody/tr[2]/td[2]",
|
1112
|
+
"//h1" => "/html/body/h1",
|
1113
|
+
"//*[contains(@class, 'chapter2')]" => "/html/body/h2[2]",
|
1114
|
+
"//*[contains(@class, 'p1')]" => "/html/body/p[1]",
|
1115
|
+
"//*[contains(@class, 'p2')]" => "/html/body/div[@id='intro']/p[2]",
|
1116
|
+
"//*[contains(@class, 'p3')]" => "/html/body/p[3]",
|
1117
|
+
}
|
1118
|
+
|
1119
|
+
cases.each do |xpath, path|
|
1120
|
+
nodes = subject.find(xpath)
|
1121
|
+
nodes.size.should == 1
|
1122
|
+
nodes[0].path.should == path
|
1123
|
+
end
|
1124
|
+
end
|
1125
|
+
end
|
1126
|
+
|
1127
|
+
context "css overflow app" do
|
1128
|
+
before(:all) do
|
1129
|
+
@app = lambda do |env|
|
1130
|
+
body = <<-HTML
|
1131
|
+
<html>
|
1132
|
+
<head>
|
1133
|
+
<style type="text/css">
|
1134
|
+
#overflow { overflow: hidden }
|
1135
|
+
</style>
|
1136
|
+
</head>
|
1137
|
+
<body>
|
1138
|
+
<div id="overflow">Overflow</div>
|
1139
|
+
</body>
|
1140
|
+
</html>
|
1141
|
+
HTML
|
1142
|
+
[200,
|
1143
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
1144
|
+
[body]]
|
1145
|
+
end
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
it "handles overflow hidden" do
|
1149
|
+
subject.find("//div[@id='overflow']").first.text.should == "Overflow"
|
1150
|
+
end
|
1151
|
+
end
|
1152
|
+
|
1017
1153
|
context "javascript redirect app" do
|
1018
1154
|
before(:all) do
|
1019
1155
|
@app = lambda do |env|
|
@@ -1041,4 +1177,100 @@ describe Capybara::Driver::Webkit do
|
|
1041
1177
|
end
|
1042
1178
|
end
|
1043
1179
|
end
|
1180
|
+
|
1181
|
+
context "app with a lot of HTML tags" do
|
1182
|
+
before(:all) do
|
1183
|
+
@app = lambda do |env|
|
1184
|
+
body = <<-HTML
|
1185
|
+
<html>
|
1186
|
+
<head>
|
1187
|
+
<title>My eBook</title>
|
1188
|
+
<meta class="charset" name="charset" value="utf-8" />
|
1189
|
+
<meta class="author" name="author" value="Firstname Lastname" />
|
1190
|
+
</head>
|
1191
|
+
<body>
|
1192
|
+
<div id="toc">
|
1193
|
+
<table>
|
1194
|
+
<thead id="head">
|
1195
|
+
<tr><td class="td1">Chapter</td><td>Page</td></tr>
|
1196
|
+
</thead>
|
1197
|
+
<tbody>
|
1198
|
+
<tr><td>Intro</td><td>1</td></tr>
|
1199
|
+
<tr><td>Chapter 1</td><td class="td2">1</td></tr>
|
1200
|
+
<tr><td>Chapter 2</td><td>1</td></tr>
|
1201
|
+
</tbody>
|
1202
|
+
</table>
|
1203
|
+
</div>
|
1204
|
+
|
1205
|
+
<h1 class="h1">My first book</h1>
|
1206
|
+
<p class="p1">Written by me</p>
|
1207
|
+
<div id="intro" class="intro">
|
1208
|
+
<p>Let's try out XPath</p>
|
1209
|
+
<p class="p2">in capybara-webkit</p>
|
1210
|
+
</div>
|
1211
|
+
|
1212
|
+
<h2 class="chapter1">Chapter 1</h2>
|
1213
|
+
<p>This paragraph is fascinating.</p>
|
1214
|
+
<p class="p3">But not as much as this one.</p>
|
1215
|
+
|
1216
|
+
<h2 class="chapter2">Chapter 2</h2>
|
1217
|
+
<p>Let's try if we can select this</p>
|
1218
|
+
</body>
|
1219
|
+
</html>
|
1220
|
+
HTML
|
1221
|
+
[200,
|
1222
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
1223
|
+
[body]]
|
1224
|
+
end
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
it "builds up node paths correctly" do
|
1228
|
+
cases = {
|
1229
|
+
"//*[contains(@class, 'author')]" => "/html/head/meta[2]",
|
1230
|
+
"//*[contains(@class, 'td1')]" => "/html/body/div[@id='toc']/table/thead[@id='head']/tr/td[1]",
|
1231
|
+
"//*[contains(@class, 'td2')]" => "/html/body/div[@id='toc']/table/tbody/tr[2]/td[2]",
|
1232
|
+
"//h1" => "/html/body/h1",
|
1233
|
+
"//*[contains(@class, 'chapter2')]" => "/html/body/h2[2]",
|
1234
|
+
"//*[contains(@class, 'p1')]" => "/html/body/p[1]",
|
1235
|
+
"//*[contains(@class, 'p2')]" => "/html/body/div[@id='intro']/p[2]",
|
1236
|
+
"//*[contains(@class, 'p3')]" => "/html/body/p[3]",
|
1237
|
+
}
|
1238
|
+
|
1239
|
+
cases.each do |xpath, path|
|
1240
|
+
nodes = subject.find(xpath)
|
1241
|
+
nodes.size.should == 1
|
1242
|
+
nodes[0].path.should == path
|
1243
|
+
end
|
1244
|
+
end
|
1245
|
+
end
|
1246
|
+
|
1247
|
+
context "form app with server-side handler" do
|
1248
|
+
before(:all) do
|
1249
|
+
@app = lambda do |env|
|
1250
|
+
if env["REQUEST_METHOD"] == "POST"
|
1251
|
+
body = "<html><body><p>Congrats!</p></body></html>"
|
1252
|
+
else
|
1253
|
+
body = <<-HTML
|
1254
|
+
<html>
|
1255
|
+
<head><title>Form</title>
|
1256
|
+
<body>
|
1257
|
+
<form action="/" method="POST">
|
1258
|
+
<input type="hidden" name="abc" value="123" />
|
1259
|
+
<input type="submit" value="Submit" />
|
1260
|
+
</form>
|
1261
|
+
</body>
|
1262
|
+
</html>
|
1263
|
+
HTML
|
1264
|
+
end
|
1265
|
+
[200,
|
1266
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
1267
|
+
[body]]
|
1268
|
+
end
|
1269
|
+
end
|
1270
|
+
|
1271
|
+
it "submits a form without clicking" do
|
1272
|
+
subject.find("//form")[0].submit
|
1273
|
+
subject.body.should include "Congrats"
|
1274
|
+
end
|
1275
|
+
end
|
1044
1276
|
end
|