lapis_lazuli 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +42 -0
  5. data/LICENSE +30 -0
  6. data/README.md +74 -0
  7. data/Rakefile +1 -0
  8. data/bin/lapis_lazuli +3 -0
  9. data/lapis_lazuli.gemspec +32 -0
  10. data/lib/lapis_lazuli/api.rb +52 -0
  11. data/lib/lapis_lazuli/argparse.rb +128 -0
  12. data/lib/lapis_lazuli/ast.rb +160 -0
  13. data/lib/lapis_lazuli/browser/error.rb +93 -0
  14. data/lib/lapis_lazuli/browser/find.rb +500 -0
  15. data/lib/lapis_lazuli/browser/interaction.rb +91 -0
  16. data/lib/lapis_lazuli/browser/screenshots.rb +70 -0
  17. data/lib/lapis_lazuli/browser/wait.rb +158 -0
  18. data/lib/lapis_lazuli/browser.rb +246 -0
  19. data/lib/lapis_lazuli/cli.rb +110 -0
  20. data/lib/lapis_lazuli/cucumber.rb +25 -0
  21. data/lib/lapis_lazuli/generators/cucumber/template/.gitignore +6 -0
  22. data/lib/lapis_lazuli/generators/cucumber/template/Gemfile +37 -0
  23. data/lib/lapis_lazuli/generators/cucumber/template/README.md +27 -0
  24. data/lib/lapis_lazuli/generators/cucumber/template/config/config.yml +29 -0
  25. data/lib/lapis_lazuli/generators/cucumber/template/config/cucumber.yml +34 -0
  26. data/lib/lapis_lazuli/generators/cucumber/template/features/example.feature +11 -0
  27. data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/interaction_steps.rb +20 -0
  28. data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/validation_steps.rb +21 -0
  29. data/lib/lapis_lazuli/generators/cucumber/template/features/support/env.rb +12 -0
  30. data/lib/lapis_lazuli/generators/cucumber/template/features/support/transition.rb +12 -0
  31. data/lib/lapis_lazuli/generators/cucumber.rb +128 -0
  32. data/lib/lapis_lazuli/generic/xpath.rb +49 -0
  33. data/lib/lapis_lazuli/options.rb +28 -0
  34. data/lib/lapis_lazuli/placeholders.rb +36 -0
  35. data/lib/lapis_lazuli/proxy.rb +179 -0
  36. data/lib/lapis_lazuli/runtime.rb +88 -0
  37. data/lib/lapis_lazuli/scenario.rb +88 -0
  38. data/lib/lapis_lazuli/storage.rb +59 -0
  39. data/lib/lapis_lazuli/version.rb +10 -0
  40. data/lib/lapis_lazuli/versions.rb +40 -0
  41. data/lib/lapis_lazuli/world/annotate.rb +45 -0
  42. data/lib/lapis_lazuli/world/api.rb +35 -0
  43. data/lib/lapis_lazuli/world/browser.rb +75 -0
  44. data/lib/lapis_lazuli/world/config.rb +292 -0
  45. data/lib/lapis_lazuli/world/error.rb +141 -0
  46. data/lib/lapis_lazuli/world/hooks.rb +109 -0
  47. data/lib/lapis_lazuli/world/logging.rb +53 -0
  48. data/lib/lapis_lazuli/world/proxy.rb +59 -0
  49. data/lib/lapis_lazuli/world/variable.rb +139 -0
  50. data/lib/lapis_lazuli.rb +75 -0
  51. data/test/.gitignore +8 -0
  52. data/test/Gemfile +42 -0
  53. data/test/README.md +35 -0
  54. data/test/config/config.yml +37 -0
  55. data/test/config/cucumber.yml +37 -0
  56. data/test/features/annotation.feature +23 -0
  57. data/test/features/browser.feature +10 -0
  58. data/test/features/button.feature +38 -0
  59. data/test/features/click.feature +35 -0
  60. data/test/features/error.feature +30 -0
  61. data/test/features/find.feature +92 -0
  62. data/test/features/har.feature +9 -0
  63. data/test/features/modules.feature +14 -0
  64. data/test/features/step_definitions/interaction_steps.rb +154 -0
  65. data/test/features/step_definitions/validation_steps.rb +350 -0
  66. data/test/features/support/env.rb +21 -0
  67. data/test/features/text_field.feature +32 -0
  68. data/test/features/timing.feature +47 -0
  69. data/test/features/variable.feature +11 -0
  70. data/test/features/xpath.feature +41 -0
  71. data/test/server/start.rb +17 -0
  72. data/test/server/www/button.html +22 -0
  73. data/test/server/www/error_html.html +9 -0
  74. data/test/server/www/find.html +66 -0
  75. data/test/server/www/javascript_error.html +12 -0
  76. data/test/server/www/text_fields.html +15 -0
  77. data/test/server/www/timing.html +32 -0
  78. data/test/server/www/xpath.html +22 -0
  79. metadata +295 -0
