cyperful 0.1.8 → 0.1.10

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: 341d51e03119eed5f4354673d25c81f2630ee73bb48e29312a797db76b389f80
4
- data.tar.gz: f52e880307fa10e3529ebaac44d165e860bcb94e5913d9c0808732881ac96e63
3
+ metadata.gz: f340215ffcef3e16c1738d36eb2b71461bbd32e2011598d000793326f806b6d4
4
+ data.tar.gz: 545e19084294a2d481a7e9d156f6ba5e4758fb52332c17dc2e2574be820b2256
5
5
  SHA512:
6
- metadata.gz: 5baea001193479787f029a38b800fc98fdd5eee46450eefdf6a874c16b890181f49c110c97fe1ae668d160d1c925996ce3d40fe8013dedfa342f015a0cdaa6b5
7
- data.tar.gz: d72eaed23330c7ebcc2c865685c0ee4c1854911f5680c5b7dc42d951d8ba5d9c77cb8e89a4a3609d33523f4cfd0b47c52685d3a15257ffbd9a3057b7de308806
6
+ metadata.gz: 524bbb91d707a457adfa888bc8db5a2d97886714dedc026d8647d2f243f8ff381f46a47af8cffe3fb4d46242aacfd515f2dda1b2a794252f758b5899ca312e9a
7
+ data.tar.gz: e20223308f8c0df25df5aab64d97f24d4ef6f0b57bb530106e82a3234f5701f1ca434afa87dad9b50155f73043aef6b1afbe328ebb99aed35ad53fac29dc5ef9
@@ -61,7 +61,7 @@ class Cyperful::Driver
61
61
  @steps =
62
62
  Cyperful::TestParser.new(@test_class).steps_per_test.fetch(@test_name)
63
63
 
64
- editor = "vscode" # TODO: support other editors?
64
+ editor_scheme = Cyperful.config.editor_scheme
65
65
 
66
66
  @steps.each_with_index do |step, i|
67
67
  step.merge!(
@@ -70,11 +70,14 @@ class Cyperful::Driver
70
70
  start_at: nil,
71
71
  end_at: nil,
72
72
  paused_at: nil,
73
- permalink: "#{editor}://file/#{@source_filepath}:#{step.fetch(:line)}",
73
+ permalink:
74
+ if editor_scheme && !editor_scheme.empty?
75
+ "#{editor_scheme}://file/#{@source_filepath}:#{step.fetch(:line)}"
76
+ end,
74
77
  )
75
78
  end
76
79
 
77
- # NOTE: there can be multiple steps per line, this takes the last instance
80
+ # TODO: support multiple multiple steps per line, this takes only the last instance
78
81
  @step_per_line = @steps.index_by { |step| step[:line] }
79
82
 
80
83
  @current_step = nil
@@ -1,5 +1,29 @@
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
+
3
27
  # we need to override the some Capybara::Session methods because they
4
28
  # control the top-level browser window, but we want them
5
29
  # to control the iframe instead
@@ -37,34 +61,14 @@ module PrependCapybaraWindow
37
61
  end
38
62
  Capybara::Window.prepend(PrependCapybaraWindow)
39
63
 
40
- # The Minitest test helper.
41
- # TODO: support other test frameworks like RSpec
42
- module Cyperful::SystemTestHelper
43
- def setup
44
- Cyperful.setup(self.class, self.method_name)
45
- super
46
- end
47
-
48
- def teardown
49
- error = passed? ? nil : failure
50
-
51
- error = error.error if error.is_a?(Minitest::UnexpectedError)
52
-
53
- Cyperful.teardown(error)
54
- super
55
- end
56
-
57
- # Disable default screenshot on failure b/c we handle them ourselves.
58
- # https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb#L156
59
- def take_failed_screenshot
60
- nil
61
- end
62
- end
63
-
64
64
  module PrependSystemTestingDriver
65
65
  def initialize(...)
66
66
  super(...)
67
67
 
68
+ # SUPER NAIVE way to include the width/height of the sidebar/header
69
+ # in the new screen size
70
+ @screen_size = [@screen_size.fetch(0) + 300, @screen_size.fetch(1) + 60]
71
+
68
72
  prev_capabilities = @capabilities
69
73
  @capabilities =
70
74
  proc do |driver_opts|
@@ -81,6 +85,10 @@ module PrependSystemTestingDriver
81
85
  # make sure we're not in headless mode
