Class: Capybara::Lightpanda::Driver

Inherits:
Driver::Base
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/capybara/lightpanda/driver.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, options = {}) ⇒ Driver

Returns a new instance of Driver.



15
16
17
18
19
20
21
# File 'lib/capybara/lightpanda/driver.rb', line 15

def initialize(app, options = {})
  super()
  @app = app
  @options = options
  @browser = nil
  @started = false
end

Instance Attribute Details

#appObject (readonly)

Returns the value of attribute app.



11
12
13
# File 'lib/capybara/lightpanda/driver.rb', line 11

def app
  @app
end

#optionsObject (readonly)

Returns the value of attribute options.



11
12
13
# File 'lib/capybara/lightpanda/driver.rb', line 11

def options
  @options
end

Instance Method Details

#accept_modal(type, **options, &block) ⇒ Object

find_modal owns the wait default (browser.options.timeout) — pass wait only when the caller overrode it.



165
166
167
168
169
# File 'lib/capybara/lightpanda/driver.rb', line 165

def accept_modal(type, **options, &block)
  browser.accept_modal(type, text: options[:with])
  block&.call
  browser.find_modal(type, **{ text: options[:text], wait: options[:wait] }.compact)
end

#active_elementObject



70
71
72
73
# File 'lib/capybara/lightpanda/driver.rb', line 70

def active_element
  oid = browser.active_element
  oid && Node.new(self, oid)
end

#add_headers(headers) ⇒ Object



211
212
213
# File 'lib/capybara/lightpanda/driver.rb', line 211

def add_headers(headers)
  browser.network.add_headers(headers)
end

#browserObject



23
24
25
26
# File 'lib/capybara/lightpanda/driver.rb', line 23

def browser
  @browser = nil if @browser && !browser_alive?
  @browser ||= Browser.new(@options)
end

#browser_alive?Boolean

Returns:

  • (Boolean)


28
29
30
# File 'lib/capybara/lightpanda/driver.rb', line 28

def browser_alive?
  !@browser.nil? && @browser.alive?
end

#clear_cookiesObject



135
136
137
# File 'lib/capybara/lightpanda/driver.rb', line 135

def clear_cookies
  browser.cookies.clear
end

#dismiss_modal(type, **options, &block) ⇒ Object



171
172
173
174
175
# File 'lib/capybara/lightpanda/driver.rb', line 171

def dismiss_modal(type, **options, &block)
  browser.dismiss_modal(type)
  block&.call
  browser.find_modal(type, **{ text: options[:text], wait: options[:wait] }.compact)
end

#evaluate_async_script(script, *args) ⇒ Object



100
101
102
# File 'lib/capybara/lightpanda/driver.rb', line 100

def evaluate_async_script(script, *args)
  unwrap_script_result(browser.evaluate_async(script.strip, *native_args(args)))
end

#evaluate_script(script, *args) ⇒ Object



91
92
93
# File 'lib/capybara/lightpanda/driver.rb', line 91

def evaluate_script(script, *args)
  unwrap_script_result(browser.evaluate(script.strip, *native_args(args)))
end

#execute_script(script, *args) ⇒ Object



95
96
97
98
# File 'lib/capybara/lightpanda/driver.rb', line 95

def execute_script(script, *args)
  browser.execute(script.strip, *native_args(args))
  nil
end

#find_css(selector) ⇒ Object



86
87
88
89
# File 'lib/capybara/lightpanda/driver.rb', line 86

def find_css(selector)
  object_ids = browser.find("css", selector)
  object_ids.map { |oid| Node.new(self, oid) }
end

#find_xpath(selector) ⇒ Object



81
82
83
84
# File 'lib/capybara/lightpanda/driver.rb', line 81

def find_xpath(selector)
  object_ids = browser.find("xpath", selector)
  object_ids.map { |oid| Node.new(self, oid) }
end

#go_backObject



53
54
55
# File 'lib/capybara/lightpanda/driver.rb', line 53

def go_back
  browser.back
end

#go_forwardObject



57
58
59
# File 'lib/capybara/lightpanda/driver.rb', line 57

def go_forward
  browser.forward
end

#headersObject

– Headers (Cuprite-compatible driver surface) – Delegates to Network, which lazily enables the Network domain and remembers the headers across reset. Cuprite exposes these on the driver, and real suites call them there (page.driver.headers = …).



203
204
205
# File 'lib/capybara/lightpanda/driver.rb', line 203

def headers
  browser.network.extra_headers
end

#headers=(headers) ⇒ Object



207
208
209
# File 'lib/capybara/lightpanda/driver.rb', line 207

def headers=(headers)
  browser.network.headers = headers
end

#htmlObject Also known as: body



65
66
67
# File 'lib/capybara/lightpanda/driver.rb', line 65

def html
  browser.body
end

#invalid_element_errorsObject

Expanded error list for Capybara retry logic (Cuprite pattern). MouseEventFailed is in Cuprite’s list, but Lightpanda has no rendering engine and the gem dispatches clicks through JS — the underlying CDP Input.dispatchMouseEvent path doesn’t run, so MouseEventFailed is never raised.



254
255
256
257
258
259
260
# File 'lib/capybara/lightpanda/driver.rb', line 254

def invalid_element_errors
  [
    NodeNotFoundError,
    NoExecutionContextError,
    ObsoleteNode,
  ]
end

#needs_server?Boolean

Returns:

  • (Boolean)


241
242
243
# File 'lib/capybara/lightpanda/driver.rb', line 241

def needs_server?
  true
end

#networkObject

