screenplay 0.1.1
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 +7 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/bin/screenplay +61 -0
- data/lib/screenplay.rb +59 -0
- data/lib/screenplay/actor.rb +46 -0
- data/lib/screenplay/actors/api.rb +64 -0
- data/lib/screenplay/actors/cache.rb +32 -0
- data/lib/screenplay/actors/data.rb +14 -0
- data/lib/screenplay/actors/prompt.rb +18 -0
- data/lib/screenplay/actors/test.rb +94 -0
- data/lib/screenplay/cast.rb +49 -0
- data/lib/screenplay/configuration.rb +38 -0
- data/lib/screenplay/datatype-extensions.rb +125 -0
- data/lib/screenplay/scenario.rb +37 -0
- data/lib/screenplay/scenarios.rb +38 -0
- metadata +88 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: dbb612ed6af3abe140b84a1f5ff9f260c7d9b682
|
|
4
|
+
data.tar.gz: 6a7606ad77ba8dd2999d676b92d8b580b85759cb
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1ae0e7d50879140a9f208e3ff93bdc3c27fa0b20ff01b2bd70df4529fe1aafab406c7d846be6e5c0f03a8a8bb32656d7539069de6b46c3bee2ea570bad1853e4
|
|
7
|
+
data.tar.gz: 4f3c5df6e4d8221f1b9950b9f76e1ff7164774e0098341680f3d5fe943fcc66fb8ae40c827061c73fdac718ce6c7bcfcf7ffad375796ac84664ac38b1755218d
|
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2014 Taco Jan Osinga
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/bin/screenplay
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
|
|
4
|
+
|
|
5
|
+
require 'screenplay'
|
|
6
|
+
require 'optparse'
|
|
7
|
+
|
|
8
|
+
$SCREENPLAY_OPTIONS = {}
|
|
9
|
+
|
|
10
|
+
$ARGV_PARSER = OptionParser.new { | opts |
|
|
11
|
+
opts.banner = 'Usage: screenplay command [-c|--config FILE] [-a|--actors DIR] [-s|--scenarios DIR] [-v|--verbose] [-h|--human-friendly]'
|
|
12
|
+
opts.separator('')
|
|
13
|
+
|
|
14
|
+
opts.separator('Available commands:')
|
|
15
|
+
opts.separator('- play: Plays all scenarios.')
|
|
16
|
+
opts.separator('- actors: Lists all known actors.')
|
|
17
|
+
opts.separator('- scenarios: Lists all found scenarios.')
|
|
18
|
+
opts.separator('')
|
|
19
|
+
|
|
20
|
+
opts.separator('Specific options:')
|
|
21
|
+
opts.on('-c FILE', '--config FILE', 'Filename of the configuration file.') { | f | $SCREENPLAY_CONFIGFILE = f }
|
|
22
|
+
opts.on('-a DIR', '--actors DIR', 'Directory where custom actors can be found. By default in the ./actors directory near the config file.') { | d | $SCREENPLAY_ACTORS_DIR = d }
|
|
23
|
+
opts.on('-s DIR', '--scenarios DIR', 'Directory where the scenarios can be found. By default in the ./scenarios director near the config file.') { | d | $SCREENPLAY_SCENARIOS_DIR = d }
|
|
24
|
+
opts.on('-d', '--debug', 'Shows full stack trace on when failed. Especially usefull when developing new actors or other information.') { $SCREENPLAY_OPTIONS[:debug] = true }
|
|
25
|
+
opts.on('-q', '--quiet', 'Doesn\'t show the output of the scenes.') { $SCREENPLAY_OPTIONS[:quiet] = true }
|
|
26
|
+
opts.on('-h', '--human-friendly', 'Formats outputted JSON.') { $SCREENPLAY_OPTIONS[:human_friendly] = true }
|
|
27
|
+
opts.separator('')
|
|
28
|
+
}
|
|
29
|
+
command = $ARGV_PARSER.parse![0] rescue ''
|
|
30
|
+
|
|
31
|
+
def usage
|
|
32
|
+
STDOUT << $ARGV_PARSER.help
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def play
|
|
36
|
+
Screenplay.prepare
|
|
37
|
+
begin
|
|
38
|
+
Screenplay.play($SCREENPLAY_OPTIONS)
|
|
39
|
+
rescue Exception => e
|
|
40
|
+
raise if $SCREENPLAY_OPTIONS[:debug]
|
|
41
|
+
abort(e.message)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def actors
|
|
46
|
+
Screenplay.prepare
|
|
47
|
+
Screenplay::Cast.each { | actor | puts actor.name }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def scenarios
|
|
51
|
+
Screenplay.prepare
|
|
52
|
+
Screenplay::Scenarios.each { | scenario | puts scenario.name }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
case command
|
|
56
|
+
when 'play' then play
|
|
57
|
+
when 'actors' then actors
|
|
58
|
+
when 'scenarios' then scenarios
|
|
59
|
+
else usage
|
|
60
|
+
end
|
|
61
|
+
|
data/lib/screenplay.rb
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
$:.unshift(File.expand_path(File.dirname(__FILE__)))
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
require 'screenplay/datatype-extensions'
|
|
6
|
+
require 'screenplay/configuration'
|
|
7
|
+
require 'screenplay/cast'
|
|
8
|
+
require 'screenplay/actor'
|
|
9
|
+
require 'screenplay/scenarios'
|
|
10
|
+
|
|
11
|
+
module Screenplay
|
|
12
|
+
extend self
|
|
13
|
+
|
|
14
|
+
def prepare
|
|
15
|
+
Configuration.load
|
|
16
|
+
Cast.autoload
|
|
17
|
+
Scenarios.autoload
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def play(options = {})
|
|
21
|
+
options[:quiet] ||= false
|
|
22
|
+
options[:human_friendly] ||= false
|
|
23
|
+
|
|
24
|
+
raise Exception.new('ERROR: Couldn\'t find any scenarios to play.') if Scenarios.size == 0
|
|
25
|
+
|
|
26
|
+
# First check if we know all needed actors
|
|
27
|
+
each_scene { | scenario_name, actor_name |
|
|
28
|
+
raise UnknownActorException.new(scenario_name, actor_name) if Cast.get(actor_name).nil?
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
each_scene { | scenario, actor_name, params, input, index |
|
|
32
|
+
puts "##### #{scenario.name} - #{actor_name}: #####" unless (options[:quiet])
|
|
33
|
+
begin
|
|
34
|
+
output = Cast.get(actor_name).play(params, input)
|
|
35
|
+
rescue Exception => e
|
|
36
|
+
raise ScenarioFailedException.new(scenario, index, actor_name, e.message)
|
|
37
|
+
end
|
|
38
|
+
output.symbolize_keys!
|
|
39
|
+
unless (options[:quiet])
|
|
40
|
+
output_str = options[:human_friendly] ? JSON.pretty_generate(output) : output.to_s
|
|
41
|
+
puts "output = #{output_str}"
|
|
42
|
+
puts ''
|
|
43
|
+
end
|
|
44
|
+
output
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def each_scene
|
|
49
|
+
Scenarios.each { | scenario |
|
|
50
|
+
input = {}
|
|
51
|
+
index = 1
|
|
52
|
+
scenario.each { | actor_name, params |
|
|
53
|
+
input = yield scenario, actor_name, params, input, index
|
|
54
|
+
index += 1
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'screenplay/cast'
|
|
2
|
+
require 'screenplay/configuration'
|
|
3
|
+
|
|
4
|
+
module Screenplay
|
|
5
|
+
|
|
6
|
+
class MethodNotImplemented < Exception
|
|
7
|
+
|
|
8
|
+
def initialize(name)
|
|
9
|
+
super("Method #{name} is not implemented.")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class UnknownActorException < Exception
|
|
16
|
+
|
|
17
|
+
def initialize(scenario, name)
|
|
18
|
+
super("The scenario #{scenario} uses unknown actor #{name}.")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Actor
|
|
24
|
+
|
|
25
|
+
attr_reader :name
|
|
26
|
+
|
|
27
|
+
def initialize(name)
|
|
28
|
+
@name = name.to_sym
|
|
29
|
+
configure(Configuration[@name] || {})
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.descendants
|
|
33
|
+
ObjectSpace.each_object(Class).select { | klass | klass < self }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def configure(config = {})
|
|
37
|
+
# Not needed to override this, but might be useful
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def play(params, input)
|
|
41
|
+
raise MethodNotImplemented.new('play')
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require 'rest_client'
|
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'actor')
|
|
3
|
+
|
|
4
|
+
module Screenplay
|
|
5
|
+
|
|
6
|
+
class WrongResponseCodeException < Exception
|
|
7
|
+
def initialize(expected, actual)
|
|
8
|
+
super("Expected HTTP response code #{expected}, but received #{actual}.")
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class ApiActor < Actor
|
|
13
|
+
|
|
14
|
+
attr_reader :cookies
|
|
15
|
+
|
|
16
|
+
def configure(config)
|
|
17
|
+
@url = config[:url].to_s
|
|
18
|
+
@url += '/' unless @url.end_with?('/')
|
|
19
|
+
@request_headers = config[:request_headers] || {}
|
|
20
|
+
@cookies = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def play(params, input)
|
|
24
|
+
raise Exception.new('Missing configuration api.url') if @url == '/'
|
|
25
|
+
path = params[:path]
|
|
26
|
+
method = params[:method].downcase.to_sym rescue :get
|
|
27
|
+
expects = (params[:expect] || 200).to_i
|
|
28
|
+
data = params[:data] || {} rescue {}
|
|
29
|
+
data = input if (data.is_a?(String)) && (data == '$input')
|
|
30
|
+
data.stringify_keys!
|
|
31
|
+
headers = @request_headers
|
|
32
|
+
headers[:cookie] = @cookies unless @cookies.nil?
|
|
33
|
+
url = @url + path.to_s.replace_vars(input)
|
|
34
|
+
if ([:get, :head, :delete].include?(method))
|
|
35
|
+
headers[:params] = data
|
|
36
|
+
data = {}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
response = RestClient::Request.execute({
|
|
41
|
+
url: url,
|
|
42
|
+
method: method,
|
|
43
|
+
headers: headers,
|
|
44
|
+
payload: data
|
|
45
|
+
})
|
|
46
|
+
rescue => e
|
|
47
|
+
response = e.response
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if response.code != expects
|
|
51
|
+
raise WrongResponseCodeException.new(expects, response.code)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
unless response.nil?
|
|
55
|
+
output = JSON.parse(response.body) rescue {}
|
|
56
|
+
@cookies = response.headers[:set_cookie][0] rescue nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
return output || {}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'actor')
|
|
2
|
+
|
|
3
|
+
module Screenplay
|
|
4
|
+
|
|
5
|
+
class CacheActor < Actor
|
|
6
|
+
|
|
7
|
+
attr_reader :cache
|
|
8
|
+
|
|
9
|
+
def play(params = {}, input = {})
|
|
10
|
+
@cache ||= {}
|
|
11
|
+
output = {}
|
|
12
|
+
params.each { | action, values |
|
|
13
|
+
output = input.dup if action == :merge
|
|
14
|
+
|
|
15
|
+
if action == :clear
|
|
16
|
+
@cache.clear
|
|
17
|
+
elsif action == :set
|
|
18
|
+
values.each { | input_key, cache_key |
|
|
19
|
+
@cache[cache_key.to_sym] = (input_key == '$input'.to_sym) ? input : input[input_key]
|
|
20
|
+
}
|
|
21
|
+
elsif (action == :merge || action == :get)
|
|
22
|
+
values.each { | cache_key, input_key |
|
|
23
|
+
output[input_key.to_sym] = @cache[cache_key.to_sym]
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
}
|
|
27
|
+
return output
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'actor')
|
|
2
|
+
require 'highline/import'
|
|
3
|
+
|
|
4
|
+
module Screenplay
|
|
5
|
+
|
|
6
|
+
class PromptActor < Actor
|
|
7
|
+
|
|
8
|
+
def play(params, input)
|
|
9
|
+
output = input
|
|
10
|
+
params.each { | key, title |
|
|
11
|
+
output[key] = ask(title.replace_vars(input) + ': ') #{ | q | q.echo = true }
|
|
12
|
+
}
|
|
13
|
+
return output
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'actor')
|
|
2
|
+
|
|
3
|
+
module Screenplay
|
|
4
|
+
|
|
5
|
+
class TestFailedException < Exception
|
|
6
|
+
def initialize(test, a, b)
|
|
7
|
+
super("#{test} on #{a.to_s} failed. Expected: #{b.to_s}.")
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class UnknownTestException < Exception
|
|
12
|
+
def initialize(test)
|
|
13
|
+
super("Unknown test #{test}")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class UnsupportedTypeTestException < Exception
|
|
18
|
+
def initialize(test, a)
|
|
19
|
+
super("Unsupported data type for test #{test}: #{a.to_s}")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class UnknownInputKeyException < Exception
|
|
24
|
+
def initialize(test, key)
|
|
25
|
+
super("Couldn't find #{key} in the input for test #{test}.")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class TestActor < Actor
|
|
30
|
+
|
|
31
|
+
def play(params, input)
|
|
32
|
+
params.each { | key, values |
|
|
33
|
+
values.each { | test, b |
|
|
34
|
+
expects = !test.to_s.start_with?('not-')
|
|
35
|
+
test = test.to_s[4..-1] unless expects
|
|
36
|
+
method = "test_#{test}".to_sym
|
|
37
|
+
raise UnknownTestException.new(test) if !respond_to?(method)
|
|
38
|
+
raise UnknownInputKeyException.new(test, key) unless input.include?(key.to_sym)
|
|
39
|
+
a = input[key.to_sym]
|
|
40
|
+
raise TestFailedException.new(test, a, b) unless (public_send(method, a, b) == expects)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return input
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_lt(a, b)
|
|
47
|
+
a < b
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_lte(a, b)
|
|
51
|
+
a <= b
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_eq(a, b)
|
|
55
|
+
a === b
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_gte(a, b)
|
|
59
|
+
a >= b
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_gt(a, b)
|
|
63
|
+
a > b
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_in(a, b)
|
|
67
|
+
if (b.is_a?(String))
|
|
68
|
+
b.index(a) != nil
|
|
69
|
+
elsif (b.is_a?(Array))
|
|
70
|
+
b.to_a.include?(a)
|
|
71
|
+
elsif (b.is_a?(Hash))
|
|
72
|
+
b.include?(a.to_sym)
|
|
73
|
+
else
|
|
74
|
+
raise UnsupportedTypeTestException.new(:in, b)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_size(a, b)
|
|
79
|
+
if (a.respond_to?(:size))
|
|
80
|
+
a.size == b
|
|
81
|
+
else
|
|
82
|
+
raise UnsupportedTypeTestException.new(:size, a)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_regexp(a, b)
|
|
87
|
+
b = Regexp.new(b.to_s) if b.is_a?(String) || b.is_a?(Symbol)
|
|
88
|
+
raise UnsupportedTypeTestException.new(:regexp, b) unless b.is_a?(Regexp)
|
|
89
|
+
raise UnsupportedTypeTestException.new(:regexp, a) unless a.is_a?(String)
|
|
90
|
+
!a.match(b).nil?
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Screenplay
|
|
2
|
+
|
|
3
|
+
# The Cast module is a singleton which is available for actors to register themselves and make themselves available.
|
|
4
|
+
module Cast
|
|
5
|
+
extend self
|
|
6
|
+
|
|
7
|
+
@actors = []
|
|
8
|
+
|
|
9
|
+
include Enumerable
|
|
10
|
+
|
|
11
|
+
def register(actor)
|
|
12
|
+
raise Exception.new("Actor #{actor.name} is already registered.") if @actors.include?(actor.name)
|
|
13
|
+
@actors.push(actor)
|
|
14
|
+
@actors.sort_by!{ | actor | actor.name }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def each
|
|
18
|
+
@actors.each { | actor | yield actor }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def get(actor_name)
|
|
22
|
+
@actors.each { | actor |
|
|
23
|
+
return actor if actor.name == actor_name.to_sym
|
|
24
|
+
}
|
|
25
|
+
return nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def autoload
|
|
29
|
+
# Require all standard actors
|
|
30
|
+
Dir[File.dirname(__FILE__) + '/actors/*.rb'].each { | filename |
|
|
31
|
+
require filename
|
|
32
|
+
}
|
|
33
|
+
## Require custom actors
|
|
34
|
+
@actors_path = $SCREENPLAY_ACTORS_DIR || Configuration[:general][:actors_dir] rescue File.join(Configuration.path, 'actors')
|
|
35
|
+
Dir[@actors_path + '/**/*.rb'].each { | filename |
|
|
36
|
+
require filename
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
## Create an instance of each actor and register it to the cast
|
|
40
|
+
Actor.descendants.each { | klass |
|
|
41
|
+
name = klass.name.match(/(\w+)(Actor)?$/)[0].gsub(/Actor$/, '').snake_case
|
|
42
|
+
register(klass.new(name))
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'screenplay/datatype-extensions'
|
|
3
|
+
|
|
4
|
+
module Screenplay
|
|
5
|
+
|
|
6
|
+
module Configuration
|
|
7
|
+
extend self
|
|
8
|
+
|
|
9
|
+
include Enumerable
|
|
10
|
+
|
|
11
|
+
@config = {}
|
|
12
|
+
|
|
13
|
+
def each
|
|
14
|
+
@config.each { | k, v | yield k, v }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def [](key)
|
|
18
|
+
@config[key]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def load(reload = false)
|
|
22
|
+
return unless @config.empty? || reload
|
|
23
|
+
@filename = $SCREENPLAY_CONFIGFILE || './config.yml'
|
|
24
|
+
@config = YAML.load_file(@filename) if File.exists?(@filename)
|
|
25
|
+
@config.symbolize_keys!
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def path
|
|
29
|
+
File.dirname(@filename) || './'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_h
|
|
33
|
+
@config.to_h
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# The classes in this file extend common datatypes with some extra functionality.
|
|
3
|
+
|
|
4
|
+
# The Boolean module is used to mixin in the TrueClass and FalseClass for easy comparison.
|
|
5
|
+
# This way fields can be given the Boolean datatype instead of a TrueClass or a FalseClass.
|
|
6
|
+
module Boolean
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Adds the Boolean module into the TrueClass as mixin
|
|
11
|
+
class TrueClass
|
|
12
|
+
include Boolean
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Adds the Boolean module into the FalseClass as mixin
|
|
17
|
+
class FalseClass
|
|
18
|
+
include Boolean
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Adds a few extra methods to the standard Hash
|
|
23
|
+
class Hash
|
|
24
|
+
|
|
25
|
+
# Removes all nil values from the hash. If the value is an array or hash, it will do this recursively.
|
|
26
|
+
def remove_nil_values!
|
|
27
|
+
self.delete_if { |_, v| v.nil? }
|
|
28
|
+
self.each { |_, v| v.remove_nil_values! if (v.is_a?(Hash) || v.is_a?(Array)) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Changes all keys to symbols. If the value is an array or hash, it will do this recursively.
|
|
32
|
+
def symbolize_keys!(recursive = true)
|
|
33
|
+
self.keys.each { | key |
|
|
34
|
+
if !key.is_a?(Symbol)
|
|
35
|
+
val = self.delete(key)
|
|
36
|
+
val.symbolize_keys! if (recursive && (val.is_a?(Hash) || val.is_a?(Array)))
|
|
37
|
+
self[(key.to_sym rescue key) || key] = val
|
|
38
|
+
end
|
|
39
|
+
self[key.to_sym].symbolize_keys! if (recursive && (self[key.to_sym].is_a?(Hash) || self[key.to_sym].is_a?(Array)))
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Changes all keys to strings. If the value is an array or hash, it will do this recursively.
|
|
44
|
+
def stringify_keys!(recursive = true)
|
|
45
|
+
self.keys.each do |key|
|
|
46
|
+
if !key.is_a?(String)
|
|
47
|
+
val = self.delete(key)
|
|
48
|
+
val.stringify_keys! if (recursive && (val.is_a?(Hash) || val.is_a?(Array)))
|
|
49
|
+
self[(key.to_s rescue key) || key] = val
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Adds a few extra methods to the standard Hash
|
|
58
|
+
class Array
|
|
59
|
+
|
|
60
|
+
# Removes all nil values from the hash. If the value is an array or hash, it will do this recursively.
|
|
61
|
+
def remove_nil_values!
|
|
62
|
+
self.compact!
|
|
63
|
+
self.each { |val| val.remove_nil_values! if (val.is_a?(Hash) || val.is_a?(Array)) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Changes all keys to symbols. If the value is an array or hash, it will do this recursively.
|
|
67
|
+
def symbolize_keys!(recursive = true)
|
|
68
|
+
self.map! { |val| val.symbolize_keys! if (recursive && (val.is_a?(Hash) || val.is_a?(Array))); val }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Changes all keys to strings. If the value is an array or hash, it will do this recursively.
|
|
72
|
+
def stringify_keys!(recursive = true)
|
|
73
|
+
self.map! { |val| val.stringify_keys! if (recursive && (val.is_a?(Hash) || val.is_a?(Array))); val }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Adds a few extra methods to the standard String
|
|
80
|
+
class String
|
|
81
|
+
|
|
82
|
+
# Returns true if a string is numeric.
|
|
83
|
+
def numeric?
|
|
84
|
+
self.to_i.to_s == self
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Strips single or double quotes at the start and end of the given string.
|
|
88
|
+
def strip_quotes
|
|
89
|
+
gsub(/\A['"]+|['"]+\Z/, '')
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Normalizes a string, remove diacritics (accents)
|
|
93
|
+
def normalize
|
|
94
|
+
tr(
|
|
95
|
+
"ÀÁÂÃÄÅàáâãäåĀāĂ㥹ÇçĆćĈĉĊċČčÐðĎďĐđÈÉÊËèéêëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħÌÍÎÏìíîïĨĩĪīĬĭĮįİıĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÒÓÔÕÖØòóôõöøŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšſŢţŤťŦŧÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųŴŵÝýÿŶŷŸŹźŻżŽž",
|
|
96
|
+
"AAAAAAaaaaaaAaAaAaCcCcCcCcCcDdDdDdEEEEeeeeEeEeEeEeEeGgGgGgGgHhHhIIIIiiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnNnnNnOOOOOOooooooOoOoOoRrRrRrSsSsSsSssTtTtTtUUUUuuuuUuUuUuUuUuUuWwYyyYyYZzZzZz")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def truncate(max_length, ellipses = '...')
|
|
100
|
+
(self.length > max_length) ? self.to_s[0..max_length].gsub(/[^\w]\w+\s*$/, '...') : self.to_s
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def replace_vars!(input)
|
|
104
|
+
input = {} unless input.is_a?(Hash)
|
|
105
|
+
return if input.empty?
|
|
106
|
+
matches = {}
|
|
107
|
+
input.each { | k, v | matches['#{' + k.to_s + '}'] = v.to_s }
|
|
108
|
+
self.gsub!(/\#({\w+})/, matches)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def replace_vars(input)
|
|
112
|
+
result = self.dup
|
|
113
|
+
result.replace_vars!(input)
|
|
114
|
+
return result
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def snake_case
|
|
118
|
+
self.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr('-', '_').downcase
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def camel_case
|
|
122
|
+
self.split('_').collect(&:capitalize).join
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
module Screenplay
|
|
4
|
+
|
|
5
|
+
class ScenarioFailedException < Exception
|
|
6
|
+
def initialize(scenario, index, actor_name, message)
|
|
7
|
+
super("FAILED: Scenario #{scenario.name}, scene #{index} of #{scenario.size}, actor #{actor_name}: #{message}")
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class Scenario
|
|
12
|
+
|
|
13
|
+
include Enumerable
|
|
14
|
+
|
|
15
|
+
attr_reader :name
|
|
16
|
+
|
|
17
|
+
def initialize(name, filename)
|
|
18
|
+
@name = name
|
|
19
|
+
@actions = YAML.load_file(filename)
|
|
20
|
+
@actions.symbolize_keys!
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def each
|
|
24
|
+
@actions.each { | action |
|
|
25
|
+
actor = action.keys[0]
|
|
26
|
+
data = action[actor]
|
|
27
|
+
yield actor, data
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def size
|
|
32
|
+
@actions.size
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'screenplay/scenario'
|
|
2
|
+
|
|
3
|
+
module Screenplay
|
|
4
|
+
|
|
5
|
+
module Scenarios
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
include Enumerable
|
|
9
|
+
|
|
10
|
+
@scenarios = []
|
|
11
|
+
|
|
12
|
+
def register(filename)
|
|
13
|
+
name = filename.gsub(@path, '').gsub(/^\//, '') unless @path.empty?
|
|
14
|
+
@scenarios.push(Scenario.new(name, filename))
|
|
15
|
+
@scenarios.sort_by{ | actor | actor.name }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def autoload
|
|
19
|
+
@path = $SCREENPLAY_SCENARIOS_DIR || Configuration[:general][:scenarios_dir] rescue File.join(Configuration.path, 'scenarios')
|
|
20
|
+
load_path(@path)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def load_path(path)
|
|
24
|
+
yml_files = File.join(path, '**', '*.yml')
|
|
25
|
+
Dir[yml_files].each { | filename | register(filename) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def size
|
|
29
|
+
@scenarios.size
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def each
|
|
33
|
+
@scenarios.each { | k | yield k }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: screenplay
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Taco Jan Osinga
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2014-08-31 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rest-client
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.7'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.7'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: highline
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.6'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.6'
|
|
41
|
+
description: Screenplay helps with testing data driven applications like RESTful API's
|
|
42
|
+
by writing scenario's in Yaml.
|
|
43
|
+
email: info@osingasoftware.nl
|
|
44
|
+
executables:
|
|
45
|
+
- screenplay
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- Gemfile
|
|
50
|
+
- LICENSE
|
|
51
|
+
- bin/screenplay
|
|
52
|
+
- lib/screenplay.rb
|
|
53
|
+
- lib/screenplay/actor.rb
|
|
54
|
+
- lib/screenplay/actors/api.rb
|
|
55
|
+
- lib/screenplay/actors/cache.rb
|
|
56
|
+
- lib/screenplay/actors/data.rb
|
|
57
|
+
- lib/screenplay/actors/prompt.rb
|
|
58
|
+
- lib/screenplay/actors/test.rb
|
|
59
|
+
- lib/screenplay/cast.rb
|
|
60
|
+
- lib/screenplay/configuration.rb
|
|
61
|
+
- lib/screenplay/datatype-extensions.rb
|
|
62
|
+
- lib/screenplay/scenario.rb
|
|
63
|
+
- lib/screenplay/scenarios.rb
|
|
64
|
+
homepage: https://github.com/tjosinga/screenplay
|
|
65
|
+
licenses:
|
|
66
|
+
- MIT
|
|
67
|
+
metadata: {}
|
|
68
|
+
post_install_message:
|
|
69
|
+
rdoc_options: []
|
|
70
|
+
require_paths:
|
|
71
|
+
- lib
|
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: '0'
|
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
requirements: []
|
|
83
|
+
rubyforge_project:
|
|
84
|
+
rubygems_version: 2.2.2
|
|
85
|
+
signing_key:
|
|
86
|
+
specification_version: 4
|
|
87
|
+
summary: Screenplay
|
|
88
|
+
test_files: []
|