Class: Puppeteer::DOMWorld

Inherits:
Object
  • Object
show all
Defined in:
lib/puppeteer/dom_world.rb

Overview

Defined Under Namespace

Classes: BindingFunction, DetachedError, ElementNotFoundError

Constant Summary collapse

ADD_SCRIPT_URL =
<<~JAVASCRIPT
async (url, type) => {
  const script = document.createElement('script');
  script.src = url;
  if (type)
    script.type = type;
  const promise = new Promise((res, rej) => {
    script.onload = res;
    script.onerror = rej;
  });
  document.head.appendChild(script);
  await promise;
  return script;
}
JAVASCRIPT
ADD_SCRIPT_CONTENT =
<<~JAVASCRIPT
(content, type) => {
  if (type === undefined) type = 'text/javascript';
  const script = document.createElement('script');
  script.type = type;
  script.text = content;
  let error = null;
  script.onerror = e => error = e;
  document.head.appendChild(script);
  if (error)
    throw error;
  return script;
}
JAVASCRIPT
ADD_STYLE_URL =
<<~JAVASCRIPT
  async (url) => {
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = url;
    const promise = new Promise((res, rej) => {
      link.onload = res;
      link.onerror = rej;
    });
    document.head.appendChild(link);
    await promise;
    return link;
  }
JAVASCRIPT
ADD_STYLE_CONTENT =
<<~JAVASCRIPT
  async (content) => {
    const style = document.createElement('style');
    style.type = 'text/css';
    style.appendChild(document.createTextNode(content));
    const promise = new Promise((res, rej) => {
      style.onload = res;
      style.onerror = rej;
    });
    document.head.appendChild(style);
    await promise;
    return style;
  }
JAVASCRIPT

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(frame_manager, frame, timeout_settings) ⇒ DOMWorld

Returns a new instance of DOMWorld.

Parameters:



51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/puppeteer/dom_world.rb', line 51

def initialize(frame_manager, frame, timeout_settings)
  @frame_manager = frame_manager
  @frame = frame
  @timeout_settings = timeout_settings
  @context_promise = resolvable_future
  @wait_tasks = Set.new
  @bound_functions = {}
  @ctx_bindings = Set.new
  @detached = false

  frame_manager.client.on_event('Runtime.bindingCalled', &method(:handle_binding_called))
end

Instance Attribute Details

#frameObject (readonly)

Returns the value of attribute frame.



64
65
66
# File 'lib/puppeteer/dom_world.rb', line 64

def frame
  @frame
end

Instance Method Details

#add_binding_to_context(context, binding_function) ⇒ Object



429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
# File 'lib/puppeteer/dom_world.rb', line 429

def add_binding_to_context(context, binding_function)
  return if @ctx_bindings.include?(binding_identifier(binding_function.name, context))

  expression = binding_function.page_binding_init_string
  begin
    context.client.send_message('Runtime.addBinding',
      name: binding_function.name,
      executionContextName: context.send(:_context_name))
    context.evaluate(expression, 'internal', binding_function.name)
  rescue => err
    # We could have tried to evaluate in a context which was already
    # destroyed. This happens, for example, if the page is navigated while
    # we are trying to add the binding
    allowed = [
      'Execution context was destroyed',
      'Cannot find context with specified id',
    ]
    if allowed.any? { |msg| err.message.include?(msg) }
      # ignore
    else
      raise
    end
  end
  @ctx_bindings << binding_identifier(binding_function.name, context)
end

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

Parameters:

  • url (String?) (defaults to: nil)
  • path (String?) (defaults to: nil)
  • content (String?) (defaults to: nil)
  • type (String?) (defaults to: nil)

Raises:

  • (ArgumentError)


237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/puppeteer/dom_world.rb', line 237

