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