Class: Capybara::Dommy::Driver
- Inherits:
-
Capybara::Driver::Base
- Object
- Capybara::Driver::Base
- Capybara::Dommy::Driver
- Defined in:
- lib/capybara/dommy/driver.rb
Overview
A Capybara driver backed by Dommy::Rack::Session. Implements the navigation / query / reset! parts of the Capybara::Driver::Base contract; element interaction lives in Capybara::Dommy::Node. JavaScript, screenshot, window, and modal methods are left to Driver::Base (which raises Capybara::NotSupportedByDriverError).
Constant Summary collapse
- VISIBILITY_MODES =
%i[all html none].freeze
Instance Attribute Summary collapse
-
#app ⇒ Object
readonly
Returns the value of attribute app.
-
#time_pump ⇒ Object
--- Deterministic-time seam (used by JS runtimes) ---.
-
#visibility ⇒ Object
readonly
Returns the value of attribute visibility.
Instance Method Summary collapse
-
#active_element ⇒ Object
--- Focus / keyboard ---.
- #current_url ⇒ Object
-
#document ⇒ Object
The document queries run against: the innermost switched-to frame's document, or the top-level page when no frame is active.
-
#drain_js ⇒ Object
Drain the JS runtime after an interaction's events (promise reactions settle before the next Capybara step).
- #evaluate_async_script(_script, *_args) ⇒ Object
- #evaluate_script(script, *args) ⇒ Object
-
#execute_script(script, *args) ⇒ Object
--- JavaScript (unsupported) --- When raise_on_unsupported_js is false these become no-ops, so tests that incidentally call them don't fail.
-
#find_css(query, **_options) ⇒ Object
--- Query (returns Capybara::Dommy::Node arrays) ---.
- #find_xpath(query, **_options) ⇒ Object
- #follow_link(element) ⇒ Object
- #frame_title ⇒ Object
- #frame_url ⇒ Object
- #go_back ⇒ Object
- #go_forward ⇒ Object
-
#html ⇒ Object
--- Page state ---.
-
#initialize(app, default_host: nil, follow_redirects: nil, max_redirects: nil, visibility: nil, javascript: nil) ⇒ Driver
constructor
A new instance of Driver.
-
#invalid_element_errors ⇒ Object
Lets Capybara reload a node when it goes stale (after navigation).
-
#javascript? ⇒ Boolean
Whether this driver runs page JavaScript (
javascript: true, backed by aDommy::Rack::Session.new(app, javascript: true)). - #needs_server? ⇒ Boolean
-
#rack_session ⇒ Object
The dommy-rack session.
- #refresh ⇒ Object
-
#reset! ⇒ Object
--- Lifecycle ---.
- #response_headers ⇒ Object
-
#send_keys(*keys) ⇒ Object
Session-level send_keys.
- #status_code ⇒ Object
- #submit_form(form, submitter:) ⇒ Object
-
#switch_to_frame(frame) ⇒ Object
--- Frames --- Capybara::Session#switch_to_frame drives these with an iframe element node, :parent, or :top.
-
#title ⇒ Object
The title of the top-level browsing context, even inside a frame (Capybara's #title contract); the current frame's title is #frame_title.
-
#visible?(element) ⇒ Boolean
Visibility decision used by Node#visible?.
-
#visit(path) ⇒ Object
--- Navigation ---.
- #wait? ⇒ Boolean
Constructor Details
#initialize(app, default_host: nil, follow_redirects: nil, max_redirects: nil, visibility: nil, javascript: nil) ⇒ Driver
Returns a new instance of Driver.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/capybara/dommy/driver.rb', line 27 def initialize(app, default_host: nil, follow_redirects: nil, max_redirects: nil, visibility: nil, javascript: nil) super() config = Capybara::Dommy.configuration @app = app @javascript = javascript.nil? ? config.javascript : javascript @visibility = visibility || config.visibility unless VISIBILITY_MODES.include?(@visibility) raise ArgumentError, "unknown visibility mode #{@visibility.inspect} (expected one of #{VISIBILITY_MODES.join(", ")})" end @raise_on_unsupported_js = config.raise_on_unsupported_js @session_options = { default_host: default_host || config.default_host, follow_redirects: follow_redirects.nil? ? config.follow_redirects : follow_redirects, max_redirects: max_redirects || config.max_redirects, # Capybara drives a trusted app and legitimately visits multiple # hosts (e.g. app_host / multi-server specs), so don't enforce origin. enforce_same_origin: false } @session_options[:javascript] = true if @javascript # A JS session needs the virtual clock pumped inside Capybara's # synchronize loop, so waiting expectations converge on timer/fetch # driven updates. A host-installed pump (the documented seam) wins. @time_pump ||= -> { @rack_session&.advance_time(16) } if @javascript end |
Instance Attribute Details
#app ⇒ Object (readonly)
Returns the value of attribute app.
13 14 15 |
# File 'lib/capybara/dommy/driver.rb', line 13 def app @app end |
#time_pump ⇒ Object
--- Deterministic-time seam (used by JS runtimes) ---
A JS runtime assigns a callable here; the driver invokes it before
each DOM read Capybara polls in its synchronize loop (find_css /
find_xpath / html / title). The pump is expected to advance Dommy's
virtual scheduler a small slice and drain microtasks, so "content
appears after a timeout" specs converge without wall-clock sleeps.
Installing a pump also flips wait? to true, making Capybara retry
failed expectations instead of raising immediately. Survives
reset! (it belongs to the runtime, not to one page session).
25 26 27 |
# File 'lib/capybara/dommy/driver.rb', line 25 def time_pump @time_pump end |
#visibility ⇒ Object (readonly)
Returns the value of attribute visibility.
13 14 15 |
# File 'lib/capybara/dommy/driver.rb', line 13 def visibility @visibility end |
Instance Method Details
#active_element ⇒ Object
--- Focus / keyboard ---
181 182 183 |
# File 'lib/capybara/dommy/driver.rb', line 181 def active_element Node.new(self, document.active_element) end |
#current_url ⇒ Object
96 97 98 |
# File 'lib/capybara/dommy/driver.rb', line 96 def current_url rack_session.current_url.to_s end |
#document ⇒ Object
The document queries run against: the innermost switched-to frame's document, or the top-level page when no frame is active.
150 151 152 |
# File 'lib/capybara/dommy/driver.rb', line 150 def document frame_stack.empty? ? rack_session.document : frame_stack.last[:document] end |
#drain_js ⇒ Object
Drain the JS runtime after an interaction's events (promise reactions settle before the next Capybara step). No-op without JavaScript.
66 67 68 69 |
# File 'lib/capybara/dommy/driver.rb', line 66 def drain_js rack_session.after_interaction if @javascript nil end |
#evaluate_async_script(_script, *_args) ⇒ Object
239 240 241 |
# File 'lib/capybara/dommy/driver.rb', line 239 def evaluate_async_script(_script, *_args) unsupported_js!("evaluate_async_script") end |
#evaluate_script(script, *args) ⇒ Object
232 233 234 235 236 237 |
# File 'lib/capybara/dommy/driver.rb', line 232 def evaluate_script(script, *args) return unsupported_js!("evaluate_script") unless @javascript raise ArgumentError, "script arguments are not supported" unless args.empty? rack_session.evaluate_script(script) end |
#execute_script(script, *args) ⇒ Object
--- JavaScript (unsupported) --- When raise_on_unsupported_js is false these become no-ops, so tests that incidentally call them don't fail.
225 226 227 228 229 230 |
# File 'lib/capybara/dommy/driver.rb', line 225 def execute_script(script, *args) return unsupported_js!("execute_script") unless @javascript raise ArgumentError, "script arguments are not supported" unless args.empty? rack_session.execute_script(script) end |
#find_css(query, **_options) ⇒ Object
--- Query (returns Capybara::Dommy::Node arrays) ---
136 137 138 139 |
# File 'lib/capybara/dommy/driver.rb', line 136 def find_css(query, **) pump! wrap(document&.query_selector_all(query)) end |
#find_xpath(query, **_options) ⇒ Object
141 142 143 144 |
# File 'lib/capybara/dommy/driver.rb', line 141 def find_xpath(query, **) pump! wrap(document&.xpath(query)) end |
#follow_link(element) ⇒ Object
192 193 194 |
# File 'lib/capybara/dommy/driver.rb', line 192 def follow_link(element) rack_session.click_link_element(element) end |
#frame_title ⇒ Object
175 176 177 |
# File 'lib/capybara/dommy/driver.rb', line 175 def frame_title document&.title end |
#frame_url ⇒ Object
171 172 173 |
# File 'lib/capybara/dommy/driver.rb', line 171 def frame_url frame_stack.empty? ? rack_session.current_url.to_s : frame_stack.last[:url] end |
#go_back ⇒ Object
104 105 106 |
# File 'lib/capybara/dommy/driver.rb', line 104 def go_back rack_session.back end |
#go_forward ⇒ Object
108 109 110 |
# File 'lib/capybara/dommy/driver.rb', line 108 def go_forward rack_session.forward end |
#html ⇒ Object
--- Page state ---
114 115 116 117 |
# File 'lib/capybara/dommy/driver.rb', line 114 def html pump! rack_session.html end |
#invalid_element_errors ⇒ Object
Lets Capybara reload a node when it goes stale (after navigation).
217 218 219 |
# File 'lib/capybara/dommy/driver.rb', line 217 def invalid_element_errors [Capybara::Dommy::StaleElementReferenceError] end |
#javascript? ⇒ Boolean
Whether this driver runs page JavaScript (javascript: true, backed by
a Dommy::Rack::Session.new(app, javascript: true)). Node interactions
then dispatch real DOM events (Turbo/Stimulus handlers run) instead of
the HTML-only fast paths.
62 |
# File 'lib/capybara/dommy/driver.rb', line 62 def javascript? = @javascript |
#needs_server? ⇒ Boolean
212 213 214 |
# File 'lib/capybara/dommy/driver.rb', line 212 def needs_server? false end |
#rack_session ⇒ Object
The dommy-rack session. Named rack_session to avoid colliding with
Capybara::Driver::Base#session (the owning Capybara::Session). Rebuilt
when the effective host (Capybara app_host / default_host) changes so
current_url reflects it and same-origin checks pass.
75 76 77 78 79 80 81 82 83 |
# File 'lib/capybara/dommy/driver.rb', line 75 def rack_session host = effective_host if @rack_session.nil? || @rack_session_host != host @rack_session&.dispose @rack_session = ::Dommy::Rack::Session.new(@app, **@session_options.merge(default_host: host)) @rack_session_host = host end @rack_session end |
#refresh ⇒ Object
100 101 102 |
# File 'lib/capybara/dommy/driver.rb', line 100 def refresh rack_session.reload end |
#reset! ⇒ Object
--- Lifecycle ---
202 203 204 205 206 |
# File 'lib/capybara/dommy/driver.rb', line 202 def reset! @rack_session&.dispose @rack_session = nil @frame_stack = [] end |
#response_headers ⇒ Object
130 131 132 |
# File 'lib/capybara/dommy/driver.rb', line 130 def response_headers rack_session.headers || {} end |
#send_keys(*keys) ⇒ Object
Session-level send_keys. Without JavaScript only focus navigation is meaningful, so :tab (the key Capybara's focused: specs use) moves focus through the tab order; other keys are ignored.
188 189 190 |
# File 'lib/capybara/dommy/driver.rb', line 188 def send_keys(*keys) keys.each { |key| focus_next_tabbable if key == :tab } end |
#status_code ⇒ Object
126 127 128 |
# File 'lib/capybara/dommy/driver.rb', line 126 def status_code rack_session.status end |
#submit_form(form, submitter:) ⇒ Object
196 197 198 |
# File 'lib/capybara/dommy/driver.rb', line 196 def submit_form(form, submitter:) rack_session.submit_form(form, submitter: submitter) end |
#switch_to_frame(frame) ⇒ Object
--- Frames --- Capybara::Session#switch_to_frame drives these with an iframe element node, :parent, or :top. Frame documents are fetched through the dommy-rack session (sharing cookies); nothing here touches the top-level page state, so current_url / title stay top-level.
160 161 162 163 164 165 166 167 168 169 |
# File 'lib/capybara/dommy/driver.rb', line 160 def switch_to_frame(frame) case frame when :top @frame_stack = [] when :parent frame_stack.pop else frame_stack.push(load_frame(frame.native)) end end |
#title ⇒ Object
The title of the top-level browsing context, even inside a frame (Capybara's #title contract); the current frame's title is #frame_title.
121 122 123 124 |
# File 'lib/capybara/dommy/driver.rb', line 121 def title pump! rack_session.document&.title end |
#visible?(element) ⇒ Boolean
Visibility decision used by Node#visible?. :all / :none treat every element as visible; :html defers to dommy-rack's HTML-level check.
245 246 247 248 249 |
# File 'lib/capybara/dommy/driver.rb', line 245 def visible?(element) return true if @visibility == :all || @visibility == :none ::Dommy::Rack.visible?(element) end |
#visit(path) ⇒ Object
--- Navigation ---
87 88 89 90 91 92 93 94 |
# File 'lib/capybara/dommy/driver.rb', line 87 def visit(path) @frame_stack = [] # A fresh visit resolves a relative path against the host root (not the # current page's directory), matching browser address-bar semantics. rack_session.visit(::URI.join("#{effective_host}/", path.to_s).to_s) rescue URI::InvalidURIError rack_session.visit(path) end |
#wait? ⇒ Boolean
208 209 210 |
# File 'lib/capybara/dommy/driver.rb', line 208 def wait? !@time_pump.nil? end |