class Capybara::Apparition::Node
Constants
- CLEAR_ELEMENT_JS
- CURRENT_NODE_SELECTED_JS
- DELETE_TEXT_JS
- DISPATCH_EVENT_JS
- ELEMENT_DISABLED_JS
- ELEMENT_PROP_OR_ATTR_JS
- ELEMENT_VISIBLE_TEXT_JS
- EVENTS
- FIND_CSS_JS
- FIND_XPATH_JS
- GET_ATTRIBUTES_JS
- GET_CLIENT_RECT_JS
- GET_PATH_JS
JS snippets
- GET_STYLES_JS
- GET_VALUE_JS
- SELECT_OPTION_JS
- UNSELECT_OPTION_JS
- VISIBLE_JS
if an area element, check visibility of relevant image
Attributes
page_id[R]
Public Class Methods
new(driver, page, remote_object, initial_cache)
click to toggle source
Calls superclass method
# File lib/capybara/apparition/node.rb, line 14 def initialize(driver, page, remote_object, initial_cache) super(driver, self, initial_cache) @page = page @remote_object = remote_object end
Public Instance Methods
==(other)
click to toggle source
# File lib/capybara/apparition/node.rb, line 272 def ==(other) evaluate_on('el => this == el', objectId: other.id) rescue ObsoleteNode false end
[](name)
click to toggle source
# File lib/capybara/apparition/node.rb, line 99 def [](name) # Although the attribute matters, the property is consistent. Return that in # preference to the attribute for links and images. evaluate_on ELEMENT_PROP_OR_ATTR_JS, value: name end
all_text()
click to toggle source
# File lib/capybara/apparition/node.rb, line 50 def all_text text = evaluate_on('() => this.textContent') text.to_s.gsub(/[\u200b\u200e\u200f]/, '') .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ') .gsub(/\A[[:space:]&&[^\u00a0]]+/, '') .gsub(/[[:space:]&&[^\u00a0]]+\z/, '') .tr("\u00a0", ' ') end
attribute(name)
click to toggle source
# File lib/capybara/apparition/node.rb, line 91 def attribute(name) if %w[checked selected].include?(name.to_s) property(name) else evaluate_on('name => this.getAttribute(name)', value: name) end end
attributes()
click to toggle source
# File lib/capybara/apparition/node.rb, line 105 def attributes evaluate_on GET_ATTRIBUTES_JS end
checked?()
click to toggle source
# File lib/capybara/apparition/node.rb, line 188 def checked? self[:checked] end
click(keys = [], button: 'left', count: 1, delay: 0, **options)
click to toggle source
# File lib/capybara/apparition/node.rb, line 200 def click(keys = [], button: 'left', count: 1, delay: 0, **options) pos = element_click_pos(**options) raise ::Capybara::Apparition::MouseEventImpossible.new(self, 'args' => ['click']) if pos.nil? test = mouse_event_test(**pos) raise ::Capybara::Apparition::MouseEventImpossible.new(self, 'args' => ['click']) if test.nil? unless options[:x] && options[:y] raise ::Capybara::Apparition::MouseEventFailed.new(self, 'args' => ['click', test.selector, pos]) unless test.success end @page.mouse.click_at(**pos.merge(button: button, count: count, modifiers: keys, delay: delay)) if ENV['DEBUG'] begin new_pos = element_click_pos(**options) puts "Element moved from #{pos} to #{new_pos}" unless pos == new_pos rescue WrongWorld # rubocop:disable Lint/SuppressedException end end # Wait a short time to see if click triggers page load sleep 0.05 @page.wait_for_loaded(allow_obsolete: true) end
disabled?()
click to toggle source
# File lib/capybara/apparition/node.rb, line 196 def disabled? evaluate_on ELEMENT_DISABLED_JS end
double_click(keys = [], **options)
click to toggle source
# File lib/capybara/apparition/node.rb, line 228 def double_click(keys = [], **options) click(keys, count: 2, **options) end
element_click_pos(x: nil, y: nil, offset: nil, **)
click to toggle source
# File lib/capybara/apparition/node.rb, line 288 def element_click_pos(x: nil, y: nil, offset: nil, **) if x && y if offset == :center visible_center else visible_top_left end.tap do |p| p[:x] += x p[:y] += y end else visible_center end end
find(method, selector)
click to toggle source
# File lib/capybara/apparition/node.rb, line 30 def find(method, selector) js = method == :css ? FIND_CSS_JS : FIND_XPATH_JS evaluate_on(js, value: selector).map do |r_o| tag_name = r_o['description'].split(/[.#]/, 2)[0] Capybara::Apparition::Node.new(driver, @page, r_o['objectId'], tag_name: tag_name) end rescue ::Capybara::Apparition::BrowserError => e raise unless /is not a valid (XPath expression|selector)/.match? e.name raise Capybara::Apparition::InvalidSelector, 'args' => [method, selector] end
find_css(selector)
click to toggle source
# File lib/capybara/apparition/node.rb, line 46 def find_css(selector) find :css, selector end
find_xpath(selector)
click to toggle source
# File lib/capybara/apparition/node.rb, line 42 def find_xpath(selector) find :xpath, selector end
hover()
click to toggle source
# File lib/capybara/apparition/node.rb, line 232 def hover pos = visible_center raise ::Capybara::Apparition::MouseEventImpossible.new(self, 'args' => ['hover']) if pos.nil? @page.mouse.move_to(**pos) end
id()
click to toggle source
# File lib/capybara/apparition/node.rb, line 22 def id @remote_object end
inner_html()
click to toggle source
capybara-webkit method
# File lib/capybara/apparition/node.rb, line 76 def inner_html self[:innerHTML] end
inner_html=(value)
click to toggle source
capybara-webkit method
# File lib/capybara/apparition/node.rb, line 81 def inner_html=(value) driver.execute_script <<~JS, self, value arguments[0].innerHTML = arguments[1] JS end
obscured?(**)
click to toggle source
# File lib/capybara/apparition/node.rb, line 173 def obscured?(**) pos = visible_center(allow_scroll: false) return true if pos.nil? hit_node = @page.element_from_point(**pos) return true if hit_node.nil? begin return evaluate_on('el => !this.contains(el)', objectId: hit_node['objectId']) rescue WrongWorld # rubocop:disable Lint/SuppressedException end true end
parents()
click to toggle source
# File lib/capybara/apparition/node.rb, line 26 def parents find('xpath', 'ancestor::*').reverse end
path()
click to toggle source
# File lib/capybara/apparition/node.rb, line 284 def path evaluate_on GET_PATH_JS end
property(name)
click to toggle source
# File lib/capybara/apparition/node.rb, line 87 def property(name) evaluate_on('name => this[name]', value: name) end
rect()
click to toggle source
# File lib/capybara/apparition/node.rb, line 387 def rect evaluate_on GET_CLIENT_RECT_JS end
right_click(keys = [], **options)
click to toggle source
# File lib/capybara/apparition/node.rb, line 224 def right_click(keys = [], **options) click(keys, button: 'right', **options) end
scroll_by(x, y)
click to toggle source
# File lib/capybara/apparition/node.rb, line 391 def scroll_by(x, y) evaluate_on <<~JS, { value: x }, value: y (x, y) => this.scrollBy(x,y) JS self end
scroll_to(element, location, position = nil)
click to toggle source
# File lib/capybara/apparition/node.rb, line 398 def scroll_to(element, location, position = nil) if element.is_a? Capybara::Apparition::Node scroll_element_to_location(element, location) elsif location.is_a? Symbol scroll_to_location(location) else scroll_to_coords(*position) end self end
select_option()
click to toggle source
# File lib/capybara/apparition/node.rb, line 151 def select_option return false if disabled? evaluate_on SELECT_OPTION_JS true end
selected?()
click to toggle source
# File lib/capybara/apparition/node.rb, line 192 def selected? !!self[:selected] end
send_keys(*keys, delay: 0, **opts)
click to toggle source
# File lib/capybara/apparition/node.rb, line 278 def send_keys(*keys, delay: 0, **opts) click unless evaluate_on CURRENT_NODE_SELECTED_JS _send_keys(*keys, delay: delay, **opts) end
Also aliased as: send_key
set(value, **options)
click to toggle source
# File lib/capybara/apparition/node.rb, line 117 def set(value, **options) if tag_name == 'input' case self[:type] when 'radio' click when 'checkbox' click if value != checked? when 'file' files = value.respond_to?(:to_ary) ? value.to_ary.map(&:to_s) : value.to_s set_files(files) when 'date' set_date(value) when 'time' set_time(value) when 'datetime-local' set_datetime_local(value) when 'color' set_color(value) when 'range' set_range(value) else set_text(value.to_s, **{ delay: 0 }.merge(options)) end elsif tag_name == 'textarea' set_text(value.to_s) elsif tag_name == 'select' warn "Setting the value of a select element via 'set' is deprecated, please use 'select' or 'select_option'." evaluate_on '()=>{ this.value = arguments[0] }', value: value.to_s elsif self[:isContentEditable] delete_text send_keys(value.to_s, delay: options.fetch(:delay, 0)) end end
style(styles)
click to toggle source
# File lib/capybara/apparition/node.rb, line 113 def style(styles) evaluate_on GET_STYLES_JS, value: styles end
submit()
click to toggle source
# File lib/capybara/apparition/node.rb, line 268 def submit evaluate_on '()=>{ this.submit() }' end
tag_name()
click to toggle source
# File lib/capybara/apparition/node.rb, line 165 def tag_name @tag_name ||= evaluate_on('() => this.tagName').downcase end
text()
click to toggle source
capybara-webkit method
# File lib/capybara/apparition/node.rb, line 70 def text warn 'Node#text is deprecated, please use Node#visible_text instead' visible_text end
top_left()
click to toggle source
# File lib/capybara/apparition/node.rb, line 380 def top_left result = evaluate_on GET_CLIENT_RECT_JS return nil if result.nil? { x: result['x'], y: result['y'] } end
trigger(name, event_type = nil, **options)
click to toggle source
# File lib/capybara/apparition/node.rb, line 260 def trigger(name, event_type = nil, **options) raise ArgumentError, 'Unknown event' unless EVENTS.key?(name.to_sym) || event_type event_type, opts = *EVENTS[name.to_sym], {} if event_type.nil? evaluate_on DISPATCH_EVENT_JS, { value: event_type }, { value: name }, value: opts.merge(options) end
unselect_option()
click to toggle source
# File lib/capybara/apparition/node.rb, line 158 def unselect_option return false if disabled? evaluate_on(UNSELECT_OPTION_JS) || raise(Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.') end
value()
click to toggle source
# File lib/capybara/apparition/node.rb, line 109 def value evaluate_on GET_VALUE_JS end
visible?()
click to toggle source
# File lib/capybara/apparition/node.rb, line 169 def visible? evaluate_on VISIBLE_JS end
visible_center(allow_scroll: true)
click to toggle source
# File lib/capybara/apparition/node.rb, line 337 def visible_center(allow_scroll: true) rect = in_view_client_rect(allow_scroll: allow_scroll) return nil if rect.nil? frame_offset = @page.current_frame_offset if rect['width'].zero? || rect['height'].zero? return nil unless tag_name == 'area' map = find('xpath', 'ancestor::map').first img = find('xpath', "//img[@usemap='##{map[:name]}']").first return nil unless img.visible? img_pos = img.top_left coords = self[:coords].split(',').map(&:to_i) offset_pos = case self[:shape] when 'rect' { x: (coords[0] + coords[2]) / 2, y: (coords[1] + coords[2]) / 2 } when 'circle' { x: coords[0], y: coords[1] } when 'poly' raise 'TODO: Poly not implemented' else raise 'Unknown Shape' end { x: img_pos[:x] + offset_pos[:x] + frame_offset[:x], y: img_pos[:y] + offset_pos[:y] + frame_offset[:y] } else lm = @page.command('Page.getLayoutMetrics') x_extents = [rect['left'], rect['right']].minmax y_extents = [rect['top'], rect['bottom']].minmax x_extents[1] = [x_extents[1], lm['layoutViewport']['clientWidth']].min y_extents[1] = [y_extents[1], lm['layoutViewport']['clientHeight']].min { x: (x_extents.sum / 2) + frame_offset[:x], y: (y_extents.sum / 2) + frame_offset[:y] } end end
visible_text()
click to toggle source
# File lib/capybara/apparition/node.rb, line 59 def visible_text return '' unless visible? text = evaluate_on ELEMENT_VISIBLE_TEXT_JS text.to_s.gsub(/\A[[:space:]&&[^\u00a0]]+/, '') .gsub(/[[:space:]&&[^\u00a0]]+\z/, '') .gsub(/\n+/, "\n") .tr("\u00a0", ' ') end
visible_top_left()
click to toggle source
# File lib/capybara/apparition/node.rb, line 303 def visible_top_left rect = in_view_client_rect return nil if rect.nil? frame_offset = @page.current_frame_offset if rect['width'].zero? || rect['height'].zero? return nil unless tag_name == 'area' map = find('xpath', 'ancestor::map').first img = find('xpath', "//img[@usemap='##{map[:name]}']").first return nil unless img.visible? img_pos = img.top_left coords = self[:coords].split(',').map(&:to_i) offset_pos = case self[:shape] when 'rect' { x: coords[0], y: coords[1] } when 'circle' { x: coords[0], y: coords[1] } when 'poly' raise 'TODO: Poly not implemented' else raise 'Unknown Shape' end { x: img_pos[:x] + offset_pos[:x] + frame_offset[:x], y: img_pos[:y] + offset_pos[:y] + frame_offset[:y] } else { x: rect['left'] + frame_offset[:x], y: rect['top'] + frame_offset[:y] } end end
Protected Instance Methods
evaluate_on(page_function, *args)
click to toggle source
# File lib/capybara/apparition/node.rb, line 411 def evaluate_on(page_function, *args) obsolete_checked_function = <<~JS function(){ if (!this.ownerDocument.contains(this)) { throw 'ObsoleteNode' }; return (#{page_function.strip}).apply(this, arguments); } JS response = @page.command('Runtime.callFunctionOn', functionDeclaration: obsolete_checked_function, objectId: id, returnByValue: false, awaitPromise: true, arguments: args) process_response(response) end
scroll_if_needed()
click to toggle source
# File lib/capybara/apparition/node.rb, line 427 def scroll_if_needed driver.execute_script <<~JS, self arguments[0].scrollIntoViewIfNeeded({behavior: 'instant', block: 'center', inline: 'center'}) JS end
Private Instance Methods
_send_keys(*keys, delay: 0, **_opts)
click to toggle source
# File lib/capybara/apparition/node.rb, line 461 def _send_keys(*keys, delay: 0, **_opts) @page.keyboard.type(keys, delay: delay) end
delete_text()
click to toggle source
# File lib/capybara/apparition/node.rb, line 622 def delete_text evaluate_on DELETE_TEXT_JS end
filter_text(text)
click to toggle source
# File lib/capybara/apparition/node.rb, line 472 def filter_text(text) text.to_s.gsub(/[[:space:]]+/, ' ').strip end
focus()
click to toggle source
# File lib/capybara/apparition/node.rb, line 435 def focus @page.command('DOM.focus', objectId: id) end
in_view_client_rect(allow_scroll: true)
click to toggle source
# File lib/capybara/apparition/node.rb, line 465 def in_view_client_rect(allow_scroll: true) evaluate_on('() => this.scrollIntoViewIfNeeded()') if allow_scroll result = evaluate_on GET_CLIENT_RECT_JS result = result['model'] if result && result['model'] result end
keys_to_send(value, clear)
click to toggle source
# File lib/capybara/apparition/node.rb, line 439 def keys_to_send(value, clear) case clear when :backspace # Clear field by sending the correct number of backspace keys. [:end] + ([:backspace] * self.value.to_s.length) + [value] when :none [value] when Array clear << value else # Clear field by JavaScript assignment of the value property. # Script can change a readonly element which user input cannot, so # don't execute if readonly. driver.execute_script <<~JS, self if (!arguments[0].readOnly) { arguments[0].value = '' } JS [value] end end
mouse_event_test(x:, y:)
click to toggle source
# File lib/capybara/apparition/node.rb, line 570 def mouse_event_test(x:, y:) r_o = @page.element_from_point(x: x, y: y) return nil unless r_o && r_o['objectId'] tag_name = r_o['description'].split(/[.#]/, 2)[0] hit_node = Capybara::Apparition::Node.new(driver, @page, r_o['objectId'], tag_name: tag_name) result = begin evaluate_on(<<~JS, objectId: hit_node.id) (hit_node) => { if ((hit_node == this) || this.contains(hit_node)) return { status: 'success' }; return { status: 'failure' }; } JS rescue WrongWorld { 'status': 'failure' } end OpenStruct.new(success: result['status'] == 'success', selector: r_o['description']) end
mouse_event_test?(x:, y:)
click to toggle source
# File lib/capybara/apparition/node.rb, line 566 def mouse_event_test?(x:, y:) mouse_event_test(x: x, y: y).success end
process_response(response)
click to toggle source
# File lib/capybara/apparition/node.rb, line 476 def process_response(response) exception_details = response['exceptionDetails'] if exception_details && (exception = exception_details['exception']) case exception['className'] when 'DOMException' raise ::Capybara::Apparition::BrowserError.new('name' => exception['description'], 'args' => nil) else raise ::Capybara::Apparition::ObsoleteNode.new(self, '') if exception['value'] == 'ObsoleteNode' puts "Unknown Exception: #{exception['value']}" end raise exception_details end DevToolsProtocol::RemoteObject.new(@page, response['result'] || response['object']).value end
scroll_element_to_location(element, location)
click to toggle source
# File lib/capybara/apparition/node.rb, line 590 def scroll_element_to_location(element, location) scroll_opts = case location when :top 'true' when :bottom 'false' when :center "{behavior: 'instant', block: 'center'}" else raise ArgumentError, "Invalid scroll_to location: #{location}" end element.evaluate_on "() => this.scrollIntoView(#{scroll_opts})" end
scroll_to_coords(x, y)
click to toggle source
# File lib/capybara/apparition/node.rb, line 616 def scroll_to_coords(x, y) evaluate_on <<~JS, { value: x }, value: y (x,y) => this.scrollTo(x,y) JS end
scroll_to_location(location)
click to toggle source
# File lib/capybara/apparition/node.rb, line 604 def scroll_to_location(location) scroll_y = case location when :top '0' when :bottom 'this.scrollHeight' when :center '(this.scrollHeight - this.clientHeight)/2' end evaluate_on "() => this.scrollTo(0, #{scroll_y})" end
set_color(value)
click to toggle source
# File lib/capybara/apparition/node.rb, line 543 def set_color(value) update_value_js(value.to_s) end
set_date(value)
click to toggle source
# File lib/capybara/apparition/node.rb, line 515 def set_date(value) value = SettableValue.new(value) unless value.dateable? # click(x:5, y:10) # debug return set_text(value) end # TODO: this would be better if locale can be detected and correct keystrokes sent update_value_js(value.to_date_str) end
set_datetime_local(value)
click to toggle source
# File lib/capybara/apparition/node.rb, line 535 def set_datetime_local(value) value = SettableValue.new(value) return set_text(value) unless value.timeable? # TODO: this would be better if locale can be detected and correct keystrokes sent update_value_js(value.to_datetime_str) end
set_files(files)
click to toggle source
# File lib/capybara/apparition/node.rb, line 511 def set_files(files) @page.command('DOM.setFileInputFiles', files: Array(files), objectId: id) end
set_range(value)
click to toggle source
# File lib/capybara/apparition/node.rb, line 547 def set_range(value) update_value_js(value.to_s) end
set_text(value, clear: nil, delay: 0, rapid: nil, **_unused)
click to toggle source
# File lib/capybara/apparition/node.rb, line 493 def set_text(value, clear: nil, delay: 0, rapid: nil, **_unused) value = value.to_s if value.empty? && clear.nil? evaluate_on CLEAR_ELEMENT_JS else focus if (rapid && (value.length >= 6)) || ((value.length > 30) && rapid != false) _send_keys(*keys_to_send(value[0..2], clear), delay: delay) driver.execute_script <<~JS, self, value[0...-3] arguments[0].value = arguments[1] JS _send_keys(*keys_to_send(value[-3..-1], :none), delay: delay) else _send_keys(*keys_to_send(value, clear), delay: delay) end end end
set_time(value)
click to toggle source
# File lib/capybara/apparition/node.rb, line 527 def set_time(value) value = SettableValue.new(value) return set_text(value) unless value.timeable? # TODO: this would be better if locale can be detected and correct keystrokes sent update_value_js(value.to_time_str) end
update_value_js(value)
click to toggle source
# File lib/capybara/apparition/node.rb, line 551 def update_value_js(value) evaluate_on(<<~JS, value: value) value => { if (document.activeElement !== this){ this.focus(); } if (this.value != value) { this.value = value; this.dispatchEvent(new InputEvent('input')); this.dispatchEvent(new Event('change', { bubbles: true })); } } JS end