obxcura 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/lib/obxcura/client.rb +1 -0
- data/lib/obxcura/frame/dom.rb +1 -2
- data/lib/obxcura/node.rb +67 -6
- data/lib/obxcura/page.rb +1 -1
- data/lib/obxcura/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d0870aca291d2df42d4229d960e6382150034ee7a7294a1b75829392d0e13188
|
|
4
|
+
data.tar.gz: 9daacc30f29cfd803cab0135173ca1466f75f27e077ccba09f578a3b4edcc8dc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f1cb4c51f185f9898115c38743f5b83964442be4b14381f7cdb45b1bb75da1698ef8b918c053a6f9df4c5b9fed7058614e12a6bed1baf43a44813c16396e26e7
|
|
7
|
+
data.tar.gz: 510b721af05b438f4f72791a3e78c49e97be680912f21fd1ca59064e59f72191f27cc3a6058d86d68b97445c15d7ca65091a1bd51de0bc50da2bfb51c4567efc
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.2.0] - 2026-07-20
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `Node` form actions: `value`/`attribute`, `at_css` (scoped `querySelector`),
|
|
8
|
+
`focus`, `type` (sets value and fires `input`/`change`, since Obscura has no
|
|
9
|
+
Input domain), and `submit` (raising `ProtocolError` for a node with no form).
|
|
10
|
+
- `Frame#to_node` is now public so `Node#at_css` reuses the same node
|
|
11
|
+
resolution.
|
|
12
|
+
|
|
3
13
|
## [0.1.1] - 2026-07-14
|
|
4
14
|
|
|
5
15
|
### Fixed
|
data/lib/obxcura/client.rb
CHANGED
data/lib/obxcura/frame/dom.rb
CHANGED
|
@@ -46,9 +46,8 @@ module Obxcura
|
|
|
46
46
|
.filter_map { |stub| to_node(stub) }
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
private
|
|
50
|
-
|
|
51
49
|
# Resolve Obscura's serialized node stub ({"_nid"=>N,...}) into a Node.
|
|
50
|
+
# Public so {Node#at_css} can reuse the same resolution path.
|
|
52
51
|
#
|
|
53
52
|
# @param stub [Hash, nil] the serialized node from a querySelector call.
|
|
54
53
|
# @return [Obxcura::Node, nil] a live node handle, or nil for a blank match.
|
data/lib/obxcura/node.rb
CHANGED
|
@@ -5,19 +5,34 @@ module Obxcura
|
|
|
5
5
|
# reads run as function calls bound to that object and reflect the current DOM.
|
|
6
6
|
# Returned by Frame::DOM#at_css / #css.
|
|
7
7
|
class Node
|
|
8
|
-
# @return [String] the CDP remote objectId backing this node.
|
|
9
|
-
|
|
8
|
+
# @return [String] the CDP remote objectId backing this node. Named
|
|
9
|
+
# `remote_object_id` (not `object_id`) so it doesn't shadow Ruby's
|
|
10
|
+
# `Object#object_id`, which every object relies on for identity.
|
|
11
|
+
attr_reader :remote_object_id
|
|
10
12
|
|
|
11
13
|
# @param frame [Obxcura::Frame] the frame the node lives in.
|
|
12
14
|
# @param object_id [String] the CDP objectId of the node.
|
|
13
15
|
def initialize(frame, object_id)
|
|
14
16
|
@frame = frame
|
|
15
|
-
@
|
|
17
|
+
@remote_object_id = object_id
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
# @return [String] the visible text of the node and its descendants.
|
|
19
21
|
def text
|
|
20
|
-
@frame.call_on(
|
|
22
|
+
@frame.call_on(remote_object_id, "function() { return this.textContent; }")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @return [String, nil] the node's `value` (form controls), or nil.
|
|
26
|
+
def value
|
|
27
|
+
@frame.call_on(remote_object_id, "function() { return this.value; }")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# First descendant matching a CSS selector, searched within this node.
|
|
31
|
+
#
|
|
32
|
+
# @param selector [String] a CSS selector.
|
|
33
|
+
# @return [Obxcura::Node, nil] the matching node, or nil if none.
|
|
34
|
+
def at_css(selector)
|
|
35
|
+
@frame.to_node(@frame.call_on(remote_object_id, "function(s) { return this.querySelector(s); }", [ selector ]))
|
|
21
36
|
end
|
|
22
37
|
|
|
23
38
|
# Read an attribute value.
|
|
@@ -25,12 +40,58 @@ module Obxcura
|
|
|
25
40
|
# @param name [String] the attribute name.
|
|
26
41
|
# @return [String, nil] the attribute value, or nil if absent.
|
|
27
42
|
def [](name)
|
|
28
|
-
@frame.call_on(
|
|
43
|
+
@frame.call_on(remote_object_id, "function(name) { return this.getAttribute(name); }", [ name ])
|
|
44
|
+
end
|
|
45
|
+
alias attribute []
|
|
46
|
+
|
|
47
|
+
# Give this node keyboard focus.
|
|
48
|
+
#
|
|
49
|
+
# @return [self]
|
|
50
|
+
def focus
|
|
51
|
+
tap { @frame.page.command("DOM.focus", objectId: remote_object_id) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Type text into this node. Obscura has no Input domain (Input.insertText /
|
|
55
|
+
# dispatchKeyEvent are unimplemented), so this sets `value` in page context
|
|
56
|
+
# and fires the `input`/`change` events real typing would, letting listeners
|
|
57
|
+
# react. Appends, matching keyboard behaviour when the node already has text.
|
|
58
|
+
#
|
|
59
|
+
# @param keys [Array<String>] text fragments to type (joined).
|
|
60
|
+
# @return [self]
|
|
61
|
+
def type(*keys)
|
|
62
|
+
tap { @frame.call_on(remote_object_id, <<~JS, [ keys.join ])
|
|
63
|
+
function(text) {
|
|
64
|
+
this.value = (this.value || "") + text;
|
|
65
|
+
this.dispatchEvent(new Event("input", { bubbles: true }));
|
|
66
|
+
this.dispatchEvent(new Event("change", { bubbles: true }));
|
|
67
|
+
}
|
|
68
|
+
JS
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Submit this node's form. Works whether the node is the `<form>` itself or a
|
|
73
|
+
# control inside one (resolved via `.form` / closest `<form>`). Prefers
|
|
74
|
+
# `requestSubmit` (runs validation and fires the submit event) and falls back
|
|
75
|
+
# to `submit` where it's unavailable.
|
|
76
|
+
#
|
|
77
|
+
# @return [self]
|
|
78
|
+
# @raise [Obxcura::ProtocolError] if the node isn't a form or inside one.
|
|
79
|
+
def submit
|
|
80
|
+
result = @frame.call_on(remote_object_id, <<~JS)
|
|
81
|
+
function() {
|
|
82
|
+
const form = this.tagName === "FORM" ? this : (this.form || this.closest("form"));
|
|
83
|
+
if (!form) return { error: "node is not a form and has no ancestor form" };
|
|
84
|
+
form.requestSubmit ? form.requestSubmit() : form.submit();
|
|
85
|
+
}
|
|
86
|
+
JS
|
|
87
|
+
raise ProtocolError, result["error"] if result.is_a?(Hash) && result["error"]
|
|
88
|
+
|
|
89
|
+
self
|
|
29
90
|
end
|
|
30
91
|
|
|
31
92
|
# @return [String] the node's serialized outer HTML.
|
|
32
93
|
def outer_html
|
|
33
|
-
@frame.call_on(
|
|
94
|
+
@frame.call_on(remote_object_id, "function() { return this.outerHTML; }")
|
|
34
95
|
end
|
|
35
96
|
end
|
|
36
97
|
end
|
data/lib/obxcura/page.rb
CHANGED
|
@@ -67,7 +67,7 @@ module Obxcura
|
|
|
67
67
|
# @return [Obxcura::Page, nil] the page, removed from the browser.
|
|
68
68
|
def close
|
|
69
69
|
@client.unsubscribe(@session_id)
|
|
70
|
-
@client.command("Network.clearBrowserCookies", { targetId: @target_id })
|
|
70
|
+
# @client.command("Network.clearBrowserCookies", { targetId: @target_id })
|
|
71
71
|
@client.command("Target.closeTarget", { targetId: @target_id })
|
|
72
72
|
rescue ProtocolError
|
|
73
73
|
# Target already gone — nothing to do.
|
data/lib/obxcura/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: obxcura
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- memoxmrdl
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: websocket-driver
|