Class: Puppeteer::Bidi::Browser

Inherits:
Object
  • Object
show all
Defined in:
lib/puppeteer/bidi/browser.rb,
sig/puppeteer/bidi/browser.rbs

Overview

Browser represents a browser instance with BiDi connection

Constant Summary collapse

TARGET_EVENTS =

Returns:

  • (Object)
%i[targetcreated targetchanged targetdestroyed].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection:, launcher:, core_browser:, session:, ws_endpoint:) ⇒ Browser

Returns a new instance of Browser.

RBS:

  • connection: Connection -- BiDi connection

  • launcher: BrowserLauncher? -- Browser launcher instance

  • core_browser: Core::Browser -- Core browser instance

  • session: Core::Session -- BiDi session

  • ws_endpoint: String? -- WebSocket endpoint URL

  • return: void

Parameters:



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/puppeteer/bidi/browser.rb', line 54

def initialize(connection:, launcher:, core_browser:, session:, ws_endpoint:)
  @connection = connection
  @launcher = launcher
  @closed = false
  @disconnected = false
  @core_browser = core_browser
  @session = session
  @ws_endpoint = ws_endpoint
  @emitter = Core::EventEmitter.new
  @browser_contexts = {}

  @core_browser.once(:disconnected) { @emitter.dispose }

  # Create default browser context
  default_user_context = @core_browser.default_user_context
  @default_browser_context = browser_context_for(default_user_context)

  register_exit_cleanup if @launcher
end

Instance Attribute Details

#connectionConnection (readonly)

Signature:

  • Connection

Returns:



13
14
15
# File 'lib/puppeteer/bidi/browser.rb', line 13

def connection
  @connection
end

#default_browser_contextBrowserContext (readonly)

Signature:

  • BrowserContext

Returns:



15
16
17
# File 'lib/puppeteer/bidi/browser.rb', line 15

def default_browser_context
  @default_browser_context
end

#processObject (readonly)

Signature:

  • untyped

Returns:

  • (Object)


14
15
16
# File 'lib/puppeteer/bidi/browser.rb', line 14

def process
  @process
end

#ws_endpointString? (readonly)

Signature:

  • String?

Returns:

  • (String, nil)


16
17
18
# File 'lib/puppeteer/bidi/browser.rb', line 16

def ws_endpoint
  @ws_endpoint
end

Class Method Details

.connect(ws_endpoint, timeout: nil, accept_insecure_certs: false) ⇒ Browser

Connect to an existing Firefox browser instance

RBS:

  • ws_endpoint: String -- WebSocket endpoint URL

  • timeout: Numeric? -- Connect timeout in seconds

  • accept_insecure_certs: bool -- Accept insecure certificates

  • return: Browser -- Browser instance

Parameters:

  • ws_endpoint (String)
  • timeout: (Numeric, nil) (defaults to: nil)
  • accept_insecure_certs: (Boolean) (defaults to: false)

Returns:



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/puppeteer/bidi/browser.rb', line 115

def self.connect(ws_endpoint, timeout: nil, accept_insecure_certs: false)
  transport = Transport.new(ws_endpoint)
  ws_endpoint = transport.url
  timeout_ms = ((timeout || 30) * 1000).to_i
  AsyncUtils.async_timeout(timeout_ms) { transport.connect }.wait
  connection = Connection.new(transport)

  # Verify that this endpoint speaks WebDriver BiDi (and is ready) before creating a new session.
  status = connection.async_send_command('session.status', {}, timeout: timeout_ms).wait
  unless status.is_a?(Hash) && status['ready'] == true
    raise Error, "WebDriver BiDi endpoint is not ready: #{status.inspect}"
  end

  create(connection: connection, launcher: nil, ws_endpoint: ws_endpoint,
         accept_insecure_certs: accept_insecure_certs)
end

.create(connection:, launcher: nil, ws_endpoint: nil, accept_insecure_certs: false) ⇒ Browser

RBS:

  • connection: Connection -- BiDi connection

  • launcher: BrowserLauncher? -- Browser launcher instance

  • ws_endpoint: String? -- WebSocket endpoint URL

  • accept_insecure_certs: bool -- Accept insecure certificates

  • return: Browser -- Browser instance

Parameters:

  • connection: (Connection)
  • launcher: (BrowserLauncher, nil) (defaults to: nil)
  • ws_endpoint: (String, nil) (defaults to: nil)
  • accept_insecure_certs: (Boolean) (defaults to: false)

Returns:



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/puppeteer/bidi/browser.rb', line 23

