Class: Obxcura::Node

Inherits:
Object
  • Object
show all
Defined in:
lib/obxcura/node.rb

Overview

A live handle to a DOM node inside a Frame. Backed by a CDP remote object, so reads run as function calls bound to that object and reflect the current DOM. Returned by Frame::DOM#at_css / #css.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(frame, object_id) ⇒ Node

Returns a new instance of Node.

Parameters:

  • frame (Obxcura::Frame)

    the frame the node lives in.

  • object_id (String)

    the CDP objectId of the node.



15
16
17
18
# File 'lib/obxcura/node.rb', line 15

def initialize(frame, object_id)
  @frame = frame
  @remote_object_id = object_id
end

Instance Attribute Details

#remote_object_idString (readonly)

remote_object_id (not object_id) so it doesn't shadow Ruby's Object#object_id, which every object relies on for identity.

Returns:

  • (String)

    the CDP remote objectId backing this node. Named



11
12
13
# File 'lib/obxcura/node.rb', line 11

def remote_object_id
  @remote_object_id
end

Instance Method Details

#[](name) ⇒ String? Also known as: attribute

Read an attribute value.

Parameters:

  • name (String)

    the attribute name.

Returns:

  • (String, nil)

    the attribute value, or nil if absent.



42
43
44
# File 'lib/obxcura/node.rb', line 42

def [](name)
  @frame.call_on(remote_object_id, "function(name) { return this.getAttribute(name); }", [ name ])
end

#at_css(selector) ⇒ Obxcura::Node?

First descendant matching a CSS selector, searched within this node.

Parameters:

  • selector (String)

    a CSS selector.

Returns:



34
35
36
# File 'lib/obxcura/node.rb', line 34

def at_css(selector)
  @frame.to_node(@frame.call_on(remote_object_id, "function(s) { return this.querySelector(s); }", [ selector ]))
end

#focusself

Give this node keyboard focus.

Returns:

  • (self)


50
51
52
# File 'lib/obxcura/node.rb', line 50

def focus
  tap { @frame.page.command("DOM.focus", objectId: remote_object_id) }
end

#outer_htmlString

Returns the node's serialized outer HTML.

Returns:

  • (String)

    the node's serialized outer HTML.



107
108
109
# File 'lib/obxcura/node.rb', line 107

def outer_html
  @frame.call_on(remote_object_id, "function() { return this.outerHTML; }")
end

#submitself

Submit this node's form. Works whether the node is the <form> itself or a control inside one (resolved via .form / closest <form>).

Always goes through requestSubmit, which follows the interactive path: constraint validation runs and a cancelable submit event fires. There is no fallback to submit() — as of Obscura 0.1.11 the two genuinely differ, with submit() bypassing the submit event entirely, so falling back would quietly skip both validation and any listener a caller registered.

Returns:

  • (self)

Raises:



93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/obxcura/node.rb', line 93

def submit
  result = @frame.call_on(remote_object_id, <<~JS)
    function() {
      const form = this.tagName === "FORM" ? this : (this.form || this.closest("form"));
      if (!form) return { error: "node is not a form and has no ancestor form" };
      form.requestSubmit();
    }
  JS
  raise ProtocolError, result["error"] if result.is_a?(Hash) && result["error"]

  self
end

#textString

Returns the visible text of the node and its descendants.

Returns:

  • (String)

    the visible text of the node and its descendants.



21
22
23
# File 'lib/obxcura/node.rb', line 21

def text
  @frame.call_on(remote_object_id, "function() { return this.textContent; }")
end

#type(*keys) ⇒ self

Type text into this node with real key events, one character at a time — Obscura implements Input.dispatchKeyEvent, so the browser itself performs the insertion and fires keydown, input and keyup the way a keyboard would. Focuses the node first, since key events go to the active element. Appends, matching keyboard behaviour when the node already has text.

Note change does not fire per keystroke — real browsers only fire it on blur. The previous implementation synthesized both by assigning value directly; listeners that relied on that change need to react to input.

Input.insertText is still unimplemented in Obscura 0.1.11, so there is no bulk-insert fast path: this costs two CDP round trips per character. Fine for form fields (100 chars ≈ 40ms), but it is genuinely slower than the old single-assignment approach — 500 chars ≈ 0.11s — so don't use it to stuff large text through. Assign value via Frame::Runtime#call_on for that, and accept that no key events fire.

Parameters:

  • keys (Array<String>)

    text fragments to type (joined).

Returns:

  • (self)


73
74
75
76
77
78
79
80
# File 'lib/obxcura/node.rb', line 73

def type(*keys)
  focus
  keys.join.each_char do |char|
    dispatch_key("keyDown", char, text: char)
    dispatch_key("keyUp", char)
  end
  self
end

#valueString?

Returns the node's value (form controls), or nil.

Returns:

  • (String, nil)

    the node's value (form controls), or nil.



26
27
28
# File 'lib/obxcura/node.rb', line 26

def value
  @frame.call_on(remote_object_id, "function() { return this.value; }")
end