capybara 3.32.2
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 +7 -0
- data/.yardopts +1 -0
- data/History.md +1813 -0
- data/License.txt +22 -0
- data/README.md +1099 -0
- data/lib/capybara.rb +511 -0
- data/lib/capybara/config.rb +94 -0
- data/lib/capybara/cucumber.rb +27 -0
- data/lib/capybara/driver/base.rb +170 -0
- data/lib/capybara/driver/node.rb +139 -0
- data/lib/capybara/dsl.rb +65 -0
- data/lib/capybara/helpers.rb +108 -0
- data/lib/capybara/minitest.rb +386 -0
- data/lib/capybara/minitest/spec.rb +264 -0
- data/lib/capybara/node/actions.rb +420 -0
- data/lib/capybara/node/base.rb +143 -0
- data/lib/capybara/node/document.rb +48 -0
- data/lib/capybara/node/document_matchers.rb +67 -0
- data/lib/capybara/node/element.rb +606 -0
- data/lib/capybara/node/finders.rb +325 -0
- data/lib/capybara/node/matchers.rb +883 -0
- data/lib/capybara/node/simple.rb +208 -0
- data/lib/capybara/queries/ancestor_query.rb +27 -0
- data/lib/capybara/queries/base_query.rb +106 -0
- data/lib/capybara/queries/current_path_query.rb +51 -0
- data/lib/capybara/queries/match_query.rb +26 -0
- data/lib/capybara/queries/selector_query.rb +710 -0
- data/lib/capybara/queries/sibling_query.rb +26 -0
- data/lib/capybara/queries/style_query.rb +45 -0
- data/lib/capybara/queries/text_query.rb +110 -0
- data/lib/capybara/queries/title_query.rb +39 -0
- data/lib/capybara/rack_test/browser.rb +140 -0
- data/lib/capybara/rack_test/css_handlers.rb +13 -0
- data/lib/capybara/rack_test/driver.rb +109 -0
- data/lib/capybara/rack_test/errors.rb +6 -0
- data/lib/capybara/rack_test/form.rb +127 -0
- data/lib/capybara/rack_test/node.rb +325 -0
- data/lib/capybara/rails.rb +16 -0
- data/lib/capybara/registrations/drivers.rb +36 -0
- data/lib/capybara/registrations/patches/puma_ssl.rb +27 -0
- data/lib/capybara/registrations/servers.rb +44 -0
- data/lib/capybara/result.rb +190 -0
- data/lib/capybara/rspec.rb +29 -0
- data/lib/capybara/rspec/features.rb +23 -0
- data/lib/capybara/rspec/matcher_proxies.rb +82 -0
- data/lib/capybara/rspec/matchers.rb +201 -0
- data/lib/capybara/rspec/matchers/base.rb +111 -0
- data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
- data/lib/capybara/rspec/matchers/compound.rb +88 -0
- data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
- data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
- data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
- data/lib/capybara/rspec/matchers/have_selector.rb +77 -0
- data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
- data/lib/capybara/rspec/matchers/have_text.rb +33 -0
- data/lib/capybara/rspec/matchers/have_title.rb +29 -0
- data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
- data/lib/capybara/rspec/matchers/match_style.rb +38 -0
- data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
- data/lib/capybara/selector.rb +233 -0
- data/lib/capybara/selector/builders/css_builder.rb +84 -0
- data/lib/capybara/selector/builders/xpath_builder.rb +69 -0
- data/lib/capybara/selector/css.rb +102 -0
- data/lib/capybara/selector/definition.rb +276 -0
- data/lib/capybara/selector/definition/button.rb +51 -0
- data/lib/capybara/selector/definition/checkbox.rb +26 -0
- data/lib/capybara/selector/definition/css.rb +10 -0
- data/lib/capybara/selector/definition/datalist_input.rb +35 -0
- data/lib/capybara/selector/definition/datalist_option.rb +25 -0
- data/lib/capybara/selector/definition/element.rb +27 -0
- data/lib/capybara/selector/definition/field.rb +40 -0
- data/lib/capybara/selector/definition/fieldset.rb +14 -0
- data/lib/capybara/selector/definition/file_field.rb +13 -0
- data/lib/capybara/selector/definition/fillable_field.rb +33 -0
- data/lib/capybara/selector/definition/frame.rb +17 -0
- data/lib/capybara/selector/definition/id.rb +6 -0
- data/lib/capybara/selector/definition/label.rb +62 -0
- data/lib/capybara/selector/definition/link.rb +46 -0
- data/lib/capybara/selector/definition/link_or_button.rb +16 -0
- data/lib/capybara/selector/definition/option.rb +27 -0
- data/lib/capybara/selector/definition/radio_button.rb +27 -0
- data/lib/capybara/selector/definition/select.rb +81 -0
- data/lib/capybara/selector/definition/table.rb +109 -0
- data/lib/capybara/selector/definition/table_row.rb +21 -0
- data/lib/capybara/selector/definition/xpath.rb +5 -0
- data/lib/capybara/selector/filter.rb +5 -0
- data/lib/capybara/selector/filter_set.rb +124 -0
- data/lib/capybara/selector/filters/base.rb +77 -0
- data/lib/capybara/selector/filters/expression_filter.rb +22 -0
- data/lib/capybara/selector/filters/locator_filter.rb +29 -0
- data/lib/capybara/selector/filters/node_filter.rb +31 -0
- data/lib/capybara/selector/regexp_disassembler.rb +214 -0
- data/lib/capybara/selector/selector.rb +147 -0
- data/lib/capybara/selector/xpath_extensions.rb +17 -0
- data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
- data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
- data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
- data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
- data/lib/capybara/selenium/driver.rb +496 -0
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +119 -0
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +126 -0
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +89 -0
- data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
- data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
- data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
- data/lib/capybara/selenium/extensions/find.rb +110 -0
- data/lib/capybara/selenium/extensions/html5_drag.rb +228 -0
- data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
- data/lib/capybara/selenium/extensions/scroll.rb +78 -0
- data/lib/capybara/selenium/logger_suppressor.rb +34 -0
- data/lib/capybara/selenium/node.rb +610 -0
- data/lib/capybara/selenium/nodes/chrome_node.rb +119 -0
- data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
- data/lib/capybara/selenium/nodes/firefox_node.rb +131 -0
- data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
- data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
- data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
- data/lib/capybara/selenium/patches/atoms.rb +18 -0
- data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
- data/lib/capybara/selenium/patches/logs.rb +47 -0
- data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
- data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
- data/lib/capybara/server.rb +126 -0
- data/lib/capybara/server/animation_disabler.rb +58 -0
- data/lib/capybara/server/checker.rb +44 -0
- data/lib/capybara/server/middleware.rb +69 -0
- data/lib/capybara/session.rb +942 -0
- data/lib/capybara/session/config.rb +124 -0
- data/lib/capybara/session/matchers.rb +87 -0
- data/lib/capybara/spec/fixtures/another_test_file.txt +1 -0
- data/lib/capybara/spec/fixtures/capybara.jpg +3 -0
- data/lib/capybara/spec/fixtures/no_extension +1 -0
- data/lib/capybara/spec/fixtures/test_file.txt +1 -0
- data/lib/capybara/spec/public/jquery-ui.js +13 -0
- data/lib/capybara/spec/public/jquery.js +5 -0
- data/lib/capybara/spec/public/offset.js +6 -0
- data/lib/capybara/spec/public/test.js +268 -0
- data/lib/capybara/spec/session/accept_alert_spec.rb +81 -0
- data/lib/capybara/spec/session/accept_confirm_spec.rb +32 -0
- data/lib/capybara/spec/session/accept_prompt_spec.rb +78 -0
- data/lib/capybara/spec/session/all_spec.rb +278 -0
- data/lib/capybara/spec/session/ancestor_spec.rb +88 -0
- data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +140 -0
- data/lib/capybara/spec/session/assert_current_path_spec.rb +75 -0
- data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
- data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
- data/lib/capybara/spec/session/assert_text_spec.rb +258 -0
- data/lib/capybara/spec/session/assert_title_spec.rb +93 -0
- data/lib/capybara/spec/session/attach_file_spec.rb +216 -0
- data/lib/capybara/spec/session/body_spec.rb +23 -0
- data/lib/capybara/spec/session/check_spec.rb +235 -0
- data/lib/capybara/spec/session/choose_spec.rb +121 -0
- data/lib/capybara/spec/session/click_button_spec.rb +506 -0
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +129 -0
- data/lib/capybara/spec/session/click_link_spec.rb +229 -0
- data/lib/capybara/spec/session/current_scope_spec.rb +31 -0
- data/lib/capybara/spec/session/current_url_spec.rb +115 -0
- data/lib/capybara/spec/session/dismiss_confirm_spec.rb +36 -0
- data/lib/capybara/spec/session/dismiss_prompt_spec.rb +21 -0
- data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +38 -0
- data/lib/capybara/spec/session/element/match_css_spec.rb +31 -0
- data/lib/capybara/spec/session/element/match_xpath_spec.rb +25 -0
- data/lib/capybara/spec/session/element/matches_selector_spec.rb +120 -0
- data/lib/capybara/spec/session/evaluate_async_script_spec.rb +23 -0
- data/lib/capybara/spec/session/evaluate_script_spec.rb +49 -0
- data/lib/capybara/spec/session/execute_script_spec.rb +28 -0
- data/lib/capybara/spec/session/fill_in_spec.rb +286 -0
- data/lib/capybara/spec/session/find_button_spec.rb +74 -0
- data/lib/capybara/spec/session/find_by_id_spec.rb +33 -0
- data/lib/capybara/spec/session/find_field_spec.rb +113 -0
- data/lib/capybara/spec/session/find_link_spec.rb +70 -0
- data/lib/capybara/spec/session/find_spec.rb +531 -0
- data/lib/capybara/spec/session/first_spec.rb +156 -0
- data/lib/capybara/spec/session/frame/frame_title_spec.rb +23 -0
- data/lib/capybara/spec/session/frame/frame_url_spec.rb +23 -0
- data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +116 -0
- data/lib/capybara/spec/session/frame/within_frame_spec.rb +112 -0
- data/lib/capybara/spec/session/go_back_spec.rb +12 -0
- data/lib/capybara/spec/session/go_forward_spec.rb +14 -0
- data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
- data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
- data/lib/capybara/spec/session/has_any_selectors_spec.rb +25 -0
- data/lib/capybara/spec/session/has_button_spec.rb +69 -0
- data/lib/capybara/spec/session/has_css_spec.rb +374 -0
- data/lib/capybara/spec/session/has_current_path_spec.rb +138 -0
- data/lib/capybara/spec/session/has_field_spec.rb +349 -0
- data/lib/capybara/spec/session/has_link_spec.rb +39 -0
- data/lib/capybara/spec/session/has_none_selectors_spec.rb +78 -0
- data/lib/capybara/spec/session/has_select_spec.rb +310 -0
- data/lib/capybara/spec/session/has_selector_spec.rb +202 -0
- data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
- data/lib/capybara/spec/session/has_table_spec.rb +198 -0
- data/lib/capybara/spec/session/has_text_spec.rb +394 -0
- data/lib/capybara/spec/session/has_title_spec.rb +71 -0
- data/lib/capybara/spec/session/has_xpath_spec.rb +149 -0
- data/lib/capybara/spec/session/headers_spec.rb +8 -0
- data/lib/capybara/spec/session/html_spec.rb +47 -0
- data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
- data/lib/capybara/spec/session/node_spec.rb +1292 -0
- data/lib/capybara/spec/session/node_wrapper_spec.rb +39 -0
- data/lib/capybara/spec/session/refresh_spec.rb +33 -0
- data/lib/capybara/spec/session/reset_session_spec.rb +148 -0
- data/lib/capybara/spec/session/response_code_spec.rb +8 -0
- data/lib/capybara/spec/session/save_and_open_page_spec.rb +21 -0
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +43 -0
- data/lib/capybara/spec/session/save_page_spec.rb +110 -0
- data/lib/capybara/spec/session/save_screenshot_spec.rb +55 -0
- data/lib/capybara/spec/session/screenshot_spec.rb +18 -0
- data/lib/capybara/spec/session/scroll_spec.rb +117 -0
- data/lib/capybara/spec/session/select_spec.rb +229 -0
- data/lib/capybara/spec/session/selectors_spec.rb +98 -0
- data/lib/capybara/spec/session/sibling_spec.rb +52 -0
- data/lib/capybara/spec/session/source_spec.rb +0 -0
- data/lib/capybara/spec/session/text_spec.rb +74 -0
- data/lib/capybara/spec/session/title_spec.rb +29 -0
- data/lib/capybara/spec/session/uncheck_spec.rb +100 -0
- data/lib/capybara/spec/session/unselect_spec.rb +116 -0
- data/lib/capybara/spec/session/visit_spec.rb +204 -0
- data/lib/capybara/spec/session/window/become_closed_spec.rb +89 -0
- data/lib/capybara/spec/session/window/current_window_spec.rb +28 -0
- data/lib/capybara/spec/session/window/open_new_window_spec.rb +31 -0
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +132 -0
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +99 -0
- data/lib/capybara/spec/session/window/window_spec.rb +203 -0
- data/lib/capybara/spec/session/window/windows_spec.rb +34 -0
- data/lib/capybara/spec/session/window/within_window_spec.rb +157 -0
- data/lib/capybara/spec/session/within_spec.rb +199 -0
- data/lib/capybara/spec/spec_helper.rb +134 -0
- data/lib/capybara/spec/test_app.rb +226 -0
- data/lib/capybara/spec/views/animated.erb +49 -0
- data/lib/capybara/spec/views/buttons.erb +5 -0
- data/lib/capybara/spec/views/fieldsets.erb +30 -0
- data/lib/capybara/spec/views/form.erb +685 -0
- data/lib/capybara/spec/views/frame_child.erb +18 -0
- data/lib/capybara/spec/views/frame_one.erb +10 -0
- data/lib/capybara/spec/views/frame_parent.erb +9 -0
- data/lib/capybara/spec/views/frame_two.erb +9 -0
- data/lib/capybara/spec/views/header_links.erb +8 -0
- data/lib/capybara/spec/views/host_links.erb +13 -0
- data/lib/capybara/spec/views/initial_alert.erb +10 -0
- data/lib/capybara/spec/views/obscured.erb +47 -0
- data/lib/capybara/spec/views/offset.erb +32 -0
- data/lib/capybara/spec/views/path.erb +13 -0
- data/lib/capybara/spec/views/popup_one.erb +9 -0
- data/lib/capybara/spec/views/popup_two.erb +9 -0
- data/lib/capybara/spec/views/postback.erb +14 -0
- data/lib/capybara/spec/views/react.erb +45 -0
- data/lib/capybara/spec/views/scroll.erb +20 -0
- data/lib/capybara/spec/views/spatial.erb +31 -0
- data/lib/capybara/spec/views/tables.erb +130 -0
- data/lib/capybara/spec/views/with_animation.erb +74 -0
- data/lib/capybara/spec/views/with_base_tag.erb +11 -0
- data/lib/capybara/spec/views/with_count.erb +8 -0
- data/lib/capybara/spec/views/with_dragula.erb +22 -0
- data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
- data/lib/capybara/spec/views/with_hover.erb +24 -0
- data/lib/capybara/spec/views/with_hover1.erb +10 -0
- data/lib/capybara/spec/views/with_html.erb +208 -0
- data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
- data/lib/capybara/spec/views/with_html_entities.erb +2 -0
- data/lib/capybara/spec/views/with_js.erb +160 -0
- data/lib/capybara/spec/views/with_jstree.erb +26 -0
- data/lib/capybara/spec/views/with_namespace.erb +20 -0
- data/lib/capybara/spec/views/with_scope.erb +42 -0
- data/lib/capybara/spec/views/with_scope_other.erb +6 -0
- data/lib/capybara/spec/views/with_simple_html.erb +2 -0
- data/lib/capybara/spec/views/with_slow_unload.erb +17 -0
- data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
- data/lib/capybara/spec/views/with_title.erb +5 -0
- data/lib/capybara/spec/views/with_unload_alert.erb +14 -0
- data/lib/capybara/spec/views/with_windows.erb +54 -0
- data/lib/capybara/spec/views/within_frames.erb +15 -0
- data/lib/capybara/version.rb +5 -0
- data/lib/capybara/window.rb +146 -0
- data/spec/basic_node_spec.rb +154 -0
- data/spec/capybara_spec.rb +112 -0
- data/spec/css_builder_spec.rb +101 -0
- data/spec/css_splitter_spec.rb +38 -0
- data/spec/dsl_spec.rb +276 -0
- data/spec/filter_set_spec.rb +46 -0
- data/spec/fixtures/capybara.csv +1 -0
- data/spec/fixtures/certificate.pem +25 -0
- data/spec/fixtures/key.pem +27 -0
- data/spec/fixtures/selenium_driver_rspec_failure.rb +13 -0
- data/spec/fixtures/selenium_driver_rspec_success.rb +13 -0
- data/spec/minitest_spec.rb +163 -0
- data/spec/minitest_spec_spec.rb +162 -0
- data/spec/per_session_config_spec.rb +68 -0
- data/spec/rack_test_spec.rb +268 -0
- data/spec/regexp_dissassembler_spec.rb +250 -0
- data/spec/result_spec.rb +196 -0
- data/spec/rspec/features_spec.rb +99 -0
- data/spec/rspec/scenarios_spec.rb +19 -0
- data/spec/rspec/shared_spec_matchers.rb +947 -0
- data/spec/rspec/views_spec.rb +14 -0
- data/spec/rspec_matchers_spec.rb +62 -0
- data/spec/rspec_spec.rb +145 -0
- data/spec/sauce_spec_chrome.rb +43 -0
- data/spec/selector_spec.rb +513 -0
- data/spec/selenium_spec_chrome.rb +188 -0
- data/spec/selenium_spec_chrome_remote.rb +96 -0
- data/spec/selenium_spec_edge.rb +47 -0
- data/spec/selenium_spec_firefox.rb +208 -0
- data/spec/selenium_spec_firefox_remote.rb +80 -0
- data/spec/selenium_spec_ie.rb +150 -0
- data/spec/selenium_spec_safari.rb +148 -0
- data/spec/server_spec.rb +292 -0
- data/spec/session_spec.rb +91 -0
- data/spec/shared_selenium_node.rb +83 -0
- data/spec/shared_selenium_session.rb +476 -0
- data/spec/spec_helper.rb +100 -0
- data/spec/xpath_builder_spec.rb +93 -0
- metadata +753 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Capybara
|
|
4
|
+
module Node
|
|
5
|
+
##
|
|
6
|
+
#
|
|
7
|
+
# A {Capybara::Node::Simple} is a simpler version of {Capybara::Node::Base} which
|
|
8
|
+
# includes only {Capybara::Node::Finders} and {Capybara::Node::Matchers} and does
|
|
9
|
+
# not include {Capybara::Node::Actions}. This type of node is returned when
|
|
10
|
+
# using {Capybara.string}.
|
|
11
|
+
#
|
|
12
|
+
# It is useful in that it does not require a session, an application or a driver,
|
|
13
|
+
# but can still use Capybara's finders and matchers on any string that contains HTML.
|
|
14
|
+
#
|
|
15
|
+
class Simple
|
|
16
|
+
include Capybara::Node::Finders
|
|
17
|
+
include Capybara::Node::Matchers
|
|
18
|
+
include Capybara::Node::DocumentMatchers
|
|
19
|
+
|
|
20
|
+
attr_reader :native
|
|
21
|
+
|
|
22
|
+
def initialize(native)
|
|
23
|
+
native = Capybara::HTML(native) if native.is_a?(String)
|
|
24
|
+
@native = native
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
#
|
|
29
|
+
# @return [String] The text of the element
|
|
30
|
+
#
|
|
31
|
+
def text(_type = nil, normalize_ws: false)
|
|
32
|
+
txt = native.text
|
|
33
|
+
normalize_ws ? txt.gsub(/[[:space:]]+/, ' ').strip : txt
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
#
|
|
38
|
+
# Retrieve the given attribute
|
|
39
|
+
#
|
|
40
|
+
# element[:title] # => HTML title attribute
|
|
41
|
+
#
|
|
42
|
+
# @param [Symbol] name The attribute name to retrieve
|
|
43
|
+
# @return [String] The value of the attribute
|
|
44
|
+
#
|
|
45
|
+
def [](name)
|
|
46
|
+
attr_name = name.to_s
|
|
47
|
+
if attr_name == 'value'
|
|
48
|
+
value
|
|
49
|
+
elsif (tag_name == 'input') && (native[:type] == 'checkbox') && (attr_name == 'checked')
|
|
50
|
+
native['checked'] == 'checked'
|
|
51
|
+
else
|
|
52
|
+
native[attr_name]
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
#
|
|
58
|
+
# @return [String] The tag name of the element
|
|
59
|
+
#
|
|
60
|
+
def tag_name
|
|
61
|
+
native.node_name
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
##
|
|
65
|
+
#
|
|
66
|
+
# An XPath expression describing where on the page the element can be found
|
|
67
|
+
#
|
|
68
|
+
# @return [String] An XPath expression
|
|
69
|
+
#
|
|
70
|
+
def path
|
|
71
|
+
native.path
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
##
|
|
75
|
+
#
|
|
76
|
+
# @return [String] The value of the form element
|
|
77
|
+
#
|
|
78
|
+
def value
|
|
79
|
+
if tag_name == 'textarea'
|
|
80
|
+
native['_capybara_raw_value']
|
|
81
|
+
elsif tag_name == 'select'
|
|
82
|
+
selected_options = find_xpath('.//option[@selected]')
|
|
83
|
+
if multiple?
|
|
84
|
+
selected_options.map(&method(:option_value))
|
|
85
|
+
else
|
|
86
|
+
option_value(selected_options.first || find_xpath('.//option').first)
|
|
87
|
+
end
|
|
88
|
+
elsif tag_name == 'input' && %w[radio checkbox].include?(native[:type])
|
|
89
|
+
native[:value] || 'on'
|
|
90
|
+
else
|
|
91
|
+
native[:value]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
##
|
|
96
|
+
#
|
|
97
|
+
# Whether or not the element is visible. Does not support CSS, so
|
|
98
|
+
# the result may be inaccurate.
|
|
99
|
+
#
|
|
100
|
+
# @param [Boolean] check_ancestors Whether to inherit visibility from ancestors
|
|
101
|
+
# @return [Boolean] Whether the element is visible
|
|
102
|
+
#
|
|
103
|
+
def visible?(check_ancestors = true)
|
|
104
|
+
return false if (tag_name == 'input') && (native[:type] == 'hidden')
|
|
105
|
+
return false if tag_name == 'template'
|
|
106
|
+
|
|
107
|
+
if check_ancestors
|
|
108
|
+
!find_xpath(VISIBILITY_XPATH)
|
|
109
|
+
else
|
|
110
|
+
# No need for an xpath if only checking the current element
|
|
111
|
+
!(native.key?('hidden') ||
|
|
112
|
+
/display:\s?none/.match?(native[:style] || '') ||
|
|
113
|
+
%w[script head].include?(tag_name))
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
##
|
|
118
|
+
#
|
|
119
|
+
# Whether or not the element is checked.
|
|
120
|
+
#
|
|
121
|
+
# @return [Boolean] Whether the element is checked
|
|
122
|
+
#
|
|
123
|
+
def checked?
|
|
124
|
+
native.has_attribute?('checked')
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
##
|
|
128
|
+
#
|
|
129
|
+
# Whether or not the element is disabled.
|
|
130
|
+
#
|
|
131
|
+
# @return [Boolean] Whether the element is disabled
|
|
132
|
+
def disabled?
|
|
133
|
+
native.has_attribute?('disabled') &&
|
|
134
|
+
%w[button input select textarea optgroup option menuitem fieldset].include?(tag_name)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
##
|
|
138
|
+
#
|
|
139
|
+
# Whether or not the element is selected.
|
|
140
|
+
#
|
|
141
|
+
# @return [Boolean] Whether the element is selected
|
|
142
|
+
#
|
|
143
|
+
def selected?
|
|
144
|
+
native.has_attribute?('selected')
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def multiple?
|
|
148
|
+
native.has_attribute?('multiple')
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def synchronize(_seconds = nil)
|
|
152
|
+
yield # simple nodes don't need to wait
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def allow_reload!(*)
|
|
156
|
+
# no op
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
##
|
|
160
|
+
#
|
|
161
|
+
# @return [String] The title of the document
|
|
162
|
+
def title
|
|
163
|
+
native.title
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def inspect
|
|
167
|
+
%(#<Capybara::Node::Simple tag="#{tag_name}" path="#{path}">)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# @api private
|
|
171
|
+
def find_css(css, **_options)
|
|
172
|
+
native.css(css)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# @api private
|
|
176
|
+
def find_xpath(xpath, **_options)
|
|
177
|
+
native.xpath(xpath)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# @api private
|
|
181
|
+
def session_options
|
|
182
|
+
Capybara.session_options
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# @api private
|
|
186
|
+
def initial_cache
|
|
187
|
+
{}
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
private
|
|
191
|
+
|
|
192
|
+
def option_value(option)
|
|
193
|
+
return nil if option.nil?
|
|
194
|
+
|
|
195
|
+
option[:value] || option.content
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
VISIBILITY_XPATH = XPath.generate do |x|
|
|
199
|
+
x.ancestor_or_self[
|
|
200
|
+
x.attr(:style)[x.contains('display:none') | x.contains('display: none')] |
|
|
201
|
+
x.attr(:hidden) |
|
|
202
|
+
x.qname.one_of('script', 'head') |
|
|
203
|
+
(~x.self(:summary) & XPath.parent(:details)[!XPath.attr(:open)])
|
|
204
|
+
].boolean
|
|
205
|
+
end.to_s.freeze
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Capybara
|
|
4
|
+
module Queries
|
|
5
|
+
class AncestorQuery < Capybara::Queries::SelectorQuery
|
|
6
|
+
# @api private
|
|
7
|
+
def resolve_for(node, exact = nil)
|
|
8
|
+
@child_node = node
|
|
9
|
+
|
|
10
|
+
node.synchronize do
|
|
11
|
+
match_results = super(node.session.current_scope, exact)
|
|
12
|
+
ancestors = node.find_xpath(XPath.ancestor.to_s)
|
|
13
|
+
.map(&method(:to_element))
|
|
14
|
+
.select { |el| match_results.include?(el) }
|
|
15
|
+
Capybara::Result.new(ordered_results(ancestors), self)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def description(applied = false)
|
|
20
|
+
child_query = @child_node&.instance_variable_get(:@query)
|
|
21
|
+
desc = super
|
|
22
|
+
desc += " that is an ancestor of #{child_query.description}" if child_query
|
|
23
|
+
desc
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Capybara
|
|
4
|
+
# @api private
|
|
5
|
+
module Queries
|
|
6
|
+
class BaseQuery
|
|
7
|
+
COUNT_KEYS = %i[count minimum maximum between].freeze
|
|
8
|
+
|
|
9
|
+
attr_reader :options
|
|
10
|
+
attr_writer :session_options
|
|
11
|
+
|
|
12
|
+
def initialize(options)
|
|
13
|
+
@session_options = options.delete(:session_options)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def session_options
|
|
17
|
+
@session_options || Capybara.session_options
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def wait
|
|
21
|
+
self.class.wait(options, session_options.default_max_wait_time)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.wait(options, default = Capybara.default_max_wait_time)
|
|
25
|
+
# if no value or nil for the :wait option is passed it should default to the default
|
|
26
|
+
wait = options.fetch(:wait, nil)
|
|
27
|
+
wait = default if wait.nil?
|
|
28
|
+
wait || 0
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
#
|
|
33
|
+
# Checks if a count of 0 is valid for the query
|
|
34
|
+
# Returns false if query does not have any count options specified.
|
|
35
|
+
#
|
|
36
|
+
def expects_none?
|
|
37
|
+
count_specified? ? matches_count?(0) : false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
##
|
|
41
|
+
#
|
|
42
|
+
# Checks if the given count matches the query count options.
|
|
43
|
+
# Defaults to true if no count options are specified. If multiple
|
|
44
|
+
# count options exist, it tests that all conditions are met;
|
|
45
|
+
# however, if :count is specified, all other options are ignored.
|
|
46
|
+
#
|
|
47
|
+
# @param [Integer] count The actual number. Should be coercible via Integer()
|
|
48
|
+
#
|
|
49
|
+
def matches_count?(count)
|
|
50
|
+
return (Integer(options[:count]) == count) if options[:count]
|
|
51
|
+
return false if options[:maximum] && (Integer(options[:maximum]) < count)
|
|
52
|
+
return false if options[:minimum] && (Integer(options[:minimum]) > count)
|
|
53
|
+
return false if options[:between] && !options[:between].include?(count)
|
|
54
|
+
|
|
55
|
+
true
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
##
|
|
59
|
+
#
|
|
60
|
+
# Generates a failure message from the query description and count options.
|
|
61
|
+
#
|
|
62
|
+
def failure_message
|
|
63
|
+
+"expected to find #{description}" << count_message
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def negative_failure_message
|
|
67
|
+
+"expected not to find #{description}" << count_message
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def count_specified?
|
|
73
|
+
COUNT_KEYS.any? { |key| options.key? key }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def count_message
|
|
77
|
+
message = +''
|
|
78
|
+
count, between, maximum, minimum = options.values_at(:count, :between, :maximum, :minimum)
|
|
79
|
+
if count
|
|
80
|
+
message << " #{occurrences count}"
|
|
81
|
+
elsif between
|
|
82
|
+
message << " between #{between.begin ? between.first : 1} and" \
|
|
83
|
+
" #{between.end ? between.last : 'infinite'} times"
|
|
84
|
+
elsif maximum
|
|
85
|
+
message << " at most #{occurrences maximum}"
|
|
86
|
+
elsif minimum
|
|
87
|
+
message << " at least #{occurrences minimum}"
|
|
88
|
+
end
|
|
89
|
+
message
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def occurrences(count)
|
|
93
|
+
"#{count} #{Capybara::Helpers.declension('time', 'times', count)}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def assert_valid_keys
|
|
97
|
+
invalid_keys = @options.keys - valid_keys
|
|
98
|
+
return if invalid_keys.empty?
|
|
99
|
+
|
|
100
|
+
invalid_names = invalid_keys.map(&:inspect).join(', ')
|
|
101
|
+
valid_names = valid_keys.map(&:inspect).join(', ')
|
|
102
|
+
raise ArgumentError, "Invalid option(s) #{invalid_names}, should be one of #{valid_names}"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'addressable/uri'
|
|
4
|
+
|
|
5
|
+
module Capybara
|
|
6
|
+
# @api private
|
|
7
|
+
module Queries
|
|
8
|
+
class CurrentPathQuery < BaseQuery
|
|
9
|
+
def initialize(expected_path, **options)
|
|
10
|
+
super(options)
|
|
11
|
+
@expected_path = expected_path
|
|
12
|
+
@options = {
|
|
13
|
+
url: !@expected_path.is_a?(Regexp) && !::Addressable::URI.parse(@expected_path || '').hostname.nil?,
|
|
14
|
+
ignore_query: false
|
|
15
|
+
}.merge(options)
|
|
16
|
+
assert_valid_keys
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def resolves_for?(session)
|
|
20
|
+
uri = ::Addressable::URI.parse(session.current_url)
|
|
21
|
+
uri&.query = nil if options[:ignore_query]
|
|
22
|
+
@actual_path = options[:url] ? uri&.to_s : uri&.request_uri
|
|
23
|
+
|
|
24
|
+
if @expected_path.is_a? Regexp
|
|
25
|
+
@actual_path.to_s.match?(@expected_path)
|
|
26
|
+
else
|
|
27
|
+
::Addressable::URI.parse(@expected_path) == ::Addressable::URI.parse(@actual_path)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def failure_message
|
|
32
|
+
failure_message_helper
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def negative_failure_message
|
|
36
|
+
failure_message_helper(' not')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def failure_message_helper(negated = '')
|
|
42
|
+
verb = @expected_path.is_a?(Regexp) ? 'match' : 'equal'
|
|
43
|
+
"expected #{@actual_path.inspect}#{negated} to #{verb} #{@expected_path.inspect}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def valid_keys
|
|
47
|
+
%i[wait url ignore_query]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Capybara
|
|
4
|
+
module Queries
|
|
5
|
+
class MatchQuery < Capybara::Queries::SelectorQuery
|
|
6
|
+
def visible
|
|
7
|
+
options.key?(:visible) ? super : :all
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def assert_valid_keys
|
|
13
|
+
invalid_options = @options.keys & COUNT_KEYS
|
|
14
|
+
unless invalid_options.empty?
|
|
15
|
+
raise ArgumentError, "Match queries don't support quantity options. Invalid keys - #{invalid_options.join(', ')}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
super
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def valid_keys
|
|
22
|
+
super - COUNT_KEYS
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'matrix'
|
|
4
|
+
|
|
5
|
+
module Capybara
|
|
6
|
+
module Queries
|
|
7
|
+
class SelectorQuery < Queries::BaseQuery
|
|
8
|
+
attr_reader :expression, :selector, :locator, :options
|
|
9
|
+
SPATIAL_KEYS = %i[above below left_of right_of near].freeze
|
|
10
|
+
VALID_KEYS = SPATIAL_KEYS + COUNT_KEYS +
|
|
11
|
+
%i[text id class style visible obscured exact exact_text normalize_ws match wait filter_set]
|
|
12
|
+
VALID_MATCH = %i[first smart prefer_exact one].freeze
|
|
13
|
+
|
|
14
|
+
def initialize(*args,
|
|
15
|
+
session_options:,
|
|
16
|
+
enable_aria_label: session_options.enable_aria_label,
|
|
17
|
+
test_id: session_options.test_id,
|
|
18
|
+
selector_format: nil,
|
|
19
|
+
order: nil,
|
|
20
|
+
**options,
|
|
21
|
+
&filter_block)
|
|
22
|
+
@resolved_node = nil
|
|
23
|
+
@resolved_count = 0
|
|
24
|
+
@options = options.dup
|
|
25
|
+
@order = order
|
|
26
|
+
@filter_cache = Hash.new { |hsh, key| hsh[key] = {} }
|
|
27
|
+
|
|
28
|
+
super(@options)
|
|
29
|
+
self.session_options = session_options
|
|
30
|
+
|
|
31
|
+
@selector = Selector.new(
|
|
32
|
+
find_selector(args[0].is_a?(Symbol) ? args.shift : args[0]),
|
|
33
|
+
config: { enable_aria_label: enable_aria_label, test_id: test_id },
|
|
34
|
+
format: selector_format
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
@locator = args.shift
|
|
38
|
+
@filter_block = filter_block
|
|
39
|
+
|
|
40
|
+
raise ArgumentError, "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?
|
|
41
|
+
|
|
42
|
+
@expression = selector.call(@locator, **@options)
|
|
43
|
+
|
|
44
|
+
warn_exact_usage
|
|
45
|
+
|
|
46
|
+
assert_valid_keys
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def name; selector.name; end
|
|
50
|
+
def label; selector.label || selector.name; end
|
|
51
|
+
|
|
52
|
+
def description(only_applied = false)
|
|
53
|
+
desc = +''
|
|
54
|
+
show_for = show_for_stage(only_applied)
|
|
55
|
+
|
|
56
|
+
if show_for[:any]
|
|
57
|
+
desc << 'visible ' if visible == :visible
|
|
58
|
+
desc << 'non-visible ' if visible == :hidden
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
desc << "#{label} #{locator.inspect}"
|
|
62
|
+
|
|
63
|
+
if show_for[:any]
|
|
64
|
+
desc << " with#{' exact' if exact_text == true} text #{options[:text].inspect}" if options[:text]
|
|
65
|
+
desc << " with exact text #{exact_text}" if exact_text.is_a?(String)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
desc << " with id #{options[:id]}" if options[:id]
|
|
69
|
+
desc << " with classes [#{Array(options[:class]).join(',')}]" if options[:class]
|
|
70
|
+
|
|
71
|
+
desc << case options[:style]
|
|
72
|
+
when String
|
|
73
|
+
" with style attribute #{options[:style].inspect}"
|
|
74
|
+
when Regexp
|
|
75
|
+
" with style attribute matching #{options[:style].inspect}"
|
|
76
|
+
when Hash
|
|
77
|
+
" with styles #{options[:style].inspect}"
|
|
78
|
+
else ''
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
%i[above below left_of right_of near].each do |spatial_filter|
|
|
82
|
+
if options[spatial_filter] && show_for[:spatial]
|
|
83
|
+
desc << " #{spatial_filter} #{options[spatial_filter] rescue '<ERROR>'}" # rubocop:disable Style/RescueModifier
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
desc << selector.description(node_filters: show_for[:node], **options)
|
|
88
|
+
|
|
89
|
+
desc << ' that also matches the custom filter block' if @filter_block && show_for[:node]
|
|
90
|
+
|
|
91
|
+
desc << " within #{@resolved_node.inspect}" if describe_within?
|
|
92
|
+
if locator.is_a?(String) && locator.start_with?('#', './/', '//')
|
|
93
|
+
unless selector.raw_locator?
|
|
94
|
+
desc << "\nNote: It appears you may be passing a CSS selector or XPath expression rather than a locator. " \
|
|
95
|
+
"Please see the documentation for acceptable locator values.\n\n"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
desc
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def applied_description
|
|
102
|
+
description(true)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def matches_filters?(node, node_filter_errors = [])
|
|
106
|
+
return true if (@resolved_node&.== node) && options[:allow_self]
|
|
107
|
+
|
|
108
|
+
matches_locator_filter?(node) &&
|
|
109
|
+
matches_system_filters?(node) &&
|
|
110
|
+
matches_spatial_filters?(node) &&
|
|
111
|
+
matches_node_filters?(node, node_filter_errors) &&
|
|
112
|
+
matches_filter_block?(node)
|
|
113
|
+
rescue *(node.respond_to?(:session) ? node.session.driver.invalid_element_errors : [])
|
|
114
|
+
false
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def visible
|
|
118
|
+
case (vis = options.fetch(:visible) { default_visibility })
|
|
119
|
+
when true then :visible
|
|
120
|
+
when false then :all
|
|
121
|
+
else vis
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def exact?
|
|
126
|
+
supports_exact? ? options.fetch(:exact, session_options.exact) : false
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def match
|
|
130
|
+
options.fetch(:match, session_options.match)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def xpath(exact = nil)
|
|
134
|
+
exact = exact? if exact.nil?
|
|
135
|
+
expr = apply_expression_filters(@expression)
|
|
136
|
+
expr = exact ? expr.to_xpath(:exact) : expr.to_s if expr.respond_to?(:to_xpath)
|
|
137
|
+
expr = filtered_expression(expr)
|
|
138
|
+
expr = "(#{expr})[#{xpath_text_conditions}]" if try_text_match_in_expression?
|
|
139
|
+
expr
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def css
|
|
143
|
+
filtered_expression(apply_expression_filters(@expression))
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# @api private
|
|
147
|
+
def resolve_for(node, exact = nil)
|
|
148
|
+
applied_filters.clear
|
|
149
|
+
@filter_cache.clear
|
|
150
|
+
@resolved_node = node
|
|
151
|
+
@resolved_count += 1
|
|
152
|
+
|
|
153
|
+
node.synchronize do
|
|
154
|
+
children = find_nodes_by_selector_format(node, exact).map(&method(:to_element))
|
|
155
|
+
Capybara::Result.new(ordered_results(children), self)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @api private
|
|
160
|
+
def supports_exact?
|
|
161
|
+
return @expression.respond_to? :to_xpath if @selector.supports_exact?.nil?
|
|
162
|
+
|
|
163
|
+
@selector.supports_exact?
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def failure_message
|
|
167
|
+
+"expected to find #{applied_description}" << count_message
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def negative_failure_message
|
|
171
|
+
+"expected not to find #{applied_description}" << count_message
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
private
|
|
175
|
+
|
|
176
|
+
def selector_format
|
|
177
|
+
@selector.format
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def matching_text
|
|
181
|
+
options[:text] || options[:exact_text]
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def text_fragments
|
|
185
|
+
(text = matching_text).is_a?(String) ? text.split : []
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def xpath_text_conditions
|
|
189
|
+
case (text = matching_text)
|
|
190
|
+
when String
|
|
191
|
+
text.split.map { |txt| XPath.contains(txt) }.reduce(&:&)
|
|
192
|
+
when Regexp
|
|
193
|
+
condition = XPath.current
|
|
194
|
+
condition = condition.uppercase if text.casefold?
|
|
195
|
+
Selector::RegexpDisassembler.new(text).alternated_substrings.map do |strs|
|
|
196
|
+
strs.flat_map(&:split).map { |str| condition.contains(str) }.reduce(:&)
|
|
197
|
+
end.reduce(:|)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def try_text_match_in_expression?
|
|
202
|
+
first_try? &&
|
|
203
|
+
matching_text &&
|
|
204
|
+
@resolved_node.is_a?(Capybara::Node::Base) &&
|
|
205
|
+
@resolved_node.session&.driver&.wait?
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def first_try?
|
|
209
|
+
@resolved_count == 1
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def show_for_stage(only_applied)
|
|
213
|
+
lambda do |stage = :any|
|
|
214
|
+
!only_applied || (stage == :any ? applied_filters.any? : applied_filters.include?(stage))
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def applied_filters
|
|
219
|
+
@applied_filters ||= []
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def find_selector(locator)
|
|
223
|
+
case locator
|
|
224
|
+
when Symbol then Selector[locator]
|
|
225
|
+
else Selector.for(locator)
|
|
226
|
+
end || Selector[session_options.default_selector]
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def find_nodes_by_selector_format(node, exact)
|
|
230
|
+
hints = {}
|
|
231
|
+
hints[:uses_visibility] = true unless visible == :all
|
|
232
|
+
hints[:texts] = text_fragments unless selector_format == :xpath
|
|
233
|
+
hints[:styles] = options[:style] if use_default_style_filter?
|
|
234
|
+
hints[:position] = true if use_spatial_filter?
|
|
235
|
+
|
|
236
|
+
if selector_format == :css
|
|
237
|
+
if node.method(:find_css).arity != 1
|
|
238
|
+
node.find_css(css, **hints)
|
|
239
|
+
else
|
|
240
|
+
node.find_css(css)
|
|
241
|
+
end
|
|
242
|
+
elsif selector_format == :xpath
|
|
243
|
+
if node.method(:find_xpath).arity != 1
|
|
244
|
+
node.find_xpath(xpath(exact), **hints)
|
|
245
|
+
else
|
|
246
|
+
node.find_xpath(xpath(exact))
|
|
247
|
+
end
|
|
248
|
+
else
|
|
249
|
+
raise ArgumentError, "Unknown format: #{selector_format}"
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def to_element(node)
|
|
254
|
+
if @resolved_node.is_a?(Capybara::Node::Base)
|
|
255
|
+
Capybara::Node::Element.new(@resolved_node.session, node, @resolved_node, self)
|
|
256
|
+
else
|
|
257
|
+
Capybara::Node::Simple.new(node)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def valid_keys
|
|
262
|
+
VALID_KEYS + custom_keys
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def matches_node_filters?(node, errors)
|
|
266
|
+
applied_filters << :node
|
|
267
|
+
|
|
268
|
+
unapplied_options = options.keys - valid_keys
|
|
269
|
+
@selector.with_filter_errors(errors) do
|
|
270
|
+
node_filters.all? do |filter_name, filter|
|
|
271
|
+
next true unless apply_filter?(filter)
|
|
272
|
+
|
|
273
|
+
if filter.matcher?
|
|
274
|
+
unapplied_options.select { |option_name| filter.handles_option?(option_name) }.all? do |option_name|
|
|
275
|
+
unapplied_options.delete(option_name)
|
|
276
|
+
filter.matches?(node, option_name, options[option_name], @selector)
|
|
277
|
+
end
|
|
278
|
+
elsif options.key?(filter_name)
|
|
279
|
+
unapplied_options.delete(filter_name)
|
|
280
|
+
filter.matches?(node, filter_name, options[filter_name], @selector)
|
|
281
|
+
elsif filter.default?
|
|
282
|
+
filter.matches?(node, filter_name, filter.default, @selector)
|
|
283
|
+
else
|
|
284
|
+
true
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def matches_filter_block?(node)
|
|
291
|
+
return true unless @filter_block
|
|
292
|
+
|
|
293
|
+
if node.respond_to?(:session)
|
|
294
|
+
node.session.using_wait_time(0) { @filter_block.call(node) }
|
|
295
|
+
else
|
|
296
|
+
@filter_block.call(node)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def filter_set(name)
|
|
301
|
+
::Capybara::Selector::FilterSet[name]
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def node_filters
|
|
305
|
+
if options.key?(:filter_set)
|
|
306
|
+
filter_set(options[:filter_set])
|
|
307
|
+
else
|
|
308
|
+
@selector
|
|
309
|
+
end.node_filters
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def expression_filters
|
|
313
|
+
filters = @selector.expression_filters
|
|
314
|
+
filters.merge filter_set(options[:filter_set]).expression_filters if options.key?(:filter_set)
|
|
315
|
+
filters
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def ordered_results(results)
|
|
319
|
+
case @order
|
|
320
|
+
when :reverse
|
|
321
|
+
results.reverse
|
|
322
|
+
else
|
|
323
|
+
results
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def custom_keys
|
|
328
|
+
@custom_keys ||= node_filters.keys + expression_filters.keys
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def assert_valid_keys
|
|
332
|
+
unless VALID_MATCH.include?(match)
|
|
333
|
+
raise ArgumentError, "Invalid option #{match.inspect} for :match, should be one of #{VALID_MATCH.map(&:inspect).join(', ')}"
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
unhandled_options = @options.keys.reject do |option_name|
|
|
337
|
+
valid_keys.include?(option_name) ||
|
|
338
|
+
expression_filters.any? { |_name, ef| ef.handles_option? option_name } ||
|
|
339
|
+
node_filters.any? { |_name, nf| nf.handles_option? option_name }
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
return if unhandled_options.empty?
|
|
343
|
+
|
|
344
|
+
invalid_names = unhandled_options.map(&:inspect).join(', ')
|
|
345
|
+
valid_names = (valid_keys - [:allow_self]).map(&:inspect).join(', ')
|
|
346
|
+
raise ArgumentError, "Invalid option(s) #{invalid_names}, should be one of #{valid_names}"
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def filtered_expression(expr)
|
|
350
|
+
conditions = {}
|
|
351
|
+
conditions[:id] = options[:id] if use_default_id_filter?
|
|
352
|
+
conditions[:class] = options[:class] if use_default_class_filter?
|
|
353
|
+
conditions[:style] = options[:style] if use_default_style_filter? && !options[:style].is_a?(Hash)
|
|
354
|
+
builder(expr).add_attribute_conditions(**conditions)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def use_default_id_filter?
|
|
358
|
+
options.key?(:id) && !custom_keys.include?(:id)
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def use_default_class_filter?
|
|
362
|
+
options.key?(:class) && !custom_keys.include?(:class)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def use_default_style_filter?
|
|
366
|
+
options.key?(:style) && !custom_keys.include?(:style)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def use_spatial_filter?
|
|
370
|
+
options.values_at(*SPATIAL_KEYS).compact.any?
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def apply_expression_filters(expression)
|
|
374
|
+
unapplied_options = options.keys - valid_keys
|
|
375
|
+
expression_filters.inject(expression) do |expr, (name, ef)|
|
|
376
|
+
next expr unless apply_filter?(ef)
|
|
377
|
+
|
|
378
|
+
if ef.matcher?
|
|
379
|
+
unapplied_options.select(&ef.method(:handles_option?)).inject(expr) do |memo, option_name|
|
|
380
|
+
unapplied_options.delete(option_name)
|
|
381
|
+
ef.apply_filter(memo, option_name, options[option_name], @selector)
|
|
382
|
+
end
|
|
383
|
+
elsif options.key?(name)
|
|
384
|
+
unapplied_options.delete(name)
|
|
385
|
+
ef.apply_filter(expr, name, options[name], @selector)
|
|
386
|
+
elsif ef.default?
|
|
387
|
+
ef.apply_filter(expr, name, ef.default, @selector)
|
|
388
|
+
else
|
|
389
|
+
expr
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def warn_exact_usage
|
|
395
|
+
return unless options.key?(:exact) && !supports_exact?
|
|
396
|
+
|
|
397
|
+
warn "The :exact option only has an effect on queries using the XPath#is method. Using it with the query \"#{expression}\" has no effect."
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def exact_text
|
|
401
|
+
options.fetch(:exact_text, session_options.exact_text)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def describe_within?
|
|
405
|
+
@resolved_node && !document?(@resolved_node) && !simple_root?(@resolved_node)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def document?(node)
|
|
409
|
+
node.is_a?(::Capybara::Node::Document)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def simple_root?(node)
|
|
413
|
+
node.is_a?(::Capybara::Node::Simple) && node.path == '/'
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def apply_filter?(filter)
|
|
417
|
+
filter.format.nil? || (filter.format == selector_format)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def matches_locator_filter?(node)
|
|
421
|
+
return true unless @selector.locator_filter && apply_filter?(@selector.locator_filter)
|
|
422
|
+
|
|
423
|
+
@selector.locator_filter.matches?(node, @locator, @selector, exact: exact?)
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def matches_system_filters?(node)
|
|
427
|
+
applied_filters << :system
|
|
428
|
+
|
|
429
|
+
matches_visibility_filters?(node) &&
|
|
430
|
+
matches_id_filter?(node) &&
|
|
431
|
+
matches_class_filter?(node) &&
|
|
432
|
+
matches_style_filter?(node) &&
|
|
433
|
+
matches_text_filter?(node) &&
|
|
434
|
+
matches_exact_text_filter?(node)
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def matches_spatial_filters?(node)
|
|
438
|
+
applied_filters << :spatial
|
|
439
|
+
return true unless use_spatial_filter?
|
|
440
|
+
|
|
441
|
+
node_rect = Rectangle.new(node.initial_cache[:position] || node.rect)
|
|
442
|
+
|
|
443
|
+
if options[:above]
|
|
444
|
+
el_rect = rect_cache(options[:above])
|
|
445
|
+
return false unless node_rect.above? el_rect
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
if options[:below]
|
|
449
|
+
el_rect = rect_cache(options[:below])
|
|
450
|
+
return false unless node_rect.below? el_rect
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
if options[:left_of]
|
|
454
|
+
el_rect = rect_cache(options[:left_of])
|
|
455
|
+
return false unless node_rect.left_of? el_rect
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
if options[:right_of]
|
|
459
|
+
el_rect = rect_cache(options[:right_of])
|
|
460
|
+
return false unless node_rect.right_of? el_rect
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
if options[:near]
|
|
464
|
+
return false if node == options[:near]
|
|
465
|
+
|
|
466
|
+
el_rect = rect_cache(options[:near])
|
|
467
|
+
return false unless node_rect.near? el_rect
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
true
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def matches_id_filter?(node)
|
|
474
|
+
return true unless use_default_id_filter? && options[:id].is_a?(Regexp)
|
|
475
|
+
|
|
476
|
+
options[:id].match? node[:id]
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def matches_class_filter?(node)
|
|
480
|
+
return true unless use_default_class_filter? && options[:class].is_a?(Regexp)
|
|
481
|
+
|
|
482
|
+
options[:class].match? node[:class]
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
def matches_style_filter?(node)
|
|
486
|
+
case options[:style]
|
|
487
|
+
when String, nil
|
|
488
|
+
true
|
|
489
|
+
when Regexp
|
|
490
|
+
options[:style].match? node[:style]
|
|
491
|
+
when Hash
|
|
492
|
+
matches_style?(node, options[:style])
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
def matches_style?(node, styles)
|
|
497
|
+
@actual_styles = node.initial_cache[:style] || node.style(*styles.keys)
|
|
498
|
+
styles.all? do |style, value|
|
|
499
|
+
if value.is_a? Regexp
|
|
500
|
+
value.match? @actual_styles[style.to_s]
|
|
501
|
+
else
|
|
502
|
+
@actual_styles[style.to_s] == value
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def matches_text_filter?(node)
|
|
508
|
+
value = options[:text]
|
|
509
|
+
return true unless value
|
|
510
|
+
return matches_text_exactly?(node, value) if exact_text == true
|
|
511
|
+
|
|
512
|
+
regexp = value.is_a?(Regexp) ? value : Regexp.escape(value.to_s)
|
|
513
|
+
matches_text_regexp?(node, regexp)
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def matches_exact_text_filter?(node)
|
|
517
|
+
return true unless exact_text.is_a?(String)
|
|
518
|
+
|
|
519
|
+
matches_text_exactly?(node, exact_text)
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def matches_visibility_filters?(node)
|
|
523
|
+
obscured = options[:obscured]
|
|
524
|
+
return (visible != :hidden) && (node.initial_cache[:visible] != false) && !node.obscured? if obscured == false
|
|
525
|
+
|
|
526
|
+
vis = case visible
|
|
527
|
+
when :visible
|
|
528
|
+
node.initial_cache[:visible] || (node.initial_cache[:visible].nil? && node.visible?)
|
|
529
|
+
when :hidden
|
|
530
|
+
(node.initial_cache[:visible] == false) || (node.initial_cache[:visbile].nil? && !node.visible?)
|
|
531
|
+
else
|
|
532
|
+
true
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
vis && case obscured
|
|
536
|
+
when true
|
|
537
|
+
node.obscured?
|
|
538
|
+
when false
|
|
539
|
+
!node.obscured?
|
|
540
|
+
else
|
|
541
|
+
true
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def matches_text_exactly?(node, value)
|
|
546
|
+
regexp = value.is_a?(Regexp) ? value : /\A#{Regexp.escape(value.to_s)}\z/
|
|
547
|
+
matches_text_regexp?(node, regexp)
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
def normalize_ws
|
|
551
|
+
options.fetch(:normalize_ws, session_options.default_normalize_ws)
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
def matches_text_regexp?(node, regexp)
|
|
555
|
+
text_visible = visible
|
|
556
|
+
text_visible = :all if text_visible == :hidden
|
|
557
|
+
node.text(text_visible, normalize_ws: normalize_ws).match?(regexp)
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def default_visibility
|
|
561
|
+
@selector.default_visibility(session_options.ignore_hidden_elements, options)
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
def builder(expr)
|
|
565
|
+
selector.builder(expr)
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
def position_cache(key)
|
|
569
|
+
@filter_cache[key][:position] ||= key.rect
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def rect_cache(key)
|
|
573
|
+
@filter_cache[key][:rect] ||= Rectangle.new(position_cache(key))
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
class Rectangle
|
|
577
|
+
attr_reader :top, :bottom, :left, :right
|
|
578
|
+
def initialize(position)
|
|
579
|
+
# rubocop:disable Style/RescueModifier
|
|
580
|
+
@top = position['top'] rescue position['y']
|
|
581
|
+
@bottom = position['bottom'] rescue (@top + position['height'])
|
|
582
|
+
@left = position['left'] rescue position['x']
|
|
583
|
+
@right = position['right'] rescue (@left + position['width'])
|
|
584
|
+
# rubocop:enable Style/RescueModifier
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
def distance(other)
|
|
588
|
+
distance = Float::INFINITY
|
|
589
|
+
|
|
590
|
+
line_segments.each do |ls1|
|
|
591
|
+
other.line_segments.each do |ls2|
|
|
592
|
+
distance = [
|
|
593
|
+
distance,
|
|
594
|
+
distance_segment_segment(*ls1, *ls2)
|
|
595
|
+
].min
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
distance
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
def above?(other)
|
|
603
|
+
bottom <= other.top
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
def below?(other)
|
|
607
|
+
top >= other.bottom
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
def left_of?(other)
|
|
611
|
+
right <= other.left
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
def right_of?(other)
|
|
615
|
+
left >= other.right
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
def near?(other)
|
|
619
|
+
distance(other) <= 50
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
protected
|
|
623
|
+
|
|
624
|
+
def line_segments
|
|
625
|
+
[
|
|
626
|
+
[Vector[top, left], Vector[top, right]],
|
|
627
|
+
[Vector[top, right], Vector[bottom, left]],
|
|
628
|
+
[Vector[bottom, left], Vector[bottom, right]],
|
|
629
|
+
[Vector[bottom, right], Vector[top, left]]
|
|
630
|
+
]
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
private
|
|
634
|
+
|
|
635
|
+
def distance_segment_segment(l1p1, l1p2, l2p1, l2p2)
|
|
636
|
+
# See http://geomalgorithms.com/a07-_distance.html
|
|
637
|
+
# rubocop:disable Naming/VariableName
|
|
638
|
+
u = l1p2 - l1p1
|
|
639
|
+
v = l2p2 - l2p1
|
|
640
|
+
w = l1p1 - l2p1
|
|
641
|
+
|
|
642
|
+
a = u.dot u
|
|
643
|
+
b = u.dot v
|
|
644
|
+
c = v.dot v
|
|
645
|
+
|
|
646
|
+
d = u.dot w
|
|
647
|
+
e = v.dot w
|
|
648
|
+
cap_d = (a * c) - (b * b)
|
|
649
|
+
sD = tD = cap_d
|
|
650
|
+
|
|
651
|
+
# compute the line parameters of the two closest points
|
|
652
|
+
if cap_d < Float::EPSILON # the lines are almost parallel
|
|
653
|
+
sN = 0.0 # force using point P0 on segment S1
|
|
654
|
+
sD = 1.0 # to prevent possible division by 0.0 later
|
|
655
|
+
tN = e
|
|
656
|
+
tD = c
|
|
657
|
+
else # get the closest points on the infinite lines
|
|
658
|
+
sN = (b * e) - (c * d)
|
|
659
|
+
tN = (a * e) - (b * d)
|
|
660
|
+
if sN.negative? # sc < 0 => the s=0 edge is visible
|
|
661
|
+
sN = 0
|
|
662
|
+
tN = e
|
|
663
|
+
tD = c
|
|
664
|
+
elsif sN > sD # sc > 1 => the s=1 edge is visible
|
|
665
|
+
sN = sD
|
|
666
|
+
tN = e + b
|
|
667
|
+
tD = c
|
|
668
|
+
end
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
if tN.negative? # tc < 0 => the t=0 edge is visible
|
|
672
|
+
tN = 0
|
|
673
|
+
# recompute sc for this edge
|
|
674
|
+
if (-d).negative?
|
|
675
|
+
sN = 0.0
|
|
676
|
+
elsif -d > a
|
|
677
|
+
sN = sD
|
|
678
|
+
else
|
|
679
|
+
sN = -d
|
|
680
|
+
sD = a
|
|
681
|
+
end
|
|
682
|
+
elsif tN > tD # tc > 1 => the t=1 edge is visible
|
|
683
|
+
tN = tD
|
|
684
|
+
# recompute sc for this edge
|
|
685
|
+
if (-d + b).negative?
|
|
686
|
+
sN = 0.0
|
|
687
|
+
elsif (-d + b) > a
|
|
688
|
+
sN = sD
|
|
689
|
+
else
|
|
690
|
+
sN = (-d + b)
|
|
691
|
+
sD = a
|
|
692
|
+
end
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
# finally do the division to get sc and tc
|
|
696
|
+
sc = sN.abs < Float::EPSILON ? 0.0 : sN / sD
|
|
697
|
+
tc = tN.abs < Float::EPSILON ? 0.0 : tN / tD
|
|
698
|
+
|
|
699
|
+
# difference of the two closest points
|
|
700
|
+
dP = w + (u * sc) - (v * tc)
|
|
701
|
+
|
|
702
|
+
Math.sqrt(dP.dot(dP))
|
|
703
|
+
# rubocop:enable Naming/VariableName
|
|
704
|
+
end
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
private_constant :Rectangle
|
|
708
|
+
end
|
|
709
|
+
end
|
|
710
|
+
end
|