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,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