def self.create(connection:, launcher: nil, ws_endpoint: nil, accept_insecure_certs: false)
  # Create a new BiDi session
  session = Core::Session.from(
    connection: connection,
    capabilities: {
      alwaysMatch: {
        acceptInsecureCerts: accept_insecure_certs,
        unhandledPromptBehavior: { default: 'ignore' },
        webSocketUrl: true,
      },
    },
  ).wait

  core_browser = Core::Browser.from(session).wait
  session.browser = core_browser

  new(
    connection: connection,
    launcher: launcher,
    core_browser: core_browser,
    session: session,
    ws_endpoint: ws_endpoint,
  )
end

.launch(executable_path: nil, user_data_dir: nil, headless: true, args: nil, timeout: nil, accept_insecure_certs: false) ⇒ Browser

Launch a new Firefox browser instance

RBS:

  • executable_path: String? -- Path to browser executable

  • user_data_dir: String? -- Path to user data directory

  • headless: bool -- Run browser in headless mode

  • args: Array[String]? -- Additional browser arguments

  • timeout: Numeric? -- Launch timeout in seconds

  • accept_insecure_certs: bool -- Accept insecure certificates

  • return: Browser -- Browser instance

Parameters:

  • executable_path: (String, nil) (defaults to: nil)
  • user_data_dir: (String, nil) (defaults to: nil)
  • headless: (Boolean) (defaults to: true)
  • args: (Array[String], nil) (defaults to: nil)
  • timeout: (Numeric, nil) (defaults to: nil)
  • accept_insecure_certs: (Boolean) (defaults to: false)

Returns:



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/puppeteer/bidi/browser.rb', line 82

def self.launch(executable_path: nil, user_data_dir: nil, headless: true, args: nil, timeout: nil,
                accept_insecure_certs: false)
  launcher = BrowserLauncher.new(
    executable_path: executable_path,
    user_data_dir: user_data_dir,
    headless: headless,
    args: args || []
  )

  ws_endpoint = launcher.launch

  # Create transport and connection
  transport = Transport.new(ws_endpoint)
  ws_endpoint = transport.url

  # Start transport connection in background thread with Sync reactor
  # Sync is the preferred way to run async code at the top level
  timeout_ms = ((timeout || 30) * 1000).to_i
  AsyncUtils.async_timeout(timeout_ms) { transport.connect }.wait

  connection = Connection.new(transport)

  browser = create(connection: connection, launcher: launcher, ws_endpoint: ws_endpoint,
                   accept_insecure_certs: accept_insecure_certs)
  _target = browser.wait_for_target { |target| target.type == 'page' }
  browser
end

Instance Method Details

#browser_context_for(user_context) ⇒ BrowserContext

RBS:

  • user_context: Core::UserContext -- User context to get browser context for

  • return: BrowserContext -- Browser context

Parameters:

Returns:



494
495
496
497
498
499
500
501
502
503
504
505
506
507
# File 'lib/puppeteer/bidi/browser.rb', line 494

def browser_context_for(user_context)
  return @browser_contexts[user_context.id] if @browser_contexts.key?(user_context.id)

  context = BrowserContext.new(self, user_context)
  TARGET_EVENTS.each do |event|
    context.on(event) do |target|
      @emitter.emit(event, target)
    end
  end
  user_context.once(:closed) do
    @browser_contexts.delete(user_context.id)
  end
  @browser_contexts[user_context.id] = context
end

#closevoid

This method returns an undefined value.

Close the browser

RBS:

  • return: void



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/puppeteer/bidi/browser.rb', line 297

def close
  return if @closed

  @closed = true

  begin
    begin
      @connection.async_send_command('browser.close', {}).wait
    rescue StandardError => e
      debug_error(e)
    ensure
      @connection.close
    end
  rescue => e
    debug_error(e)
  ensure
    @emitter.dispose
  end

  @launcher&.kill
end

#closed?Boolean

RBS:

  • return: bool

Returns:

  • (Boolean)


342
343
344
# File 'lib/puppeteer/bidi/browser.rb', line 342

def closed?
  @closed
end

#cookiesArray[Hash[String, untyped]]

Get all cookies in the default browser context.

RBS:

  • return: Array[Hash[String, untyped]] -- Cookies

Returns:

  • (Array[Hash[String, untyped]])


182
183
184
# File 'lib/puppeteer/bidi/browser.rb', line 182

def cookies
  @default_browser_context.cookies
end

#create_browser_contextBrowserContext

Create a new browser context

RBS:

  • return: BrowserContext -- New browser context

Returns:



155
156
157
158
# File 'lib/puppeteer/bidi/browser.rb', line 155

