Class: Ferrum::Node
- Inherits:
-
Object
- Object
- Ferrum::Node
- Defined in:
- lib/ferrum/node.rb
Constant Summary collapse
- MOVING_WAIT_DELAY =
ENV.fetch("FERRUM_NODE_MOVING_WAIT", 0.01).to_f
- MOVING_WAIT_ATTEMPTS =
ENV.fetch("FERRUM_NODE_MOVING_ATTEMPTS", 50).to_i
Instance Attribute Summary collapse
-
#description ⇒ Object
readonly
Returns the value of attribute description.
-
#node_id ⇒ Object
readonly
Returns the value of attribute node_id.
-
#page ⇒ Object
readonly
Returns the value of attribute page.
-
#tag_name ⇒ Object
readonly
Returns the value of attribute tag_name.
-
#target_id ⇒ Object
readonly
Returns the value of attribute target_id.
Instance Method Summary collapse
- #==(other) ⇒ Object
- #at_css(selector) ⇒ Object
- #at_xpath(selector) ⇒ Object
- #attribute(name) ⇒ Object
- #blur ⇒ Object
-
#click(mode: :left, keys: [], offset: {}, delay: 0) ⇒ Object
mode: (:left | :right | :double) keys: (:alt, (:ctrl | :control), (:meta | :command), :shift) offset: { :x, :y, :position (:top | :center) }.
-
#computed_style ⇒ Object
Returns a hash of the computed styles for the node.
- #css(selector) ⇒ Object
- #evaluate(expression) ⇒ Object
- #exists? ⇒ Boolean
- #find_position(x: nil, y: nil, position: :top) ⇒ Object
- #focus ⇒ Object
- #focusable? ⇒ Boolean
- #frame ⇒ Object
- #frame_id ⇒ Object
- #hover ⇒ Object
- #in_viewport?(of: nil) ⇒ Boolean
-
#initialize(frame, target_id, node_id, description) ⇒ Node
constructor
A new instance of Node.
-
#inner_text ⇒ Object
FIXME: clear API for text and inner_text.
- #inspect ⇒ Object
- #moving?(delay: MOVING_WAIT_DELAY) ⇒ Boolean
- #node? ⇒ Boolean
- #property(name) ⇒ Object (also: #[])
- #remove ⇒ Object
- #scroll_into_view ⇒ Object
- #select(*values, by: :value) ⇒ Object
- #select_file(value) ⇒ Object
- #selected ⇒ Object
- #text ⇒ Object
- #type(*keys) ⇒ Object
- #value ⇒ Object
- #wait_for_stop_moving(delay: MOVING_WAIT_DELAY, attempts: MOVING_WAIT_ATTEMPTS) ⇒ Object
- #xpath(selector) ⇒ Object
Constructor Details
#initialize(frame, target_id, node_id, description) ⇒ Node
Returns a new instance of Node.
10 11 12 13 14 15 16 |
# File 'lib/ferrum/node.rb', line 10 def initialize(frame, target_id, node_id, description) @page = frame.page @target_id = target_id @node_id = node_id @description = description @tag_name = description["nodeName"].downcase end |
Instance Attribute Details
#description ⇒ Object (readonly)
Returns the value of attribute description.
8 9 10 |
# File 'lib/ferrum/node.rb', line 8 def description @description end |
#node_id ⇒ Object (readonly)
Returns the value of attribute node_id.
8 9 10 |
# File 'lib/ferrum/node.rb', line 8 def node_id @node_id end |
#page ⇒ Object (readonly)
Returns the value of attribute page.
8 9 10 |
# File 'lib/ferrum/node.rb', line 8 def page @page end |
#tag_name ⇒ Object (readonly)
Returns the value of attribute tag_name.
8 9 10 |
# File 'lib/ferrum/node.rb', line 8 def tag_name @tag_name end |
#target_id ⇒ Object (readonly)
Returns the value of attribute target_id.
8 9 10 |
# File 'lib/ferrum/node.rb', line 8 def target_id @target_id end |
Instance Method Details
#==(other) ⇒ Object
191 192 193 194 195 196 197 198 |
# File 'lib/ferrum/node.rb', line 191 def ==(other) return false unless other.is_a?(Node) # We compare backendNodeId because once nodeId is sent to frontend backend # never returns same nodeId sending 0. In other words frontend is # responsible for keeping track of node ids. target_id == other.target_id && description["backendNodeId"] == other.description["backendNodeId"] end |
#at_css(selector) ⇒ Object
120 121 122 |
# File 'lib/ferrum/node.rb', line 120 def at_css(selector) page.at_css(selector, within: self) end |
#at_xpath(selector) ⇒ Object
116 117 118 |
# File 'lib/ferrum/node.rb', line 116 def at_xpath(selector) page.at_xpath(selector, within: self) end |
#attribute(name) ⇒ Object
150 151 152 |
# File 'lib/ferrum/node.rb', line 150 def attribute(name) evaluate("this.getAttribute('#{name}')") end |
#blur ⇒ Object
55 56 57 |
# File 'lib/ferrum/node.rb', line 55 def blur tap { evaluate("this.blur()") } end |
#click(mode: :left, keys: [], offset: {}, delay: 0) ⇒ Object
mode: (:left | :right | :double) keys: (:alt, (:ctrl | :control), (:meta | :command), :shift) offset: { :x, :y, :position (:top | :center) }
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/ferrum/node.rb', line 66 def click(mode: :left, keys: [], offset: {}, delay: 0) x, y = find_position(**offset) modifiers = page.keyboard.modifiers(keys) case mode when :right page.mouse.move(x: x, y: y) page.mouse.down(button: :right, modifiers: modifiers) sleep(delay) page.mouse.up(button: :right, modifiers: modifiers) when :double page.mouse.move(x: x, y: y) page.mouse.down(modifiers: modifiers, count: 2) sleep(delay) page.mouse.up(modifiers: modifiers, count: 2) when :left page.mouse.click(x: x, y: y, modifiers: modifiers, delay: delay) end self end |
#computed_style ⇒ Object
Returns a hash of the computed styles for the node
215 216 217 218 219 |
# File 'lib/ferrum/node.rb', line 215 def computed_style page .command("CSS.getComputedStyleForNode", nodeId: node_id)["computedStyle"] .each_with_object({}) { |style, memo| memo.merge!(style["name"] => style["value"]) } end |
#css(selector) ⇒ Object
128 129 130 |
# File 'lib/ferrum/node.rb', line 128 def css(selector) page.css(selector, within: self) end |
#evaluate(expression) ⇒ Object
187 188 189 |
# File 'lib/ferrum/node.rb', line 187 def evaluate(expression) page.evaluate_on(node: self, expression: expression) end |
#exists? ⇒ Boolean
225 226 227 228 229 230 |
# File 'lib/ferrum/node.rb', line 225 def exists? page.command("DOM.resolveNode", nodeId: node_id) true rescue Ferrum::NodeNotFoundError false end |
#find_position(x: nil, y: nil, position: :top) ⇒ Object
204 205 206 207 208 209 210 211 212 |
# File 'lib/ferrum/node.rb', line 204 def find_position(x: nil, y: nil, position: :top) points = wait_for_stop_moving.map { |q| to_points(q) }.first get_position(points, x, y, position) rescue CoordinatesNotFoundError x, y = bounding_rect_coordinates raise if x.zero? && y.zero? [x, y] end |
#focus ⇒ Object
30 31 32 |
# File 'lib/ferrum/node.rb', line 30 def focus tap { page.command("DOM.focus", slowmoable: true, nodeId: node_id) } end |
#focusable? ⇒ Boolean
34 35 36 37 38 39 |
# File 'lib/ferrum/node.rb', line 34 def focusable? focus true rescue BrowserError => e e. == "Element is not focusable" ? false : raise end |
#frame ⇒ Object
26 27 28 |
# File 'lib/ferrum/node.rb', line 26 def frame page.frame_by(id: frame_id) end |
#frame_id ⇒ Object
22 23 24 |
# File 'lib/ferrum/node.rb', line 22 def frame_id description["frameId"] end |
#hover ⇒ Object
88 89 90 |
# File 'lib/ferrum/node.rb', line 88 def hover raise NotImplementedError end |
#in_viewport?(of: nil) ⇒ Boolean
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/ferrum/node.rb', line 96 def (of: nil) function = " function(element, scope) {\n const rect = element.getBoundingClientRect();\n const [height, width] = scope\n ? [scope.offsetHeight, scope.offsetWidth]\n : [window.innerHeight, window.innerWidth];\n return rect.top >= 0 &&\n rect.left >= 0 &&\n rect.bottom <= height &&\n rect.right <= width;\n }\n JS\n page.evaluate_func(function, self, of)\nend\n" |
#inner_text ⇒ Object
FIXME: clear API for text and inner_text
137 138 139 |
# File 'lib/ferrum/node.rb', line 137 def inner_text evaluate("this.innerText") end |
#inspect ⇒ Object
200 201 202 |
# File 'lib/ferrum/node.rb', line 200 def inspect %(#<#{self.class} @target_id=#{@target_id.inspect} @node_id=#{@node_id} @description=#{@description.inspect}>) end |
#moving?(delay: MOVING_WAIT_DELAY) ⇒ Boolean
50 51 52 53 |
# File 'lib/ferrum/node.rb', line 50 def moving?(delay: MOVING_WAIT_DELAY) previous, current = content_quads_with(delay: delay) previous == current end |
#node? ⇒ Boolean
18 19 20 |
# File 'lib/ferrum/node.rb', line 18 def node? description["nodeType"] == 1 # nodeType: 3, nodeName: "#text" e.g. end |
#property(name) ⇒ Object Also known as: []
145 146 147 |
# File 'lib/ferrum/node.rb', line 145 def property(name) evaluate("this['#{name}']") end |
#remove ⇒ Object
221 222 223 |
# File 'lib/ferrum/node.rb', line 221 def remove page.command("DOM.removeNode", nodeId: node_id) end |
#scroll_into_view ⇒ Object
92 93 94 |
# File 'lib/ferrum/node.rb', line 92 def scroll_into_view tap { page.command("DOM.scrollIntoViewIfNeeded", nodeId: node_id) } end |
#select(*values, by: :value) ⇒ Object
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/ferrum/node.rb', line 166 def select(*values, by: :value) tap do function = " function(element, values, by) {\n if (element.nodeName.toLowerCase() !== 'select') {\n throw new Error('Element is not a <select> element.');\n }\n const options = Array.from(element.options);\n element.value = undefined;\n for (const option of options) {\n option.selected = values.some((value) => option[by] === value);\n if (option.selected && !element.multiple) break;\n }\n element.dispatchEvent(new Event('input', { bubbles: true }));\n element.dispatchEvent(new Event('change', { bubbles: true }));\n }\n JS\n page.evaluate_func(function, self, values.flatten, by, on: self)\n end\nend\n" |
#select_file(value) ⇒ Object
112 113 114 |
# File 'lib/ferrum/node.rb', line 112 def select_file(value) page.command("DOM.setFileInputFiles", slowmoable: true, nodeId: node_id, files: Array(value)) end |
#selected ⇒ Object
154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/ferrum/node.rb', line 154 def selected function = " function(element) {\n if (element.nodeName.toLowerCase() !== 'select') {\n throw new Error('Element is not a <select> element.');\n }\n return Array.from(element).filter(option => option.selected);\n }\n JS\n page.evaluate_func(function, self, on: self)\nend\n" |
#text ⇒ Object
132 133 134 |
# File 'lib/ferrum/node.rb', line 132 def text evaluate("this.textContent") end |
#type(*keys) ⇒ Object
59 60 61 |
# File 'lib/ferrum/node.rb', line 59 def type(*keys) tap { page.keyboard.type(*keys) } end |
#value ⇒ Object
141 142 143 |
# File 'lib/ferrum/node.rb', line 141 def value evaluate("this.value") end |
#wait_for_stop_moving(delay: MOVING_WAIT_DELAY, attempts: MOVING_WAIT_ATTEMPTS) ⇒ Object
41 42 43 44 45 46 47 48 |
# File 'lib/ferrum/node.rb', line 41 def wait_for_stop_moving(delay: MOVING_WAIT_DELAY, attempts: MOVING_WAIT_ATTEMPTS) Utils::Attempt.with_retry(errors: NodeMovingError, max: attempts, wait: 0) do previous, current = content_quads_with(delay: delay) raise NodeMovingError.new(self, previous, current) if previous != current current end end |
#xpath(selector) ⇒ Object
124 125 126 |
# File 'lib/ferrum/node.rb', line 124 def xpath(selector) page.xpath(selector, within: self) end |