82
86
  driver_opts.args.delete("--headless")
83
87
  driver_opts.args.delete("--headless=new")
88
+
89
+ # hide the "Chrome is being controlled by automated test software" infobar
90
+ driver_opts.args.delete("--enable-automation")
91
+ driver_opts.exclude_switches << "enable-automation"
84
92
  end
85
93
  end
86
94
  end
@@ -90,15 +98,5 @@ ActionDispatch::SystemTesting::Driver.prepend(PrependSystemTestingDriver)
90
98
  # Minitest::Test::PASSTHROUGH_EXCEPTIONS << Cyperful::AbstractCommand
91
99
  # end
92
100
 
93
- # we need to allow the iframe to be embedded in the cyperful server
94
- # TODO: use Rack middleware instead to support non-Rails apps
95
- if defined?(Rails)
96
- Rails.application.config.content_security_policy do |policy|
97
- policy.frame_ancestors(:self, "localhost:#{Cyperful.config.port}")
98
- end
99
- else
100
- warn "Cyperful: Rails not detected, skipping content_security_policy fix.\nThe Cyperful UI may not work correctly."
101
- end
102
-
103
101
  # fix for: Set-Cookie (SameSite=Lax) doesn't work when within an iframe with host 127.0.0.1
104
102
  Capybara.server_host = "localhost"
@@ -0,0 +1,9 @@
1
+ # we need to allow the iframe to be embedded in the cyperful server
2
+ # TODO: consider Rack middleware instead to support non-Rails apps?
3
+ class Cyperful::Railtie < Rails::Railtie
4
+ config.after_initialize do |app|
5
+ app.config.content_security_policy do |policy|
6
+ policy.frame_ancestors(:self, "localhost:#{Cyperful.config.port}")
7
+ end
8
+ end
9
+ end
@@ -17,10 +17,12 @@ class Cyperful::TestParser
17
17
  current_scope
18
18
  status_code
19
19
  response_headers
20
+ title
21
+ query
20
22
  ]
21
23
 
22
- def self.step_at_methods
23
- @step_at_methods
24
+ def self.step_at_methods_set
25
+ @step_at_methods_set ||= @step_at_methods.to_set
24
26
  end
25
27
  def self.add_step_at_methods(*mods_or_methods)
26
28
  mods_or_methods.each do |mod_or_method|
@@ -78,9 +80,13 @@ class Cyperful::TestParser
78
80
 
79
81
  block_node = node.children[2]
80
82
  [test_method, block_node]
81
- else
83
+
82
84
  # e.g. `def test_my_test; ... end`
83
- # TODO
85
+ elsif node.type == :def && node.children[0].to_s.start_with?("test_")
86
+ test_method = node.children[0]
87
+
88
+ block_node = node.children[2]
89
+ [test_method, block_node]
84
90
  end
85
91
  end
86
92
  .compact
@@ -88,6 +94,10 @@ class Cyperful::TestParser
88
94
  out = []
89
95
  block_node.children.each { |child| find_test_steps(child, out) }
90
96
 
97
+ # de-duplicate steps by line number
98
+ # TODO: support multiple steps on the same line. `step_per_line = ...` needs to be refactored
99
+ out = out.reverse.uniq { |step| step[:line] }.reverse
100
+
91
101
  [test_method, out]
92
102
  end
93
103
  end
@@ -96,8 +106,10 @@ class Cyperful::TestParser
96
106
  return out unless ast&.is_a?(Parser::AST::Node)
97
107
 
98
108
  if ast.type == :send
99
- add_node(ast, out, depth)
100
- ast.children.each { |child| find_test_steps(child, out, depth) }
109
+ # e.g. `assert_equal current_path, "/"` should be a single step, not 2
110
+ unless add_node(ast, out, depth)
111
+ ast.children.each { |child| find_test_steps(child, out, depth) }
112
+ end
101
113
  elsif ast.type == :block
102
114
  method, _args, child = ast.children
103
115
 
@@ -105,6 +117,7 @@ class Cyperful::TestParser
105
117
 
106
118
  if method.type == :send
107
119
  depth += 1 if add_node(method, out, depth)
120
+
108
121
  method.children.each { |child| find_test_steps(child, out, depth) }
109
122
  end
110
123
 
@@ -115,7 +128,7 @@ class Cyperful::TestParser
115
128
  end
116
129
 
117
130
  private def add_node(node, out, depth)