def add_script_tag(url: nil, path: nil, content: nil, type: nil)
  if url
    begin
      return execution_context.
        evaluate_handle(ADD_SCRIPT_URL, url, type || '').
        as_element
    rescue Puppeteer::ExecutionContext::EvaluationError # for Chrome
      raise "Loading script from #{url} failed"
    rescue Puppeteer::Connection::ProtocolError # for Firefox
      raise "Loading script from #{url} failed"
    end
  end

  if path
    contents = File.read(path)
    contents += "//# sourceURL=#{path.gsub(/\n/, '')}"
    return execution_context.
      evaluate_handle(ADD_SCRIPT_CONTENT, contents, type || '').
      as_element
  end

  if content
    return execution_context.
      evaluate_handle(ADD_SCRIPT_CONTENT, content, type || '').
      as_element
  end

  raise ArgumentError.new('Provide an object with a `url`, `path` or `content` property')
end

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

Parameters:

  • url (String?) (defaults to: nil)
  • path (String?) (defaults to: nil)
  • content (String?) (defaults to: nil)

Raises:

  • (ArgumentError)


301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/puppeteer/dom_world.rb', line 301

def add_style_tag(url: nil, path: nil, content: nil)
  if url
    begin
      return execution_context.evaluate_handle(ADD_STYLE_URL, url).as_element
    rescue Puppeteer::ExecutionContext::EvaluationError # for Chrome
      raise "Loading style from #{url} failed"
    rescue Puppeteer::Connection::ProtocolError # for Firefox
      raise "Loading style from #{url} failed"
    end
  end

  if path
    contents = File.read(path)
    contents += "/*# sourceURL=#{path.gsub(/\n/, '')}*/"
    return execution_context.evaluate_handle(ADD_STYLE_CONTENT, contents).as_element
  end

  if content
    return execution_context.evaluate_handle(ADD_STYLE_CONTENT, content).as_element
  end

  raise ArgumentError.new('Provide an object with a `url`, `path` or `content` property')
end

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

Parameters:

  • selector (String)
  • delay (Number) (defaults to: nil)
  • button (String) (defaults to: nil)

    “left”|“right”|“middle”

  • click_count (Number) (defaults to: nil)


365
366
367
368
369
# File 'lib/puppeteer/dom_world.rb', line 365

def click(selector, delay: nil, button: nil, click_count: nil)
  handle = query_selector(selector) or raise ElementNotFoundError.new(selector)
  handle.click(delay: delay, button: button, click_count: click_count)
  handle.dispose
end

#contentString

Returns:

  • (String)


191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/puppeteer/dom_world.rb', line 191

def content
  evaluate(<<-JAVASCRIPT)
  () => {
    let retVal = '';
    if (document.doctype)
      retVal = new XMLSerializer().serializeToString(document.doctype);
    if (document.documentElement)
      retVal += document.documentElement.outerHTML;
    return retVal;
  }
  JAVASCRIPT
end

#context=(context) ⇒ Object

Parameters:



77
78
79
80
81
82
83
84
85
86
87
# File 'lib/puppeteer/dom_world.rb', line 77

def context=(context)
  if context
    @ctx_bindings.clear
    unless @context_promise.resolved?
      @context_promise.fulfill(context)
    end
    @wait_tasks.each(&:async_rerun)
  else
    raise ArgumentError.new("context should now be nil. Use #delete_context for clearing document.")
  end
end

#delete_context(execution_context_id) ⇒ Object



89
90
91
92
# File 'lib/puppeteer/dom_world.rb', line 89

def delete_context(execution_context_id)
  @document = nil
  @context_promise = resolvable_future
end

#detachObject



98
99
100
101
102
103
# File 'lib/puppeteer/dom_world.rb', line 98

def detach
  @detached = true
  @wait_tasks.each do |wait_task|
    wait_task.terminate(Puppeteer::WaitTask::TerminatedError.new('waitForFunction failed: frame got detached.'))
  end
end

#eval_on_selector(selector, page_function, *args) ⇒ !Promise<(!Object|undefined)> Also known as: Seval

`$eval()` in JavaScript.

