capybara-webkit 0.7.2 → 0.8.0

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.
@@ -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
@@ -74,7 +74,11 @@ class Capybara::Driver::Webkit
74
74
  end
75
75
 
76
76
  def path
77
- raise Capybara::NotSupportedByDriverError
77
+ invoke "path"
78
+ end
79
+
80
+ def submit(opts = {})
81
+ invoke "submit"
78
82
  end
79
83
 
80
84
  def trigger(event)
@@ -0,0 +1,7 @@
1
+ module Capybara
2
+ module Driver
3
+ class Webkit
4
+ VERSION = '0.8.0'.freeze
5
+ end
6
+ end
7
+ 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 RUBY_PLATFORM
15
+ case RbConfig::CONFIG['host_os']
15
16
  when /linux/
16
17
  system("#{qmake} -spec linux-g++")
17
18
  when /freebsd/
@@ -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 == "hello world\n"
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
@@ -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 "escapes URLs" do
223
- subject.visit("/hello there")
224
- subject.current_url.should =~ /hello%20there/
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="text"/>
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
- it "triggers text input events" do
544
- subject.find("//input[@type='text']").first.set(newtext)
545
- subject.find("//li").map(&:text).should == keyevents
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