capybara 3.32.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.yardopts +1 -0
- data/History.md +1813 -0
- data/License.txt +22 -0
- data/README.md +1099 -0
- data/lib/capybara.rb +511 -0
- data/lib/capybara/config.rb +94 -0
- data/lib/capybara/cucumber.rb +27 -0
- data/lib/capybara/driver/base.rb +170 -0
- data/lib/capybara/driver/node.rb +139 -0
- data/lib/capybara/dsl.rb +65 -0
- data/lib/capybara/helpers.rb +108 -0
- data/lib/capybara/minitest.rb +386 -0
- data/lib/capybara/minitest/spec.rb +264 -0
- data/lib/capybara/node/actions.rb +420 -0
- data/lib/capybara/node/base.rb +143 -0
- data/lib/capybara/node/document.rb +48 -0
- data/lib/capybara/node/document_matchers.rb +67 -0
- data/lib/capybara/node/element.rb +606 -0
- data/lib/capybara/node/finders.rb +325 -0
- data/lib/capybara/node/matchers.rb +883 -0
- data/lib/capybara/node/simple.rb +208 -0
- data/lib/capybara/queries/ancestor_query.rb +27 -0
- data/lib/capybara/queries/base_query.rb +106 -0
- data/lib/capybara/queries/current_path_query.rb +51 -0
- data/lib/capybara/queries/match_query.rb +26 -0
- data/lib/capybara/queries/selector_query.rb +710 -0
- data/lib/capybara/queries/sibling_query.rb +26 -0
- data/lib/capybara/queries/style_query.rb +45 -0
- data/lib/capybara/queries/text_query.rb +110 -0
- data/lib/capybara/queries/title_query.rb +39 -0
- data/lib/capybara/rack_test/browser.rb +140 -0
- data/lib/capybara/rack_test/css_handlers.rb +13 -0
- data/lib/capybara/rack_test/driver.rb +109 -0
- data/lib/capybara/rack_test/errors.rb +6 -0
- data/lib/capybara/rack_test/form.rb +127 -0
- data/lib/capybara/rack_test/node.rb +325 -0
- data/lib/capybara/rails.rb +16 -0
- data/lib/capybara/registrations/drivers.rb +36 -0
- data/lib/capybara/registrations/patches/puma_ssl.rb +27 -0
- data/lib/capybara/registrations/servers.rb +44 -0
- data/lib/capybara/result.rb +190 -0
- data/lib/capybara/rspec.rb +29 -0
- data/lib/capybara/rspec/features.rb +23 -0
- data/lib/capybara/rspec/matcher_proxies.rb +82 -0
- data/lib/capybara/rspec/matchers.rb +201 -0
- data/lib/capybara/rspec/matchers/base.rb +111 -0
- data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
- data/lib/capybara/rspec/matchers/compound.rb +88 -0
- data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
- data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
- data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
- data/lib/capybara/rspec/matchers/have_selector.rb +77 -0
- data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
- data/lib/capybara/rspec/matchers/have_text.rb +33 -0
- data/lib/capybara/rspec/matchers/have_title.rb +29 -0
- data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
- data/lib/capybara/rspec/matchers/match_style.rb +38 -0
- data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
- data/lib/capybara/selector.rb +233 -0
- data/lib/capybara/selector/builders/css_builder.rb +84 -0
- data/lib/capybara/selector/builders/xpath_builder.rb +69 -0
- data/lib/capybara/selector/css.rb +102 -0
- data/lib/capybara/selector/definition.rb +276 -0
- data/lib/capybara/selector/definition/button.rb +51 -0
- data/lib/capybara/selector/definition/checkbox.rb +26 -0
- data/lib/capybara/selector/definition/css.rb +10 -0
- data/lib/capybara/selector/definition/datalist_input.rb +35 -0
- data/lib/capybara/selector/definition/datalist_option.rb +25 -0
- data/lib/capybara/selector/definition/element.rb +27 -0
- data/lib/capybara/selector/definition/field.rb +40 -0
- data/lib/capybara/selector/definition/fieldset.rb +14 -0
- data/lib/capybara/selector/definition/file_field.rb +13 -0
- data/lib/capybara/selector/definition/fillable_field.rb +33 -0
- data/lib/capybara/selector/definition/frame.rb +17 -0
- data/lib/capybara/selector/definition/id.rb +6 -0
- data/lib/capybara/selector/definition/label.rb +62 -0
- data/lib/capybara/selector/definition/link.rb +46 -0
- data/lib/capybara/selector/definition/link_or_button.rb +16 -0
- data/lib/capybara/selector/definition/option.rb +27 -0
- data/lib/capybara/selector/definition/radio_button.rb +27 -0
- data/lib/capybara/selector/definition/select.rb +81 -0
- data/lib/capybara/selector/definition/table.rb +109 -0
- data/lib/capybara/selector/definition/table_row.rb +21 -0
- data/lib/capybara/selector/definition/xpath.rb +5 -0
- data/lib/capybara/selector/filter.rb +5 -0
- data/lib/capybara/selector/filter_set.rb +124 -0
- data/lib/capybara/selector/filters/base.rb +77 -0
- data/lib/capybara/selector/filters/expression_filter.rb +22 -0
- data/lib/capybara/selector/filters/locator_filter.rb +29 -0
- data/lib/capybara/selector/filters/node_filter.rb +31 -0
- data/lib/capybara/selector/regexp_disassembler.rb +214 -0
- data/lib/capybara/selector/selector.rb +147 -0
- data/lib/capybara/selector/xpath_extensions.rb +17 -0
- data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
- data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
- data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
- data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
- data/lib/capybara/selenium/driver.rb +496 -0
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +119 -0
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +126 -0
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +89 -0
- data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
- data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
- data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
- data/lib/capybara/selenium/extensions/find.rb +110 -0
- data/lib/capybara/selenium/extensions/html5_drag.rb +228 -0
- data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
- data/lib/capybara/selenium/extensions/scroll.rb +78 -0
- data/lib/capybara/selenium/logger_suppressor.rb +34 -0
- data/lib/capybara/selenium/node.rb +610 -0
- data/lib/capybara/selenium/nodes/chrome_node.rb +119 -0
- data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
- data/lib/capybara/selenium/nodes/firefox_node.rb +131 -0
- data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
- data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
- data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
- data/lib/capybara/selenium/patches/atoms.rb +18 -0
- data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
- data/lib/capybara/selenium/patches/logs.rb +47 -0
- data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
- data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
- data/lib/capybara/server.rb +126 -0
- data/lib/capybara/server/animation_disabler.rb +58 -0
- data/lib/capybara/server/checker.rb +44 -0
- data/lib/capybara/server/middleware.rb +69 -0
- data/lib/capybara/session.rb +942 -0
- data/lib/capybara/session/config.rb +124 -0
- data/lib/capybara/session/matchers.rb +87 -0
- data/lib/capybara/spec/fixtures/another_test_file.txt +1 -0
- data/lib/capybara/spec/fixtures/capybara.jpg +3 -0
- data/lib/capybara/spec/fixtures/no_extension +1 -0
- data/lib/capybara/spec/fixtures/test_file.txt +1 -0
- data/lib/capybara/spec/public/jquery-ui.js +13 -0
- data/lib/capybara/spec/public/jquery.js +5 -0
- data/lib/capybara/spec/public/offset.js +6 -0
- data/lib/capybara/spec/public/test.js +268 -0
- data/lib/capybara/spec/session/accept_alert_spec.rb +81 -0
- data/lib/capybara/spec/session/accept_confirm_spec.rb +32 -0
- data/lib/capybara/spec/session/accept_prompt_spec.rb +78 -0
- data/lib/capybara/spec/session/all_spec.rb +278 -0
- data/lib/capybara/spec/session/ancestor_spec.rb +88 -0
- data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +140 -0
- data/lib/capybara/spec/session/assert_current_path_spec.rb +75 -0
- data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
- data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
- data/lib/capybara/spec/session/assert_text_spec.rb +258 -0
- data/lib/capybara/spec/session/assert_title_spec.rb +93 -0
- data/lib/capybara/spec/session/attach_file_spec.rb +216 -0
- data/lib/capybara/spec/session/body_spec.rb +23 -0
- data/lib/capybara/spec/session/check_spec.rb +235 -0
- data/lib/capybara/spec/session/choose_spec.rb +121 -0
- data/lib/capybara/spec/session/click_button_spec.rb +506 -0
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +129 -0
- data/lib/capybara/spec/session/click_link_spec.rb +229 -0
- data/lib/capybara/spec/session/current_scope_spec.rb +31 -0
- data/lib/capybara/spec/session/current_url_spec.rb +115 -0
- data/lib/capybara/spec/session/dismiss_confirm_spec.rb +36 -0
- data/lib/capybara/spec/session/dismiss_prompt_spec.rb +21 -0
- data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +38 -0
- data/lib/capybara/spec/session/element/match_css_spec.rb +31 -0
- data/lib/capybara/spec/session/element/match_xpath_spec.rb +25 -0
- data/lib/capybara/spec/session/element/matches_selector_spec.rb +120 -0
- data/lib/capybara/spec/session/evaluate_async_script_spec.rb +23 -0
- data/lib/capybara/spec/session/evaluate_script_spec.rb +49 -0
- data/lib/capybara/spec/session/execute_script_spec.rb +28 -0
- data/lib/capybara/spec/session/fill_in_spec.rb +286 -0
- data/lib/capybara/spec/session/find_button_spec.rb +74 -0
- data/lib/capybara/spec/session/find_by_id_spec.rb +33 -0
- data/lib/capybara/spec/session/find_field_spec.rb +113 -0
- data/lib/capybara/spec/session/find_link_spec.rb +70 -0
- data/lib/capybara/spec/session/find_spec.rb +531 -0
- data/lib/capybara/spec/session/first_spec.rb +156 -0
- data/lib/capybara/spec/session/frame/frame_title_spec.rb +23 -0
- data/lib/capybara/spec/session/frame/frame_url_spec.rb +23 -0
- data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +116 -0
- data/lib/capybara/spec/session/frame/within_frame_spec.rb +112 -0
- data/lib/capybara/spec/session/go_back_spec.rb +12 -0
- data/lib/capybara/spec/session/go_forward_spec.rb +14 -0
- data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
- data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
- data/lib/capybara/spec/session/has_any_selectors_spec.rb +25 -0
- data/lib/capybara/spec/session/has_button_spec.rb +69 -0
- data/lib/capybara/spec/session/has_css_spec.rb +374 -0
- data/lib/capybara/spec/session/has_current_path_spec.rb +138 -0
- data/lib/capybara/spec/session/has_field_spec.rb +349 -0
- data/lib/capybara/spec/session/has_link_spec.rb +39 -0
- data/lib/capybara/spec/session/has_none_selectors_spec.rb +78 -0
- data/lib/capybara/spec/session/has_select_spec.rb +310 -0
- data/lib/capybara/spec/session/has_selector_spec.rb +202 -0
- data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
- data/lib/capybara/spec/session/has_table_spec.rb +198 -0
- data/lib/capybara/spec/session/has_text_spec.rb +394 -0
- data/lib/capybara/spec/session/has_title_spec.rb +71 -0
- data/lib/capybara/spec/session/has_xpath_spec.rb +149 -0
- data/lib/capybara/spec/session/headers_spec.rb +8 -0
- data/lib/capybara/spec/session/html_spec.rb +47 -0
- data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
- data/lib/capybara/spec/session/node_spec.rb +1292 -0
- data/lib/capybara/spec/session/node_wrapper_spec.rb +39 -0
- data/lib/capybara/spec/session/refresh_spec.rb +33 -0
- data/lib/capybara/spec/session/reset_session_spec.rb +148 -0
- data/lib/capybara/spec/session/response_code_spec.rb +8 -0
- data/lib/capybara/spec/session/save_and_open_page_spec.rb +21 -0
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +43 -0
- data/lib/capybara/spec/session/save_page_spec.rb +110 -0
- data/lib/capybara/spec/session/save_screenshot_spec.rb +55 -0
- data/lib/capybara/spec/session/screenshot_spec.rb +18 -0
- data/lib/capybara/spec/session/scroll_spec.rb +117 -0
- data/lib/capybara/spec/session/select_spec.rb +229 -0
- data/lib/capybara/spec/session/selectors_spec.rb +98 -0
- data/lib/capybara/spec/session/sibling_spec.rb +52 -0
- data/lib/capybara/spec/session/source_spec.rb +0 -0
- data/lib/capybara/spec/session/text_spec.rb +74 -0
- data/lib/capybara/spec/session/title_spec.rb +29 -0
- data/lib/capybara/spec/session/uncheck_spec.rb +100 -0
- data/lib/capybara/spec/session/unselect_spec.rb +116 -0
- data/lib/capybara/spec/session/visit_spec.rb +204 -0
- data/lib/capybara/spec/session/window/become_closed_spec.rb +89 -0
- data/lib/capybara/spec/session/window/current_window_spec.rb +28 -0
- data/lib/capybara/spec/session/window/open_new_window_spec.rb +31 -0
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +132 -0
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +99 -0
- data/lib/capybara/spec/session/window/window_spec.rb +203 -0
- data/lib/capybara/spec/session/window/windows_spec.rb +34 -0
- data/lib/capybara/spec/session/window/within_window_spec.rb +157 -0
- data/lib/capybara/spec/session/within_spec.rb +199 -0
- data/lib/capybara/spec/spec_helper.rb +134 -0
- data/lib/capybara/spec/test_app.rb +226 -0
- data/lib/capybara/spec/views/animated.erb +49 -0
- data/lib/capybara/spec/views/buttons.erb +5 -0
- data/lib/capybara/spec/views/fieldsets.erb +30 -0
- data/lib/capybara/spec/views/form.erb +685 -0
- data/lib/capybara/spec/views/frame_child.erb +18 -0
- data/lib/capybara/spec/views/frame_one.erb +10 -0
- data/lib/capybara/spec/views/frame_parent.erb +9 -0
- data/lib/capybara/spec/views/frame_two.erb +9 -0
- data/lib/capybara/spec/views/header_links.erb +8 -0
- data/lib/capybara/spec/views/host_links.erb +13 -0
- data/lib/capybara/spec/views/initial_alert.erb +10 -0
- data/lib/capybara/spec/views/obscured.erb +47 -0
- data/lib/capybara/spec/views/offset.erb +32 -0
- data/lib/capybara/spec/views/path.erb +13 -0
- data/lib/capybara/spec/views/popup_one.erb +9 -0
- data/lib/capybara/spec/views/popup_two.erb +9 -0
- data/lib/capybara/spec/views/postback.erb +14 -0
- data/lib/capybara/spec/views/react.erb +45 -0
- data/lib/capybara/spec/views/scroll.erb +20 -0
- data/lib/capybara/spec/views/spatial.erb +31 -0
- data/lib/capybara/spec/views/tables.erb +130 -0
- data/lib/capybara/spec/views/with_animation.erb +74 -0
- data/lib/capybara/spec/views/with_base_tag.erb +11 -0
- data/lib/capybara/spec/views/with_count.erb +8 -0
- data/lib/capybara/spec/views/with_dragula.erb +22 -0
- data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
- data/lib/capybara/spec/views/with_hover.erb +24 -0
- data/lib/capybara/spec/views/with_hover1.erb +10 -0
- data/lib/capybara/spec/views/with_html.erb +208 -0
- data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
- data/lib/capybara/spec/views/with_html_entities.erb +2 -0
- data/lib/capybara/spec/views/with_js.erb +160 -0
- data/lib/capybara/spec/views/with_jstree.erb +26 -0
- data/lib/capybara/spec/views/with_namespace.erb +20 -0
- data/lib/capybara/spec/views/with_scope.erb +42 -0
- data/lib/capybara/spec/views/with_scope_other.erb +6 -0
- data/lib/capybara/spec/views/with_simple_html.erb +2 -0
- data/lib/capybara/spec/views/with_slow_unload.erb +17 -0
- data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
- data/lib/capybara/spec/views/with_title.erb +5 -0
- data/lib/capybara/spec/views/with_unload_alert.erb +14 -0
- data/lib/capybara/spec/views/with_windows.erb +54 -0
- data/lib/capybara/spec/views/within_frames.erb +15 -0
- data/lib/capybara/version.rb +5 -0
- data/lib/capybara/window.rb +146 -0
- data/spec/basic_node_spec.rb +154 -0
- data/spec/capybara_spec.rb +112 -0
- data/spec/css_builder_spec.rb +101 -0
- data/spec/css_splitter_spec.rb +38 -0
- data/spec/dsl_spec.rb +276 -0
- data/spec/filter_set_spec.rb +46 -0
- data/spec/fixtures/capybara.csv +1 -0
- data/spec/fixtures/certificate.pem +25 -0
- data/spec/fixtures/key.pem +27 -0
- data/spec/fixtures/selenium_driver_rspec_failure.rb +13 -0
- data/spec/fixtures/selenium_driver_rspec_success.rb +13 -0
- data/spec/minitest_spec.rb +163 -0
- data/spec/minitest_spec_spec.rb +162 -0
- data/spec/per_session_config_spec.rb +68 -0
- data/spec/rack_test_spec.rb +268 -0
- data/spec/regexp_dissassembler_spec.rb +250 -0
- data/spec/result_spec.rb +196 -0
- data/spec/rspec/features_spec.rb +99 -0
- data/spec/rspec/scenarios_spec.rb +19 -0
- data/spec/rspec/shared_spec_matchers.rb +947 -0
- data/spec/rspec/views_spec.rb +14 -0
- data/spec/rspec_matchers_spec.rb +62 -0
- data/spec/rspec_spec.rb +145 -0
- data/spec/sauce_spec_chrome.rb +43 -0
- data/spec/selector_spec.rb +513 -0
- data/spec/selenium_spec_chrome.rb +188 -0
- data/spec/selenium_spec_chrome_remote.rb +96 -0
- data/spec/selenium_spec_edge.rb +47 -0
- data/spec/selenium_spec_firefox.rb +208 -0
- data/spec/selenium_spec_firefox_remote.rb +80 -0
- data/spec/selenium_spec_ie.rb +150 -0
- data/spec/selenium_spec_safari.rb +148 -0
- data/spec/server_spec.rb +292 -0
- data/spec/session_spec.rb +91 -0
- data/spec/shared_selenium_node.rb +83 -0
- data/spec/shared_selenium_session.rb +476 -0
- data/spec/spec_helper.rb +100 -0
- data/spec/xpath_builder_spec.rb +93 -0
- metadata +753 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'xpath'
|
|
4
|
+
|
|
5
|
+
module Capybara
|
|
6
|
+
class Selector
|
|
7
|
+
# @api private
|
|
8
|
+
class CSSBuilder
|
|
9
|
+
def initialize(expression)
|
|
10
|
+
@expression = expression || ''
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :expression
|
|
14
|
+
|
|
15
|
+
def add_attribute_conditions(**attributes)
|
|
16
|
+
@expression = attributes.inject(expression) do |css, (name, value)|
|
|
17
|
+
conditions = if name == :class
|
|
18
|
+
class_conditions(value)
|
|
19
|
+
elsif value.is_a? Regexp
|
|
20
|
+
regexp_conditions(name, value)
|
|
21
|
+
else
|
|
22
|
+
[attribute_conditions(name => value)]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
::Capybara::Selector::CSS.split(css).map do |sel|
|
|
26
|
+
next sel if conditions.empty?
|
|
27
|
+
|
|
28
|
+
conditions.map { |cond| sel + cond }.join(', ')
|
|
29
|
+
end.join(', ')
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def regexp_conditions(name, value)
|
|
36
|
+
Selector::RegexpDisassembler.new(value).alternated_substrings.map do |strs|
|
|
37
|
+
strs.map do |str|
|
|
38
|
+
"[#{name}*='#{str}'#{' i' if value.casefold?}]"
|
|
39
|
+
end.join
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def attribute_conditions(attributes)
|
|
44
|
+
attributes.map do |attribute, value|
|
|
45
|
+
case value
|
|
46
|
+
when XPath::Expression
|
|
47
|
+
raise ArgumentError, "XPath expressions are not supported for the :#{attribute} filter with CSS based selectors"
|
|
48
|
+
when Regexp
|
|
49
|
+
Selector::RegexpDisassembler.new(value).substrings.map do |str|
|
|
50
|
+
"[#{attribute}*='#{str}'#{' i' if value.casefold?}]"
|
|
51
|
+
end.join
|
|
52
|
+
when true
|
|
53
|
+
"[#{attribute}]"
|
|
54
|
+
when false
|
|
55
|
+
':not([attribute])'
|
|
56
|
+
else
|
|
57
|
+
if attribute == :id
|
|
58
|
+
"##{::Capybara::Selector::CSS.escape(value)}"
|
|
59
|
+
else
|
|
60
|
+
"[#{attribute}='#{value}']"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end.join
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def class_conditions(classes)
|
|
67
|
+
case classes
|
|
68
|
+
when XPath::Expression
|
|
69
|
+
raise ArgumentError, 'XPath expressions are not supported for the :class filter with CSS based selectors'
|
|
70
|
+
when Regexp
|
|
71
|
+
Selector::RegexpDisassembler.new(classes).alternated_substrings.map do |strs|
|
|
72
|
+
strs.map do |str|
|
|
73
|
+
"[class*='#{str}'#{' i' if classes.casefold?}]"
|
|
74
|
+
end.join
|
|
75
|
+
end
|
|
76
|
+
else
|
|
77
|
+
cls = Array(classes).group_by { |cl| cl.match?(/^!(?!!!)/) }
|
|
78
|
+
[(cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl.sub(/^!!/, ''))}" } +
|
|
79
|
+
cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'xpath'
|
|
4
|
+
|
|
5
|
+
module Capybara
|
|
6
|
+
class Selector
|
|
7
|
+
# @api private
|
|
8
|
+
class XPathBuilder
|
|
9
|
+
def initialize(expression)
|
|
10
|
+
@expression = expression || ''
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :expression
|
|
14
|
+
|
|
15
|
+
def add_attribute_conditions(**conditions)
|
|
16
|
+
@expression = conditions.inject(expression) do |xp, (name, value)|
|
|
17
|
+
conditions = name == :class ? class_conditions(value) : attribute_conditions(name => value)
|
|
18
|
+
if xp.is_a? XPath::Expression
|
|
19
|
+
xp[conditions]
|
|
20
|
+
else
|
|
21
|
+
"(#{xp})[#{conditions}]"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def attribute_conditions(attributes)
|
|
29
|
+
attributes.map do |attribute, value|
|
|
30
|
+
case value
|
|
31
|
+
when XPath::Expression
|
|
32
|
+
XPath.attr(attribute)[value]
|
|
33
|
+
when Regexp
|
|
34
|
+
XPath.attr(attribute)[regexp_to_xpath_conditions(value)]
|
|
35
|
+
when true
|
|
36
|
+
XPath.attr(attribute)
|
|
37
|
+
when false, nil
|
|
38
|
+
!XPath.attr(attribute)
|
|
39
|
+
else
|
|
40
|
+
XPath.attr(attribute) == value.to_s
|
|
41
|
+
end
|
|
42
|
+
end.reduce(:&)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def class_conditions(classes)
|
|
46
|
+
case classes
|
|
47
|
+
when XPath::Expression, Regexp
|
|
48
|
+
attribute_conditions(class: classes)
|
|
49
|
+
else
|
|
50
|
+
Array(classes).map do |klass|
|
|
51
|
+
if klass.match?(/^!(?!!!)/)
|
|
52
|
+
!XPath.attr(:class).contains_word(klass.slice(1..-1))
|
|
53
|
+
else
|
|
54
|
+
XPath.attr(:class).contains_word(klass.sub(/^!!/, ''))
|
|
55
|
+
end
|
|
56
|
+
end.reduce(:&)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def regexp_to_xpath_conditions(regexp)
|
|
61
|
+
condition = XPath.current
|
|
62
|
+
condition = condition.uppercase if regexp.casefold?
|
|
63
|
+
Selector::RegexpDisassembler.new(regexp).alternated_substrings.map do |strs|
|
|
64
|
+
strs.map { |str| condition.contains(str) }.reduce(:&)
|
|
65
|
+
end.reduce(:|)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'capybara/selector/selector'
|
|
4
|
+
|
|
5
|
+
module Capybara
|
|
6
|
+
class Selector
|
|
7
|
+
class CSS
|
|
8
|
+
def self.escape(str)
|
|
9
|
+
value = str.dup
|
|
10
|
+
out = +''
|
|
11
|
+
out << value.slice!(0...1) if value.match?(/^[-_]/)
|
|
12
|
+
out << (value[0].match?(NMSTART) ? value.slice!(0...1) : escape_char(value.slice!(0...1)))
|
|
13
|
+
out << value.gsub(/[^a-zA-Z0-9_-]/) { |char| escape_char char }
|
|
14
|
+
out
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.escape_char(char)
|
|
18
|
+
char.match?(%r{[ -/:-~]}) ? "\\#{char}" : format('\\%06<hex>x', hex: char.ord)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.split(css)
|
|
22
|
+
Splitter.new.split(css)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
S = '\u{80}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}'
|
|
26
|
+
H = /[0-9a-fA-F]/.freeze
|
|
27
|
+
UNICODE = /\\#{H}{1,6}[ \t\r\n\f]?/.freeze
|
|
28
|
+
NONASCII = /[#{S}]/.freeze
|
|
29
|
+
ESCAPE = /#{UNICODE}|\\[ -~#{S}]/.freeze
|
|
30
|
+
NMSTART = /[_a-zA-Z]|#{NONASCII}|#{ESCAPE}/.freeze
|
|
31
|
+
|
|
32
|
+
class Splitter
|
|
33
|
+
def split(css)
|
|
34
|
+
selectors = []
|
|
35
|
+
StringIO.open(css.to_s) do |str|
|
|
36
|
+
selector = +''
|
|
37
|
+
while (char = str.getc)
|
|
38
|
+
case char
|
|
39
|
+
when '['
|
|
40
|
+
selector << parse_square(str)
|
|
41
|
+
when '('
|
|
42
|
+
selector << parse_paren(str)
|
|
43
|
+
when '"', "'"
|
|
44
|
+
selector << parse_string(char, str)
|
|
45
|
+
when '\\'
|
|
46
|
+
selector << char + str.getc
|
|
47
|
+
when ','
|
|
48
|
+
selectors << selector.strip
|
|
49
|
+
selector.clear
|
|
50
|
+
else
|
|
51
|
+
selector << char
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
selectors << selector.strip
|
|
55
|
+
end
|
|
56
|
+
selectors
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def parse_square(strio)
|
|
62
|
+
parse_block('[', ']', strio)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def parse_paren(strio)
|
|
66
|
+
parse_block('(', ')', strio)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def parse_block(start, final, strio)
|
|
70
|
+
block = start
|
|
71
|
+
while (char = strio.getc)
|
|
72
|
+
case char
|
|
73
|
+
when final
|
|
74
|
+
return block + char
|
|
75
|
+
when '\\'
|
|
76
|
+
block += char + strio.getc
|
|
77
|
+
when '"', "'"
|
|
78
|
+
block += parse_string(char, strio)
|
|
79
|
+
else
|
|
80
|
+
block += char
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
raise ArgumentError, "Invalid CSS Selector - Block end '#{final}' not found"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def parse_string(quote, strio)
|
|
87
|
+
string = quote
|
|
88
|
+
while (char = strio.getc)
|
|
89
|
+
string += char
|
|
90
|
+
case char
|
|
91
|
+
when quote
|
|
92
|
+
return string
|
|
93
|
+
when '\\'
|
|
94
|
+
string += strio.getc
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
raise ArgumentError, 'Invalid CSS Selector - string end not found'
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'capybara/selector/filter_set'
|
|
4
|
+
require 'capybara/selector/css'
|
|
5
|
+
require 'capybara/selector/regexp_disassembler'
|
|
6
|
+
require 'capybara/selector/builders/xpath_builder'
|
|
7
|
+
require 'capybara/selector/builders/css_builder'
|
|
8
|
+
|
|
9
|
+
module Capybara
|
|
10
|
+
class Selector
|
|
11
|
+
class Definition
|
|
12
|
+
attr_reader :name, :expressions
|
|
13
|
+
extend Forwardable
|
|
14
|
+
|
|
15
|
+
def initialize(name, locator_type: nil, raw_locator: false, supports_exact: nil, &block)
|
|
16
|
+
@name = name
|
|
17
|
+
@filter_set = Capybara::Selector::FilterSet.add(name) {}
|
|
18
|
+
@match = nil
|
|
19
|
+
@label = nil
|
|
20
|
+
@failure_message = nil
|
|
21
|
+
@expressions = {}
|
|
22
|
+
@expression_filters = {}
|
|
23
|
+
@locator_filter = nil
|
|
24
|
+
@default_visibility = nil
|
|
25
|
+
@locator_type = locator_type
|
|
26
|
+
@raw_locator = raw_locator
|
|
27
|
+
@supports_exact = supports_exact
|
|
28
|
+
instance_eval(&block)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def custom_filters
|
|
32
|
+
warn "Deprecated: Selector#custom_filters is not valid when same named expression and node filter exist - don't use"
|
|
33
|
+
node_filters.merge(expression_filters).freeze
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def node_filters
|
|
37
|
+
@filter_set.node_filters
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def expression_filters
|
|
41
|
+
@filter_set.expression_filters
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
#
|
|
46
|
+
# Define a selector by an xpath expression
|
|
47
|
+
#
|
|
48
|
+
# @overload xpath(*expression_filters, &block)
|
|
49
|
+
# @param [Array<Symbol>] expression_filters ([]) Names of filters that are implemented via this expression, if not specified the names of any keyword parameters in the block will be used
|
|
50
|
+
# @yield [locator, options] The block to use to generate the XPath expression
|
|
51
|
+
# @yieldparam [String] locator The locator string passed to the query
|
|
52
|
+
# @yieldparam [Hash] options The options hash passed to the query
|
|
53
|
+
# @yieldreturn [#to_xpath, #to_s] An object that can produce an xpath expression
|
|
54
|
+
#
|
|
55
|
+
# @overload xpath()
|
|
56
|
+
# @return [#call] The block that will be called to generate the XPath expression
|
|
57
|
+
#
|
|
58
|
+
def xpath(*allowed_filters, &block)
|
|
59
|
+
expression(:xpath, allowed_filters, &block)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
#
|
|
64
|
+
# Define a selector by a CSS selector
|
|
65
|
+
#
|
|
66
|
+
# @overload css(*expression_filters, &block)
|
|
67
|
+
# @param [Array<Symbol>] expression_filters ([]) Names of filters that can be implemented via this CSS selector
|
|
68
|
+
# @yield [locator, options] The block to use to generate the CSS selector
|
|
69
|
+
# @yieldparam [String] locator The locator string passed to the query
|
|
70
|
+
# @yieldparam [Hash] options The options hash passed to the query
|
|
71
|
+
# @yieldreturn [#to_s] An object that can produce a CSS selector
|
|
72
|
+
#
|
|
73
|
+
# @overload css()
|
|
74
|
+
# @return [#call] The block that will be called to generate the CSS selector
|
|
75
|
+
#
|
|
76
|
+
def css(*allowed_filters, &block)
|
|
77
|
+
expression(:css, allowed_filters, &block)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
#
|
|
82
|
+
# Automatic selector detection
|
|
83
|
+
#
|
|
84
|
+
# @yield [locator] This block takes the passed in locator string and returns whether or not it matches the selector
|
|
85
|
+
# @yieldparam [String], locator The locator string used to determin if it matches the selector
|
|
86
|
+
# @yieldreturn [Boolean] Whether this selector matches the locator string
|
|
87
|
+
# @return [#call] The block that will be used to detect selector match
|
|
88
|
+
#
|
|
89
|
+
def match(&block)
|
|
90
|
+
@match = block if block
|
|
91
|
+
@match
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
##
|
|
95
|
+
#
|
|
96
|
+
# Set/get a descriptive label for the selector
|
|
97
|
+
#
|
|
98
|
+
# @overload label(label)
|
|
99
|
+
# @param [String] label A descriptive label for this selector - used in error messages
|
|
100
|
+
# @overload label()
|
|
101
|
+
# @return [String] The currently set label
|
|
102
|
+
#
|
|
103
|
+
def label(label = nil)
|
|
104
|
+
@label = label if label
|
|
105
|
+
@label
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
##
|
|
109
|
+
#
|
|
110
|
+
# Description of the selector
|
|
111
|
+
#
|
|
112
|
+
# @!method description(options)
|
|
113
|
+
# @param [Hash] options The options of the query used to generate the description
|
|
114
|
+
# @return [String] Description of the selector when used with the options passed
|
|
115
|
+
def_delegator :@filter_set, :description
|
|
116
|
+
|
|
117
|
+
##
|
|
118
|
+
#
|
|
119
|
+
# Should this selector be used for the passed in locator
|
|
120
|
+
#
|
|
121
|
+
# This is used by the automatic selector selection mechanism when no selector type is passed to a selector query
|
|
122
|
+
#
|
|
123
|
+
# @param [String] locator The locator passed to the query
|
|
124
|
+
# @return [Boolean] Whether or not to use this selector
|
|
125
|
+
#
|
|
126
|
+
def match?(locator)
|
|
127
|
+
@match&.call(locator)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
##
|
|
131
|
+
#
|
|
132
|
+
# Define a node filter for use with this selector
|
|
133
|
+
#
|
|
134
|
+
# @!method node_filter(name, *types, options={}, &block)
|
|
135
|
+
# @param [Symbol, Regexp] name The filter name
|
|
136
|
+
# @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
|
|
137
|
+
# @param [Hash] options ({}) Options of the filter
|
|
138
|
+
# @option options [Array<>] :valid_values Valid values for this filter
|
|
139
|
+
# @option options :default The default value of the filter (if any)
|
|
140
|
+
# @option options :skip_if Value of the filter that will cause it to be skipped
|
|
141
|
+
# @option options [Regexp] :matcher (nil) A Regexp used to check whether a specific option is handled by this filter. If not provided the filter will be used for options matching the filter name.
|
|
142
|
+
#
|
|
143
|
+
# If a Symbol is passed for the name the block should accept | node, option_value |, while if a Regexp
|
|
144
|
+
# is passed for the name the block should accept | node, option_name, option_value |. In either case
|
|
145
|
+
# the block should return `true` if the node passes the filer or `false` if it doesn't
|
|
146
|
+
|
|
147
|
+
##
|
|
148
|
+
#
|
|
149
|
+
# Define an expression filter for use with this selector
|
|
150
|
+
#
|
|
151
|
+
# @!method expression_filter(name, *types, matcher: nil, **options, &block)
|
|
152
|
+
# @param [Symbol, Regexp] name The filter name
|
|
153
|
+
# @param [Regexp] matcher (nil) A Regexp used to check whether a specific option is handled by this filter
|
|
154
|
+
# @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
|
|
155
|
+
# @param [Hash] options ({}) Options of the filter
|
|
156
|
+
# @option options [Array<>] :valid_values Valid values for this filter
|
|
157
|
+
# @option options :default The default value of the filter (if any)
|
|
158
|
+
# @option options :skip_if Value of the filter that will cause it to be skipped
|
|
159
|
+
# @option options [Regexp] :matcher (nil) A Regexp used to check whether a specific option is handled by this filter. If not provided the filter will be used for options matching the filter name.
|
|
160
|
+
#
|
|
161
|
+
# If a Symbol is passed for the name the block should accept | current_expression, option_value |, while if a Regexp
|
|
162
|
+
# is passed for the name the block should accept | current_expression, option_name, option_value |. In either case
|
|
163
|
+
# the block should return the modified expression
|
|
164
|
+
|
|
165
|
+
def_delegators :@filter_set, :node_filter, :expression_filter, :filter
|
|
166
|
+
|
|
167
|
+
def locator_filter(*types, **options, &block)
|
|
168
|
+
types.each { |type| options[type] = true }
|
|
169
|
+
@locator_filter = Capybara::Selector::Filters::LocatorFilter.new(block, **options) if block
|
|
170
|
+
@locator_filter
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def filter_set(name, filters_to_use = nil)
|
|
174
|
+
@filter_set.import(name, filters_to_use)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def_delegator :@filter_set, :describe
|
|
178
|
+
|
|
179
|
+
def describe_expression_filters(&block)
|
|
180
|
+
if block_given?
|
|
181
|
+
describe(:expression_filters, &block)
|
|
182
|
+
else
|
|
183
|
+
describe(:expression_filters) do |**options|
|
|
184
|
+
describe_all_expression_filters(**options)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def describe_all_expression_filters(**opts)
|
|
190
|
+
expression_filters.map do |ef_name, ef|
|
|
191
|
+
if ef.matcher?
|
|
192
|
+
handled_custom_keys(ef, opts.keys).map { |key| " with #{ef_name}[#{key} => #{opts[key]}]" }.join
|
|
193
|
+
elsif opts.key?(ef_name)
|
|
194
|
+
" with #{ef_name} #{opts[ef_name]}"
|
|
195
|
+
end
|
|
196
|
+
end.join
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def describe_node_filters(&block)
|
|
200
|
+
describe(:node_filters, &block)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
##
|
|
204
|
+
#
|
|
205
|
+
# Set the default visibility mode that shouble be used if no visibile option is passed when using the selector.
|
|
206
|
+
# If not specified will default to the behavior indicated by Capybara.ignore_hidden_elements
|
|
207
|
+
#
|
|
208
|
+
# @param [Symbol] default_visibility Only find elements with the specified visibility:
|
|
209
|
+
# * :all - finds visible and invisible elements.
|
|
210
|
+
# * :hidden - only finds invisible elements.
|
|
211
|
+
# * :visible - only finds visible elements.
|
|
212
|
+
def visible(default_visibility = nil, &block)
|
|
213
|
+
@default_visibility = block || default_visibility
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def default_visibility(fallback = Capybara.ignore_hidden_elements, options = {})
|
|
217
|
+
vis = if @default_visibility&.respond_to?(:call)
|
|
218
|
+
@default_visibility.call(options)
|
|
219
|
+
else
|
|
220
|
+
@default_visibility
|
|
221
|
+
end
|
|
222
|
+
vis.nil? ? fallback : vis
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# @api private
|
|
226
|
+
def raw_locator?
|
|
227
|
+
!!@raw_locator
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# @api private
|
|
231
|
+
def supports_exact?
|
|
232
|
+
@supports_exact
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def default_format
|
|
236
|
+
return nil if @expressions.keys.empty?
|
|
237
|
+
|
|
238
|
+
if @expressions.size == 1
|
|
239
|
+
@expressions.keys.first
|
|
240
|
+
else
|
|
241
|
+
:xpath
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# @api private
|
|
246
|
+
def locator_types
|
|
247
|
+
return nil unless @locator_type
|
|
248
|
+
|
|
249
|
+
Array(@locator_type)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
private
|
|
253
|
+
|
|
254
|
+
def handled_custom_keys(filter, keys)
|
|
255
|
+
keys.select do |key|
|
|
256
|
+
filter.handles_option?(key) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(key)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def parameter_names(block)
|
|
261
|
+
block.parameters.select { |(type, _name)| %i[key keyreq].include? type }.map { |(_type, name)| name }
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def expression(type, allowed_filters, &block)
|
|
265
|
+
if block
|
|
266
|
+
@expressions[type] = block
|
|
267
|
+
allowed_filters = parameter_names(block) if allowed_filters.empty?
|
|
268
|
+
allowed_filters.flatten.each do |ef|
|
|
269
|
+
expression_filters[ef] = Capybara::Selector::Filters::IdentityExpressionFilter.new(ef)
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
@expressions[type]
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|