Class: Lightpanda::Capybara::Node

Inherits:
Capybara::Driver::Node
  • Object
show all
Defined in:
lib/lightpanda/capybara/node.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(driver, native, _index) ⇒ Node



8
9
10
11
12
# File 'lib/lightpanda/capybara/node.rb', line 8

def initialize(driver, native, _index)
  super(driver, native)

  @selector_info = native # { selector: "h1", index: 0 }
end

Instance Attribute Details

#selector_infoObject (readonly)

Returns the value of attribute selector_info.



6
7
8
# File 'lib/lightpanda/capybara/node.rb', line 6

def selector_info
  @selector_info
end

Instance Method Details

#==(other) ⇒ Object



208
209
210
# File 'lib/lightpanda/capybara/node.rb', line 208

def ==(other)
  other.is_a?(self.class) && selector_info == other.selector_info
end

#[](name) ⇒ Object



22
23
24
# File 'lib/lightpanda/capybara/node.rb', line 22

def [](name)
  evaluate_on("this.getAttribute(#{name.to_s.inspect})")
end

#checked?Boolean



144
145
146
# File 'lib/lightpanda/capybara/node.rb', line 144

def checked?
  evaluate_on("this.checked")
end

#click(_keys = [], **_options) ⇒ Object



43
44
45
# File 'lib/lightpanda/capybara/node.rb', line 43

def click(_keys = [], **_options)
  evaluate_on("this.click()")
end

#disabled?Boolean



152
153
154
# File 'lib/lightpanda/capybara/node.rb', line 152

def disabled?
  evaluate_on("this.disabled")
end

#double_click(_keys = [], **_options) ⇒ Object



53
54
55
56
57
# File 'lib/lightpanda/capybara/node.rb', line 53

def double_click(_keys = [], **_options)
  evaluate_on(<<~JS)
    this.dispatchEvent(new MouseEvent('dblclick', {bubbles: true, cancelable: true}))
  JS
end

#find_css(selector) ⇒ Object



196
197
198
199
200
201
202
203
204
205
206
# File 'lib/lightpanda/capybara/node.rb', line 196

def find_css(selector)
  count = evaluate_on("this.querySelectorAll(#{selector.inspect}).length")

  return [] if count.nil? || count.zero?

  (0...count).map do |idx|
    child_selector = "#{element_selector} #{selector}"

    Node.new(driver, { selector: child_selector, index: idx }, idx)
  end
end

#find_xpath(selector) ⇒ Object



192
193
194
# File 'lib/lightpanda/capybara/node.rb', line 192

def find_xpath(selector)
  driver.find_xpath(selector)
end

#hoverObject



59
60
61
62
63
# File 'lib/lightpanda/capybara/node.rb', line 59

def hover
  evaluate_on(<<~JS)
    this.dispatchEvent(new MouseEvent('mouseover', {bubbles: true, cancelable: true}))
  JS
end

#multiple?Boolean



160
161
162
# File 'lib/lightpanda/capybara/node.rb', line 160

def multiple?
  evaluate_on("this.multiple")
end

#pathObject



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/lightpanda/capybara/node.rb', line 164

def path
  evaluate_on(<<~JS)
    (function(el) {
      if (!el) return '';
      var path = [];

      while (el && el.nodeType === Node.ELEMENT_NODE) {
        var selector = el.nodeName.toLowerCase();
        if (el.id) {
          selector += '#' + el.id;
          path.unshift(selector);
          break;
        } else {
          var sibling = el;
          var nth = 1;
          while (sibling = sibling.previousElementSibling) {
            if (sibling.nodeName.toLowerCase() === selector) nth++;
          }
          if (nth > 1) selector += ':nth-of-type(' + nth + ')';
        }
        path.unshift(selector);
        el = el.parentNode;
      }
      return path.join(' > ');
    })(this)
  JS
end

#readonly?Boolean



156
157
158
# File 'lib/lightpanda/capybara/node.rb', line 156

def readonly?
  evaluate_on("this.readOnly")
end

#right_click(_keys = [], **_options) ⇒ Object



