happybara 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/happybara.rb +9 -0
- data/lib/happybara/agent.rb +117 -0
- data/lib/happybara/callbacks.rb +49 -0
- data/lib/happybara/executor.rb +133 -0
- data/lib/happybara/registry.rb +29 -0
- data/lib/happybara/serializer.rb +39 -0
- data/lib/happybara/synchronizer.rb +94 -0
- data/lib/happybara/version.rb +3 -0
- data/spec/lib/happybara/agent_spec.rb +7 -0
- data/spec/lib/happybara/serializer_spec.rb +19 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/bootstrap.rb +38 -0
- data/spec/support/log/test.log +0 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f07111257fd0128b45efe622094b7cbadb1904e2
|
4
|
+
data.tar.gz: d9dd81e7fe06f279ae1590f17ae5513ba686e0c7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ceae202a3842453f47586a01e7a5818e26f3b96e7da9a226d162edee0153f8a6ef7a6eab40c4216fc8c20dafbc1d4edfeb90e52f2ca47ee0c75d712cfdac8db1
|
7
|
+
data.tar.gz: 84b24d10b0b2af0a9a4f1583b99d4cefe71e7ff17067c6464078487266f376994a97d8fccc76479e0653ced15288b68335d0593b0f1ff9d9199d0856d8587c2f
|
data/lib/happybara.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require_relative './happybara/agent'
|
2
|
+
require_relative './happybara/callbacks'
|
3
|
+
require_relative './happybara/executor'
|
4
|
+
require_relative './happybara/serializer'
|
5
|
+
require_relative './happybara/synchronizer'
|
6
|
+
require_relative './happybara/version'
|
7
|
+
|
8
|
+
module Happybara
|
9
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
require 'singleton'
|
3
|
+
require_relative './callbacks'
|
4
|
+
require_relative './serializer'
|
5
|
+
require_relative './synchronizer'
|
6
|
+
require_relative './registry'
|
7
|
+
|
8
|
+
module Happybara
|
9
|
+
class Agent
|
10
|
+
include Callbacks
|
11
|
+
|
12
|
+
attr_accessor :grace_timeout
|
13
|
+
|
14
|
+
# Create a new Agent instance and configure it.
|
15
|
+
#
|
16
|
+
# @yield [Happybara::Agent] instance
|
17
|
+
# The new agent instance. The block will evaluated in this instance's
|
18
|
+
# scope so you can call methods like #on and #before() globally.
|
19
|
+
#
|
20
|
+
# @return [Happybara::Agent]
|
21
|
+
def self.create(&block)
|
22
|
+
Happybara::Agent.new.tap do |instance|
|
23
|
+
instance.instance_exec do
|
24
|
+
block[self]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(serializer: Serializer.new)
|
30
|
+
@serializer = serializer
|
31
|
+
@reflections = {}
|
32
|
+
@mutex = Synchronizer.new({
|
33
|
+
on_abrupt_termination: ->() {
|
34
|
+
run_callbacks(:abrupt_termination)
|
35
|
+
run_after_example
|
36
|
+
},
|
37
|
+
|
38
|
+
on_forced_release: ->() {
|
39
|
+
run_callbacks(:forced_release)
|
40
|
+
run_after_example
|
41
|
+
},
|
42
|
+
|
43
|
+
on_race_condition: ->() {
|
44
|
+
run_callbacks(:race_condition)
|
45
|
+
}
|
46
|
+
})
|
47
|
+
end
|
48
|
+
|
49
|
+
# Bind the Agent to a websocket to start a testing session.
|
50
|
+
#
|
51
|
+
# @param [Websocket] socket
|
52
|
+
# @param [Object] actor
|
53
|
+
# An instance of an object that will be receiving the RPC calls. Any
|
54
|
+
# `eval` RPCs will be evaluated in this object's context. If you're
|
55
|
+
# using Tubesock and hijacking a Rails controller, you can pass the
|
56
|
+
# controller itself as an actor, then you also have access to
|
57
|
+
# ApplicationController methods and helpers as RPCs.
|
58
|
+
#
|
59
|
+
# @return [NilClass]
|
60
|
+
def bind(socket, actor)
|
61
|
+
@mutex.lock(socket: socket, timeout: grace_timeout) do
|
62
|
+
executor = Executor.new(actor: actor, socket: socket, serializer: @serializer)
|
63
|
+
executor.run_before_example = method(:run_before_example)
|
64
|
+
executor.run_after_example = method(:run_after_example)
|
65
|
+
executor.run_around_command = method(:run_around_command)
|
66
|
+
|
67
|
+
socket.onopen do
|
68
|
+
puts "Happybara: client connected.".green
|
69
|
+
end
|
70
|
+
|
71
|
+
socket.onmessage do |payload|
|
72
|
+
executor.handle_message(payload)
|
73
|
+
end
|
74
|
+
|
75
|
+
socket.onclose do
|
76
|
+
puts "Happybara: client disconnected.".green
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
|
83
|
+
def on_forced_release(&callback)
|
84
|
+
on(:forced_release, &callback)
|
85
|
+
end
|
86
|
+
|
87
|
+
def on_abrupt_termination(&callback)
|
88
|
+
on(:abrupt_termination, &callback)
|
89
|
+
end
|
90
|
+
|
91
|
+
def on_race_condition(&callback)
|
92
|
+
on(:race_condition, &callback)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def run_before_example
|
98
|
+
run_callbacks(:before_each)
|
99
|
+
end
|
100
|
+
|
101
|
+
def run_after_example
|
102
|
+
run_callbacks(:after_each)
|
103
|
+
@mutex.release
|
104
|
+
end
|
105
|
+
|
106
|
+
def run_around_command(message, &block)
|
107
|
+
# TODO: support nested procs.. ?
|
108
|
+
callback = callbacks[:around_command] && callbacks[:around_command].first
|
109
|
+
|
110
|
+
if callback
|
111
|
+
callback.call(message, block)
|
112
|
+
else
|
113
|
+
block.call
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Happybara
|
2
|
+
module Callbacks
|
3
|
+
def before(stage, &callback)
|
4
|
+
case stage.to_sym
|
5
|
+
when :each
|
6
|
+
on(:before_each, &callback)
|
7
|
+
when :all
|
8
|
+
Rails.configuration.after_initialize(&callback)
|
9
|
+
else
|
10
|
+
fail "Unknown :before stage '#{stage}'"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def after(stage, &callback)
|
15
|
+
case stage.to_sym
|
16
|
+
when :each
|
17
|
+
on(:after_each, &callback)
|
18
|
+
else
|
19
|
+
fail "Unknown :after stage '#{stage}'"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def around(stage, &callback)
|
24
|
+
case stage.to_sym
|
25
|
+
when :command
|
26
|
+
on(:around_command, &callback)
|
27
|
+
else
|
28
|
+
fail "Unknown :around stage '#{stage}'"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def on(event, &handler)
|
33
|
+
callbacks[event.to_sym] ||= []
|
34
|
+
callbacks[event.to_sym] << handler
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def callbacks
|
40
|
+
@callbacks ||= {}
|
41
|
+
end
|
42
|
+
|
43
|
+
def run_callbacks(event)
|
44
|
+
callbacks.fetch(event).each do |callback|
|
45
|
+
callback.call
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Happybara
|
2
|
+
class Executor
|
3
|
+
attr_accessor :run_before_example, :run_after_example, :run_around_command
|
4
|
+
|
5
|
+
def initialize(actor:, socket:, serializer:)
|
6
|
+
@actor = actor
|
7
|
+
@socket = socket
|
8
|
+
@serializer = serializer
|
9
|
+
@registry = Registry.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def handle_message(payload)
|
13
|
+
message = @serializer.deserialize payload
|
14
|
+
respond = method(:respond_to_message).curry[message['id']]
|
15
|
+
|
16
|
+
case message['type']
|
17
|
+
when 'SETUP'
|
18
|
+
handle_setup(message, &respond)
|
19
|
+
when 'RPC'
|
20
|
+
handle_rpc(message, &respond)
|
21
|
+
when 'EVAL'
|
22
|
+
handle_eval(message, &respond)
|
23
|
+
when 'QUERY'
|
24
|
+
handle_query(message, &respond)
|
25
|
+
when 'REFLECT'
|
26
|
+
handle_reflect(message, &respond)
|
27
|
+
when 'TEARDOWN'
|
28
|
+
handle_teardown(message, &respond)
|
29
|
+
else
|
30
|
+
fail "unknown message type #{message['type']}"
|
31
|
+
end
|
32
|
+
rescue StandardError => e
|
33
|
+
puts "#{'=' * 80}\nHandler error!\n#{'-' * 80}"
|
34
|
+
puts e.message
|
35
|
+
puts e.backtrace
|
36
|
+
|
37
|
+
respond['error', { details: e.message, backtrace: e.backtrace }]
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def respond_to_message(message_id, type, data)
|
43
|
+
@socket.send_data({
|
44
|
+
id: message_id,
|
45
|
+
type: type,
|
46
|
+
data: data
|
47
|
+
}.to_json)
|
48
|
+
end
|
49
|
+
|
50
|
+
def handle_setup(message, &respond)
|
51
|
+
run_before_example[]
|
52
|
+
|
53
|
+
respond['SETUP_RESPONSE', @serializer.serialize_value(true)]
|
54
|
+
end
|
55
|
+
|
56
|
+
def handle_query(message, &respond)
|
57
|
+
@registry.open!(message['data']['klass_name'])
|
58
|
+
|
59
|
+
klass = message['data']['klass_name'].constantize
|
60
|
+
klass.public_instance_methods(true)
|
61
|
+
.sort
|
62
|
+
.map(&:to_s)
|
63
|
+
.reject { |s| s[0] =~ /[\W|_]/ }
|
64
|
+
.tap do |visible_instance_methods|
|
65
|
+
respond['QUERY_RESPONSE', @serializer.serialize({
|
66
|
+
instance_methods: visible_instance_methods
|
67
|
+
})]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def handle_rpc(message, &respond)
|
72
|
+
method_name = message['data']['procedure']
|
73
|
+
method_args = message['data']['payload'] || {}
|
74
|
+
method_opts = message['data']['options'] || {}
|
75
|
+
|
76
|
+
fail "Unknown RPC #{method_name}" unless @actor.respond_to?(method_name)
|
77
|
+
|
78
|
+
result = around_command(message) do
|
79
|
+
@actor.send(message['data']['procedure'], **method_args.symbolize_keys)
|
80
|
+
end
|
81
|
+
|
82
|
+
@registry << result
|
83
|
+
|
84
|
+
respond['RPC_RESPONSE', @serializer.serialize_value(result)]
|
85
|
+
end
|
86
|
+
|
87
|
+
def handle_eval(message, &respond)
|
88
|
+
string = message['data']['string']
|
89
|
+
options = message['data']['options'] || {}
|
90
|
+
|
91
|
+
around_command(message) do
|
92
|
+
result = @actor.instance_eval { eval(string) }
|
93
|
+
@registry << result
|
94
|
+
respond['EVAL_RESPONSE', @serializer.serialize_value(result)]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def handle_reflect(message, &respond)
|
99
|
+
object_id = message['data']['object_id']
|
100
|
+
klass_name = message['data']['klass_name']
|
101
|
+
method_name = message['data']['method']
|
102
|
+
method_args = message['data']['arguments']
|
103
|
+
method_opts = message['data']['options'] || {}
|
104
|
+
|
105
|
+
ref = resolve_reference(message['data'])
|
106
|
+
|
107
|
+
fail "Object #{object_id} of type #{klass_name} could not be reflected" if ref.nil?
|
108
|
+
|
109
|
+
rc = around_command(message) do
|
110
|
+
ref.send(method_name, *method_args)
|
111
|
+
end
|
112
|
+
|
113
|
+
@registry << rc
|
114
|
+
|
115
|
+
respond['REFLECT_RESPONSE', @serializer.serialize_value(rc)]
|
116
|
+
end
|
117
|
+
|
118
|
+
def handle_teardown(message, &respond)
|
119
|
+
@registry.release!
|
120
|
+
run_after_example[]
|
121
|
+
|
122
|
+
respond['TEARDOWN_RESPONSE', @serializer.serialize_value(true)]
|
123
|
+
end
|
124
|
+
|
125
|
+
def around_command(message, &block)
|
126
|
+
run_around_command[message['data'], &block]
|
127
|
+
end
|
128
|
+
|
129
|
+
def resolve_reference(descriptor)
|
130
|
+
@registry[descriptor['object_id']]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Happybara
|
2
|
+
class Registry
|
3
|
+
def initialize()
|
4
|
+
@reflections = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def open!(klass_name)
|
8
|
+
@reflections[klass_name.to_s] = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def <<(object)
|
12
|
+
self.tap do
|
13
|
+
klass_name = object.class.name.to_s
|
14
|
+
|
15
|
+
if @reflections[klass_name]
|
16
|
+
@reflections[klass_name][object.object_id] = object
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](object_id)
|
22
|
+
ObjectSpace._id2ref(object_id) || nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def release!()
|
26
|
+
@reflections.clear
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Happybara
|
2
|
+
class Serializer
|
3
|
+
def serialize(result)
|
4
|
+
result.as_json
|
5
|
+
end
|
6
|
+
|
7
|
+
def serialize_value(result)
|
8
|
+
{
|
9
|
+
klass: result.class.name,
|
10
|
+
object_id: result.object_id,
|
11
|
+
value: result.as_json
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def serialize_reference(ref)
|
16
|
+
{
|
17
|
+
klass: ref.class.name,
|
18
|
+
object_id: ref.object_id,
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def deserialize(payload)
|
23
|
+
JSON.load(payload, ->(datum) {
|
24
|
+
case datum
|
25
|
+
when Hash
|
26
|
+
datum.each_pair do |key, value|
|
27
|
+
if value.is_a?(Hash) && value['$$ref'] == true && value['$$object_id']
|
28
|
+
datum[key] = ObjectSpace._id2ref(value['$$object_id']) || nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
datum
|
33
|
+
else
|
34
|
+
datum
|
35
|
+
end
|
36
|
+
})
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Happybara
|
2
|
+
class Synchronizer
|
3
|
+
def initialize(
|
4
|
+
on_forced_release:,
|
5
|
+
on_abrupt_termination:,
|
6
|
+
on_race_condition: nil
|
7
|
+
)
|
8
|
+
@on_forced_release = on_forced_release || ->() {}
|
9
|
+
@on_abrupt_termination = on_abrupt_termination || ->() {}
|
10
|
+
@on_race_condition = on_race_condition || ->() {}
|
11
|
+
@mutex = Mutex.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def lock(socket:, timeout: 5.0, frequency: 0.1, &block)
|
15
|
+
socket.onopen do
|
16
|
+
acquire!(timeout: timeout, frequency: frequency, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
socket.onclose do
|
20
|
+
@on_abrupt_termination[] if @mutex.owned? && @mutex.locked?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def release
|
25
|
+
@mutex.unlock
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def acquire!(timeout:, frequency:, &block)
|
31
|
+
if @mutex.locked? && !@mutex.owned?
|
32
|
+
fail "
|
33
|
+
The Happybara agent is being accessed by multiple threads. This
|
34
|
+
mode of operation is not currently supported.
|
35
|
+
|
36
|
+
(This most likely indicates that you have multiple client runners
|
37
|
+
communicating with this Rails server concurrently.)
|
38
|
+
"
|
39
|
+
elsif @mutex.locked?
|
40
|
+
@on_race_condition[]
|
41
|
+
|
42
|
+
begin
|
43
|
+
Timer.new(frequency: frequency, timeout: timeout)
|
44
|
+
.until { vacant? }
|
45
|
+
.otherwise { @on_forced_release[] }
|
46
|
+
.start!
|
47
|
+
rescue TimeoutError
|
48
|
+
@on_forced_release[]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
@mutex.lock
|
53
|
+
|
54
|
+
yield
|
55
|
+
end
|
56
|
+
|
57
|
+
def vacant?
|
58
|
+
!@mutex.locked?
|
59
|
+
end
|
60
|
+
|
61
|
+
class Timer
|
62
|
+
def initialize(frequency:, timeout:)
|
63
|
+
@frequency = frequency
|
64
|
+
@timeout = timeout
|
65
|
+
@until = nil
|
66
|
+
@otherwise = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def until(&block)
|
70
|
+
@until = block
|
71
|
+
end
|
72
|
+
|
73
|
+
def otherwise(&block)
|
74
|
+
@otherwise = block
|
75
|
+
end
|
76
|
+
|
77
|
+
def start!
|
78
|
+
elapsed = 0.0
|
79
|
+
|
80
|
+
while !@until[]
|
81
|
+
elapsed += frequency
|
82
|
+
|
83
|
+
if elapsed < timeout
|
84
|
+
sleep frequency
|
85
|
+
elsif @otherwise
|
86
|
+
@otherwise[]
|
87
|
+
else
|
88
|
+
raise TimeoutError.new
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Happybara::Serializer do
|
5
|
+
describe '#deserialize' do
|
6
|
+
it 'resolves references' do
|
7
|
+
some_object = Object.new
|
8
|
+
|
9
|
+
result = subject.deserialize({
|
10
|
+
foo: {
|
11
|
+
'$$ref': true,
|
12
|
+
'$$object_id': some_object.object_id
|
13
|
+
}
|
14
|
+
}.to_json)
|
15
|
+
|
16
|
+
expect(result['foo']).to be(some_object)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
ENV['RAILS_ENV'] ||= "test"
|
2
|
+
|
3
|
+
require 'pry'
|
4
|
+
require_relative '../lib/happybara'
|
5
|
+
require_relative './support/bootstrap'
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.expect_with :rspec do |expectations|
|
9
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
10
|
+
end
|
11
|
+
|
12
|
+
config.mock_with :rspec do |mocks|
|
13
|
+
mocks.verify_partial_doubles = true
|
14
|
+
end
|
15
|
+
|
16
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
17
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# This dance is necessary to subvert railtie from resolving get_smart's Rails
|
2
|
+
# application as the "Rails.application"; we really only need a dummy
|
3
|
+
# application that can host some routes, and if this gem was sourced elsewhere
|
4
|
+
# we could just define such an application directly but since it's sourced
|
5
|
+
# within get_smart, railtie will resolve the root `config.ru` and cause sadness.
|
6
|
+
#
|
7
|
+
# Be happy, unless you're upgrading Rails and this gets broken. :-)
|
8
|
+
|
9
|
+
require 'rails/application'
|
10
|
+
require 'rails/configuration'
|
11
|
+
require 'rails/railtie'
|
12
|
+
require 'active_support'
|
13
|
+
|
14
|
+
module Rails
|
15
|
+
class << self
|
16
|
+
attr_writer :app_class, :application
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module Happybara
|
21
|
+
class TestApplication < ::Rails::Application
|
22
|
+
class << self
|
23
|
+
def find_root(*)
|
24
|
+
Pathname.new File.realpath File.expand_path(File.dirname(__FILE__))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
require 'rails'
|
31
|
+
require 'action_view'
|
32
|
+
require 'action_controller'
|
33
|
+
|
34
|
+
Happybara::TestApplication.configure do
|
35
|
+
config.eager_load = ENV['COVERAGE'] != '1'
|
36
|
+
end
|
37
|
+
|
38
|
+
Happybara::TestApplication.initialize!
|
File without changes
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: happybara
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ahmad Amireh
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-04-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '6'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '5'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '6'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rspec
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '3.5'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '3.5'
|
47
|
+
description: |
|
48
|
+
Happybara is an integration testing platform providing a webserver in Ruby that
|
49
|
+
accepts connections over a websocket where test clients can dispatch RPCs to
|
50
|
+
Ruby from a different language like JavaScript.
|
51
|
+
email:
|
52
|
+
- ahmad@amireh.net
|
53
|
+
executables: []
|
54
|
+
extensions: []
|
55
|
+
extra_rdoc_files: []
|
56
|
+
files:
|
57
|
+
- lib/happybara.rb
|
58
|
+
- lib/happybara/agent.rb
|
59
|
+
- lib/happybara/callbacks.rb
|
60
|
+
- lib/happybara/executor.rb
|
61
|
+
- lib/happybara/registry.rb
|
62
|
+
- lib/happybara/serializer.rb
|
63
|
+
- lib/happybara/synchronizer.rb
|
64
|
+
- lib/happybara/version.rb
|
65
|
+
- spec/lib/happybara/agent_spec.rb
|
66
|
+
- spec/lib/happybara/serializer_spec.rb
|
67
|
+
- spec/spec_helper.rb
|
68
|
+
- spec/support/bootstrap.rb
|
69
|
+
- spec/support/log/test.log
|
70
|
+
homepage: https://github.com/amireh/happybara
|
71
|
+
licenses:
|
72
|
+
- BSD-3
|
73
|
+
metadata: {}
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 2.4.5.1
|
91
|
+
signing_key:
|
92
|
+
specification_version: 4
|
93
|
+
summary: An integration test server using Websockets.
|
94
|
+
test_files:
|
95
|
+
- spec/lib/happybara/agent_spec.rb
|
96
|
+
- spec/lib/happybara/serializer_spec.rb
|
97
|
+
- spec/spec_helper.rb
|
98
|
+
- spec/support/bootstrap.rb
|
99
|
+
- spec/support/log/test.log
|
100
|
+
has_rdoc:
|