imseng-capybara-webkit 0.12.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.
- data/.gitignore +21 -0
- data/.rspec +2 -0
- data/Appraisals +7 -0
- data/CONTRIBUTING.md +47 -0
- data/ChangeLog +70 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/NEWS.md +36 -0
- data/README.md +114 -0
- data/Rakefile +65 -0
- data/bin/Info.plist +22 -0
- data/capybara-webkit.gemspec +28 -0
- data/extconf.rb +2 -0
- data/gemfiles/1.0.gemfile +7 -0
- data/gemfiles/1.0.gemfile.lock +70 -0
- data/gemfiles/1.1.gemfile +7 -0
- data/gemfiles/1.1.gemfile.lock +70 -0
- data/lib/capybara/driver/webkit/browser.rb +164 -0
- data/lib/capybara/driver/webkit/connection.rb +120 -0
- data/lib/capybara/driver/webkit/cookie_jar.rb +55 -0
- data/lib/capybara/driver/webkit/node.rb +118 -0
- data/lib/capybara/driver/webkit/socket_debugger.rb +43 -0
- data/lib/capybara/driver/webkit/version.rb +7 -0
- data/lib/capybara/driver/webkit.rb +136 -0
- data/lib/capybara/webkit/matchers.rb +37 -0
- data/lib/capybara/webkit.rb +13 -0
- data/lib/capybara-webkit.rb +1 -0
- data/lib/capybara_webkit_builder.rb +68 -0
- data/spec/browser_spec.rb +173 -0
- data/spec/capybara_webkit_builder_spec.rb +37 -0
- data/spec/connection_spec.rb +54 -0
- data/spec/cookie_jar_spec.rb +48 -0
- data/spec/driver_rendering_spec.rb +80 -0
- data/spec/driver_resize_window_spec.rb +59 -0
- data/spec/driver_spec.rb +1552 -0
- data/spec/integration/driver_spec.rb +20 -0
- data/spec/integration/session_spec.rb +137 -0
- data/spec/self_signed_ssl_cert.rb +42 -0
- data/spec/spec_helper.rb +46 -0
- data/src/Body.h +12 -0
- data/src/ClearCookies.cpp +15 -0
- data/src/ClearCookies.h +11 -0
- data/src/Command.cpp +19 -0
- data/src/Command.h +31 -0
- data/src/CommandFactory.cpp +37 -0
- data/src/CommandFactory.h +16 -0
- data/src/CommandParser.cpp +76 -0
- data/src/CommandParser.h +33 -0
- data/src/Connection.cpp +71 -0
- data/src/Connection.h +37 -0
- data/src/ConsoleMessages.cpp +10 -0
- data/src/ConsoleMessages.h +12 -0
- data/src/CurrentUrl.cpp +68 -0
- data/src/CurrentUrl.h +16 -0
- data/src/Evaluate.cpp +84 -0
- data/src/Evaluate.h +22 -0
- data/src/Execute.cpp +16 -0
- data/src/Execute.h +12 -0
- data/src/Find.cpp +19 -0
- data/src/Find.h +13 -0
- data/src/FrameFocus.cpp +66 -0
- data/src/FrameFocus.h +28 -0
- data/src/GetCookies.cpp +20 -0
- data/src/GetCookies.h +14 -0
- data/src/Header.cpp +18 -0
- data/src/Header.h +11 -0
- data/src/Headers.cpp +10 -0
- data/src/Headers.h +12 -0
- data/src/IgnoreSslErrors.cpp +12 -0
- data/src/IgnoreSslErrors.h +12 -0
- data/src/JavascriptInvocation.cpp +14 -0
- data/src/JavascriptInvocation.h +19 -0
- data/src/NetworkAccessManager.cpp +29 -0
- data/src/NetworkAccessManager.h +19 -0
- data/src/NetworkCookieJar.cpp +101 -0
- data/src/NetworkCookieJar.h +15 -0
- data/src/Node.cpp +14 -0
- data/src/Node.h +13 -0
- data/src/NullCommand.cpp +10 -0
- data/src/NullCommand.h +11 -0
- data/src/PageLoadingCommand.cpp +46 -0
- data/src/PageLoadingCommand.h +40 -0
- data/src/Render.cpp +18 -0
- data/src/Render.h +12 -0
- data/src/RequestedUrl.cpp +12 -0
- data/src/RequestedUrl.h +12 -0
- data/src/Reset.cpp +29 -0
- data/src/Reset.h +15 -0
- data/src/ResizeWindow.cpp +16 -0
- data/src/ResizeWindow.h +12 -0
- data/src/Response.cpp +24 -0
- data/src/Response.h +15 -0
- data/src/Server.cpp +24 -0
- data/src/Server.h +21 -0
- data/src/SetCookie.cpp +16 -0
- data/src/SetCookie.h +11 -0
- data/src/SetProxy.cpp +22 -0
- data/src/SetProxy.h +11 -0
- data/src/Source.cpp +18 -0
- data/src/Source.h +19 -0
- data/src/Status.cpp +12 -0
- data/src/Status.h +12 -0
- data/src/UnsupportedContentHandler.cpp +32 -0
- data/src/UnsupportedContentHandler.h +18 -0
- data/src/Url.cpp +12 -0
- data/src/Url.h +12 -0
- data/src/Visit.cpp +12 -0
- data/src/Visit.h +12 -0
- data/src/WebPage.cpp +239 -0
- data/src/WebPage.h +58 -0
- data/src/body.cpp +10 -0
- data/src/capybara.js +315 -0
- data/src/find_command.h +29 -0
- data/src/main.cpp +33 -0
- data/src/webkit_server.pro +85 -0
- data/src/webkit_server.qrc +5 -0
- data/templates/Command.cpp +10 -0
- data/templates/Command.h +12 -0
- data/webkit_server.pro +4 -0
- metadata +298 -0
data/spec/driver_spec.rb
ADDED
@@ -0,0 +1,1552 @@
|
|
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
|
+
|
136
|
+
it "returns the current URL when changed by pushState after a redirect" do
|
137
|
+
subject.visit("/redirect-me")
|
138
|
+
port = subject.instance_variable_get("@rack_server").port
|
139
|
+
subject.execute_script("window.history.pushState({}, '', '/pushed-after-redirect')")
|
140
|
+
subject.current_url.should == "http://127.0.0.1:#{port}/pushed-after-redirect"
|
141
|
+
end
|
142
|
+
|
143
|
+
it "returns the current URL when changed by replaceState after a redirect" do
|
144
|
+
subject.visit("/redirect-me")
|
145
|
+
port = subject.instance_variable_get("@rack_server").port
|
146
|
+
subject.execute_script("window.history.replaceState({}, '', '/replaced-after-redirect')")
|
147
|
+
subject.current_url.should == "http://127.0.0.1:#{port}/replaced-after-redirect"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context "css app" do
|
152
|
+
before(:all) do
|
153
|
+
body = "css"
|
154
|
+
@app = lambda do |env|
|
155
|
+
[200, {"Content-Type" => "text/css", "Content-Length" => body.length.to_s}, [body]]
|
156
|
+
end
|
157
|
+
subject.visit("/")
|
158
|
+
end
|
159
|
+
|
160
|
+
it "renders unsupported content types gracefully" do
|
161
|
+
subject.body.should =~ /css/
|
162
|
+
end
|
163
|
+
|
164
|
+
it "sets the response headers with respect to the unsupported request" do
|
165
|
+
subject.response_headers["Content-Type"].should == "text/css"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context "hello app" do
|
170
|
+
before(:all) do
|
171
|
+
@app = lambda do |env|
|
172
|
+
body = <<-HTML
|
173
|
+
<html>
|
174
|
+
<head>
|
175
|
+
<style type="text/css">
|
176
|
+
#display_none { display: none }
|
177
|
+
</style>
|
178
|
+
</head>
|
179
|
+
<body>
|
180
|
+
<div class='normalize'>Spaces not normalized </div>
|
181
|
+
<div id="display_none">
|
182
|
+
<div id="invisible">Can't see me</div>
|
183
|
+
</div>
|
184
|
+
<input type="text" disabled="disabled"/>
|
185
|
+
<input id="checktest" type="checkbox" checked="checked"/>
|
186
|
+
<script type="text/javascript">
|
187
|
+
document.write("<p id='greeting'>he" + "llo</p>");
|
188
|
+
</script>
|
189
|
+
</body>
|
190
|
+
</html>
|
191
|
+
HTML
|
192
|
+
[200,
|
193
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
194
|
+
[body]]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
it "handles anchor tags" do
|
199
|
+
subject.visit("#test")
|
200
|
+
subject.find("//*[contains(., 'hello')]").should_not be_empty
|
201
|
+
subject.visit("#test")
|
202
|
+
subject.find("//*[contains(., 'hello')]").should_not be_empty
|
203
|
+
end
|
204
|
+
|
205
|
+
it "finds content after loading a URL" do
|
206
|
+
subject.find("//*[contains(., 'hello')]").should_not be_empty
|
207
|
+
end
|
208
|
+
|
209
|
+
it "has an empty page after reseting" do
|
210
|
+
subject.reset!
|
211
|
+
subject.find("//*[contains(., 'hello')]").should be_empty
|
212
|
+
end
|
213
|
+
|
214
|
+
it "has a location of 'about:blank' after reseting" do
|
215
|
+
subject.reset!
|
216
|
+
subject.current_url.should == "about:blank"
|
217
|
+
end
|
218
|
+
|
219
|
+
it "raises an error for an invalid xpath query" do
|
220
|
+
expect { subject.find("totally invalid salad") }.
|
221
|
+
to raise_error(Capybara::Driver::Webkit::WebkitInvalidResponseError, /xpath/i)
|
222
|
+
end
|
223
|
+
|
224
|
+
it "returns an attribute's value" do
|
225
|
+
subject.find("//p").first["id"].should == "greeting"
|
226
|
+
end
|
227
|
+
|
228
|
+
it "parses xpath with quotes" do
|
229
|
+
subject.find('//*[contains(., "hello")]').should_not be_empty
|
230
|
+
end
|
231
|
+
|
232
|
+
it "returns a node's text" do
|
233
|
+
subject.find("//p").first.text.should == "hello"
|
234
|
+
end
|
235
|
+
|
236
|
+
it "normalizes a node's text" do
|
237
|
+
subject.find("//div[contains(@class, 'normalize')]").first.text.should == "Spaces not normalized"
|
238
|
+
end
|
239
|
+
|
240
|
+
it "returns the current URL" do
|
241
|
+
port = subject.instance_variable_get("@rack_server").port
|
242
|
+
subject.current_url.should == "http://127.0.0.1:#{port}/hello/world?success=true"
|
243
|
+
end
|
244
|
+
|
245
|
+
it "returns the current URL when changed by pushState" do
|
246
|
+
port = subject.instance_variable_get("@rack_server").port
|
247
|
+
subject.execute_script("window.history.pushState({}, '', '/pushed')")
|
248
|
+
subject.current_url.should == "http://127.0.0.1:#{port}/pushed"
|
249
|
+
end
|
250
|
+
|
251
|
+
it "returns the current URL when changed by replaceState" do
|
252
|
+
port = subject.instance_variable_get("@rack_server").port
|
253
|
+
subject.execute_script("window.history.replaceState({}, '', '/replaced')")
|
254
|
+
subject.current_url.should == "http://127.0.0.1:#{port}/replaced"
|
255
|
+
end
|
256
|
+
|
257
|
+
it "does not double-encode URLs" do
|
258
|
+
subject.visit("/hello/world?success=%25true")
|
259
|
+
subject.current_url.should =~ /success=\%25true/
|
260
|
+
end
|
261
|
+
|
262
|
+
it "visits a page with an anchor" do
|
263
|
+
subject.visit("/hello#display_none")
|
264
|
+
subject.current_url.should =~ /hello#display_none/
|
265
|
+
end
|
266
|
+
|
267
|
+
it "returns the source code for the page" do
|
268
|
+
subject.source.should =~ %r{<html>.*greeting.*}m
|
269
|
+
end
|
270
|
+
|
271
|
+
it "evaluates Javascript and returns a string" do
|
272
|
+
result = subject.evaluate_script(%<document.getElementById('greeting').innerText>)
|
273
|
+
result.should == "hello"
|
274
|
+
end
|
275
|
+
|
276
|
+
it "evaluates Javascript and returns an array" do
|
277
|
+
result = subject.evaluate_script(%<["hello", "world"]>)
|
278
|
+
result.should == %w(hello world)
|
279
|
+
end
|
280
|
+
|
281
|
+
it "evaluates Javascript and returns an int" do
|
282
|
+
result = subject.evaluate_script(%<123>)
|
283
|
+
result.should == 123
|
284
|
+
end
|
285
|
+
|
286
|
+
it "evaluates Javascript and returns a float" do
|
287
|
+
result = subject.evaluate_script(%<1.5>)
|
288
|
+
result.should == 1.5
|
289
|
+
end
|
290
|
+
|
291
|
+
it "evaluates Javascript and returns null" do
|
292
|
+
result = subject.evaluate_script(%<(function () {})()>)
|
293
|
+
result.should == nil
|
294
|
+
end
|
295
|
+
|
296
|
+
it "evaluates Javascript and returns an object" do
|
297
|
+
result = subject.evaluate_script(%<({ 'one' : 1 })>)
|
298
|
+
result.should == { 'one' => 1 }
|
299
|
+
end
|
300
|
+
|
301
|
+
it "evaluates Javascript and returns true" do
|
302
|
+
result = subject.evaluate_script(%<true>)
|
303
|
+
result.should === true
|
304
|
+
end
|
305
|
+
|
306
|
+
it "evaluates Javascript and returns false" do
|
307
|
+
result = subject.evaluate_script(%<false>)
|
308
|
+
result.should === false
|
309
|
+
end
|
310
|
+
|
311
|
+
it "evaluates Javascript and returns an escaped string" do
|
312
|
+
result = subject.evaluate_script(%<'"'>)
|
313
|
+
result.should === "\""
|
314
|
+
end
|
315
|
+
|
316
|
+
it "evaluates Javascript with multiple lines" do
|
317
|
+
result = subject.evaluate_script("[1,\n2]")
|
318
|
+
result.should == [1, 2]
|
319
|
+
end
|
320
|
+
|
321
|
+
it "executes Javascript" do
|
322
|
+
subject.execute_script(%<document.getElementById('greeting').innerHTML = 'yo'>)
|
323
|
+
subject.find("//p[contains(., 'yo')]").should_not be_empty
|
324
|
+
end
|
325
|
+
|
326
|
+
it "raises an error for failing Javascript" do
|
327
|
+
expect { subject.execute_script(%<invalid salad>) }.
|
328
|
+
to raise_error(Capybara::Driver::Webkit::WebkitInvalidResponseError)
|
329
|
+
end
|
330
|
+
|
331
|
+
it "doesn't raise an error for Javascript that doesn't return anything" do
|
332
|
+
lambda { subject.execute_script(%<(function () { "returns nothing" })()>) }.
|
333
|
+
should_not raise_error
|
334
|
+
end
|
335
|
+
|
336
|
+
it "returns a node's tag name" do
|
337
|
+
subject.find("//p").first.tag_name.should == "p"
|
338
|
+
end
|
339
|
+
|
340
|
+
it "reads disabled property" do
|
341
|
+
subject.find("//input").first.should be_disabled
|
342
|
+
end
|
343
|
+
|
344
|
+
it "reads checked property" do
|
345
|
+
subject.find("//input[@id='checktest']").first.should be_checked
|
346
|
+
end
|
347
|
+
|
348
|
+
it "finds visible elements" do
|
349
|
+
subject.find("//p").first.should be_visible
|
350
|
+
subject.find("//*[@id='invisible']").first.should_not be_visible
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
context "console messages app" do
|
355
|
+
|
356
|
+
before(:all) do
|
357
|
+
@app = lambda do |env|
|
358
|
+
body = <<-HTML
|
359
|
+
<html>
|
360
|
+
<head>
|
361
|
+
</head>
|
362
|
+
<body>
|
363
|
+
<script type="text/javascript">
|
364
|
+
console.log("hello");
|
365
|
+
console.log("hello again");
|
366
|
+
oops
|
367
|
+
</script>
|
368
|
+
</body>
|
369
|
+
</html>
|
370
|
+
HTML
|
371
|
+
[200,
|
372
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
373
|
+
[body]]
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
it "collects messages logged to the console" do
|
378
|
+
subject.console_messages.first.should include :source, :message => "hello", :line_number => 6
|
379
|
+
subject.console_messages.length.should eq 3
|
380
|
+
end
|
381
|
+
|
382
|
+
it "logs errors to the console" do
|
383
|
+
subject.error_messages.length.should eq 1
|
384
|
+
end
|
385
|
+
|
386
|
+
end
|
387
|
+
|
388
|
+
context "form app" do
|
389
|
+
before(:all) do
|
390
|
+
@app = lambda do |env|
|
391
|
+
body = <<-HTML
|
392
|
+
<html><body>
|
393
|
+
<form action="/" method="GET">
|
394
|
+
<input type="text" name="foo" value="bar"/>
|
395
|
+
<input type="text" name="maxlength_foo" value="bar" maxlength="10"/>
|
396
|
+
<input type="text" id="disabled_input" disabled="disabled"/>
|
397
|
+
<input type="checkbox" name="checkedbox" value="1" checked="checked"/>
|
398
|
+
<input type="checkbox" name="uncheckedbox" value="2"/>
|
399
|
+
<select name="animal">
|
400
|
+
<option id="select-option-monkey">Monkey</option>
|
401
|
+
<option id="select-option-capybara" selected="selected">Capybara</option>
|
402
|
+
</select>
|
403
|
+
<select name="toppings" multiple="multiple">
|
404
|
+
<optgroup label="Mediocre Toppings">
|
405
|
+
<option selected="selected" id="topping-apple">Apple</option>
|
406
|
+
<option selected="selected" id="topping-banana">Banana</option>
|
407
|
+
</optgroup>
|
408
|
+
<optgroup label="Best Toppings">
|
409
|
+
<option selected="selected" id="topping-cherry">Cherry</option>
|
410
|
+
</optgroup>
|
411
|
+
</select>
|
412
|
+
<textarea id="only-textarea">what a wonderful area for text</textarea>
|
413
|
+
<input type="radio" id="only-radio" value="1"/>
|
414
|
+
<button type="reset">Reset Form</button>
|
415
|
+
</form>
|
416
|
+
</body></html>
|
417
|
+
HTML
|
418
|
+
[200,
|
419
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
420
|
+
[body]]
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
it "returns a textarea's value" do
|
425
|
+
subject.find("//textarea").first.value.should == "what a wonderful area for text"
|
426
|
+
end
|
427
|
+
|
428
|
+
it "returns a text input's value" do
|
429
|
+
subject.find("//input").first.value.should == "bar"
|
430
|
+
end
|
431
|
+
|
432
|
+
it "returns a select's value" do
|
433
|
+
subject.find("//select").first.value.should == "Capybara"
|
434
|
+
end
|
435
|
+
|
436
|
+
it "sets an input's value" do
|
437
|
+
input = subject.find("//input").first
|
438
|
+
input.set("newvalue")
|
439
|
+
input.value.should == "newvalue"
|
440
|
+
end
|
441
|
+
|
442
|
+
it "sets an input's value greater than the max length" do
|
443
|
+
input = subject.find("//input[@name='maxlength_foo']").first
|
444
|
+
input.set("allegories (poems)")
|
445
|
+
input.value.should == "allegories"
|
446
|
+
end
|
447
|
+
|
448
|
+
it "sets an input's value equal to the max length" do
|
449
|
+
input = subject.find("//input[@name='maxlength_foo']").first
|
450
|
+
input.set("allegories")
|
451
|
+
input.value.should == "allegories"
|
452
|
+
end
|
453
|
+
|
454
|
+
it "sets an input's value less than the max length" do
|
455
|
+
input = subject.find("//input[@name='maxlength_foo']").first
|
456
|
+
input.set("poems")
|
457
|
+
input.value.should == "poems"
|
458
|
+
end
|
459
|
+
|
460
|
+
it "sets an input's nil value" do
|
461
|
+
input = subject.find("//input").first
|
462
|
+
input.set(nil)
|
463
|
+
input.value.should == ""
|
464
|
+
end
|
465
|
+
|
466
|
+
it "sets a select's value" do
|
467
|
+
select = subject.find("//select").first
|
468
|
+
select.set("Monkey")
|
469
|
+
select.value.should == "Monkey"
|
470
|
+
end
|
471
|
+
|
472
|
+
it "sets a textarea's value" do
|
473
|
+
textarea = subject.find("//textarea").first
|
474
|
+
textarea.set("newvalue")
|
475
|
+
textarea.value.should == "newvalue"
|
476
|
+
end
|
477
|
+
|
478
|
+
let(:monkey_option) { subject.find("//option[@id='select-option-monkey']").first }
|
479
|
+
let(:capybara_option) { subject.find("//option[@id='select-option-capybara']").first }
|
480
|
+
let(:animal_select) { subject.find("//select[@name='animal']").first }
|
481
|
+
let(:apple_option) { subject.find("//option[@id='topping-apple']").first }
|
482
|
+
let(:banana_option) { subject.find("//option[@id='topping-banana']").first }
|
483
|
+
let(:cherry_option) { subject.find("//option[@id='topping-cherry']").first }
|
484
|
+
let(:toppings_select) { subject.find("//select[@name='toppings']").first }
|
485
|
+
let(:reset_button) { subject.find("//button[@type='reset']").first }
|
486
|
+
|
487
|
+
context "a select element's selection has been changed" do
|
488
|
+
before do
|
489
|
+
animal_select.value.should == "Capybara"
|
490
|
+
monkey_option.select_option
|
491
|
+
end
|
492
|
+
|
493
|
+
it "returns the new selection" do
|
494
|
+
animal_select.value.should == "Monkey"
|
495
|
+
end
|
496
|
+
|
497
|
+
it "does not modify the selected attribute of a new selection" do
|
498
|
+
monkey_option['selected'].should be_empty
|
499
|
+
end
|
500
|
+
|
501
|
+
it "returns the old value when a reset button is clicked" do
|
502
|
+
reset_button.click
|
503
|
+
|
504
|
+
animal_select.value.should == "Capybara"
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
context "a multi-select element's option has been unselected" do
|
509
|
+
before do
|
510
|
+
toppings_select.value.should include("Apple", "Banana", "Cherry")
|
511
|
+
|
512
|
+
apple_option.unselect_option
|
513
|
+
end
|
514
|
+
|
515
|
+
it "does not return the deselected option" do
|
516
|
+
toppings_select.value.should_not include("Apple")
|
517
|
+
end
|
518
|
+
|
519
|
+
it "returns the deselected option when a reset button is clicked" do
|
520
|
+
reset_button.click
|
521
|
+
|
522
|
+
toppings_select.value.should include("Apple", "Banana", "Cherry")
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
it "reselects an option in a multi-select" do
|
527
|
+
apple_option.unselect_option
|
528
|
+
banana_option.unselect_option
|
529
|
+
cherry_option.unselect_option
|
530
|
+
|
531
|
+
toppings_select.value.should == []
|
532
|
+
|
533
|
+
apple_option.select_option
|
534
|
+
banana_option.select_option
|
535
|
+
cherry_option.select_option
|
536
|
+
|
537
|
+
toppings_select.value.should include("Apple", "Banana", "Cherry")
|
538
|
+
end
|
539
|
+
|
540
|
+
let(:checked_box) { subject.find("//input[@name='checkedbox']").first }
|
541
|
+
let(:unchecked_box) { subject.find("//input[@name='uncheckedbox']").first }
|
542
|
+
|
543
|
+
it "knows a checked box is checked" do
|
544
|
+
checked_box['checked'].should be_true
|
545
|
+
end
|
546
|
+
|
547
|
+
it "knows a checked box is checked using checked?" do
|
548
|
+
checked_box.should be_checked
|
549
|
+
end
|
550
|
+
|
551
|
+
it "knows an unchecked box is unchecked" do
|
552
|
+
unchecked_box['checked'].should_not be_true
|
553
|
+
end
|
554
|
+
|
555
|
+
it "knows an unchecked box is unchecked using checked?" do
|
556
|
+
unchecked_box.should_not be_checked
|
557
|
+
end
|
558
|
+
|
559
|
+
it "checks an unchecked box" do
|
560
|
+
unchecked_box.set(true)
|
561
|
+
unchecked_box.should be_checked
|
562
|
+
end
|
563
|
+
|
564
|
+
it "unchecks a checked box" do
|
565
|
+
checked_box.set(false)
|
566
|
+
checked_box.should_not be_checked
|
567
|
+
end
|
568
|
+
|
569
|
+
it "leaves a checked box checked" do
|
570
|
+
checked_box.set(true)
|
571
|
+
checked_box.should be_checked
|
572
|
+
end
|
573
|
+
|
574
|
+
it "leaves an unchecked box unchecked" do
|
575
|
+
unchecked_box.set(false)
|
576
|
+
unchecked_box.should_not be_checked
|
577
|
+
end
|
578
|
+
|
579
|
+
let(:enabled_input) { subject.find("//input[@name='foo']").first }
|
580
|
+
let(:disabled_input) { subject.find("//input[@id='disabled_input']").first }
|
581
|
+
|
582
|
+
it "knows a disabled input is disabled" do
|
583
|
+
disabled_input['disabled'].should be_true
|
584
|
+
end
|
585
|
+
|
586
|
+
it "knows a not disabled input is not disabled" do
|
587
|
+
enabled_input['disabled'].should_not be_true
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
context "dom events" do
|
592
|
+
before(:all) do
|
593
|
+
@app = lambda do |env|
|
594
|
+
body = <<-HTML
|
595
|
+
|
596
|
+
<html><body>
|
597
|
+
<a href='#' class='watch'>Link</a>
|
598
|
+
<ul id="events"></ul>
|
599
|
+
<script type="text/javascript">
|
600
|
+
var events = document.getElementById("events");
|
601
|
+
var recordEvent = function (event) {
|
602
|
+
var element = document.createElement("li");
|
603
|
+
element.innerHTML = event.type;
|
604
|
+
events.appendChild(element);
|
605
|
+
};
|
606
|
+
|
607
|
+
var elements = document.getElementsByClassName("watch");
|
608
|
+
for (var i = 0; i < elements.length; i++) {
|
609
|
+
var element = elements[i];
|
610
|
+
element.addEventListener("mousedown", recordEvent);
|
611
|
+
element.addEventListener("mouseup", recordEvent);
|
612
|
+
element.addEventListener("click", recordEvent);
|
613
|
+
}
|
614
|
+
</script>
|
615
|
+
</body></html>
|
616
|
+
HTML
|
617
|
+
[200,
|
618
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
619
|
+
[body]]
|
620
|
+
end
|
621
|
+
end
|
622
|
+
|
623
|
+
it "triggers mouse events" do
|
624
|
+
subject.find("//a").first.click
|
625
|
+
subject.find("//li").map(&:text).should == %w(mousedown mouseup click)
|
626
|
+
end
|
627
|
+
end
|
628
|
+
|
629
|
+
context "form events app" do
|
630
|
+
before(:all) do
|
631
|
+
@app = lambda do |env|
|
632
|
+
body = <<-HTML
|
633
|
+
<html><body>
|
634
|
+
<form action="/" method="GET">
|
635
|
+
<input class="watch" type="email"/>
|
636
|
+
<input class="watch" type="number"/>
|
637
|
+
<input class="watch" type="password"/>
|
638
|
+
<input class="watch" type="search"/>
|
639
|
+
<input class="watch" type="tel"/>
|
640
|
+
<input class="watch" type="text"/>
|
641
|
+
<input class="watch" type="url"/>
|
642
|
+
<textarea class="watch"></textarea>
|
643
|
+
<input class="watch" type="checkbox"/>
|
644
|
+
<input class="watch" type="radio"/>
|
645
|
+
</form>
|
646
|
+
<ul id="events"></ul>
|
647
|
+
<script type="text/javascript">
|
648
|
+
var events = document.getElementById("events");
|
649
|
+
var recordEvent = function (event) {
|
650
|
+
var element = document.createElement("li");
|
651
|
+
element.innerHTML = event.type;
|
652
|
+
events.appendChild(element);
|
653
|
+
};
|
654
|
+
|
655
|
+
var elements = document.getElementsByClassName("watch");
|
656
|
+
for (var i = 0; i < elements.length; i++) {
|
657
|
+
var element = elements[i];
|
658
|
+
element.addEventListener("focus", recordEvent);
|
659
|
+
element.addEventListener("keydown", recordEvent);
|
660
|
+
element.addEventListener("keypress", recordEvent);
|
661
|
+
element.addEventListener("keyup", recordEvent);
|
662
|
+
element.addEventListener("input", recordEvent);
|
663
|
+
element.addEventListener("change", recordEvent);
|
664
|
+
element.addEventListener("blur", recordEvent);
|
665
|
+
element.addEventListener("mousedown", recordEvent);
|
666
|
+
element.addEventListener("mouseup", recordEvent);
|
667
|
+
element.addEventListener("click", recordEvent);
|
668
|
+
}
|
669
|
+
</script>
|
670
|
+
</body></html>
|
671
|
+
HTML
|
672
|
+
[200,
|
673
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
674
|
+
[body]]
|
675
|
+
end
|
676
|
+
end
|
677
|
+
|
678
|
+
let(:newtext) { 'newvalue' }
|
679
|
+
|
680
|
+
let(:keyevents) do
|
681
|
+
(%w{focus} +
|
682
|
+
newtext.length.times.collect { %w{keydown keypress keyup input} } +
|
683
|
+
%w{change blur}).flatten
|
684
|
+
end
|
685
|
+
|
686
|
+
%w(email number password search tel text url).each do | field_type |
|
687
|
+
it "triggers text input events on inputs of type #{field_type}" do
|
688
|
+
subject.find("//input[@type='#{field_type}']").first.set(newtext)
|
689
|
+
subject.find("//li").map(&:text).should == keyevents
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
it "triggers textarea input events" do
|
694
|
+
subject.find("//textarea").first.set(newtext)
|
695
|
+
subject.find("//li").map(&:text).should == keyevents
|
696
|
+
end
|
697
|
+
|
698
|
+
it "triggers radio input events" do
|
699
|
+
subject.find("//input[@type='radio']").first.set(true)
|
700
|
+
subject.find("//li").map(&:text).should == %w(mousedown mouseup change click)
|
701
|
+
end
|
702
|
+
|
703
|
+
it "triggers checkbox events" do
|
704
|
+
subject.find("//input[@type='checkbox']").first.set(true)
|
705
|
+
subject.find("//li").map(&:text).should == %w(mousedown mouseup change click)
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
709
|
+
context "mouse app" do
|
710
|
+
before(:all) do
|
711
|
+
@app =lambda do |env|
|
712
|
+
body = <<-HTML
|
713
|
+
<html><body>
|
714
|
+
<div id="change">Change me</div>
|
715
|
+
<div id="mouseup">Push me</div>
|
716
|
+
<div id="mousedown">Release me</div>
|
717
|
+
<form action="/" method="GET">
|
718
|
+
<select id="change_select" name="change_select">
|
719
|
+
<option value="1" id="option-1" selected="selected">one</option>
|
720
|
+
<option value="2" id="option-2">two</option>
|
721
|
+
</select>
|
722
|
+
</form>
|
723
|
+
<script type="text/javascript">
|
724
|
+
document.getElementById("change_select").
|
725
|
+
addEventListener("change", function () {
|
726
|
+
this.className = "triggered";
|
727
|
+
});
|
728
|
+
document.getElementById("change").
|
729
|
+
addEventListener("change", function () {
|
730
|
+
this.className = "triggered";
|
731
|
+
});
|
732
|
+
document.getElementById("mouseup").
|
733
|
+
addEventListener("mouseup", function () {
|
734
|
+
this.className = "triggered";
|
735
|
+
});
|
736
|
+
document.getElementById("mousedown").
|
737
|
+
addEventListener("mousedown", function () {
|
738
|
+
this.className = "triggered";
|
739
|
+
});
|
740
|
+
</script>
|
741
|
+
<a href="/next">Next</a>
|
742
|
+
</body></html>
|
743
|
+
HTML
|
744
|
+
[200,
|
745
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
746
|
+
[body]]
|
747
|
+
end
|
748
|
+
end
|
749
|
+
|
750
|
+
it "clicks an element" do
|
751
|
+
subject.find("//a").first.click
|
752
|
+
subject.current_url =~ %r{/next$}
|
753
|
+
end
|
754
|
+
|
755
|
+
it "fires a mouse event" do
|
756
|
+
subject.find("//*[@id='mouseup']").first.trigger("mouseup")
|
757
|
+
subject.find("//*[@class='triggered']").should_not be_empty
|
758
|
+
end
|
759
|
+
|
760
|
+
it "fires a non-mouse event" do
|
761
|
+
subject.find("//*[@id='change']").first.trigger("change")
|
762
|
+
subject.find("//*[@class='triggered']").should_not be_empty
|
763
|
+
end
|
764
|
+
|
765
|
+
it "fires a change on select" do
|
766
|
+
select = subject.find("//select").first
|
767
|
+
select.value.should == "1"
|
768
|
+
option = subject.find("//option[@id='option-2']").first
|
769
|
+
option.select_option
|
770
|
+
select.value.should == "2"
|
771
|
+
subject.find("//select[@class='triggered']").should_not be_empty
|
772
|
+
end
|
773
|
+
|
774
|
+
it "fires drag events" do
|
775
|
+
draggable = subject.find("//*[@id='mousedown']").first
|
776
|
+
container = subject.find("//*[@id='mouseup']").first
|
777
|
+
|
778
|
+
draggable.drag_to(container)
|
779
|
+
|
780
|
+
subject.find("//*[@class='triggered']").size.should == 1
|
781
|
+
end
|
782
|
+
end
|
783
|
+
|
784
|
+
context "nesting app" do
|
785
|
+
before(:all) do
|
786
|
+
@app = lambda do |env|
|
787
|
+
body = <<-HTML
|
788
|
+
<html><body>
|
789
|
+
<div id="parent">
|
790
|
+
<div class="find">Expected</div>
|
791
|
+
</div>
|
792
|
+
<div class="find">Unexpected</div>
|
793
|
+
</body></html>
|
794
|
+
HTML
|
795
|
+
[200,
|
796
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
797
|
+
[body]]
|
798
|
+
end
|
799
|
+
end
|
800
|
+
|
801
|
+
it "evaluates nested xpath expressions" do
|
802
|
+
parent = subject.find("//*[@id='parent']").first
|
803
|
+
parent.find("./*[@class='find']").map(&:text).should == %w(Expected)
|
804
|
+
end
|
805
|
+
end
|
806
|
+
|
807
|
+
context "slow app" do
|
808
|
+
before(:all) do
|
809
|
+
@result = ""
|
810
|
+
@app = lambda do |env|
|
811
|
+
if env["PATH_INFO"] == "/result"
|
812
|
+
sleep(0.5)
|
813
|
+
@result << "finished"
|
814
|
+
end
|
815
|
+
body = %{<html><body><a href="/result">Go</a></body></html>}
|
816
|
+
[200,
|
817
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
818
|
+
[body]]
|
819
|
+
end
|
820
|
+
end
|
821
|
+
|
822
|
+
it "waits for a request to load" do
|
823
|
+
subject.find("//a").first.click
|
824
|
+
@result.should == "finished"
|
825
|
+
end
|
826
|
+
end
|
827
|
+
|
828
|
+
context "error app" do
|
829
|
+
before(:all) do
|
830
|
+
@app = lambda do |env|
|
831
|
+
if env['PATH_INFO'] == "/error"
|
832
|
+
[404, {}, []]
|
833
|
+
else
|
834
|
+
body = <<-HTML
|
835
|
+
<html><body>
|
836
|
+
<form action="/error"><input type="submit"/></form>
|
837
|
+
</body></html>
|
838
|
+
HTML
|
839
|
+
[200,
|
840
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
841
|
+
[body]]
|
842
|
+
end
|
843
|
+
end
|
844
|
+
end
|
845
|
+
|
846
|
+
it "raises a webkit error for the requested url" do
|
847
|
+
expect {
|
848
|
+
subject.find("//input").first.click
|
849
|
+
wait_for_error_to_complete
|
850
|
+
subject.find("//body")
|
851
|
+
}.
|
852
|
+
to raise_error(Capybara::Driver::Webkit::WebkitInvalidResponseError, %r{/error})
|
853
|
+
end
|
854
|
+
|
855
|
+
def wait_for_error_to_complete
|
856
|
+
sleep(0.5)
|
857
|
+
end
|
858
|
+
end
|
859
|
+
|
860
|
+
context "slow error app" do
|
861
|
+
before(:all) do
|
862
|
+
@app = lambda do |env|
|
863
|
+
if env['PATH_INFO'] == "/error"
|
864
|
+
body = "error"
|
865
|
+
sleep(1)
|
866
|
+
[304, {}, []]
|
867
|
+
else
|
868
|
+
body = <<-HTML
|
869
|
+
<html><body>
|
870
|
+
<form action="/error"><input type="submit"/></form>
|
871
|
+
<p>hello</p>
|
872
|
+
</body></html>
|
873
|
+
HTML
|
874
|
+
[200,
|
875
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
876
|
+
[body]]
|
877
|
+
end
|
878
|
+
end
|
879
|
+
end
|
880
|
+
|
881
|
+
it "raises a webkit error and then continues" do
|
882
|
+
subject.find("//input").first.click
|
883
|
+
expect { subject.find("//p") }.to raise_error(Capybara::Driver::Webkit::WebkitInvalidResponseError)
|
884
|
+
subject.visit("/")
|
885
|
+
subject.find("//p").first.text.should == "hello"
|
886
|
+
end
|
887
|
+
end
|
888
|
+
|
889
|
+
context "popup app" do
|
890
|
+
before(:all) do
|
891
|
+
@app = lambda do |env|
|
892
|
+
body = <<-HTML
|
893
|
+
<html><body>
|
894
|
+
<script type="text/javascript">
|
895
|
+
alert("alert");
|
896
|
+
confirm("confirm");
|
897
|
+
prompt("prompt");
|
898
|
+
</script>
|
899
|
+
<p>success</p>
|
900
|
+
</body></html>
|
901
|
+
HTML
|
902
|
+
sleep(0.5)
|
903
|
+
[200,
|
904
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
905
|
+
[body]]
|
906
|
+
end
|
907
|
+
end
|
908
|
+
|
909
|
+
it "doesn't crash from alerts" do
|
910
|
+
subject.find("//p").first.text.should == "success"
|
911
|
+
end
|
912
|
+
end
|
913
|
+
|
914
|
+
context "custom header" do
|
915
|
+
before(:all) do
|
916
|
+
@app = lambda do |env|
|
917
|
+
body = <<-HTML
|
918
|
+
<html><body>
|
919
|
+
<p id="user-agent">#{env['HTTP_USER_AGENT']}</p>
|
920
|
+
<p id="x-capybara-webkit-header">#{env['HTTP_X_CAPYBARA_WEBKIT_HEADER']}</p>
|
921
|
+
<p id="accept">#{env['HTTP_ACCEPT']}</p>
|
922
|
+
<a href="/">/</a>
|
923
|
+
</body></html>
|
924
|
+
HTML
|
925
|
+
[200,
|
926
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
927
|
+
[body]]
|
928
|
+
end
|
929
|
+
end
|
930
|
+
|
931
|
+
before do
|
932
|
+
subject.header('user-agent', 'capybara-webkit/custom-user-agent')
|
933
|
+
subject.header('x-capybara-webkit-header', 'x-capybara-webkit-header')
|
934
|
+
subject.header('accept', 'text/html')
|
935
|
+
subject.visit('/')
|
936
|
+
end
|
937
|
+
|
938
|
+
it "can set user_agent" do
|
939
|
+
subject.find('id("user-agent")').first.text.should == 'capybara-webkit/custom-user-agent'
|
940
|
+
subject.evaluate_script('navigator.userAgent').should == 'capybara-webkit/custom-user-agent'
|
941
|
+
end
|
942
|
+
|
943
|
+
it "keep user_agent in next page" do
|
944
|
+
subject.find("//a").first.click
|
945
|
+
subject.find('id("user-agent")').first.text.should == 'capybara-webkit/custom-user-agent'
|
946
|
+
subject.evaluate_script('navigator.userAgent').should == 'capybara-webkit/custom-user-agent'
|
947
|
+
end
|
948
|
+
|
949
|
+
it "can set custom header" do
|
950
|
+
subject.find('id("x-capybara-webkit-header")').first.text.should == 'x-capybara-webkit-header'
|
951
|
+
end
|
952
|
+
|
953
|
+
it "can set Accept header" do
|
954
|
+
subject.find('id("accept")').first.text.should == 'text/html'
|
955
|
+
end
|
956
|
+
|
957
|
+
it "can reset all custom header" do
|
958
|
+
subject.reset!
|
959
|
+
subject.visit('/')
|
960
|
+
subject.find('id("user-agent")').first.text.should_not == 'capybara-webkit/custom-user-agent'
|
961
|
+
subject.evaluate_script('navigator.userAgent').should_not == 'capybara-webkit/custom-user-agent'
|
962
|
+
subject.find('id("x-capybara-webkit-header")').first.text.should be_empty
|
963
|
+
subject.find('id("accept")').first.text.should_not == 'text/html'
|
964
|
+
end
|
965
|
+
end
|
966
|
+
|
967
|
+
context "no response app" do
|
968
|
+
before(:all) do
|
969
|
+
@app = lambda do |env|
|
970
|
+
body = <<-HTML
|
971
|
+
<html><body>
|
972
|
+
<form action="/error"><input type="submit"/></form>
|
973
|
+
</body></html>
|
974
|
+
HTML
|
975
|
+
[200,
|
976
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
977
|
+
[body]]
|
978
|
+
end
|
979
|
+
end
|
980
|
+
|
981
|
+
it "raises a webkit error for the requested url" do
|
982
|
+
make_the_server_go_away
|
983
|
+
expect {
|
984
|
+
subject.find("//body")
|
985
|
+
}.
|
986
|
+
to raise_error(Capybara::Driver::Webkit::WebkitNoResponseError, %r{response})
|
987
|
+
make_the_server_come_back
|
988
|
+
end
|
989
|
+
|
990
|
+
def make_the_server_come_back
|
991
|
+
subject.browser.instance_variable_get(:@connection).unstub!(:gets)
|
992
|
+
subject.browser.instance_variable_get(:@connection).unstub!(:puts)
|
993
|
+
subject.browser.instance_variable_get(:@connection).unstub!(:print)
|
994
|
+
end
|
995
|
+
|
996
|
+
def make_the_server_go_away
|
997
|
+
subject.browser.instance_variable_get(:@connection).stub!(:gets).and_return(nil)
|
998
|
+
subject.browser.instance_variable_get(:@connection).stub!(:puts)
|
999
|
+
subject.browser.instance_variable_get(:@connection).stub!(:print)
|
1000
|
+
end
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
context "custom font app" do
|
1004
|
+
before(:all) do
|
1005
|
+
@app = lambda do |env|
|
1006
|
+
body = <<-HTML
|
1007
|
+
<html>
|
1008
|
+
<head>
|
1009
|
+
<style type="text/css">
|
1010
|
+
p { font-family: "Verdana"; }
|
1011
|
+
</style>
|
1012
|
+
</head>
|
1013
|
+
<body>
|
1014
|
+
<p id="text">Hello</p>
|
1015
|
+
</body>
|
1016
|
+
</html>
|
1017
|
+
HTML
|
1018
|
+
[200,
|
1019
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
1020
|
+
[body]]
|
1021
|
+
end
|
1022
|
+
end
|
1023
|
+
|
1024
|
+
it "ignores custom fonts" do
|
1025
|
+
font_family = subject.evaluate_script(<<-SCRIPT)
|
1026
|
+
var element = document.getElementById("text");
|
1027
|
+
element.ownerDocument.defaultView.getComputedStyle(element, null).getPropertyValue("font-family");
|
1028
|
+
SCRIPT
|
1029
|
+
font_family.should == "Arial"
|
1030
|
+
end
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
context "cookie-based app" do
|
1034
|
+
before(:all) do
|
1035
|
+
@cookie = 'cookie=abc; domain=127.0.0.1; path=/'
|
1036
|
+
@app = lambda do |env|
|
1037
|
+
request = ::Rack::Request.new(env)
|
1038
|
+
|
1039
|
+
body = <<-HTML
|
1040
|
+
<html><body>
|
1041
|
+
<p id="cookie">#{request.cookies["cookie"] || ""}</p>
|
1042
|
+
</body></html>
|
1043
|
+
HTML
|
1044
|
+
[200,
|
1045
|
+
{ 'Content-Type' => 'text/html; charset=UTF-8',
|
1046
|
+
'Content-Length' => body.length.to_s,
|
1047
|
+
'Set-Cookie' => @cookie,
|
1048
|
+
},
|
1049
|
+
[body]]
|
1050
|
+
end
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
def echoed_cookie
|
1054
|
+
subject.find('id("cookie")').first.text
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
it "remembers the cookie on second visit" do
|
1058
|
+
echoed_cookie.should == ""
|
1059
|
+
subject.visit "/"
|
1060
|
+
echoed_cookie.should == "abc"
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
it "uses a custom cookie" do
|
1064
|
+
subject.browser.set_cookie @cookie
|
1065
|
+
subject.visit "/"
|
1066
|
+
echoed_cookie.should == "abc"
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
it "clears cookies" do
|
1070
|
+
subject.browser.clear_cookies
|
1071
|
+
subject.visit "/"
|
1072
|
+
echoed_cookie.should == ""
|
1073
|
+
end
|
1074
|
+
|
1075
|
+
it "allows enumeration of cookies" do
|
1076
|
+
cookies = subject.browser.get_cookies
|
1077
|
+
|
1078
|
+
cookies.size.should == 1
|
1079
|
+
|
1080
|
+
cookie = Hash[cookies[0].split(/\s*;\s*/).map { |x| x.split("=", 2) }]
|
1081
|
+
cookie["cookie"].should == "abc"
|
1082
|
+
cookie["domain"].should include "127.0.0.1"
|
1083
|
+
cookie["path"].should == "/"
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
it "allows reading access to cookies using a nice syntax" do
|
1087
|
+
subject.cookies["cookie"].should == "abc"
|
1088
|
+
end
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
context "with socket debugger" do
|
1092
|
+
let(:socket_debugger_class){ Capybara::Driver::Webkit::SocketDebugger }
|
1093
|
+
let(:browser_with_debugger){
|
1094
|
+
connection = Capybara::Driver::Webkit::Connection.new(:socket_class => socket_debugger_class)
|
1095
|
+
Capybara::Driver::Webkit::Browser.new(connection)
|
1096
|
+
}
|
1097
|
+
let(:driver_with_debugger){ Capybara::Driver::Webkit.new(@app, :browser => browser_with_debugger) }
|
1098
|
+
|
1099
|
+
before(:all) do
|
1100
|
+
@app = lambda do |env|
|
1101
|
+
body = <<-HTML
|
1102
|
+
<html><body>
|
1103
|
+
<div id="parent">
|
1104
|
+
<div class="find">Expected</div>
|
1105
|
+
</div>
|
1106
|
+
<div class="find">Unexpected</div>
|
1107
|
+
</body></html>
|
1108
|
+
HTML
|
1109
|
+
[200,
|
1110
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
1111
|
+
[body]]
|
1112
|
+
end
|
1113
|
+
end
|
1114
|
+
|
1115
|
+
it "prints out sent content" do
|
1116
|
+
socket_debugger_class.any_instance.stub(:received){|content| content }
|
1117
|
+
sent_content = ['Find', 1, 17, "//*[@id='parent']"]
|
1118
|
+
socket_debugger_class.any_instance.should_receive(:sent).exactly(sent_content.size).times
|
1119
|
+
driver_with_debugger.find("//*[@id='parent']")
|
1120
|
+
end
|
1121
|
+
|
1122
|
+
it "prints out received content" do
|
1123
|
+
socket_debugger_class.any_instance.stub(:sent)
|
1124
|
+
socket_debugger_class.any_instance.should_receive(:received).at_least(:once).and_return("ok")
|
1125
|
+
driver_with_debugger.find("//*[@id='parent']")
|
1126
|
+
end
|
1127
|
+
end
|
1128
|
+
|
1129
|
+
context "remove node app" do
|
1130
|
+
before(:all) do
|
1131
|
+
@app = lambda do |env|
|
1132
|
+
body = <<-HTML
|
1133
|
+
<html>
|
1134
|
+
<div id="parent">
|
1135
|
+
<p id="removeMe">Hello</p>
|
1136
|
+
</div>
|
1137
|
+
</html>
|
1138
|
+
HTML
|
1139
|
+
[200,
|
1140
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
1141
|
+
[body]]
|
1142
|
+
end
|
1143
|
+
end
|
1144
|
+
|
1145
|
+
before { set_automatic_reload false }
|
1146
|
+
after { set_automatic_reload true }
|
1147
|
+
|
1148
|
+
def set_automatic_reload(value)
|
1149
|
+
if Capybara.respond_to?(:automatic_reload)
|
1150
|
+
Capybara.automatic_reload = value
|
1151
|
+
end
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
it "allows removed nodes when reloading is disabled" do
|
1155
|
+
node = subject.find("//p[@id='removeMe']").first
|
1156
|
+
subject.evaluate_script("document.getElementById('parent').innerHTML = 'Magic'")
|
1157
|
+
node.text.should == 'Hello'
|
1158
|
+
end
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
context "app with a lot of HTML tags" do
|
1162
|
+
before(:all) do
|
1163
|
+
@app = lambda do |env|
|
1164
|
+
body = <<-HTML
|
1165
|
+
<html>
|
1166
|
+
<head>
|
1167
|
+
<title>My eBook</title>
|
1168
|
+
<meta class="charset" name="charset" value="utf-8" />
|
1169
|
+
<meta class="author" name="author" value="Firstname Lastname" />
|
1170
|
+
</head>
|
1171
|
+
<body>
|
1172
|
+
<div id="toc">
|
1173
|
+
<table>
|
1174
|
+
<thead id="head">
|
1175
|
+
<tr><td class="td1">Chapter</td><td>Page</td></tr>
|
1176
|
+
</thead>
|
1177
|
+
<tbody>
|
1178
|
+
<tr><td>Intro</td><td>1</td></tr>
|
1179
|
+
<tr><td>Chapter 1</td><td class="td2">1</td></tr>
|
1180
|
+
<tr><td>Chapter 2</td><td>1</td></tr>
|
1181
|
+
</tbody>
|
1182
|
+
</table>
|
1183
|
+
</div>
|
1184
|
+
|
1185
|
+
<h1 class="h1">My first book</h1>
|
1186
|
+
<p class="p1">Written by me</p>
|
1187
|
+
<div id="intro" class="intro">
|
1188
|
+
<p>Let's try out XPath</p>
|
1189
|
+
<p class="p2">in capybara-webkit</p>
|
1190
|
+
</div>
|
1191
|
+
|
1192
|
+
<h2 class="chapter1">Chapter 1</h2>
|
1193
|
+
<p>This paragraph is fascinating.</p>
|
1194
|
+
<p class="p3">But not as much as this one.</p>
|
1195
|
+
|
1196
|
+
<h2 class="chapter2">Chapter 2</h2>
|
1197
|
+
<p>Let's try if we can select this</p>
|
1198
|
+
</body>
|
1199
|
+
</html>
|
1200
|
+
HTML
|
1201
|
+
[200,
|
1202
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
1203
|
+
[body]]
|
1204
|
+
end
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
it "builds up node paths correctly" do
|
1208
|
+
cases = {
|
1209
|
+
"//*[contains(@class, 'author')]" => "/html/head/meta[2]",
|
1210
|
+
"//*[contains(@class, 'td1')]" => "/html/body/div[@id='toc']/table/thead[@id='head']/tr/td[1]",
|
1211
|
+
"//*[contains(@class, 'td2')]" => "/html/body/div[@id='toc']/table/tbody/tr[2]/td[2]",
|
1212
|
+
"//h1" => "/html/body/h1",
|
1213
|
+
"//*[contains(@class, 'chapter2')]" => "/html/body/h2[2]",
|
1214
|
+
"//*[contains(@class, 'p1')]" => "/html/body/p[1]",
|
1215
|
+
"//*[contains(@class, 'p2')]" => "/html/body/div[@id='intro']/p[2]",
|
1216
|
+
"//*[contains(@class, 'p3')]" => "/html/body/p[3]",
|
1217
|
+
}
|
1218
|
+
|
1219
|
+
cases.each do |xpath, path|
|
1220
|
+
nodes = subject.find(xpath)
|
1221
|
+
nodes.size.should == 1
|
1222
|
+
nodes[0].path.should == path
|
1223
|
+
end
|
1224
|
+
end
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
context "css overflow app" do
|
1228
|
+
before(:all) do
|
1229
|
+
@app = lambda do |env|
|
1230
|
+
body = <<-HTML
|
1231
|
+
<html>
|
1232
|
+
<head>
|
1233
|
+
<style type="text/css">
|
1234
|
+
#overflow { overflow: hidden }
|
1235
|
+
</style>
|
1236
|
+
</head>
|
1237
|
+
<body>
|
1238
|
+
<div id="overflow">Overflow</div>
|
1239
|
+
</body>
|
1240
|
+
</html>
|
1241
|
+
HTML
|
1242
|
+
[200,
|
1243
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
1244
|
+
[body]]
|
1245
|
+
end
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
it "handles overflow hidden" do
|
1249
|
+
subject.find("//div[@id='overflow']").first.text.should == "Overflow"
|
1250
|
+
end
|
1251
|
+
end
|
1252
|
+
|
1253
|
+
context "javascript redirect app" do
|
1254
|
+
before(:all) do
|
1255
|
+
@app = lambda do |env|
|
1256
|
+
if env['PATH_INFO'] == '/redirect'
|
1257
|
+
body = <<-HTML
|
1258
|
+
<html>
|
1259
|
+
<script type="text/javascript">
|
1260
|
+
window.location = "/next";
|
1261
|
+
</script>
|
1262
|
+
</html>
|
1263
|
+
HTML
|
1264
|
+
else
|
1265
|
+
body = "<html><p>finished</p></html>"
|
1266
|
+
end
|
1267
|
+
[200,
|
1268
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
1269
|
+
[body]]
|
1270
|
+
end
|
1271
|
+
end
|
1272
|
+
|
1273
|
+
it "loads a page without error" do
|
1274
|
+
10.times do
|
1275
|
+
subject.visit("/redirect")
|
1276
|
+
subject.find("//p").first.text.should == "finished"
|
1277
|
+
end
|
1278
|
+
end
|
1279
|
+
end
|
1280
|
+
|
1281
|
+
context "localStorage works" do
|
1282
|
+
before(:all) do
|
1283
|
+
@app = lambda do |env|
|
1284
|
+
body = <<-HTML
|
1285
|
+
<html>
|
1286
|
+
<body>
|
1287
|
+
<span id='output'></span>
|
1288
|
+
<script type="text/javascript">
|
1289
|
+
if (typeof localStorage !== "undefined") {
|
1290
|
+
if (!localStorage.refreshCounter) {
|
1291
|
+
localStorage.refreshCounter = 0;
|
1292
|
+
}
|
1293
|
+
if (localStorage.refreshCounter++ > 0) {
|
1294
|
+
document.getElementById("output").innerHTML = "localStorage is enabled";
|
1295
|
+
}
|
1296
|
+
}
|
1297
|
+
</script>
|
1298
|
+
</body>
|
1299
|
+
</html>
|
1300
|
+
HTML
|
1301
|
+
[200,
|
1302
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
1303
|
+
[body]]
|
1304
|
+
end
|
1305
|
+
end
|
1306
|
+
|
1307
|
+
it "displays the message on subsequent page loads" do
|
1308
|
+
subject.find("//span[contains(.,'localStorage is enabled')]").should be_empty
|
1309
|
+
subject.visit "/"
|
1310
|
+
subject.find("//span[contains(.,'localStorage is enabled')]").should_not be_empty
|
1311
|
+
end
|
1312
|
+
end
|
1313
|
+
|
1314
|
+
context "app with a lot of HTML tags" do
|
1315
|
+
before(:all) do
|
1316
|
+
@app = lambda do |env|
|
1317
|
+
body = <<-HTML
|
1318
|
+
<html>
|
1319
|
+
<head>
|
1320
|
+
<title>My eBook</title>
|
1321
|
+
<meta class="charset" name="charset" value="utf-8" />
|
1322
|
+
<meta class="author" name="author" value="Firstname Lastname" />
|
1323
|
+
</head>
|
1324
|
+
<body>
|
1325
|
+
<div id="toc">
|
1326
|
+
<table>
|
1327
|
+
<thead id="head">
|
1328
|
+
<tr><td class="td1">Chapter</td><td>Page</td></tr>
|
1329
|
+
</thead>
|
1330
|
+
<tbody>
|
1331
|
+
<tr><td>Intro</td><td>1</td></tr>
|
1332
|
+
<tr><td>Chapter 1</td><td class="td2">1</td></tr>
|
1333
|
+
<tr><td>Chapter 2</td><td>1</td></tr>
|
1334
|
+
</tbody>
|
1335
|
+
</table>
|
1336
|
+
</div>
|
1337
|
+
|
1338
|
+
<h1 class="h1">My first book</h1>
|
1339
|
+
<p class="p1">Written by me</p>
|
1340
|
+
<div id="intro" class="intro">
|
1341
|
+
<p>Let's try out XPath</p>
|
1342
|
+
<p class="p2">in capybara-webkit</p>
|
1343
|
+
</div>
|
1344
|
+
|
1345
|
+
<h2 class="chapter1">Chapter 1</h2>
|
1346
|
+
<p>This paragraph is fascinating.</p>
|
1347
|
+
<p class="p3">But not as much as this one.</p>
|
1348
|
+
|
1349
|
+
<h2 class="chapter2">Chapter 2</h2>
|
1350
|
+
<p>Let's try if we can select this</p>
|
1351
|
+
</body>
|
1352
|
+
</html>
|
1353
|
+
HTML
|
1354
|
+
[200,
|
1355
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
1356
|
+
[body]]
|
1357
|
+
end
|
1358
|
+
end
|
1359
|
+
|
1360
|
+
it "builds up node paths correctly" do
|
1361
|
+
cases = {
|
1362
|
+
"//*[contains(@class, 'author')]" => "/html/head/meta[2]",
|
1363
|
+
"//*[contains(@class, 'td1')]" => "/html/body/div[@id='toc']/table/thead[@id='head']/tr/td[1]",
|
1364
|
+
"//*[contains(@class, 'td2')]" => "/html/body/div[@id='toc']/table/tbody/tr[2]/td[2]",
|
1365
|
+
"//h1" => "/html/body/h1",
|
1366
|
+
"//*[contains(@class, 'chapter2')]" => "/html/body/h2[2]",
|
1367
|
+
"//*[contains(@class, 'p1')]" => "/html/body/p[1]",
|
1368
|
+
"//*[contains(@class, 'p2')]" => "/html/body/div[@id='intro']/p[2]",
|
1369
|
+
"//*[contains(@class, 'p3')]" => "/html/body/p[3]",
|
1370
|
+
}
|
1371
|
+
|
1372
|
+
cases.each do |xpath, path|
|
1373
|
+
nodes = subject.find(xpath)
|
1374
|
+
nodes.size.should == 1
|
1375
|
+
nodes[0].path.should == path
|
1376
|
+
end
|
1377
|
+
end
|
1378
|
+
end
|
1379
|
+
|
1380
|
+
context "form app with server-side handler" do
|
1381
|
+
before(:all) do
|
1382
|
+
@app = lambda do |env|
|
1383
|
+
if env["REQUEST_METHOD"] == "POST"
|
1384
|
+
body = "<html><body><p>Congrats!</p></body></html>"
|
1385
|
+
else
|
1386
|
+
body = <<-HTML
|
1387
|
+
<html>
|
1388
|
+
<head><title>Form</title>
|
1389
|
+
<body>
|
1390
|
+
<form action="/" method="POST">
|
1391
|
+
<input type="hidden" name="abc" value="123" />
|
1392
|
+
<input type="submit" value="Submit" />
|
1393
|
+
</form>
|
1394
|
+
</body>
|
1395
|
+
</html>
|
1396
|
+
HTML
|
1397
|
+
end
|
1398
|
+
[200,
|
1399
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
1400
|
+
[body]]
|
1401
|
+
end
|
1402
|
+
end
|
1403
|
+
|
1404
|
+
it "submits a form without clicking" do
|
1405
|
+
subject.find("//form")[0].submit
|
1406
|
+
subject.body.should include "Congrats"
|
1407
|
+
end
|
1408
|
+
end
|
1409
|
+
|
1410
|
+
def key_app_body(event)
|
1411
|
+
body = <<-HTML
|
1412
|
+
<html>
|
1413
|
+
<head><title>Form</title></head>
|
1414
|
+
<body>
|
1415
|
+
<div id="charcode_value"></div>
|
1416
|
+
<div id="keycode_value"></div>
|
1417
|
+
<div id="which_value"></div>
|
1418
|
+
<input type="text" id="charcode" name="charcode" on#{event}="setcharcode" />
|
1419
|
+
<script type="text/javascript">
|
1420
|
+
var element = document.getElementById("charcode")
|
1421
|
+
element.addEventListener("#{event}", setcharcode);
|
1422
|
+
function setcharcode(event) {
|
1423
|
+
var element = document.getElementById("charcode_value");
|
1424
|
+
element.innerHTML = event.charCode;
|
1425
|
+
element = document.getElementById("keycode_value");
|
1426
|
+
element.innerHTML = event.keyCode;
|
1427
|
+
element = document.getElementById("which_value");
|
1428
|
+
element.innerHTML = event.which;
|
1429
|
+
}
|
1430
|
+
</script>
|
1431
|
+
</body>
|
1432
|
+
</html>
|
1433
|
+
HTML
|
1434
|
+
body
|
1435
|
+
end
|
1436
|
+
|
1437
|
+
def charCode_for(character)
|
1438
|
+
subject.find("//input")[0].set(character)
|
1439
|
+
subject.find("//div[@id='charcode_value']")[0].text
|
1440
|
+
end
|
1441
|
+
|
1442
|
+
def keyCode_for(character)
|
1443
|
+
subject.find("//input")[0].set(character)
|
1444
|
+
subject.find("//div[@id='keycode_value']")[0].text
|
1445
|
+
end
|
1446
|
+
|
1447
|
+
def which_for(character)
|
1448
|
+
subject.find("//input")[0].set(character)
|
1449
|
+
subject.find("//div[@id='which_value']")[0].text
|
1450
|
+
end
|
1451
|
+
|
1452
|
+
context "keypress app" do
|
1453
|
+
before(:all) do
|
1454
|
+
@app = lambda do |env|
|
1455
|
+
body = key_app_body("keypress")
|
1456
|
+
[200, { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s }, [body]]
|
1457
|
+
end
|
1458
|
+
end
|
1459
|
+
|
1460
|
+
it "returns the charCode for the keypressed" do
|
1461
|
+
charCode_for("a").should == "97"
|
1462
|
+
charCode_for("A").should == "65"
|
1463
|
+
charCode_for("\r").should == "13"
|
1464
|
+
charCode_for(",").should == "44"
|
1465
|
+
charCode_for("<").should == "60"
|
1466
|
+
charCode_for("0").should == "48"
|
1467
|
+
end
|
1468
|
+
|
1469
|
+
it "returns the keyCode for the keypressed" do
|
1470
|
+
keyCode_for("a").should == "97"
|
1471
|
+
keyCode_for("A").should == "65"
|
1472
|
+
keyCode_for("\r").should == "13"
|
1473
|
+
keyCode_for(",").should == "44"
|
1474
|
+
keyCode_for("<").should == "60"
|
1475
|
+
keyCode_for("0").should == "48"
|
1476
|
+
end
|
1477
|
+
|
1478
|
+
it "returns the which for the keypressed" do
|
1479
|
+
which_for("a").should == "97"
|
1480
|
+
which_for("A").should == "65"
|
1481
|
+
which_for("\r").should == "13"
|
1482
|
+
which_for(",").should == "44"
|
1483
|
+
which_for("<").should == "60"
|
1484
|
+
which_for("0").should == "48"
|
1485
|
+
end
|
1486
|
+
end
|
1487
|
+
|
1488
|
+
shared_examples "a keyupdown app" do
|
1489
|
+
it "returns a 0 charCode for the event" do
|
1490
|
+
charCode_for("a").should == "0"
|
1491
|
+
charCode_for("A").should == "0"
|
1492
|
+
charCode_for("\r").should == "0"
|
1493
|
+
charCode_for(",").should == "0"
|
1494
|
+
charCode_for("<").should == "0"
|
1495
|
+
charCode_for("0").should == "0"
|
1496
|
+
end
|
1497
|
+
|
1498
|
+
it "returns the keyCode for the event" do
|
1499
|
+
keyCode_for("a").should == "65"
|
1500
|
+
keyCode_for("A").should == "65"
|
1501
|
+
keyCode_for("\r").should == "13"
|
1502
|
+
keyCode_for(",").should == "188"
|
1503
|
+
keyCode_for("<").should == "188"
|
1504
|
+
keyCode_for("0").should == "48"
|
1505
|
+
end
|
1506
|
+
|
1507
|
+
it "returns the which for the event" do
|
1508
|
+
which_for("a").should == "65"
|
1509
|
+
which_for("A").should == "65"
|
1510
|
+
which_for("\r").should == "13"
|
1511
|
+
which_for(",").should == "188"
|
1512
|
+
which_for("<").should == "188"
|
1513
|
+
which_for("0").should == "48"
|
1514
|
+
end
|
1515
|
+
end
|
1516
|
+
|
1517
|
+
context "keydown app" do
|
1518
|
+
before(:all) do
|
1519
|
+
@app = lambda do |env|
|
1520
|
+
body = key_app_body("keydown")
|
1521
|
+
[200, { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s }, [body]]
|
1522
|
+
end
|
1523
|
+
end
|
1524
|
+
it_behaves_like "a keyupdown app"
|
1525
|
+
end
|
1526
|
+
|
1527
|
+
context "keyup app" do
|
1528
|
+
before(:all) do
|
1529
|
+
@app = lambda do |env|
|
1530
|
+
body = key_app_body("keyup")
|
1531
|
+
[200, { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s }, [body]]
|
1532
|
+
end
|
1533
|
+
end
|
1534
|
+
|
1535
|
+
it_behaves_like "a keyupdown app"
|
1536
|
+
end
|
1537
|
+
|
1538
|
+
context "null byte app" do
|
1539
|
+
before(:all) do
|
1540
|
+
@app = lambda do |env|
|
1541
|
+
body = "Hello\0World"
|
1542
|
+
[200,
|
1543
|
+
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
|
1544
|
+
[body]]
|
1545
|
+
end
|
1546
|
+
end
|
1547
|
+
|
1548
|
+
it "should include all the bytes in the source" do
|
1549
|
+
subject.source.should == "Hello\0World"
|
1550
|
+
end
|
1551
|
+
end
|
1552
|
+
end
|