intentmedia-capybara-webkit 0.7.2.1

Sign up to get free protection for your applications and to get access to all the features.
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