capybara 2.18.0 → 3.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/History.md +945 -12
- data/License.txt +1 -1
- data/README.md +264 -90
- data/lib/capybara/config.rb +29 -57
- data/lib/capybara/cucumber.rb +2 -3
- data/lib/capybara/driver/base.rb +35 -18
- data/lib/capybara/driver/node.rb +40 -10
- data/lib/capybara/dsl.rb +10 -7
- data/lib/capybara/helpers.rb +70 -31
- data/lib/capybara/minitest/spec.rb +185 -83
- data/lib/capybara/minitest.rb +232 -112
- data/lib/capybara/node/actions.rb +274 -171
- data/lib/capybara/node/base.rb +42 -34
- data/lib/capybara/node/document.rb +15 -3
- data/lib/capybara/node/document_matchers.rb +19 -21
- data/lib/capybara/node/element.rb +362 -135
- data/lib/capybara/node/finders.rb +151 -137
- data/lib/capybara/node/matchers.rb +394 -209
- data/lib/capybara/node/simple.rb +59 -26
- 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 +12 -9
- data/lib/capybara/queries/base_query.rb +39 -28
- data/lib/capybara/queries/current_path_query.rb +21 -27
- data/lib/capybara/queries/match_query.rb +14 -7
- data/lib/capybara/queries/selector_query.rb +659 -149
- data/lib/capybara/queries/sibling_query.rb +11 -9
- data/lib/capybara/queries/style_query.rb +45 -0
- data/lib/capybara/queries/text_query.rb +56 -38
- data/lib/capybara/queries/title_query.rb +8 -11
- data/lib/capybara/rack_test/browser.rb +114 -42
- data/lib/capybara/rack_test/css_handlers.rb +6 -4
- data/lib/capybara/rack_test/driver.rb +22 -17
- data/lib/capybara/rack_test/errors.rb +6 -0
- data/lib/capybara/rack_test/form.rb +93 -58
- data/lib/capybara/rack_test/node.rb +184 -81
- data/lib/capybara/rails.rb +3 -7
- 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 +97 -63
- data/lib/capybara/rspec/features.rb +17 -50
- data/lib/capybara/rspec/matcher_proxies.rb +52 -15
- 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 +146 -310
- data/lib/capybara/rspec.rb +7 -11
- data/lib/capybara/selector/builders/css_builder.rb +84 -0
- data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
- data/lib/capybara/selector/css.rb +85 -13
- 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 +2 -17
- data/lib/capybara/selector/filter_set.rb +80 -33
- data/lib/capybara/selector/filters/base.rb +50 -6
- data/lib/capybara/selector/filters/expression_filter.rb +8 -26
- data/lib/capybara/selector/filters/locator_filter.rb +29 -0
- data/lib/capybara/selector/filters/node_filter.rb +16 -12
- data/lib/capybara/selector/regexp_disassembler.rb +211 -0
- data/lib/capybara/selector/selector.rb +93 -210
- data/lib/capybara/selector/xpath_extensions.rb +17 -0
- data/lib/capybara/selector.rb +475 -523
- 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 +298 -267
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +117 -0
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +84 -0
- data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
- data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
- data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
- data/lib/capybara/selenium/extensions/find.rb +110 -0
- data/lib/capybara/selenium/extensions/html5_drag.rb +229 -0
- data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
- data/lib/capybara/selenium/extensions/scroll.rb +76 -0
- data/lib/capybara/selenium/node.rb +517 -145
- data/lib/capybara/selenium/nodes/chrome_node.rb +125 -0
- data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
- data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
- data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
- data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
- data/lib/capybara/selenium/patches/atoms.rb +18 -0
- data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
- data/lib/capybara/selenium/patches/logs.rb +45 -0
- data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
- data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
- data/lib/capybara/server/animation_disabler.rb +80 -0
- data/lib/capybara/server/checker.rb +44 -0
- data/lib/capybara/server/middleware.rb +71 -0
- data/lib/capybara/server.rb +59 -67
- data/lib/capybara/session/config.rb +81 -67
- data/lib/capybara/session/matchers.rb +28 -20
- data/lib/capybara/session.rb +337 -365
- 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 +151 -12
- data/lib/capybara/spec/session/accept_alert_spec.rb +12 -11
- data/lib/capybara/spec/session/accept_confirm_spec.rb +6 -5
- data/lib/capybara/spec/session/accept_prompt_spec.rb +10 -10
- data/lib/capybara/spec/session/active_element_spec.rb +31 -0
- data/lib/capybara/spec/session/all_spec.rb +161 -57
- data/lib/capybara/spec/session/ancestor_spec.rb +27 -24
- data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +68 -38
- 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.rb → assert_title_spec.rb} +22 -12
- data/lib/capybara/spec/session/attach_file_spec.rb +144 -69
- data/lib/capybara/spec/session/body_spec.rb +12 -13
- data/lib/capybara/spec/session/check_spec.rb +117 -55
- data/lib/capybara/spec/session/choose_spec.rb +64 -31
- data/lib/capybara/spec/session/click_button_spec.rb +231 -173
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +55 -35
- data/lib/capybara/spec/session/click_link_spec.rb +93 -58
- data/lib/capybara/spec/session/current_scope_spec.rb +12 -11
- data/lib/capybara/spec/session/current_url_spec.rb +57 -39
- data/lib/capybara/spec/session/dismiss_confirm_spec.rb +4 -4
- data/lib/capybara/spec/session/dismiss_prompt_spec.rb +3 -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 +9 -7
- data/lib/capybara/spec/session/element/matches_selector_spec.rb +71 -57
- data/lib/capybara/spec/session/evaluate_async_script_spec.rb +8 -7
- data/lib/capybara/spec/session/evaluate_script_spec.rb +29 -8
- data/lib/capybara/spec/session/execute_script_spec.rb +10 -8
- data/lib/capybara/spec/session/fill_in_spec.rb +134 -43
- data/lib/capybara/spec/session/find_button_spec.rb +25 -24
- data/lib/capybara/spec/session/find_by_id_spec.rb +10 -9
- data/lib/capybara/spec/session/find_field_spec.rb +37 -41
- data/lib/capybara/spec/session/find_link_spec.rb +46 -17
- data/lib/capybara/spec/session/find_spec.rb +260 -145
- data/lib/capybara/spec/session/first_spec.rb +80 -52
- 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 +33 -20
- data/lib/capybara/spec/session/frame/within_frame_spec.rb +52 -32
- data/lib/capybara/spec/session/go_back_spec.rb +2 -1
- data/lib/capybara/spec/session/go_forward_spec.rb +2 -1
- data/lib/capybara/spec/session/has_all_selectors_spec.rb +31 -31
- 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 +100 -13
- data/lib/capybara/spec/session/has_css_spec.rb +272 -137
- data/lib/capybara/spec/session/has_current_path_spec.rb +60 -61
- data/lib/capybara/spec/session/has_element_spec.rb +47 -0
- data/lib/capybara/spec/session/has_field_spec.rb +139 -59
- data/lib/capybara/spec/session/has_link_spec.rb +47 -6
- data/lib/capybara/spec/session/has_none_selectors_spec.rb +42 -40
- data/lib/capybara/spec/session/has_select_spec.rb +107 -72
- data/lib/capybara/spec/session/has_selector_spec.rb +120 -71
- data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
- data/lib/capybara/spec/session/has_table_spec.rb +183 -5
- data/lib/capybara/spec/session/has_text_spec.rb +106 -62
- data/lib/capybara/spec/session/has_title_spec.rb +20 -14
- data/lib/capybara/spec/session/has_xpath_spec.rb +57 -38
- data/lib/capybara/spec/session/{headers.rb → headers_spec.rb} +3 -2
- data/lib/capybara/spec/session/html_spec.rb +14 -6
- data/lib/capybara/spec/session/matches_style_spec.rb +37 -0
- data/lib/capybara/spec/session/node_spec.rb +1024 -153
- data/lib/capybara/spec/session/node_wrapper_spec.rb +39 -0
- data/lib/capybara/spec/session/refresh_spec.rb +12 -6
- data/lib/capybara/spec/session/reset_session_spec.rb +82 -35
- data/lib/capybara/spec/session/{response_code.rb → response_code_spec.rb} +2 -1
- data/lib/capybara/spec/session/save_and_open_page_spec.rb +3 -2
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +8 -12
- data/lib/capybara/spec/session/save_page_spec.rb +42 -55
- data/lib/capybara/spec/session/save_screenshot_spec.rb +16 -14
- 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 +107 -81
- data/lib/capybara/spec/session/selectors_spec.rb +52 -19
- data/lib/capybara/spec/session/sibling_spec.rb +10 -10
- data/lib/capybara/spec/session/text_spec.rb +37 -21
- data/lib/capybara/spec/session/title_spec.rb +17 -5
- data/lib/capybara/spec/session/uncheck_spec.rb +43 -23
- data/lib/capybara/spec/session/unselect_spec.rb +39 -38
- data/lib/capybara/spec/session/visit_spec.rb +85 -53
- data/lib/capybara/spec/session/window/become_closed_spec.rb +24 -20
- data/lib/capybara/spec/session/window/current_window_spec.rb +5 -3
- data/lib/capybara/spec/session/window/open_new_window_spec.rb +5 -3
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +27 -22
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +12 -6
- data/lib/capybara/spec/session/window/window_spec.rb +97 -63
- data/lib/capybara/spec/session/window/windows_spec.rb +12 -10
- data/lib/capybara/spec/session/window/within_window_spec.rb +31 -86
- data/lib/capybara/spec/session/within_spec.rb +83 -44
- data/lib/capybara/spec/spec_helper.rb +54 -44
- data/lib/capybara/spec/test_app.rb +158 -43
- data/lib/capybara/spec/views/animated.erb +49 -0
- data/lib/capybara/spec/views/form.erb +163 -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 +69 -12
- data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
- data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
- data/lib/capybara/spec/views/with_js.erb +30 -5
- data/lib/capybara/spec/views/with_jstree.erb +26 -0
- data/lib/capybara/spec/views/with_namespace.erb +21 -0
- data/lib/capybara/spec/views/with_scope.erb +2 -2
- data/lib/capybara/spec/views/with_scope_other.erb +6 -0
- data/lib/capybara/spec/views/with_shadow.erb +31 -0
- data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
- data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
- data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
- data/lib/capybara/spec/views/with_windows.erb +1 -1
- data/lib/capybara/spec/views/within_frames.erb +5 -2
- data/lib/capybara/version.rb +2 -1
- data/lib/capybara/window.rb +36 -34
- data/lib/capybara.rb +134 -107
- data/spec/basic_node_spec.rb +60 -34
- data/spec/capybara_spec.rb +63 -88
- data/spec/counter_spec.rb +35 -0
- data/spec/css_builder_spec.rb +101 -0
- data/spec/css_splitter_spec.rb +38 -0
- data/spec/dsl_spec.rb +85 -64
- data/spec/filter_set_spec.rb +27 -9
- data/spec/fixtures/certificate.pem +25 -0
- data/spec/fixtures/key.pem +27 -0
- data/spec/fixtures/selenium_driver_rspec_failure.rb +6 -5
- data/spec/fixtures/selenium_driver_rspec_success.rb +6 -5
- data/spec/minitest_spec.rb +52 -7
- data/spec/minitest_spec_spec.rb +94 -63
- data/spec/per_session_config_spec.rb +14 -13
- data/spec/rack_test_spec.rb +194 -125
- data/spec/regexp_dissassembler_spec.rb +250 -0
- data/spec/result_spec.rb +111 -50
- data/spec/rspec/features_spec.rb +37 -31
- data/spec/rspec/scenarios_spec.rb +10 -8
- data/spec/rspec/shared_spec_matchers.rb +473 -422
- data/spec/rspec/views_spec.rb +5 -3
- data/spec/rspec_matchers_spec.rb +52 -11
- data/spec/rspec_spec.rb +109 -89
- data/spec/sauce_spec_chrome.rb +43 -0
- data/spec/selector_spec.rb +397 -68
- data/spec/selenium_spec_chrome.rb +187 -40
- data/spec/selenium_spec_chrome_remote.rb +96 -0
- data/spec/selenium_spec_edge.rb +60 -0
- data/spec/selenium_spec_firefox.rb +201 -41
- data/spec/selenium_spec_firefox_remote.rb +94 -0
- data/spec/selenium_spec_ie.rb +149 -0
- data/spec/selenium_spec_safari.rb +162 -0
- data/spec/server_spec.rb +213 -102
- data/spec/session_spec.rb +53 -16
- data/spec/shared_selenium_node.rb +79 -0
- data/spec/shared_selenium_session.rb +473 -122
- data/spec/spec_helper.rb +126 -7
- data/spec/whitespace_normalizer_spec.rb +54 -0
- data/spec/xpath_builder_spec.rb +93 -0
- metadata +355 -73
- 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/query.rb +0 -7
- data/lib/capybara/rspec/compound.rb +0 -95
- data/lib/capybara/spec/session/assert_current_path.rb +0 -72
- data/lib/capybara/spec/session/assert_selector.rb +0 -148
- data/lib/capybara/spec/session/assert_text.rb +0 -234
- 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 -127
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Selenium specific implementation of the Capybara::Driver::Node API
|
|
4
|
+
|
|
5
|
+
require 'capybara/selenium/extensions/find'
|
|
6
|
+
require 'capybara/selenium/extensions/scroll'
|
|
7
|
+
require 'capybara/node/whitespace_normalizer'
|
|
8
|
+
|
|
2
9
|
class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
10
|
+
include Capybara::Node::WhitespaceNormalizer
|
|
11
|
+
include Capybara::Selenium::Find
|
|
12
|
+
include Capybara::Selenium::Scroll
|
|
3
13
|
|
|
4
14
|
def visible_text
|
|
5
|
-
|
|
6
|
-
|
|
15
|
+
raise NotImplementedError, 'Getting visible text is not currently supported directly on shadow roots' if shadow_root?
|
|
16
|
+
|
|
17
|
+
native.text
|
|
7
18
|
end
|
|
8
19
|
|
|
9
20
|
def all_text
|
|
10
|
-
text = driver.
|
|
11
|
-
|
|
21
|
+
text = driver.evaluate_script('arguments[0].textContent', self) || ''
|
|
22
|
+
normalize_spacing(text)
|
|
12
23
|
end
|
|
13
24
|
|
|
14
25
|
def [](name)
|
|
@@ -18,13 +29,17 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
18
29
|
end
|
|
19
30
|
|
|
20
31
|
def value
|
|
21
|
-
if tag_name ==
|
|
22
|
-
native.find_elements(:css,
|
|
32
|
+
if tag_name == 'select' && multiple?
|
|
33
|
+
native.find_elements(:css, 'option:checked').map { |el| el[:value] || el.text }
|
|
23
34
|
else
|
|
24
35
|
native[:value]
|
|
25
36
|
end
|
|
26
37
|
end
|
|
27
38
|
|
|
39
|
+
def style(styles)
|
|
40
|
+
styles.to_h { |style| [style, native.css_value(style)] }
|
|
41
|
+
end
|
|
42
|
+
|
|
28
43
|
##
|
|
29
44
|
#
|
|
30
45
|
# Set the value of the form element to the given value.
|
|
@@ -36,91 +51,102 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
36
51
|
# :none => append the new value to the existing value <br/>
|
|
37
52
|
# :backspace => send backspace keystrokes to clear the field <br/>
|
|
38
53
|
# Array => an array of keys to send before the value being set, e.g. [[:command, 'a'], :backspace]
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
# @option options [Boolean] :rapid (nil) Whether setting text inputs should use a faster "rapid" mode<br/>
|
|
55
|
+
# nil => Text inputs with length greater than 30 characters will be set using a faster driver script mode<br/>
|
|
56
|
+
# true => Rapid mode will be used regardless of input length<br/>
|
|
57
|
+
# false => Sends keys via conventional mode. This may be required to avoid losing key-presses if you have certain
|
|
58
|
+
# Javascript interactions on form inputs<br/>
|
|
59
|
+
def set(value, **options)
|
|
60
|
+
if value.is_a?(Array) && !multiple?
|
|
61
|
+
raise ArgumentError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
|
|
45
62
|
end
|
|
46
63
|
|
|
64
|
+
tag_name, type = attrs(:tagName, :type).map { |val| val&.downcase }
|
|
65
|
+
@tag_name ||= tag_name
|
|
66
|
+
|
|
47
67
|
case tag_name
|
|
48
68
|
when 'input'
|
|
49
69
|
case type
|
|
50
70
|
when 'radio'
|
|
51
71
|
click
|
|
52
72
|
when 'checkbox'
|
|
53
|
-
click if value ^
|
|
73
|
+
click if value ^ checked?
|
|
54
74
|
when 'file'
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
75
|
+
set_file(value)
|
|
76
|
+
when 'date'
|
|
77
|
+
set_date(value)
|
|
78
|
+
when 'time'
|
|
79
|
+
set_time(value)
|
|
80
|
+
when 'datetime-local'
|
|
81
|
+
set_datetime_local(value)
|
|
82
|
+
when 'color'
|
|
83
|
+
set_color(value)
|
|
84
|
+
when 'range'
|
|
85
|
+
set_range(value)
|
|
61
86
|
else
|
|
62
|
-
set_text(value, options)
|
|
87
|
+
set_text(value, **options)
|
|
63
88
|
end
|
|
64
89
|
when 'textarea'
|
|
65
|
-
set_text(value, options)
|
|
90
|
+
set_text(value, **options)
|
|
66
91
|
else
|
|
67
|
-
|
|
68
|
-
#ensure we are focused on the element
|
|
69
|
-
click
|
|
70
|
-
|
|
71
|
-
script = <<-JS
|
|
72
|
-
var range = document.createRange();
|
|
73
|
-
var sel = window.getSelection();
|
|
74
|
-
arguments[0].focus();
|
|
75
|
-
range.selectNodeContents(arguments[0]);
|
|
76
|
-
sel.removeAllRanges();
|
|
77
|
-
sel.addRange(range);
|
|
78
|
-
JS
|
|
79
|
-
driver.execute_script script, self
|
|
80
|
-
|
|
81
|
-
if driver.chrome? || driver.firefox?
|
|
82
|
-
# chromedriver raises a can't focus element for child elements if we use native.send_keys
|
|
83
|
-
# we've already focused it so just use action api
|
|
84
|
-
driver.browser.action.send_keys(value.to_s).perform
|
|
85
|
-
else
|
|
86
|
-
# action api is really slow here just use native.send_keys
|
|
87
|
-
native.send_keys(value.to_s)
|
|
88
|
-
end
|
|
89
|
-
end
|
|
92
|
+
set_content_editable(value)
|
|
90
93
|
end
|
|
91
94
|
end
|
|
92
95
|
|
|
93
96
|
def select_option
|
|
94
|
-
|
|
97
|
+
click unless selected? || disabled?
|
|
95
98
|
end
|
|
96
99
|
|
|
97
100
|
def unselect_option
|
|
98
|
-
raise Capybara::UnselectNotAllowed,
|
|
99
|
-
|
|
101
|
+
raise Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.' unless select_node.multiple?
|
|
102
|
+
|
|
103
|
+
click if selected?
|
|
100
104
|
end
|
|
101
105
|
|
|
102
|
-
def click
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
def click(keys = [], **options)
|
|
107
|
+
click_options = ClickOptions.new(keys, options)
|
|
108
|
+
return native.click if click_options.empty?
|
|
109
|
+
|
|
110
|
+
perform_with_options(click_options) do |action|
|
|
111
|
+
target = click_options.coords? ? nil : native
|
|
112
|
+
if click_options.delay.zero?
|
|
113
|
+
action.click(target)
|
|
114
|
+
else
|
|
115
|
+
action.click_and_hold(target)
|
|
116
|
+
action_pause(action, click_options.delay)
|
|
117
|
+
action.release
|
|
110
118
|
end
|
|
111
119
|
end
|
|
120
|
+
rescue StandardError => e
|
|
121
|
+
if e.is_a?(::Selenium::WebDriver::Error::ElementClickInterceptedError) ||
|
|
122
|
+
e.message.include?('Other element would receive the click')
|
|
123
|
+
scroll_to_center
|
|
124
|
+
end
|
|
125
|
+
|
|
112
126
|
raise e
|
|
113
127
|
end
|
|
114
128
|
|
|
115
|
-
def right_click
|
|
116
|
-
|
|
117
|
-
|
|
129
|
+
def right_click(keys = [], **options)
|
|
130
|
+
click_options = ClickOptions.new(keys, options)
|
|
131
|
+
perform_with_options(click_options) do |action|
|
|
132
|
+
target = click_options.coords? ? nil : native
|
|
133
|
+
if click_options.delay.zero?
|
|
134
|
+
action.context_click(target)
|
|
135
|
+
else
|
|
136
|
+
action.move_to(target) if target
|
|
137
|
+
action.pointer_down(:right).then do |act|
|
|
138
|
+
action_pause(act, click_options.delay)
|
|
139
|
+
end.pointer_up(:right)
|
|
140
|
+
end
|
|
118
141
|
end
|
|
119
142
|
end
|
|
120
143
|
|
|
121
|
-
def double_click
|
|
122
|
-
|
|
123
|
-
|
|
144
|
+
def double_click(keys = [], **options)
|
|
145
|
+
click_options = ClickOptions.new(keys, options)
|
|
146
|
+
raise ArgumentError, "double_click doesn't support a delay option" unless click_options.delay.zero?
|
|
147
|
+
|
|
148
|
+
perform_with_options(click_options) do |action|
|
|
149
|
+
click_options.coords? ? action.double_click : action.double_click(native)
|
|
124
150
|
end
|
|
125
151
|
end
|
|
126
152
|
|
|
@@ -129,136 +155,482 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
129
155
|
end
|
|
130
156
|
|
|
131
157
|
def hover
|
|
132
|
-
scroll_if_needed
|
|
133
|
-
driver.browser.action.move_to(native).perform
|
|
134
|
-
end
|
|
158
|
+
scroll_if_needed { browser_action.move_to(native).perform }
|
|
135
159
|
end
|
|
136
160
|
|
|
137
|
-
def drag_to(element)
|
|
138
|
-
|
|
139
|
-
|
|
161
|
+
def drag_to(element, drop_modifiers: [], **)
|
|
162
|
+
drop_modifiers = Array(drop_modifiers)
|
|
163
|
+
# Due to W3C spec compliance - The Actions API no longer scrolls to elements when necessary
|
|
164
|
+
# which means Seleniums `drag_and_drop` is now broken - do it manually
|
|
165
|
+
scroll_if_needed { browser_action.click_and_hold(native).perform }
|
|
166
|
+
# element.scroll_if_needed { browser_action.move_to(element.native).release.perform }
|
|
167
|
+
element.scroll_if_needed do
|
|
168
|
+
keys_down = modifiers_down(browser_action, drop_modifiers)
|
|
169
|
+
keys_up = modifiers_up(keys_down.move_to(element.native).release, drop_modifiers)
|
|
170
|
+
keys_up.perform
|
|
140
171
|
end
|
|
141
172
|
end
|
|
142
173
|
|
|
143
|
-
def
|
|
144
|
-
|
|
174
|
+
def drop(*_)
|
|
175
|
+
raise NotImplementedError, 'Out of browser drop emulation is not implemented for the current browser'
|
|
145
176
|
end
|
|
146
177
|
|
|
147
|
-
def
|
|
148
|
-
|
|
149
|
-
|
|
178
|
+
def tag_name
|
|
179
|
+
@tag_name ||=
|
|
180
|
+
if native.respond_to? :tag_name
|
|
181
|
+
native.tag_name.downcase
|
|
182
|
+
else
|
|
183
|
+
shadow_root? ? 'ShadowRoot' : 'Unknown'
|
|
184
|
+
end
|
|
150
185
|
end
|
|
151
186
|
|
|
152
|
-
def
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
end
|
|
187
|
+
def visible?; boolean_attr(native.displayed?); end
|
|
188
|
+
def readonly?; boolean_attr(self[:readonly]); end
|
|
189
|
+
def multiple?; boolean_attr(self[:multiple]); end
|
|
190
|
+
def selected?; boolean_attr(native.selected?); end
|
|
156
191
|
alias :checked? :selected?
|
|
157
192
|
|
|
158
193
|
def disabled?
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
194
|
+
return true unless native.enabled?
|
|
195
|
+
|
|
196
|
+
# WebDriver only defines `disabled?` for form controls but fieldset makes sense too
|
|
197
|
+
find_xpath('self::fieldset/ancestor-or-self::fieldset[@disabled]').any?
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def content_editable?
|
|
201
|
+
native.attribute('isContentEditable') == 'true'
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def path
|
|
205
|
+
driver.evaluate_script GET_XPATH_SCRIPT, self
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def obscured?(x: nil, y: nil)
|
|
209
|
+
res = driver.evaluate_script(OBSCURED_OR_OFFSET_SCRIPT, self, x, y)
|
|
210
|
+
return true if res == true
|
|
211
|
+
|
|
212
|
+
driver.frame_obscured_at?(x: res['x'], y: res['y'])
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def rect
|
|
216
|
+
native.rect
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def shadow_root
|
|
220
|
+
root = native.shadow_root
|
|
221
|
+
root && build_node(native.shadow_root)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
protected
|
|
225
|
+
|
|
226
|
+
def scroll_if_needed
|
|
227
|
+
yield
|
|
228
|
+
rescue ::Selenium::WebDriver::Error::MoveTargetOutOfBoundsError
|
|
229
|
+
scroll_to_center
|
|
230
|
+
yield
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def scroll_to_center
|
|
234
|
+
script = <<-JS
|
|
235
|
+
try {
|
|
236
|
+
arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
|
|
237
|
+
} catch(e) {
|
|
238
|
+
arguments[0].scrollIntoView(true);
|
|
239
|
+
}
|
|
240
|
+
JS
|
|
241
|
+
begin
|
|
242
|
+
driver.execute_script(script, self)
|
|
243
|
+
rescue StandardError
|
|
244
|
+
# Swallow error if scrollIntoView with options isn't supported
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
private
|
|
249
|
+
|
|
250
|
+
def sibling_index(parent, node, selector)
|
|
251
|
+
siblings = parent.find_xpath(selector)
|
|
252
|
+
case siblings.size
|
|
253
|
+
when 0
|
|
254
|
+
'[ERROR]' # IE doesn't support full XPath (namespace-uri, etc)
|
|
255
|
+
when 1
|
|
256
|
+
'' # index not necessary when only one matching element
|
|
257
|
+
else
|
|
258
|
+
idx = siblings.index(node)
|
|
259
|
+
# Element may not be found in the siblings if it has gone away
|
|
260
|
+
idx.nil? ? '[ERROR]' : "[#{idx + 1}]"
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def boolean_attr(val)
|
|
265
|
+
val && (val != 'false')
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# a reference to the select node if this is an option node
|
|
269
|
+
def select_node
|
|
270
|
+
find_xpath(XPath.ancestor(:select)[1]).first
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def set_text(value, clear: nil, rapid: nil, **_unused)
|
|
274
|
+
value = value.to_s
|
|
275
|
+
if value.empty? && clear.nil?
|
|
276
|
+
native.clear
|
|
277
|
+
elsif clear == :backspace
|
|
278
|
+
# Clear field by sending the correct number of backspace keys.
|
|
279
|
+
backspaces = [:backspace] * self.value.to_s.length
|
|
280
|
+
send_keys(:end, *backspaces, value)
|
|
281
|
+
elsif clear.is_a? Array
|
|
282
|
+
send_keys(*clear, value)
|
|
283
|
+
else
|
|
284
|
+
driver.execute_script 'arguments[0].select()', self unless clear == :none
|
|
285
|
+
if rapid == true || ((value.length > auto_rapid_set_length) && rapid != false)
|
|
286
|
+
send_keys(value[0..3])
|
|
287
|
+
driver.execute_script RAPID_APPEND_TEXT, self, value[4...-3]
|
|
288
|
+
send_keys(value[-3..])
|
|
163
289
|
else
|
|
164
|
-
|
|
290
|
+
send_keys(value)
|
|
165
291
|
end
|
|
166
|
-
else
|
|
167
|
-
!native.enabled?
|
|
168
292
|
end
|
|
169
293
|
end
|
|
170
294
|
|
|
171
|
-
def
|
|
172
|
-
|
|
173
|
-
readonly and readonly != "false"
|
|
295
|
+
def auto_rapid_set_length
|
|
296
|
+
30
|
|
174
297
|
end
|
|
175
298
|
|
|
176
|
-
def
|
|
177
|
-
|
|
178
|
-
|
|
299
|
+
def perform_with_options(click_options, &block)
|
|
300
|
+
raise ArgumentError, 'A block must be provided' unless block
|
|
301
|
+
|
|
302
|
+
scroll_if_needed do
|
|
303
|
+
action_with_modifiers(click_options) do |action|
|
|
304
|
+
if block
|
|
305
|
+
yield action
|
|
306
|
+
else
|
|
307
|
+
click_options.coords? ? action.click : action.click(native)
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
179
311
|
end
|
|
180
312
|
|
|
181
|
-
def
|
|
182
|
-
|
|
313
|
+
def set_date(value) # rubocop:disable Naming/AccessorMethodName
|
|
314
|
+
value = SettableValue.new(value)
|
|
315
|
+
return set_text(value) unless value.dateable?
|
|
316
|
+
|
|
317
|
+
# TODO: this would be better if locale can be detected and correct keystrokes sent
|
|
318
|
+
update_value_js(value.to_date_str)
|
|
183
319
|
end
|
|
184
320
|
|
|
185
|
-
def
|
|
186
|
-
|
|
321
|
+
def set_time(value) # rubocop:disable Naming/AccessorMethodName
|
|
322
|
+
value = SettableValue.new(value)
|
|
323
|
+
return set_text(value) unless value.timeable?
|
|
324
|
+
|
|
325
|
+
# TODO: this would be better if locale can be detected and correct keystrokes sent
|
|
326
|
+
update_value_js(value.to_time_str)
|
|
187
327
|
end
|
|
188
328
|
|
|
189
|
-
def
|
|
190
|
-
|
|
329
|
+
def set_datetime_local(value) # rubocop:disable Naming/AccessorMethodName
|
|
330
|
+
value = SettableValue.new(value)
|
|
331
|
+
return set_text(value) unless value.timeable?
|
|
332
|
+
|
|
333
|
+
# TODO: this would be better if locale can be detected and correct keystrokes sent
|
|
334
|
+
update_value_js(value.to_datetime_str)
|
|
191
335
|
end
|
|
192
336
|
|
|
193
|
-
def
|
|
194
|
-
|
|
337
|
+
def set_color(value) # rubocop:disable Naming/AccessorMethodName
|
|
338
|
+
update_value_js(value)
|
|
195
339
|
end
|
|
196
340
|
|
|
197
|
-
def
|
|
198
|
-
|
|
199
|
-
|
|
341
|
+
def set_range(value) # rubocop:disable Naming/AccessorMethodName
|
|
342
|
+
update_value_js(value)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def update_value_js(value)
|
|
346
|
+
driver.execute_script(<<-JS, self, value)
|
|
347
|
+
if (arguments[0].readOnly) { return };
|
|
348
|
+
if (document.activeElement !== arguments[0]){
|
|
349
|
+
arguments[0].focus();
|
|
350
|
+
}
|
|
351
|
+
if (arguments[0].value != arguments[1]) {
|
|
352
|
+
arguments[0].value = arguments[1]
|
|
353
|
+
arguments[0].dispatchEvent(new InputEvent('input'));
|
|
354
|
+
arguments[0].dispatchEvent(new Event('change', { bubbles: true }));
|
|
355
|
+
}
|
|
356
|
+
JS
|
|
357
|
+
end
|
|
200
358
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
359
|
+
def set_file(value) # rubocop:disable Naming/AccessorMethodName
|
|
360
|
+
with_file_detector do
|
|
361
|
+
path_names = value.to_s.empty? ? [] : value
|
|
362
|
+
file_names = Array(path_names).map do |pn|
|
|
363
|
+
Pathname.new(pn).absolute? ? pn : File.expand_path(pn)
|
|
364
|
+
end.join("\n")
|
|
365
|
+
native.send_keys(file_names)
|
|
366
|
+
end
|
|
367
|
+
end
|
|
204
368
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
369
|
+
def with_file_detector
|
|
370
|
+
if driver.options[:browser] == :remote &&
|
|
371
|
+
bridge.respond_to?(:file_detector) &&
|
|
372
|
+
bridge.file_detector.nil?
|
|
373
|
+
begin
|
|
374
|
+
bridge.file_detector = lambda do |(fn, *)|
|
|
375
|
+
str = fn.to_s
|
|
376
|
+
str if File.exist?(str)
|
|
377
|
+
end
|
|
378
|
+
yield
|
|
379
|
+
ensure
|
|
380
|
+
bridge.file_detector = nil
|
|
381
|
+
end
|
|
382
|
+
else
|
|
383
|
+
yield
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def set_content_editable(value) # rubocop:disable Naming/AccessorMethodName
|
|
388
|
+
# Ensure we are focused on the element
|
|
389
|
+
click
|
|
390
|
+
|
|
391
|
+
editable = driver.execute_script <<-JS, self
|
|
392
|
+
if (arguments[0].isContentEditable) {
|
|
393
|
+
var range = document.createRange();
|
|
394
|
+
var sel = window.getSelection();
|
|
395
|
+
arguments[0].focus();
|
|
396
|
+
range.selectNodeContents(arguments[0]);
|
|
397
|
+
sel.removeAllRanges();
|
|
398
|
+
sel.addRange(range);
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
return false;
|
|
402
|
+
JS
|
|
403
|
+
|
|
404
|
+
# The action api has a speed problem but both chrome and firefox 58 raise errors
|
|
405
|
+
# if we use the faster direct send_keys. For now just send_keys to the element
|
|
406
|
+
# we've already focused.
|
|
407
|
+
# native.send_keys(value.to_s)
|
|
408
|
+
browser_action.send_keys(value.to_s).perform if editable
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def action_with_modifiers(click_options)
|
|
412
|
+
actions = browser_action.tap do |acts|
|
|
413
|
+
if click_options.coords?
|
|
414
|
+
if click_options.center_offset?
|
|
415
|
+
acts.move_to(native, *click_options.coords)
|
|
209
416
|
else
|
|
210
|
-
|
|
211
|
-
|
|
417
|
+
right_by, down_by = *click_options.coords
|
|
418
|
+
size = native.size
|
|
419
|
+
left_offset = (size[:width] / 2).to_i
|
|
420
|
+
top_offset = (size[:height] / 2).to_i
|
|
421
|
+
left = -left_offset + right_by
|
|
422
|
+
top = -top_offset + down_by
|
|
423
|
+
acts.move_to(native, left, top)
|
|
212
424
|
end
|
|
213
425
|
else
|
|
214
|
-
|
|
426
|
+
acts.move_to(native)
|
|
215
427
|
end
|
|
216
428
|
end
|
|
429
|
+
modifiers_down(actions, click_options.keys)
|
|
430
|
+
yield actions
|
|
431
|
+
modifiers_up(actions, click_options.keys)
|
|
432
|
+
actions.perform
|
|
433
|
+
ensure
|
|
434
|
+
act = browser_action
|
|
435
|
+
act.release_actions if act.respond_to?(:release_actions)
|
|
436
|
+
end
|
|
217
437
|
|
|
218
|
-
|
|
438
|
+
def modifiers_down(actions, keys)
|
|
439
|
+
each_key(keys) { |key| actions.key_down(key) }
|
|
440
|
+
actions
|
|
219
441
|
end
|
|
220
442
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
find_xpath('./ancestor::select[1]').first
|
|
443
|
+
def modifiers_up(actions, keys)
|
|
444
|
+
each_key(keys) { |key| actions.key_up(key) }
|
|
445
|
+
actions
|
|
225
446
|
end
|
|
226
447
|
|
|
227
|
-
def
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
448
|
+
def browser
|
|
449
|
+
driver.browser
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def bridge
|
|
453
|
+
browser.send(:bridge)
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def browser_action
|
|
457
|
+
browser.action
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def capabilities
|
|
461
|
+
browser.capabilities
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def action_pause(action, duration)
|
|
465
|
+
action.pause(device: action.pointer_inputs.first, duration: duration)
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def normalize_keys(keys)
|
|
469
|
+
keys.map do |key|
|
|
470
|
+
case key
|
|
471
|
+
when :ctrl then :control
|
|
472
|
+
when :command, :cmd then :meta
|
|
241
473
|
else
|
|
242
|
-
|
|
243
|
-
# Script can change a readonly element which user input cannot, so
|
|
244
|
-
# don't execute if readonly.
|
|
245
|
-
driver.execute_script "arguments[0].value = ''", self
|
|
246
|
-
native.send_keys(value.to_s)
|
|
474
|
+
key
|
|
247
475
|
end
|
|
248
476
|
end
|
|
249
477
|
end
|
|
250
478
|
|
|
251
|
-
def
|
|
252
|
-
block
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
479
|
+
def each_key(keys, &block)
|
|
480
|
+
normalize_keys(keys).each(&block)
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def find_context
|
|
484
|
+
native
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def build_node(native_node, initial_cache = {})
|
|
488
|
+
self.class.new(driver, native_node, initial_cache)
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def attrs(*attr_names)
|
|
492
|
+
return attr_names.map { |name| self[name.to_s] } if ENV['CAPYBARA_THOROUGH']
|
|
493
|
+
|
|
494
|
+
driver.evaluate_script <<~JS, self, attr_names.map(&:to_s)
|
|
495
|
+
(function(el, names){
|
|
496
|
+
return names.map(function(name){
|
|
497
|
+
return el[name]
|
|
498
|
+
});
|
|
499
|
+
})(arguments[0], arguments[1]);
|
|
260
500
|
JS
|
|
261
|
-
driver.execute_script(script, self)
|
|
262
|
-
block.call
|
|
263
501
|
end
|
|
502
|
+
|
|
503
|
+
def native_id
|
|
504
|
+
# Selenium 3 -> 4 changed the return of ref
|
|
505
|
+
type_or_id, id = native.ref
|
|
506
|
+
id || type_or_id
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def shadow_root?
|
|
510
|
+
defined?(::Selenium::WebDriver::ShadowRoot) && native.is_a?(::Selenium::WebDriver::ShadowRoot)
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
GET_XPATH_SCRIPT = <<~'JS'
|
|
514
|
+
(function(el, xml){
|
|
515
|
+
var xpath = '';
|
|
516
|
+
var pos, tempitem2;
|
|
517
|
+
|
|
518
|
+
if (el.getRootNode && el.getRootNode() instanceof ShadowRoot) {
|
|
519
|
+
return "(: Shadow DOM element - no XPath :)";
|
|
520
|
+
};
|
|
521
|
+
while(el !== xml.documentElement) {
|
|
522
|
+
pos = 0;
|
|
523
|
+
tempitem2 = el;
|
|
524
|
+
while(tempitem2) {
|
|
525
|
+
if (tempitem2.nodeType === 1 && tempitem2.nodeName === el.nodeName) { // If it is ELEMENT_NODE of the same name
|
|
526
|
+
pos += 1;
|
|
527
|
+
}
|
|
528
|
+
tempitem2 = tempitem2.previousSibling;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (el.namespaceURI != xml.documentElement.namespaceURI) {
|
|
532
|
+
xpath = "*[local-name()='"+el.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']["+pos+']'+'/'+xpath;
|
|
533
|
+
} else {
|
|
534
|
+
xpath = el.nodeName.toUpperCase()+"["+pos+"]/"+xpath;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
el = el.parentNode;
|
|
538
|
+
}
|
|
539
|
+
xpath = '/'+xml.documentElement.nodeName.toUpperCase()+'/'+xpath;
|
|
540
|
+
xpath = xpath.replace(/\/$/, '');
|
|
541
|
+
return xpath;
|
|
542
|
+
})(arguments[0], document)
|
|
543
|
+
JS
|
|
544
|
+
|
|
545
|
+
OBSCURED_OR_OFFSET_SCRIPT = <<~JS
|
|
546
|
+
(function(el, x, y) {
|
|
547
|
+
var box = el.getBoundingClientRect();
|
|
548
|
+
if (x == null) x = box.width/2;
|
|
549
|
+
if (y == null) y = box.height/2 ;
|
|
550
|
+
|
|
551
|
+
var px = box.left + x,
|
|
552
|
+
py = box.top + y,
|
|
553
|
+
e = document.elementFromPoint(px, py);
|
|
554
|
+
|
|
555
|
+
if (!el.contains(e))
|
|
556
|
+
return true;
|
|
557
|
+
|
|
558
|
+
return { x: px, y: py };
|
|
559
|
+
})(arguments[0], arguments[1], arguments[2])
|
|
560
|
+
JS
|
|
561
|
+
|
|
562
|
+
RAPID_APPEND_TEXT = <<~JS
|
|
563
|
+
(function(el, value) {
|
|
564
|
+
value = el.value + value;
|
|
565
|
+
if (el.maxLength && el.maxLength != -1){
|
|
566
|
+
value = value.slice(0, el.maxLength);
|
|
567
|
+
}
|
|
568
|
+
el.value = value;
|
|
569
|
+
})(arguments[0], arguments[1])
|
|
570
|
+
JS
|
|
571
|
+
|
|
572
|
+
# SettableValue encapsulates time/date field formatting
|
|
573
|
+
class SettableValue
|
|
574
|
+
attr_reader :value
|
|
575
|
+
|
|
576
|
+
def initialize(value)
|
|
577
|
+
@value = value
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
def to_s
|
|
581
|
+
value.to_s
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
def dateable?
|
|
585
|
+
!value.is_a?(String) && value.respond_to?(:to_date)
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
def to_date_str
|
|
589
|
+
value.to_date.iso8601
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
def timeable?
|
|
593
|
+
!value.is_a?(String) && value.respond_to?(:to_time)
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
def to_time_str
|
|
597
|
+
value.to_time.strftime('%H:%M')
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
def to_datetime_str
|
|
601
|
+
value.to_time.strftime('%Y-%m-%dT%H:%M')
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
private_constant :SettableValue
|
|
605
|
+
|
|
606
|
+
# ClickOptions encapsulates click option logic
|
|
607
|
+
class ClickOptions
|
|
608
|
+
attr_reader :keys, :options
|
|
609
|
+
|
|
610
|
+
def initialize(keys, options)
|
|
611
|
+
@keys = keys
|
|
612
|
+
@options = options
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
def coords?
|
|
616
|
+
options[:x] && options[:y]
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
def coords
|
|
620
|
+
[options[:x], options[:y]]
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
def center_offset?
|
|
624
|
+
options[:offset] == :center
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
def empty?
|
|
628
|
+
keys.empty? && !coords? && delay.zero?
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
def delay
|
|
632
|
+
options[:delay] || 0
|
|
633
|
+
end
|
|
634
|
+
end
|
|
635
|
+
private_constant :ClickOptions
|
|
264
636
|
end
|