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