class Ferrum::Node

Constants

MOVING_WAIT_ATTEMPTS
MOVING_WAIT_DELAY

Attributes

description[R]
node_id[R]
page[R]
tag_name[R]
target_id[R]

Public Class Methods

new(frame, target_id, node_id, description) click to toggle source
# File lib/ferrum/node.rb, line 10
def initialize(frame, target_id, node_id, description)
  @page = frame.page
  @target_id = target_id
  @node_id, @description = node_id, description
  @tag_name = description["nodeName"].downcase
end

Public Instance Methods

==(other) click to toggle source
# File lib/ferrum/node.rb, line 134
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) click to toggle source
# File lib/ferrum/node.rb, line 97
def at_css(selector)
  page.at_css(selector, within: self)
end
at_xpath(selector) click to toggle source
# File lib/ferrum/node.rb, line 93
def at_xpath(selector)
  page.at_xpath(selector, within: self)
end
attribute(name) click to toggle source
# File lib/ferrum/node.rb, line 126
def attribute(name)
  evaluate("this.getAttribute('#{name}')")
end
blur() click to toggle source
# File lib/ferrum/node.rb, line 53
def blur
  tap { evaluate("this.blur()") }
end
click(mode: :left, keys: [], offset: {}, delay: 0) click to toggle source

mode: (:left | :right | :double) keys: (:alt, (:ctrl | :control), (:meta | :command), :shift) offset: { :x, :y, :position (:top | :center) }

# File lib/ferrum/node.rb, line 64
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)
    page.mouse.up(modifiers: modifiers, count: 2)
  when :left
    page.mouse.click(x: x, y: y, modifiers: modifiers, delay: delay)
  end

  self
end
css(selector) click to toggle source
# File lib/ferrum/node.rb, line 105
def css(selector)
  page.css(selector, within: self)
end
evaluate(expression) click to toggle source
# File lib/ferrum/node.rb, line 130
def evaluate(expression)
  page.evaluate_on(node: self, expression: expression)
end
find_position(x: nil, y: nil, position: :top) click to toggle source
# File lib/ferrum/node.rb, line 146
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 = get_bounding_rect_coordinates
  raise if x == 0 && y == 0
  [x, y]
end
focus() click to toggle source
# File lib/ferrum/node.rb, line 29
def focus
  tap { page.command("DOM.focus", slowmoable: true, nodeId: node_id) }
end
focusable?() click to toggle source
# File lib/ferrum/node.rb, line 33
def focusable?
  focus
  true
rescue BrowserError => e
  e.message == "Element is not focusable" ? false : raise
end
frame() click to toggle source
# File lib/ferrum/node.rb, line 25
def frame
  page.frame_by(id: frame_id)
end
frame_id() click to toggle source
# File lib/ferrum/node.rb, line 21
def frame_id
  description["frameId"]
end
hover() click to toggle source
# File lib/ferrum/node.rb, line 85
def hover
  raise NotImplementedError
end
inner_text() click to toggle source

FIXME: clear API for text and inner_text

# File lib/ferrum/node.rb, line 114
def inner_text
  evaluate("this.innerText")
end
inspect() click to toggle source
# File lib/ferrum/node.rb, line 142
def inspect
  %(#<#{self.class} @target_id=#{@target_id.inspect} @node_id=#{@node_id} @description=#{@description.inspect}>)
end
moving?(delay: MOVING_WAIT_DELAY) click to toggle source
# File lib/ferrum/node.rb, line 48
def moving?(delay: MOVING_WAIT_DELAY)
  previous, current = get_content_quads_with(delay: delay)
  previous == current
end
node?() click to toggle source
# File lib/ferrum/node.rb, line 17
def node?
  description["nodeType"] == 1 # nodeType: 3, nodeName: "#text" e.g.
end
property(name) click to toggle source
# File lib/ferrum/node.rb, line 122
def property(name)
  evaluate("this['#{name}']")
end
select_file(value) click to toggle source
# File lib/ferrum/node.rb, line 89
def select_file(value)
  page.command("DOM.setFileInputFiles", slowmoable: true, nodeId: node_id, files: Array(value))
end
text() click to toggle source
# File lib/ferrum/node.rb, line 109
def text
  evaluate("this.textContent")
end
type(*keys) click to toggle source
# File lib/ferrum/node.rb, line 57
def type(*keys)
  tap { page.keyboard.type(*keys) }
end
value() click to toggle source
# File lib/ferrum/node.rb, line 118
def value
  evaluate("this.value")
end
wait_for_stop_moving(delay: MOVING_WAIT_DELAY, attempts: MOVING_WAIT_ATTEMPTS) click to toggle source
# File lib/ferrum/node.rb, line 40
def wait_for_stop_moving(delay: MOVING_WAIT_DELAY, attempts: MOVING_WAIT_ATTEMPTS)
  Ferrum.with_attempts(errors: NodeMovingError, max: attempts, wait: 0) do
    previous, current = get_content_quads_with(delay: delay)
    raise NodeMovingError.new(self, previous, current) if previous != current
    current
  end
end
xpath(selector) click to toggle source
# File lib/ferrum/node.rb, line 101
def xpath(selector)
  page.xpath(selector, within: self)
end

Private Instance Methods

get_bounding_rect_coordinates() click to toggle source
# File lib/ferrum/node.rb, line 157
    def get_bounding_rect_coordinates
      evaluate <<~JS
        [this.getBoundingClientRect().left + window.pageXOffset + (this.offsetWidth / 2),
         this.getBoundingClientRect().top + window.pageYOffset + (this.offsetHeight / 2)]
      JS
    end
get_content_quads() click to toggle source
# File lib/ferrum/node.rb, line 164
def get_content_quads
  quads = page.command("DOM.getContentQuads", nodeId: node_id)["quads"]
  raise CoordinatesNotFoundError, "Node is either not visible or not an HTMLElement" if quads.size == 0
  quads
end
get_content_quads_with(delay: MOVING_WAIT_DELAY) click to toggle source
# File lib/ferrum/node.rb, line 170
def get_content_quads_with(delay: MOVING_WAIT_DELAY)
  previous = get_content_quads
  sleep(delay)
  current = get_content_quads
  [previous, current]
end
get_position(points, offset_x, offset_y, position) click to toggle source
# File lib/ferrum/node.rb, line 177
def get_position(points, offset_x, offset_y, position)
  x = y = nil

  if offset_x && offset_y && position == :top
    point = points.first
    x = point[:x] + offset_x.to_i
    y = point[:y] + offset_y.to_i
  else
    x, y = points.inject([0, 0]) do |memo, point|
      [memo[0] + point[:x],
       memo[1] + point[:y]]
    end

    x = x / 4
    y = y / 4
  end

  if offset_x && offset_y && position == :center
    x = x + offset_x.to_i
    y = y + offset_y.to_i
  end

  [x, y]
end
to_points(quad) click to toggle source
# File lib/ferrum/node.rb, line 202
def to_points(quad)
  [{x: quad[0], y: quad[1]},
   {x: quad[2], y: quad[3]},
   {x: quad[4], y: quad[5]},
   {x: quad[6], y: quad[7]}]
end