47
48
49
50
51
# File 'lib/lightpanda/capybara/node.rb', line 47

def right_click(_keys = [], **_options)
  evaluate_on(<<~JS)
    this.dispatchEvent(new MouseEvent('contextmenu', {bubbles: true, cancelable: true}))
  JS
end

#select_optionObject



87
88
89
90
91
92
93
# File 'lib/lightpanda/capybara/node.rb', line 87

def select_option
  evaluate_on(<<~JS)
    this.selected = true;
    var event = new Event('change', {bubbles: true});
    this.parentElement.dispatchEvent(event);
  JS
end

#selected?Boolean



148
149
150
# File 'lib/lightpanda/capybara/node.rb', line 148

def selected?
  evaluate_on("this.selected")
end

#send_keys(*args) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
# File 'lib/lightpanda/capybara/node.rb', line 109

def send_keys(*args)
  args.each do |key|
    next unless key.is_a?(String)

    evaluate_on(<<~JS)
      this.focus();
      this.value += #{key.inspect};
      this.dispatchEvent(new Event('input', {bubbles: true}));
    JS
  end
end

#set(value, **_options) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/lightpanda/capybara/node.rb', line 65

def set(value, **_options)
  if tag_name == "input"
    type = self["type"]
    case type
    when "checkbox", "radio"
      if value
        evaluate_on("this.checked = true; this.dispatchEvent(new Event('change', {bubbles: true}))")
      else
        evaluate_on("this.checked = false; this.dispatchEvent(new Event('change', {bubbles: true}))")
      end
    when "file"
      raise NotImplementedError, "File inputs need special handling via CDP. File uploads not yet supported"
    else
      set_text_value(value)
    end
  elsif tag_name == "textarea"
    set_text_value(value)
  elsif self["contenteditable"]
    evaluate_on("this.innerHTML = #{value.to_s.inspect}")
  end
end

#style(styles) ⇒ Object



37
38
39
40
41
# File 'lib/lightpanda/capybara/node.rb', line 37

def style(styles)
  styles.each_with_object({}) do |style, result|
    result[style] = evaluate_on("window.getComputedStyle(this).#{style}")
  end
end

#tag_nameObject



121
122
123
# File 'lib/lightpanda/capybara/node.rb', line 121

def tag_name
  evaluate_on("this.tagName.toLowerCase()")
end

#textObject



14
15
16
# File 'lib/lightpanda/capybara/node.rb', line 14

def text
  evaluate_on("this.textContent")
end

#unselect_optionObject



95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/lightpanda/capybara/node.rb', line 95

def unselect_option
  select = find_xpath("./ancestor::select")[0]

  if select && !select["multiple"]
    raise ::Capybara::UnselectNotAllowed, "Cannot unselect option from single select"
  end

  evaluate_on(<<~JS)
    this.selected = false;
    var event = new Event('change', {bubbles: true});
    this.parentElement.dispatchEvent(event);
  JS
end

#valueObject



26
27
28
29
30
31
32
33
34
35
# File 'lib/lightpanda/capybara/node.rb', line 26

def value
  evaluate_on(<<~JS)
    (function(el) {
      if (el.tagName === 'SELECT' && el.multiple) {
        return Array.from(el.selectedOptions).map(o => o.value);
      }
      return el.value;
    })(this)
  JS
end

#visible?Boolean



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/lightpanda/capybara/node.rb', line 125

def visible?
  selector = @selector_info[:selector]
  index = @selector_info[:index]

  js = <<~JS
    (function() {
      var el = document.querySelectorAll(#{selector.inspect})[#{index}];
      if (!el) return false;
      var style = window.getComputedStyle(el);
      var isVisible = style.display !== 'none' &&
             style.visibility !== 'hidden' &&
             el.offsetParent !== null;
      return isVisible;
    })()
  JS

  driver.browser.evaluate(js)
end

#visible_textObject



18
19
20
# File 'lib/lightpanda/capybara/node.rb', line 18

def visible_text
  evaluate_on("this.innerText")
end