Parameters:

  • selector (string)
  • pageFunction (Function|string)
  • args (!Array<*>)

Returns:

  • (!Promise<(!Object|undefined)>)


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

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

#eval_on_selector_all(selector, page_function, *args) ⇒ !Promise<(!Object|undefined)> Also known as: SSeval

`$$eval()` in JavaScript.

Parameters:

  • selector (string)
  • pageFunction (Function|string)
  • args (!Array<*>)

Returns:

  • (!Promise<(!Object|undefined)>)


177
178
179
# File 'lib/puppeteer/dom_world.rb', line 177

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

#evaluate(page_function, *args) ⇒ !Promise<*>

Parameters:

  • pageFunction (Function|string)
  • args (!Array<*>)

Returns:

  • (!Promise<*>)


125
126
127
# File 'lib/puppeteer/dom_world.rb', line 125

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

#evaluate_handle(page_function, *args) ⇒ !Promise<!Puppeteer.JSHandle>

Parameters:

  • pageFunction (Function|string)
  • args (!Array<*>)

Returns:



118
119
120
# File 'lib/puppeteer/dom_world.rb', line 118

def evaluate_handle(page_function, *args)
  execution_context.evaluate_handle(page_function, *args)
end

#execution_context!Promise<!Puppeteer.ExecutionContext>

Returns:



108
109
110
111
112
113
# File 'lib/puppeteer/dom_world.rb', line 108

def execution_context
  if @detached
    raise DetachedError.new("Execution Context is not available in detached frame \"#{@frame.url}\" (are you trying to evaluate?)")
  end
  @context_promise.value!
end

#focus(selector) ⇒ Object

Parameters:

  • selector (String)


372
373
374
375
376
# File 'lib/puppeteer/dom_world.rb', line 372

def focus(selector)
  handle = query_selector(selector) or raise ElementNotFoundError.new(selector)
  handle.focus
  handle.dispose
end

#has_context?Boolean

Returns:

  • (Boolean)


94
95
96
# File 'lib/puppeteer/dom_world.rb', line 94

def has_context?
  @context_promise.resolved?
end

#query_selector(selector) ⇒ !Promise<?Puppeteer.ElementHandle> Also known as: S

`$()` in JavaScript.

Parameters:

  • selector (string)

Returns:



132
133
134
# File 'lib/puppeteer/dom_world.rb', line 132

def query_selector(selector)
  document.query_selector(selector)
end

#query_selector_all(selector) ⇒ !Promise<!Array<!Puppeteer.ElementHandle>> Also known as: SS

`$$()` in JavaScript.

Parameters:

  • selector (string)

Returns:



185
186
187
# File 'lib/puppeteer/dom_world.rb', line 185

def query_selector_all(selector)
  document.query_selector_all(selector)
end

#select(selector, *values) ⇒ Array<String>

Parameters:

  • selector (String)

Returns:

  • (Array<String>)


390
391
392
393
394
395
396
# File 'lib/puppeteer/dom_world.rb', line 390

def select(selector, *values)
  handle = query_selector(selector) or raise ElementNotFoundError.new(selector)
  result = handle.select(*values)
  handle.dispose

  result
end

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

Parameters:

  • html (String)
  • timeout (Integer) (defaults to: nil)
  • wait_until (String|Array<String>) (defaults to: nil)


207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/puppeteer/dom_world.rb', line 207

def set_content(html, timeout: nil, wait_until: nil)
  option_wait_until = [wait_until || 'load'].flatten
  option_timeout = timeout || @timeout_settings.navigation_timeout

  # We rely upon the fact that document.open() will reset frame lifecycle with "init"
  # lifecycle event. @see https://crrev.com/608658
  js = <<-JAVASCRIPT
  (html) => {
    document.open();
    document.write(html);
    document.close();
  }
  JAVASCRIPT
  evaluate(js, html)

  watcher = Puppeteer::LifecycleWatcher.new(@frame_manager, @frame, option_wait_until, option_timeout)
  begin
    await_any(
      watcher.timeout_or_termination_promise,
      watcher.lifecycle_promise,
    )
  ensure
    watcher.dispose
  end
