cyperful 0.1.10 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f340215ffcef3e16c1738d36eb2b71461bbd32e2011598d000793326f806b6d4
4
- data.tar.gz: 545e19084294a2d481a7e9d156f6ba5e4758fb52332c17dc2e2574be820b2256
3
+ metadata.gz: 928af50672505bb877c08f2ef62533dee16149822b591eb0d854b28235fcd207
4
+ data.tar.gz: 8b3974789ec4d679fd6413fd5a8ac09b770e038487741871249da41815faeb1a
5
5
  SHA512:
6
- metadata.gz: 524bbb91d707a457adfa888bc8db5a2d97886714dedc026d8647d2f243f8ff381f46a47af8cffe3fb4d46242aacfd515f2dda1b2a794252f758b5899ca312e9a
7
- data.tar.gz: e20223308f8c0df25df5aab64d97f24d4ef6f0b57bb530106e82a3234f5701f1ca434afa87dad9b50155f73043aef6b1afbe328ebb99aed35ad53fac29dc5ef9
6
+ metadata.gz: '08d44698ee7d2e39c0b6cc36395d359983348e24378fa0f71a13c029d26cdeded6283828054498e524db52f39564214a33bb8f860ad857accaa4d22edd8c6a02'
7
+ data.tar.gz: a3aa8baf3c5d3a12747e316aeff713b834cc0ef8ac1a784ab24ffc2c405fb4f60908f8109b2b0a24a504e8d66a3d71e620ad6cd02c11b53c9734b04e005f8b12
@@ -1,6 +1,14 @@
1
1
  class Cyperful::Driver
2
2
  attr_reader :steps, :pausing
3
3
 
4
+ # delegate
5
+ private def logger
6
+ Cyperful.logger
7
+ end
8
+ private def config
9
+ Cyperful.config
10
+ end
11
+
4
12
  SCREENSHOTS_DIR = File.join(Cyperful::ROOT_DIR, "public/screenshots")
5
13
 
6
14
  def initialize
@@ -8,6 +16,12 @@ class Cyperful::Driver
8
16
 
9
17
  @session = Capybara.current_session
10
18
  raise "Could not find Capybara session" unless @session
19
+ unless @session.driver.is_a?(Capybara::Selenium::Driver)
20
+ raise "Cyperful only supports Selenium, got: #{@session.driver}"
21
+ end
22
+ unless @session.driver.browser.browser == :chrome
23
+ raise "Cyperful only supports Chrome, got: #{@session.driver.browser.browser}"
24
+ end
11
25
 
12
26
  setup_api_server
13
27
  end
@@ -17,8 +31,11 @@ class Cyperful::Driver
17
31
  @test_name = test_name.to_sym
18
32
 
19
33
  @source_filepath =
20
- Object.const_source_location(test_class.name).first ||
21
- raise("Could not find source file for #{test_class.name}")
34
+ if Cyperful.rspec?
35
+ test_class.metadata.fetch(:absolute_file_path)
36
+ else
37
+ Object.const_source_location(test_class.name).first
38
+ end || raise("Could not find source file for #{test_class.name}")
22
39
 
23
40
  reset_steps
24
41
 
@@ -61,7 +78,9 @@ class Cyperful::Driver
61
78
  @steps =
62
79
  Cyperful::TestParser.new(@test_class).steps_per_test.fetch(@test_name)
63
80
 
64
- editor_scheme = Cyperful.config.editor_scheme
81
+ raise "No steps found in #{@test_class}:#{@test_name}" if @steps.blank?
82
+
83
+ editor_scheme = config.editor_scheme
65
84
 
66
85
  @steps.each_with_index do |step, i|