def create_browser_context
  user_context = @core_browser.create_user_context.wait
  browser_context_for(user_context)
end

#debug_error(error) ⇒ Object

Parameters:

  • error (Object)

Returns:

  • (Object)


459
460
461
462
463
# File 'lib/puppeteer/bidi/browser.rb', line 459

def debug_error(error)
  return unless ENV['DEBUG_BIDI_COMMAND']

  warn(error.full_message)
end

This method returns an undefined value.

Delete cookies in the default browser context.

RBS:

  • *cookies: Array[Hash[String, untyped]] -- Cookies to delete

  • **cookie: untyped -- Single cookie via keyword arguments

  • return: void

Parameters:

  • cookies (Array[Hash[String, untyped]])
  • cookie (Object)


198
199
200
# File 'lib/puppeteer/bidi/browser.rb', line 198

def delete_cookie(*cookies, **cookie)
  @default_browser_context.delete_cookie(*cookies, **cookie)
end

#delete_matching_cookies(*filters, **filter) ⇒ void

This method returns an undefined value.

Delete cookies matching the provided filters in the default browser context.

RBS:

  • *filters: Array[Hash[String, untyped]] -- Cookie filters

  • **filter: untyped -- Single filter via keyword arguments

  • return: void

Parameters:

  • filters (Array[Hash[String, untyped]])
  • filter (Object)


206
207
208
# File 'lib/puppeteer/bidi/browser.rb', line 206

def delete_matching_cookies(*filters, **filter)
  @default_browser_context.delete_matching_cookies(*filters, **filter)
end

#disconnectvoid

This method returns an undefined value.

Disconnect from the browser (does not close the browser process).

RBS:

  • return: void



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/puppeteer/bidi/browser.rb', line 321

def disconnect
  return if @closed || @disconnected

  @disconnected = true

  begin
    @session.end_session
  rescue StandardError => e
    debug_error(e)
  ensure
    begin
      @connection.close
    rescue StandardError => e
      debug_error(e)
    ensure
      @emitter.dispose
    end
  end
end

#disconnected?Boolean

RBS:

  • return: bool

Returns:

  • (Boolean)


347
348
349
# File 'lib/puppeteer/bidi/browser.rb', line 347

def disconnected?
  @disconnected
end

#each_targetEnumerator[BrowserTarget | PageTarget | FrameTarget, void] #each_targetvoid

Overloads:

RBS:

  • () -> Enumerator[BrowserTarget | PageTarget | FrameTarget, void] | () { (BrowserTarget | PageTarget | FrameTarget) -> void } -> void

Yields:

Yield Parameters:

Yield Returns:

  • (void)


467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/puppeteer/bidi/browser.rb', line 467

def each_target(&block)
  return enum_for(:each_target) unless block_given?
  return unless @core_browser

  yield target

  @core_browser.user_contexts.each do |user_context|
    next if user_context.disposed?

    browser_context = browser_context_for(user_context)
    next unless browser_context

    browser_context.targets.each { |target| yield target }
  end
end

#find_target(predicate) ⇒ BrowserTarget, ...

RBS:

  • predicate: ^(BrowserTarget | PageTarget | FrameTarget) -> boolish -- Predicate to match targets

  • return: (BrowserTarget | PageTarget | FrameTarget)? -- Matching target or nil

Returns:



485
486
487
488
489
490
# File 'lib/puppeteer/bidi/browser.rb', line 485

def find_target(predicate)
  each_target do |target|
    return target if predicate.call(target)
  end
  nil
end

#get_window_bounds(window_id) ⇒ Hash[Symbol, untyped]

Get browser window bounds for a given window ID.

RBS:

  • window_id: String -- Window ID

  • return: Hash[Symbol, untyped] -- Window bounds hash

Parameters:

  • window_id (String)

Returns:

  • (Hash[Symbol, untyped])


221
222
223
224
225
226
227
228
229
230
# File 'lib/puppeteer/bidi/browser.rb', line 221

def get_window_bounds(window_id)
  info = @core_browser.get_client_window_info(window_id).wait
  {
    left: info['x'],
    top: info['y'],
    width: info['width'],
    height: info['height'],
    window_state: info['state']
  }
end

#new_page(background: nil, type: nil, window_bounds: nil) ⇒ Page

Create a new page (Puppeteer-like API)

RBS:

  • background: bool? -- Whether to open the page in background

  • type: String? -- 'tab' or 'window'

  • window_bounds: Hash[Symbol | String, untyped]? -- Initial window bounds for window pages

  • return: Page -- New page instance

Parameters:

  • background: (Boolean, nil) (defaults to: nil)
  • type: (String, nil) (defaults to: nil)
  • window_bounds: (Hash[Symbol | String, untyped], nil) (defaults to: nil)