Network tracker (lazily auto-enabled). Exposes ‘traffic`, `clear`, `wait_for_idle`, header overrides, etc. Cuprite parity.



108
109
110
# File 'lib/capybara/lightpanda/driver.rb', line 108

def network
  browser.network
end

#pauseObject

Pause execution for interactive debugging.



263
264
265
266
267
268
269
270
271
272
# File 'lib/capybara/lightpanda/driver.rb', line 263

def pause
  if $stdin.tty?
    warn "\nPaused. Press Enter to continue."
    $stdin.gets
  else
    warn "\nPaused. Send SIGCONT (kill -CONT #{::Process.pid}) to continue."
    trap("CONT") {} # rubocop:disable Lint/EmptyBlock
    ::Process.kill("STOP", ::Process.pid)
  end
end

#quitObject



236
237
238
239
# File 'lib/capybara/lightpanda/driver.rb', line 236

def quit
  @browser&.quit
  @browser = nil
end

#refreshObject



61
62
63
# File 'lib/capybara/lightpanda/driver.rb', line 61

def refresh
  browser.refresh
end


139
140
141
# File 'lib/capybara/lightpanda/driver.rb', line 139

def remove_cookie(name, **)
  browser.cookies.remove(name: name, **)
end

#reset!Object

Thin Cuprite-style wrapper. The interesting work — disposing the BrowserContext (cookies, storage, all targets) and starting a fresh one — happens in Browser#reset.

Rescue is the gem hierarchy plus raw IO escapees only — NOT StandardError: reset! runs between every test, so a blanket rescue turns programmer errors (e.g. a NoMethodError in browser.rb) into a silent quit-and-respawn on every example with zero signal. The warn keeps repeated respawns visible.



226
227
228
229
230
231
232
233
234
# File 'lib/capybara/lightpanda/driver.rb', line 226

def reset!
  browser.reset
rescue Error, SystemCallError, IOError => e
  warn "[capybara-lightpanda] reset! failed (#{e.class}: #{e.message}); respawning browser"
  @browser&.quit
  @browser = nil
ensure
  @started = false
end

#save_screenshot(path, **_options) ⇒ Object Also known as: render

– Screenshots – Lightpanda has no rendering engine so screenshots are blank, but we handle the call gracefully so Rails’ before_teardown (screenshot on failure) doesn’t raise NotSupportedByDriverError.



182
183
184
185
186
187
188
189
190
# File 'lib/capybara/lightpanda/driver.rb', line 182

def save_screenshot(path, **_options)
  browser.screenshot(path: path)
rescue BinaryError, BinaryNotFoundError, BrowserError, TimeoutError
  # Browser can't start (version too old), is already dead (DeadBrowserError),
  # the CDP call timed out, or returned any other CDP-level error. Teardown
  # screenshots are best-effort — swallow so the real test failure surfaces
  # instead of a "browser already gone" stack trace.
  nil
end

#send_keys(*keys) ⇒ Object

Capybara’s Session#send_keys routes to Driver#send_keys; Cuprite’s pattern is to fan that out to whatever element currently has focus.



77
78
79
# File 'lib/capybara/lightpanda/driver.rb', line 77

def send_keys(*keys)
  active_element&.send_keys(*keys)
end

– Cookie Management –



122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/capybara/lightpanda/driver.rb', line 122

def set_cookie(name, value, **options)
  cookie_options = { domain: options[:domain] || default_domain }
  cookie_options[:path] = options[:path] if options[:path]
  cookie_options[:secure] = options[:secure] if options.key?(:secure)
  if options.key?(:httpOnly) || options.key?(:http_only)
    cookie_options[:http_only] =
      options[:httpOnly] || options[:http_only]
  end
  cookie_options[:expires] = options[:expires] if options[:expires]

  browser.cookies.set(name: name, value: value, **cookie_options)
end

#switch_to_frame(frame) ⇒ Object

– Frame Support – Passes Node objects (with remote_object_id) to Browser’s frame stack. callFunctionOn on the iframe element scopes finding to its contentDocument.



147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/capybara/lightpanda/driver.rb', line 147

def switch_to_frame(frame)
  case frame
  when :top
    browser.clear_frames
  when :parent
    browser.pop_frame
  when Node
    browser.push_frame(frame)
  else
    # Capybara passes a Capybara::Node::Element; extract our driver Node
    browser.push_frame(frame.base)
  end
end

#visit(url) ⇒ Object



48
49
50
51
# File 'lib/capybara/lightpanda/driver.rb', line 48

def visit(url)
  @started = true
  browser.go_to(url)
end

#wait?Boolean

Returns:

  • (Boolean)


245
246
247
# File 'lib/capybara/lightpanda/driver.rb', line 245

def wait?
  true
end

#wait_for_network_idle(timeout: 5, connections: 0) ⇒ Object

Block until in-flight HTTP traffic settles. Auto-enables the tracker on first call so callers don’t have to remember to flip it on. Returns true on success, false on timeout.



115
116
117
118
# File 'lib/capybara/lightpanda/driver.rb', line 115

def wait_for_network_idle(timeout: 5, connections: 0)
  network.enable
  network.wait_for_idle(timeout: timeout, connections: connections)
end

#with_lightpanda_browser(&block) ⇒ Object

Escape hatch to the underlying Browser for callers that need raw CDP access — e.g. Lightpanda’s ‘LP.*` extensions (`getMarkdown`, `getSemanticTree`, `detectForms`, …) that aren’t worth exposing through the Capybara DSL. Mirrors ‘capybara-playwright-driver`’s ‘with_playwright_page`. Yields the Browser; returns whatever the block returns.

driver.with_lightpanda_browser do |browser|
  browser.page_command("LP.getMarkdown")
end

Raises:

  • (ArgumentError)


42
43
44
45
46
# File 'lib/capybara/lightpanda/driver.rb', line 42

def with_lightpanda_browser(&block)
  raise ArgumentError, "block must be given" unless block

  block.call(browser)
end