67
86
  step.merge!(
@@ -115,19 +134,29 @@ class Cyperful::Driver
115
134
  Object.const_get(class_name)
116
135
  end
117
136
 
118
- def queue_reset
137
+ def enqueue_reset
119
138
  at_exit do
120
- # reload test-suite code on reset (for `setup_file_listener`)
121
- # TODO: also reload dependent files
122
- # NOTE: run_on_method will fail if test_name also changed
123
- @test_class = reload_const(@test_class.name, @source_filepath)
124
-
125
- # TODO
126
- # if Cyperful.config.reload_source_files && defined?(Rails)
127
- # Rails.application.reloader.reload!
128
- # end
129
-
130
- Minitest.run_one_method(@test_class, @test_name)
139
+ case Cyperful.test_framework
140
+ when :rspec
141
+ RSpec.configuration.reset # private API. this resets the test reporter
142
+ RSpec.configuration.start_time = RSpec::Core::Time.now # this needs to be reset
143
+ RSpec.world.reset # private API. this unloads constants and clears examples
144
+ RSpec::Core::Runner.invoke # this reloads and starts the test suite
145
+ when :minitest
146
+ # reload test-suite code on reset (for `setup_file_listener`)
147
+ # TODO: also reload dependent files
148
+ # NOTE: run_on_method will fail if test_name also changed
149
+ @test_class = reload_const(@test_class.name, @source_filepath)
150
+
151
+ # TODO
152
+ # if Cyperful.config.reload_source_files && defined?(Rails)
153
+ # Rails.application.reloader.reload!
154
+ # end
155
+
156
+ Minitest.run_one_method(@test_class, @test_name)
157
+ else
158
+ raise "Unsupported test framework: #{Cyperful.test_framework}"
159
+ end
131
160
  end
132
161
  end
133
162
 
@@ -161,11 +190,11 @@ class Cyperful::Driver
161
190
  # @source_file_listener = Listen.to(rails_directory) ...
162
191
  # end
163
192
 
164
- if Cyperful.config.reload_test_files
193
+ if config.reload_test_files
165
194
  @file_listener&.stop
166
195
  @file_listener =
167
196
  Listen.to(test_directory) do |_modified, _added, _removed|
168
- puts "Test files changed, resetting test..."
197
+ logger.puts "Test files changed, resetting test..."
169
198
 
170
199
  # keep the same pause state after the reload
171
200
  self.class.next_run_options = { pause_at_step: @pause_at_step }
@@ -178,13 +207,15 @@ class Cyperful::Driver
178
207
  end
179
208
 
180
209
  def print_steps
181
- puts "Found #{@steps.length} steps:"
210
+ logger.puts("found #{@steps.length} steps:")
182
211
  @steps.each_with_index do |step, i|
183
- puts " #{
184
- (i + 1).to_s.rjust(2)
185
- }. #{step[:method]}: #{step[:line]}:#{step[:column]}"
212
+ logger.plain(
213
+ " #{
214
+ (i + 1).to_s.rjust(2)
215
+ }. #{step[:method]}: #{step[:line]}:#{step[:column]}",
216
+ )
186
217
  end
187
- puts
218
+ logger.plain
188
219
  end
189
220
 
190
221
  # pending (i.e. test hasn't started), paused, running, passed, failed
@@ -267,7 +298,7 @@ class Cyperful::Driver
267
298
  @current_step[:end_at] = (Time.now.to_f * 1000.0).to_i
268
299
  @current_step[:status] = !error ? "passed" : "failed"
269
300
 
