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

– Modal/Dialog Support –



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

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

#active_elementObject



72
73
74
75
# File 'lib/capybara/lightpanda/driver.rb', line 72

def active_element
  oid = browser.active_element
  oid && Node.new(self, oid)
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
31
32
# File 'lib/capybara/lightpanda/driver.rb', line 28

def browser_alive?
  @browser.client && !@browser.client.closed?
rescue StandardError
  false
end

#clear_cookiesObject



137
138
139
# File 'lib/capybara/lightpanda/driver.rb', line 137

def clear_cookies
  browser.cookies.clear
end

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



193
194
195
196
197
198
199
# File 'lib/capybara/lightpanda/driver.rb', line 193

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

#evaluate_async_script(script, *args) ⇒ Object



102
103
104
# File 'lib/capybara/lightpanda/driver.rb', line 102

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

#evaluate_script(script, *args) ⇒ Object



93
94
95
# File 'lib/capybara/lightpanda/driver.rb', line 93

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

#execute_script(script, *args) ⇒ Object



97
98
99
100
# File 'lib/capybara/lightpanda/driver.rb', line 97

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

#find_css(selector) ⇒ Object



88
89
90
91
# File 'lib/capybara/lightpanda/driver.rb', line 88

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

#find_xpath(selector) ⇒ Object



83
84
85
86
# File 'lib/capybara/lightpanda/driver.rb', line 83

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

#frame_titleObject



175
176
177
178
179
180
181
# File 'lib/capybara/lightpanda/driver.rb', line 175

def frame_title
  frame = browser.frame_stack.last
  return browser.title unless frame

  browser.call_function_on(frame.remote_object_id,
                           "function() { return this.contentDocument.title }")
end

#frame_urlObject

Capybara::Driver::Base falls back to running these via the top execution context, which always reports the parent document. Resolve them through the iframe element’s contentWindow / contentDocument so they reflect the active frame.



167
168
169
170
171
172
173
# File 'lib/capybara/lightpanda/driver.rb', line 167

def frame_url
  frame = browser.frame_stack.last
  return browser.current_url unless frame

  browser.call_function_on(frame.remote_object_id,
                           "function() { return this.contentWindow.location.href }")
end

#go_backObject



55
56
57
# File 'lib/capybara/lightpanda/driver.rb', line 55

def go_back
  browser.back
end

#go_forwardObject



59
60
61
# File 'lib/capybara/lightpanda/driver.rb', line 59

def go_forward
  browser.forward
end

#htmlObject Also known as: body



67
68
69
# File 'lib/capybara/lightpanda/driver.rb', line 67

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.



247
248
249
250
251
252
253
# File 'lib/capybara/lightpanda/driver.rb', line 247

def invalid_element_errors
  [
    NodeNotFoundError,
    NoExecutionContextError,
    ObsoleteNode,
  ]
end

#needs_server?Boolean

Returns:

  • (Boolean)


234
235
236
# File 'lib/capybara/lightpanda/driver.rb', line 234

def needs_server?
  true
end

#networkObject

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



110
111
112
# File 'lib/capybara/lightpanda/driver.rb', line 110

def network
  browser.network
end

#pauseObject

Pause execution for interactive debugging.



256
257
258
259
260
261
262
263
264
265
# File 'lib/capybara/lightpanda/driver.rb', line 256

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



229
230
231
232
# File 'lib/capybara/lightpanda/driver.rb', line 229

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

#refreshObject



63
64
65
# File 'lib/capybara/lightpanda/driver.rb', line 63

def refresh
  browser.refresh
end


141
142
143
# File 'lib/capybara/lightpanda/driver.rb', line 141

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.



221
222
223
224
225
226
227
# File 'lib/capybara/lightpanda/driver.rb', line 221

def reset!
  browser.reset
  @started = false
rescue StandardError
  @browser&.quit
  @browser = nil
end

#save_screenshot(path, **_options) ⇒ Object

– 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.



206
207
208
209
210
211
212
213
214
# File 'lib/capybara/lightpanda/driver.rb', line 206

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.



79
80
81
# File 'lib/capybara/lightpanda/driver.rb', line 79

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

– Cookie Management –



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

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.



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

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



50
51
52
53
# File 'lib/capybara/lightpanda/driver.rb', line 50

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

#wait?Boolean

Returns:

  • (Boolean)


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

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.



117
118
119
120
# File 'lib/capybara/lightpanda/driver.rb', line 117

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)


44
45
46
47
48
# File 'lib/capybara/lightpanda/driver.rb', line 44

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

  block.call(browser)
end