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.
@@ -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