screenplay 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|