Returns:



149
150
151
# File 'lib/puppeteer/bidi/browser.rb', line 149

def new_page(background: nil, type: nil, window_bounds: nil)
  @default_browser_context.new_page(background: background, type: type, window_bounds: window_bounds)
end

#off(event, &block) ⇒ void

This method returns an undefined value.

Remove an event handler

RBS:

  • event: String | Symbol -- Event name

  • &block: ((untyped) -> void)? -- Event handler to remove

  • return: void

Parameters:

  • event (String, Symbol)


289
290
291
292
293
# File 'lib/puppeteer/bidi/browser.rb', line 289

def off(event, &block)
  return @emitter.off(event, &block) if TARGET_EVENTS.include?(event.to_sym)

  @connection.off(event, &block)
end

#on(event) {|arg0| ... } ⇒ void

This method returns an undefined value.

Register event handler

RBS:

  • event: String | Symbol -- Event name

  • &block: (untyped) -> void -- Event handler

  • return: void

Parameters:

  • event (String, Symbol)

Yields:

Yield Parameters:

  • arg0 (Object)

Yield Returns:

  • (void)


264
265
266
267
268
# File 'lib/puppeteer/bidi/browser.rb', line 264

def on(event, &block)
  return @emitter.on(event, &block) if TARGET_EVENTS.include?(event.to_sym)

  @connection.on(event, &block)
end

#once(event) {|arg0| ... } ⇒ void

This method returns an undefined value.

Register a one-time event handler

RBS:

  • event: String | Symbol -- Event name

  • &block: (untyped) -> void -- Event handler

  • return: void

Parameters:

  • event (String, Symbol)

Yields:

Yield Parameters:

  • arg0 (Object)

Yield Returns:

  • (void)


274
275
276
277
278
279
280
281
282
283
# File 'lib/puppeteer/bidi/browser.rb', line 274

def once(event, &block)
  return @emitter.once(event, &block) if TARGET_EVENTS.include?(event.to_sym)

  # @type var wrapper: ^(untyped) -> void
  wrapper = proc do |data|
    @connection.off(event, &wrapper)
    block.call(data)
  end
  @connection.on(event, &wrapper)
end

#pagesArray[Page]

Get all pages

RBS:

  • return: Array[Page] -- All pages

Returns:



162
163
164
165
166
# File 'lib/puppeteer/bidi/browser.rb', line 162

def pages
  return [] if @closed || @disconnected

  @default_browser_context.pages
end

#register_exit_cleanupvoid

This method returns an undefined value.

RBS:

  • return: void



447
448
449
450
451
452
453
454
455
456
457
# File 'lib/puppeteer/bidi/browser.rb', line 447

def register_exit_cleanup
  at_exit do
    next if @closed || @disconnected

    begin
      @launcher&.kill
    rescue StandardError => e
      debug_error(e)
    end
  end
end

This method returns an undefined value.

Set cookies in the default browser context.

RBS:

  • *cookies: Array[Hash[String, untyped]] -- Cookie data

  • **cookie: untyped -- Single cookie via keyword arguments

  • return: void

Parameters:

  • cookies (Array[Hash[String, untyped]])
  • cookie (Object)


190
191
192
# File 'lib/puppeteer/bidi/browser.rb', line 190

def set_cookie(*cookies, **cookie)
  @default_browser_context.set_cookie(*cookies, **cookie)
end

#set_permission(origin, *permissions) ⇒ void

This method returns an undefined value.

Set permission states in the default browser context.

RBS:

  • origin: String | Symbol -- Origin URL (must not be '*')

  • *permissions: Hash[Symbol | String, untyped] -- Permission descriptors with states

  • return: void

Parameters:

  • origin (String, Symbol)
  • permissions (Hash[Symbol | String, untyped])


214
215
216
# File 'lib/puppeteer/bidi/browser.rb', line 214

def set_permission(origin, *permissions)
  @default_browser_context.set_permission(origin, *permissions)
end

#set_window_bounds(window_id, window_bounds) ⇒ void

This method returns an undefined value.

Set browser window bounds for a given window ID.

RBS:

  • window_id: String -- Window ID

  • window_bounds: Hash[Symbol | String, untyped] -- Bounds/state to apply

  • return: void

Parameters:

  • window_id (String)
  • window_bounds (Hash[Symbol | String, untyped])


236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/puppeteer/bidi/browser.rb', line 236

