capybara-webkit 0.1.0

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