unobtainium-cucumber 0.1.0

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.
@@ -0,0 +1,115 @@
1
+ Feature:
2
+ Provide the ability to conveniently perform actions when a scenario finishes
3
+ and its status is known.
4
+
5
+ Background:
6
+ Given I have a test instance of the StatusActions module
7
+ Then I expect all configured actions to be present
8
+ And I want to clear configured actions
9
+
10
+ Scenario Outline: Function `#action_key`
11
+ Given I have a scenario which has <has_passed>
12
+ And the scenario <is_outline> an outline
13
+ Then I expect the output to contain <status> and <type>
14
+
15
+ Examples:
16
+ | has_passed | is_outline | status | type |
17
+ | passed | is not | :passed? | :scenario |
18
+ | failed | is not | :failed? | :scenario |
19
+ | passed | is | :passed? | :outline |
20
+ | failed | is | :failed? | :outline |
21
+
22
+ Scenario Outline: Function `#register_action` status
23
+ Given I try to register an action for status <status>
24
+ Then I expect the function to <succeed_or_fail>
25
+
26
+ Examples:
27
+ | status | succeed_or_fail |
28
+ | passed? | succeed |
29
+ | failed? | succeed |
30
+ | passed | fail |
31
+ | | fail |
32
+
33
+ Scenario Outline: Function `#register_action` type option
34
+ Given I try to register an action for the type <type>
35
+ Then I expect the function to <succeed_or_fail>
36
+
37
+ Examples:
38
+ | type | succeed_or_fail |
39
+ | scenario | succeed |
40
+ | outline | succeed |
41
+ | other | fail |
42
+ | | fail |
43
+
44
+ Scenario: Try to register neither function nor block
45
+ Given I try to register no action
46
+ Then I expect the function to fail
47
+
48
+ Scenario: Try to register both function and block
49
+ Given I try to register two actions
50
+ Then I expect the function to fail
51
+
52
+ Scenario: No action, but options
53
+ Given I register no action, but provide options
54
+ Then I expect the there to be an error
55
+
56
+ Scenario: Invalid Hash action
57
+ Given I register a Hash as an action
58
+ Then I expect the there to be an error
59
+
60
+ Scenario: Multiple actions
61
+ Given I register an action
62
+ When I register another action
63
+ Then I expect there to be two registered actions
64
+
65
+ Scenario: No actions
66
+ Given I have registered no actions
67
+ Then I expect there to be no registered actions
68
+
69
+ Scenario: Action is executed
70
+ Given I have registered an action
71
+ Then I expect it to be executed
72
+
73
+ Scenario: Configured actions should be present
74
+ Given I register configured actions
75
+ Then I expect only configured actions to be present
76
+
77
+ Scenario: Execute method
78
+ Given I execute a method action
79
+ Then I expect this to succeed
80
+
81
+ Scenario: Execute block
82
+ Given I execute a block action
83
+ Then I expect this to succeed
84
+
85
+ Scenario: Execute symbol
86
+ Given I execute a symbol action
87
+ Then I expect this to succeed
88
+
89
+ Scenario: Execute string
90
+ Given I execute a string action
91
+ Then I expect this to succeed
92
+
93
+ Scenario: Execute namespaced string
94
+ Given I execute a namespaced string action
95
+ Then I expect this to succeed
96
+
97
+ Scenario: Execute dot-separated namespaced string
98
+ Given I execute a namespaced string action that is dot-separated
99
+ Then I expect this to succeed
100
+
101
+ Scenario: Execute non-resolving string
102
+ Given I execute string action that does not resolve
103
+ Then I expect this to fail
104
+
105
+ Scenario: Execute non-resolving symbol
106
+ Given I execute symbol action that does not resolve
107
+ Then I expect this to fail
108
+
109
+ Scenario: Execute non-resolving string with two dots
110
+ Given I execute string action with two dots
111
+ Then I expect this to fail
112
+
113
+ Scenario: Try to execute number
114
+ Given I execute a number action
115
+ Then I expect this to fail
@@ -0,0 +1,61 @@
1
+ # coding: utf-8
2
+ #
3
+ # unobtainium-cucumber
4
+ # https://github.com/jfinkhaeuser/unobtainium-cucumber
5
+ #
6
+ # Copyright (c) 2016 Jens Finkhaeuser and other unobtainium-cucumber
7
+ # contributors. All rights reserved.
8
+ #
9
+
10
+ require 'unobtainium-cucumber/action/screenshot'
11
+ require 'unobtainium-cucumber/action/content'
12
+ require_relative './mocks/scenario'
13
+
14
+ Given(/^I take a screenshot$/) do
15
+ ::Unobtainium::Cucumber::Action.store_screenshot(
16
+ self, MockScenario.new('screenshots')
17
+ )
18
+ end
19
+
20
+ Then(/^I expect there to be a matching screenshot file$/) do
21
+ # The expectation is that the file starts with an ISO8601 timestamp of today
22
+ # and ends in 'screenshots.png'. The part we can't be certain about is the
23
+ # exact time stamp, because seconds, minutes, etc. even years could have
24
+ # rolled over between taking the screenshot and finding it.
25
+
26
+ # So what we do instead is find files that match the end. If the start matches
27
+ # the syntax of a timestamp string, we can convert it to a timestamp. Then we
28
+ # can find out if between said timestamp and right now only a few seconds
29
+ # elapsed. If we find one such file, the test succeeds.
30
+ pattern = 'screenshots/*-screenshots.png'
31
+ timeout_match_files(pattern)
32
+
33
+ # *After* all checks, remove matching files.
34
+ FileUtils.rm(Dir.glob(pattern))
35
+ end
36
+
37
+ Given(/^I navigate to the best site in the world$/) do
38
+ driver.navigate.to 'http://finkhaeuser.de'
39
+ end
40
+
41
+ When(/^I capture the page content$/) do
42
+ # See the similar screnshot matching step for some details.
43
+ ::Unobtainium::Cucumber::Action.store_content(
44
+ self, MockScenario.new('contents')
45
+ )
46
+ end
47
+
48
+ Then(/^I expect there to be a matching content file$/) do
49
+ # See the similar screnshot matching step for some details.
50
+ pattern = 'content/*-contents.txt'
51
+ match_file = timeout_match_files(pattern)
52
+
53
+ # Check that some expected content exists.
54
+ match = File.open(match_file).grep(/<html/)
55
+ if match.empty?
56
+ raise "File content of '#{match_file}' does not seem to be valid HTML!"
57
+ end
58
+
59
+ # *After* all checks, remove matching files.
60
+ FileUtils.rm(Dir.glob(pattern))
61
+ end
@@ -0,0 +1,40 @@
1
+ # coding: utf-8
2
+ #
3
+ # unobtainium-cucumber
4
+ # https://github.com/jfinkhaeuser/unobtainium-cucumber
5
+ #
6
+ # Copyright (c) 2016 Jens Finkhaeuser and other unobtainium-cucumber
7
+ # contributors. All rights reserved.
8
+ #
9
+
10
+ require 'unobtainium-cucumber/action/support/naming.rb'
11
+ require_relative './mocks/scenario'
12
+
13
+ Given(/^I have a scenario named (.+)$/) do |scenario|
14
+ @action_support = {}
15
+ @action_support[:scenario] = MockScenario.new(scenario)
16
+ end
17
+
18
+ Given(/^I provide a tag (.+)$/) do |tag|
19
+ if tag == 'NIL'
20
+ tag = nil
21
+ end
22
+ @action_support[:tag] = tag
23
+ end
24
+
25
+ Given(/^the timestamp is (\d+\-\d+\-\d+T\d+:\d+:\d+Z)$/) do |timestamp|
26
+ @action_support[:timestamp] = timestamp
27
+ end
28
+
29
+ Then(/^I expect the filename to match (.+)/) do |expected|
30
+ expected = Regexp.new(expected)
31
+
32
+ tester = Class.new { extend ::Unobtainium::Cucumber::Action::Support }
33
+ result = tester.base_filename(@action_support[:scenario],
34
+ @action_support[:tag],
35
+ @action_support[:timestamp])
36
+
37
+ if not expected.match(result)
38
+ raise "Result '#{result}' did not match expectation #{expected}!"
39
+ end
40
+ end
@@ -0,0 +1,85 @@
1
+ # coding: utf-8
2
+ #
3
+ # unobtainium-cucumber
4
+ # https://github.com/jfinkhaeuser/unobtainium-cucumber
5
+ #
6
+ # Copyright (c) 2016 Jens Finkhaeuser and other unobtainium-cucumber
7
+ # contributors. All rights reserved.
8
+ #
9
+
10
+ # rubocop:disable Style/GlobalVars
11
+ $reset_called = 0
12
+
13
+ def define_reset(the_driver = driver)
14
+ the_driver.class.class_eval do
15
+ def reset(*args)
16
+ $reset_called += 1
17
+ # rubocop:disable Lint/HandleExceptions
18
+ begin
19
+ super(*args)
20
+ rescue NoMethodError
21
+ # Work around drivers not supporting reset
22
+ end
23
+ # rubocop:enable Lint/HandleExceptions
24
+ end
25
+ end
26
+ end
27
+
28
+ def undefine_reset(the_driver = driver)
29
+ the_driver.class.class_eval do
30
+ # rubocop:disable Lint/HandleExceptions
31
+ begin
32
+ remove_method :reset
33
+ rescue NameError
34
+ end
35
+ # rubocop:enable Lint/HandleExceptions
36
+ end
37
+ end
38
+
39
+ Given(/^I remove any reset functions$/) do
40
+ undefine_reset
41
+ end
42
+
43
+ Given(/^I run a scenario to test driver reset$/) do
44
+ # We decorate the driver class with a 'reset' method for two reasons:
45
+ # a) we can this way track that it was called, and
46
+ # b) we work around the fact that not all drivers support the 'reset'
47
+ # function.
48
+ define_reset
49
+ end
50
+
51
+ Then(/^the driver should be reset at the end$/) do
52
+ # Absolutely nothing to do here; but see $reset_called
53
+ end
54
+
55
+ Given(/^I run a scenario with a driver that knowns no reset$/) do
56
+ # Instanciate the driver by looking at its class.
57
+ driver.class
58
+ end
59
+
60
+ Then(/^the driver should not be reset at the end$/) do
61
+ # Absolutely nothing to do here; but see $reset_called
62
+ end
63
+
64
+ Given(/^I run a scenario with driver reset switched off$/) do
65
+ define_reset
66
+ # Note: enable this line, and $reset_called should be *zero* at the end.
67
+ # Testing this is really, really awkward.
68
+ # config['cucumber.driver_reset'] = false
69
+ end
70
+
71
+ at_exit do
72
+ # We expect reset to be called exactly twice, for the driver that defines
73
+ # such a function. But see the scenario for switching off driver reset
74
+ # above.
75
+ if not $reset_called >= 2
76
+ warn '*' * 80
77
+ warn "* If this fails, check the steps in 'driver_reset.feature'!"
78
+ warn "* We expected reset to be twice or more, but it got called "\
79
+ "#{$reset_called} times!"
80
+ warn '*' * 80
81
+ Kernel.exit(3)
82
+ end
83
+ end
84
+
85
+ # rubocop:enable Style/GlobalVars
@@ -0,0 +1,19 @@
1
+ # coding: utf-8
2
+ #
3
+ # unobtainium-cucumber
4
+ # https://github.com/jfinkhaeuser/unobtainium-cucumber
5
+ #
6
+ # Copyright (c) 2016 Jens Finkhaeuser and other unobtainium-cucumber
7
+ # contributors. All rights reserved.
8
+ #
9
+
10
+ Given(/^I register an extension with cucumber's World$/) do
11
+ # There's nothing to do here. The extension is registered in
12
+ # features/support/env.rb as it should be.
13
+ end
14
+
15
+ Then(/^I expect this extension to be used$/) do
16
+ # Calling the method should already fail, so no need for testing whether
17
+ # the method is callable :)
18
+ method_from_own_extension
19
+ end
@@ -0,0 +1,18 @@
1
+ # coding: utf-8
2
+ #
3
+ # unobtainium-cucumber
4
+ # https://github.com/jfinkhaeuser/unobtainium-cucumber
5
+ #
6
+ # Copyright (c) 2016 Jens Finkhaeuser and other unobtainium-cucumber
7
+ # contributors. All rights reserved.
8
+ #
9
+
10
+ ##
11
+ # A mock scenario has a name
12
+ class MockScenario
13
+ def initialize(name)
14
+ @name = name
15
+ end
16
+
17
+ attr_reader :name
18
+ end
@@ -0,0 +1,358 @@
1
+ # coding: utf-8
2
+ #
3
+ # unobtainium-cucumber
4
+ # https://github.com/jfinkhaeuser/unobtainium-cucumber
5
+ #
6
+ # Copyright (c) 2016 Jens Finkhaeuser and other unobtainium-cucumber
7
+ # contributors. All rights reserved.
8
+ #
9
+
10
+ # rubocop:disable Style/GlobalVars
11
+
12
+ def dummy_action(*_); end
13
+
14
+ def global_action(*_); end
15
+
16
+ $counter = 0
17
+ def counting_action(*_)
18
+ $counter += 1
19
+ end
20
+
21
+ def recording_action(_, scenario)
22
+ scenario.called = true
23
+ end
24
+
25
+ # TestModule
26
+ module TestModule
27
+ def self.inner_recorder(*args)
28
+ recording_action(*args)
29
+ end
30
+ end
31
+
32
+ # StatusActionsTester doubles both as a test target for the actions, a
33
+ # state crossing multiple step definitions, and behaving a bit like
34
+ # a scenario.
35
+ class StatusActionsTester
36
+ # Include StatusActions functionality...
37
+ include ::Unobtainium::Cucumber::StatusActions
38
+
39
+ # ... but also double as scenario
40
+ def passed?
41
+ return @passed
42
+ end
43
+
44
+ def outline?
45
+ return @outline
46
+ end
47
+
48
+ # Also, add setters for the above.
49
+ attr_writer :passed
50
+ attr_writer :outline
51
+
52
+ # Accessors for various test states
53
+ attr_accessor :register_status
54
+ attr_accessor :register_type
55
+ attr_accessor :register_functions
56
+
57
+ attr_accessor :exception
58
+
59
+ attr_accessor :called
60
+ end
61
+
62
+ Given(/^I have a test instance of the StatusActions module$/) do
63
+ @status_actions = StatusActionsTester.new
64
+ end
65
+
66
+ Then(/^I want to clear configured actions$/) do
67
+ @status_actions.clear_actions
68
+ end
69
+
70
+ Given(/^I have a scenario which has (passed|failed)$/) do |result|
71
+ @status_actions.passed = result == 'passed' ? true : false
72
+ end
73
+
74
+ Given(/^the scenario is( not)? an outline$/) do |negation|
75
+ @status_actions.outline = negation.nil? ? true : false
76
+ end
77
+
78
+ Then(
79
+ /^I expect the output to contain :(passed\?|failed\?) and :(scenario|outline)$/
80
+ ) do |status, type|
81
+ # Since the status action instance behaves as a scenario from the previous
82
+ # steps, we can pass it to the action_key method.
83
+ result = @status_actions.action_key(@status_actions)
84
+
85
+ expectation = [status.to_sym, type.to_sym]
86
+ if result != expectation
87
+ raise "Got '#{result}' but expected '#{expectation}'!"
88
+ end
89
+ end
90
+
91
+ Given(/^I try to register an action for status (.*)$/) do |status|
92
+ @status_actions.register_status = status.strip
93
+ @status_actions.register_type = :scenario # valid
94
+ @status_actions.register_functions = [:dummy_action, nil] # valid
95
+ end
96
+
97
+ Given(/^I try to register an action for the type (.*)$/) do |type|
98
+ @status_actions.register_status = :passed? # valid
99
+ @status_actions.register_type = type.strip
100
+ @status_actions.register_functions = [:dummy_action, nil] # valid
101
+ end
102
+
103
+ Given(/^I try to register no action$/) do
104
+ @status_actions.register_status = :passed? # valid
105
+ @status_actions.register_type = :scenario # valid
106
+ @status_actions.register_functions = [nil, nil]
107
+ end
108
+
109
+ Given(/^I try to register two actions$/) do
110
+ @status_actions.register_status = :passed? # valid
111
+ @status_actions.register_type = :scenario # valid
112
+ @status_actions.register_functions = [:dummy_action, proc { |*args| }]
113
+ end
114
+
115
+ Then(/^I expect the function to (.+)$/) do |result|
116
+ result.strip!
117
+
118
+ status_sym = @status_actions.register_status.to_sym
119
+ type_sym = @status_actions.register_type.to_sym
120
+ action_func = @status_actions.register_functions[0]
121
+ action_block = @status_actions.register_functions[1]
122
+
123
+ begin
124
+ @status_actions.register_action(status_sym, action_func, type: type_sym,
125
+ &action_block)
126
+ rescue RuntimeError => err
127
+ if result == 'succeed'
128
+ raise err
129
+ end
130
+ else
131
+ if result == 'fail'
132
+ raise "Expected this to fail, but it didn't!"
133
+ end
134
+ end
135
+ end
136
+
137
+ Given(/^I register no action, but provide options$/) do
138
+ # We'll perform the call, and record if there was an exception
139
+ begin
140
+ @status_actions.register_action(:passed?, type: :scenario)
141
+ rescue RuntimeError => err
142
+ @status_actions.exception = err
143
+ else
144
+ @status_actions.exception = nil
145
+ end
146
+ end
147
+
148
+ Given(/^I register a Hash as an action$/) do
149
+ # We'll perform the call, and record if there was an exception
150
+ begin
151
+ # Note that this is possible to do, but shouldn't happen. Normally
152
+ # if you pass a Hash as the second parmeter, you'd expect that to be
153
+ # the options, and a block to be provided.
154
+ @status_actions.register_action(:passed?, {}, {})
155
+ rescue RuntimeError => err
156
+ @status_actions.exception = err
157
+ else
158
+ @status_actions.exception = nil
159
+ end
160
+ end
161
+
162
+ Then(/^I expect the there to be an error$/) do
163
+ # Named differently from "the function to fail" because the above step would
164
+ # get far too complex.
165
+
166
+ # We expect an exception to be recorded, and if there is none, we fail
167
+ if @status_actions.exception.nil?
168
+ raise "Expected this to fail, but it didn't!"
169
+ end
170
+ end
171
+
172
+ Given(/^I register an action$/) do
173
+ @status_actions.register_action(:passed?, :dummy_action)
174
+ end
175
+
176
+ When(/^I register another action$/) do
177
+ @status_actions.register_action(:passed?) do |*args|
178
+ end
179
+ end
180
+
181
+ Then(/^I expect there to be two registered actions$/) do
182
+ # The default type is :scenario
183
+ registered = @status_actions.registered_actions(:passed?, :scenario)
184
+ if registered.length != 2
185
+ raise "Expected two registered actions, but found: #{registered.lenght}"
186
+ end
187
+ end
188
+
189
+ Given(/^I have registered no actions$/) do
190
+ # Not doing anything here!
191
+ end
192
+
193
+ Then(/^I expect there to be no registered actions$/) do
194
+ # The default type is :scenario
195
+ registered = @status_actions.registered_actions(:passed?, :scenario)
196
+ if not registered.empty?
197
+ raise "Expected zero registered actions, but found: #{registered.lenght}"
198
+ end
199
+ end
200
+
201
+ Given(/^I have registered an action$/) do
202
+ @status_actions.register_action(:passed?, :counting_action)
203
+ end
204
+
205
+ Then(/^I expect it to be executed$/) do
206
+ # Nothing to do; see at_exit below
207
+ end
208
+
209
+ Given(/^I register configured actions$/) do
210
+ # Configured actions should be registered automatically; that's hard to
211
+ # test here. However, see the background step!
212
+ # Let's just register them again!
213
+ register_config_actions(self)
214
+ end
215
+
216
+ Then(/^I expect all configured actions to be present$/) do
217
+ registered = @status_actions.registered_actions(:passed?, :scenario)
218
+ if not registered.include?('global_action')
219
+ raise "Expected 'global_action' in #{registered}!"
220
+ end
221
+
222
+ registered = @status_actions.registered_actions(:passed?, :outline)
223
+ if not registered.include?('global_action')
224
+ raise "Expected 'global_action' in #{registered}!"
225
+ end
226
+
227
+ registered = @status_actions.registered_actions(:failed?, :scenario)
228
+ if not registered.include?('method_from_own_extension')
229
+ raise "Expected 'global_action' in #{registered}!"
230
+ end
231
+
232
+ registered = @status_actions.registered_actions(:failed?, :outline)
233
+ if not registered.include?('dummy_action')
234
+ raise "Expected 'global_action' in #{registered}!"
235
+ end
236
+ end
237
+
238
+ Then(/^I expect only configured actions to be present$/) do
239
+ registered = @status_actions.registered_actions(:passed?, :scenario)
240
+ if registered != %w(global_action)
241
+ raise "Expected different actions than #{registered}!"
242
+ end
243
+
244
+ registered = @status_actions.registered_actions(:passed?, :outline)
245
+ if registered != %w(global_action)
246
+ raise "Expected different actions than #{registered}!"
247
+ end
248
+
249
+ registered = @status_actions.registered_actions(:failed?, :scenario)
250
+ if registered != %w(method_from_own_extension)
251
+ raise "Expected different actions than #{registered}!"
252
+ end
253
+
254
+ registered = @status_actions.registered_actions(:failed?, :outline)
255
+ if registered != %w(dummy_action)
256
+ raise "Expected different actions than #{registered}!"
257
+ end
258
+ end
259
+
260
+ Given(/^I execute a method action$/) do
261
+ @status_actions.called = false
262
+ @status_actions.execute_action(self, method(:recording_action), @status_actions)
263
+ end
264
+
265
+ Then(/^I expect this to succeed$/) do
266
+ if not @status_actions.called
267
+ raise "Expected the action to be invoked, but it wasn't!"
268
+ end
269
+ end
270
+
271
+ Given(/^I execute a block action$/) do
272
+ action = proc do |_, scenario|
273
+ scenario.called = true
274
+ end
275
+
276
+ @status_actions.called = false
277
+ @status_actions.execute_action(self, action, @status_actions)
278
+ end
279
+
280
+ Given(/^I execute a symbol action$/) do
281
+ @status_actions.called = false
282
+ @status_actions.execute_action(self, :recording_action, @status_actions)
283
+ end
284
+
285
+ Given(/^I execute a string action$/) do
286
+ @status_actions.called = false
287
+ @status_actions.execute_action(self, 'recording_action', @status_actions)
288
+ end
289
+
290
+ Given(/^I execute a namespaced string action$/) do
291
+ @status_actions.called = false
292
+ @status_actions.execute_action(self, '::TestModule::inner_recorder',
293
+ @status_actions)
294
+ end
295
+
296
+ Given(/^I execute a namespaced string action that is dot\-separated$/) do
297
+ @status_actions.called = false
298
+ @status_actions.execute_action(self, '::TestModule.inner_recorder',
299
+ @status_actions)
300
+ end
301
+
302
+ Given(/^I execute string action that does not resolve$/) do
303
+ @status_actions.called = false
304
+ # rubocop:disable Lint/HandleExceptions
305
+ begin
306
+ @status_actions.execute_action(self, 'does not resolve', @status_actions)
307
+ rescue => _err
308
+ end
309
+ # rubocop:enable Lint/HandleExceptions
310
+ end
311
+
312
+ Given(/^I execute symbol action that does not resolve$/) do
313
+ @status_actions.called = false
314
+ # rubocop:disable Lint/HandleExceptions
315
+ begin
316
+ @status_actions.execute_action(self, :does_not_resolve, @status_actions)
317
+ rescue => _err
318
+ end
319
+ # rubocop:enable Lint/HandleExceptions
320
+ end
321
+
322
+ Given(/^I execute string action with two dots$/) do
323
+ @status_actions.called = false
324
+ # rubocop:disable Lint/HandleExceptions
325
+ begin
326
+ @status_actions.execute_action(self, 'can.not.resolve', @status_actions)
327
+ rescue => _err
328
+ end
329
+ # rubocop:enable Lint/HandleExceptions
330
+ end
331
+
332
+ Given(/^I execute a number action$/) do
333
+ @status_actions.called = false
334
+ # rubocop:disable Lint/HandleExceptions
335
+ begin
336
+ @status_actions.execute_action(self, 42, @status_actions)
337
+ rescue => _err
338
+ end
339
+ # rubocop:enable Lint/HandleExceptions
340
+ end
341
+
342
+ Then(/^I expect this to fail$/) do
343
+ if @status_actions.called
344
+ raise "Expected the action not to be invoked, but it was!"
345
+ end
346
+ end
347
+
348
+ at_exit do
349
+ # We expect the counting_action to be called once.
350
+ if not $counter >= 1
351
+ warn '*' * 80
352
+ warn "* If this fails, check the steps in 'status_actions.feature'!"
353
+ warn '*' * 80
354
+ Kernel.exit(4)
355
+ end
356
+ end
357
+
358
+ # rubocop:enable Style/GlobalVars