Class: Puppeteer::Page

Inherits:
Object
  • Object
show all
Includes:
DebugPrint, EventCallbackable, IfPresent
Defined in:
lib/puppeteer/page.rb,
lib/puppeteer/page/metrics.rb,
lib/puppeteer/page/pdf_options.rb,
lib/puppeteer/page/screenshot_options.rb,
lib/puppeteer/page/screenshot_task_queue.rb

Defined Under Namespace

Classes: JavaScriptExpression, JavaScriptFunction, Metrics, MetricsEvent, PDFOptions, PageError, PrintToPdfIsNotImplementedError, ScreenshotOptions, ScreenshotTaskQueue, TargetCrashedError

Constant Summary collapse

VISION_DEFICIENCY_TYPES =
%w[
  none
  achromatopsia
  blurredVision
  deuteranopia
  protanopia
  tritanopia
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from IfPresent

#if_present

Methods included from EventCallbackable

#add_event_listener, #emit_event, #observe_first, #on_event, #remove_event_listener

Methods included from DebugPrint

#debug_print, #debug_puts

Constructor Details

#initialize(client, target, ignore_https_errors, network_enabled: true) ⇒ Page

Returns a new instance of Page.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/puppeteer/page.rb', line 39

def initialize(client, target, ignore_https_errors, network_enabled: true)
  @closed = false
  @client = client
  @target = target
  @tab_id = nil
  @keyboard = Puppeteer::Keyboard.new(client)
  @mouse = Puppeteer::Mouse.new(client, @keyboard)
  @timeout_settings = Puppeteer::TimeoutSettings.new
  @touchscreen = Puppeteer::TouchScreen.new(client, @keyboard)
  # @accessibility = Accessibility.new(client)
  @frame_manager = Puppeteer::FrameManager.new(client, self, ignore_https_errors, @timeout_settings, network_enabled: network_enabled)
  @emulation_manager = Puppeteer::EmulationManager.new(client)
  @tracing = Puppeteer::Tracing.new(client)
  @page_bindings = {}
  @page_binding_ids = {}
  @coverage = Puppeteer::Coverage.new(client)
  @javascript_enabled = true
  @screenshot_task_queue = ScreenshotTaskQueue.new
  @inflight_requests = Set.new
  @request_intercepted_listener_map = ObjectSpace::WeakMap.new
  @attached_sessions = Set.new

  @workers = {}
  @user_drag_interception_enabled = false
  @service_worker_bypassed = false

  @attached_session_listener_id = @client.add_event_listener(CDPSessionEmittedEvents::Ready) do |session|
    handle_attached_to_session(session)
  end
  @target_gone_listener_id = @target.target_manager.add_event_listener(
    TargetManagerEmittedEvents::TargetGone,
    &method(:handle_detached_from_target)
  )

  @frame_manager.on_event(FrameManagerEmittedEvents::FrameAttached) do |event|
    emit_event(PageEmittedEvents::FrameAttached, event)
  end
  @frame_manager.on_event(FrameManagerEmittedEvents::FrameDetached) do |event|
    emit_event(PageEmittedEvents::FrameDetached, event)
  end
  @frame_manager.on_event(FrameManagerEmittedEvents::FrameNavigated) do |event|
    emit_event(PageEmittedEvents::FrameNavigated, event)
  end

  network_manager = @frame_manager.network_manager
  network_manager.on_event(NetworkManagerEmittedEvents::Request) do |event|
    @inflight_requests.add(event)
    emit_event(PageEmittedEvents::Request, event)
  end
  network_manager.on_event(NetworkManagerEmittedEvents::Response) do |event|
    emit_event(PageEmittedEvents::Response, event)
  end
  network_manager.on_event(NetworkManagerEmittedEvents::RequestServedFromCache) do |event|
    emit_event(PageEmittedEvents::RequestServedFromCache, event)
  end
  network_manager.on_event(NetworkManagerEmittedEvents::RequestFailed) do |event|
    @inflight_requests.delete(event)
    emit_event(PageEmittedEvents::RequestFailed, event)
  end
  network_manager.on_event(NetworkManagerEmittedEvents::RequestFinished) do |event|
    @inflight_requests.delete(event)
    emit_event(PageEmittedEvents::RequestFinished, event)
  end
  @file_chooser_interception_is_disabled = false
  @file_chooser_interceptors = Set.new

  @client.on_event('Page.domContentEventFired') do |event|
    emit_event(PageEmittedEvents::DOMContentLoaded)
  end
  @client.on_event('Page.loadEventFired') do |event|
    emit_event(PageEmittedEvents::Load)
  end
  @client.add_event_listener('Runtime.consoleAPICalled') do |event|
    handle_console_api(event)
  end
  @client.add_event_listener('Runtime.bindingCalled') do |event|
    handle_binding_called(event)
  end
  @client.on_event('Page.javascriptDialogOpening') do |event|
    handle_dialog_opening(event)
  end
  @client.on_event('Runtime.exceptionThrown') do |exception|
    handle_exception(exception['exceptionDetails'])
  end
  @client.on_event('Inspector.targetCrashed') do |event|
    handle_target_crashed
  end
  @client.on_event('Performance.metrics') do |event|
    emit_event(PageEmittedEvents::Metrics, MetricsEvent.new(event))
  end
  @client.on_event('Log.entryAdded') do |event|
    handle_log_entry_added(event)
  end
  @client.on_event('Page.fileChooserOpened') do |event|
    handle_file_chooser(event)
  end
  Async do
    @target.is_closed_promise.wait
    @client.remove_event_listener(@attached_session_listener_id)
    @target.target_manager.remove_event_listener(@target_gone_listener_id)

    emit_event(PageEmittedEvents::Close)
    @closed = true
  end
end

Instance Attribute Details

#accessibilityObject (readonly)

Returns the value of attribute accessibility.



412
413
414
# File 'lib/puppeteer/page.rb', line 412

def accessibility
  @accessibility
end

#clientObject (readonly)

Returns the value of attribute client.



329
330
331
# File 'lib/puppeteer/page.rb', line 329

def client
  @client
end

#coverageObject (readonly)

Returns the value of attribute coverage.



412
413
414
# File 'lib/puppeteer/page.rb', line 412

def coverage
  @coverage
end

#javascript_enabledObject Also known as: javascript_enabled?

Returns the value of attribute javascript_enabled.



329
330
331
# File 'lib/puppeteer/page.rb', line 329

def javascript_enabled
  @javascript_enabled
end

#mouseObject (readonly)

Returns the value of attribute mouse.



1642
1643
1644
# File 'lib/puppeteer/page.rb', line 1642

def mouse
  @mouse
end

#service_worker_bypassedObject Also known as: service_worker_bypassed?

Returns the value of attribute service_worker_bypassed.



329
330
331
# File 'lib/puppeteer/page.rb', line 329

def service_worker_bypassed
  @service_worker_bypassed
end

#targetObject (readonly)

Returns the value of attribute target.



329
330
331
# File 'lib/puppeteer/page.rb', line 329

def target
  @target
end

#touchscreenObject (readonly) Also known as: touch_screen

Returns the value of attribute touchscreen.



412
413
414
# File 'lib/puppeteer/page.rb', line 412

def touchscreen
  @touchscreen
end

#tracingObject (readonly)

Returns the value of attribute tracing.



412
413
414
# File 'lib/puppeteer/page.rb', line 412

def tracing
  @tracing
end

#viewportObject

Returns the value of attribute viewport.



1370
1371
1372
# File 'lib/puppeteer/page.rb', line 1370

def viewport
  @viewport
end

Class Method Details

.create(client, target, ignore_https_errors, default_viewport, network_enabled: true) ⇒ Object



25
26
27
28
29
30
31
32
# File 'lib/puppeteer/page.rb', line 25

def self.create(client, target, ignore_https_errors, default_viewport, network_enabled: true)
  page = Puppeteer::Page.new(client, target, ignore_https_errors, network_enabled: network_enabled)
  page.init
  if default_viewport
    page.viewport = default_viewport
  end
  page
end

Instance Method Details

#==(other) ⇒ Object



341
342
343
344
345
346
347
348
# File 'lib/puppeteer/page.rb', line 341

def ==(other)
  other = other.__getobj__ if other.is_a?(Puppeteer::ReactorRunner::Proxy)
  return true if equal?(other)
  return false unless other.is_a?(Puppeteer::Page)
  return false unless @target&.target_id && other.target&.target_id

  @target.target_id == other.target.target_id
end

#_tab_idObject



332
333
334
335
336
337
# File 'lib/puppeteer/page.rb', line 332

def _tab_id
  return @tab_id if @tab_id

  parent_session = @client.respond_to?(:parent_session) ? @client.parent_session : nil
  @tab_id = parent_session&.target&.target_id || @target.target_id
end

#add_script_tag(url: nil, path: nil, content: nil, type: nil, id: nil) ⇒ Object



645
646
647
# File 'lib/puppeteer/page.rb', line 645

def add_script_tag(url: nil, path: nil, content: nil, type: nil, id: nil)
  main_frame.add_script_tag(url: url, path: path, content: content, type: type, id: id)
end

#add_style_tag(url: nil, path: nil, content: nil) ⇒ Object



653
654
655
# File 'lib/puppeteer/page.rb', line 653

def add_style_tag(url: nil, path: nil, content: nil)
  main_frame.add_style_tag(url: url, path: path, content: content)
end

#async_wait_for_frame(url: nil, predicate: nil, timeout: nil) ⇒ Object



1223
# File 'lib/puppeteer/page.rb', line 1223

define_async_method :async_wait_for_frame

#async_wait_for_navigation(timeout: nil, wait_until: nil) ⇒ Object



1005
# File 'lib/puppeteer/page.rb', line 1005

define_async_method :async_wait_for_navigation

#async_wait_for_request(url: nil, predicate: nil, timeout: nil) ⇒ Object

Waits until request URL matches or request matches the given predicate.

Waits until request URL matches

wait_for_request(url: 'https://example.com/awesome')

Waits until request matches the given predicate

wait_for_request(predicate: -> (req){ req.url.start_with?('https://example.com/search') })


1105
# File 'lib/puppeteer/page.rb', line 1105

define_async_method :async_wait_for_request

#async_wait_for_response(url: nil, predicate: nil, timeout: nil) ⇒ Object



1133
# File 'lib/puppeteer/page.rb', line 1133

define_async_method :async_wait_for_response

#authenticate(username: nil, password: nil) ⇒ Object



731
732
733
# File 'lib/puppeteer/page.rb', line 731

def authenticate(username: nil, password: nil)
  @frame_manager.network_manager.authenticate(username: username, password: password)
end

#bring_to_frontObject

Brings page to front (activates tab).



1254
1255
1256
# File 'lib/puppeteer/page.rb', line 1254

def bring_to_front
  @client.send_message('Page.bringToFront')
end

#browserObject



353
354
355
# File 'lib/puppeteer/page.rb', line 353

def browser
  @target.browser
end

#browser_contextObject



358
359
360
# File 'lib/puppeteer/page.rb', line 358

def browser_context
  @target.browser_context
end

#bypass_csp=(enabled) ⇒ Object



1275
1276
1277
# File 'lib/puppeteer/page.rb', line 1275

def bypass_csp=(enabled)
  @client.send_message('Page.setBypassCSP', enabled: enabled)
end

#cache_enabled=(enabled) ⇒ Object



1434
1435
1436
# File 'lib/puppeteer/page.rb', line 1434

def cache_enabled=(enabled)
  @frame_manager.network_manager.cache_enabled = enabled
end

#capture_heap_snapshot(path:) ⇒ Object



757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
# File 'lib/puppeteer/page.rb', line 757

def capture_heap_snapshot(path:)
  @client.send_message('HeapProfiler.enable')
  @client.send_message('HeapProfiler.collectGarbage')

  begin
    File.open(path, 'w') do |file|
      listener_id = @client.add_event_listener('HeapProfiler.addHeapSnapshotChunk') do |event|
        file.write(event['chunk'])
      end

      begin
        @client.send_message('HeapProfiler.takeHeapSnapshot', reportProgress: false)
      ensure
        @client.remove_event_listener(listener_id)
      end
    end
  ensure
    @client.send_message('HeapProfiler.disable')
  end
end

#click(selector, delay: nil, button: nil, click_count: nil, count: nil) ⇒ Object



1650
1651
1652
# File 'lib/puppeteer/page.rb', line 1650

def click(selector, delay: nil, button: nil, click_count: nil, count: nil)
  main_frame.click(selector, delay: delay, button: button, click_count: click_count, count: count)
end

#close(run_before_unload: false) ⇒ Object



1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
# File 'lib/puppeteer/page.rb', line 1611

def close(run_before_unload: false)
  guard = browser_context.wait_for_screenshot_operations
  begin
    unless @client.connection
      raise 'Protocol error: Connection closed. Most likely the page has been closed.'
    end

    if run_before_unload
      @client.send_message('Page.close')
    else
      @client.connection.send_message('Target.closeTarget', targetId: @target.target_id)
      @target.is_closed_promise.wait

      # @closed sometimes remains false, so wait for @closed = true with 100ms timeout.
      25.times do
        break if @closed
        Puppeteer::AsyncUtils.sleep_seconds(0.004)
      end
    end
  rescue Puppeteer::Connection::ProtocolError => err
    raise unless err.message.match?(/Target closed/i)
  ensure
    guard&.release
  end
end

#closed?Boolean

Returns:

  • (Boolean)


1638
1639
1640
# File 'lib/puppeteer/page.rb', line 1638

def closed?
  @closed
end

#contentObject



938
939
940
# File 'lib/puppeteer/page.rb', line 938

def content
  main_frame.content
end

#content=(html) ⇒ Object



952
953
954
# File 'lib/puppeteer/page.rb', line 952

def content=(html)
  main_frame.set_content(html)
end

#cookies(*urls) ⇒ Object



565
566
567
# File 'lib/puppeteer/page.rb', line 565

def cookies(*urls)
  @client.send_message('Network.getCookies', urls: (urls.empty? ? [url] : urls))['cookies']
end

#create_pdf_stream(options = {}) ⇒ Object



1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
# File 'lib/puppeteer/page.rb', line 1558

def create_pdf_stream(options = {})
  timeout_helper = Puppeteer::TimeoutHelper.new('Page.printToPDF',
                    timeout_ms: options[:timeout],
                    default_timeout_ms: 30000)
  pdf_options = PDFOptions.new(options)
  omit_background = options[:omit_background]
  set_transparent_background_color if omit_background
  result =
    begin
      timeout_helper.with_timeout do
        @client.send_message('Page.printToPDF', pdf_options.page_print_args)
      end
    ensure
      reset_default_background_color if omit_background
    end

  Puppeteer::ProtocolStreamReader.new(
    client: @client,
    handle: result['stream'],
  ).read_as_chunks
end

#default_navigation_timeout=(timeout) ⇒ Object



467
468
469
# File 'lib/puppeteer/page.rb', line 467

def default_navigation_timeout=(timeout)
  @timeout_settings.default_navigation_timeout = timeout
end

#default_timeoutObject



478
479
480
# File 'lib/puppeteer/page.rb', line 478

def default_timeout
  @timeout_settings.timeout
end

#default_timeout=(timeout) ⇒ Object



473
474
475
# File 'lib/puppeteer/page.rb', line 473

def default_timeout=(timeout)
  @timeout_settings.default_timeout = timeout
end


589
590
591
592
593
594
595
596
597
598
# File 'lib/puppeteer/page.rb', line 589

def delete_cookie(*cookies)
  assert_cookie_params(cookies, requires: %i(name))

  page_url = url
  starts_with_http = page_url.start_with?("http")
  cookies.each do |cookie|
    item = (starts_with_http ? { url: page_url } : {}).merge(cookie)
    @client.send_message("Network.deleteCookies", item)
  end
end

#drag_interception_enabled=(enabled) ⇒ Object



441
442
443
444
# File 'lib/puppeteer/page.rb', line 441

def drag_interception_enabled=(enabled)
  @user_drag_interception_enabled = enabled
  @client.send_message('Input.setInterceptDrags', enabled: enabled)
end

#drag_interception_enabled?Boolean Also known as: drag_interception_enabled

Returns:

  • (Boolean)


208
209
210
# File 'lib/puppeteer/page.rb', line 208

def drag_interception_enabled?
  @user_drag_interception_enabled
end

#emulate(device) ⇒ Object



1260
1261
1262
1263
# File 'lib/puppeteer/page.rb', line 1260

def emulate(device)
  self.viewport = device.viewport
  self.user_agent = device.user_agent
end

#emulate_cpu_throttling(factor) ⇒ Object



1291
1292
1293
1294
1295
1296
1297
# File 'lib/puppeteer/page.rb', line 1291

def emulate_cpu_throttling(factor)
  if factor.nil? || factor >= 1
    @client.send_message('Emulation.setCPUThrottlingRate', rate: factor || 1)
  else
    raise ArgumentError.new('Throttling rate should be greater or equal to 1')
  end
end

#emulate_idle_state(is_user_active: nil, is_screen_unlocked: nil) ⇒ Object



1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
# File 'lib/puppeteer/page.rb', line 1349

def emulate_idle_state(is_user_active: nil, is_screen_unlocked: nil)
  overrides = {
    isUserActive: is_user_active,
    isScreenUnlocked: is_screen_unlocked,
  }.compact

  if overrides.empty?
    @client.send_message('Emulation.clearIdleOverride')
  else
    @client.send_message('Emulation.setIdleOverride', overrides)
  end
end

#emulate_media_features(features) ⇒ Object



1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
# File 'lib/puppeteer/page.rb', line 1301

def emulate_media_features(features)
  if features.nil?
    @client.send_message('Emulation.setEmulatedMedia', features: nil)
  elsif features.is_a?(Array)
    features.each do |media_feature|
      name = media_feature[:name]
      unless /^(?:prefers-(?:color-scheme|reduced-motion)|color-gamut)$/.match?(name)
        raise ArgumentError.new("Unsupported media feature: #{name}")
      end
    end
    @client.send_message('Emulation.setEmulatedMedia', features: features)
  end
end

#emulate_media_type(media_type) ⇒ Object



1281
1282
1283
1284
1285
1286
1287
# File 'lib/puppeteer/page.rb', line 1281

def emulate_media_type(media_type)
  media_type_str = media_type.to_s
  unless ['screen', 'print', ''].include?(media_type_str)
    raise ArgumentError.new("Unsupported media type: #{media_type}")
  end
  @client.send_message('Emulation.setEmulatedMedia', media: media_type_str)
end

#emulate_network_conditions(network_condition) ⇒ Object



461
462
463
# File 'lib/puppeteer/page.rb', line 461

def emulate_network_conditions(network_condition)
  @frame_manager.network_manager.emulate_network_conditions(network_condition)
end

#emulate_timezone(timezone_id) ⇒ Object



1317
1318
1319
1320
1321
1322
1323
1324
1325
# File 'lib/puppeteer/page.rb', line 1317

def emulate_timezone(timezone_id)
  @client.send_message('Emulation.setTimezoneOverride', timezoneId: timezone_id || '')
rescue => err
  if err.message.include?('Invalid timezone')
    raise ArgumentError.new("Invalid timezone ID: #{timezone_id}")
  else
    raise err
  end
end

#emulate_vision_deficiency(vision_deficiency_type) ⇒ Object



1338
1339
1340
1341
1342
1343
1344
# File 'lib/puppeteer/page.rb', line 1338

def emulate_vision_deficiency(vision_deficiency_type)
  value = vision_deficiency_type || 'none'
  unless VISION_DEFICIENCY_TYPES.include?(value)
    raise ArgumentError.new("Unsupported vision deficiency: #{vision_deficiency_type}")
  end
  @client.send_message('Emulation.setEmulatedVisionDeficiency', type: value)
end

#eval_on_selector(selector, page_function, *args) ⇒ Object Also known as: Seval

‘$eval()` in JavaScript.



535
536
537
# File 'lib/puppeteer/page.rb', line 535

def eval_on_selector(selector, page_function, *args)
  main_frame.eval_on_selector(selector, page_function, *args)
end

#eval_on_selector_all(selector, page_function, *args) ⇒ Object Also known as: SSeval

‘$$eval()` in JavaScript.



547
548
549
# File 'lib/puppeteer/page.rb', line 547

def eval_on_selector_all(selector, page_function, *args)
  main_frame.eval_on_selector_all(selector, page_function, *args)
end

#evaluate(page_function, *args) ⇒ Object



1375
1376
1377
# File 'lib/puppeteer/page.rb', line 1375

def evaluate(page_function, *args)
  main_frame.evaluate(page_function, *args)
end

#evaluate_handle(page_function, *args) ⇒ Object



516
517
518
519
# File 'lib/puppeteer/page.rb', line 516

def evaluate_handle(page_function, *args)
  context = main_frame.execution_context
  context.evaluate_handle(page_function, *args)
end

#evaluate_on_new_document(page_function, *args) ⇒ Object



1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
# File 'lib/puppeteer/page.rb', line 1416

def evaluate_on_new_document(page_function, *args)
  source =
    if ['=>', 'async', 'function'].any? { |keyword| page_function.include?(keyword) }
      JavaScriptFunction.new(page_function, args).source
    else
      JavaScriptExpression.new(page_function).source
    end

  @client.send_message('Page.addScriptToEvaluateOnNewDocument', source: source)
end

#expose_function(name, puppeteer_function) ⇒ Object



660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
# File 'lib/puppeteer/page.rb', line 660

def expose_function(name, puppeteer_function)
  if @page_bindings[name]
    raise ArgumentError.new("Failed to add page binding with name `#{name}` already exists!")
  end
  @page_bindings[name] = puppeteer_function

  add_page_binding = <<~JAVASCRIPT
  function (type, bindingName) {
    /* Cast window to any here as we're about to add properties to it
    * via win[bindingName] which TypeScript doesn't like.
    */
    const win = window;
    const binding = win[bindingName];

    win[bindingName] = (...args) => {
      const me = window[bindingName];
      let callbacks = me.callbacks;
      if (!callbacks) {
        callbacks = new Map();
        me.callbacks = callbacks;
      }
      const seq = (me.lastSeq || 0) + 1;
      me.lastSeq = seq;
      const promise = new Promise((resolve, reject) =>
        callbacks.set(seq, { resolve, reject })
      );
      binding(JSON.stringify({ type, name: bindingName, seq, args }));
      return promise;
    };
  }
  JAVASCRIPT

  source = JavaScriptFunction.new(add_page_binding, ['exposedFun', name]).source
  @client.send_message('Runtime.addBinding', name: name)
  script = @client.send_message('Page.addScriptToEvaluateOnNewDocument', source: source)
  @page_binding_ids[name] = script['identifier']

  promises = @frame_manager.frames.map do |frame|
    frame.async_evaluate("() => #{source}")
  end
  Puppeteer::AsyncUtils.await_promise_all(*promises)

  nil
end

#extension_realmsObject



374
375
376
# File 'lib/puppeteer/page.rb', line 374

def extension_realms
  main_frame.extension_realms
end

#extra_http_headers=(headers) ⇒ Object



737
738
739
# File 'lib/puppeteer/page.rb', line 737

def extra_http_headers=(headers)
  @frame_manager.network_manager.extra_http_headers = headers
end

#focus(selector) ⇒ Object



1658
1659
1660
# File 'lib/puppeteer/page.rb', line 1658

def focus(selector)
  main_frame.focus(selector)
end

#framesObject



424
425
426
# File 'lib/puppeteer/page.rb', line 424

def frames
  @frame_manager.frames
end

#geolocation=(geolocation) ⇒ Object



325
326
327
# File 'lib/puppeteer/page.rb', line 325

def geolocation=(geolocation)
  @client.send_message('Emulation.setGeolocationOverride', geolocation.to_h)
end

#go_back(timeout: nil, wait_until: nil) ⇒ Object



1228
1229
1230
# File 'lib/puppeteer/page.rb', line 1228

def go_back(timeout: nil, wait_until: nil)
  go(-1, timeout: timeout, wait_until: wait_until)
end

#go_forward(timeout: nil, wait_until: nil) ⇒ Object



1235
1236
1237
# File 'lib/puppeteer/page.rb', line 1235

def go_forward(timeout: nil, wait_until: nil)
  go(+1, timeout: timeout, wait_until: wait_until)
end

#goto(url, referer: nil, referrer_policy: nil, timeout: nil, wait_until: nil) ⇒ Object



963
964
965
966
967
968
969
970
971
# File 'lib/puppeteer/page.rb', line 963

def goto(url, referer: nil, referrer_policy: nil, timeout: nil, wait_until: nil)
  main_frame.goto(
    url,
    referer: referer,
    referrer_policy: referrer_policy,
    timeout: timeout,
    wait_until: wait_until,
  )
end

#handle_binding_called(event) ⇒ Object



824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
# File 'lib/puppeteer/page.rb', line 824

def handle_binding_called(event)
  execution_context_id = event['executionContextId']
  payload =
    begin
      JSON.parse(event['payload'])
    rescue
      # The binding was either called by something in the page or it was
      # called before our wrapper was initialized.
      return
    end
  name = payload['name']
  seq = payload['seq']
  args = payload['args']

  if payload['type'] != 'exposedFun' || !@page_bindings[name]
    return
  end

  expression =
    begin
      result = @page_bindings[name].call(*args)

      deliver_result = <<~JAVASCRIPT
      function (name, seq, result) {
        window[name].callbacks.get(seq).resolve(result);
        window[name].callbacks.delete(seq);
      }
      JAVASCRIPT

      JavaScriptFunction.new(deliver_result, [name, seq, result]).source
    rescue => err
      deliver_error = <<~JAVASCRIPT
      function (name, seq, message) {
        const error = new Error(message);
        window[name].callbacks.get(seq).reject(error);
        window[name].callbacks.delete(seq);
      }
      JAVASCRIPT
      JavaScriptFunction.new(deliver_error, [name, seq, err.message]).source
    end

  Async do
    @client.async_send_message('Runtime.evaluate', expression: expression, contextId: execution_context_id).wait
  rescue => error
    debug_puts(error)
  end
end

#handle_file_chooser(event) ⇒ Object



284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/puppeteer/page.rb', line 284

def handle_file_chooser(event)
  return if @file_chooser_interceptors.empty?

  frame = @frame_manager.frame(event['frameId'])
  element = frame.main_world.adopt_backend_node(event['backendNodeId'])
  interceptors = @file_chooser_interceptors.to_a
  @file_chooser_interceptors.clear
  file_chooser = Puppeteer::FileChooser.new(element, event)
  interceptors.each do |promise|
    promise.resolve(file_chooser)
  end
end

#has_devtoolsObject



363
364
365
# File 'lib/puppeteer/page.rb', line 363

def has_devtools
  !!browser._has_devtools_target(@target.target_id)
end

#hover(selector) ⇒ Object



1666
1667
1668
# File 'lib/puppeteer/page.rb', line 1666

def hover(selector)
  main_frame.hover(selector)
end

#initObject



199
200
201
202
203
204
205
# File 'lib/puppeteer/page.rb', line 199

def init
  Puppeteer::AsyncUtils.await_promise_all(
    @frame_manager.async_init(@target.target_id),
    @client.async_send_message('Performance.enable'),
    @client.async_send_message('Log.enable'),
  )
end

#keyboard(&block) ⇒ Object



417
418
419
420
421
# File 'lib/puppeteer/page.rb', line 417

def keyboard(&block)
  @keyboard.instance_eval(&block) unless block.nil?

  @keyboard
end

#locator(selector_or_function) ⇒ Object



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

def locator(selector_or_function)
  if Puppeteer::Locator.function_string?(selector_or_function)
    Puppeteer::FunctionLocator.create(self, selector_or_function)
  else
    Puppeteer::NodeLocator.create(self, selector_or_function)
  end
end

#main_frameObject



408
409
410
# File 'lib/puppeteer/page.rb', line 408

def main_frame
  @frame_manager.main_frame
end

#metricsObject



750
751
752
753
# File 'lib/puppeteer/page.rb', line 750

def metrics
  response = @client.send_message('Performance.getMetrics')
  Metrics.new(response['metrics'])
end

#off(event_name_or_id, listener = nil, &block) ⇒ Object



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/puppeteer/page.rb', line 258

def off(event_name_or_id, listener = nil, &block)
  listener ||= block
  if listener && PageEmittedEvents.values.include?(event_name_or_id.to_s)
    event_name = event_name_or_id.to_s
    if event_name == 'request'
      listeners = @request_intercepted_listener_map[listener]
      wrapped = listeners&.shift
      return unless wrapped
      if listeners.empty?
        if @request_intercepted_listener_map.respond_to?(:delete)
          @request_intercepted_listener_map.delete(listener)
        else
          @request_intercepted_listener_map[listener] = nil
        end
      end
      super(event_name, wrapped)
    else
      super(event_name, listener)
    end
  else
    super(event_name_or_id)
  end
end

#offline_mode=(enabled) ⇒ Object



455
456
457
# File 'lib/puppeteer/page.rb', line 455

def offline_mode=(enabled)
  @frame_manager.network_manager.offline_mode = enabled
end

#on(event_name, &block) ⇒ Object



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/puppeteer/page.rb', line 216

def on(event_name, &block)
  unless PageEmittedEvents.values.include?(event_name.to_s)
    raise ArgumentError.new("Unknown event name: #{event_name}. Known events are #{PageEmittedEvents.values.to_a.join(", ")}")
  end

  if event_name.to_s == 'request'
    wrapped = ->(req) { req.enqueue_intercept_action(-> { block.call(req) }) }
    if (listeners = @request_intercepted_listener_map[block])
      listeners << wrapped
    else
      @request_intercepted_listener_map[block] = [wrapped]
    end
    super('request', &wrapped)
  else
    super(event_name.to_s, &block)
  end
end

#once(event_name, &block) ⇒ Object



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/puppeteer/page.rb', line 237

def once(event_name, &block)
  unless PageEmittedEvents.values.include?(event_name.to_s)
    raise ArgumentError.new("Unknown event name: #{event_name}. Known events are #{PageEmittedEvents.values.to_a.join(", ")}")
  end

  if event_name.to_s == 'request'
    wrapped = ->(req) { req.enqueue_intercept_action(-> { block.call(req) }) }
    if (listeners = @request_intercepted_listener_map[block])
      listeners << wrapped
    else
      @request_intercepted_listener_map[block] = [wrapped]
    end
    super('request', &wrapped)
  else
    super(event_name.to_s, &block)
  end
end

#pdf(options = {}) ⇒ Object



1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
# File 'lib/puppeteer/page.rb', line 1582

def pdf(options = {})
  chunks = create_pdf_stream(options)

  StringIO.open do |stringio|
    if options[:path]
      File.open(options[:path], 'wb') do |f|
        chunks.each do |chunk|
          f.write(chunk)
          stringio.write(chunk)
        end
      end
    else
      chunks.each do |chunk|
        stringio.write(chunk)
      end
    end

    stringio.string
  end
rescue => err
  if err.message.include?('PrintToPDF is not implemented')
    raise PrintToPdfIsNotImplementedError.new
  else
    raise
  end
end

#query_objects(prototype_handle) ⇒ Object



525
526
527
528
# File 'lib/puppeteer/page.rb', line 525

def query_objects(prototype_handle)
  context = main_frame.execution_context
  context.query_objects(prototype_handle)
end

#query_selector(selector) ⇒ Object Also known as: S

‘$()` in JavaScript.



495
496
497
# File 'lib/puppeteer/page.rb', line 495

def query_selector(selector)
  main_frame.query_selector(selector)
end

#query_selector_all(selector, isolate: nil) ⇒ Object Also known as: SS

‘$$()` in JavaScript.



506
507
508
# File 'lib/puppeteer/page.rb', line 506

def query_selector_all(selector, isolate: nil)
  main_frame.query_selector_all(selector, isolate: isolate)
end

#reload(timeout: nil, wait_until: nil, ignore_cache: nil) ⇒ Object



977
978
979
980
981
982
983
984
985
986
987
988
# File 'lib/puppeteer/page.rb', line 977

def reload(timeout: nil, wait_until: nil, ignore_cache: nil)
  params = {}
  params[:ignoreCache] = ignore_cache unless ignore_cache.nil?

  wait_for_navigation(timeout: timeout, wait_until: wait_until, ignore_same_document_navigation: true) do
    if params.empty?
      @client.send_message('Page.reload')
    else
      @client.send_message('Page.reload', **params)
    end
  end
end

#remove_exposed_function(name) ⇒ Object



707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
# File 'lib/puppeteer/page.rb', line 707

def remove_exposed_function(name)
  identifier = @page_binding_ids[name]
  unless identifier
    raise ArgumentError.new("Function with name \"#{name}\" does not exist")
  end

  @page_binding_ids.delete(name)
  @page_bindings.delete(name)

  @client.send_message('Runtime.removeBinding', name: name)
  @client.send_message('Page.removeScriptToEvaluateOnNewDocument', identifier: identifier)

  remove_script = '(name) => { delete window[name]; }'
  @frame_manager.frames.each do |frame|
    frame.evaluate(remove_script, name)
  rescue StandardError
    nil
  end
  nil
end

#remove_script_to_evaluate_on_new_document(identifier) ⇒ Object



1429
1430
1431
# File 'lib/puppeteer/page.rb', line 1429

def remove_script_to_evaluate_on_new_document(identifier)
  @client.send_message('Page.removeScriptToEvaluateOnNewDocument', identifier: identifier)
end

#request_interception=(value) ⇒ Object



435
436
437
# File 'lib/puppeteer/page.rb', line 435

def request_interception=(value)
  @frame_manager.network_manager.request_interception = value
end

#screenshot(type: nil, path: nil, full_page: nil, clip: nil, quality: nil, omit_background: nil, encoding: nil, capture_beyond_viewport: nil, from_surface: nil) ⇒ Object



1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
# File 'lib/puppeteer/page.rb', line 1453

def screenshot(type: nil,
               path: nil,
               full_page: nil,
               clip: nil,
               quality: nil,
               omit_background: nil,
               encoding: nil,
               capture_beyond_viewport: nil,
               from_surface: nil)
  options = {
    type: type,
    path: path,
    full_page: full_page,
    clip: clip,
    quality:  quality,
    omit_background: omit_background,
    encoding: encoding,
    capture_beyond_viewport: capture_beyond_viewport,
    from_surface: from_surface,
  }.compact
  screenshot_options = ScreenshotOptions.new(options)

  guard = browser_context.start_screenshot
  @screenshot_task_queue.post_task do
    screenshot_task(screenshot_options.type, screenshot_options)
  end
ensure
  guard&.release
end

#select(selector, *values) ⇒ Object



1673
1674
1675
# File 'lib/puppeteer/page.rb', line 1673

def select(selector, *values)
  main_frame.select(selector, *values)
end

#set_content(html, timeout: nil, wait_until: nil) ⇒ Object



946
947
948
# File 'lib/puppeteer/page.rb', line 946

def set_content(html, timeout: nil, wait_until: nil)
  main_frame.set_content(html, timeout: timeout, wait_until: wait_until)
end


602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
# File 'lib/puppeteer/page.rb', line 602

def set_cookie(*cookies)
  assert_cookie_params(cookies, requires: %i(name value))

  page_url = url
  starts_with_http = page_url.start_with?("http")
  items = cookies.map do |cookie|
    (starts_with_http ? { url: page_url } : {}).merge(cookie).tap do |item|
      item_name = item[:name] || item['name']
      item_url = item[:url] || item['url']
      raise ArgumentError.new("Blank page can not have cookie \"#{item_name}\"") if item_url == "about:blank"
      raise ArgumentError.new("Data URL page can not have cookie \"#{item_name}\"") if item_url&.start_with?("data:")

      same_site =
        if item.key?(:sameSite)
          item[:sameSite]
        elsif item.key?('sameSite')
          item['sameSite']
        elsif item.key?(:same_site)
          item[:same_site]
        else
          item['same_site']
        end

      converted_same_site = convert_same_site_for_cdp(same_site)
      item.delete(:sameSite)
      item.delete('sameSite')
      item.delete(:same_site)
      item.delete('same_site')
      item[:sameSite] = converted_same_site if converted_same_site
    end
  end
  delete_cookie(*items)
  unless items.empty?
    @client.send_message("Network.setCookies", cookies: items)
  end
end

#set_user_agent(user_agent, user_agent_metadata = nil) ⇒ Object Also known as: user_agent=



744
745
746
# File 'lib/puppeteer/page.rb', line 744

def set_user_agent(user_agent,  = nil)
  @frame_manager.network_manager.set_user_agent(user_agent, )
end

#Sx(expression) ⇒ Object

‘$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.



557
558
559
# File 'lib/puppeteer/page.rb', line 557

def Sx(expression)
  main_frame.Sx(expression)
end

#tap(selector: nil, &block) ⇒ Object



1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
# File 'lib/puppeteer/page.rb', line 1682

def tap(selector: nil, &block)
  # resolves double meaning of tap.
  if selector.nil? && block
    # Original usage of Object#tap.
    #
    # browser.new_page.tap do |page|
    #   ...
    # end
    block.call(self)
    return self
  end

  # Puppeteer's Page#tap.
  main_frame.tap(selector)
  nil
end

#titleObject



1439
1440
1441
# File 'lib/puppeteer/page.rb', line 1439

def title
  main_frame.title
end

#trigger_extension_action(extension) ⇒ Object



369
370
371
# File 'lib/puppeteer/page.rb', line 369

def trigger_extension_action(extension)
  extension.trigger_action(self)
end

#type_text(selector, text, delay: nil) ⇒ Object



1705
1706
1707
# File 'lib/puppeteer/page.rb', line 1705

def type_text(selector, text, delay: nil)
  main_frame.type_text(selector, text, delay: delay)
end

#urlObject



933
934
935
# File 'lib/puppeteer/page.rb', line 933

def url
  main_frame.url
end

#wait_for_file_chooser(timeout: nil) ⇒ Object



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

def wait_for_file_chooser(timeout: nil)
  if @file_chooser_interceptors.empty?
    @client.send_message('Page.setInterceptFileChooserDialog', enabled: true)
  end

  option_timeout = timeout || @timeout_settings.timeout
  promise = Async::Promise.new
  @file_chooser_interceptors << promise

  begin
    if option_timeout == 0
      promise.wait
    else
      Puppeteer::AsyncUtils.async_timeout(option_timeout, promise).wait
    end
  rescue Async::TimeoutError
    raise Puppeteer::TimeoutError.new("Waiting for `FileChooser` failed: #{option_timeout}ms exceeded")
  ensure
    @file_chooser_interceptors.delete(promise)
  end
end

#wait_for_frame(url: nil, predicate: nil, timeout: nil) ⇒ Object



1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
# File 'lib/puppeteer/page.rb', line 1195

def wait_for_frame(url: nil, predicate: nil, timeout: nil)
  if !url && !predicate
    raise ArgumentError.new('url or predicate must be specified')
  end
  if predicate && !predicate.is_a?(Proc)
    raise ArgumentError.new('predicate must be a proc.')
  end
  frame_predicate =
    if url
      -> (frame) { frame.url == url }
    else
      predicate
    end

  frames.each do |frame|
    return frame if frame_predicate.call(frame)
  end

  wait_for_frame_manager_event(
    FrameManagerEmittedEvents::FrameAttached,
    FrameManagerEmittedEvents::FrameNavigated,
    predicate: frame_predicate,
    timeout: timeout,
  )
end

#wait_for_function(page_function, args: [], polling: nil, timeout: nil) ⇒ Object



1744
1745
1746
# File 'lib/puppeteer/page.rb', line 1744

def wait_for_function(page_function, args: [], polling: nil, timeout: nil)
  main_frame.wait_for_function(page_function, args: args, polling: polling, timeout: timeout)
end

#wait_for_navigation(timeout: nil, wait_until: nil, ignore_same_document_navigation: false) ⇒ Object



994
995
996
997
998
999
1000
1001
# File 'lib/puppeteer/page.rb', line 994

def wait_for_navigation(timeout: nil, wait_until: nil, ignore_same_document_navigation: false)
  main_frame.send(
    :wait_for_navigation,
    timeout: timeout,
    wait_until: wait_until,
    ignore_same_document_navigation: ignore_same_document_navigation,
  )
end

#wait_for_network_idle(idle_time: 500, timeout: nil, concurrency: 0) ⇒ Object



1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
# File 'lib/puppeteer/page.rb', line 1139

def wait_for_network_idle(idle_time: 500, timeout: nil, concurrency: 0)
  option_timeout = timeout || @timeout_settings.timeout

  promise = Async::Promise.new
  idle_timer = nil

  schedule_idle = lambda do
    return if @inflight_requests.size > concurrency

    idle_timer&.stop
    idle_timer = Async do
      Puppeteer::AsyncUtils.sleep_seconds(idle_time / 1000.0)
      unless promise.resolved? || @inflight_requests.size > concurrency
        promise.resolve(nil)
      end
    end
  end

  # Use raw listener to avoid request interception queue delaying idle tracking.
  request_listener = add_event_listener('request') do
    idle_timer&.stop
    idle_timer = nil
  end
  request_finished_listener = on('requestfinished') do
    schedule_idle.call
  end
  request_failed_listener = on('requestfailed') do
    schedule_idle.call
  end

  schedule_idle.call

  begin
    if option_timeout == 0
      Puppeteer::AsyncUtils.await_promise_race(promise, session_close_promise)
    else
      Puppeteer::AsyncUtils.async_timeout(option_timeout, -> {
        Puppeteer::AsyncUtils.await_promise_race(promise, session_close_promise)
      }).wait
    end
  rescue Async::TimeoutError
    raise Puppeteer::TimeoutError.new("waiting for network idle failed: timeout #{option_timeout}ms exceeded")
  ensure
    off(request_listener)
    off(request_finished_listener)
    off(request_failed_listener)
    idle_timer&.stop
  end
end

#wait_for_request(url: nil, predicate: nil, timeout: nil) ⇒ Object



1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
# File 'lib/puppeteer/page.rb', line 1075

def wait_for_request(url: nil, predicate: nil, timeout: nil)
  if !url && !predicate
    raise ArgumentError.new('url or predicate must be specified')
  end
  if predicate && !predicate.is_a?(Proc)
    raise ArgumentError.new('predicate must be a proc.')
  end
  request_predicate =
    if url
      -> (request) { request.url == url }
    else
      predicate
    end

  wait_for_network_manager_event(NetworkManagerEmittedEvents::Request,
    predicate: request_predicate,
    timeout: timeout,
  )
end

#wait_for_response(url: nil, predicate: nil, timeout: nil) ⇒ Object



1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
# File 'lib/puppeteer/page.rb', line 1111

def wait_for_response(url: nil, predicate: nil, timeout: nil)
  if !url && !predicate
    raise ArgumentError.new('url or predicate must be specified')
  end
  if predicate && !predicate.is_a?(Proc)
    raise ArgumentError.new('predicate must be a proc.')
  end
  response_predicate =
    if url
      -> (response) { response.url == url }
    else
      predicate
    end

  wait_for_network_manager_event(NetworkManagerEmittedEvents::Response,
    predicate: response_predicate,
    timeout: timeout,
  )
end

#wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil) ⇒ Object



1716
1717
1718
# File 'lib/puppeteer/page.rb', line 1716

def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
  main_frame.wait_for_selector(selector, visible: visible, hidden: hidden, timeout: timeout)
end

#wait_for_timeout(milliseconds) ⇒ Object



1724
1725
1726
# File 'lib/puppeteer/page.rb', line 1724

def wait_for_timeout(milliseconds)
  main_frame.wait_for_timeout(milliseconds)
end

#wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil) ⇒ Object



1733
1734
1735
# File 'lib/puppeteer/page.rb', line 1733

def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
  main_frame.wait_for_xpath(xpath, visible: visible, hidden: hidden, timeout: timeout)
end

#workersObject



429
430
431
# File 'lib/puppeteer/page.rb', line 429

def workers
  @workers.values
end