capybara 3.3.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 -0
- data/History.md +803 -13
- data/License.txt +1 -1
- data/README.md +257 -84
- data/lib/capybara/config.rb +25 -9
- data/lib/capybara/cucumber.rb +1 -1
- data/lib/capybara/driver/base.rb +17 -3
- data/lib/capybara/driver/node.rb +31 -6
- data/lib/capybara/dsl.rb +9 -7
- data/lib/capybara/helpers.rb +31 -7
- data/lib/capybara/minitest/spec.rb +180 -88
- data/lib/capybara/minitest.rb +262 -149
- data/lib/capybara/node/actions.rb +202 -116
- data/lib/capybara/node/base.rb +34 -19
- data/lib/capybara/node/document.rb +14 -2
- data/lib/capybara/node/document_matchers.rb +10 -12
- data/lib/capybara/node/element.rb +269 -115
- data/lib/capybara/node/finders.rb +99 -77
- data/lib/capybara/node/matchers.rb +327 -151
- data/lib/capybara/node/simple.rb +48 -13
- 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 +8 -9
- data/lib/capybara/queries/base_query.rb +23 -16
- data/lib/capybara/queries/current_path_query.rb +16 -6
- data/lib/capybara/queries/match_query.rb +1 -0
- data/lib/capybara/queries/selector_query.rb +587 -130
- data/lib/capybara/queries/sibling_query.rb +8 -6
- data/lib/capybara/queries/style_query.rb +6 -2
- data/lib/capybara/queries/text_query.rb +28 -14
- data/lib/capybara/queries/title_query.rb +2 -2
- data/lib/capybara/rack_test/browser.rb +92 -25
- 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 +68 -41
- data/lib/capybara/rack_test/node.rb +106 -39
- 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 +75 -52
- data/lib/capybara/rspec/features.rb +7 -7
- 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 +141 -339
- data/lib/capybara/rspec.rb +2 -0
- 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 +27 -25
- 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 +73 -25
- data/lib/capybara/selector/filters/base.rb +24 -5
- data/lib/capybara/selector/filters/expression_filter.rb +3 -3
- data/lib/capybara/selector/filters/locator_filter.rb +29 -0
- data/lib/capybara/selector/filters/node_filter.rb +16 -2
- data/lib/capybara/selector/regexp_disassembler.rb +211 -0
- data/lib/capybara/selector/selector.rb +85 -348
- data/lib/capybara/selector/xpath_extensions.rb +17 -0
- data/lib/capybara/selector.rb +474 -447
- 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 +255 -143
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +93 -11
- 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 +436 -134
- 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 +56 -19
- data/lib/capybara/server/checker.rb +9 -3
- data/lib/capybara/server/middleware.rb +28 -12
- data/lib/capybara/server.rb +33 -10
- data/lib/capybara/session/config.rb +34 -10
- data/lib/capybara/session/matchers.rb +23 -16
- data/lib/capybara/session.rb +230 -170
- 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 +121 -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 +127 -40
- 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_spec.rb +21 -18
- data/lib/capybara/spec/session/assert_selector_spec.rb +52 -58
- data/lib/capybara/spec/session/assert_style_spec.rb +7 -7
- data/lib/capybara/spec/session/assert_text_spec.rb +74 -50
- data/lib/capybara/spec/session/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 +6 -6
- data/lib/capybara/spec/session/check_spec.rb +102 -47
- data/lib/capybara/spec/session/choose_spec.rb +58 -32
- data/lib/capybara/spec/session/click_button_spec.rb +219 -163
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +49 -23
- data/lib/capybara/spec/session/click_link_spec.rb +77 -54
- data/lib/capybara/spec/session/current_scope_spec.rb +8 -8
- data/lib/capybara/spec/session/current_url_spec.rb +38 -29
- 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_spec.rb +8 -8
- data/lib/capybara/spec/session/element/match_css_spec.rb +16 -10
- data/lib/capybara/spec/session/element/match_xpath_spec.rb +6 -6
- data/lib/capybara/spec/session/element/matches_selector_spec.rb +68 -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 +101 -46
- data/lib/capybara/spec/session/find_button_spec.rb +23 -23
- data/lib/capybara/spec/session/find_by_id_spec.rb +7 -7
- data/lib/capybara/spec/session/find_field_spec.rb +32 -30
- data/lib/capybara/spec/session/find_link_spec.rb +31 -21
- data/lib/capybara/spec/session/find_spec.rb +244 -141
- data/lib/capybara/spec/session/first_spec.rb +43 -43
- data/lib/capybara/spec/session/frame/frame_title_spec.rb +5 -5
- data/lib/capybara/spec/session/frame/frame_url_spec.rb +5 -5
- data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +30 -18
- data/lib/capybara/spec/session/frame/within_frame_spec.rb +45 -18
- 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 +94 -13
- data/lib/capybara/spec/session/has_css_spec.rb +272 -132
- 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 +44 -4
- data/lib/capybara/spec/session/has_none_selectors_spec.rb +31 -31
- data/lib/capybara/spec/session/has_select_spec.rb +84 -50
- data/lib/capybara/spec/session/has_selector_spec.rb +111 -71
- 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_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 +894 -142
- data/lib/capybara/spec/session/node_wrapper_spec.rb +10 -7
- data/lib/capybara/spec/session/refresh_spec.rb +9 -7
- data/lib/capybara/spec/session/reset_session_spec.rb +63 -35
- data/lib/capybara/spec/session/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 +2 -2
- data/lib/capybara/spec/session/save_page_spec.rb +37 -37
- data/lib/capybara/spec/session/save_screenshot_spec.rb +10 -10
- 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 +85 -85
- data/lib/capybara/spec/session/selectors_spec.rb +49 -18
- data/lib/capybara/spec/session/sibling_spec.rb +9 -9
- data/lib/capybara/spec/session/text_spec.rb +25 -24
- data/lib/capybara/spec/session/title_spec.rb +7 -6
- data/lib/capybara/spec/session/uncheck_spec.rb +33 -21
- data/lib/capybara/spec/session/unselect_spec.rb +37 -37
- data/lib/capybara/spec/session/visit_spec.rb +68 -49
- data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
- data/lib/capybara/spec/session/window/current_window_spec.rb +1 -1
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +20 -16
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +6 -2
- data/lib/capybara/spec/session/window/window_spec.rb +62 -63
- data/lib/capybara/spec/session/window/windows_spec.rb +5 -1
- data/lib/capybara/spec/session/window/within_window_spec.rb +14 -14
- data/lib/capybara/spec/session/within_spec.rb +79 -42
- data/lib/capybara/spec/spec_helper.rb +41 -53
- data/lib/capybara/spec/test_app.rb +132 -43
- data/lib/capybara/spec/views/animated.erb +49 -0
- data/lib/capybara/spec/views/form.erb +139 -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 +67 -0
- data/lib/capybara/spec/views/with_animation.erb +39 -4
- 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 +37 -9
- 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 +26 -5
- data/lib/capybara/spec/views/with_jstree.erb +26 -0
- data/lib/capybara/spec/views/with_namespace.erb +1 -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 +19 -25
- data/lib/capybara.rb +126 -111
- data/spec/basic_node_spec.rb +59 -34
- data/spec/capybara_spec.rb +56 -44
- data/spec/counter_spec.rb +35 -0
- data/spec/css_builder_spec.rb +101 -0
- data/spec/css_splitter_spec.rb +8 -8
- data/spec/dsl_spec.rb +79 -52
- data/spec/filter_set_spec.rb +9 -9
- data/spec/fixtures/selenium_driver_rspec_failure.rb +4 -4
- data/spec/fixtures/selenium_driver_rspec_success.rb +4 -4
- data/spec/minitest_spec.rb +45 -7
- data/spec/minitest_spec_spec.rb +87 -64
- data/spec/per_session_config_spec.rb +6 -6
- data/spec/rack_test_spec.rb +172 -116
- data/spec/regexp_dissassembler_spec.rb +250 -0
- data/spec/result_spec.rb +80 -72
- data/spec/rspec/features_spec.rb +21 -16
- data/spec/rspec/scenarios_spec.rb +10 -6
- data/spec/rspec/shared_spec_matchers.rb +407 -365
- data/spec/rspec/views_spec.rb +3 -3
- data/spec/rspec_matchers_spec.rb +35 -10
- data/spec/rspec_spec.rb +63 -41
- data/spec/sauce_spec_chrome.rb +43 -0
- data/spec/selector_spec.rb +334 -89
- data/spec/selenium_spec_chrome.rb +176 -62
- data/spec/selenium_spec_chrome_remote.rb +54 -14
- 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 +171 -97
- data/spec/session_spec.rb +34 -18
- data/spec/shared_selenium_node.rb +79 -0
- data/spec/shared_selenium_session.rb +344 -80
- data/spec/spec_helper.rb +124 -2
- data/spec/whitespace_normalizer_spec.rb +54 -0
- data/spec/xpath_builder_spec.rb +93 -0
- metadata +326 -28
- data/lib/capybara/rspec/compound.rb +0 -94
- data/lib/capybara/selenium/driver_specializations/marionette_driver.rb +0 -31
- data/lib/capybara/selenium/nodes/marionette_node.rb +0 -31
- data/lib/capybara/spec/session/has_style_spec.rb +0 -25
- 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 -167
|
@@ -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,68 +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
|
-
|
|
279
|
+
def matches_node_filters?(node, errors)
|
|
280
|
+
applied_filters << :node
|
|
130
281
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
136
299
|
end
|
|
137
|
-
elsif options.key?(filter_name)
|
|
138
|
-
unapplied_options.delete(filter_name)
|
|
139
|
-
filter.matches?(node, filter_name, options[filter_name])
|
|
140
|
-
elsif filter.default?
|
|
141
|
-
filter.matches?(node, filter_name, filter.default)
|
|
142
|
-
else
|
|
143
|
-
true
|
|
144
300
|
end
|
|
145
301
|
end
|
|
146
302
|
end
|
|
@@ -155,115 +311,107 @@ module Capybara
|
|
|
155
311
|
end
|
|
156
312
|
end
|
|
157
313
|
|
|
314
|
+
def filter_set(name)
|
|
315
|
+
::Capybara::Selector::FilterSet[name]
|
|
316
|
+
end
|
|
317
|
+
|
|
158
318
|
def node_filters
|
|
159
319
|
if options.key?(:filter_set)
|
|
160
|
-
|
|
320
|
+
filter_set(options[:filter_set])
|
|
161
321
|
else
|
|
162
|
-
@selector
|
|
163
|
-
end
|
|
322
|
+
@selector
|
|
323
|
+
end.node_filters
|
|
164
324
|
end
|
|
165
325
|
|
|
166
326
|
def expression_filters
|
|
167
327
|
filters = @selector.expression_filters
|
|
168
|
-
filters.merge
|
|
328
|
+
filters.merge filter_set(options[:filter_set]).expression_filters if options.key?(:filter_set)
|
|
169
329
|
filters
|
|
170
330
|
end
|
|
171
331
|
|
|
332
|
+
def ordered_results(results)
|
|
333
|
+
case @order
|
|
334
|
+
when :reverse
|
|
335
|
+
results.reverse
|
|
336
|
+
else
|
|
337
|
+
results
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
172
341
|
def custom_keys
|
|
173
342
|
@custom_keys ||= node_filters.keys + expression_filters.keys
|
|
174
343
|
end
|
|
175
344
|
|
|
176
345
|
def assert_valid_keys
|
|
177
346
|
unless VALID_MATCH.include?(match)
|
|
178
|
-
raise ArgumentError, "
|
|
347
|
+
raise ArgumentError, "Invalid option #{match.inspect} for :match, should be one of #{VALID_MATCH.map(&:inspect).join(', ')}"
|
|
179
348
|
end
|
|
180
|
-
|
|
181
|
-
unhandled_options
|
|
182
|
-
|
|
349
|
+
|
|
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 } ||
|
|
183
353
|
node_filters.any? { |_name, nf| nf.handles_option? option_name }
|
|
184
354
|
end
|
|
185
355
|
|
|
186
356
|
return if unhandled_options.empty?
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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}"
|
|
190
361
|
end
|
|
191
362
|
|
|
192
|
-
def
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
if options.key?(:class) && !custom_keys.include?(:class)
|
|
201
|
-
class_xpath = if options[:class].is_a?(XPath::Expression)
|
|
202
|
-
XPath.attr(:class)[options[:class]]
|
|
203
|
-
else
|
|
204
|
-
Array(options[:class]).map do |klass|
|
|
205
|
-
if klass.start_with?('!')
|
|
206
|
-
!XPath.attr(:class).contains_word(klass.slice(1))
|
|
207
|
-
else
|
|
208
|
-
XPath.attr(:class).contains_word(klass)
|
|
209
|
-
end
|
|
210
|
-
end.reduce(:&)
|
|
211
|
-
end
|
|
212
|
-
expr = "(#{expr})[#{class_xpath}]"
|
|
213
|
-
end
|
|
214
|
-
expr
|
|
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)
|
|
215
369
|
end
|
|
216
370
|
|
|
217
|
-
def
|
|
218
|
-
|
|
219
|
-
|
|
371
|
+
def use_default_id_filter?
|
|
372
|
+
options.key?(:id) && !custom_keys.include?(:id)
|
|
373
|
+
end
|
|
220
374
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if process_class && options[:class].is_a?(XPath::Expression)
|
|
225
|
-
raise ArgumentError, "XPath expressions are not supported for the :class filter with CSS based selectors"
|
|
226
|
-
end
|
|
375
|
+
def use_default_class_filter?
|
|
376
|
+
options.key?(:class) && !custom_keys.include?(:class)
|
|
377
|
+
end
|
|
227
378
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
sel += css_from_classes(Array(options[:class])) if process_class
|
|
232
|
-
sel
|
|
233
|
-
end.join(", ")
|
|
234
|
-
end
|
|
379
|
+
def use_default_style_filter?
|
|
380
|
+
options.key?(:style) && !custom_keys.include?(:style)
|
|
381
|
+
end
|
|
235
382
|
|
|
236
|
-
|
|
383
|
+
def use_default_focused_filter?
|
|
384
|
+
options.key?(:focused) && !custom_keys.include?(:focused)
|
|
237
385
|
end
|
|
238
386
|
|
|
239
|
-
def
|
|
240
|
-
|
|
241
|
-
(classes[false].to_a.map { |c| ".#{Capybara::Selector::CSS.escape(c)}" } +
|
|
242
|
-
classes[true].to_a.map { |c| ":not(.#{Capybara::Selector::CSS.escape(c.slice(1))})" }).join
|
|
387
|
+
def use_spatial_filter?
|
|
388
|
+
options.values_at(*SPATIAL_KEYS).compact.any?
|
|
243
389
|
end
|
|
244
390
|
|
|
245
|
-
def apply_expression_filters(
|
|
391
|
+
def apply_expression_filters(expression)
|
|
246
392
|
unapplied_options = options.keys - valid_keys
|
|
247
|
-
expression_filters.inject(
|
|
393
|
+
expression_filters.inject(expression) do |expr, (name, ef)|
|
|
394
|
+
next expr unless apply_filter?(ef)
|
|
395
|
+
|
|
248
396
|
if ef.matcher?
|
|
249
|
-
unapplied_options.select
|
|
397
|
+
unapplied_options.select(&ef.method(:handles_option?)).inject(expr) do |memo, option_name|
|
|
250
398
|
unapplied_options.delete(option_name)
|
|
251
|
-
|
|
399
|
+
ef.apply_filter(memo, option_name, options[option_name], @selector)
|
|
252
400
|
end
|
|
253
|
-
memo
|
|
254
401
|
elsif options.key?(name)
|
|
255
402
|
unapplied_options.delete(name)
|
|
256
|
-
ef.apply_filter(
|
|
403
|
+
ef.apply_filter(expr, name, options[name], @selector)
|
|
257
404
|
elsif ef.default?
|
|
258
|
-
ef.apply_filter(
|
|
405
|
+
ef.apply_filter(expr, name, ef.default, @selector)
|
|
259
406
|
else
|
|
260
|
-
|
|
407
|
+
expr
|
|
261
408
|
end
|
|
262
409
|
end
|
|
263
410
|
end
|
|
264
411
|
|
|
265
412
|
def warn_exact_usage
|
|
266
413
|
return unless options.key?(:exact) && !supports_exact?
|
|
414
|
+
|
|
267
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."
|
|
268
416
|
end
|
|
269
417
|
|
|
@@ -283,22 +431,331 @@ module Capybara
|
|
|
283
431
|
node.is_a?(::Capybara::Node::Simple) && node.path == '/'
|
|
284
432
|
end
|
|
285
433
|
|
|
286
|
-
def
|
|
287
|
-
|
|
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?)
|
|
442
|
+
end
|
|
443
|
+
|
|
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]
|
|
503
|
+
else
|
|
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
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
|
|
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
|
+
|
|
288
553
|
regexp = value.is_a?(Regexp) ? value : Regexp.escape(value.to_s)
|
|
289
|
-
matches_text_regexp(node, regexp)
|
|
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
|
|
290
564
|
end
|
|
291
565
|
|
|
292
|
-
def
|
|
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)
|
|
293
592
|
regexp = value.is_a?(Regexp) ? value : /\A#{Regexp.escape(value.to_s)}\z/
|
|
294
|
-
matches_text_regexp(node, regexp)
|
|
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)
|
|
295
598
|
end
|
|
296
599
|
|
|
297
600
|
def matches_text_regexp(node, regexp)
|
|
298
601
|
text_visible = visible
|
|
299
602
|
text_visible = :all if text_visible == :hidden
|
|
300
|
-
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))
|
|
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
|
|
301
756
|
end
|
|
757
|
+
|
|
758
|
+
private_constant :Rectangle
|
|
302
759
|
end
|
|
303
760
|
end
|
|
304
761
|
end
|