intentmedia-capybara-webkit 0.7.2.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.
Files changed (97) hide show
  1. data/.gitignore +17 -0
  2. data/.rspec +2 -0
  3. data/Appraisals +7 -0
  4. data/CONTRIBUTING.md +38 -0
  5. data/Gemfile +8 -0
  6. data/Gemfile.lock +65 -0
  7. data/LICENSE +19 -0
  8. data/README.md +67 -0
  9. data/Rakefile +78 -0
  10. data/bin/Info.plist +22 -0
  11. data/capybara-webkit.gemspec +24 -0
  12. data/extconf.rb +2 -0
  13. data/gemfiles/1.0.gemfile +7 -0
  14. data/gemfiles/1.0.gemfile.lock +65 -0
  15. data/gemfiles/1.1.gemfile +7 -0
  16. data/gemfiles/1.1.gemfile.lock +65 -0
  17. data/lib/capybara-webkit.rb +1 -0
  18. data/lib/capybara/driver/webkit.rb +113 -0
  19. data/lib/capybara/driver/webkit/browser.rb +216 -0
  20. data/lib/capybara/driver/webkit/node.rb +118 -0
  21. data/lib/capybara/driver/webkit/socket_debugger.rb +43 -0
  22. data/lib/capybara/webkit.rb +11 -0
  23. data/lib/capybara_webkit_builder.rb +40 -0
  24. data/spec/browser_spec.rb +178 -0
  25. data/spec/driver_rendering_spec.rb +80 -0
  26. data/spec/driver_spec.rb +1048 -0
  27. data/spec/integration/driver_spec.rb +20 -0
  28. data/spec/integration/session_spec.rb +137 -0
  29. data/spec/self_signed_ssl_cert.rb +42 -0
  30. data/spec/spec_helper.rb +25 -0
  31. data/src/Body.h +12 -0
  32. data/src/ClearCookies.cpp +18 -0
  33. data/src/ClearCookies.h +11 -0
  34. data/src/Command.cpp +15 -0
  35. data/src/Command.h +29 -0
  36. data/src/CommandFactory.cpp +29 -0
  37. data/src/CommandFactory.h +16 -0
  38. data/src/CommandParser.cpp +68 -0
  39. data/src/CommandParser.h +29 -0
  40. data/src/Connection.cpp +82 -0
  41. data/src/Connection.h +36 -0
  42. data/src/Evaluate.cpp +84 -0
  43. data/src/Evaluate.h +22 -0
  44. data/src/Execute.cpp +16 -0
  45. data/src/Execute.h +12 -0
  46. data/src/Find.cpp +19 -0
  47. data/src/Find.h +13 -0
  48. data/src/FrameFocus.cpp +66 -0
  49. data/src/FrameFocus.h +28 -0
  50. data/src/GetCookies.cpp +22 -0
  51. data/src/GetCookies.h +14 -0
  52. data/src/Header.cpp +18 -0
  53. data/src/Header.h +11 -0
  54. data/src/Headers.cpp +11 -0
  55. data/src/Headers.h +12 -0
  56. data/src/JavascriptInvocation.cpp +14 -0
  57. data/src/JavascriptInvocation.h +19 -0
  58. data/src/NetworkAccessManager.cpp +25 -0
  59. data/src/NetworkAccessManager.h +18 -0
  60. data/src/NetworkCookieJar.cpp +101 -0
  61. data/src/NetworkCookieJar.h +15 -0
  62. data/src/Node.cpp +14 -0
  63. data/src/Node.h +13 -0
  64. data/src/Render.cpp +19 -0
  65. data/src/Render.h +12 -0
  66. data/src/Reset.cpp +20 -0
  67. data/src/Reset.h +12 -0
  68. data/src/Response.cpp +19 -0
  69. data/src/Response.h +13 -0
  70. data/src/Server.cpp +25 -0
  71. data/src/Server.h +21 -0
  72. data/src/SetCookie.cpp +18 -0
  73. data/src/SetCookie.h +11 -0
  74. data/src/SetProxy.cpp +24 -0
  75. data/src/SetProxy.h +11 -0
  76. data/src/Source.cpp +20 -0
  77. data/src/Source.h +19 -0
  78. data/src/Status.cpp +13 -0
  79. data/src/Status.h +12 -0
  80. data/src/UnsupportedContentHandler.cpp +32 -0
  81. data/src/UnsupportedContentHandler.h +18 -0
  82. data/src/Url.cpp +15 -0
  83. data/src/Url.h +12 -0
  84. data/src/Visit.cpp +21 -0
  85. data/src/Visit.h +15 -0
  86. data/src/WebPage.cpp +226 -0
  87. data/src/WebPage.h +54 -0
  88. data/src/body.cpp +11 -0
  89. data/src/capybara.js +205 -0
  90. data/src/find_command.h +24 -0
  91. data/src/main.cpp +34 -0
  92. data/src/webkit_server.pro +71 -0
  93. data/src/webkit_server.qrc +5 -0
  94. data/templates/Command.cpp +10 -0
  95. data/templates/Command.h +12 -0
  96. data/webkit_server.pro +4 -0
  97. metadata +246 -0