end

#Sx(expression) ⇒ !Promise<!Array<!Puppeteer.ElementHandle>>

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

Parameters:

  • expression (string)

Returns:



158
159
160
# File 'lib/puppeteer/dom_world.rb', line 158

def Sx(expression)
  document.Sx(expression)
end

#tap(selector) ⇒ Object

Parameters:

  • selector (String)


399
400
401
402
403
# File 'lib/puppeteer/dom_world.rb', line 399

def tap(selector)
  handle = query_selector(selector) or raise ElementNotFoundError.new(selector)
  handle.tap
  handle.dispose
end

#titleString

Returns:

  • (String)


597
598
599
# File 'lib/puppeteer/dom_world.rb', line 597

def title
  evaluate('() => document.title')
end

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

Parameters:

  • selector (String)
  • text (String)
  • delay (Number) (defaults to: nil)


408
409
410
411
412
# File 'lib/puppeteer/dom_world.rb', line 408

def type_text(selector, text, delay: nil)
  handle = query_selector(selector) or raise ElementNotFoundError.new(selector)
  handle.type_text(text, delay: delay)
  handle.dispose
end

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

Parameters:

  • page_function (String)
  • args (Array) (defaults to: [])
  • polling (Integer|String) (defaults to: nil)
  • timeout (Integer) (defaults to: nil)

Returns:



581
582
583
584
585
586
587
588
589
590
591
592
593
# File 'lib/puppeteer/dom_world.rb', line 581

def wait_for_function(page_function, args: [], polling: nil, timeout: nil)
  option_polling = polling || 'raf'
  option_timeout = timeout || @timeout_settings.timeout

  Puppeteer::WaitTask.new(
    dom_world: self,
    predicate_body: page_function,
    title: 'function',
    polling: option_polling,
    timeout: option_timeout,
    args: args,
  ).await_promise
end

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

Parameters:

  • selector (String)
  • visible (Boolean) (defaults to: nil)

    Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.

  • hidden (Boolean) (defaults to: nil)

    Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.

  • timeout (Integer) (defaults to: nil)


418
419
420
421
422
# File 'lib/puppeteer/dom_world.rb', line 418

def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
  # call wait_for_selector_in_page with custom query selector.
  query_selector_manager = Puppeteer::QueryHandlerManager.instance
  query_selector_manager.detect_query_handler(selector).wait_for(self, visible: visible, hidden: hidden, timeout: timeout)
end

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

Parameters:

  • xpath (String)
  • visible (Boolean) (defaults to: nil)

    Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.

  • hidden (Boolean) (defaults to: nil)

    Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.

  • timeout (Integer) (defaults to: nil)


539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
# File 'lib/puppeteer/dom_world.rb', line 539

def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
  option_wait_for_visible = visible || false
  option_wait_for_hidden = hidden || false
  option_timeout = timeout || @timeout_settings.timeout

  polling =
    if option_wait_for_visible || option_wait_for_hidden
      'raf'
    else
      'mutation'
    end
  title = "XPath #{xpath}#{option_wait_for_hidden ? 'to be hidden' : ''}"

  xpath_predicate = make_predicate_string(
    predicate_arg_def: '(selector, waitForVisible, waitForHidden)',
    predicate_body: <<~JAVASCRIPT
      const node = document.evaluate(selector, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
      return checkWaitForOptions(node, waitForVisible, waitForHidden);
    JAVASCRIPT
  )

  wait_task = Puppeteer::WaitTask.new(
    dom_world: self,
    predicate_body: xpath_predicate,
    title: title,
    polling: polling,
    timeout: option_timeout,
    args: [xpath, option_wait_for_visible, option_wait_for_hidden],
  )
  handle = wait_task.await_promise
  unless handle.as_element
    handle.dispose
    return nil
  end
  handle.as_element
end