270
- puts(
301
+ logger.plain(
271
302
  " (#{@current_step[:end_at] - @current_step[:start_at]}ms)#{
272
303
  error ? " FAILED" : ""
273
304
  }",
@@ -293,7 +324,7 @@ class Cyperful::Driver
293
324
  end
294
325
 
295
326
  def drive_iframe
296
- puts "Driving iframe..."
327
+ logger.puts "Driving iframe..."
297
328
 
298
329
  # make sure a `within` block doesn't affect these commands
299
330
  without_finder_scopes do
@@ -353,8 +384,13 @@ class Cyperful::Driver
353
384
  end
354
385
 
355
386
  private def skip_multi_sessions
356
- unless Capybara.current_session == @session
357
- warn "Skipped Cyperful setup in non-default session: #{Capybara.session_name}"
387
+ if Capybara.current_session != @session
388
+ logger.warn "Skipped setup in non-default session"
389
+ # for debugging: {
390
+ # "current_session.mode": Capybara.current_session.mode,
391
+ # "session.mode": @session.mode,
392
+ # current_driver: Capybara.current_driver,
393
+ # }.to_json
358
394
  return true
359
395
  end
360
396
  false
@@ -390,7 +426,7 @@ class Cyperful::Driver
390
426
  end
391
427
 
392
428
  def setup_api_server
393
- @ui_server = Cyperful::UiServer.new(port: Cyperful.config.port)
429
+ @ui_server = Cyperful::UiServer.new(port: config.port)
394
430
 
395
431
  @cyperful_origin = @ui_server.url_origin
396
432
 
@@ -422,7 +458,7 @@ class Cyperful::Driver
422
458
  # The server appears to always stop on it's own,
423
459
  # so we don't need to stop it within an `at_exit` or `Minitest.after_run`
424
460
 
425
- puts "Cyperful server started: #{@cyperful_origin}"
461
+ logger.puts "server started: #{@cyperful_origin}"
426
462
  end
427
463
 
428
464
  def teardown(error = nil)
@@ -430,11 +466,11 @@ class Cyperful::Driver
430
466
  @tracepoint = nil
431
467
 
432
468
  if error&.is_a?(Cyperful::ResetCommand)
433
- puts "\nResetting test (ignore any error logs)..."
469
+ logger.puts "Resetting test (ignore any error logs)..."
434
470
 
435
471
  @ui_server.notify(nil) # `break` out of the `loop` (see `UiServer#socket_open`)
436
472
 
437
- queue_reset
473
+ enqueue_reset
438
474
  return
439
475
  end
440
476
 
@@ -461,10 +497,12 @@ class Cyperful::Driver
461
497
 
462
498
  @ui_server.notify(nil) # `break` out of the `loop` (see `UiServer#socket_open`)
463
499
 
464
- puts "Cyperful teardown complete. Waiting for command..."
465
- # NOTE: this will raise an `Interrupt` if the user Ctrl+C's here
500
+ logger.puts "teardown complete. Waiting for command..."
501
+
502
+ # NOTE: MiniTest will raise an `Interrupt` if the user Ctrl+C's here
503
+ # TODO: can't seem to capture Rspec Ctrl+C's :(
466
504
  command = @step_pausing_queue.deq
467
- queue_reset if command == :reset
505
+ enqueue_reset if command == :reset
468
506
  ensure
469
507
  @file_listener&.stop
470
508
  @file_listener = nil
@@ -0,0 +1,7 @@
1
+ module Cyperful::FrameworkHelper
2
+ # Disable default screenshot on failure b/c we handle them ourselves.
3
+ # https://github.com/rails/rails/blob/v7.0.1/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb#L156
4
+ def take_failed_screenshot
5
+ nil
6
+ end
7
+ end
@@ -1,29 +1,5 @@
1
1
  require "action_dispatch/system_testing/driver"
2
2
 
3
- # The Minitest test helper.
4
- # TODO: support other test frameworks like RSpec
5
- module Cyperful::SystemTestHelper
6
- def setup
7
- Cyperful.setup(self.class, self.method_name)
8
- super
9
- end
10
-
11
- def teardown
12
- error = passed? ? nil : failure
13
-
14
- error = error.error if error.is_a?(Minitest::UnexpectedError)
15
-
16
- Cyperful.teardown(error)
17
- super
18
- end
19
-
20
- # Disable default screenshot on failure b/c we handle them ourselves.
21
- # https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb#L156
22
- def take_failed_screenshot
23
- nil
24
- end
25
- end
26
-
27
3
  # we need to override the some Capybara::Session methods because they
28
4
  # control the top-level browser window, but we want them
29
5
  # to control the iframe instead
@@ -78,10 +54,32 @@ module PrependSystemTestingDriver
78
54
 
79
55
  # this assumes Selenium and Chrome:
80
56
 
57
+ # all chrome flags: https://peter.sh/experiments/chromium-command-line-switches
58
+ # chrome flags for tooling: https://github.com/GoogleChrome/chrome-launcher/blob/main/docs/chrome-flags-for-tools.md
59
+
60
+ # set the min flags to make cyperful functional:
61
+
81
62
  # so user isn't prompted when we start recording video w/ MediaStream
82
63
  driver_opts.add_argument("--auto-accept-this-tab-capture")
83
64
  driver_opts.add_argument("--use-fake-ui-for-media-stream")
84
65
 
66
+ if driver_opts.args.none? { |arg| arg.include?("--disable-features=") }
67
+ # credit: https://github.com/krausest/js-framework-benchmark/discussions/1682
68
+ disabled_features = %w[
69
+ InterestFeedContentSuggestions
70
+ IPH_DemoMode
71
+ BackForwardCache
72
+ OptimizationHints
73
+ OptimizationHintsFetching
74
+ OptimizationTargetPrediction
75
+ PrivacySandboxSettings4
76
+ Translate
77
+ ]
78
+ driver_opts.add_argument(
79
+ "--flag-switches-begin --disable-features=#{disabled_features.join(",")} --flag-switches-end",
80
+ )
81
+ end
82
+
85
83
  # make sure we're not in headless mode
86
84
  driver_opts.args.delete("--headless")
87
85
  driver_opts.args.delete("--headless=new")
@@ -94,9 +92,5 @@ module PrependSystemTestingDriver
94
92
  end
95
93
  ActionDispatch::SystemTesting::Driver.prepend(PrependSystemTestingDriver)
96
94
 
97
- # if defined?(Minitest::Test)
98
- # Minitest::Test::PASSTHROUGH_EXCEPTIONS << Cyperful::AbstractCommand
99
- # end
100
-
101
95
  # fix for: Set-Cookie (SameSite=Lax) doesn't work when within an iframe with host 127.0.0.1
102
96
  Capybara.server_host = "localhost"
@@ -0,0 +1,14 @@
1
+ class Cyperful::Logger
2
+ def puts(message = nil)
3
+ # call puts method from Kernel module
4
+ Kernel.puts("[cyperful] #{message}")
5
+ end
6
+
7
+ def warn(message = nil)
8
+ Kernel.warn("[cyperful] #{message}")
9
+ end
10
+
11
+ def plain(message = nil)
12
+ Kernel.puts(message)
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ require "cyperful"
2
+ require "capybara/minitest"
3
+
4
+ module Cyperful::Minitest # rubocop:disable Style/ClassAndModuleChildren
5
+ module SystemTestHelper
6
+ include Cyperful::FrameworkHelper
7
+
8
+ def setup
9
+ Cyperful.setup(self.class, self.method_name)
10
+ super
11
+ end
12
+
13
+ def teardown
14
+ error = passed? ? nil : failure
15
+
16
+ error = error.error if error.is_a?(Minitest::UnexpectedError)
17
+
18
+ Cyperful.teardown(error)
19
+ super
20
+ end
21
+ end
22
+ end
23
+
24
+ Cyperful.add_step_at_methods(
25
+ Capybara::Minitest::Assertions.instance_methods(false),
26
+ )
27
+
28
+ # if defined?(Minitest::Test)
29
+ # Minitest::Test::PASSTHROUGH_EXCEPTIONS << Cyperful::AbstractCommand
30
+ # end
@@ -0,0 +1,24 @@
1
+ require "cyperful"
2
+
3
+ RSpec.configure do |rspec_conf|
4
+ rspec_conf.include(Cyperful::FrameworkHelper)
5
+
6
+ rspec_conf.before(:example, type: :system) do
7
+ # e.g. class = `RSpec::ExampleGroups::MyTest`
8
+ # e.g. full_description = "my_test can visit root"
9
+ example = RSpec.current_example
10
+ Cyperful.setup(self.class, example.full_description)
11
+ end
12
+
13
+ rspec_conf.after(:example, type: :system) do
14
+ example = RSpec.current_example
15
+ error = example.exception # RSpec::Expectations::ExpectationNotMetError | nil
16
+
17
+ Cyperful.teardown(error)
18
+ end
19
+ end
20
+
21
+ Cyperful.add_step_at_methods(
22
+ Capybara::RSpecMatchers.instance_methods(false),
23
+ # Capybara::RSpecMatcherProxies.instance_methods(false),
24
+ )
@@ -1,11 +1,9 @@
1
- require "parser/current"
2
- require "capybara/minitest"
1
+ require "parser/current" # TODO: switch to Prism?
3
2
 
4
3
  class Cyperful::TestParser
5
4
  # see docs for methods: https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Session
6
5
  @step_at_methods =
7
- Capybara::Session::DSL_METHODS.to_set +
8
- Capybara::Minitest::Assertions.instance_methods(false) -
6
+ Capybara::Session::DSL_METHODS -
9
7
  # exclude methods that don't have side-effects i.e. don't modify the page:
10
8
  %i[
11
9
  body
@@ -25,7 +23,7 @@ class Cyperful::TestParser
25
23
  @step_at_methods_set ||= @step_at_methods.to_set
26
24
  end
27
25
  def self.add_step_at_methods(*mods_or_methods)
28
- mods_or_methods.each do |mod_or_method|
26
+ mods_or_methods.flatten.each do |mod_or_method|
29
27
  case mod_or_method
30
28
  when Module
31
29
  @step_at_methods +=
@@ -33,17 +31,81 @@ class Cyperful::TestParser
33
31
  when String, Symbol
34
32
  @step_at_methods << mod_or_method.to_sym
35
33
  else
36
- raise "Expected Module or Array of strings/symbols, got #{mod_or_method.class}"
34
+ raise "Expected Module or string/symbol, got: #{mod_or_method.class.name}"
37
35
  end
38
36
  end
39
37
  end
40
38
 
41
39
  def initialize(test_class)
42
40
  @test_class = test_class
43
- @source_filepath = Object.const_source_location(test_class.name).first
41
+
42
+ @source_filepath =
43
+ if Cyperful.rspec?
44
+ test_class.metadata.fetch(:absolute_file_path)
45
+ else
46
+ Object.const_source_location(test_class.name).first
47
+ end
44
48
  end
45
49
 
46
50
  def steps_per_test
51
+ case Cyperful.test_framework
52
+ when :rspec
53
+ rspec_steps_per_test
54
+ when :minitest
55
+ minitest_steps_per_test
56
+ else
57
+ raise "Unsupported test framework: #{Cyperful.test_framework}"
58
+ end
59
+ end
60
+
61
+ private def rspec_steps_per_test
62
+ ast = Parser::CurrentRuby.parse(File.read(@source_filepath))
63
+
64
+ example_per_line =
65
+ @test_class.examples.to_h do |example|
66
+ # file_path = example.metadata.fetch(:absolute_file_path)
67
+ [example.metadata.fetch(:line_number) || -1, example]
68
+ end
69
+
70
+ example_asts =
71
+ search_nodes(ast) do |node|
72
+ next nil unless node.type == :block
73
+
74
+ # assumption: the block is on the same line as the example, and there's at most one example per line
75
+ example = example_per_line[node.loc.begin.line]
76
+ next nil unless example
77
+
78
+ # "#{@test_class.name} #{example.description} #{}"
79
+
80
+ [example, node]
81
+ end
82
+
83
+ example_asts.to_h do |(example, block_node)|
84
+ out = []
85
+ block_node.children.each { |child| find_test_steps(child, out) }
86
+
87
+ # de-duplicate steps by line number
88
+ # TODO: support multiple steps on the same line. `step_per_line = ...` needs to be refactored
89
+ out = out.reverse.uniq { |step| step[:line] }.reverse
90
+
91
+ [example.full_description.to_sym, out]
92
+ end
93
+ end
94
+
95
+ private def search_nodes(parent, out = [], &block)
96
+ parent.children.each do |node|
97
+ next unless node.is_a?(Parser::AST::Node)
98
+ if (ret = block.call(node))
99
+ #
100
+ out << ret
101
+ else
102
+ search_nodes(node, out, &block)
103
+ end
104
+ end
105
+ out
106
+ end
107
+
108
+ private def minitest_steps_per_test
47
109
  ast = Parser::CurrentRuby.parse(File.read(@source_filepath))
48
110
 
49
111
  test_class_name = @test_class.name.to_sym
@@ -57,7 +119,7 @@ class Cyperful::TestParser
57
119
  end
58
120
  end
59
121
  unless system_test_class
60
- raise "Could not find class #{test_class.name} in #{@source_filepath}"
122
+ raise "Could not find class #{@test_class.name} in #{@source_filepath}"
61
123
  end
62
124
 
63
125
  (
@@ -76,21 +138,21 @@ class Cyperful::TestParser
76
138
  test_string = node.children[0].children[2].children[0]
77
139
 
78
140
  # https://github.com/rails/rails/blob/66676ce499a32e4c62220bd05f8ee2cdf0e15f0c/activesupport/lib/active_support/testing/declarative.rb#L14C23-L14C61
79
- test_method = :"test_#{test_string.gsub(/\s+/, "_")}"
141
+ test_name = :"test_#{test_string.gsub(/\s+/, "_")}"
80
142
 
81
143
  block_node = node.children[2]
82
- [test_method, block_node]
144
+ [test_name, block_node]
83
145
 
84
146
  # e.g. `def test_my_test; ... end`
85
147
  elsif node.type == :def && node.children[0].to_s.start_with?("test_")
86
- test_method = node.children[0]
148
+ test_name = node.children[0]
87
149
 
88
150
  block_node = node.children[2]
89
- [test_method, block_node]
151
+ [test_name, block_node]
90
152
  end
91
153
  end
92
154
  .compact
93
- .to_h do |test_method, block_node|
155
+ .to_h do |test_name, block_node|
94
156
  out = []
95
157
  block_node.children.each { |child| find_test_steps(child, out) }
96
158
 
@@ -98,7 +160,7 @@ class Cyperful::TestParser
98
160
  # TODO: support multiple steps on the same line. `step_per_line = ...` needs to be refactored
99
161
  out = out.reverse.uniq { |step| step[:line] }.reverse
100
162
 
101
- [test_method, out]
163
+ [test_name, out]
102
164
  end
103
165
  end
104
166
 
@@ -122,6 +184,8 @@ class Cyperful::TestParser
122
184
  end
123
185
 
124
186
  children.each { |child| find_test_steps(child, out, depth) }
187
+ elsif ast.type == :begin || ast.type == :kwbegin || ast.type == :ensure
188
+ ast.children.each { |child| find_test_steps(child, out, depth) }
125
189
  end
126
190
 
127
191
  out
@@ -1,15 +1,6 @@
1
1
  require "webrick/websocket"
2
2
  require "webrick/httpproxy"
3
3
 
4
- # fix for: webrick-websocket incorrectly assumes `Upgrade` header is always present
5
- module FixWebrickWebsocketServer
6
- def service(req, res)
7
- req.header["upgrade"] = [""] if req["upgrade"].nil?
8
- super
9
- end
10
- end
11
- WEBrick::Websocket::HTTPServer.prepend(FixWebrickWebsocketServer)
12
-
13
4
  class Cyperful::UiServer
14
5
  def initialize(port:)
15
6
  @port = port
data/lib/cyperful.rb CHANGED
@@ -33,11 +33,36 @@ module Cyperful
33
33
  @config ||= Config.new
34
34
  end
35
35
 
36
+ def self.logger
37
+ @logger ||= Cyperful::Logger.new
38
+ end
39
+
40
+ def self.test_framework
41
+ @test_framework || raise("Cyperful not set up yet")
42
+ end
43
+ def self.rspec?
44
+ @test_framework == :rspec
45
+ end
46
+ def self.minitest?
47
+ @test_framework == :minitest
48
+ end
49
+
36
50
  def self.current
37
51
  @current
38
52
  end
39
53
  def self.setup(test_class, test_name)
40
- puts "Setting up Cyperful for: #{test_class}##{test_name}"
54
+ @test_framework =
55
+ if defined?(RSpec::Core::ExampleGroup) &&
56
+ test_class < RSpec::Core::ExampleGroup
57
+ :rspec
58
+ elsif defined?(ActiveSupport::TestCase) &&
59
+ test_class < ActiveSupport::TestCase
60
+ :minitest
61
+ else
62
+ raise "Unsupported test framework for class: #{test_class.name}"
63
+ end
64
+
65
+ logger.puts "init test: \"#{rspec? ? test_name : "#{test_class}##{test_name}"}\""
41
66
 
42
67
  # must set `Cyperful.current` before calling `async_setup`
43
68
  @current ||= Cyperful::Driver.new
@@ -62,8 +87,10 @@ module Cyperful
62
87
  end
63
88
 
64
89
  require "cyperful/commands"
90
+ require "cyperful/logger"
65
91
  require "cyperful/test_parser"
66
92
  require "cyperful/ui_server"
67
93
  require "cyperful/driver"
94
+ require "cyperful/framework_helper"
68
95
  require "cyperful/framework_injections"
69
96
  require "cyperful/railtie" if defined?(Rails::Railtie)