capybara-webkit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. data/.gitignore +13 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +6 -0
  4. data/Gemfile.lock +55 -0
  5. data/LICENSE +19 -0
  6. data/README.md +37 -0
  7. data/Rakefile +76 -0
  8. data/capybara-webkit.gemspec +15 -0
  9. data/extconf.rb +2 -0
  10. data/lib/capybara-webkit.rb +7 -0
  11. data/lib/capybara/driver/webkit.rb +84 -0
  12. data/lib/capybara/driver/webkit/browser.rb +86 -0
  13. data/lib/capybara/driver/webkit/node.rb +87 -0
  14. data/spec/driver_spec.rb +465 -0
  15. data/spec/integration/driver_spec.rb +21 -0
  16. data/spec/integration/session_spec.rb +12 -0
  17. data/spec/spec_helper.rb +22 -0
  18. data/spec/support/socket_debugger.rb +42 -0
  19. data/src/Command.cpp +15 -0
  20. data/src/Command.h +28 -0
  21. data/src/Connection.cpp +130 -0
  22. data/src/Connection.h +36 -0
  23. data/src/Evaluate.cpp +84 -0
  24. data/src/Evaluate.h +22 -0
  25. data/src/Execute.cpp +17 -0
  26. data/src/Execute.h +12 -0
  27. data/src/Find.cpp +20 -0
  28. data/src/Find.h +13 -0
  29. data/src/JavascriptInvocation.cpp +14 -0
  30. data/src/JavascriptInvocation.h +19 -0
  31. data/src/Node.cpp +14 -0
  32. data/src/Node.h +13 -0
  33. data/src/Reset.cpp +16 -0
  34. data/src/Reset.h +12 -0
  35. data/src/Server.cpp +21 -0
  36. data/src/Server.h +20 -0
  37. data/src/Source.cpp +14 -0
  38. data/src/Source.h +12 -0
  39. data/src/Url.cpp +14 -0
  40. data/src/Url.h +12 -0
  41. data/src/Visit.cpp +20 -0
  42. data/src/Visit.h +16 -0
  43. data/src/WebPage.cpp +81 -0
  44. data/src/WebPage.h +29 -0
  45. data/src/capybara.js +98 -0
  46. data/src/find_command.h +13 -0
  47. data/src/main.cpp +20 -0
  48. data/src/webkit_server.pro +9 -0
  49. data/src/webkit_server.qrc +5 -0
  50. data/templates/Command.cpp +10 -0
  51. data/templates/Command.h +12 -0
  52. data/webkit_server.pro +4 -0
  53. metadata +140 -0
