kookaburra 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.travis.yml +1 -3
- data/README.markdown +259 -181
- data/Rakefile +11 -2
- data/kookaburra.gemspec +1 -0
- data/lib/kookaburra.rb +24 -9
- data/lib/kookaburra/api_client.rb +1 -1
- data/lib/kookaburra/api_driver.rb +1 -1
- data/lib/kookaburra/assertion.rb +1 -1
- data/lib/kookaburra/configuration.rb +25 -9
- data/lib/kookaburra/configuration/proxy.rb +55 -0
- data/lib/kookaburra/dependency_accessor.rb +7 -0
- data/lib/kookaburra/exceptions.rb +11 -3
- data/lib/kookaburra/mental_model.rb +5 -5
- data/lib/kookaburra/rack_app_server.rb +3 -12
- data/lib/kookaburra/test_helpers.rb +53 -12
- data/lib/kookaburra/ui_driver/ui_component.rb +9 -2
- data/lib/kookaburra/version.rb +1 -1
- data/spec/integration/test_a_rack_application_spec.rb +3 -2
- data/spec/integration/test_multiple_applications_spec.rb +96 -0
- data/spec/kookaburra/api_client_spec.rb +6 -5
- data/spec/kookaburra/api_driver_spec.rb +18 -0
- data/spec/kookaburra/configuration/proxy_spec.rb +39 -0
- data/spec/kookaburra/configuration_spec.rb +54 -2
- data/spec/kookaburra/mental_model_spec.rb +22 -20
- data/spec/kookaburra/test_helpers_spec.rb +60 -24
- data/spec/kookaburra/ui_driver/scoped_browser_spec.rb +3 -2
- data/spec/kookaburra/ui_driver/ui_component/address_bar_spec.rb +2 -1
- data/spec/kookaburra/ui_driver/ui_component_spec.rb +48 -14
- data/spec/kookaburra/ui_driver_spec.rb +4 -3
- data/spec/kookaburra_spec.rb +19 -14
- data/spec/spec_helper.rb +79 -0
- data/spec/support/shared_examples/it_can_have_ui_components.rb +2 -2
- data/spec/support/shared_examples/it_can_make_assertions.rb +3 -3
- data/spec/support/shared_examples/it_has_a_dependency_accessor.rb +3 -4
- metadata +25 -2
data/Rakefile
CHANGED
@@ -3,8 +3,17 @@
|
|
3
3
|
require 'bundler/gem_tasks'
|
4
4
|
require 'rspec/core/rake_task'
|
5
5
|
|
6
|
-
task :default => :spec
|
7
|
-
|
6
|
+
task :default => [:spec, 'spec:slow']
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new do |t|
|
9
|
+
t.rspec_opts = "-t ~slow"
|
10
|
+
end
|
11
|
+
|
12
|
+
namespace :spec do
|
13
|
+
RSpec::Core::RakeTask.new(:slow) do |t|
|
14
|
+
t.rspec_opts = "-t slow"
|
15
|
+
end
|
16
|
+
end
|
8
17
|
|
9
18
|
desc 'Runs reek to detect code smells'
|
10
19
|
task :reek do
|
data/kookaburra.gemspec
CHANGED
data/lib/kookaburra.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'forwardable'
|
1
2
|
require 'kookaburra/exceptions'
|
2
3
|
require 'kookaburra/mental_model'
|
3
4
|
require 'kookaburra/api_driver'
|
@@ -17,21 +18,31 @@ require 'kookaburra/configuration'
|
|
17
18
|
#
|
18
19
|
# @see Kookaburra::TestHelpers
|
19
20
|
class Kookaburra
|
21
|
+
extend Forwardable
|
22
|
+
|
20
23
|
class << self
|
21
24
|
# Stores the configuration object that is used by default when creating new
|
22
25
|
# instances of Kookaburra
|
23
26
|
#
|
24
|
-
# @return [Kookaburra::Configuration]
|
27
|
+
# @return [Kookaburra::Configuration]
|
25
28
|
def configuration
|
26
29
|
@configuration ||= Configuration.new
|
27
30
|
end
|
28
31
|
|
29
|
-
# Yields
|
32
|
+
# Yields a new configuration so that it can be modified
|
33
|
+
#
|
34
|
+
# The new configuration object will now be used by default when creating
|
35
|
+
# instances of Kookaburra.
|
30
36
|
#
|
31
37
|
# @yield [Kookaburra::Configuration]
|
32
38
|
def configure(&blk)
|
39
|
+
self.configuration = Configuration.new
|
33
40
|
blk.call(configuration)
|
34
41
|
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_writer :configuration
|
35
46
|
end
|
36
47
|
|
37
48
|
# Returns a new Kookaburra instance that wires together your application's
|
@@ -39,10 +50,7 @@ class Kookaburra
|
|
39
50
|
#
|
40
51
|
# @param [Kookaburra::Configuration] configuration (Kookaburra.configuration)
|
41
52
|
def initialize(configuration = Kookaburra.configuration)
|
42
|
-
|
43
|
-
@configuration.mental_model = MentalModel.new
|
44
|
-
@api_driver_class = configuration.api_driver_class
|
45
|
-
@ui_driver_class = configuration.ui_driver_class
|
53
|
+
self.configuration = configuration
|
46
54
|
end
|
47
55
|
|
48
56
|
# Returns an instance of your APIDriver class configured to share test
|
@@ -50,7 +58,7 @@ class Kookaburra
|
|
50
58
|
#
|
51
59
|
# @return [Kookaburra::APIDriver]
|
52
60
|
def api
|
53
|
-
@api ||=
|
61
|
+
@api ||= api_driver_class.new(configuration)
|
54
62
|
end
|
55
63
|
|
56
64
|
# Returns an instance of your UIDriver class configured to share test fixture
|
@@ -59,7 +67,7 @@ class Kookaburra
|
|
59
67
|
#
|
60
68
|
# @return [Kookaburra::UIDriver]
|
61
69
|
def ui
|
62
|
-
@ui ||=
|
70
|
+
@ui ||= ui_driver_class.new(configuration)
|
63
71
|
end
|
64
72
|
|
65
73
|
# Returns a deep-dup of the specified {MentalModel::Collection}.
|
@@ -75,6 +83,13 @@ class Kookaburra
|
|
75
83
|
#
|
76
84
|
# @return [Kookaburra::MentalModel::Collection]
|
77
85
|
def get_data(collection_name)
|
78
|
-
|
86
|
+
mental_model.send(collection_name).dup
|
79
87
|
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
attr_accessor :configuration
|
92
|
+
|
93
|
+
def_delegators :configuration, :api_driver_class, :ui_driver_class,
|
94
|
+
:mental_model
|
80
95
|
end
|
@@ -7,7 +7,7 @@ class Kookaburra
|
|
7
7
|
#
|
8
8
|
# You will create a subclass of {APIClient} in your testing
|
9
9
|
# implementation to be used with you subclass of
|
10
|
-
# {Kookaburra::APIDriver}. While the {
|
10
|
+
# {Kookaburra::APIDriver}. While the {APIDriver} implements the
|
11
11
|
# "business domain" DSL for setting up your application state, the
|
12
12
|
# {APIClient} maps discreet operations to your application's web
|
13
13
|
# service API and can (optionally) handle encoding input data and
|
@@ -36,7 +36,7 @@ class Kookaburra
|
|
36
36
|
extend Forwardable
|
37
37
|
|
38
38
|
# It is unlikely that you would call #initialize yourself; your APIDriver
|
39
|
-
# object is instantiated for you by {Kookaburra#
|
39
|
+
# object is instantiated for you by {Kookaburra#api}.
|
40
40
|
#
|
41
41
|
# @param [Kookaburra::Configuration] configuration
|
42
42
|
def initialize(configuration)
|
data/lib/kookaburra/assertion.rb
CHANGED
@@ -18,7 +18,7 @@ class Kookaburra
|
|
18
18
|
#
|
19
19
|
# @raise [Kookaburra::AssertionFailed] raised if test evaluates to false
|
20
20
|
def assert(test, message = "You might want to provide a better message, eh?")
|
21
|
-
test or raise AssertionFailed, message
|
21
|
+
(!!test == true) or raise AssertionFailed, message
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'ostruct'
|
2
2
|
require 'delegate'
|
3
3
|
require 'kookaburra/dependency_accessor'
|
4
|
+
require 'kookaburra/mental_model'
|
5
|
+
require 'kookaburra/configuration/proxy'
|
4
6
|
|
5
7
|
class Kookaburra
|
6
8
|
# Provides access to the configuration data used throughout Kookaburra
|
@@ -38,15 +40,6 @@ class Kookaburra
|
|
38
40
|
# without it having been set
|
39
41
|
dependency_accessor :app_host
|
40
42
|
|
41
|
-
# This is the {Kookaburra::MentalModel} that is shared between your
|
42
|
-
# APIDriver and your UIDriver. This attribute is managed by {Kookaburra},
|
43
|
-
# so you shouldn't need to change it yourself.
|
44
|
-
#
|
45
|
-
# @attribute [rw] mental_model
|
46
|
-
# @raise [Kookaburra::ConfigurationError] if you try to read this attribute
|
47
|
-
# without it having been set
|
48
|
-
dependency_accessor :mental_model
|
49
|
-
|
50
43
|
# This is the logger to which Kookaburra will send various messages
|
51
44
|
# about its operation. This would generally be used to allow
|
52
45
|
# UIDriver subclasses to provide detailed failure information.
|
@@ -84,5 +77,28 @@ class Kookaburra
|
|
84
77
|
def app_host_uri
|
85
78
|
URI.parse(app_host)
|
86
79
|
end
|
80
|
+
|
81
|
+
# This is the {Kookaburra::MentalModel} that is shared between your
|
82
|
+
# APIDriver and your UIDriver. This attribute is managed by {Kookaburra},
|
83
|
+
# so you shouldn't need to change it yourself.
|
84
|
+
def mental_model
|
85
|
+
@mental_model ||= MentalModel.new
|
86
|
+
end
|
87
|
+
|
88
|
+
attr_writer :mental_model
|
89
|
+
|
90
|
+
def application(name, &block)
|
91
|
+
proxy = Proxy.new(name: name, basis: self)
|
92
|
+
block.call(proxy) if block_given?
|
93
|
+
applications[name] = Kookaburra.new(proxy)
|
94
|
+
end
|
95
|
+
|
96
|
+
def applications
|
97
|
+
@applications ||= {}
|
98
|
+
end
|
99
|
+
|
100
|
+
def has_named_applications?
|
101
|
+
!applications.empty?
|
102
|
+
end
|
87
103
|
end
|
88
104
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
class Kookaburra
|
4
|
+
class Configuration
|
5
|
+
# Used to manage configuration for a named application
|
6
|
+
#
|
7
|
+
# `Configuration::Proxy` objects delegate to their basis object to retrieve
|
8
|
+
# default values. This allows a base configuration to be specified and
|
9
|
+
# inherited by each application configuration.
|
10
|
+
#
|
11
|
+
# @see Kookaburra::Configuration#application
|
12
|
+
# @private
|
13
|
+
class Proxy < DelegateClass(Configuration)
|
14
|
+
attr_reader :name
|
15
|
+
|
16
|
+
# Builds a new Proxy
|
17
|
+
#
|
18
|
+
# @param [String] name An (arbitrary) identifier for this configuration
|
19
|
+
# @param [Kookaburra::Configuration] The object that will be used as the source for default values
|
20
|
+
def initialize(name:, basis:)
|
21
|
+
self.name = name
|
22
|
+
self.basis = basis
|
23
|
+
__setobj__(basis)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_writer :name
|
29
|
+
attr_accessor :basis
|
30
|
+
|
31
|
+
def method_missing(method_name, *args, &block)
|
32
|
+
define_proxy_override(method_name, args.first) \
|
33
|
+
|| super
|
34
|
+
end
|
35
|
+
|
36
|
+
def define_proxy_override(writer_name, value)
|
37
|
+
return false unless valid_attr_writer?(writer_name)
|
38
|
+
reader_name = reader_name_for writer_name
|
39
|
+
(class << self; self; end).class_eval do
|
40
|
+
define_method(reader_name) { value }
|
41
|
+
end
|
42
|
+
return true
|
43
|
+
end
|
44
|
+
|
45
|
+
def valid_attr_writer?(writer_name)
|
46
|
+
basis.respond_to?(writer_name) \
|
47
|
+
&& /=$/ =~ writer_name.to_s
|
48
|
+
end
|
49
|
+
|
50
|
+
def reader_name_for(writer_name)
|
51
|
+
writer_name.to_s.sub(/=$/, '')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -12,13 +12,20 @@ class Kookaburra
|
|
12
12
|
private
|
13
13
|
|
14
14
|
def define_dependency_accessor(name)
|
15
|
+
define_attr_reader(name)
|
16
|
+
define_attr_writer(name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def define_attr_reader(name)
|
15
20
|
define_method(name) do
|
16
21
|
class_name = self.class.name
|
17
22
|
class_name.sub!(/^$/, 'an Anonymous Class!!!')
|
18
23
|
instance_variable_get("@#{name}") or raise "No %s object was set on %s initialization." \
|
19
24
|
% [name, class_name]
|
20
25
|
end
|
26
|
+
end
|
21
27
|
|
28
|
+
def define_attr_writer(name)
|
22
29
|
define_method("#{name}=") do |value|
|
23
30
|
instance_variable_set("@#{name}", value)
|
24
31
|
end
|
@@ -1,14 +1,22 @@
|
|
1
1
|
class Kookaburra
|
2
|
+
# Raised when accessing unknown mental model data
|
2
3
|
# @private
|
3
4
|
class UnknownKeyError < ArgumentError; end
|
5
|
+
|
6
|
+
# Raised when there is a problem with the Kookaburra runtime configuration
|
4
7
|
# @private
|
5
8
|
class ConfigurationError < StandardError; end
|
9
|
+
|
10
|
+
# Raised when an API or UI request results in an enexpected HTTP response
|
6
11
|
# @private
|
7
12
|
class UnexpectedResponse < RuntimeError; end
|
13
|
+
|
14
|
+
# Raised when an assertion fails inside the drivers.
|
8
15
|
# @private
|
9
16
|
class AssertionFailed < RuntimeError; end
|
17
|
+
|
18
|
+
# Raised when calling `api` or `ui` test helpers and named applications are
|
19
|
+
# configured
|
10
20
|
# @private
|
11
|
-
class
|
12
|
-
# @private
|
13
|
-
class NullBrowserError < ConfigurationError; end
|
21
|
+
class AmbiguousDriverError < StandardError; end
|
14
22
|
end
|
@@ -28,7 +28,7 @@ class Kookaburra
|
|
28
28
|
# MentalModel instances respond to everything.
|
29
29
|
#
|
30
30
|
# @see #method_missing
|
31
|
-
def respond_to?
|
31
|
+
def respond_to?(_)
|
32
32
|
true
|
33
33
|
end
|
34
34
|
|
@@ -53,12 +53,12 @@ class Kookaburra
|
|
53
53
|
# @param [String] name The name of the collection. Used to provide
|
54
54
|
# helpful error messages when unknown keys are accessed.
|
55
55
|
# @param [Hash] init_data Preloads specific data into the collection
|
56
|
-
def initialize(name, init_data =
|
56
|
+
def initialize(name, init_data = {})
|
57
57
|
self.name = name
|
58
58
|
data = Hash.new do |hash, key|
|
59
59
|
raise UnknownKeyError, "Can't find mental_model.#{@name}[#{key.inspect}]. Did you forget to set it?"
|
60
60
|
end
|
61
|
-
data.merge!(init_data)
|
61
|
+
data.merge!(init_data)
|
62
62
|
super(data)
|
63
63
|
end
|
64
64
|
|
@@ -132,8 +132,8 @@ class Kookaburra
|
|
132
132
|
#
|
133
133
|
# @return [Hash] the key/value pairs still remaining after the deletion
|
134
134
|
def delete_if(&block)
|
135
|
-
move =
|
136
|
-
super { |
|
135
|
+
move = ->(key, value) { deleted[key] = value; true }
|
136
|
+
super { |key, value| block.call(key, value) && move.call(key, value) }
|
137
137
|
end
|
138
138
|
|
139
139
|
def dup
|
@@ -49,16 +49,11 @@ class Kookaburra::RackAppServer
|
|
49
49
|
# will then monitor that port and only return once the app server is
|
50
50
|
# responding (or after the timeout period specified on {#initialize}).
|
51
51
|
def boot
|
52
|
-
|
53
|
-
thread_app_server
|
54
|
-
else
|
55
|
-
fork_app_server
|
56
|
-
end
|
52
|
+
fork_app_server
|
57
53
|
wait_for_app_to_respond
|
58
54
|
end
|
59
55
|
|
60
56
|
def shutdown
|
61
|
-
return if defined?(JRUBY_VERSION)
|
62
57
|
Process.kill(9, rack_server_pid)
|
63
58
|
Process.wait
|
64
59
|
end
|
@@ -68,10 +63,6 @@ class Kookaburra::RackAppServer
|
|
68
63
|
attr_accessor :rack_app_initializer, :rack_server_pid, :startup_timeout
|
69
64
|
attr_writer :port
|
70
65
|
|
71
|
-
def thread_app_server
|
72
|
-
Thread.new { start_server }
|
73
|
-
end
|
74
|
-
|
75
66
|
def fork_app_server
|
76
67
|
self.rack_server_pid = fork do
|
77
68
|
start_server
|
@@ -98,8 +89,8 @@ class Kookaburra::RackAppServer
|
|
98
89
|
end
|
99
90
|
|
100
91
|
def running?
|
101
|
-
|
102
|
-
|
92
|
+
case (Net::HTTP.start('localhost', port) { |http| http.get('/__identify__') })
|
93
|
+
when Net::HTTPSuccess, Net::HTTPRedirection
|
103
94
|
true
|
104
95
|
else
|
105
96
|
false
|
@@ -82,21 +82,62 @@ class Kookaburra
|
|
82
82
|
module TestHelpers
|
83
83
|
extend Forwardable
|
84
84
|
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
85
|
+
# @method api
|
86
|
+
# Delegates to main {Kookaburra} instance
|
87
|
+
#
|
88
|
+
# @note This method will only be available when no named applications have
|
89
|
+
# been defined. (See {Kookaburra::Configuration#application}.)
|
90
|
+
#
|
91
|
+
# @raise [Kookaburra::AmbiguousDriverError] if named applications have been
|
92
|
+
# configured.
|
93
|
+
def_delegator :kookaburra, :api
|
94
|
+
|
95
|
+
# @method ui
|
96
|
+
# Delegates to main {Kookaburra} instance
|
88
97
|
#
|
89
|
-
# @
|
90
|
-
|
91
|
-
|
98
|
+
# @note This method will only be available when no named applications have
|
99
|
+
# been defined. (See {Kookaburra::Configuration#application}.)
|
100
|
+
#
|
101
|
+
# @raise [Kookaburra::AmbiguousDriverError] if named applications have been
|
102
|
+
# configured.
|
103
|
+
def_delegator :kookaburra, :ui
|
104
|
+
|
105
|
+
# Delegates to main {Kookaburra} instance
|
106
|
+
def get_data(*args)
|
107
|
+
main_kookaburra.get_data(*args)
|
92
108
|
end
|
93
109
|
|
94
|
-
#
|
95
|
-
#
|
96
|
-
|
110
|
+
# Will return the {Kookaburra} instance for any configured applications
|
111
|
+
#
|
112
|
+
# After an application is configured via
|
113
|
+
# {Kookaburra::Configuration#application}, the TestHelpers will respond to
|
114
|
+
# the name of that application by returning the associated Kookaburra
|
115
|
+
# instance. (Messages that do not match the name of a configured application
|
116
|
+
# will be handled with the usual #method_missing semantics.)
|
117
|
+
#
|
118
|
+
# @see [Kookaburra::Configuration#application]
|
119
|
+
def method_missing(method_name, *args, &block)
|
120
|
+
Kookaburra.configuration.applications[method_name.to_sym] \
|
121
|
+
|| super
|
122
|
+
end
|
97
123
|
|
98
|
-
# @
|
99
|
-
|
100
|
-
|
124
|
+
# @see [Kookaburra::TestHelpers#method_missing]
|
125
|
+
def respond_to?(method_name)
|
126
|
+
Kookaburra.configuration.applications.has_key?(method_name) \
|
127
|
+
|| super
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def kookaburra
|
133
|
+
if Kookaburra.configuration.has_named_applications?
|
134
|
+
raise AmbiguousDriverError
|
135
|
+
end
|
136
|
+
main_kookaburra
|
137
|
+
end
|
138
|
+
|
139
|
+
def main_kookaburra
|
140
|
+
@kookaburra ||= Kookaburra.new
|
141
|
+
end
|
101
142
|
end
|
102
143
|
end
|
@@ -106,15 +106,22 @@ class Kookaburra
|
|
106
106
|
end
|
107
107
|
|
108
108
|
# Is the component's element found on the page and is it considered
|
109
|
-
# "visible" by the browser driver
|
109
|
+
# "visible" by the browser driver?
|
110
110
|
def visible?
|
111
|
-
visible = browser.has_css?(component_locator, :
|
111
|
+
visible = browser.has_css?(component_locator, visible: true)
|
112
112
|
unless visible
|
113
113
|
detect_server_error!
|
114
114
|
end
|
115
115
|
visible
|
116
116
|
end
|
117
117
|
|
118
|
+
# The opposite of {#visible?}
|
119
|
+
#
|
120
|
+
# @note This does not check for a server error.
|
121
|
+
def not_visible?
|
122
|
+
browser.has_no_css?(component_locator, visible: true)
|
123
|
+
end
|
124
|
+
|
118
125
|
# Returns the full URL by appending {#component_path} to the value of the
|
119
126
|
# {Kookaburra::Configuration#app_host} from the initialized configuration.
|
120
127
|
def url(*args)
|