lapis_lazuli 0.6.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.
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,59 @@
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
+ # Simple storage class
11
+ class Storage
12
+ @data
13
+ def initialize
14
+ @data = {}
15
+ end
16
+
17
+ def set(key, value)
18
+ @data[key] = value
19
+ end
20
+
21
+ def get(key)
22
+ return @data[key]
23
+ end
24
+
25
+ def has?(key)
26
+ return @data.include? key
27
+ end
28
+
29
+ ##
30
+ # Write all stored data to file
31
+ def writeToFile(filename)
32
+ # Make storage directory
33
+ dir = File.dirname(filename)
34
+ begin
35
+ Dir.mkdir dir
36
+ rescue SystemCallError => ex
37
+ # Swallow this error; it occurs (amongst other situations) when the
38
+ # directory exists. Checking for an existing directory beforehand is
39
+ # not concurrency safe.
40
+ end
41
+
42
+ File.open(filename, 'w') { |file|
43
+ # Write the JSON to the file
44
+ file.write(@data.to_json)
45
+ }
46
+ end
47
+
48
+ ##
49
+ # This will be called during the destruction of the world
50
+ def destroy(world)
51
+ filename = world.config("storage_dir", ".#{File::SEPARATOR}storage") +
52
+ File::SEPARATOR +
53
+ world.time[:timestamp] +
54
+ ".json"
55
+ world.log.debug("Writing storage to: #{filename}")
56
+ self.writeToFile(filename)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,10 @@
1
+ #
2
+ # LapisLazuli
3
+ # https://github.com/spriteCloud/lapis-lazuli
4
+ #
5
+ # Copyright (c) 2013-2015 spriteCloud B.V. and other LapisLazuli contributors.
6
+ # All rights reserved.
7
+ #
8
+ module LapisLazuli
9
+ VERSION = "0.6.1"
10
+ end
@@ -0,0 +1,40 @@
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 'lapis_lazuli/api'
9
+
10
+ module LapisLazuli
11
+
12
+ ##
13
+ # Given a versions string or hash, stores it for later use with the library.
14
+ attr_accessor :software_versions
15
+ extend self
16
+
17
+ ##
18
+ # Connedt to the endpoint or to ENV['VERSION_ENDPOINT'], then retrieve the
19
+ # url. The contents should be the software versions used.
20
+ def self.fetch_versions(url, endpoint = nil)
21
+ # Set the connection endpoint. This is either the endpoint, or the
22
+ # environment variable 'VERSION_ENDPOINT'.
23
+ if ENV.has_key?('VERSION_ENDPOINT')
24
+ endpoint = ENV['VERSION_ENDPOINT']
25
+ end
26
+
27
+ # Connect to the endpoint
28
+ api = API.new
29
+ api.set_conn(endpoint)
30
+
31
+ # Fetch versions
32
+ response = api.get(url)
33
+ if 2 != response.status / 100
34
+ raise "Error retrieving software versions, got status code #{response.status}"
35
+ end
36
+
37
+ # Store that stuff for later.
38
+ self.software_versions = response.body
39
+ end
40
+ end # module LapisLazuli
@@ -0,0 +1,45 @@
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 'json'
10
+
11
+ require 'lapis_lazuli/argparse'
12
+
13
+ module LapisLazuli
14
+ module WorldModule
15
+ ##
16
+ # Module with annotation related functionality
17
+ #
18
+ # Annotations are embedded into the report via cucumber's embed function, and
19
+ # that means they're embedded at the step level.
20
+ #
21
+ # They're also stored at scenario scope, so one step in a scenario can access
22
+ # annotations made in another step.
23
+ module Annotate
24
+
25
+ include LapisLazuli::ArgParse
26
+
27
+ def annotate(*args)
28
+ @annotations ||= {}
29
+
30
+ scope = scenario.scope(true) || 'items'
31
+ stuff = parse_args({}, scope, *args)
32
+
33
+ for_scope = @annotations.fetch(scope, [])
34
+ for_scope << stuff[scope]
35
+ @annotations[scope] = for_scope
36
+
37
+ embed(JSON.generate(stuff), 'application/json')
38
+ end
39
+
40
+ def annotations
41
+ @annotations
42
+ end
43
+ end # module Annotate
44
+ end # module WorldModule
45
+ end # module LapisLazuli
@@ -0,0 +1,35 @@
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/api"
10
+ require "lapis_lazuli/runtime"
11
+
12
+ module LapisLazuli
13
+ module WorldModule
14
+ ##
15
+ # Module managing an API instance
16
+ module API
17
+
18
+ ##
19
+ # Has API?
20
+ def has_api?
21
+ a = Runtime::instance.get :api
22
+ return !a.nil?
23
+ end
24
+
25
+ ##
26
+ # Get/create the API instance
27
+ def api
28
+ return Runtime.instance.set_if(self, :api) do
29
+ LapisLazuli::API.new
30
+ end
31
+ end
32
+
33
+ end # module API
34
+ end # module WorldModule
35
+ end # module LapisLazuli
@@ -0,0 +1,75 @@
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/browser"
10
+ require "lapis_lazuli/runtime"
11
+
12
+ require "lapis_lazuli/world/config"
13
+ require "lapis_lazuli/world/logging"
14
+ require "lapis_lazuli/world/error"
15
+ require "lapis_lazuli/world/proxy"
16
+
17
+ module LapisLazuli
18
+ module WorldModule
19
+ ##
20
+ # Module managing a browser instance
21
+ module Browser
22
+ include LapisLazuli::WorldModule::Config
23
+ include LapisLazuli::WorldModule::Logging
24
+ include LapisLazuli::WorldModule::Error
25
+ include LapisLazuli::WorldModule::Proxy
26
+
27
+ ##
28
+ # Store extension modules for the browser
29
+ module ClassMethods
30
+ def browser_module(module_name)
31
+ @extensions ||= []
32
+ @extensions << module_name
33
+ end
34
+
35
+ def browser_modules
36
+ @extensions
37
+ end
38
+ end
39
+ extend ClassMethods
40
+
41
+ ##
42
+ # Checks if there is a browser started
43
+ def has_browser?
44
+ b = Runtime.instance.get :browser
45
+ return (not b.nil? and b.is_open?)
46
+ end
47
+
48
+ ##
49
+ # Get the current main browser
50
+ def browser(*args)
51
+ b = Runtime.instance.set_if(self, :browser) do
52
+ # Add LL to the arguments for the browser
53
+ browser_args = args.unshift(self)
54
+ # Create a new browser object
55
+ inst = LapisLazuli::Browser.new(*browser_args)
56
+ # Extend the instance
57
+ if not Browser.browser_modules.nil?
58
+ Browser.browser_modules.each do |ext|
59
+ inst.extend(ext)
60
+ end
61
+ end
62
+ # Return the instance
63
+ inst
64
+ end
65
+
66
+ if not b.is_open?
67
+ b.start
68
+ end
69
+
70
+ return b
71
+ end
72
+
73
+ end # module Browser
74
+ end # module WorldModule
75
+ end # module LapisLazuli
@@ -0,0 +1,292 @@
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/options"
10
+
11
+ module LapisLazuli
12
+ module WorldModule
13
+ ##
14
+ # Module with configuration loading related functions
15
+ #
16
+ # Manages the following:
17
+ # @config - internal configuration representation
18
+ # config_file - Needs to be set before config can be accessed.
19
+ # @env - loaded/detected config/test environment
20
+ module Config
21
+ ##
22
+ # Explicitly store the configuration file name.
23
+ module ClassMethods
24
+ def config_file=(name)
25
+ @config_file = name
26
+ end
27
+
28
+ def config_file
29
+ return @config_file || "config/config.yml"
30
+ end
31
+ end
32
+ extend ClassMethods
33
+
34
+
35
+ ##
36
+ # The configuration is not a singleton, precisely, but it does not need to
37
+ # be created more than once. Note that explicitly calling load_config will
38
+ # still work to overwrite an existing configuration.
39
+ def init
40
+ # Guard against doing this more than once.
41
+ if not @config.nil?
42
+ return
43
+ end
44
+
45
+ if Config.config_file.nil?
46
+ raise "No configuration file provided, set LapisLazuli::WorldModule::Config.config_file"
47
+ end
48
+
49
+ load_config(Config.config_file)
50
+
51
+ if @config.nil?
52
+ raise "Could not load configuration."
53
+ end
54
+ end
55
+
56
+
57
+ ##
58
+ # Loads a config based on a filename
59
+ #
60
+ # Supports: YML, JSON
61
+ #
62
+ # Example:
63
+ # ENV['TEST_ENV'] = 'production'
64
+ # load_config("config/config.yml")
65
+ #
66
+ # Will try to load the following files:
67
+ # - config/config-production.yml
68
+ # - config/config-debug.yml
69
+ # - config/config-test.yml
70
+ # - config/config-local.yml
71
+ # - config/config.yml
72
+ def load_config(config_name)
73
+ # Split the filename
74
+ ext = File.extname(config_name)
75
+ dir, filename = File.split(config_name)
76
+ basename = File.basename(filename, ext)
77
+
78
+ # What are the suffixes to check
79
+ suffixes = [
80
+ "debug",
81
+ "test",
82
+ "local"
83
+ ]
84
+
85
+ if ENV["TEST_ENV"]
86
+ @env = ENV["TEST_ENV"]
87
+ end
88
+
89
+ # Do we have an environment
90
+ if not @env.nil?
91
+ # Add it to the suffixes
92
+ suffixes.unshift(@env)
93
+ end
94
+
95
+ # Turn suffixes into files to try
96
+ files = []
97
+ suffixes.each do |suffix|
98
+ files << "#{dir}#{File::SEPARATOR}#{basename}-#{suffix}#{ext}"
99
+ end
100
+ files << config_name
101
+
102
+ # Try all files in order
103
+ files.each do |file|
104
+ begin
105
+ # Try to load a config file
106
+ return self.load_config_from_file(file)
107
+ rescue
108
+ # Do nothing, load the next file
109
+ end
110
+ end
111
+ end
112
+
113
+
114
+ ##
115
+ # Loads a config file
116
+ #
117
+ # Supports: YML, JSON
118
+ #
119
+ # Throws errors if:
120
+ # - Config file isn't readable
121
+ # - Environment doesn't exist in config
122
+ # - Default environment not set in config if no environment is set
123
+ def load_config_from_file(filename)
124
+ # Try to load the file from disk
125
+ begin
126
+ # Determine the extension
127
+ ext = File.extname(filename)
128
+ # Use the correct loader
129
+ if ext == ".yml"
130
+ @config = YAML.load_file(filename)
131
+ elsif ext == ".json"
132
+ json = File.read(filename)
133
+ @config = JSON.parse(json)
134
+ end
135
+ rescue RuntimeError => err
136
+ # Can't help you
137
+ raise "Error loading file: #{filename} #{err}"
138
+ end
139
+
140
+ # If we have an environment this config should have it
141
+ if not @env.nil? and not self.has_config?(@env)
142
+ raise "Environment doesn't exist in config file"
143
+ end
144
+
145
+ # If we don't have one then load the default
146
+ if @env.nil? and self.has_config?("default_env")
147
+ tmp = self.config("default_env")
148
+ if self.has_config?(tmp)
149
+ @env = tmp
150
+ else
151
+ raise "Default environment not present in config file"
152
+ end
153
+ end
154
+ end
155
+
156
+
157
+
158
+ ##
159
+ # Does the config have a variable?
160
+ # Uses config and catches any errors it raises
161
+ def has_config?(variable)
162
+ # Make sure the configured configuration is loaded, if possible
163
+ init
164
+
165
+ begin
166
+ value = self.config(variable)
167
+ return (not value.nil?)
168
+ rescue RuntimeError => err
169
+ return false
170
+ end
171
+ end
172
+
173
+ ##
174
+ # Get the configuration from the config,
175
+ # uses a dot seperator for object traversing
176
+ #
177
+ # Example:
178
+ # ll.config("test.google.url") => "www.google.com"
179
+ #
180
+ # Raises error if traversing the object is impossible
181
+ def config(variable=false, default=nil)
182
+ # Make sure the configured configuration is loaded, if possible
183
+ init
184
+
185
+ # No variable given? Return the entire object.
186
+ result = @config
187
+ if not variable
188
+ return result
189
+ end
190
+
191
+ # Environment variables for known options override the option.
192
+ if CONFIG_OPTIONS.has_key? variable
193
+ var = variable.upcase
194
+ if ENV.has_key? var
195
+ return ENV[var]
196
+ end
197
+ end
198
+
199
+ # Otherwise try to find it in the configuration object
200
+ variable.split(".").each do |part|
201
+ if default.nil? and result.nil?
202
+ raise "Unknown configuration variable '#{variable}' and no default given!"
203
+ end
204
+ break if result.nil?
205
+ result = result[part]
206
+ end
207
+
208
+ if default.nil? and result.nil?
209
+ if CONFIG_OPTIONS.has_key? variable
210
+ return CONFIG_OPTIONS[variable][0]
211
+ else
212
+ raise "Unknown configuration variable '#{variable}' and no default given!"
213
+ end
214
+ else
215
+ return result || default
216
+ end
217
+ end
218
+
219
+ ##
220
+ # Does the environment have a certain config variable
221
+ def has_env?(variable)
222
+ # Make sure the configured configuration is loaded, if possible
223
+ init
224
+
225
+ if @env.nil?
226
+ return false
227
+ end
228
+ return self.has_config?("#{@env}.#{variable}")
229
+ end
230
+
231
+ ##
232
+ # Returns current environment
233
+ def current_env
234
+ init
235
+
236
+ return @env
237
+ end
238
+
239
+ ##
240
+ # Get a environment variable from the config file
241
+ # Alias for ll.config(ll.env + "." + variable)
242
+ def env(variable=false, default=nil)
243
+ # Make sure the configured configuration is loaded, if possible
244
+ init
245
+
246
+ if not variable
247
+ return self.config(@env)
248
+ end
249
+
250
+ # Environment variables for known options override environment specific
251
+ # options, too
252
+ if CONFIG_OPTIONS.has_key? variable
253
+ var = variable.upcase
254
+ if ENV.has_key? var
255
+ return ENV[var]
256
+ end
257
+ end
258
+
259
+ return self.config("#{@env}.#{variable}",default)
260
+ end
261
+
262
+ ##
263
+ # Checks if a variabl exist in the env or config
264
+ def has_env_or_config?(variable)
265
+ # Make sure the configured configuration is loaded, if possible
266
+ init
267
+
268
+ return self.has_env?(variable) || self.has_config?(variable)
269
+ end
270
+
271
+ ##
272
+ # Get a variable from the config
273
+ # First checks the environment section, before it checks the global part
274
+ def env_or_config(variable, default=nil)
275
+ # Make sure the configured configuration is loaded, if possible
276
+ init
277
+
278
+ if self.has_env?(variable)
279
+ return self.env(variable, default)
280
+ elsif self.has_config?(variable)
281
+ return self.config(variable, default)
282
+ else
283
+ return nil
284
+ end
285
+ end
286
+
287
+
288
+
289
+
290
+ end # module Config
291
+ end # module WorldModule
292
+ end # module LapisLazuli
@@ -0,0 +1,141 @@
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
+ module WorldModule
11
+ ##
12
+ # Module with error handling related functionality
13
+ module Error
14
+ ##
15
+ # Throw an error based on some settings
16
+ #
17
+ # Examples:
18
+ # ll.error("Simple message") => "Simple message"
19
+ # ll.error(:message => "Simple message") => "Simple message"
20
+ # ll.error(:env => "test") => "Environment setting 'test' not found"
21
+ # ll.error(:env => "test", :exists => true) => "Environment setting 'test' found"
22
+ # ll.error(:screenshot => true, :message => "Simple") => "Simple", and screenshot is taken with the message name included.
23
+ def error(settings=nil)
24
+ # Default message
25
+ message = nil
26
+ groups = nil
27
+
28
+ # Default actions
29
+ screenshot = false
30
+ exception = nil
31
+
32
+ # Do we have settings
33
+ if not settings.nil?
34
+ # Simple string input
35
+ if settings.is_a? String
36
+ message = settings
37
+ elsif settings.is_a? Hash
38
+ if settings.has_key? :message
39
+ message = settings[:message]
40
+ end
41
+ # Environment errors
42
+ if settings.has_key? :env
43
+ # Does the value exist or not?
44
+ exists = ""
45
+ if not (settings.has_key?(:exists) or settings[:exists])
46
+ exists = ' not'
47
+ end
48
+ message = "Environment setting '#{settings[:env]}'" +
49
+ exists + " found"
50
+ end
51
+
52
+ if settings.has_key? :scenario
53
+ message = "Scenario failed: #{settings[:scenario]}"
54
+ elsif settings.has_key? :not_found
55
+ message = "Not found: #{settings[:not_found]}"
56
+ end
57
+
58
+ # Grouping of errors
59
+ if settings.has_key? :groups
60
+ grouping = settings[:groups]
61
+ if grouping.is_a? String
62
+ groups = [grouping]
63
+ elsif grouping.is_a? Array
64
+ groups = grouping
65
+ end
66
+ end
67
+
68
+ # Exception message shouldn't get lost
69
+ if settings.has_key? :exception and not settings[:exception].nil?
70
+ exception = settings[:exception]
71
+ if message.nil?
72
+ message = settings[:exception].message
73
+ else
74
+ message = "#{message} - #{settings[:exception].message}"
75
+ end
76
+ elsif message.nil?
77
+ message = "An unknown error occurred."
78
+ end
79
+
80
+ # Check if we want to take a screenshot
81
+ if settings.has_key? :screenshot
82
+ screenshot = !!settings[:screenshot]
83
+ end
84
+ end
85
+ end
86
+
87
+ # Include URL if we have a browser
88
+ if self.has_browser?
89
+ message += " (#{self.browser.url})"
90
+ end
91
+
92
+ # Add the groups to the message
93
+ if not groups.nil?
94
+ message = "[#{groups.join("][")}] #{message}"
95
+ end
96
+
97
+ # Write the error to the log
98
+ if self.log
99
+ self.log.error(message)
100
+ end
101
+
102
+ # Take screenshot, if necessary
103
+ if screenshot
104
+ self.browser.take_screenshot(message)
105
+ end
106
+
107
+ # Start debugger, if necessary
108
+ if self.env_or_config("breakpoint_on_error")
109
+ self.start_debugger
110
+ end
111
+
112
+ # Raise the message
113
+ if not exception.nil?
114
+ # message already contains ex.message here - or it should
115
+ raise exception.class, message, exception.backtrace
116
+ else
117
+ raise message
118
+ end
119
+ end
120
+
121
+ ##
122
+ # If byebug (ruby >= 2.0) or debugger (ruby < 2.0) are installed, start
123
+ # the debugger now.
124
+ def start_debugger
125
+ # First try the more modern 'byebug'
126
+ begin
127
+ require "byebug"
128
+ byebug
129
+ rescue LoadError
130
+ # If that fails, try the older debugger
131
+ begin
132
+ require 'debugger'
133
+ debugger
134
+ rescue LoadError
135
+ self.log.info "No debugger found, can't break on failures."
136
+ end
137
+ end
138
+ end
139
+ end # module Error
140
+ end # module WorldModule
141
+ end # module LapisLazuli