Class: Capybara::Simulated::Driver
- Inherits:
-
Driver::Base
- Object
- Driver::Base
- Capybara::Simulated::Driver
show all
- Defined in:
- lib/capybara/simulated/driver.rb
Defined Under Namespace
Classes: FakePlaywrightLocator, FakePlaywrightPage
Constant Summary
collapse
- PRIMARY_HANDLE =
Per-window Browser/VM. ‘open_aux_window` creates a fresh Browser sharing the Driver’s cookie + localStorage jars (origin-shared in real browsers) and visits the target URL; ‘switch_to_window` flips `@active_handle` so subsequent driver ops route through `current_browser`. sessionStorage + DOM + history + the JS VM stay per-window.
'csim-window-0'
- @@live_lock =
Mutex.new
- @@live =
- WeakRef<Driver>
-
— dead refs filtered on read.
[]
Instance Attribute Summary collapse
Class Method Summary
collapse
Instance Method Summary
collapse
-
#accept_modal(type, **options, &block) ⇒ Object
-
#active_element ⇒ Object
-
#close_window(h) ⇒ Object
-
#current_browser ⇒ Object
-
#current_trace ⇒ Object
-
#current_url ⇒ Object
-
#current_window_handle ⇒ Object
-
#dismiss_modal(type, **options, &block) ⇒ Object
-
#evaluate_async_script(script, *args) ⇒ Object
-
#evaluate_script(script, *args) ⇒ Object
-
#execute_script(script, *args) ⇒ Object
Capybara’s ‘execute_script` contract is “run it, discard the return”.
-
#find_css(query, **_) ⇒ Object
-
#find_xpath(query, **_) ⇒ Object
-
#go_back ⇒ Object
-
#go_forward ⇒ Object
-
#header(name, value) ⇒ Object
-
#html ⇒ Object
-
#initialize(app, js_engine: nil, viewport: nil, user_agent: nil) ⇒ Driver
constructor
‘viewport: [w, h]` and `user_agent:` (typically supplied via `Capybara.register_driver`) force the JS-side `innerWidth`/`innerHeight` and `navigator.userAgent` (plus `HTTP_USER_AGENT` on Rack requests) before the first navigate, so `matchMedia` / mobile-breakpoint branches and server-side UA-based mobile detection both resolve before any document loads.
-
#invalid_element_errors ⇒ Object
-
#javascript_enabled? ⇒ Boolean
-
#maximize_window(_) ⇒ Object
-
#needs_server? ⇒ Boolean
-
#no_such_window_error ⇒ Object
-
#open_aux_window(url = nil) ⇒ Object
-
#open_new_window(_kind = :tab) ⇒ Object
Capybara ‘Session#open_new_window(:tab)` entry point — visits `about:blank` so the test can `switch_to_window` then `visit` the real URL.
-
#refresh ⇒ Object
-
#reset! ⇒ Object
-
#resize(w, h) ⇒ Object
Forem’s ahoy-tracking spec calls ‘driver.resize(w, h)` directly rather than through `current_window.resize_to`.
-
#resize_window_to(_, w, h) ⇒ Object
-
#response_headers ⇒ Object
-
#save_screenshot(path, **_opts) ⇒ Object
-
#send_keys(*keys) ⇒ Object
-
#set_geolocation(latitude: nil, longitude: nil, accuracy: 10, denied: false, **rest) ⇒ Object
CDP-ish geolocation override (Capybara driver-level API).
-
#start_tracing(**metadata) ⇒ Object
Per-test trace recording.
-
#status_code ⇒ Object
-
#stop_tracing(path: nil) ⇒ Object
-
#switch_to_window(h) ⇒ Object
-
#title ⇒ Object
-
#tracing? ⇒ Boolean
-
#visit(path) ⇒ Object
-
#wait? ⇒ Boolean
Dynamic wait?: only poll when there’s pending timer work that real-time advancement could resolve.
-
#window_handles ⇒ Object
-
#window_size(_) ⇒ Object
-
#with_playwright_page {|FakePlaywrightPage.new(current_browser)| ... } ⇒ Object
Playwright-driver compatibility shim.
Constructor Details
#initialize(app, js_engine: nil, viewport: nil, user_agent: nil) ⇒ Driver
‘viewport: [w, h]` and `user_agent:` (typically supplied via `Capybara.register_driver`) force the JS-side `innerWidth`/`innerHeight` and `navigator.userAgent` (plus `HTTP_USER_AGENT` on Rack requests) before the first navigate, so `matchMedia` / mobile-breakpoint branches and server-side UA-based mobile detection both resolve before any document loads. The Browser tracks both as “defaults” so `reset!` (per-test teardown) restores them between specs.
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
# File 'lib/capybara/simulated/driver.rb', line 66
def initialize(app, js_engine: nil, viewport: nil, user_agent: nil)
@app = app
@js_engine = js_engine
@cookies = {}
@local_storage = {}
@browser = build_window_browser
@aux_windows = [] @active_handle = nil
@next_window_seq = 0
@owner_thread = Thread.current
@@live_lock.synchronize { @@live << WeakRef.new(self) }
@browser.default_viewport = viewport if viewport
@browser.default_user_agent = user_agent if user_agent
end
|
Instance Attribute Details
#app ⇒ Object
Returns the value of attribute app.
45
46
47
|
# File 'lib/capybara/simulated/driver.rb', line 45
def app
@app
end
|
#browser ⇒ Object
Returns the value of attribute browser.
108
109
110
|
# File 'lib/capybara/simulated/driver.rb', line 108
def browser
@browser
end
|
#owner_thread ⇒ Object
Returns the value of attribute owner_thread.
45
46
47
|
# File 'lib/capybara/simulated/driver.rb', line 45
def owner_thread
@owner_thread
end
|
Class Method Details
.each_live_on_thread(thread) ⇒ Object
50
51
52
53
54
55
56
|
# File 'lib/capybara/simulated/driver.rb', line 50
def self.each_live_on_thread(thread)
drivers = @@live_lock.synchronize {
@@live.select!(&:weakref_alive?)
@@live.filter_map {|ref| ref.__getobj__ rescue nil }
}
drivers.each {|d| yield d if d.owner_thread == thread }
end
|
Instance Method Details
#accept_modal(type, **options, &block) ⇒ Object
357
|
# File 'lib/capybara/simulated/driver.rb', line 357
def accept_modal(type, **options, &block) = run_modal(type, accept: true, **options, &block)
|
#active_element ⇒ Object
329
330
331
332
|
# File 'lib/capybara/simulated/driver.rb', line 329
def active_element
handle = current_browser.active_element_handle
handle ? Node.new(self, handle) : nil
end
|
#close_window(h) ⇒ Object
267
268
269
270
271
272
273
274
275
|
# File 'lib/capybara/simulated/driver.rb', line 267
def close_window(h)
return if h == PRIMARY_HANDLE
@aux_windows.reject! {|w|
next false unless w[:handle] == h
w[:browser].dispose rescue nil
true
}
@active_handle = nil if @active_handle == h
end
|
#current_browser ⇒ Object
Active window’s Browser. Primary by default; switches when the test calls ‘switch_to_window(aux_handle)`. Every DOM / URL / JS-touching driver method routes through here so per-window state (DOM, sessionStorage, history) stays window-scoped.
114
115
116
117
118
|
# File 'lib/capybara/simulated/driver.rb', line 114
def current_browser
return @browser unless @active_handle
w = @aux_windows.find {|win| win[:handle] == @active_handle }
w ? w[:browser] : @browser
end
|
#current_trace ⇒ Object
106
|
# File 'lib/capybara/simulated/driver.rb', line 106
def current_trace = browser.trace || browser.pending_trace
|
#current_url ⇒ Object
215
|
# File 'lib/capybara/simulated/driver.rb', line 215
def current_url = current_browser.current_url || ''
|
#current_window_handle ⇒ Object
237
|
# File 'lib/capybara/simulated/driver.rb', line 237
def current_window_handle = @active_handle || PRIMARY_HANDLE
|
#dismiss_modal(type, **options, &block) ⇒ Object
358
|
# File 'lib/capybara/simulated/driver.rb', line 358
def dismiss_modal(type, **options, &block) = run_modal(type, accept: false, **options, &block)
|
#evaluate_async_script(script, *args) ⇒ Object
306
307
308
|
# File 'lib/capybara/simulated/driver.rb', line 306
def evaluate_async_script(script, *args)
unwrap(current_browser.evaluate_async_script(script, args))
end
|
#evaluate_script(script, *args) ⇒ Object
291
292
293
|
# File 'lib/capybara/simulated/driver.rb', line 291
def evaluate_script(script, *args)
unwrap(current_browser.evaluate_script(script, args))
end
|
#execute_script(script, *args) ⇒ Object
Capybara’s ‘execute_script` contract is “run it, discard the return”. Route through a no-return JS path so a script that returns a non-marshallable value (jQuery `$(’…‘).text(’…‘)` returns a chainable jQuery object that the engine’s value filter recurses into until it stack-overflows) doesn’t blow up on the way back.
301
302
303
304
|
# File 'lib/capybara/simulated/driver.rb', line 301
def execute_script(script, *args)
current_browser.execute_script(script, args)
nil
end
|
#find_css(query, **_) ⇒ Object
226
227
228
|
# File 'lib/capybara/simulated/driver.rb', line 226
def find_css(query, **_)
current_browser.find_css(query).map {|id| Node.new(self, id) }
end
|
#find_xpath(query, **_) ⇒ Object
222
223
224
|
# File 'lib/capybara/simulated/driver.rb', line 222
def find_xpath(query, **_)
current_browser.find_xpath(query).map {|id| Node.new(self, id) }
end
|
#go_back ⇒ Object
213
|
# File 'lib/capybara/simulated/driver.rb', line 213
def go_back = current_browser.go_back
|
#go_forward ⇒ Object
214
|
# File 'lib/capybara/simulated/driver.rb', line 214
def go_forward = current_browser.go_forward
|
220
|
# File 'lib/capybara/simulated/driver.rb', line 220
def (name, value) = current_browser.(name, value)
|
#html ⇒ Object
216
|
# File 'lib/capybara/simulated/driver.rb', line 216
def html = current_browser.html
|
#invalid_element_errors ⇒ Object
#javascript_enabled? ⇒ Boolean
121
|
# File 'lib/capybara/simulated/driver.rb', line 121
def javascript_enabled? = true
|
#maximize_window(_) ⇒ Object
289
|
# File 'lib/capybara/simulated/driver.rb', line 289
def maximize_window(_) ; nil ; end
|
#needs_server? ⇒ Boolean
120
|
# File 'lib/capybara/simulated/driver.rb', line 120
def needs_server? = false
|
#no_such_window_error ⇒ Object
322
|
# File 'lib/capybara/simulated/driver.rb', line 322
def no_such_window_error = Capybara::WindowError
|
#open_aux_window(url = nil) ⇒ Object
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
|
# File 'lib/capybara/simulated/driver.rb', line 241
def open_aux_window(url = nil)
@next_window_seq += 1
handle = "csim-window-#{@next_window_seq}"
aux = build_window_browser
aux.visit(url) if url && !url.empty?
@aux_windows << {handle: handle, browser: aux}
handle
rescue StandardError => e
warn "[csim] open_aux_window(#{url.inspect}) raised: #{e.class}: #{e.message[0, 200]}"
@aux_windows << {handle: handle, browser: aux}
handle
end
|
#open_new_window(_kind = :tab) ⇒ Object
Capybara ‘Session#open_new_window(:tab)` entry point — visits `about:blank` so the test can `switch_to_window` then `visit` the real URL. We don’t distinguish ‘:tab` from `:window` (no window-chrome semantics in this driver).
263
264
265
|
# File 'lib/capybara/simulated/driver.rb', line 263
def open_new_window(_kind = :tab)
open_aux_window
end
|
#refresh ⇒ Object
206
|
# File 'lib/capybara/simulated/driver.rb', line 206
def refresh = current_browser.refresh
|
#reset! ⇒ Object
207
208
209
210
211
212
|
# File 'lib/capybara/simulated/driver.rb', line 207
def reset!
@aux_windows.each {|w| w[:browser].dispose rescue nil }
@aux_windows.clear
@active_handle = nil
browser.reset!
end
|
#resize(w, h) ⇒ Object
Forem’s ahoy-tracking spec calls ‘driver.resize(w, h)` directly rather than through `current_window.resize_to`.
288
|
# File 'lib/capybara/simulated/driver.rb', line 288
def resize(w, h) = current_browser.set_viewport(w, h)
|
#resize_window_to(_, w, h) ⇒ Object
285
286
287
|
# File 'lib/capybara/simulated/driver.rb', line 285
def resize_window_to(_, w, h) = current_browser.set_viewport(w, h)
|
219
|
# File 'lib/capybara/simulated/driver.rb', line 219
def = current_browser.
|
#save_screenshot(path, **_opts) ⇒ Object
324
325
326
327
|
# File 'lib/capybara/simulated/driver.rb', line 324
def save_screenshot(path, **_opts)
File.write(path, current_browser.html.to_s)
path
end
|
#send_keys(*keys) ⇒ Object
343
344
345
346
347
348
349
350
351
352
353
354
355
|
# File 'lib/capybara/simulated/driver.rb', line 343
def send_keys(*keys)
current_browser.send_session_keys(keys)
nil
end
|
#set_geolocation(latitude: nil, longitude: nil, accuracy: 10, denied: false, **rest) ⇒ Object
CDP-ish geolocation override (Capybara driver-level API).
page.driver.set_geolocation(latitude: 35.6, longitude: 139.7)
page.driver.set_geolocation(denied: true) page.driver.set_geolocation
339
340
341
|
# File 'lib/capybara/simulated/driver.rb', line 339
def set_geolocation(latitude: nil, longitude: nil, accuracy: 10, denied: false, **rest)
current_browser.set_geolocation(latitude: latitude, longitude: longitude, accuracy: accuracy, denied: denied, **rest)
end
|
#start_tracing(**metadata) ⇒ Object
Per-test trace recording. Mirrors capybara-playwright-driver’s ‘start_tracing` / `stop_tracing` shape so suites can swap drivers without rewriting hooks.
96
|
# File 'lib/capybara/simulated/driver.rb', line 96
def start_tracing(**metadata) = browser.start_trace(metadata)
|
#status_code ⇒ Object
218
|
# File 'lib/capybara/simulated/driver.rb', line 218
def status_code = current_browser.status_code
|
#stop_tracing(path: nil) ⇒ Object
98
99
100
101
102
103
|
# File 'lib/capybara/simulated/driver.rb', line 98
def stop_tracing(path: nil)
active = current_trace or return nil
result = path ? browser.finish_trace_to(path, active) : active
browser.clear_trace!
result
end
|
#switch_to_window(h) ⇒ Object
276
277
278
279
280
281
282
283
284
|
# File 'lib/capybara/simulated/driver.rb', line 276
def switch_to_window(h)
if h == PRIMARY_HANDLE
@active_handle = nil
elsif @aux_windows.any? {|w| w[:handle] == h }
@active_handle = h
else
raise Capybara::WindowError, "Unknown window handle: #{h}"
end
end
|
#title ⇒ Object
217
|
# File 'lib/capybara/simulated/driver.rb', line 217
def title = current_browser.title
|
#tracing? ⇒ Boolean
105
|
# File 'lib/capybara/simulated/driver.rb', line 105
def tracing? = !current_trace.nil?
|
#visit(path) ⇒ Object
205
|
# File 'lib/capybara/simulated/driver.rb', line 205
def visit(path) = current_browser.visit(path)
|
#wait? ⇒ Boolean
Dynamic wait?: only poll when there’s pending timer work that real-time advancement could resolve. With no timers queued, polling can’t change anything, so we fail fast via the ‘wait? = false` synchronize path.
203
|
# File 'lib/capybara/simulated/driver.rb', line 203
def wait? = current_browser.polling?
|
#window_handles ⇒ Object
238
239
240
|
# File 'lib/capybara/simulated/driver.rb', line 238
def window_handles
[PRIMARY_HANDLE] + @aux_windows.map {|w| w[:handle] }
end
|
#window_size(_) ⇒ Object
266
|
# File 'lib/capybara/simulated/driver.rb', line 266
def window_size(_) = [current_browser.viewport_width, current_browser.viewport_height]
|
#with_playwright_page {|FakePlaywrightPage.new(current_browser)| ... } ⇒ Object
Playwright-driver compatibility shim. Discourse’s system-spec ‘before(:each)` calls `page.driver.with_playwright_page` to install a JS-console logger, apply a CDP `setTimezoneOverride`, and (in dev_tools_spec) evaluate `window.enableDevTools()`. Yield a `FakePlaywrightPage` that delegates `evaluate(js)` to our JS engine and silently no-ops every other Playwright-only method via `method_missing → self`. Chained accessors like `pw.context.new_cdp_session(pw).send_message(“…”)` therefore propagate as a no-op rather than NoMethodError, while `pw.evaluate(“…”)` runs the JS where it matters.
133
134
135
|
# File 'lib/capybara/simulated/driver.rb', line 133
def with_playwright_page
yield FakePlaywrightPage.new(current_browser) if block_given?
end
|