def set_window_bounds(window_id, window_bounds)
  normalized = window_bounds.transform_keys(&:to_sym)
  window_state = normalized[:window_state] || normalized[:windowState] || 'normal'
  window_state = window_state.to_s

  params = if window_state == 'normal'
             {
               clientWindow: window_id,
               state: 'normal',
               x: normalized[:left],
               y: normalized[:top],
               width: normalized[:width],
               height: normalized[:height]
             }
           else
             {
               clientWindow: window_id,
               state: window_state
             }
           end

  @core_browser.set_client_window_state(params).wait
end

#statusObject

Get BiDi session status

RBS:

  • return: untyped -- Session status

Returns:

  • (Object)


134
135
136
# File 'lib/puppeteer/bidi/browser.rb', line 134

def status
  @connection.async_send_command('session.status').wait
end

#targetBrowserTarget

Get the browser target.

RBS:

  • return: BrowserTarget -- Browser target

Returns:



176
177
178
# File 'lib/puppeteer/bidi/browser.rb', line 176

def target
  @target ||= BrowserTarget.new(self)
end

#targetsArray[BrowserTarget | PageTarget | FrameTarget]

Get all known targets.

RBS:

  • return: Array[BrowserTarget | PageTarget | FrameTarget] -- Known targets

Returns:



170
171
172
# File 'lib/puppeteer/bidi/browser.rb', line 170

def targets
  each_target.to_a
end

#user_agentString

Get the browser's original user agent

RBS:

  • return: String -- User agent string

Returns:

  • (String)


140
141
142
# File 'lib/puppeteer/bidi/browser.rb', line 140

def user_agent
  @session.capabilities["userAgent"]
end

#wait_for_exitvoid

This method returns an undefined value.

Wait for browser process to exit

RBS:

  • return: void



440
441
442
# File 'lib/puppeteer/bidi/browser.rb', line 440

def wait_for_exit
  @launcher&.wait
end

#wait_for_target(timeout: nil) {|arg0| ... } ⇒ BrowserTarget, ...

Wait until a target (top-level browsing context) satisfies the predicate.

RBS:

  • timeout: Integer? -- Timeout in milliseconds (default: 30000)

  • &predicate: (BrowserTarget | PageTarget | FrameTarget) -> boolish -- Predicate for each Target

  • return: BrowserTarget | PageTarget | FrameTarget -- Matching target

Parameters:

  • timeout: (Integer, nil) (defaults to: nil)

Yields:

Yield Parameters:

Yield Returns:

  • (boolish)

Returns:



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/puppeteer/bidi/browser.rb', line 355

def wait_for_target(timeout: nil, &predicate)
  predicate ||= ->(_target) { true }
  timeout_ms = timeout || 30_000
  raise ArgumentError, 'timeout must be >= 0' if timeout_ms && timeout_ms.negative?

  if (target = find_target(predicate))
    return target
  end

  promise = Async::Promise.new
  session_listeners = []
  browser_listeners = []

  cleanup = lambda do
    session_listeners.each do |event, listener|
      @session.off(event, &listener)
    end
    session_listeners.clear

    browser_listeners.each do |event, listener|
      @core_browser.off(event, &listener)
    end
    browser_listeners.clear
  end

  check_and_resolve = lambda do
    return if promise.resolved?

    begin
      if (match = find_target(predicate))
        promise.resolve(match)
        cleanup.call
      end
    rescue => error
      promise.reject(error) unless promise.resolved?
      cleanup.call
    end
  end

  session_listener = proc { |_data| check_and_resolve.call }
  session_events = [
    :'browsingContext.contextCreated',
    :'browsingContext.navigationStarted',
    :'browsingContext.navigationCommitted',
    :'browsingContext.historyUpdated',
    :'browsingContext.fragmentNavigated',
    :'browsingContext.domContentLoaded',
    :'browsingContext.load'
  ]

  session_events.each do |event|
    @session.on(event, &session_listener)
    session_listeners << [event, session_listener]
  end

  browser_disconnect_listener = proc do |reason|
    next if promise.resolved?

    promise.reject(Core::BrowserDisconnectedError.new(reason || 'Browser disconnected'))
    cleanup.call
  end

  @core_browser.on(:disconnected, &browser_disconnect_listener)
  browser_listeners << [:disconnected, browser_disconnect_listener]

  # Re-check after listeners are set up to avoid missing fast events.
  check_and_resolve.call

  begin
    result = if timeout_ms
               AsyncUtils.async_timeout(timeout_ms, promise).wait
             else
               promise.wait
             end
  rescue Async::TimeoutError
    raise TimeoutError, "Waiting for target failed: timeout #{timeout_ms}ms exceeded"
  ensure
    cleanup.call
  end

  result
end