@@ -0,0 +1,465 @@
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 "hello app" do
10
+ before(:all) do
11
+ @app = lambda do |env|
12
+ body = <<-HTML
13
+ <html>
14
+ <head>
15
+ <style type="text/css">
16
+ #display_none { display: none }
17
+ </style>
18
+ </head>
19
+ <body>
20
+ <div id="display_none">
21
+ <div id="invisible">Can't see me</div>
22
+ </div>
23
+ <script type="text/javascript">
24
+ document.write("<p id='greeting'>he" + "llo</p>");
25
+ </script>
26
+ </body>
27
+ </html>
28
+ HTML
29
+ [200,
30
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
31
+ [body]]
32
+ end
33
+ end
34
+
35
+ it "finds content after loading a URL" do
36
+ subject.find("//*[contains(., 'hello')]").should_not be_empty
37
+ end
38
+
39
+ it "has an empty page after reseting" do
40
+ subject.reset!
41
+ subject.find("//*[contains(., 'hello')]").should be_empty
42
+ end
43
+
44
+ it "raises an error for an invalid xpath query" do
45
+ expect { subject.find("totally invalid salad") }.
46
+ to raise_error(Capybara::Driver::Webkit::WebkitError, /xpath/i)
47
+ end
48
+
49
+ it "returns an attribute's value" do
50
+ subject.find("//p").first["id"].should == "greeting"
51
+ end
52
+
53
+ it "parses xpath with quotes" do
54
+ subject.find('//*[contains(., "hello")]').should_not be_empty
55
+ end
56
+
57
+ it "returns a node's text" do
58
+ subject.find("//p").first.text.should == "hello"
59
+ end
60
+
61
+ it "returns the current URL" do
62
+ port = subject.instance_variable_get("@rack_server").port
63
+ subject.current_url.should == "http://127.0.0.1:#{port}/hello/world?success=true"
64
+ end
65
+
66
+ it "returns the source code for the page" do
67
+ subject.source.should =~ %r{<html>.*greeting.*}m
68
+ end
69
+
70
+ it "aliases body as source" do
71
+ subject.body.should == subject.source
72
+ end
73
+
74
+ it "evaluates Javascript and returns a string" do
75
+ result = subject.evaluate_script(%<document.getElementById('greeting').innerText>)
76
+ result.should == "hello"
77
+ end
78
+
79
+ it "evaluates Javascript and returns an array" do
80
+ result = subject.evaluate_script(%<["hello", "world"]>)
81
+ result.should == %w(hello world)
82
+ end
83
+
84
+ it "evaluates Javascript and returns an int" do
85
+ result = subject.evaluate_script(%<123>)
86
+ result.should == 123
87
+ end
88
+
89
+ it "evaluates Javascript and returns a float" do
90
+ result = subject.evaluate_script(%<1.5>)
91
+ result.should == 1.5
92
+ end
93
+
94
+ it "evaluates Javascript and returns null" do
95
+ result = subject.evaluate_script(%<(function () {})()>)
96
+ result.should == nil
97
+ end
98
+
99
+ it "evaluates Javascript and returns an object" do
100
+ result = subject.evaluate_script(%<({ 'one' : 1 })>)
101
+ result.should == { 'one' => 1 }
102
+ end
103
+
104
+ it "evaluates Javascript and returns true" do
105
+ result = subject.evaluate_script(%<true>)
106
+ result.should === true
107
+ end
108
+
109
+ it "evaluates Javascript and returns false" do
110
+ result = subject.evaluate_script(%<false>)
111
+ result.should === false
112
+ end
113
+
114
+ it "evaluates Javascript and returns an escaped string" do
115
+ result = subject.evaluate_script(%<'"'>)
116
+ result.should === "\""
117
+ end
118
+
119
+ it "evaluates Javascript with multiple lines" do
120
+ result = subject.evaluate_script("[1,\n2]")
121
+ result.should == [1, 2]
122
+ end
123
+
124
+ it "executes Javascript" do
125
+ subject.execute_script(%<document.getElementById('greeting').innerHTML = 'yo'>)
126
+ subject.find("//p[contains(., 'yo')]").should_not be_empty
127
+ end
128
+
129
+ it "raises an error for failing Javascript" do
130
+ expect { subject.execute_script(%<invalid salad>) }.
131
+ to raise_error(Capybara::Driver::Webkit::WebkitError)
132
+ end
133
+
134
+ it "returns a node's tag name" do
135
+ subject.find("//p").first.tag_name.should == "p"
136
+ end
137
+
138
+ it "finds visible elements" do
139
+ subject.find("//p").first.should be_visible
140
+ subject.find("//*[@id='invisible']").first.should_not be_visible
141
+ end
142
+ end
143
+
144
+ context "form app" do
145
+ before(:all) do
146
+ @app = lambda do |env|
147
+ body = <<-HTML
148
+ <html><body>
149
+ <form action="/" method="GET">
150
+ <input type="text" name="foo" value="bar"/>
151
+ <input type="checkbox" name="checkedbox" value="1" checked="checked"/>
152
+ <input type="checkbox" name="uncheckedbox" value="2"/>
153
+ <select name="animal">
154
+ <option id="select-option-monkey">Monkey</option>
155
+ <option id="select-option-capybara" selected="selected">Capybara</option>
156
+ </select>
157
+ <select name="toppings" multiple="multiple">
158
+ <optgroup label="Mediocre Toppings">
159
+ <option selected="selected" id="topping-apple">Apple</option>
160
+ <option selected="selected" id="topping-banana">Banana</option>
161
+ </optgroup>
162
+ <optgroup label="Best Toppings">
163
+ <option selected="selected" id="topping-cherry">Cherry</option>
164
+ </optgroup>
165
+ </select>
166
+ <textarea id="only-textarea">what a wonderful area for text</textarea>
167
+ <input type="radio" id="only-radio" value="1"/>
168
+ </form>
169
+ </body></html>
170
+ HTML
171
+ [200,
172
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
173
+ [body]]
174
+ end
175
+ end
176
+
177
+ it "returns a textarea's value" do
178
+ subject.find("//textarea").first.value.should == "what a wonderful area for text"
179
+ end
180
+
181
+ it "returns a text input's value" do
182
+ subject.find("//input").first.value.should == "bar"
183
+ end
184
+
185
+ it "returns a select's value" do
186
+ subject.find("//select").first.value.should == "Capybara"
187
+ end
188
+
189
+ it "sets an input's value" do
190
+ input = subject.find("//input").first
191
+ input.set("newvalue")
192
+ input.value.should == "newvalue"
193
+ end
194
+
195
+ it "sets a select's value" do
196
+ select = subject.find("//select").first
197
+ select.set("Monkey")
198
+ select.value.should == "Monkey"
199
+ end
200
+
201
+ it "sets a textarea's value" do
202
+ textarea = subject.find("//textarea").first
203
+ textarea.set("newvalue")
204
+ textarea.value.should == "newvalue"
205
+ end
206
+
207
+ let(:monkey_option) { subject.find("//option[@id='select-option-monkey']").first }
208
+ let(:capybara_option) { subject.find("//option[@id='select-option-capybara']").first }
209
+ let(:animal_select) { subject.find("//select[@name='animal']").first }
210
+ let(:apple_option) { subject.find("//option[@id='topping-apple']").first }
211
+ let(:banana_option) { subject.find("//option[@id='topping-banana']").first }
212
+ let(:cherry_option) { subject.find("//option[@id='topping-cherry']").first }
213
+ let(:toppings_select) { subject.find("//select[@name='toppings']").first }
214
+
215
+ it "selects an option" do
216
+ animal_select.value.should == "Capybara"
217
+ monkey_option.select_option
218
+ animal_select.value.should == "Monkey"
219
+ end
220
+
221
+ it "unselects an option in a multi-select" do
222
+ toppings_select.value.should include("Apple", "Banana", "Cherry")
223
+
224
+ apple_option.unselect_option
225
+ toppings_select.value.should_not include("Apple")
226
+ end
227
+
228
+ it "reselects an option in a multi-select" do
229
+ apple_option.unselect_option
230
+ banana_option.unselect_option
231
+ cherry_option.unselect_option
232
+
233
+ toppings_select.value.should == []
234
+
235
+ apple_option.select_option
236
+ banana_option.select_option
237
+ cherry_option.select_option
238
+
239
+ toppings_select.value.should include("Apple", "Banana", "Cherry")
240
+ end
241
+
242
+ let(:checked_box) { subject.find("//input[@name='checkedbox']").first }
243
+ let(:unchecked_box) { subject.find("//input[@name='uncheckedbox']").first }
244
+
245
+ it "knows a checked box is checked" do
246
+ checked_box['checked'].should be_true
247
+ end
248
+
249
+ it "knows an unchecked box is unchecked" do
250
+ unchecked_box['checked'].should_not be_true
251
+ end
252
+
253
+ it "checks an unchecked box" do
254
+ unchecked_box.set(true)
255
+ unchecked_box['checked'].should be_true
256
+ end
257
+
258
+ it "unchecks a checked box" do
259
+ checked_box.set(false)
260
+ checked_box['checked'].should_not be_true
261
+ end
262
+
263
+ it "leaves a checked box checked" do
264
+ checked_box.set(true)
265
+ checked_box['checked'].should be_true
266
+ end
267
+
268
+ it "leaves an unchecked box unchecked" do
269
+ unchecked_box.set(false)
270
+ unchecked_box['checked'].should_not be_true
271
+ end
272
+ end
273
+
274
+ context "form events app" do
275
+ before(:all) do
276
+ @app = lambda do |env|
277
+ body = <<-HTML
278
+ <html><body>
279
+ <form action="/" method="GET">
280
+ <input class="watch" type="text"/>
281
+ <input class="watch" type="password"/>
282
+ <textarea class="watch"></textarea>
283
+ <input class="watch" type="checkbox"/>
284
+ <input class="watch" type="radio"/>
285
+ </form>
286
+ <ul id="events"></ul>
287
+ <script type="text/javascript">
288
+ var events = document.getElementById("events");
289
+ var recordEvent = function (event) {
290
+ var element = document.createElement("li");
291
+ element.innerHTML = event.type;
292
+ events.appendChild(element);
293
+ };
294
+
295
+ var elements = document.getElementsByClassName("watch");
296
+ for (var i = 0; i < elements.length; i++) {
297
+ var element = elements[i];
298
+ element.addEventListener("focus", recordEvent);
299
+ element.addEventListener("keydown", recordEvent);
300
+ element.addEventListener("keyup", recordEvent);
301
+ element.addEventListener("change", recordEvent);
302
+ element.addEventListener("blur", recordEvent);
303
+ element.addEventListener("click", recordEvent);
304
+ }
305
+ </script>
306
+ </body></html>
307
+ HTML
308
+ [200,
309
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
310
+ [body]]
311
+ end
312
+ end
313
+
314
+ it "triggers text input events" do
315
+ subject.find("//input[@type='text']").first.set("newvalue")
316
+ subject.find("//li").map(&:text).should == %w(focus keydown keyup change blur)
317
+ end
318
+
319
+ it "triggers textarea input events" do
320
+ subject.find("//textarea").first.set("newvalue")
321
+ subject.find("//li").map(&:text).should == %w(focus keydown keyup change blur)
322
+ end
323
+
324
+ it "triggers password input events" do
325
+ subject.find("//input[@type='password']").first.set("newvalue")
326
+ subject.find("//li").map(&:text).should == %w(focus keydown keyup change blur)
327
+ end
328
+
329
+ it "triggers radio input events" do
330
+ subject.find("//input[@type='radio']").first.set(true)
331
+ subject.find("//li").map(&:text).should == %w(click)
332
+ end
333
+
334
+ it "triggers checkbox events" do
335
+ subject.find("//input[@type='checkbox']").first.set(true)
336
+ subject.find("//li").map(&:text).should == %w(click)
337
+ end
338
+ end
339
+
340
+ context "mouse app" do
341
+ before(:all) do
342
+ @app =lambda do |env|
343
+ body = <<-HTML
344
+ <html><body>
345
+ <div id="change">Change me</div>
346
+ <div id="mouseup">Push me</div>
347
+ <div id="mousedown">Release me</div>
348
+ <script type="text/javascript">
349
+ document.getElementById("change").
350
+ addEventListener("change", function () {
351
+ this.className = "triggered";
352
+ });
353
+ document.getElementById("mouseup").
354
+ addEventListener("mouseup", function () {
355
+ this.className = "triggered";
356
+ });
357
+ document.getElementById("mousedown").
358
+ addEventListener("mousedown", function () {
359
+ this.className = "triggered";
360
+ });
361
+ </script>
362
+ <a href="/next">Next</a>
363
+ </body></html>
364
+ HTML
365
+ [200,
366
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
367
+ [body]]
368
+ end
369
+ end
370
+
371
+ it "clicks an element" do
372
+ subject.find("//a").first.click
373
+ subject.current_url =~ %r{/next$}
374
+ end
375
+
376
+ it "fires a mouse event" do
377
+ subject.find("//*[@id='mouseup']").first.trigger("mouseup")
378
+ subject.find("//*[@class='triggered']").should_not be_empty
379
+ end
380
+
381
+ it "fires a non-mouse event" do
382
+ subject.find("//*[@id='change']").first.trigger("change")
383
+ subject.find("//*[@class='triggered']").should_not be_empty
384
+ end
385
+
386
+ it "fires drag events" do
387
+ draggable = subject.find("//*[@id='mousedown']").first
388
+ container = subject.find("//*[@id='mouseup']").first
389
+
390
+ draggable.drag_to(container)
391
+
392
+ subject.find("//*[@class='triggered']").size.should == 2
393
+ end
394
+ end
395
+
396
+ context "nesting app" do
397
+ before(:all) do
398
+ @app = lambda do |env|
399
+ body = <<-HTML
400
+ <html><body>
401
+ <div id="parent">
402
+ <div class="find">Expected</div>
403
+ </div>
404
+ <div class="find">Unexpected</div>
405
+ </body></html>
406
+ HTML
407
+ [200,
408
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
409
+ [body]]
410
+ end
411
+ end
412
+
413
+ it "evaluates nested xpath expressions" do
414
+ parent = subject.find("//*[@id='parent']").first
415
+ parent.find("./*[@class='find']").map(&:text).should == %w(Expected)
416
+ end
417
+ end
418
+
419
+ context "slow app" do
420
+ before(:all) do
421
+ @app = lambda do |env|
422
+ body = <<-HTML
423
+ <html><body>
424
+ <form action="/next"><input type="submit"/></form>
425
+ <p>#{env['PATH_INFO']}</p>
426
+ </body></html>
427
+ HTML
428
+ sleep(0.5)
429
+ [200,
430
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
431
+ [body]]
432
+ end
433
+ end
434
+
435
+ it "waits for a request to load" do
436
+ subject.find("//input").first.click
437
+ subject.find("//p").first.text.should == "/next"
438
+ end
439
+ end
440
+
441
+ context "popup app" do
442
+ before(:all) do
443
+ @app = lambda do |env|
444
+ body = <<-HTML
445
+ <html><body>
446
+ <script type="text/javascript">
447
+ alert("alert");
448
+ confirm("confirm");
449
+ prompt("prompt");
450
+ </script>
451
+ <p>success</p>
452
+ </body></html>
453
+ HTML
454
+ sleep(0.5)
455
+ [200,
456
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
457
+ [body]]
458
+ end
459
+ end
460
+
461
+ it "doesn't crash from alerts" do
462
+ subject.find("//p").first.text.should == "success"
463
+ end
464
+ end
465
+ end