@@ -0,0 +1,20 @@
1
+ ################################################################################
2
+ # Copyright <%= config[:year] %> spriteCloud B.V. All rights reserved.
3
+ # Generated by LapisLazuli, version <%= config[:lapis_lazuli][:version] %>
4
+ # Author: "<%= config[:user] %>" <<%= config[:email] %>>
5
+
6
+ Given(/^I navigate to (.*) in (.*)$/) do |site,language|
7
+ config_name = "#{site.downcase}.#{language.downcase}"
8
+ if has_env?(config_name)
9
+ url = env(config_name)
10
+ browser.goto url
11
+ else
12
+ error(:env => config_name)
13
+ end
14
+ end
15
+
16
+ Given(/^I search for "(.*?)"$/) do |query|
17
+ searchbox = browser.find(:text_field => {:name => "q"})
18
+ searchbox.clear rescue log.debug "Could not clear searchbox"
19
+ searchbox.send_keys(query)
20
+ end
@@ -0,0 +1,21 @@
1
+ ################################################################################
2
+ # Copyright <%= config[:year] %> spriteCloud B.V. All rights reserved.
3
+ # Generated by LapisLazuli, version <%= config[:lapis_lazuli][:version] %>
4
+ # Author: "<%= config[:user] %>" <<%= config[:email] %>>
5
+
6
+ Then(/I see "([^"]*)" on the page/) do |string|
7
+ # Note: The following is *really* slow, as it'll apply the regex to all
8
+ # elements in the page, one after the other. Of course, if any element
9
+ # includes the regex, all its parent elements also will, so you have
10
+ # tons of matches to process.
11
+ #
12
+ # browser.wait(:text => /#{string}/i)
13
+
14
+ # Instead, you will want to search only the root element for some
15
+ # text, e.g.
16
+ #
17
+ # browser.wait(:html => {:text => /#{string}/i})
18
+
19
+ # There's a shortcut for that in find/wait:
20
+ browser.wait(:html => /#{string}/i)
21
+ end
@@ -0,0 +1,12 @@
1
+ ################################################################################
2
+ # Copyright <%= config[:year] %> spriteCloud B.V. All rights reserved.
3
+ # Generated by LapisLazuli, version <%= config[:lapis_lazuli][:version] %>
4
+ # Author: "<%= config[:user] %>" <<%= config[:email] %>>
5
+ require 'lapis_lazuli'
6
+ require 'lapis_lazuli/cucumber'
7
+
8
+ LapisLazuli::WorldModule::Config.config_file = "config/config.yml"
9
+ World(LapisLazuli)
10
+
11
+ # Transition function from old codebase to new
12
+ load 'features/support/transition.rb'
@@ -0,0 +1,12 @@
1
+ ##
2
+ # All function in this file SHOULD not be used.
3
+ # There only included for backwards compatibility
4
+ #
5
+
6
+ ##
7
+ # Creating a link for easy debugging afterwards
8
+ def create_link(name, url)
9
+ log.info("[DEPRECATED] [feature/support/transition.rb] create_link")
10
+ #Lets just send the url without the parameters to prevent html display problems
11
+ "<a href='#{url}' target='_blank'>#{name}</a>"
12
+ end
@@ -0,0 +1,128 @@
1
+ #
2
+ # LapisLazuli
3
+ # https://github.com/spriteCloud/lapis-lazuli
4
+ #
5
+ # Copyright (c) 2013-2014 spriteCloud B.V. and other LapisLazuli contributors.
6
+ # All rights reserved.
7
+ #
8
+
9
+ require 'lapis_lazuli/version'
10
+
11
+ require 'thor/group'
12
+
13
+ module LapisLazuli
14
+ module Generators
15
+
16
+ PROJECT_PATHS = [
17
+ 'config',
18
+ 'features',
19
+ File.join('features', 'step_definitions'),
20
+ File.join('features', 'support'),
21
+ 'log',
22
+ 'results',
23
+ 'screenshots',
24
+ ]
25
+
26
+
27
+ ALLOWED_HIDDEN = [
28
+ '.gitignore'
29
+ ]
30
+
31
+
32
+
33
+ class Cucumber < Thor::Group
34
+ include Thor::Actions
35
+
36
+ argument :path, :type => :string
37
+
38
+ # Bug in Thor: this seems to need to be in place both here, and in the CLI
39
+ # class - here to actually work, and in the CLI class to be shown in the help.
40
+ class_option :branch, :aliases => "-b", :type => :string, :default => nil
41
+
42
+
43
+
44
+ def create_directory_structure
45
+ empty_directory(path)
46
+ PROJECT_PATHS.each do |p|
47
+ empty_directory(File.join(path, p))
48
+ end
49
+ end
50
+
51
+
52
+
53
+ def copy_template
54
+ opts = {
55
+ :year => Time.now.year,
56
+ :user => Cucumber.get_username(self),
57
+ :email => Cucumber.get_email(self),
58
+ :lapis_lazuli => {
59
+ :version => LapisLazuli::VERSION,
60
+ :dependency => '"' + LapisLazuli::VERSION + '"',
61
+ },
62
+ :project => {
63
+ :name => File.basename(path),
64
+ },
65
+ }
66
+
67
+ # If a branch was specified on the CLI, we have to update the dependency
68
+ # string.
69
+ if options.has_key?("branch")
70
+ opts[:lapis_lazuli][:dependency] = ":github => 'spriteCloud/lapis-lazuli', :branch => '#{options["branch"]}'"
71
+ end
72
+
73
+ require 'facets/string/lchomp'
74
+ require 'find'
75
+ Find.find(Cucumber.source_root) do |name|
76
+ # Skip the source root itself
77
+ next if name == Cucumber.source_root
78
+
79
+ # Find the relative path and file name component
80
+ relative = name.lchomp(Cucumber.source_root + File::SEPARATOR)
81
+ filename = File.basename(relative)
82
+
83
+ # Ignore hidden files UNLESS they're listed in the allowed hidden
84
+ # files.
85
+ if filename.start_with?('.')
86
+ next if not ALLOWED_HIDDEN.include?(filename)
87
+ end
88
+
89
+ # Create directories as empty directories, and treat every file as
90
+ # a template.
91
+ if File.directory?(name)
92
+ empty_directory(File.join(path, relative))
93
+ else
94
+ template(relative, File.join(path, relative), opts)
95
+ end
96
+ end
97
+ end
98
+
99
+
100
+
101
+ def self.source_root
102
+ File.join(File.dirname(__FILE__), "cucumber", "template")
103
+ end
104
+
105
+
106
+
107
+ def self.run_helper(cuke, command, default)
108
+ begin
109
+ cuke.run(command, {:capture => true}).strip || default
110
+ rescue
111
+ default
112
+ end
113
+ end
114
+
115
+
116
+
117
+ def self.get_username(cuke)
118
+ run_helper(cuke, 'git config --get user.name', 'spriteCloud B.V.')
119
+ end
120
+
121
+
122
+
123
+ def self.get_email(cuke)
124
+ run_helper(cuke, 'git config --get user.email', 'info@spritecloud.com')
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,49 @@
1
+ #
2
+ # LapisLazuli
3
+ # https://github.com/spriteCloud/lapis-lazuli
4
+ #
5
+ # Copyright (c) 2013-2014 spriteCloud B.V. and other LapisLazuli contributors.
6
+ # All rights reserved.
7
+ #
8
+ module LapisLazuli
9
+ module GenericModule
10
+
11
+ ##
12
+ # Helper functions for XPath composition
13
+ module XPath
14
+
15
+
16
+ ##
17
+ # Return an xpath contains clause for e.g. checking wether an element's
18
+ # class attribute node contains a string. The optional third parameter
19
+ # determines how substrings are separated in the attribute node; the
20
+ # default is space for matching class names.
21
+ # Note that enclosing [ and ] are not included in the return value; this
22
+ # lets you more easily use and()/or()/not() operators.
23
+ def xp_contains(node, needle, separator = ' ')
24
+ contains = "contains(concat('#{separator}', normalize-space(#{node}), '#{separator}'), '#{separator}#{needle}#{separator}')"
25
+ return contains
26
+ end
27
+
28
+ ##
29
+ # Constructs xpath and clause
30
+ def xp_and(first, second)
31
+ return "(#{first} and #{second})"
32
+ end
33
+
34
+ ##
35
+ # Constructs xpath or clause
36
+ def xp_or(first, second)
37
+ return "(#{first} or #{second})"
38
+ end
39
+
40
+ ##
41
+ # Constructs xpath or clause
42
+ def xp_not(expr)
43
+ return "not(#{expr})"
44
+ end
45
+
46
+
47
+ end # module XPath
48
+ end # module GenericModule
49
+ end # module LapisLazuli
@@ -0,0 +1,28 @@
1
+ #
2
+ # LapisLazuli
3
+ # https://github.com/spriteCloud/lapis-lazuli
4
+ #
5
+ # Copyright (c) 2013-2014 spriteCloud B.V. and other LapisLazuli contributors.
6
+ # All rights reserved.
7
+ #
8
+ module LapisLazuli
9
+ ##
10
+ # Configuration options and their default values
11
+ CONFIG_OPTIONS = {
12
+ "close_browser_after" => ["feature", "Close the browser after every scenario, feature, etc. Possible values are 'feature', 'scenario', 'end' and 'never'."],
13
+ "error_strings" => [nil, "List of strings that indicate errors when detected on a web page."],
14
+ "default_env" => [nil, "Indicates which environment specific configuration to load when no test environment is provided explicitly."],
15
+ "test_env" => [nil, "Indicates which environment specific configuration to load in this test run."],
16
+ "browser" => ['firefox', "Indicates the browser in which to run tests. Possible values are 'firefox', 'chrome', 'safari', 'ie', 'ios'."],
17
+ "email_domain" => ["google.com", "The domain name used when generating email addresses. See the `placeholders` command for more information."],
18
+ "screenshot_on_failure" => [true, "Toggle whether failed scenarios should result in a screenshot being taken automatically."],
19
+ "screenshot_dir" => [".#{File::SEPARATOR}screenshots", "Location prefix for the screenshot path."],
20
+ "screenshot_scheme" => ["old", "Naming scheme for screenshots. Possible values are 'old' and 'new'. This option will be deprecated in the near future, and only the new scheme will be supported."],
21
+ "breakpoint_on_error" => [false, "If the error() function is used to create errors, should the debugger be started?"],
22
+ "step_pause_time" => [0, "(Deprecated) Number of seconds to wait after each cucumber step is executed."],
23
+ "log_dir" => [".#{File::SEPARATOR}logs", "Location for log files; they'll be named like the configuration file but with the '.log' extension."],
24
+ "log_file" => [nil, "Location of log file; overrides 'log_dir'."],
25
+ "log_level" => ['DEBUG', "Log level; see ruby Logger class for details."],
26
+ "storage_dir" => [".#{File::SEPARATOR}storage", "Location prefix where to output test information file with the '.json' extension."]
27
+ }
28
+ end # module LapisLazuli
@@ -0,0 +1,36 @@
1
+ #
2
+ # LapisLazuli
3
+ # https://github.com/spriteCloud/lapis-lazuli
4
+ #
5
+ # Copyright (c) 2013-2014 spriteCloud B.V. and other LapisLazuli contributors.
6
+ # All rights reserved.
7
+ #
8
+ #
9
+ module LapisLazuli
10
+ ##
11
+ # Placeholders and their meanings.
12
+ # The first value is a string to be eval'd to determine the value the
13
+ # placeholder is to be replaced with.
14
+ # The second value describes the meaning.
15
+ PLACEHOLDERS = {
16
+ :timestamp => ['time[:timestamp]', 'The local time at which the test run started.'],
17
+ :iso_timestamp => ['time[:iso_timestamp]', 'The UTC time at which the test run started.'],
18
+ :iso_short => ['time[:iso_short]', 'A shorter version of the UTC time above.'],
19
+ :epoch => ['time[:epoch]', 'An integer representation of the local time above, relative to the epoch.'],
20
+ :email => ['"test_#{uuid}@#{email_domain}"', 'A unique email for the test run (contains the UUID).'],
21
+ :uuid => ['uuid', 'A UUID for the test run.'],
22
+ :scenario_id => ['scenario.id', 'A unique identifier for the current scenario based on the title, in filesystem safe form.'],
23
+ :scenario_timestamp => ['scenario.time[:timestamp]', 'Same as timestamp, but relative to the start of the scenario.'],
24
+ :scenario_iso_timestamp => ['scenario.time[:iso_timestamp]', 'Same as iso_timestamp, but relative to the start of the scenario.'],
25
+ :scenario_iso_short => ['scenario.time[:iso_short]', 'Same as iso_short, but relative to the start of the scenario.'],
26
+ :scenario_epoch => ['scenario.time[:epoch]', 'Same as epoch, but relative to the start of the scenario.'],
27
+ :scenario_email => ['"test_#{uuid}_scenario_#{scenario.uuid}@#{email_domain}"', 'Same as email, but contains the test run UUID and the scenario UUID.'],
28
+ :scenario_uuid => ['scenario.uuid', 'A UUID for the scenario.'],
29
+ :random => ['rand(9999)', 'A random integer <10,000.'],
30
+ :random_small => ['rand(99)', 'A random integer <100.'],
31
+ :random_lange => ['rand(999999)', 'A random integer <1,000,000.'],
32
+ :random_uuid => ['random_uuid', 'A random UUID.'],
33
+ :random_email => ['"test_#{uuid}_random_#{random_uuid}@#{email_domain}"', 'Same as email, but contains the test run and the random UUID.'],
34
+ :versions => ['LapisLazuli.software_versions.nil? ? "" : JSON.generate(LapisLazuli.software_versions)', 'A JSON serialized string of software versions found in e.g. the AfterConfiguration hook.']
35
+ }
36
+ end # module LapisLazuli
@@ -0,0 +1,179 @@
1
+ #
2
+ # LapisLazuli
3
+ # https://github.com/spriteCloud/lapis-lazuli
4
+ #
5
+ # Copyright (c) 2013-2014 spriteCloud B.V. and other LapisLazuli contributors.
6
+ # All rights reserved.
7
+ #
8
+
9
+ require 'socket'
10
+ require 'timeout'
11
+ require "lapis_lazuli/api"
12
+
13
+ module LapisLazuli
14
+ ##
15
+ # Proxy class to map to sc-proxy
16
+ class Proxy
17
+ attr_reader :is_scproxy, :api, :ip, :scproxy_port, :port
18
+
19
+ ##
20
+ # Create a new LL Proxy
21
+ # What is the ip/port of the master?
22
+ def initialize(ip, port, scproxy=true)
23
+ # Save the information
24
+ @ip = ip
25
+ @is_scproxy = scproxy
26
+ if scproxy
27
+ @scproxy_port = port
28
+ else
29
+ @port = port
30
+ end
31
+ # We should have a master
32
+ if !is_port_open?(ip, port)
33
+ raise "Proxy not online"
34
+ end
35
+ if @is_scproxy
36
+ # Create an API connection to the master
37
+ @api = API.new()
38
+ end
39
+ end
40
+
41
+ def has_session?()
42
+ return !@port.nil? && is_port_open?(@ip, @port);
43
+ end
44
+
45
+ ##
46
+ # Creates a new session with the proxy
47
+ def create()
48
+ # Do we already have a connection?
49
+ if @is_scproxy and self.has_session?
50
+ # Close it before starting a new one
51
+ self.close()
52
+ end
53
+ # Create a new
54
+ if @is_scproxy and @api
55
+ # Let the master create a new proxy
56
+ response = self.proxy_new :master => true
57
+ # Did we get on?
58
+ if response["status"] == true
59
+ @port = response["result"]["port"]
60
+ else
61
+ # Show the error
62
+ raise response["message"]
63
+ end
64
+ end
65
+
66
+ if @port.nil?
67
+ raise "Coult not create a new proxy"
68
+ end
69
+
70
+ return @port
71
+ end
72
+
73
+ ##
74
+ # Close the session with the proxy
75
+ def close()
76
+ # If we don't have one we don't do anything
77
+ return if !@is_scproxy or !self.has_session?
78
+
79
+ # Send the call to the master
80
+ response = self.proxy_close :port => @port, :master => true
81
+
82
+ # Did we close it?
83
+ if response["status"] == true
84
+ # Clear our session
85
+ @port = nil
86
+ else
87
+ # Show an error
88
+ raise response["message"]
89
+ end
90
+ end
91
+
92
+ ##
93
+ # Check if a TCP port is open on a host
94
+ def is_port_open?(ip, port)
95
+ begin
96
+ # Timeout is important
97
+ Timeout::timeout(1) do
98
+ begin
99
+ # Create the socket and close it
100
+ s = TCPSocket.new(ip, port)
101
+ s.close
102
+ return true
103
+ # If it fails the port is closed
104
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
105
+ end
106
+ end
107
+ rescue Timeout::Error
108
+ end
109
+
110
+ # Sorry port is closed
111
+ return false
112
+ end
113
+
114
+ ##
115
+ # Map any missing method to the API object
116
+ #
117
+ # Example
118
+ # proxy.har_get
119
+ # proxy.proxy_close :port => 10002
120
+ def method_missing(meth, *args, &block)
121
+ # Only for spritecloud proxies
122
+ if !@is_scproxy
123
+ raise "Incorrect method: #{meth}"
124
+ end
125
+
126
+ # We should have no arguments or a Hash
127
+ if args.length > 1 or (args.length == 1 and not args[0].is_a? Hash)
128
+ raise "Incorrect arguments: #{args}"
129
+ end
130
+ settings = args[0] || {}
131
+
132
+ # A custom block or arguments?
133
+ block = block_given? ? block : Proc.new do |req|
134
+ if args.length == 1
135
+ settings.each do |key,value|
136
+ req.params[key.to_s] = value.to_s
137
+ end
138
+ end
139
+ end
140
+
141
+ # Pick the master proxy or the proxy for this session
142
+ @api.set_conn("http://#{@ip}:#{(settings.has_key? :master) ? @scproxy_port : @port}/")
143
+
144
+ # Call the API
145
+ response = @api.get("/#{meth.to_s.gsub("_","/")}", nil, &block)
146
+ # Only return the body if we could parse the JSOn
147
+ if response.body.is_a? Hash
148
+ return response.body
149
+ else
150
+ # Got a serious issue here, label as code 500
151
+ return {
152
+ "code" => 500,
153
+ "status" => false,
154
+ "message" => "Incorrect response from proxy",
155
+ "result" => response
156
+ }
157
+ end
158
+ end
159
+
160
+ ##
161
+ # During the end of the test run all data should be added to the storage
162
+ def destroy(world)
163
+ begin
164
+ # Is it a spriteCloud proxy?
165
+ if @is_scproxy
166
+ # Request HAR data
167
+ response = self.har_get
168
+ if response["status"] == true
169
+ # Add it to the storage
170
+ world.storage.set("har", response["result"])
171
+ end
172
+ end
173
+ self.close
174
+ rescue StandardError => err
175
+ world.log.debug("Failed to close the proxy: #{err}")
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,88 @@
1
+ #
2
+ # LapisLazuli
3
+ # https://github.com/spriteCloud/lapis-lazuli
4
+ #
5
+ # Copyright (c) 2013-2014 spriteCloud B.V. and other LapisLazuli contributors.
6
+ # All rights reserved.
7
+ #
8
+ require 'singleton'
9
+
10
+ module LapisLazuli
11
+
12
+ ##
13
+ # Simple singleton class (that therefore lives for the duration of the test's
14
+ # run time for managing objects whose lifetime should also be this long.
15
+ class Runtime
16
+ include Singleton
17
+
18
+ def initialize
19
+ @objects = {}
20
+ end
21
+
22
+ def has?(name)
23
+ return @objects.has_key? name
24
+ end
25
+
26
+ def set(world, name, object, destructor = nil)
27
+ if @objects.has_key? name
28
+ Runtime.destroy(world, name, destructor)
29
+ end
30
+
31
+ # Register a finalizer, so we can clean up the proxy again
32
+ ObjectSpace.define_finalizer(self, Runtime.destroy(world, name, destructor))
33
+
34
+ @objects[name] = object
35
+ end
36
+
37
+ def set_if(world, name, destructor = nil, &block)
38
+ if @objects.has_key? name
39
+ return @objects[name]
40
+ end
41
+
42
+ obj = block.call
43
+
44
+ set(world, name, obj, destructor)
45
+
46
+ return obj
47
+ end
48
+
49
+ def get(name)
50
+ return @objects[name]
51
+ end
52
+
53
+
54
+ private
55
+ def self.destroy(world, name, destructor)
56
+ Proc.new do
57
+ # If a destructor is given, call that.
58
+ if not destructor.nil?
59
+ return destructor.call(world)
60
+ end
61
+
62
+ # Next, try a has_foo?/foo.destroy combination
63
+ if world.respond_to? "has_#{name}?" and world.respond_to? name
64
+ if world.send("has_#{name}?")
65
+ return world.send(name).destroy(world)
66
+ end
67
+ return false
68
+ end
69
+
70
+ # If it only responds to a destroy function, then we can just
71
+ # call that.
72
+ if world.respond_to? name
73
+ return world.send(name).destroy(world)
74
+ end
75
+
76
+ # If all else fails, we have to log an error. We can't rely
77
+ # on log existing in world, though...
78
+ message = "No destructor available for #{name}."
79
+ if world.respond_to? :log
80
+ world.log.info(message)
81
+ else
82
+ puts message
83
+ end
84
+ end
85
+ end
86
+
87
+ end # class Runtime
88
+ end
@@ -0,0 +1,88 @@
1
+ #
2
+ # LapisLazuli
3
+ # https://github.com/spriteCloud/lapis-lazuli
4
+ #
5
+ # Copyright (c) 2013-2014 spriteCloud B.V. and other LapisLazuli contributors.
6
+ # All rights reserved.
7
+ #
8
+ require "securerandom"
9
+ require "lapis_lazuli/storage"
10
+ require "lapis_lazuli/ast"
11
+
12
+ module LapisLazuli
13
+ ##
14
+ # Stores the Cucumber scenario
15
+ # Includes timing, running state and a name
16
+ class Scenario
17
+ include LapisLazuli::Ast
18
+
19
+ attr_reader :id, :time, :uuid, :data, :storage, :error
20
+ attr_accessor :running, :check_browser_errors
21
+
22
+ def initialize
23
+ @uuid = SecureRandom.hex
24
+ @storage = Storage.new
25
+ @running = false
26
+ @name = "start_of_test_run"
27
+ self.update_timestamp
28
+ end
29
+
30
+ ##
31
+ # Update the scenario with a new one
32
+ def update(scenario)
33
+ @uuid = SecureRandom.hex
34
+ # Reset the fail attribute
35
+ @check_browser_errors = true
36
+ # The original scenario from cucumber
37
+ @data = scenario
38
+ # A name without special characters.
39
+ @id = clean(scenario_id(scenario))
40
+ self.update_timestamp
41
+ end
42
+
43
+ def update_timestamp
44
+ now = Time.now
45
+ # The current time
46
+ @time = {
47
+ :timestamp => now.strftime('%y%m%d_%H%M%S'),
48
+ :iso_timestamp => now.utc.strftime("%FT%TZ"),
49
+ :iso_short => now.utc.strftime("%y%m%dT%H%M%SZ"),
50
+ :epoch => now.to_i.to_s
51
+ }
52
+ end
53
+
54
+ def tags
55
+ if !@data.nil?
56
+ return @data.source_tag_names
57
+ end
58
+ end
59
+
60
+ def scope(cleaned = false)
61
+ scope = nil
62
+ if @data.respond_to? :backtrace_line
63
+ scope = @data.backtrace_line
64
+ elsif @data.respond_to? :file_colon_line
65
+ scope = @data.file_colon_line
66
+ end
67
+
68
+ if scope.nil?
69
+ return nil
70
+ elsif cleaned
71
+ return clean [scope]
72
+ else
73
+ return scope
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def clean(strings)
80
+ result = []
81
+ strings.each do |string|
82
+ clean_string = string.gsub(/[^\w\.\-]/, ' ').strip.squeeze(' ').gsub(" ","_")
83
+ result.push(clean_string)
84
+ end
85
+ return result.join("-").squeeze("-")
86
+ end
87
+ end
88
+ end