118
- unless Cyperful::TestParser.step_at_methods.include?(node.children[1])
131
+ unless Cyperful::TestParser.step_at_methods_set.include?(node.children[1])
119
132
  return false
120
133
  end
121
134
 
@@ -1,4 +1,5 @@
1
1
  require "webrick/websocket"
2
+ require "webrick/httpproxy"
2
3
 
3
4
  # fix for: webrick-websocket incorrectly assumes `Upgrade` header is always present
4
5
  module FixWebrickWebsocketServer
@@ -34,9 +35,16 @@ class Cyperful::UiServer
34
35
  @server =
35
36
  WEBrick::Websocket::HTTPServer.new(
36
37
  Port: @port,
37
- DocumentRoot: File.join(Cyperful::ROOT_DIR, "public"),
38
38
  Logger: WEBrick::Log.new("/dev/null"),
39
39
  AccessLog: [],
40
+ **unless Cyperful::DEV_MODE
41
+ {
42
+ DocumentRoot: File.join(Cyperful::ROOT_DIR, "public"),
43
+ DocumentRootOptions: {
44
+ FancyIndexing: false,
45
+ },
46
+ }
47
+ end || {},
40
48
  )
41
49
 
42
50
  notify_queue = @notify_queue
@@ -118,6 +126,10 @@ class Cyperful::UiServer
118
126
  res["Content-Type"] = "application/json"
119
127
  res.status = 200
120
128
  end
129
+
130
+ if Cyperful::DEV_MODE
131
+ @server.mount("/", ReverseProxy, target_url: "http://localhost:3005")
132
+ end
121
133
  end
122
134
 
123
135
  def start_async
@@ -134,4 +146,37 @@ class Cyperful::UiServer
134
146
 
135
147
  @server.shutdown
136
148
  end
149
+
150
+ # super naive reverse proxy
151
+ class ReverseProxy < WEBrick::HTTPServlet::AbstractServlet
152
+ def initialize(server, config = {})
153
+ super
154
+ @target_url = config.fetch(:target_url)
155
+ @forward_headers = config[:forward_headers] || ["accept"]
156
+ end
157
+
158
+ def do_GET(request, response)
159
+ # Target server URL
160
+ target_uri = URI(@target_url)
161
+ target_uri.path = request.path
162
+ target_uri.query = request.query_string if request.query_string
163
+
164
+ # Create a new request to the target server with the original request path
165
+ target_request = Net::HTTP::Get.new(target_uri)
166
+ @forward_headers.each do |header|
167
+ target_request[header] = request[header]
168
+ end
169
+
170
+ # Send the request to the target server
171
+ target_response =
172
+ Net::HTTP.start(target_uri.host, target_uri.port) do |http|
173
+ http.request(target_request)
174
+ end
175
+
176
+ # Set the response from the target server as the response to send back to the client
177
+ response.status = target_response.code.to_i
178
+ response["Content-Type"] = target_response["content-type"]
179
+ response.body = target_response.body
180
+ end
181
+ end
137
182
  end
data/lib/cyperful.rb CHANGED
@@ -4,6 +4,10 @@ require "listen"
4
4
  module Cyperful
5
5
  ROOT_DIR = File.expand_path("..", __dir__)
6
6
 
7
+ # Add `CYPERFUL_DEV=1` to use the Vite dev hot-reloading server
8
+ # instead of the pre-built files in `public/`
9
+ DEV_MODE = !!ENV["CYPERFUL_DEV"]
10
+
7
11
  @current = nil
8
12
 
9
13
  class Config < Struct.new(
@@ -11,6 +15,7 @@ module Cyperful
11
15
  :auto_run_on_reload,
12
16
  :reload_test_files,
13
17
  # :reload_source_files, # not implemented yet
18
+ :editor_scheme,
14
19
  :history_recording, # EXPERIMENTAL
15
20
  keyword_init: true,
16
21
  )
@@ -19,6 +24,7 @@ module Cyperful
19
24
  port: 3004,
20
25
  auto_run_on_reload: true,
21
26
  reload_test_files: true,
27
+ editor_scheme: "vscode",
22
28
  history_recording: true,
23
29
  )
24
30
  end
@@ -60,3 +66,4 @@ require "cyperful/test_parser"
60
66
  require "cyperful/ui_server"
61
67
  require "cyperful/driver"
62
68
  require "cyperful/framework_injections"
69
+ require "cyperful/railtie" if defined?(Rails::Railtie)