capybarbecue 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +78 -0
- data/LICENSE.txt +20 -0
- data/NOTES.txt +37 -0
- data/README.rdoc +19 -0
- data/Rakefile +44 -0
- data/lib/capybarbecue.rb +44 -0
- data/lib/capybarbecue/async_call.rb +63 -0
- data/lib/capybarbecue/async_delegate_class.rb +30 -0
- data/lib/capybarbecue/rack_runner.rb +7 -0
- data/lib/capybarbecue/server.rb +64 -0
- data/lib/capybarbecue/version.rb +3 -0
- data/spec/capybarbecue/async_call_spec.rb +94 -0
- data/spec/capybarbecue/async_delegate_class_spec.rb +64 -0
- data/spec/capybarbecue/rack_runner_spec.rb +4 -0
- data/spec/capybarbecue/server_spec.rb +55 -0
- data/spec/capybarbecue_spec.rb +69 -0
- data/spec/integration/element_spec.rb +91 -0
- data/spec/integration/session_spec.rb +133 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/support/test_rack_app.rb +64 -0
- metadata +210 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8fa420c19a4cf57eb1a6fb4d9122e1a01f7355cf
|
4
|
+
data.tar.gz: f3456b9851b38b602da77a2a542bfb78d71cf602
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c50a76bc67c480cddab96a0aa8a55459b6522ec022e6961ae369d7dc7c14953394b08c37b9501be7fc34437b44da186a6b1efc72f725014e7da1ab4db5fd065a
|
7
|
+
data.tar.gz: 329541d5aabcf0be4d796721aca4156fc52971d64c94f6b6113a0cf4cf09dbd463dc12c482487c18020e865f18773e89f2d38b9e22c556e088d8fd7a4ae14371
|
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
group :development do
|
4
|
+
gem "rspec"
|
5
|
+
gem "rr", require: false
|
6
|
+
gem "yard"
|
7
|
+
gem "rdoc"
|
8
|
+
gem "bundler"
|
9
|
+
gem "jeweler"
|
10
|
+
gem "poltergeist"
|
11
|
+
gem "launchy"
|
12
|
+
end
|
13
|
+
|
14
|
+
gem "activesupport"
|
15
|
+
gem "capybara", "~>2.1.0"
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activesupport (4.0.0)
|
5
|
+
i18n (~> 0.6, >= 0.6.4)
|
6
|
+
minitest (~> 4.2)
|
7
|
+
multi_json (~> 1.3)
|
8
|
+
thread_safe (~> 0.1)
|
9
|
+
tzinfo (~> 0.3.37)
|
10
|
+
addressable (2.3.5)
|
11
|
+
atomic (1.1.10)
|
12
|
+
capybara (2.1.0)
|
13
|
+
mime-types (>= 1.16)
|
14
|
+
nokogiri (>= 1.3.3)
|
15
|
+
rack (>= 1.0.0)
|
16
|
+
rack-test (>= 0.5.4)
|
17
|
+
xpath (~> 2.0)
|
18
|
+
diff-lcs (1.2.4)
|
19
|
+
eventmachine (1.0.3)
|
20
|
+
faye-websocket (0.4.7)
|
21
|
+
eventmachine (>= 0.12.0)
|
22
|
+
git (1.2.5)
|
23
|
+
http_parser.rb (0.5.3)
|
24
|
+
i18n (0.6.4)
|
25
|
+
jeweler (1.8.4)
|
26
|
+
bundler (~> 1.0)
|
27
|
+
git (>= 1.2.5)
|
28
|
+
rake
|
29
|
+
rdoc
|
30
|
+
json (1.8.0)
|
31
|
+
launchy (2.3.0)
|
32
|
+
addressable (~> 2.3)
|
33
|
+
mime-types (1.23)
|
34
|
+
mini_portile (0.5.1)
|
35
|
+
minitest (4.7.5)
|
36
|
+
multi_json (1.7.7)
|
37
|
+
nokogiri (1.6.0)
|
38
|
+
mini_portile (~> 0.5.0)
|
39
|
+
poltergeist (1.3.0)
|
40
|
+
capybara (~> 2.1.0)
|
41
|
+
faye-websocket (>= 0.4.4, < 0.5.0)
|
42
|
+
http_parser.rb (~> 0.5.3)
|
43
|
+
rack (1.5.2)
|
44
|
+
rack-test (0.6.2)
|
45
|
+
rack (>= 1.0)
|
46
|
+
rake (10.1.0)
|
47
|
+
rdoc (4.0.1)
|
48
|
+
json (~> 1.4)
|
49
|
+
rr (1.1.1)
|
50
|
+
rspec (2.14.1)
|
51
|
+
rspec-core (~> 2.14.0)
|
52
|
+
rspec-expectations (~> 2.14.0)
|
53
|
+
rspec-mocks (~> 2.14.0)
|
54
|
+
rspec-core (2.14.4)
|
55
|
+
rspec-expectations (2.14.0)
|
56
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
57
|
+
rspec-mocks (2.14.2)
|
58
|
+
thread_safe (0.1.2)
|
59
|
+
atomic
|
60
|
+
tzinfo (0.3.37)
|
61
|
+
xpath (2.0.0)
|
62
|
+
nokogiri (~> 1.3)
|
63
|
+
yard (0.8.7)
|
64
|
+
|
65
|
+
PLATFORMS
|
66
|
+
ruby
|
67
|
+
|
68
|
+
DEPENDENCIES
|
69
|
+
activesupport
|
70
|
+
bundler
|
71
|
+
capybara (~> 2.1.0)
|
72
|
+
jeweler
|
73
|
+
launchy
|
74
|
+
poltergeist
|
75
|
+
rdoc
|
76
|
+
rr
|
77
|
+
rspec
|
78
|
+
yard
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Andrew DiMichele
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/NOTES.txt
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
= Rails Thread =
|
2
|
+
Put a proxy class in front of Capybara::Driver::Base and Capybara::Driver::Node
|
3
|
+
Capybara:Session#driver is the instance of driver
|
4
|
+
Probably need to create a new driver which is a proxy for another driver
|
5
|
+
Proxy class puts method_missing args on MQ to be sent to Driver thread
|
6
|
+
Needs to yield to Driver thread and then execute the RackRunner
|
7
|
+
RackRunner reads rack requests from a MQ, calling the rack endpoint with those; returns when done
|
8
|
+
This should probably use the rack-test gem? This probably isn't what we want since this simulates a browser
|
9
|
+
|
10
|
+
= Driver Thread =
|
11
|
+
Reads RPCs off a MQ, calls the commands in the appropriate class, and puts the response on another MQ
|
12
|
+
Part of the new driver passed to Capybara
|
13
|
+
Should be able to use the Capybara driver of your choice
|
14
|
+
How is a Node passed back to the Rails thread? Maybe it's OK for Node queries to go straight through to the webdriver?
|
15
|
+
These could trigger web requests (like a click) that would not be processed... maybe wrap in the Barbecue driver
|
16
|
+
so that the RackRunner is triggered appropriately? That still causes a problem when clicking a link since the
|
17
|
+
webdriver might block... need to think about this some more
|
18
|
+
|
19
|
+
= Capybara::Server =
|
20
|
+
Rewrite this class entirely
|
21
|
+
Should spawn a simple Rack webserver that just take the #call Args and puts them on the MQ
|
22
|
+
Needs to block until MQ response is received
|
23
|
+
Webserver could be Puma or EventMachine
|
24
|
+
|
25
|
+
|
26
|
+
Notes:
|
27
|
+
Queue class provided by 'thread' library
|
28
|
+
|
29
|
+
----- Latest Notes -----
|
30
|
+
* Interface with Session instead
|
31
|
+
* Don't try to mimic interfaces - just ferry calls over
|
32
|
+
* Add field to the base node class so we can tell it's a node
|
33
|
+
* If return value is a Node then wrap it
|
34
|
+
* If return value is enumerable, wrap its Node values via #map
|
35
|
+
|
36
|
+
---- TODO ----
|
37
|
+
* Raise errors during timeouts if Time is frozen
|
data/README.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= capybarbecue
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Contributing to capybarbecue
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
9
|
+
* Fork the project.
|
10
|
+
* Start a feature/bugfix branch.
|
11
|
+
* Commit and push until you are happy with your contribution.
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2013 Andrew DiMichele. See LICENSE.txt for
|
18
|
+
further details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
require './lib/capybarbecue/version'
|
16
|
+
Jeweler::Tasks.new do |gem|
|
17
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
18
|
+
gem.name = "capybarbecue"
|
19
|
+
gem.homepage = "http://github.com/adimichele/capybarbecue"
|
20
|
+
gem.license = "MIT"
|
21
|
+
gem.summary = %Q{Makes your Capybara test suite work better}
|
22
|
+
gem.description = %Q{Makes fundamental changes to Capybara's threading architecture so you can write stable tests with a shared database connection.}
|
23
|
+
gem.email = "backflip@gmail.com"
|
24
|
+
gem.authors = ["Andrew DiMichele"]
|
25
|
+
gem.version = Capybarbecue::VERSION
|
26
|
+
# dependencies defined in Gemfile
|
27
|
+
end
|
28
|
+
Jeweler::RubygemsDotOrgTasks.new
|
29
|
+
|
30
|
+
require 'rspec/core'
|
31
|
+
require 'rspec/core/rake_task'
|
32
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
33
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
34
|
+
end
|
35
|
+
|
36
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
37
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
38
|
+
spec.rcov = true
|
39
|
+
end
|
40
|
+
|
41
|
+
task :default => :spec
|
42
|
+
|
43
|
+
require 'yard'
|
44
|
+
YARD::Rake::YardocTask.new
|
data/lib/capybarbecue.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
|
3
|
+
module Capybarbecue
|
4
|
+
class << self
|
5
|
+
def activated?
|
6
|
+
Capybara.respond_to? :original_session
|
7
|
+
end
|
8
|
+
|
9
|
+
def activate!
|
10
|
+
return if activated?
|
11
|
+
require 'capybarbecue/async_call'
|
12
|
+
require 'capybarbecue/async_delegate_class'
|
13
|
+
require 'capybarbecue/rack_runner'
|
14
|
+
require 'capybarbecue/server'
|
15
|
+
|
16
|
+
(class << Capybara; self end).instance_eval do
|
17
|
+
alias_method :original_session, :current_session
|
18
|
+
define_method :current_session do
|
19
|
+
Capybarbecue::AsyncDelegateClass.new(original_session) do
|
20
|
+
Capybara.app.handle_requests
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Capybara.send(:session_pool).clear
|
26
|
+
|
27
|
+
# Swap out Capybara's server for the one that waits
|
28
|
+
# Do this by deleting everything in Capybara#session_pool (private) and swapping Capybara.app
|
29
|
+
new_app = Capybarbecue::Server.new(Capybara.app)
|
30
|
+
Capybara.app = new_app
|
31
|
+
end
|
32
|
+
|
33
|
+
def deactivate!
|
34
|
+
# Make sure this kills all threads too?
|
35
|
+
return unless activated?
|
36
|
+
(class << Capybara; self end).instance_eval do
|
37
|
+
alias_method :current_session, :original_session
|
38
|
+
remove_method :original_session
|
39
|
+
end
|
40
|
+
Capybara.send(:session_pool).clear
|
41
|
+
Capybara.app = Capybara.app.app
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
class AsyncCall # Asynchronously calls a method
|
4
|
+
DEFAULT_TIMEOUT = 5.0
|
5
|
+
attr_reader :thread
|
6
|
+
attr_reader :ready
|
7
|
+
attr_reader :to_s
|
8
|
+
alias :ready? :ready
|
9
|
+
|
10
|
+
def initialize(obj, method, *args, &block)
|
11
|
+
@obj, @method, @args, @block = obj, method, args, block
|
12
|
+
@ready = false
|
13
|
+
@response = nil
|
14
|
+
@exception = nil
|
15
|
+
@thread = Thread.new do
|
16
|
+
begin
|
17
|
+
respond_with obj.send(method, *args, &block)
|
18
|
+
rescue Exception => e
|
19
|
+
respond_with_exception e
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# If a block is passed, it will be called repeatedly until a response comes in
|
25
|
+
def wait_for_response(timeout=DEFAULT_TIMEOUT)
|
26
|
+
started_at = Time.now
|
27
|
+
while Time.now - started_at < timeout.seconds && !ready?
|
28
|
+
yield if block_given?
|
29
|
+
# It feels dangerous not to sleep here... keep a pulse on this (sleep causes performance problems)
|
30
|
+
Thread.pass
|
31
|
+
end
|
32
|
+
kill! and raise Timeout::Error.new('Timeout expired before response received') unless ready?
|
33
|
+
if @exception.present?
|
34
|
+
# Add the backtrace from this thread to make it useful
|
35
|
+
backtrace = @exception.backtrace + Kernel.caller
|
36
|
+
@exception.set_backtrace(backtrace)
|
37
|
+
raise @exception
|
38
|
+
end
|
39
|
+
@response
|
40
|
+
end
|
41
|
+
|
42
|
+
def kill!
|
43
|
+
@thread.kill
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s
|
47
|
+
s = "#{@obj.class.name}##{@method}(#{@args.map{ |a| a.class.name }.join(', ')})"
|
48
|
+
s += ' { ... }' if @block.present?
|
49
|
+
s
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def respond_with(value)
|
55
|
+
@response = value
|
56
|
+
@ready = true
|
57
|
+
end
|
58
|
+
|
59
|
+
def respond_with_exception(e)
|
60
|
+
@exception = e
|
61
|
+
@ready = true
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Capybarbecue
|
2
|
+
class AsyncDelegateClass
|
3
|
+
attr_reader :instance
|
4
|
+
|
5
|
+
def initialize(instance, &wait_proc)
|
6
|
+
@instance = instance
|
7
|
+
@wait_proc = wait_proc # Called repeatedly while waiting
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(method, *args, &block)
|
11
|
+
call = AsyncCall.new(@instance, method, *args, &block)
|
12
|
+
wrap_response(call.wait_for_response(&@wait_proc))
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def respond_to_missing?(method, include_all=false)
|
18
|
+
@instance.respond_to?(method, include_all)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Wrap anything that looks like Capybara
|
22
|
+
def wrap_response(value)
|
23
|
+
if value.class.name.include?('Capybara')
|
24
|
+
AsyncDelegateClass.new(value, &@wait_proc)
|
25
|
+
else
|
26
|
+
value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
module Capybarbecue
|
2
|
+
class RackRunner
|
3
|
+
# Rack runner portion of main thread
|
4
|
+
# Reads rack requests of a message queue, runs them, and puts the result on another MQ
|
5
|
+
# Should have some sort of delay or signaling system to eliminate race conditions when waiting for requests
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Capybarbecue
|
4
|
+
class Server
|
5
|
+
DEFAULT_TIMEOUT = 30
|
6
|
+
attr_accessor :wait_for_response # For testing - set to false to keep #call from blocking
|
7
|
+
attr_accessor :timeout, :app
|
8
|
+
|
9
|
+
def initialize(app)
|
10
|
+
@app = app
|
11
|
+
@requestmq = Queue.new
|
12
|
+
@wait_for_response = true
|
13
|
+
@timeout = DEFAULT_TIMEOUT
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
queue_and_wait(env)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Should be run by another thread - respond to all queued requests
|
21
|
+
def handle_requests
|
22
|
+
until @requestmq.empty?
|
23
|
+
request = @requestmq.deq(true)
|
24
|
+
begin
|
25
|
+
request.response = @app.call(request.env)
|
26
|
+
rescue Exception => e
|
27
|
+
request.exception = e
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def queue_and_wait(env)
|
35
|
+
request = QueuedRequest.new(env)
|
36
|
+
@requestmq.enq(request)
|
37
|
+
return unless wait_for_response
|
38
|
+
started_at = Time.now
|
39
|
+
while !request.ready? && Time.now - started_at < timeout.seconds
|
40
|
+
# It feels dangerous not to sleep here... keep a pulse on this (sleep causes performance problems)
|
41
|
+
Thread.pass
|
42
|
+
end
|
43
|
+
raise Timeout::Error.new('Timeout expired before response received') unless request.ready?
|
44
|
+
if request.exception.present?
|
45
|
+
# Add the backtrace from this thread to make it useful
|
46
|
+
backtrace = request.exception.backtrace + Kernel.caller
|
47
|
+
request.exception.set_backtrace(backtrace)
|
48
|
+
raise request.exception
|
49
|
+
end
|
50
|
+
request.response
|
51
|
+
end
|
52
|
+
|
53
|
+
class QueuedRequest
|
54
|
+
attr_reader :env
|
55
|
+
attr_accessor :response, :exception
|
56
|
+
def initialize(env)
|
57
|
+
@env = env
|
58
|
+
end
|
59
|
+
def ready?
|
60
|
+
@response.present? || @exception.present?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe AsyncCall do
|
4
|
+
let(:obj) { Object.new }
|
5
|
+
let(:method) { :nil? }
|
6
|
+
let(:args) { [] }
|
7
|
+
let(:block) { nil }
|
8
|
+
subject{ AsyncCall.new(obj, method, *args, &block) }
|
9
|
+
after{ subject.kill! }
|
10
|
+
describe '#thread' do
|
11
|
+
it 'should be a thread' do
|
12
|
+
expect(subject.thread).to be_a Thread
|
13
|
+
end
|
14
|
+
end
|
15
|
+
describe '#ready' do
|
16
|
+
let(:method){ :test }
|
17
|
+
before do
|
18
|
+
stub(obj).test do
|
19
|
+
sleep 0.1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
it 'should be true once the response is ready' do
|
23
|
+
expect(subject).to_not be_ready
|
24
|
+
sleep 0.11
|
25
|
+
expect(subject).to be_ready
|
26
|
+
end
|
27
|
+
end
|
28
|
+
describe '#wait_for_response' do
|
29
|
+
let(:obj){ Object }
|
30
|
+
let(:method){ :name }
|
31
|
+
it 'returns the value' do
|
32
|
+
expect(subject.wait_for_response).to eq 'Object'
|
33
|
+
end
|
34
|
+
context 'with a block' do
|
35
|
+
before do
|
36
|
+
stub(obj).name { sleep 0.3 }
|
37
|
+
end
|
38
|
+
it 'calls the block repeatedly while waiting' do
|
39
|
+
mock(obj).foo.at_least(2)
|
40
|
+
subject.wait_for_response { obj.foo }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
context 'when the timeout expires' do
|
44
|
+
let(:method){ :test }
|
45
|
+
before do
|
46
|
+
stub(obj).test do
|
47
|
+
sleep 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
it 'raises an error and kills the thread' do
|
51
|
+
expect{ subject.wait_for_response(0.01) }.to raise_error Timeout::Error
|
52
|
+
sleep 0.1
|
53
|
+
expect(subject.thread).to_not be_alive
|
54
|
+
end
|
55
|
+
end
|
56
|
+
context 'when the call raises an exception' do
|
57
|
+
let(:method){ :test }
|
58
|
+
before do
|
59
|
+
stub(obj).test do
|
60
|
+
1 / 0
|
61
|
+
end
|
62
|
+
end
|
63
|
+
it 'also raises the exception' do
|
64
|
+
expect{ subject.wait_for_response }.to raise_error ZeroDivisionError
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
describe '#kill!' do
|
69
|
+
let(:method){ :test }
|
70
|
+
before do
|
71
|
+
stub(obj).test do
|
72
|
+
sleep 1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
it 'kills the thread' do
|
76
|
+
expect{ subject.kill!; sleep 0.01 }.to change{ subject.thread.stop? }.from(false).to(true)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
describe '#to_s' do
|
80
|
+
let(:obj) { Object.new }
|
81
|
+
let(:method) { :foo }
|
82
|
+
let(:args) { [1, Object.new] }
|
83
|
+
before{ stub(obj).foo.with_any_args }
|
84
|
+
it 'returns the method as a string' do
|
85
|
+
expect(subject.to_s).to eq 'Object#foo(Fixnum, Object)'
|
86
|
+
end
|
87
|
+
context 'with a block' do
|
88
|
+
let(:block){ Proc.new do end }
|
89
|
+
it 'returns the method as a string and indicates a block' do
|
90
|
+
expect(subject.to_s).to eq 'Object#foo(Fixnum, Object) { ... }'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '../../spec_helper')
|
2
|
+
|
3
|
+
describe Capybarbecue::AsyncDelegateClass do
|
4
|
+
let(:obj){ Object.new }
|
5
|
+
subject{ Capybarbecue::AsyncDelegateClass.new(obj) }
|
6
|
+
|
7
|
+
it 'calls the method on the session asynchronously' do
|
8
|
+
mock(obj).foo{ 'cats' }
|
9
|
+
expect(subject.foo).to eq 'cats'
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'when the object returned is not a Capybara object' do
|
13
|
+
before{ mock(obj).foo{ 'cats' } }
|
14
|
+
it 'should be itself' do
|
15
|
+
expect(subject.foo).to be_an_instance_of String
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when the object returned is a Capybara object' do
|
20
|
+
before { mock(obj).foo{ Capybara::Driver::Base.new } }
|
21
|
+
it 'wraps the return value in AsyncDelegateClass' do
|
22
|
+
expect(subject.foo).to be_an_instance_of Capybarbecue::AsyncDelegateClass
|
23
|
+
end
|
24
|
+
context 'when a block is given' do
|
25
|
+
subject do
|
26
|
+
Capybarbecue::AsyncDelegateClass.new(obj) do
|
27
|
+
obj.wait_func
|
28
|
+
end
|
29
|
+
end
|
30
|
+
it 'preserves the wait_proc' do
|
31
|
+
mock(obj).wait_func.twice
|
32
|
+
resp = subject.foo
|
33
|
+
stub(resp.instance).foo
|
34
|
+
resp.foo
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#instance' do
|
40
|
+
it 'should be the instance' do
|
41
|
+
subject.instance.should be obj
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#respond_to?' do
|
46
|
+
it 'returns @instance#respond_to?' do
|
47
|
+
stub(obj).foo
|
48
|
+
expect(subject).to respond_to :foo
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when a block is given' do
|
53
|
+
subject do
|
54
|
+
Capybarbecue::AsyncDelegateClass.new(obj) do
|
55
|
+
obj.wait_func
|
56
|
+
end
|
57
|
+
end
|
58
|
+
it 'runs the block before returning' do
|
59
|
+
mock(obj).wait_func.at_least(2)
|
60
|
+
stub(obj).foo { sleep 0.3 }
|
61
|
+
subject.foo
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Capybarbecue::Server do
|
4
|
+
let(:app) { Object.new }
|
5
|
+
subject{ Capybarbecue::Server.new(app) }
|
6
|
+
before{ subject.timeout = 5 } # This makes tests fail a little quicker
|
7
|
+
describe '#app' do
|
8
|
+
its(:app){ should be app }
|
9
|
+
end
|
10
|
+
describe '#handle_requests' do
|
11
|
+
before{ subject.wait_for_response = false }
|
12
|
+
it 'responds to all queued requests' do
|
13
|
+
request1 = Object.new
|
14
|
+
request2 = Object.new
|
15
|
+
mock(app).call(request1)
|
16
|
+
mock(app).call(request2)
|
17
|
+
subject.call(request1)
|
18
|
+
subject.call(request2)
|
19
|
+
subject.handle_requests
|
20
|
+
end
|
21
|
+
end
|
22
|
+
describe '#call' do
|
23
|
+
context 'when another thread handles the request' do
|
24
|
+
before do
|
25
|
+
stub(app).call.with_any_args { |arg| arg }
|
26
|
+
end
|
27
|
+
let!(:thread) do
|
28
|
+
@thread = Thread.new do
|
29
|
+
while true
|
30
|
+
subject.handle_requests
|
31
|
+
sleep 0.05
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
after { thread.kill }
|
36
|
+
it 'gives the response' do
|
37
|
+
expect(subject.call('avacado')).to eq 'avacado'
|
38
|
+
end
|
39
|
+
context 'when there is an exception' do
|
40
|
+
before do
|
41
|
+
stub(app).call.with_any_args { raise Exception.new }
|
42
|
+
end
|
43
|
+
it 'raises the exception' do
|
44
|
+
expect{ subject.call('avacado') }.to raise_error
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
context 'when the timeout expires' do
|
49
|
+
before{ subject.timeout = 0.01 }
|
50
|
+
it 'raises an exception' do
|
51
|
+
expect{ subject.call(nil) }.to raise_error Timeout::Error
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Capybarbecue do
|
4
|
+
describe '#activated' do
|
5
|
+
subject{ Capybarbecue.activated? }
|
6
|
+
it 'is true because spec_helper activates it' do
|
7
|
+
expect(subject).to be_true
|
8
|
+
end
|
9
|
+
context 'when deactivated' do
|
10
|
+
before{ Capybarbecue.deactivate! }
|
11
|
+
it 'is false' do
|
12
|
+
expect(subject).to be_false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
describe '#activate' do
|
17
|
+
before do
|
18
|
+
Capybarbecue.deactivate!
|
19
|
+
end
|
20
|
+
subject{ Capybarbecue.activate! }
|
21
|
+
after{ Capybarbecue.deactivate! }
|
22
|
+
it 'redefines Capybara#current_session' do
|
23
|
+
subject
|
24
|
+
expect(Capybara.current_session).to be_an_instance_of Capybarbecue::AsyncDelegateClass
|
25
|
+
expect(Capybara.current_session.instance).to be Capybara.original_session
|
26
|
+
end
|
27
|
+
it 'saves old Capybara#current_session Capybara#original_session' do
|
28
|
+
subject
|
29
|
+
expect(Capybara.original_session).to be_an_instance_of Capybara::Session
|
30
|
+
end
|
31
|
+
it 'clears the session pool' do
|
32
|
+
Capybara.current_session
|
33
|
+
Capybara.send(:session_pool).should_not be_empty
|
34
|
+
subject
|
35
|
+
Capybara.send(:session_pool).should be_empty
|
36
|
+
end
|
37
|
+
it 'inserts Capybarbecue::Server as the app' do
|
38
|
+
old_app = Capybara.app
|
39
|
+
subject
|
40
|
+
expect(Capybara.app).to be_an_instance_of Capybarbecue::Server
|
41
|
+
expect(Capybara.app.app).to be old_app
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#deactivate' do
|
46
|
+
before { Capybarbecue.activate! }
|
47
|
+
subject { Capybarbecue.deactivate! }
|
48
|
+
after { Capybarbecue.activate! }
|
49
|
+
it 'restores #current_session' do
|
50
|
+
subject
|
51
|
+
expect(Capybarbecue).to_not be_activated
|
52
|
+
expect(Capybara.current_session).to be_an_instance_of Capybara::Session
|
53
|
+
end
|
54
|
+
it 'deletes #original_session' do
|
55
|
+
subject
|
56
|
+
expect(Capybara.methods).to_not include :original_session
|
57
|
+
end
|
58
|
+
it 'restores the server app' do
|
59
|
+
subject
|
60
|
+
expect(Capybara.app).to_not be_an_instance_of Capybarbecue::Server
|
61
|
+
end
|
62
|
+
it 'clears the session pool' do
|
63
|
+
Capybara.current_session
|
64
|
+
Capybara.send(:session_pool).should_not be_empty
|
65
|
+
subject
|
66
|
+
Capybara.send(:session_pool).should be_empty
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '../../spec_helper')
|
2
|
+
|
3
|
+
describe 'With a real life app', :type => :feature, :capybara_feature => true do
|
4
|
+
# Test that operations on nodes work as expected
|
5
|
+
let(:path){ '/test/path'}
|
6
|
+
before{ visit path }
|
7
|
+
describe Capybara::Node::Element do
|
8
|
+
subject{ find("##{id}") }
|
9
|
+
describe '#text' do
|
10
|
+
let(:id){ 'hidden_text' }
|
11
|
+
its(:text){ should include 'I am text' }
|
12
|
+
end
|
13
|
+
describe '#[]' do
|
14
|
+
let(:id){ 'hidden_text' }
|
15
|
+
its(['id']){ should eq id }
|
16
|
+
end
|
17
|
+
describe '#value' do
|
18
|
+
let(:id){ 'volvo' }
|
19
|
+
its(:value){ should eq 'volvo' }
|
20
|
+
end
|
21
|
+
describe '#set' do
|
22
|
+
let(:id){ 'text_field' }
|
23
|
+
it 'sets the value of the field' do
|
24
|
+
expect{ subject.set 'peanuts' }.to change{ subject.value }.to 'peanuts'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
describe '#select_option, #unselect_option, #selected?, #checked?' do
|
28
|
+
let(:id){ 'volvo' }
|
29
|
+
it 'selects and unselects an option' do
|
30
|
+
expect{ subject.select_option }.to change{ subject.selected? }.from(false).to(true)
|
31
|
+
expect{ subject.unselect_option }.to change{ subject.selected? }.from(true).to(false)
|
32
|
+
expect(subject).to_not be_checked
|
33
|
+
end
|
34
|
+
end
|
35
|
+
describe '#click' do
|
36
|
+
let(:id){ 'link1' }
|
37
|
+
it 'follows the link' do
|
38
|
+
expect{ subject.click }.to change{ page.current_path }.to '/link/1'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
describe '#hover' do
|
42
|
+
let(:id){ 'div1' }
|
43
|
+
before do
|
44
|
+
page.execute_script("document.getElementById('#{id}').onmouseover = function(){ document.write('hovered') }")
|
45
|
+
end
|
46
|
+
it 'triggers the hover js' do
|
47
|
+
subject.hover
|
48
|
+
page.should have_text 'hovered'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
describe '#tag_name' do
|
52
|
+
let(:id){ 'div1' }
|
53
|
+
its(:tag_name){ should eq 'div' }
|
54
|
+
end
|
55
|
+
describe '#visible?' do
|
56
|
+
let(:id){ 'div1' }
|
57
|
+
it{ should be_visible }
|
58
|
+
end
|
59
|
+
describe '#disabled?' do
|
60
|
+
let(:id){ 'disabled_text' }
|
61
|
+
it{ should be_disabled }
|
62
|
+
end
|
63
|
+
describe '#path' do
|
64
|
+
let(:id){ 'text_field' }
|
65
|
+
pending 'is not supported in some drivers'
|
66
|
+
end
|
67
|
+
describe '#trigger' do
|
68
|
+
let(:id){ 'div1' }
|
69
|
+
before do
|
70
|
+
page.execute_script("document.getElementById('#{id}').onclick = function(){ document.write('sandwiches') }")
|
71
|
+
end
|
72
|
+
it 'triggers a click event' do
|
73
|
+
subject.trigger 'click'
|
74
|
+
expect(page).to have_text 'sandwiches'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
describe '#drag_to' do
|
78
|
+
let(:id){ 'text_field' }
|
79
|
+
let(:target){ find('#disabled_text') }
|
80
|
+
it 'works?' do
|
81
|
+
subject.drag_to target
|
82
|
+
end
|
83
|
+
end
|
84
|
+
describe '#reload' do
|
85
|
+
let(:id){ 'text_field' }
|
86
|
+
it 'reloads the node' do
|
87
|
+
expect(subject.reload.value).to eq 'monkeys'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '../../spec_helper')
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
# Ideally want to test everything in Capybara::Session::DSL_METHODS
|
5
|
+
# These methods haven't been tested yet:
|
6
|
+
#NODE_METHODS = [
|
7
|
+
# :resolve, :query # These don't appear to be actual methods?
|
8
|
+
#]
|
9
|
+
#SESSION_METHODS = [
|
10
|
+
#]
|
11
|
+
#
|
12
|
+
# As methods are tested, add them to this list... it will help keep track of test coverage as Capybara changes
|
13
|
+
#NODE_METHODS = [
|
14
|
+
# :find, :all, :first, :find_by_id, :find_button, :find_link, :find_field, :has_css?, :has_no_css?, :has_link?, :text,
|
15
|
+
# :has_no_link?, :has_button?, :has_no_button?, :has_field?, :has_no_field?, :has_xpath?, :has_no_xpath?,
|
16
|
+
# :has_checked_field?, :has_unchecked_field?, :has_selector?, :has_no_selector?, :assert_selector, :assert_no_selector,
|
17
|
+
# :has_no_checked_field?, :has_no_unchecked_field?, :has_select?, :has_no_select?, :has_no_table?, :has_table?,
|
18
|
+
# :has_content?, :has_text?, :has_no_content?, :has_no_text?, :field_labeled,
|
19
|
+
# :click_link_or_button, :click_button, :click_link, :click_on, :fill_in, :choose, :check, :uncheck, :select, :unselect, :attach_file
|
20
|
+
#]
|
21
|
+
#SESSION_METHODS = [
|
22
|
+
# :current_url, :current_host, :current_path, :visit, :body, :html, :source,
|
23
|
+
# :title, :has_title?, :has_no_title?, :status_code, :response_headers,
|
24
|
+
# :reset_session!, :current_scope, :save_page, :save_and_open_page, :save_screenshot,
|
25
|
+
# :within, :within_fieldset, :within_table, :within_frame, :within_window,
|
26
|
+
# :evaluate_script, :execute_script
|
27
|
+
#]
|
28
|
+
# Notes: current_scope is used extensively within Capybara
|
29
|
+
|
30
|
+
describe 'With a real life app', :type => :feature, :capybara_feature => true do
|
31
|
+
let(:path){ '/test/path'}
|
32
|
+
before{ visit path }
|
33
|
+
scenario 'page properties all work' do
|
34
|
+
expect(title).to eq 'Test Rack App'
|
35
|
+
expect(page).to have_title 'Test Rack App'
|
36
|
+
expect(page).to have_no_title 'Test Crack App'
|
37
|
+
expect(current_url).to end_with '/test/path'
|
38
|
+
expect(current_host).to include '127.0.0.1'
|
39
|
+
expect(current_path).to eq '/test/path'
|
40
|
+
expect(status_code).to eq 200
|
41
|
+
expect(response_headers['Content-Type']).to eq 'text/html'
|
42
|
+
end
|
43
|
+
context 'with a temp dir' do
|
44
|
+
let(:path){ './tmp/test' }
|
45
|
+
before{ FileUtils.mkdir_p path }
|
46
|
+
after{ FileUtils.rm_rf path }
|
47
|
+
scenario 'we can save page screenshots and pages' do
|
48
|
+
save_page File.join(path, 'save.html')
|
49
|
+
save_and_open_page File.join(path, 'save_and_open.html')
|
50
|
+
save_screenshot File.join(path, 'screenshot.png')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
scenario 'we can check page content' do
|
54
|
+
expect(page).to have_content '/test/path'
|
55
|
+
expect(page).to have_no_content '/another/path'
|
56
|
+
expect(page).to have_text '/test/path'
|
57
|
+
expect(page).to have_no_text '/another/path'
|
58
|
+
expect(body).to include '/test/path'
|
59
|
+
expect(html).to include '/test/path'
|
60
|
+
expect(source).to include '/test/path'
|
61
|
+
expect(text).to include '/test/path'
|
62
|
+
end
|
63
|
+
scenario 'we can find elements on the page' do
|
64
|
+
expect(all('div')).to be_present
|
65
|
+
expect(first('div')).to be_present
|
66
|
+
expect(find('#div1')).to be_present
|
67
|
+
expect(find_by_id('div1')).to be_present
|
68
|
+
expect(find_button('Imma button!')).to be_present
|
69
|
+
expect(find_link('click1')).to be_present
|
70
|
+
expect(find_field('text_field')).to be_present
|
71
|
+
end
|
72
|
+
scenario 'we can check for elements on the page' do
|
73
|
+
expect(page).to have_selector('div#div1')
|
74
|
+
expect(page).to have_no_selector('a#div1')
|
75
|
+
page.assert_selector 'div#div1'
|
76
|
+
page.assert_no_selector 'a#div1'
|
77
|
+
expect(page).to have_xpath('//div/div/a')
|
78
|
+
expect(page).to have_no_xpath('//div/oops/a')
|
79
|
+
expect(page).to have_css('div#div1')
|
80
|
+
expect(page).to have_no_css('a#div1')
|
81
|
+
expect(page).to have_link('click1')
|
82
|
+
expect(page).to have_no_link('dontclick1')
|
83
|
+
expect(page).to have_button('Imma button!')
|
84
|
+
expect(page).to have_no_button('I aint no button')
|
85
|
+
expect(page).to have_field('text_field')
|
86
|
+
expect(page).to have_no_field('no_text_field')
|
87
|
+
expect(page).to have_checked_field('checkbox_field2')
|
88
|
+
expect(page).to have_no_checked_field('checkbox_field')
|
89
|
+
expect(page).to have_unchecked_field('checkbox_field')
|
90
|
+
expect(page).to have_no_unchecked_field('checkbox_field2')
|
91
|
+
expect(page).to have_select('select_field')
|
92
|
+
expect(page).to have_no_select('nota_select_field')
|
93
|
+
expect(page).to have_table('table1')
|
94
|
+
expect(page).to have_no_table('div1')
|
95
|
+
end
|
96
|
+
scenario 'we can interact with an element on the page' do
|
97
|
+
expect{ click_link_or_button 'link2' }.to change{ current_path }.to '/link/2'
|
98
|
+
expect{ click_on 'link1' }.to change{ current_path }.to '/link/1'
|
99
|
+
expect{ click_link 'link2' }.to change{ current_path }.to '/link/2'
|
100
|
+
expect{ click_button 'button1' }.to change{ current_path }.to '/button/1'
|
101
|
+
expect{ fill_in 'text_field', with: 'red sox' }.to change{ find_field('text_field').value }.to 'red sox'
|
102
|
+
expect{ choose 'male' }.to change{ find_field('male').checked? }.to true
|
103
|
+
expect{ check 'checkbox_field' }.to change{ find_field('checkbox_field').checked? }.to true
|
104
|
+
expect{ uncheck 'checkbox_field' }.to change{ find_field('checkbox_field').checked? }.to false
|
105
|
+
expect{ page.select 'Volvo', from: 'select_field' }.to change{ find('#select_field').value }.to %w{volvo}
|
106
|
+
expect{ unselect 'Volvo', from: 'select_field' }.to change{ find('#select_field').value }.to []
|
107
|
+
attach_file 'file_field', File.expand_path(File.dirname(__FILE__) + '../../spec_helper.rb')
|
108
|
+
expect(find('#file_field').value).to end_with 'spec_helper.rb'
|
109
|
+
end
|
110
|
+
scenario 'we can search within elements' do
|
111
|
+
within 'div#div1' do
|
112
|
+
expect(page).to have_css 'div.divclass2'
|
113
|
+
end
|
114
|
+
within_fieldset 'fieldset1' do
|
115
|
+
expect(page).to have_field 'file_field'
|
116
|
+
end
|
117
|
+
within_table 'table1' do
|
118
|
+
expect(page).to have_content 'cell 1,1'
|
119
|
+
end
|
120
|
+
within_frame 'myframe' do
|
121
|
+
expect(page).to have_css '#inframe'
|
122
|
+
end
|
123
|
+
page.execute_script("window.open(null,'window1').document.write('i am an open window')") # create a window
|
124
|
+
within_window 'window1' do
|
125
|
+
expect(page.html).to have_content 'i am an open window'
|
126
|
+
end
|
127
|
+
end
|
128
|
+
scenario 'we can execute javascript' do
|
129
|
+
page.execute_script("document.write('it worked')")
|
130
|
+
expect(html).to match /it worked/
|
131
|
+
expect(evaluate_script('4 + 4')).to eq 8
|
132
|
+
end
|
133
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
#$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'capybara/rspec'
|
5
|
+
require 'capybara/poltergeist'
|
6
|
+
require 'capybarbecue'
|
7
|
+
require 'rr'
|
8
|
+
|
9
|
+
# Requires supporting files with custom matchers and macros, etc,
|
10
|
+
# in ./support/ and its subdirectories.
|
11
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
12
|
+
|
13
|
+
Capybara.default_driver = :poltergeist
|
14
|
+
Capybara.app = TestRackApp.new
|
15
|
+
Capybarbecue.activate!
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
config.mock_with :rr
|
19
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
class TestRackApp
|
4
|
+
def get_html(request)
|
5
|
+
<<-HTML % request.path
|
6
|
+
<html>
|
7
|
+
<head><title>Test Rack App</title></head>
|
8
|
+
<body>
|
9
|
+
<h1>%s</h1>
|
10
|
+
<div class='divclass1' id='div1'>
|
11
|
+
<div class='divclass2' id='div2'>
|
12
|
+
<a href='/link/1' id='link1'>click1</a>
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
<div class='divclass1' id='div3'>
|
16
|
+
<div class='divclass2' id='div4'>
|
17
|
+
<a href='/link/2' id='link2'>click2</a>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
<div id='hidden_text'>I am <span id='hidden_portion' style='display:none;'>hidden </span>text</div>
|
21
|
+
<form id='testform'>
|
22
|
+
<input type='text' name='text_field' id='text_field' value='monkeys'></input>
|
23
|
+
<input type='text' name='disabled_text' id='disabled_text' disabled='1'></input>
|
24
|
+
<select name='select_field' id='select_field' multiple='1'>
|
25
|
+
<option value="volvo" id='volvo'>Volvo</option>
|
26
|
+
<option value="saab" id='saab'>Saab</option>
|
27
|
+
<option value="mercedes" id='mercedes'>Mercedes</option>
|
28
|
+
<option value="audi" id='audi'>Audi</option>
|
29
|
+
</select>
|
30
|
+
<fieldset id='fieldset1'>
|
31
|
+
<input type='checkbox' id='checkbox_field' name='checkbox_field' value='raisins'></input>
|
32
|
+
<input type='checkbox' id='checkbox_field2' name='checkbox_field2' value='peanuts' checked='1'></input>
|
33
|
+
<input type='radio' id='male' name='sex' value='male'>Male</input>
|
34
|
+
<input type='radio' id='female' name='sex' value='female'>Female</input>
|
35
|
+
<input type='file' id='file_field' name='file_field'></input>
|
36
|
+
</fieldset>
|
37
|
+
<button type="button" name='button1' id='button1' onclick='window.location="/button/1"'>Imma button!</button>
|
38
|
+
</form>
|
39
|
+
<table id='table1'>
|
40
|
+
<tr>
|
41
|
+
<td>cell 1,1</td>
|
42
|
+
<td>cell 1,2</td>
|
43
|
+
</tr>
|
44
|
+
<tr>
|
45
|
+
<td>cell 2,1</td>
|
46
|
+
<td>cell 2,2</td>
|
47
|
+
</tr>
|
48
|
+
</table>
|
49
|
+
<iframe id='myframe' name='myframe' src='/iframe'>
|
50
|
+
</iframe>
|
51
|
+
</body>
|
52
|
+
</html>
|
53
|
+
HTML
|
54
|
+
end
|
55
|
+
|
56
|
+
def call(env)
|
57
|
+
request = Rack::Request.new(env)
|
58
|
+
if request.path == '/iframe'
|
59
|
+
[200, {"Content-Type" => "text/html"}, ["<html><body><div id='inframe'></div></body></html>"]]
|
60
|
+
else
|
61
|
+
[200, {"Content-Type" => "text/html"}, [get_html(request)]]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
metadata
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: capybarbecue
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrew DiMichele
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-08-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: capybara
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.1.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.1.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rr
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rdoc
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: bundler
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: jeweler
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: poltergeist
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: launchy
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - '>='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - '>='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
description: Makes fundamental changes to Capybara's threading architecture so you
|
154
|
+
can write stable tests with a shared database connection.
|
155
|
+
email: backflip@gmail.com
|
156
|
+
executables: []
|
157
|
+
extensions: []
|
158
|
+
extra_rdoc_files:
|
159
|
+
- LICENSE.txt
|
160
|
+
- README.rdoc
|
161
|
+
files:
|
162
|
+
- .document
|
163
|
+
- .rspec
|
164
|
+
- Gemfile
|
165
|
+
- Gemfile.lock
|
166
|
+
- LICENSE.txt
|
167
|
+
- NOTES.txt
|
168
|
+
- README.rdoc
|
169
|
+
- Rakefile
|
170
|
+
- lib/capybarbecue.rb
|
171
|
+
- lib/capybarbecue/async_call.rb
|
172
|
+
- lib/capybarbecue/async_delegate_class.rb
|
173
|
+
- lib/capybarbecue/rack_runner.rb
|
174
|
+
- lib/capybarbecue/server.rb
|
175
|
+
- lib/capybarbecue/version.rb
|
176
|
+
- spec/capybarbecue/async_call_spec.rb
|
177
|
+
- spec/capybarbecue/async_delegate_class_spec.rb
|
178
|
+
- spec/capybarbecue/rack_runner_spec.rb
|
179
|
+
- spec/capybarbecue/server_spec.rb
|
180
|
+
- spec/capybarbecue_spec.rb
|
181
|
+
- spec/integration/element_spec.rb
|
182
|
+
- spec/integration/session_spec.rb
|
183
|
+
- spec/spec_helper.rb
|
184
|
+
- spec/support/test_rack_app.rb
|
185
|
+
homepage: http://github.com/adimichele/capybarbecue
|
186
|
+
licenses:
|
187
|
+
- MIT
|
188
|
+
metadata: {}
|
189
|
+
post_install_message:
|
190
|
+
rdoc_options: []
|
191
|
+
require_paths:
|
192
|
+
- lib
|
193
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
194
|
+
requirements:
|
195
|
+
- - '>='
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
199
|
+
requirements:
|
200
|
+
- - '>='
|
201
|
+
- !ruby/object:Gem::Version
|
202
|
+
version: '0'
|
203
|
+
requirements: []
|
204
|
+
rubyforge_project:
|
205
|
+
rubygems_version: 2.0.3
|
206
|
+
signing_key:
|
207
|
+
specification_version: 4
|
208
|
+
summary: Makes your Capybara test suite work better
|
209
|
+
test_files: []
|
210
|
+
has_rdoc:
|