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
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ *.swp
2
+ bin
3
+ *.swo
4
+ *~
5
+ *.o
6
+ *.moc
7
+ Makefile*
8
+ qrc_*
9
+ *.xcodeproj
10
+ *.app
11
+ moc_*.cpp
12
+ .bundle
13
+ pkg
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+ gem "rake"
3
+ gem "rspec", :require => false
4
+ gem "capybara"
5
+ gem "sinatra", :require => false
6
+
data/Gemfile.lock ADDED
@@ -0,0 +1,55 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ capybara (0.4.1.2)
5
+ celerity (>= 0.7.9)
6
+ culerity (>= 0.2.4)
7
+ mime-types (>= 1.16)
8
+ nokogiri (>= 1.3.3)
9
+ rack (>= 1.0.0)
10
+ rack-test (>= 0.5.4)
11
+ selenium-webdriver (>= 0.0.27)
12
+ xpath (~> 0.1.3)
13
+ celerity (0.8.8)
14
+ childprocess (0.1.7)
15
+ ffi (~> 0.6.3)
16
+ culerity (0.2.15)
17
+ diff-lcs (1.1.2)
18
+ ffi (0.6.3)
19
+ rake (>= 0.8.7)
20
+ json_pure (1.5.1)
21
+ mime-types (1.16)
22
+ nokogiri (1.4.4)
23
+ rack (1.2.1)
24
+ rack-test (0.5.7)
25
+ rack (>= 1.0)
26
+ rake (0.8.7)
27
+ rspec (2.5.0)
28
+ rspec-core (~> 2.5.0)
29
+ rspec-expectations (~> 2.5.0)
30
+ rspec-mocks (~> 2.5.0)
31
+ rspec-core (2.5.1)
32
+ rspec-expectations (2.5.0)
33
+ diff-lcs (~> 1.1.2)
34
+ rspec-mocks (2.5.0)
35
+ rubyzip (0.9.4)
36
+ selenium-webdriver (0.1.3)
37
+ childprocess (~> 0.1.5)
38
+ ffi (~> 0.6.3)
39
+ json_pure
40
+ rubyzip
41
+ sinatra (1.1.2)
42
+ rack (~> 1.1)
43
+ tilt (~> 1.2)
44
+ tilt (1.2.2)
45
+ xpath (0.1.3)
46
+ nokogiri (~> 1.3)
47
+
48
+ PLATFORMS
49
+ ruby
50
+
51
+ DEPENDENCIES
52
+ capybara
53
+ rake
54
+ rspec
55
+ sinatra
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 thoughtbot, inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ capybara-webkit
2
+ ===============
3
+
4
+ A [capybara](https://github.com/jnicklas/capybara) driver that uses [WebKit](http://webkit.org) via [QtWebKit](http://doc.qt.nokia.com/4.7/qtwebkit.html).
5
+
6
+ Dependencies
7
+ ------------
8
+
9
+ capybara-webkit depends on a WebKit implementation from Qt, a cross-platform development toolkit. You'll need to download Qt to build and install the gem.
10
+
11
+ * [Download Qt](http://qt.nokia.com/downloads/downloads)
12
+
13
+ Usage
14
+ -----
15
+
16
+ Add the capybara-webkit gem to your Gemfile:
17
+
18
+ gem "capybara-webkit"
19
+
20
+ Set your Capybara Javascript driver to webkit:
21
+
22
+ Capybara.javascript_driver = :webkit
23
+
24
+ Tag scenarios with @javascript to run them using a headless WebKit browser.
25
+
26
+ About
27
+ -----
28
+
29
+ The capybara WebKit driver was written by Joe Ferris, Tristan Dunn, and Jason Morrison from [thoughtbot, inc](http://thoughtbot.com/community).
30
+
31
+ The names and logos for thoughtbot are trademarks of thoughtbot, inc.
32
+
33
+ License
34
+ -------
35
+
36
+ capybara-webkit is Copyright © 2011 thoughtbot, inc. It is free software, and may be redistributed under the terms specified in the LICENSE file.
37
+
data/Rakefile ADDED
@@ -0,0 +1,76 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'fileutils'
4
+ require 'rspec/core/rake_task'
5
+ require 'rake/gempackagetask'
6
+
7
+ desc "Generate a Makefile using qmake"
8
+ file 'Makefile' do
9
+ sh("qmake -spec macx-g++")
10
+ end
11
+
12
+ desc "Regenerate dependencies using qmake"
13
+ task :qmake => 'Makefile' do
14
+ sh("make qmake")
15
+ end
16
+
17
+ desc "Build the webkit server"
18
+ task :build => :qmake do
19
+ sh("make")
20
+
21
+ FileUtils.mkdir("bin") unless File.directory?("bin")
22
+
23
+ if File.exist?("src/webkit_server.app")
24
+ FileUtils.cp("src/webkit_server.app/Contents/MacOS/webkit_server", "bin")
25
+ else
26
+ FileUtils.cp("src/webkit_server", "bin")
27
+ end
28
+ end
29
+
30
+ file 'bin/webkit_server' => :build
31
+
32
+ unless ENV["BUILD"]
33
+ RSpec::Core::RakeTask.new do |t|
34
+ t.pattern = "spec/**/*_spec.rb"
35
+ t.rspec_opts = "--format progress"
36
+ end
37
+
38
+ desc "Default: build and run all specs"
39
+ task :default => [:build, :spec]
40
+
41
+ eval("$specification = begin; #{IO.read('capybara-webkit.gemspec')}; end")
42
+ Rake::GemPackageTask.new($specification) do |package|
43
+ package.need_zip = true
44
+ package.need_tar = true
45
+ end
46
+
47
+ desc "Generate a new command called NAME"
48
+ task :generate_command do
49
+ name = ENV['NAME'] or raise "Provide a name with NAME="
50
+
51
+ header = "src/#{name}.h"
52
+ source = "src/#{name}.cpp"
53
+
54
+ %w(h cpp).each do |extension|
55
+ File.open("templates/Command.#{extension}", "r") do |source_file|
56
+ contents = source_file.read
57
+ contents.gsub!("NAME", name)
58
+ File.open("src/#{name}.#{extension}", "w") do |target_file|
59
+ target_file.write(contents)
60
+ end
61
+ end
62
+ end
63
+
64
+ Dir.glob("src/*.pro").each do |project_file_name|
65
+ project = IO.read(project_file_name)
66
+ project.gsub!(/^(HEADERS = .*)/, "\\1 #{name}.h")
67
+ project.gsub!(/^(SOURCES = .*)/, "\\1 #{name}.cpp")
68
+ File.open(project_file_name, "w") { |file| file.write(project) }
69
+ end
70
+
71
+ File.open("src/find_command.h", "a") do |file|
72
+ file.write("CHECK_COMMAND(#{name})")
73
+ end
74
+ end
75
+ end
76
+
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "capybara-webkit"
3
+ s.version = "0.1.0"
4
+ s.authors = ["thoughtbot", "Joe Ferris", "Jason Morrison", "Tristan Dunn"]
5
+ s.email = "support@thoughtbot.com"
6
+ s.files = `git ls-files`.split("\n")
7
+ s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
8
+ s.homepage = "http://github.com/thoughtbot/capybara-webkit"
9
+ s.require_path = "lib"
10
+ s.rubygems_version = "1.3.5"
11
+ s.summary = "Headless Webkit driver for Capybara"
12
+ s.add_runtime_dependency "capybara", "~> 0.4.1"
13
+ s.extensions = "extconf.rb"
14
+ end
15
+
data/extconf.rb ADDED
@@ -0,0 +1,2 @@
1
+ system("rake build BUILD=true")
2
+
@@ -0,0 +1,7 @@
1
+ require "capybara"
2
+ require "capybara/driver/webkit"
3
+
4
+ Capybara.register_driver :webkit do |app|
5
+ Capybara::Driver::Webkit.new(app)
6
+ end
7
+
@@ -0,0 +1,84 @@
1
+ require "capybara"
2
+ require "capybara/driver/webkit/node"
3
+ require "capybara/driver/webkit/browser"
4
+
5
+ class Capybara::Driver::Webkit
6
+ class WebkitError < StandardError
7
+ end
8
+
9
+ attr_reader :browser
10
+
11
+ def initialize(app, options={})
12
+ @app = app
13
+ @options = options
14
+ @rack_server = Capybara::Server.new(@app)
15
+ @rack_server.boot if Capybara.run_server
16
+ @browser = options[:browser] || Browser.new
17
+ end
18
+
19
+ def current_url
20
+ browser.url
21
+ end
22
+
23
+ def visit(path)
24
+ browser.visit(url(path))
25
+ end
26
+
27
+ def find(query)
28
+ browser.find(query).map { |native| Node.new(self, native) }
29
+ end
30
+
31
+ def source
32
+ browser.source
33
+ end
34
+
35
+ def body
36
+ source
37
+ end
38
+
39
+ def execute_script(script)
40
+ browser.execute_script script
41
+ end
42
+
43
+ def evaluate_script(script)
44
+ browser.evaluate_script script
45
+ end
46
+
47
+ def response_headers
48
+ raise Capybara::NotSupportedByDriverError
49
+ end
50
+
51
+ def status_code
52
+ raise Capybara::NotSupportedByDriverError
53
+ end
54
+
55
+ def within_frame(frame_id)
56
+ raise Capybara::NotSupportedByDriverError
57
+ end
58
+
59
+ def within_window(handle)
60
+ raise Capybara::NotSupportedByDriverError
61
+ end
62
+
63
+ def wait?
64
+ false
65
+ end
66
+
67
+ def wait_until(*args)
68
+ end
69
+
70
+ def reset!
71
+ browser.reset!
72
+ end
73
+
74
+ def has_shortcircuit_timeout?
75
+ false
76
+ end
77
+
78
+ private
79
+
80
+ def url(path)
81
+ @rack_server.url(path)
82
+ end
83
+ end
84
+
@@ -0,0 +1,86 @@
1
+ require 'socket'
2
+ require 'capybara/util/timeout'
3
+ require 'json'
4
+
5
+ class Capybara::Driver::Webkit
6
+ class Browser
7
+ def initialize(options = {})
8
+ @socket_class = options[:socket_class] || TCPSocket
9
+ start_server
10
+ connect
11
+ end
12
+
13
+ def visit(url)
14
+ command "Visit", url
15
+ end
16
+
17
+ def find(query)
18
+ command("Find", query).split(",")
19
+ end
20
+
21
+ def reset!
22
+ command("Reset")
23
+ end
24
+
25
+ def source
26
+ command("Source")
27
+ end
28
+
29
+ def url
30
+ command("Url")
31
+ end
32
+
33
+ def command(name, *args)
34
+ @socket.puts name
35
+ @socket.puts args.size
36
+ args.each do |arg|
37
+ @socket.puts arg.to_s.bytesize
38
+ @socket.print arg
39
+ end
40
+ check
41
+ read_response
42
+ end
43
+
44
+ def evaluate_script(script)
45
+ json = command('Evaluate', script)
46
+ JSON.parse("[#{json}]").first
47
+ end
48
+
49
+ def execute_script(script)
50
+ command('Execute', script)
51
+ end
52
+
53
+ private
54
+
55
+ def start_server
56
+ server_path = File.expand_path("../../../../../bin/webkit_server", __FILE__)
57
+ @pid = fork { exec(server_path) }
58
+ at_exit { Process.kill("INT", @pid) }
59
+ end
60
+
61
+ def connect
62
+ Capybara.timeout(5) do
63
+ attempt_connect
64
+ !@socket.nil?
65
+ end
66
+ end
67
+
68
+ def attempt_connect
69
+ @socket = @socket_class.open("localhost", 9200)
70
+ rescue Errno::ECONNREFUSED
71
+ end
72
+
73
+ def check
74
+ result = @socket.gets.strip
75
+
76
+ unless result == 'ok'
77
+ raise WebkitError, read_response
78
+ end
79
+ end
80
+
81
+ def read_response
82
+ response_length = @socket.gets.to_i
83
+ @socket.read(response_length)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,87 @@
1
+ class Capybara::Driver::Webkit
2
+ class Node < Capybara::Driver::Node
3
+ def text
4
+ invoke "text"
5
+ end
6
+
7
+ def [](name)
8
+ value = invoke("attribute", name)
9
+ if name == 'checked'
10
+ value == 'true'
11
+ else
12
+ value
13
+ end
14
+ end
15
+
16
+ def value
17
+ if multiple_select?
18
+ self.find(".//option").select do |option|
19
+ option["selected"] == "selected"
20
+ end.map do |option|
21
+ option.value
22
+ end
23
+ else
24
+ invoke "value"
25
+ end
26
+ end
27
+
28
+ def set(value)
29
+ invoke "set", value
30
+ end
31
+
32
+ def select_option
33
+ invoke "selectOption"
34
+ end
35
+
36
+ def unselect_option
37
+ invoke "unselectOption"
38
+ end
39
+
40
+ def click
41
+ invoke "click"
42
+ end
43
+
44
+ def drag_to(element)
45
+ trigger('mousedown')
46
+ element.trigger('mousemove')
47
+ element.trigger('mouseup')
48
+ end
49
+
50
+ def tag_name
51
+ invoke "tagName"
52
+ end
53
+
54
+ def visible?
55
+ invoke("visible") == "true"
56
+ end
57
+
58
+ def path
59
+ raise NotSupportedByDriverError
60
+ end
61
+
62
+ def trigger(event)
63
+ invoke "trigger", event
64
+ end
65
+
66
+ def find(xpath)
67
+ invoke("findWithin", xpath).split(',').map do |native|
68
+ self.class.new(driver, native)
69
+ end
70
+ end
71
+
72
+ def invoke(name, *args)
73
+ browser.command "Node", name, native, *args
74
+ end
75
+
76
+ def browser
77
+ driver.browser
78
+ end
79
+
80
+ private
81
+
82
+ def multiple_select?
83
+ self.tag_name == "select" && self["multiple"] == "multiple"
84
+ end
85
+ end
86
+ end
87
+