@@ -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,11 @@
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
+ browser = Capybara::Driver::Webkit::Browser.new(:socket_class => Capybara::Driver::Webkit::SocketDebugger)
10
+ Capybara::Driver::Webkit.new(app, :browser => browser)
11
+ end
@@ -0,0 +1,40 @@
1
+ require "fileutils"
2
+
3
+ module CapybaraWebkitBuilder
4
+ extend self
5
+
6
+ def make_bin
7
+ make_binaries = ['gmake', 'make']
8
+ make_binaries.detect { |make| system("which #{make}") }
9
+ end
10
+
11
+ def makefile
12
+ qmake_binaries = ['qmake', 'qmake-qt4']
13
+ qmake = qmake_binaries.detect { |qmake| system("which #{qmake}") }
14
+ case RUBY_PLATFORM
15
+ when /linux/
16
+ system("#{qmake} -spec linux-g++")
17
+ when /freebsd/
18
+ system("#{qmake} -spec freebsd-g++")
19
+ else
20
+ system("#{qmake} -spec macx-g++")
21
+ end
22
+ end
23
+
24
+ def qmake
25
+ system("#{make_bin} qmake")
26
+ end
27
+
28
+ def build
29
+ system(make_bin) or return false
30
+
31
+ FileUtils.mkdir("bin") unless File.directory?("bin")
32
+ FileUtils.cp("src/webkit_server", "bin", :preserve => true)
33
+ end
34
+
35
+ def build_all
36
+ makefile &&
37
+ qmake &&
38
+ build
39
+ end
40
+ end
@@ -0,0 +1,178 @@
1
+ require 'spec_helper'
2
+ require 'self_signed_ssl_cert'
3
+ require 'stringio'
4
+ require 'capybara/driver/webkit/browser'
5
+ require 'socket'
6
+ require 'base64'
7
+
8
+ describe Capybara::Driver::Webkit::Browser do
9
+
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
+ }
14
+
15
+ describe '#server_port' do
16
+ subject { browser.server_port }
17
+ it 'returns a valid port number' do
18
+ should be_a(Integer)
19
+ end
20
+
21
+ it 'returns a port in the allowed range' do
22
+ should be_between 0x400, 0xffff
23
+ end
24
+ end
25
+
26
+ context 'random port' do
27
+ it 'chooses a new port number for a new browser instance' do
28
+ new_browser = Capybara::Driver::Webkit::Browser.new
29
+ new_browser.server_port.should_not == browser.server_port
30
+ end
31
+ end
32
+
33
+ it 'forwards stdout to the given IO object' do
34
+ io = StringIO.new
35
+ new_browser = Capybara::Driver::Webkit::Browser.new(:stdout => io)
36
+ new_browser.execute_script('console.log("hello world")')
37
+ sleep(0.5)
38
+ io.string.should == "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
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
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 'is possible to disable proxy again' do
172
+ @proxy_requests.clear
173
+ browser.clear_proxy
174
+ browser.visit "http://#{@host}:#{@port}/"
175
+ @proxy_requests.size.should == 0
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+ require 'capybara/driver/webkit'
3
+ require 'mini_magick'
4
+
5
+ describe Capybara::Driver::Webkit, "rendering an image" do
6
+
7
+ before(:all) do
8
+ # Set up the tmp directory and file name
9
+ tmp_dir = File.join(PROJECT_ROOT, 'tmp')
10
+ FileUtils.mkdir_p tmp_dir
11
+ @file_name = File.join(tmp_dir, 'render-test.png')
12
+
13
+ app = lambda do |env|
14
+ body = <<-HTML
15
+ <html>
16
+ <body>
17
+ <h1>Hello World</h1>
18
+ </body>
19
+ </html>
20
+ HTML
21
+ [200,
22
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
23
+ [body]]
24
+ end
25
+
26
+ @driver = Capybara::Driver::Webkit.new(app, :browser => $webkit_browser)
27
+ @driver.visit("/hello/world?success=true")
28
+ end
29
+
30
+ after(:all) { @driver.reset! }
31
+
32
+ def render(options)
33
+ FileUtils.rm_f @file_name
34
+ @driver.render @file_name, options
35
+
36
+ @image = MiniMagick::Image.open @file_name
37
+ end
38
+
39
+ context "with default options" do
40
+ before(:all){ render({}) }
41
+
42
+ it "should be a PNG" do
43
+ @image[:format].should == "PNG"
44
+ end
45
+
46
+ it "width default to 1000px (with 15px less for the scrollbar)" do
47
+ @image[:width].should be < 1001
48
+ @image[:width].should be > 1000-17
49
+ end
50
+
51
+ it "height should be at least 10px" do
52
+ @image[:height].should >= 10
53
+ end
54
+ end
55
+
56
+ context "with dimensions set larger than necessary" do
57
+ before(:all){ render(:width => 500, :height => 400) }
58
+
59
+ it "width should match the width given" do
60
+ @image[:width].should == 500
61
+ end
62
+
63
+ it "height should match the height given" do
64
+ @image[:height].should > 10
65
+ end
66
+ end
67
+
68
+ context "with dimensions set smaller than the document's default" do
69
+ before(:all){ render(:width => 50, :height => 10) }
70
+
71
+ it "width should be greater than the width given" do
72
+ @image[:width].should > 50
73
+ end
74
+
75
+ it "height should be greater than the height given" do
76
+ @image[:height].should > 10
77
+ end
78
+ end
79
+
80
+ end
@@ -0,0 +1,1048 @@
1
+ require 'spec_helper'
2
+ require 'capybara/driver/webkit'
3
+
4
+ describe Capybara::Driver::Webkit do
5
+ subject { Capybara::Driver::Webkit.new(@app, :browser => $webkit_browser) }
6
+ before { subject.visit("/hello/world?success=true") }
7
+ after { subject.reset! }
8
+
9
+ context "iframe app" do
10
+ before(:all) do
11
+ @app = lambda do |env|
12
+ params = ::Rack::Utils.parse_query(env['QUERY_STRING'])
13
+ if params["iframe"] == "true"
14
+ # We are in an iframe request.
15
+ p_id = "farewell"
16
+ msg = "goodbye"
17
+ iframe = nil
18
+ else
19
+ # We are not in an iframe request and need to make an iframe!
20
+ p_id = "greeting"
21
+ msg = "hello"
22
+ iframe = "<iframe id=\"f\" src=\"/?iframe=true\"></iframe>"
23
+ end
24
+ body = <<-HTML
25
+ <html>
26
+ <head>
27
+ <style type="text/css">
28
+ #display_none { display: none }
29
+ </style>
30
+ </head>
31
+ <body>
32
+ #{iframe}
33
+ <script type="text/javascript">
34
+ document.write("<p id='#{p_id}'>#{msg}</p>");
35
+ </script>
36
+ </body>
37
+ </html>
38
+ HTML
39
+ [200,
40
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
41
+ [body]]
42
+ end
43
+ end
44
+
45
+ it "finds frames by index" do
46
+ subject.within_frame(0) do
47
+ subject.find("//*[contains(., 'goodbye')]").should_not be_empty
48
+ end
49
+ end
50
+
51
+ it "finds frames by id" do
52
+ subject.within_frame("f") do
53
+ subject.find("//*[contains(., 'goodbye')]").should_not be_empty
54
+ end
55
+ end
56
+
57
+ it "raises error for missing frame by index" do
58
+ expect { subject.within_frame(1) { } }.
59
+ to raise_error(Capybara::Driver::Webkit::WebkitInvalidResponseError)
60
+ end
61
+
62
+ it "raise_error for missing frame by id" do
63
+ expect { subject.within_frame("foo") { } }.
64
+ to raise_error(Capybara::Driver::Webkit::WebkitInvalidResponseError)
65
+ end
66
+
67
+ it "returns an attribute's value" do
68
+ subject.within_frame("f") do
69
+ subject.find("//p").first["id"].should == "farewell"
70
+ end
71
+ end
72
+
73
+ it "returns a node's text" do
74
+ subject.within_frame("f") do
75
+ subject.find("//p").first.text.should == "goodbye"
76
+ end
77
+ end
78
+
79
+ it "returns the current URL" do
80
+ subject.within_frame("f") do
81
+ port = subject.instance_variable_get("@rack_server").port
82
+ subject.current_url.should == "http://127.0.0.1:#{port}/?iframe=true"
83
+ end
84
+ end
85
+
86
+ it "returns the source code for the page" do
87
+ subject.within_frame("f") do
88
+ subject.source.should =~ %r{<html>.*farewell.*}m
89
+ end
90
+ end
91
+
92
+ it "evaluates Javascript" do
93
+ subject.within_frame("f") do
94
+ result = subject.evaluate_script(%<document.getElementById('farewell').innerText>)
95
+ result.should == "goodbye"
96
+ end
97
+ end
98
+
99
+ it "executes Javascript" do
100
+ subject.within_frame("f") do
101
+ subject.execute_script(%<document.getElementById('farewell').innerHTML = 'yo'>)
102
+ subject.find("//p[contains(., 'yo')]").should_not be_empty
103
+ end
104
+ end
105
+ end
106
+
107
+ context "redirect app" do
108
+ before(:all) do
109
+ @app = lambda do |env|
110
+ if env['PATH_INFO'] == '/target'
111
+ content_type = "<p>#{env['CONTENT_TYPE']}</p>"
112
+ [200, {"Content-Type" => "text/html", "Content-Length" => content_type.length.to_s}, [content_type]]
113
+ elsif env['PATH_INFO'] == '/form'
114
+ body = <<-HTML
115
+ <html>
116
+ <body>
117
+ <form action="/redirect" method="POST" enctype="multipart/form-data">
118
+ <input name="submit" type="submit" />
119
+ </form>
120
+ </body>
121
+ </html>
122
+ HTML
123
+ [200, {"Content-Type" => "text/html", "Content-Length" => body.length.to_s}, [body]]
124
+ else
125
+ [301, {"Location" => "/target"}, [""]]
126
+ end
127
+ end
128
+ end
129
+
130
+ it "should redirect without content type" do
131
+ subject.visit("/form")
132
+ subject.find("//input").first.click
133
+ subject.find("//p").first.text.should == ""
134
+ end
135
+ end
136
+
137
+ context "css app" do
138
+ before(:all) do
139
+ body = "css"
140
+ @app = lambda do |env|
141
+ [200, {"Content-Type" => "text/css", "Content-Length" => body.length.to_s}, [body]]
142
+ end
143
+ subject.visit("/")
144
+ end
145
+
146
+ it "renders unsupported content types gracefully" do
147
+ subject.body.should =~ /css/
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
153
+ end
154
+
155
+ context "hello app" do
156
+ before(:all) do
157
+ @app = lambda do |env|
158
+ body = <<-HTML
159
+ <html>
160
+ <head>
161
+ <style type="text/css">
162
+ #display_none { display: none }
163
+ </style>
164
+ </head>
165
+ <body>
166
+ <div class='normalize'>Spaces&nbsp;not&nbsp;normalized&nbsp;</div>
167
+ <div id="display_none">
168
+ <div id="invisible">Can't see me</div>
169
+ </div>
170
+ <input type="text" disabled="disabled"/>
171
+ <input id="checktest" type="checkbox" checked="checked"/>
172
+ <script type="text/javascript">
173
+ document.write("<p id='greeting'>he" + "llo</p>");
174
+ </script>
175
+ </body>
176
+ </html>
177
+ HTML
178
+ [200,
179
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
180
+ [body]]
181
+ end
182
+ end
183
+
184
+ it "handles anchor tags" do
185
+ subject.visit("#test")
186
+ subject.find("//*[contains(., 'hello')]").should_not be_empty
187
+ subject.visit("#test")
188
+ subject.find("//*[contains(., 'hello')]").should_not be_empty
189
+ end
190
+
191
+ it "finds content after loading a URL" do
192
+ subject.find("//*[contains(., 'hello')]").should_not be_empty
193
+ end
194
+
195
+ it "has an empty page after reseting" do
196
+ subject.reset!
197
+ subject.find("//*[contains(., 'hello')]").should be_empty
198
+ end
199
+
200
+ it "raises an error for an invalid xpath query" do
201
+ expect { subject.find("totally invalid salad") }.
202
+ to raise_error(Capybara::Driver::Webkit::WebkitInvalidResponseError, /xpath/i)
203
+ end
204
+
205
+ it "returns an attribute's value" do
206
+ subject.find("//p").first["id"].should == "greeting"
207
+ end
208
+
209
+ it "parses xpath with quotes" do
210
+ subject.find('//*[contains(., "hello")]').should_not be_empty
211
+ end
212
+
213
+ it "returns a node's text" do
214
+ subject.find("//p").first.text.should == "hello"
215
+ end
216
+
217
+ it "normalizes a node's text" do
218
+ subject.find("//div[contains(@class, 'normalize')]").first.text.should == "Spaces not normalized"
219
+ end
220
+
221
+ it "returns the current URL" do
222
+ port = subject.instance_variable_get("@rack_server").port
223
+ subject.current_url.should == "http://127.0.0.1:#{port}/hello/world?success=true"
224
+ end
225
+
226
+ it "escapes URLs" do
227
+ subject.visit("/hello there")
228
+ subject.current_url.should =~ /hello%20there/
229
+ end
230
+
231
+ it "visits a page with an anchor" do
232
+ subject.visit("/hello#display_none")
233
+ subject.current_url.should =~ /hello#display_none/
234
+ end
235
+
236
+ it "returns the source code for the page" do
237
+ subject.source.should =~ %r{<html>.*greeting.*}m
238
+ end
239
+
240
+ it "evaluates Javascript and returns a string" do
241
+ result = subject.evaluate_script(%<document.getElementById('greeting').innerText>)
242
+ result.should == "hello"
243
+ end
244
+
245
+ it "evaluates Javascript and returns an array" do
246
+ result = subject.evaluate_script(%<["hello", "world"]>)
247
+ result.should == %w(hello world)
248
+ end
249
+
250
+ it "evaluates Javascript and returns an int" do
251
+ result = subject.evaluate_script(%<123>)
252
+ result.should == 123
253
+ end
254
+
255
+ it "evaluates Javascript and returns a float" do
256
+ result = subject.evaluate_script(%<1.5>)
257
+ result.should == 1.5
258
+ end
259
+
260
+ it "evaluates Javascript and returns null" do
261
+ result = subject.evaluate_script(%<(function () {})()>)
262
+ result.should == nil
263
+ end
264
+
265
+ it "evaluates Javascript and returns an object" do
266
+ result = subject.evaluate_script(%<({ 'one' : 1 })>)
267
+ result.should == { 'one' => 1 }
268
+ end
269
+
270
+ it "evaluates Javascript and returns true" do
271
+ result = subject.evaluate_script(%<true>)
272
+ result.should === true
273
+ end
274
+
275
+ it "evaluates Javascript and returns false" do
276
+ result = subject.evaluate_script(%<false>)
277
+ result.should === false
278
+ end
279
+
280
+ it "evaluates Javascript and returns an escaped string" do
281
+ result = subject.evaluate_script(%<'"'>)
282
+ result.should === "\""
283
+ end
284
+
285
+ it "evaluates Javascript with multiple lines" do
286
+ result = subject.evaluate_script("[1,\n2]")
287
+ result.should == [1, 2]
288
+ end
289
+
290
+ it "executes Javascript" do
291
+ subject.execute_script(%<document.getElementById('greeting').innerHTML = 'yo'>)
292
+ subject.find("//p[contains(., 'yo')]").should_not be_empty
293
+ end
294
+
295
+ it "raises an error for failing Javascript" do
296
+ expect { subject.execute_script(%<invalid salad>) }.
297
+ to raise_error(Capybara::Driver::Webkit::WebkitInvalidResponseError)
298
+ end
299
+
300
+ it "doesn't raise an error for Javascript that doesn't return anything" do
301
+ lambda { subject.execute_script(%<(function () { "returns nothing" })()>) }.
302
+ should_not raise_error
303
+ end
304
+
305
+ it "returns a node's tag name" do
306
+ subject.find("//p").first.tag_name.should == "p"
307
+ end
308
+
309
+ it "reads disabled property" do
310
+ subject.find("//input").first.should be_disabled
311
+ end
312
+
313
+ it "reads checked property" do
314
+ subject.find("//input[@id='checktest']").first.should be_checked
315
+ end
316
+
317
+ it "finds visible elements" do
318
+ subject.find("//p").first.should be_visible
319
+ subject.find("//*[@id='invisible']").first.should_not be_visible
320
+ end
321
+ end
322
+
323
+ context "form app" do
324
+ before(:all) do
325
+ @app = lambda do |env|
326
+ body = <<-HTML
327
+ <html><body>
328
+ <form action="/" method="GET">
329
+ <input type="text" name="foo" value="bar"/>
330
+ <input type="text" name="maxlength_foo" value="bar" maxlength="10"/>
331
+ <input type="text" id="disabled_input" disabled="disabled"/>
332
+ <input type="checkbox" name="checkedbox" value="1" checked="checked"/>
333
+ <input type="checkbox" name="uncheckedbox" value="2"/>
334
+ <select name="animal">
335
+ <option id="select-option-monkey">Monkey</option>
336
+ <option id="select-option-capybara" selected="selected">Capybara</option>
337
+ </select>
338
+ <select name="toppings" multiple="multiple">
339
+ <optgroup label="Mediocre Toppings">
340
+ <option selected="selected" id="topping-apple">Apple</option>
341
+ <option selected="selected" id="topping-banana">Banana</option>
342
+ </optgroup>
343
+ <optgroup label="Best Toppings">
344
+ <option selected="selected" id="topping-cherry">Cherry</option>
345
+ </optgroup>
346
+ </select>
347
+ <textarea id="only-textarea">what a wonderful area for text</textarea>
348
+ <input type="radio" id="only-radio" value="1"/>
349
+ </form>
350
+ </body></html>
351
+ HTML
352
+ [200,
353
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
354
+ [body]]
355
+ end
356
+ end
357
+
358
+ it "returns a textarea's value" do
359
+ subject.find("//textarea").first.value.should == "what a wonderful area for text"
360
+ end
361
+
362
+ it "returns a text input's value" do
363
+ subject.find("//input").first.value.should == "bar"
364
+ end
365
+
366
+ it "returns a select's value" do
367
+ subject.find("//select").first.value.should == "Capybara"
368
+ end
369
+
370
+ it "sets an input's value" do
371
+ input = subject.find("//input").first
372
+ input.set("newvalue")
373
+ input.value.should == "newvalue"
374
+ end
375
+
376
+ it "sets an input's value greater than the max length" do
377
+ input = subject.find("//input[@name='maxlength_foo']").first
378
+ input.set("allegories (poems)")
379
+ input.value.should == "allegories"
380
+ end
381
+
382
+ it "sets an input's value equal to the max length" do
383
+ input = subject.find("//input[@name='maxlength_foo']").first
384
+ input.set("allegories")
385
+ input.value.should == "allegories"
386
+ end
387
+
388
+ it "sets an input's value less than the max length" do
389
+ input = subject.find("//input[@name='maxlength_foo']").first
390
+ input.set("poems")
391
+ input.value.should == "poems"
392
+ end
393
+
394
+ it "sets an input's nil value" do
395
+ input = subject.find("//input").first
396
+ input.set(nil)
397
+ input.value.should == ""
398
+ end
399
+
400
+ it "sets a select's value" do
401
+ select = subject.find("//select").first
402
+ select.set("Monkey")
403
+ select.value.should == "Monkey"
404
+ end
405
+
406
+ it "sets a textarea's value" do
407
+ textarea = subject.find("//textarea").first
408
+ textarea.set("newvalue")
409
+ textarea.value.should == "newvalue"
410
+ end
411
+
412
+ let(:monkey_option) { subject.find("//option[@id='select-option-monkey']").first }
413
+ let(:capybara_option) { subject.find("//option[@id='select-option-capybara']").first }
414
+ let(:animal_select) { subject.find("//select[@name='animal']").first }
415
+ let(:apple_option) { subject.find("//option[@id='topping-apple']").first }
416
+ let(:banana_option) { subject.find("//option[@id='topping-banana']").first }
417
+ let(:cherry_option) { subject.find("//option[@id='topping-cherry']").first }
418
+ let(:toppings_select) { subject.find("//select[@name='toppings']").first }
419
+
420
+ it "selects an option" do
421
+ animal_select.value.should == "Capybara"
422
+ monkey_option.select_option
423
+ animal_select.value.should == "Monkey"
424
+ end
425
+
426
+ it "unselects an option in a multi-select" do
427
+ toppings_select.value.should include("Apple", "Banana", "Cherry")
428
+
429
+ apple_option.unselect_option
430
+ toppings_select.value.should_not include("Apple")
431
+ end
432
+
433
+ it "reselects an option in a multi-select" do
434
+ apple_option.unselect_option
435
+ banana_option.unselect_option
436
+ cherry_option.unselect_option
437
+
438
+ toppings_select.value.should == []
439
+
440
+ apple_option.select_option
441
+ banana_option.select_option
442
+ cherry_option.select_option
443
+
444
+ toppings_select.value.should include("Apple", "Banana", "Cherry")
445
+ end
446
+
447
+ let(:checked_box) { subject.find("//input[@name='checkedbox']").first }
448
+ let(:unchecked_box) { subject.find("//input[@name='uncheckedbox']").first }
449
+
450
+ it "knows a checked box is checked" do
451
+ checked_box['checked'].should be_true
452
+ end
453
+
454
+ it "knows a checked box is checked using checked?" do
455
+ checked_box.should be_checked
456
+ end
457
+
458
+ it "knows an unchecked box is unchecked" do
459
+ unchecked_box['checked'].should_not be_true
460
+ end
461
+
462
+ it "knows an unchecked box is unchecked using checked?" do
463
+ unchecked_box.should_not be_checked
464
+ end
465
+
466
+ it "checks an unchecked box" do
467
+ unchecked_box.set(true)
468
+ unchecked_box.should be_checked
469
+ end
470
+
471
+ it "unchecks a checked box" do
472
+ checked_box.set(false)
473
+ checked_box.should_not be_checked
474
+ end
475
+
476
+ it "leaves a checked box checked" do
477
+ checked_box.set(true)
478
+ checked_box.should be_checked
479
+ end
480
+
481
+ it "leaves an unchecked box unchecked" do
482
+ unchecked_box.set(false)
483
+ unchecked_box.should_not be_checked
484
+ end
485
+
486
+ let(:enabled_input) { subject.find("//input[@name='foo']").first }
487
+ let(:disabled_input) { subject.find("//input[@id='disabled_input']").first }
488
+
489
+ it "knows a disabled input is disabled" do
490
+ disabled_input['disabled'].should be_true
491
+ end
492
+
493
+ it "knows a not disabled input is not disabled" do
494
+ enabled_input['disabled'].should_not be_true
495
+ end
496
+ end
497
+
498
+ context "form events app" do
499
+ before(:all) do
500
+ @app = lambda do |env|
501
+ body = <<-HTML
502
+ <html><body>
503
+ <form action="/" method="GET">
504
+ <input class="watch" type="text"/>
505
+ <input class="watch" type="password"/>
506
+ <textarea class="watch"></textarea>
507
+ <input class="watch" type="checkbox"/>
508
+ <input class="watch" type="radio"/>
509
+ </form>
510
+ <ul id="events"></ul>
511
+ <script type="text/javascript">
512
+ var events = document.getElementById("events");
513
+ var recordEvent = function (event) {
514
+ var element = document.createElement("li");
515
+ element.innerHTML = event.type;
516
+ events.appendChild(element);
517
+ };
518
+
519
+ var elements = document.getElementsByClassName("watch");
520
+ for (var i = 0; i < elements.length; i++) {
521
+ var element = elements[i];
522
+ element.addEventListener("focus", recordEvent);
523
+ element.addEventListener("keydown", recordEvent);
524
+ element.addEventListener("keypress", recordEvent);
525
+ element.addEventListener("keyup", recordEvent);
526
+ element.addEventListener("change", recordEvent);
527
+ element.addEventListener("blur", recordEvent);
528
+ element.addEventListener("click", recordEvent);
529
+ }
530
+ </script>
531
+ </body></html>
532
+ HTML
533
+ [200,
534
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
535
+ [body]]
536
+ end
537
+ end
538
+
539
+ let(:newtext) { 'newvalue' }
540
+
541
+ let(:keyevents) do
542
+ (%w{focus} +
543
+ newtext.length.times.collect { %w{keydown keypress keyup} } +
544
+ %w{change blur}).flatten
545
+ end
546
+
547
+ it "triggers text input events" do
548
+ subject.find("//input[@type='text']").first.set(newtext)
549
+ subject.find("//li").map(&:text).should == keyevents
550
+ end
551
+
552
+ it "triggers textarea input events" do
553
+ subject.find("//textarea").first.set(newtext)
554
+ subject.find("//li").map(&:text).should == keyevents
555
+ end
556
+
557
+ it "triggers password input events" do
558
+ subject.find("//input[@type='password']").first.set(newtext)
559
+ subject.find("//li").map(&:text).should == keyevents
560
+ end
561
+
562
+ it "triggers radio input events" do
563
+ subject.find("//input[@type='radio']").first.set(true)
564
+ subject.find("//li").map(&:text).should == %w(click change)
565
+ end
566
+
567
+ it "triggers checkbox events" do
568
+ subject.find("//input[@type='checkbox']").first.set(true)
569
+ subject.find("//li").map(&:text).should == %w(click change)
570
+ end
571
+ end
572
+
573
+ context "mouse app" do
574
+ before(:all) do
575
+ @app =lambda do |env|
576
+ body = <<-HTML
577
+ <html><body>
578
+ <div id="change">Change me</div>
579
+ <div id="mouseup">Push me</div>
580
+ <div id="mousedown">Release me</div>
581
+ <form action="/" method="GET">
582
+ <select id="change_select" name="change_select">
583
+ <option value="1" id="option-1" selected="selected">one</option>
584
+ <option value="2" id="option-2">two</option>
585
+ </select>
586
+ </form>
587
+ <script type="text/javascript">
588
+ document.getElementById("change_select").
589
+ addEventListener("change", function () {
590
+ this.className = "triggered";
591
+ });
592
+ document.getElementById("change").
593
+ addEventListener("change", function () {
594
+ this.className = "triggered";
595
+ });
596
+ document.getElementById("mouseup").
597
+ addEventListener("mouseup", function () {
598
+ this.className = "triggered";
599
+ });
600
+ document.getElementById("mousedown").
601
+ addEventListener("mousedown", function () {
602
+ this.className = "triggered";
603
+ });
604
+ </script>
605
+ <a href="/next">Next</a>
606
+ </body></html>
607
+ HTML
608
+ [200,
609
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
610
+ [body]]
611
+ end
612
+ end
613
+
614
+ it "clicks an element" do
615
+ subject.find("//a").first.click
616
+ subject.current_url =~ %r{/next$}
617
+ end
618
+
619
+ it "fires a mouse event" do
620
+ subject.find("//*[@id='mouseup']").first.trigger("mouseup")
621
+ subject.find("//*[@class='triggered']").should_not be_empty
622
+ end
623
+
624
+ it "fires a non-mouse event" do
625
+ subject.find("//*[@id='change']").first.trigger("change")
626
+ subject.find("//*[@class='triggered']").should_not be_empty
627
+ end
628
+
629
+ it "fires a change on select" do
630
+ select = subject.find("//select").first
631
+ select.value.should == "1"
632
+ option = subject.find("//option[@id='option-2']").first
633
+ option.select_option
634
+ select.value.should == "2"
635
+ subject.find("//select[@class='triggered']").should_not be_empty
636
+ end
637
+
638
+ it "fires drag events" do
639
+ draggable = subject.find("//*[@id='mousedown']").first
640
+ container = subject.find("//*[@id='mouseup']").first
641
+
642
+ draggable.drag_to(container)
643
+
644
+ subject.find("//*[@class='triggered']").size.should == 1
645
+ end
646
+ end
647
+
648
+ context "nesting app" do
649
+ before(:all) do
650
+ @app = lambda do |env|
651
+ body = <<-HTML
652
+ <html><body>
653
+ <div id="parent">
654
+ <div class="find">Expected</div>
655
+ </div>
656
+ <div class="find">Unexpected</div>
657
+ </body></html>
658
+ HTML
659
+ [200,
660
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
661
+ [body]]
662
+ end
663
+ end
664
+
665
+ it "evaluates nested xpath expressions" do
666
+ parent = subject.find("//*[@id='parent']").first
667
+ parent.find("./*[@class='find']").map(&:text).should == %w(Expected)
668
+ end
669
+ end
670
+
671
+ context "slow app" do
672
+ before(:all) do
673
+ @app = lambda do |env|
674
+ body = <<-HTML
675
+ <html><body>
676
+ <form action="/next"><input type="submit"/></form>
677
+ <p>#{env['PATH_INFO']}</p>
678
+ </body></html>
679
+ HTML
680
+ sleep(0.5)
681
+ [200,
682
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
683
+ [body]]
684
+ end
685
+ end
686
+
687
+ it "waits for a request to load" do
688
+ subject.find("//input").first.click
689
+ subject.find("//p").first.text.should == "/next"
690
+ end
691
+ end
692
+
693
+ context "error app" do
694
+ before(:all) do
695
+ @app = lambda do |env|
696
+ if env['PATH_INFO'] == "/error"
697
+ [404, {}, []]
698
+ else
699
+ body = <<-HTML
700
+ <html><body>
701
+ <form action="/error"><input type="submit"/></form>
702
+ </body></html>
703
+ HTML
704
+ [200,
705
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
706
+ [body]]
707
+ end
708
+ end
709
+ end
710
+
711
+ it "raises a webkit error for the requested url" do
712
+ expect {
713
+ subject.find("//input").first.click
714
+ wait_for_error_to_complete
715
+ subject.find("//body")
716
+ }.
717
+ to raise_error(Capybara::Driver::Webkit::WebkitInvalidResponseError, %r{/error})
718
+ end
719
+
720
+ def wait_for_error_to_complete
721
+ sleep(0.5)
722
+ end
723
+ end
724
+
725
+ context "slow error app" do
726
+ before(:all) do
727
+ @app = lambda do |env|
728
+ if env['PATH_INFO'] == "/error"
729
+ body = "error"
730
+ sleep(1)
731
+ [304, {}, []]
732
+ else
733
+ body = <<-HTML
734
+ <html><body>
735
+ <form action="/error"><input type="submit"/></form>
736
+ <p>hello</p>
737
+ </body></html>
738
+ HTML
739
+ [200,
740
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
741
+ [body]]
742
+ end
743
+ end
744
+ end
745
+
746
+ it "raises a webkit error and then continues" do
747
+ subject.find("//input").first.click
748
+ expect { subject.find("//p") }.to raise_error(Capybara::Driver::Webkit::WebkitInvalidResponseError)
749
+ subject.visit("/")
750
+ subject.find("//p").first.text.should == "hello"
751
+ end
752
+ end
753
+
754
+ context "popup app" do
755
+ before(:all) do
756
+ @app = lambda do |env|
757
+ body = <<-HTML
758
+ <html><body>
759
+ <script type="text/javascript">
760
+ alert("alert");
761
+ confirm("confirm");
762
+ prompt("prompt");
763
+ </script>
764
+ <p>success</p>
765
+ </body></html>
766
+ HTML
767
+ sleep(0.5)
768
+ [200,
769
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
770
+ [body]]
771
+ end
772
+ end
773
+
774
+ it "doesn't crash from alerts" do
775
+ subject.find("//p").first.text.should == "success"
776
+ end
777
+ end
778
+
779
+ context "custom header" do
780
+ before(:all) do
781
+ @app = lambda do |env|
782
+ body = <<-HTML
783
+ <html><body>
784
+ <p id="user-agent">#{env['HTTP_USER_AGENT']}</p>
785
+ <p id="x-capybara-webkit-header">#{env['HTTP_X_CAPYBARA_WEBKIT_HEADER']}</p>
786
+ <p id="accept">#{env['HTTP_ACCEPT']}</p>
787
+ <a href="/">/</a>
788
+ </body></html>
789
+ HTML
790
+ [200,
791
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
792
+ [body]]
793
+ end
794
+ end
795
+
796
+ before do
797
+ subject.header('user-agent', 'capybara-webkit/custom-user-agent')
798
+ subject.header('x-capybara-webkit-header', 'x-capybara-webkit-header')
799
+ subject.header('accept', 'text/html')
800
+ subject.visit('/')
801
+ end
802
+
803
+ it "can set user_agent" do
804
+ subject.find('id("user-agent")').first.text.should == 'capybara-webkit/custom-user-agent'
805
+ subject.evaluate_script('navigator.userAgent').should == 'capybara-webkit/custom-user-agent'
806
+ end
807
+
808
+ it "keep user_agent in next page" do
809
+ subject.find("//a").first.click
810
+ subject.find('id("user-agent")').first.text.should == 'capybara-webkit/custom-user-agent'
811
+ subject.evaluate_script('navigator.userAgent').should == 'capybara-webkit/custom-user-agent'
812
+ end
813
+
814
+ it "can set custom header" do
815
+ subject.find('id("x-capybara-webkit-header")').first.text.should == 'x-capybara-webkit-header'
816
+ end
817
+
818
+ it "can set Accept header" do
819
+ subject.find('id("accept")').first.text.should == 'text/html'
820
+ end
821
+
822
+ it "can reset all custom header" do
823
+ subject.reset!
824
+ subject.visit('/')
825
+ subject.find('id("user-agent")').first.text.should_not == 'capybara-webkit/custom-user-agent'
826
+ subject.evaluate_script('navigator.userAgent').should_not == 'capybara-webkit/custom-user-agent'
827
+ subject.find('id("x-capybara-webkit-header")').first.text.should be_empty
828
+ subject.find('id("accept")').first.text.should_not == 'text/html'
829
+ end
830
+ end
831
+
832
+ context "no response app" do
833
+ before(:all) do
834
+ @app = lambda do |env|
835
+ body = <<-HTML
836
+ <html><body>
837
+ <form action="/error"><input type="submit"/></form>
838
+ </body></html>
839
+ HTML
840
+ [200,
841
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
842
+ [body]]
843
+ end
844
+ end
845
+
846
+ it "raises a webkit error for the requested url" do
847
+ make_the_server_go_away
848
+ expect {
849
+ subject.find("//body")
850
+ }.
851
+ to raise_error(Capybara::Driver::Webkit::WebkitNoResponseError, %r{response})
852
+ make_the_server_come_back
853
+ end
854
+
855
+ def make_the_server_come_back
856
+ subject.browser.instance_variable_get(:@socket).unstub!(:gets)
857
+ subject.browser.instance_variable_get(:@socket).unstub!(:puts)
858
+ subject.browser.instance_variable_get(:@socket).unstub!(:print)
859
+ end
860
+
861
+ def make_the_server_go_away
862
+ subject.browser.instance_variable_get(:@socket).stub!(:gets).and_return(nil)
863
+ subject.browser.instance_variable_get(:@socket).stub!(:puts)
864
+ subject.browser.instance_variable_get(:@socket).stub!(:print)
865
+ end
866
+ end
867
+
868
+ context "custom font app" do
869
+ before(:all) do
870
+ @app = lambda do |env|
871
+ body = <<-HTML
872
+ <html>
873
+ <head>
874
+ <style type="text/css">
875
+ p { font-family: "Verdana"; }
876
+ </style>
877
+ </head>
878
+ <body>
879
+ <p id="text">Hello</p>
880
+ </body>
881
+ </html>
882
+ HTML
883
+ [200,
884
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
885
+ [body]]
886
+ end
887
+ end
888
+
889
+ it "ignores custom fonts" do
890
+ font_family = subject.evaluate_script(<<-SCRIPT)
891
+ var element = document.getElementById("text");
892
+ element.ownerDocument.defaultView.getComputedStyle(element, null).getPropertyValue("font-family");
893
+ SCRIPT
894
+ font_family.should == "Arial"
895
+ end
896
+ end
897
+
898
+ context "cookie-based app" do
899
+ before(:all) do
900
+ @cookie = 'cookie=abc; domain=127.0.0.1; path=/'
901
+ @app = lambda do |env|
902
+ request = ::Rack::Request.new(env)
903
+
904
+ body = <<-HTML
905
+ <html><body>
906
+ <p id="cookie">#{request.cookies["cookie"] || ""}</p>
907
+ </body></html>
908
+ HTML
909
+ [200,
910
+ { 'Content-Type' => 'text/html; charset=UTF-8',
911
+ 'Content-Length' => body.length.to_s,
912
+ 'Set-Cookie' => @cookie,
913
+ },
914
+ [body]]
915
+ end
916
+ end
917
+
918
+ def echoed_cookie
919
+ subject.find('id("cookie")').first.text
920
+ end
921
+
922
+ it "remembers the cookie on second visit" do
923
+ echoed_cookie.should == ""
924
+ subject.visit "/"
925
+ echoed_cookie.should == "abc"
926
+ end
927
+
928
+ it "uses a custom cookie" do
929
+ subject.browser.set_cookie @cookie
930
+ subject.visit "/"
931
+ echoed_cookie.should == "abc"
932
+ end
933
+
934
+ it "clears cookies" do
935
+ subject.browser.clear_cookies
936
+ subject.visit "/"
937
+ echoed_cookie.should == ""
938
+ end
939
+
940
+ it "allows enumeration of cookies" do
941
+ cookies = subject.browser.get_cookies
942
+
943
+ cookies.size.should == 1
944
+
945
+ cookie = Hash[cookies[0].split(/\s*;\s*/).map { |x| x.split("=", 2) }]
946
+ cookie["cookie"].should == "abc"
947
+ cookie["domain"].should include "127.0.0.1"
948
+ cookie["path"].should == "/"
949
+ end
950
+ end
951
+
952
+ context "with socket debugger" do
953
+ let(:socket_debugger_class){ Capybara::Driver::Webkit::SocketDebugger }
954
+ let(:browser_with_debugger){
955
+ Capybara::Driver::Webkit::Browser.new(:socket_class => socket_debugger_class)
956
+ }
957
+ let(:driver_with_debugger){ Capybara::Driver::Webkit.new(@app, :browser => browser_with_debugger) }
958
+
959
+ before(:all) do
960
+ @app = lambda do |env|
961
+ body = <<-HTML
962
+ <html><body>
963
+ <div id="parent">
964
+ <div class="find">Expected</div>
965
+ </div>
966
+ <div class="find">Unexpected</div>
967
+ </body></html>
968
+ HTML
969
+ [200,
970
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
971
+ [body]]
972
+ end
973
+ end
974
+
975
+ it "prints out sent content" do
976
+ socket_debugger_class.any_instance.stub(:received){|content| content }
977
+ sent_content = ['Find', 1, 17, "//*[@id='parent']"]
978
+ socket_debugger_class.any_instance.should_receive(:sent).exactly(sent_content.size).times
979
+ driver_with_debugger.find("//*[@id='parent']")
980
+ end
981
+
982
+ it "prints out received content" do
983
+ socket_debugger_class.any_instance.stub(:sent)
984
+ socket_debugger_class.any_instance.should_receive(:received).at_least(:once).and_return("ok")
985
+ driver_with_debugger.find("//*[@id='parent']")
986
+ end
987
+ end
988
+
989
+ context "remove node app" do
990
+ before(:all) do
991
+ @app = lambda do |env|
992
+ body = <<-HTML
993
+ <html>
994
+ <div id="parent">
995
+ <p id="removeMe">Hello</p>
996
+ </div>
997
+ </html>
998
+ HTML
999
+ [200,
1000
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
1001
+ [body]]
1002
+ end
1003
+ end
1004
+
1005
+ before { set_automatic_reload false }
1006
+ after { set_automatic_reload true }
1007
+
1008
+ def set_automatic_reload(value)
1009
+ if Capybara.respond_to?(:automatic_reload)
1010
+ Capybara.automatic_reload = value
1011
+ end
1012
+ end
1013
+
1014
+ it "allows removed nodes when reloading is disabled" do
1015
+ node = subject.find("//p[@id='removeMe']").first
1016
+ subject.evaluate_script("document.getElementById('parent').innerHTML = 'Magic'")
1017
+ node.text.should == 'Hello'
1018
+ end
1019
+ end
1020
+
1021
+ context "javascript redirect app" do
1022
+ before(:all) do
1023
+ @app = lambda do |env|
1024
+ if env['PATH_INFO'] == '/redirect'
1025
+ body = <<-HTML
1026
+ <html>
1027
+ <script type="text/javascript">
1028
+ window.location = "/next";
1029
+ </script>
1030
+ </html>
1031
+ HTML
1032
+ else
1033
+ body = "<html><p>finished</p></html>"
1034
+ end
1035
+ [200,
1036
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
1037
+ [body]]
1038
+ end
1039
+ end
1040
+
1041
+ it "loads a page without error" do
1042
+ 10.times do
1043
+ subject.visit("/redirect")
1044
+ subject.find("//p").first.text.should == "finished"
1045
+ end
1046
+ end
1047
+ end
1048
+ end