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

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.

Parameters:



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

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

  # Create default browser context
  default_user_context = @core_browser.default_user_context
  @default_browser_context = BrowserContext.new(self, default_user_context)
  @browser_contexts = {
    default_user_context.id => @default_browser_context
  }

  register_exit_cleanup if @launcher
end

Instance Attribute Details

#connectionConnection (readonly)

: Connection

Returns:



11
12
13
# File 'lib/puppeteer/bidi/browser.rb', line 11

def connection
  @connection
end

#default_browser_contextBrowserContext (readonly)

: BrowserContext

Returns:



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

def default_browser_context
  @default_browser_context
end

#processObject (readonly)

: untyped

Returns:

  • (Object)


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

def process
  @process
end

#ws_endpointString? (readonly)

: String?

Returns:

  • (String, nil)


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

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

Parameters:

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

Returns:



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

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

Parameters:

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

Returns:



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

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

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:



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

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

Parameters:

Returns:



460
461
462
463
464
465
466
467
468
# File 'lib/puppeteer/bidi/browser.rb', line 460

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)
  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



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/puppeteer/bidi/browser.rb', line 267

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)
  end

  @launcher&.kill
end

#closed?Boolean

Returns:

  • (Boolean)


308
309
310
# File 'lib/puppeteer/bidi/browser.rb', line 308

def closed?
  @closed
end

#cookiesArray[Hash[String, untyped]]

Get all cookies in the default browser context.

Returns:

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


179
180
181
# File 'lib/puppeteer/bidi/browser.rb', line 179

def cookies
  @default_browser_context.cookies
end

#create_browser_contextBrowserContext

Create a new browser context

Returns:



152
153
154
155
# File 'lib/puppeteer/bidi/browser.rb', line 152

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)


425
426
427
428
429
# File 'lib/puppeteer/bidi/browser.rb', line 425

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.

Parameters:

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


195
196
197
# File 'lib/puppeteer/bidi/browser.rb', line 195

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.

Parameters:

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


203
204
205
# File 'lib/puppeteer/bidi/browser.rb', line 203

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



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/puppeteer/bidi/browser.rb', line 289

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)
    end
  end
end

#disconnected?Boolean

Returns:

  • (Boolean)


313
314
315
# File 'lib/puppeteer/bidi/browser.rb', line 313

def disconnected?
  @disconnected
end

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

Overloads:

Yields:

Yield Parameters:

Yield Returns:

  • (void)


433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
# File 'lib/puppeteer/bidi/browser.rb', line 433

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



451
452
453
454
455
456
# File 'lib/puppeteer/bidi/browser.rb', line 451

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.

Parameters:

  • window_id (String)

Returns:

  • (Hash[Symbol, untyped])


218
219
220
221
222
223
224
225
226
227
# File 'lib/puppeteer/bidi/browser.rb', line 218

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)

Parameters:

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

Returns:



146
147
148
# File 'lib/puppeteer/bidi/browser.rb', line 146

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

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

This method returns an undefined value.

Register event handler

Parameters:

  • event (String, Symbol)

Yields:

Yield Parameters:

  • arg0 (Object)

Yield Returns:

  • (void)


261
262
263
# File 'lib/puppeteer/bidi/browser.rb', line 261

def on(event, &block)
  @connection.on(event, &block)
end

#pagesArray[Page]

Get all pages

Returns:



159
160
161
162
163
# File 'lib/puppeteer/bidi/browser.rb', line 159

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

  @default_browser_context.pages
end

#register_exit_cleanupvoid

This method returns an undefined value.



413
414
415
416
417
418
419
420
421
422
423
# File 'lib/puppeteer/bidi/browser.rb', line 413

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.

Parameters:

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


187
188
189
# File 'lib/puppeteer/bidi/browser.rb', line 187

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.

Parameters:

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


211
212
213
# File 'lib/puppeteer/bidi/browser.rb', line 211

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.

Parameters:

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


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

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

Returns:

  • (Object)


131
132
133
# File 'lib/puppeteer/bidi/browser.rb', line 131

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

#targetBrowserTarget

Get the browser target.

Returns:



173
174
175
# File 'lib/puppeteer/bidi/browser.rb', line 173

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

#targetsArray[BrowserTarget | PageTarget | FrameTarget]

Get all known targets.

Returns:



167
168
169
# File 'lib/puppeteer/bidi/browser.rb', line 167

def targets
  each_target.to_a
end

#user_agentString

Get the browser's original user agent

Returns:

  • (String)


137
138
139
# File 'lib/puppeteer/bidi/browser.rb', line 137

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

#wait_for_exitvoid

This method returns an undefined value.

Wait for browser process to exit



406
407
408
# File 'lib/puppeteer/bidi/browser.rb', line 406

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.

Parameters:

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

Yields:

Yield Parameters:

Yield Returns:

  • (boolish)

Returns:



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
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
# File 'lib/puppeteer/bidi/browser.rb', line 321

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