capybara 2.7.0 → 3.35.3
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 +5 -5
- data/.yardopts +1 -0
- data/History.md +1147 -11
- data/License.txt +1 -1
- data/README.md +252 -131
- data/lib/capybara/config.rb +92 -0
- data/lib/capybara/cucumber.rb +3 -3
- data/lib/capybara/driver/base.rb +52 -21
- data/lib/capybara/driver/node.rb +48 -14
- data/lib/capybara/dsl.rb +16 -9
- data/lib/capybara/helpers.rb +72 -81
- data/lib/capybara/minitest/spec.rb +267 -0
- data/lib/capybara/minitest.rb +385 -0
- data/lib/capybara/node/actions.rb +337 -89
- data/lib/capybara/node/base.rb +50 -32
- data/lib/capybara/node/document.rb +19 -3
- data/lib/capybara/node/document_matchers.rb +22 -24
- data/lib/capybara/node/element.rb +388 -125
- data/lib/capybara/node/finders.rb +231 -121
- data/lib/capybara/node/matchers.rb +503 -217
- data/lib/capybara/node/simple.rb +64 -27
- data/lib/capybara/queries/ancestor_query.rb +27 -0
- data/lib/capybara/queries/base_query.rb +87 -11
- data/lib/capybara/queries/current_path_query.rb +24 -24
- data/lib/capybara/queries/match_query.rb +15 -10
- data/lib/capybara/queries/selector_query.rb +675 -81
- 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 +88 -20
- data/lib/capybara/queries/title_query.rb +9 -11
- data/lib/capybara/rack_test/browser.rb +63 -39
- data/lib/capybara/rack_test/css_handlers.rb +6 -4
- data/lib/capybara/rack_test/driver.rb +26 -16
- data/lib/capybara/rack_test/errors.rb +6 -0
- data/lib/capybara/rack_test/form.rb +73 -58
- data/lib/capybara/rack_test/node.rb +187 -67
- data/lib/capybara/rails.rb +4 -8
- 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 +142 -14
- data/lib/capybara/rspec/features.rb +17 -42
- data/lib/capybara/rspec/matcher_proxies.rb +82 -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 +43 -0
- data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
- data/lib/capybara/rspec/matchers.rb +143 -244
- data/lib/capybara/rspec.rb +10 -12
- 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 +102 -0
- data/lib/capybara/selector/definition/button.rb +63 -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 +278 -0
- data/lib/capybara/selector/filter.rb +3 -46
- 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 +155 -0
- data/lib/capybara/selector/xpath_extensions.rb +17 -0
- data/lib/capybara/selector.rb +232 -369
- 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 +380 -142
- 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 +528 -97
- 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 +63 -0
- data/lib/capybara/server/checker.rb +44 -0
- data/lib/capybara/server/middleware.rb +71 -0
- data/lib/capybara/server.rb +74 -71
- data/lib/capybara/session/config.rb +126 -0
- data/lib/capybara/session/matchers.rb +44 -27
- data/lib/capybara/session.rb +500 -297
- data/lib/capybara/spec/fixtures/no_extension +1 -0
- 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 +168 -14
- data/lib/capybara/spec/session/accept_alert_spec.rb +37 -14
- data/lib/capybara/spec/session/accept_confirm_spec.rb +7 -6
- data/lib/capybara/spec/session/accept_prompt_spec.rb +38 -10
- data/lib/capybara/spec/session/all_spec.rb +179 -59
- 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 +154 -48
- data/lib/capybara/spec/session/body_spec.rb +12 -13
- data/lib/capybara/spec/session/check_spec.rb +168 -41
- data/lib/capybara/spec/session/choose_spec.rb +75 -23
- data/lib/capybara/spec/session/click_button_spec.rb +243 -175
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +57 -32
- data/lib/capybara/spec/session/click_link_spec.rb +100 -53
- data/lib/capybara/spec/session/current_scope_spec.rb +11 -10
- data/lib/capybara/spec/session/current_url_spec.rb +61 -35
- data/lib/capybara/spec/session/dismiss_confirm_spec.rb +7 -7
- data/lib/capybara/spec/session/dismiss_prompt_spec.rb +5 -4
- data/lib/capybara/spec/session/element/{assert_match_selector.rb → assert_match_selector_spec.rb} +13 -6
- data/lib/capybara/spec/session/element/match_css_spec.rb +21 -7
- data/lib/capybara/spec/session/element/match_xpath_spec.rb +9 -7
- data/lib/capybara/spec/session/element/matches_selector_spec.rb +91 -34
- data/lib/capybara/spec/session/evaluate_async_script_spec.rb +23 -0
- data/lib/capybara/spec/session/evaluate_script_spec.rb +45 -3
- data/lib/capybara/spec/session/execute_script_spec.rb +24 -4
- data/lib/capybara/spec/session/fill_in_spec.rb +166 -64
- data/lib/capybara/spec/session/find_button_spec.rb +37 -18
- data/lib/capybara/spec/session/find_by_id_spec.rb +10 -9
- data/lib/capybara/spec/session/find_field_spec.rb +57 -34
- data/lib/capybara/spec/session/find_link_spec.rb +47 -10
- data/lib/capybara/spec/session/find_spec.rb +290 -144
- data/lib/capybara/spec/session/first_spec.rb +91 -48
- 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 +3 -2
- data/lib/capybara/spec/session/go_forward_spec.rb +3 -2
- 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 +76 -19
- data/lib/capybara/spec/session/has_css_spec.rb +277 -131
- data/lib/capybara/spec/session/has_current_path_spec.rb +98 -26
- data/lib/capybara/spec/session/has_field_spec.rb +177 -107
- data/lib/capybara/spec/session/has_link_spec.rb +13 -12
- data/lib/capybara/spec/session/has_none_selectors_spec.rb +78 -0
- data/lib/capybara/spec/session/has_select_spec.rb +191 -95
- data/lib/capybara/spec/session/has_selector_spec.rb +128 -64
- data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
- data/lib/capybara/spec/session/has_table_spec.rb +172 -5
- data/lib/capybara/spec/session/has_text_spec.rb +126 -60
- data/lib/capybara/spec/session/has_title_spec.rb +35 -12
- data/lib/capybara/spec/session/has_xpath_spec.rb +74 -53
- 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 +35 -0
- data/lib/capybara/spec/session/node_spec.rb +1028 -131
- data/lib/capybara/spec/session/node_wrapper_spec.rb +39 -0
- data/lib/capybara/spec/session/refresh_spec.rb +34 -0
- data/lib/capybara/spec/session/reset_session_spec.rb +75 -34
- 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 +11 -15
- 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 +117 -0
- data/lib/capybara/spec/session/select_spec.rb +112 -85
- data/lib/capybara/spec/session/selectors_spec.rb +71 -8
- data/lib/capybara/spec/session/sibling_spec.rb +52 -0
- data/lib/capybara/spec/session/text_spec.rb +38 -23
- data/lib/capybara/spec/session/title_spec.rb +17 -5
- data/lib/capybara/spec/session/uncheck_spec.rb +71 -12
- data/lib/capybara/spec/session/unselect_spec.rb +44 -43
- data/lib/capybara/spec/session/visit_spec.rb +99 -32
- data/lib/capybara/spec/session/window/become_closed_spec.rb +33 -29
- 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 +39 -30
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +17 -10
- data/lib/capybara/spec/session/window/window_spec.rb +121 -73
- data/lib/capybara/spec/session/window/windows_spec.rb +12 -10
- data/lib/capybara/spec/session/window/within_window_spec.rb +52 -82
- data/lib/capybara/spec/session/within_spec.rb +76 -43
- data/lib/capybara/spec/spec_helper.rb +67 -33
- data/lib/capybara/spec/test_app.rb +85 -36
- data/lib/capybara/spec/views/animated.erb +49 -0
- data/lib/capybara/spec/views/buttons.erb +1 -1
- data/lib/capybara/spec/views/fieldsets.erb +1 -1
- data/lib/capybara/spec/views/form.erb +227 -20
- data/lib/capybara/spec/views/frame_child.erb +10 -2
- data/lib/capybara/spec/views/frame_one.erb +2 -1
- data/lib/capybara/spec/views/frame_parent.erb +2 -2
- data/lib/capybara/spec/views/frame_two.erb +1 -1
- data/lib/capybara/spec/views/header_links.erb +1 -1
- data/lib/capybara/spec/views/host_links.erb +1 -1
- 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 +1 -1
- 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/postback.erb +1 -1
- 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 +69 -2
- data/lib/capybara/spec/views/with_animation.erb +82 -0
- data/lib/capybara/spec/views/with_base_tag.erb +1 -1
- data/lib/capybara/spec/views/with_count.erb +1 -1
- data/lib/capybara/spec/views/with_dragula.erb +24 -0
- data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
- data/lib/capybara/spec/views/with_hover.erb +7 -1
- data/lib/capybara/spec/views/with_hover1.erb +10 -0
- data/lib/capybara/spec/views/with_html.erb +100 -10
- data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
- data/lib/capybara/spec/views/with_html_entities.erb +1 -1
- data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
- data/lib/capybara/spec/views/with_js.erb +49 -3
- 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 +1 -1
- data/lib/capybara/spec/views/with_scope_other.erb +6 -0
- data/lib/capybara/spec/views/with_simple_html.erb +1 -1
- data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
- data/lib/capybara/spec/views/with_title.erb +1 -1
- data/lib/capybara/spec/views/with_unload_alert.erb +3 -1
- data/lib/capybara/spec/views/with_windows.erb +7 -1
- data/lib/capybara/spec/views/within_frames.erb +6 -3
- data/lib/capybara/version.rb +2 -1
- data/lib/capybara/window.rb +39 -21
- data/lib/capybara.rb +208 -186
- data/spec/basic_node_spec.rb +52 -39
- data/spec/capybara_spec.rb +72 -50
- data/spec/css_builder_spec.rb +101 -0
- data/spec/css_splitter_spec.rb +38 -0
- data/spec/dsl_spec.rb +81 -61
- 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 +7 -3
- data/spec/fixtures/selenium_driver_rspec_success.rb +7 -3
- data/spec/minitest_spec.rb +164 -0
- data/spec/minitest_spec_spec.rb +162 -0
- data/spec/per_session_config_spec.rb +68 -0
- data/spec/rack_test_spec.rb +189 -96
- data/spec/regexp_dissassembler_spec.rb +250 -0
- data/spec/result_spec.rb +143 -13
- data/spec/rspec/features_spec.rb +38 -32
- data/spec/rspec/scenarios_spec.rb +9 -7
- data/spec/rspec/shared_spec_matchers.rb +959 -0
- data/spec/rspec/views_spec.rb +9 -3
- data/spec/rspec_matchers_spec.rb +62 -0
- data/spec/rspec_spec.rb +127 -30
- data/spec/sauce_spec_chrome.rb +43 -0
- data/spec/selector_spec.rb +458 -37
- data/spec/selenium_spec_chrome.rb +196 -9
- data/spec/selenium_spec_chrome_remote.rb +100 -0
- data/spec/selenium_spec_edge.rb +47 -0
- data/spec/selenium_spec_firefox.rb +210 -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 +200 -101
- data/spec/session_spec.rb +91 -0
- data/spec/shared_selenium_node.rb +83 -0
- data/spec/shared_selenium_session.rb +558 -0
- data/spec/spec_helper.rb +94 -2
- data/spec/xpath_builder_spec.rb +93 -0
- metadata +420 -60
- data/lib/capybara/query.rb +0 -7
- data/lib/capybara/spec/session/assert_current_path.rb +0 -60
- data/lib/capybara/spec/session/assert_selector.rb +0 -148
- data/lib/capybara/spec/session/assert_text.rb +0 -196
- data/lib/capybara/spec/session/assert_title.rb +0 -70
- data/lib/capybara/spec/session/source_spec.rb +0 -0
- data/lib/capybara/spec/session/within_frame_spec.rb +0 -53
- data/spec/rspec/matchers_spec.rb +0 -827
- data/spec/selenium_spec.rb +0 -151
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'delegate'
|
|
4
|
+
|
|
5
|
+
module Capybara
|
|
6
|
+
class SessionConfig
|
|
7
|
+
OPTIONS = %i[always_include_port run_server default_selector default_max_wait_time ignore_hidden_elements
|
|
8
|
+
automatic_reload match exact exact_text raise_server_errors visible_text_only
|
|
9
|
+
automatic_label_click enable_aria_label save_path asset_host default_host app_host
|
|
10
|
+
server_host server_port server_errors default_set_options disable_animation test_id
|
|
11
|
+
predicates_wait default_normalize_ws w3c_click_offset enable_aria_role].freeze
|
|
12
|
+
|
|
13
|
+
attr_accessor(*OPTIONS)
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# @!method always_include_port
|
|
17
|
+
# See {Capybara.configure}
|
|
18
|
+
# @!method run_server
|
|
19
|
+
# See {Capybara.configure}
|
|
20
|
+
# @!method default_selector
|
|
21
|
+
# See {Capybara.configure}
|
|
22
|
+
# @!method default_max_wait_time
|
|
23
|
+
# See {Capybara.configure}
|
|
24
|
+
# @!method ignore_hidden_elements
|
|
25
|
+
# See {Capybara.configure}
|
|
26
|
+
# @!method automatic_reload
|
|
27
|
+
# See {Capybara.configure}
|
|
28
|
+
# @!method match
|
|
29
|
+
# See {Capybara.configure}
|
|
30
|
+
# @!method exact
|
|
31
|
+
# See {Capybara.configure}
|
|
32
|
+
# @!method raise_server_errors
|
|
33
|
+
# See {Capybara.configure}
|
|
34
|
+
# @!method visible_text_only
|
|
35
|
+
# See {Capybara.configure}
|
|
36
|
+
# @!method automatic_label_click
|
|
37
|
+
# See {Capybara.configure}
|
|
38
|
+
# @!method enable_aria_label
|
|
39
|
+
# See {Capybara.configure}
|
|
40
|
+
# @!method enable_aria_role
|
|
41
|
+
# See {Capybara.configure}
|
|
42
|
+
# @!method save_path
|
|
43
|
+
# See {Capybara.configure}
|
|
44
|
+
# @!method asset_host
|
|
45
|
+
# See {Capybara.configure}
|
|
46
|
+
# @!method default_host
|
|
47
|
+
# See {Capybara.configure}
|
|
48
|
+
# @!method app_host
|
|
49
|
+
# See {Capybara.configure}
|
|
50
|
+
# @!method server_host
|
|
51
|
+
# See {Capybara.configure}
|
|
52
|
+
# @!method server_port
|
|
53
|
+
# See {Capybara.configure}
|
|
54
|
+
# @!method server_errors
|
|
55
|
+
# See {Capybara.configure}
|
|
56
|
+
# @!method default_set_options
|
|
57
|
+
# See {Capybara.configure}
|
|
58
|
+
# @!method disable_animation
|
|
59
|
+
# See {Capybara.configure}
|
|
60
|
+
# @!method test_id
|
|
61
|
+
# See {Capybara.configure}
|
|
62
|
+
# @!method default_normalize_ws
|
|
63
|
+
# See {Capybara.configure}
|
|
64
|
+
# @!method w3c_click_offset
|
|
65
|
+
# See {Capybara.configure}
|
|
66
|
+
|
|
67
|
+
remove_method :server_host
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
#
|
|
71
|
+
# @return [String] The IP address bound by default server
|
|
72
|
+
#
|
|
73
|
+
def server_host
|
|
74
|
+
@server_host || '127.0.0.1'
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
remove_method :server_errors=
|
|
78
|
+
def server_errors=(errors)
|
|
79
|
+
(@server_errors ||= []).replace(errors.dup)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
remove_method :app_host=
|
|
83
|
+
def app_host=(url)
|
|
84
|
+
unless url.nil? || url.match?(URI::DEFAULT_PARSER.make_regexp)
|
|
85
|
+
raise ArgumentError, "Capybara.app_host should be set to a url (http://www.example.com). Attempted to set #{url.inspect}."
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
@app_host = url
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
remove_method :default_host=
|
|
92
|
+
def default_host=(url)
|
|
93
|
+
unless url.nil? || url.match?(URI::DEFAULT_PARSER.make_regexp)
|
|
94
|
+
raise ArgumentError, "Capybara.default_host should be set to a url (http://www.example.com). Attempted to set #{url.inspect}."
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
@default_host = url
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
remove_method :test_id=
|
|
101
|
+
##
|
|
102
|
+
#
|
|
103
|
+
# Set an attribue to be optionally matched against the locator for builtin selector types.
|
|
104
|
+
# This attribute will be checked by builtin selector types whenever id would normally be checked.
|
|
105
|
+
# If `nil` then it will be ignored.
|
|
106
|
+
#
|
|
107
|
+
# @param [String, Symbol, nil] id Name of the attribute to use as the test id
|
|
108
|
+
#
|
|
109
|
+
def test_id=(id)
|
|
110
|
+
@test_id = id&.to_sym
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def initialize_copy(other)
|
|
114
|
+
super
|
|
115
|
+
@server_errors = @server_errors.dup
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class ReadOnlySessionConfig < SimpleDelegator
|
|
120
|
+
SessionConfig::OPTIONS.each do |option|
|
|
121
|
+
define_method "#{option}=" do |_|
|
|
122
|
+
raise 'Per session settings are only supported when Capybara.threadsafe == true'
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -1,70 +1,87 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
module Capybara
|
|
3
4
|
module SessionMatchers
|
|
4
5
|
##
|
|
5
6
|
# Asserts that the page has the given path.
|
|
6
|
-
# By default this will compare against the
|
|
7
|
+
# By default, if passed a full url this will compare against the full url,
|
|
8
|
+
# if passed a path only the path+query portion will be compared, if passed a regexp
|
|
9
|
+
# the comparison will depend on the :url option (path+query by default)
|
|
7
10
|
#
|
|
8
11
|
# @!macro current_path_query_params
|
|
9
|
-
# @overload $0(string, options
|
|
12
|
+
# @overload $0(string, **options)
|
|
10
13
|
# @param string [String] The string that the current 'path' should equal
|
|
11
|
-
# @overload $0(regexp, options
|
|
14
|
+
# @overload $0(regexp, **options)
|
|
12
15
|
# @param regexp [Regexp] The regexp that the current 'path' should match to
|
|
13
|
-
# @option options [
|
|
14
|
-
# @option options [Boolean] :
|
|
15
|
-
# @option options [
|
|
16
|
+
# @option options [Boolean] :url (true if `string` is a full url, otherwise false) Whether the comparison should be done against the full current url or just the path
|
|
17
|
+
# @option options [Boolean] :ignore_query (false) Whether the query portion of the current url/path should be ignored
|
|
18
|
+
# @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for the current url/path to eq/match given string/regexp argument
|
|
16
19
|
# @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
|
|
17
20
|
# @return [true]
|
|
18
21
|
#
|
|
19
|
-
def assert_current_path(path, options
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
unless query.resolves_for?(self)
|
|
23
|
-
raise Capybara::ExpectationNotMet, query.failure_message
|
|
24
|
-
end
|
|
22
|
+
def assert_current_path(path, **options, &optional_filter_block)
|
|
23
|
+
_verify_current_path(path, optional_filter_block, **options) do |query|
|
|
24
|
+
raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self)
|
|
25
25
|
end
|
|
26
|
-
return true
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
##
|
|
30
29
|
# Asserts that the page doesn't have the given path.
|
|
30
|
+
# By default, if passed a full url this will compare against the full url,
|
|
31
|
+
# if passed a path only the path+query portion will be compared, if passed a regexp
|
|
32
|
+
# the comparison will depend on the :url option
|
|
31
33
|
#
|
|
32
34
|
# @macro current_path_query_params
|
|
33
35
|
# @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
|
|
34
36
|
# @return [true]
|
|
35
37
|
#
|
|
36
|
-
def assert_no_current_path(path, options
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if query.resolves_for?(self)
|
|
40
|
-
raise Capybara::ExpectationNotMet, query.negative_failure_message
|
|
41
|
-
end
|
|
38
|
+
def assert_no_current_path(path, **options, &optional_filter_block)
|
|
39
|
+
_verify_current_path(path, optional_filter_block, **options) do |query|
|
|
40
|
+
raise Capybara::ExpectationNotMet, query.negative_failure_message if query.resolves_for?(self)
|
|
42
41
|
end
|
|
43
|
-
return true
|
|
44
42
|
end
|
|
45
43
|
|
|
46
44
|
##
|
|
47
45
|
# Checks if the page has the given path.
|
|
46
|
+
# By default, if passed a full url this will compare against the full url,
|
|
47
|
+
# if passed a path only the path+query portion will be compared, if passed a regexp
|
|
48
|
+
# the comparison will depend on the :url option
|
|
48
49
|
#
|
|
49
50
|
# @macro current_path_query_params
|
|
50
51
|
# @return [Boolean]
|
|
51
52
|
#
|
|
52
|
-
def has_current_path?(path, options
|
|
53
|
-
assert_current_path(path, options)
|
|
54
|
-
rescue Capybara::ExpectationNotMet
|
|
55
|
-
return false
|
|
53
|
+
def has_current_path?(path, **options, &optional_filter_block)
|
|
54
|
+
make_predicate(options) { assert_current_path(path, **options, &optional_filter_block) }
|
|
56
55
|
end
|
|
57
56
|
|
|
58
57
|
##
|
|
59
58
|
# Checks if the page doesn't have the given path.
|
|
59
|
+
# By default, if passed a full url this will compare against the full url,
|
|
60
|
+
# if passed a path only the path+query portion will be compared, if passed a regexp
|
|
61
|
+
# the comparison will depend on the :url option
|
|
60
62
|
#
|
|
61
63
|
# @macro current_path_query_params
|
|
62
64
|
# @return [Boolean]
|
|
63
65
|
#
|
|
64
|
-
def has_no_current_path?(path, options
|
|
65
|
-
assert_no_current_path(path, options)
|
|
66
|
+
def has_no_current_path?(path, **options, &optional_filter_block)
|
|
67
|
+
make_predicate(options) { assert_no_current_path(path, **options, &optional_filter_block) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def _verify_current_path(path, filter_block, **options)
|
|
73
|
+
query = Capybara::Queries::CurrentPathQuery.new(path, **options, &filter_block)
|
|
74
|
+
document.synchronize(query.wait) do
|
|
75
|
+
yield(query)
|
|
76
|
+
end
|
|
77
|
+
true
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def make_predicate(options)
|
|
81
|
+
options[:wait] = 0 unless options.key?(:wait) || config.predicates_wait
|
|
82
|
+
yield
|
|
66
83
|
rescue Capybara::ExpectationNotMet
|
|
67
|
-
|
|
84
|
+
false
|
|
68
85
|
end
|
|
69
86
|
end
|
|
70
87
|
end
|
data/lib/capybara/session.rb
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require 'capybara/session/matchers'
|
|
4
|
+
require 'addressable/uri'
|
|
3
5
|
|
|
4
6
|
module Capybara
|
|
5
|
-
|
|
6
7
|
##
|
|
7
8
|
#
|
|
8
|
-
# The Session class represents a single user's interaction with the system. The Session can use
|
|
9
|
+
# The {Session} class represents a single user's interaction with the system. The {Session} can use
|
|
9
10
|
# any of the underlying drivers. A session can be initialized manually like this:
|
|
10
11
|
#
|
|
11
12
|
# session = Capybara::Session.new(:culerity, MyRackApp)
|
|
@@ -16,100 +17,120 @@ module Capybara
|
|
|
16
17
|
# session = Capybara::Session.new(:culerity)
|
|
17
18
|
# session.visit('http://www.google.com')
|
|
18
19
|
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
20
|
+
# When {Capybara.configure threadsafe} is `true` the sessions options will be initially set to the
|
|
21
|
+
# current values of the global options and a configuration block can be passed to the session initializer.
|
|
22
|
+
# For available options see {Capybara::SessionConfig::OPTIONS}:
|
|
23
|
+
#
|
|
24
|
+
# session = Capybara::Session.new(:driver, MyRackApp) do |config|
|
|
25
|
+
# config.app_host = "http://my_host.dev"
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# The {Session} provides a number of methods for controlling the navigation of the page, such as {#visit},
|
|
29
|
+
# {#current_path}, and so on. It also delegates a number of methods to a {Capybara::Document}, representing
|
|
21
30
|
# the current HTML document. This allows interaction:
|
|
22
31
|
#
|
|
23
|
-
# session.fill_in('q', :
|
|
32
|
+
# session.fill_in('q', with: 'Capybara')
|
|
24
33
|
# session.click_button('Search')
|
|
25
34
|
# expect(session).to have_content('Capybara')
|
|
26
35
|
#
|
|
27
|
-
# When using capybara/dsl
|
|
36
|
+
# When using `capybara/dsl`, the {Session} is initialized automatically for you.
|
|
28
37
|
#
|
|
29
38
|
class Session
|
|
30
39
|
include Capybara::SessionMatchers
|
|
31
40
|
|
|
32
|
-
NODE_METHODS = [
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
NODE_METHODS = %i[
|
|
42
|
+
all first attach_file text check choose scroll_to scroll_by
|
|
43
|
+
click_link_or_button click_button click_link
|
|
44
|
+
fill_in find find_all find_button find_by_id find_field find_link
|
|
45
|
+
has_content? has_text? has_css? has_no_content? has_no_text?
|
|
46
|
+
has_no_css? has_no_xpath? has_xpath? select uncheck
|
|
47
|
+
has_link? has_no_link? has_button? has_no_button? has_field?
|
|
48
|
+
has_no_field? has_checked_field? has_unchecked_field?
|
|
49
|
+
has_no_table? has_table? unselect has_select? has_no_select?
|
|
50
|
+
has_selector? has_no_selector? click_on has_no_checked_field?
|
|
51
|
+
has_no_unchecked_field? query assert_selector assert_no_selector
|
|
52
|
+
assert_all_of_selectors assert_none_of_selectors assert_any_of_selectors
|
|
53
|
+
refute_selector assert_text assert_no_text
|
|
54
|
+
].freeze
|
|
45
55
|
# @api private
|
|
46
|
-
DOCUMENT_METHODS = [
|
|
47
|
-
|
|
48
|
-
]
|
|
49
|
-
SESSION_METHODS = [
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
] + DOCUMENT_METHODS
|
|
59
|
-
MODAL_METHODS = [
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
]
|
|
56
|
+
DOCUMENT_METHODS = %i[
|
|
57
|
+
title assert_title assert_no_title has_title? has_no_title?
|
|
58
|
+
].freeze
|
|
59
|
+
SESSION_METHODS = %i[
|
|
60
|
+
body html source current_url current_host current_path
|
|
61
|
+
execute_script evaluate_script visit refresh go_back go_forward send_keys
|
|
62
|
+
within within_element within_fieldset within_table within_frame switch_to_frame
|
|
63
|
+
current_window windows open_new_window switch_to_window within_window window_opened_by
|
|
64
|
+
save_page save_and_open_page save_screenshot
|
|
65
|
+
save_and_open_screenshot reset_session! response_headers
|
|
66
|
+
status_code current_scope
|
|
67
|
+
assert_current_path assert_no_current_path has_current_path? has_no_current_path?
|
|
68
|
+
].freeze + DOCUMENT_METHODS
|
|
69
|
+
MODAL_METHODS = %i[
|
|
70
|
+
accept_alert accept_confirm dismiss_confirm accept_prompt dismiss_prompt
|
|
71
|
+
].freeze
|
|
63
72
|
DSL_METHODS = NODE_METHODS + SESSION_METHODS + MODAL_METHODS
|
|
64
73
|
|
|
65
74
|
attr_reader :mode, :app, :server
|
|
66
75
|
attr_accessor :synchronized
|
|
67
76
|
|
|
68
|
-
def initialize(mode, app=nil)
|
|
77
|
+
def initialize(mode, app = nil)
|
|
78
|
+
if app && !app.respond_to?(:call)
|
|
79
|
+
raise TypeError, 'The second parameter to Session::new should be a rack app if passed.'
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
@@instance_created = true # rubocop:disable Style/ClassVars
|
|
69
83
|
@mode = mode
|
|
70
84
|
@app = app
|
|
71
|
-
if
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
85
|
+
if block_given?
|
|
86
|
+
raise 'A configuration block is only accepted when Capybara.threadsafe == true' unless Capybara.threadsafe
|
|
87
|
+
|
|
88
|
+
yield config
|
|
89
|
+
end
|
|
90
|
+
@server = if config.run_server && @app && driver.needs_server?
|
|
91
|
+
server_options = { port: config.server_port, host: config.server_host, reportable_errors: config.server_errors }
|
|
92
|
+
server_options[:extra_middleware] = [Capybara::Server::AnimationDisabler] if config.disable_animation
|
|
93
|
+
Capybara::Server.new(@app, **server_options).boot
|
|
75
94
|
end
|
|
76
95
|
@touched = false
|
|
77
96
|
end
|
|
78
97
|
|
|
79
98
|
def driver
|
|
80
99
|
@driver ||= begin
|
|
81
|
-
unless Capybara.drivers
|
|
82
|
-
other_drivers = Capybara.drivers.
|
|
100
|
+
unless Capybara.drivers[mode]
|
|
101
|
+
other_drivers = Capybara.drivers.names.map(&:inspect)
|
|
83
102
|
raise Capybara::DriverNotFoundError, "no driver called #{mode.inspect} was found, available drivers: #{other_drivers.join(', ')}"
|
|
84
103
|
end
|
|
85
|
-
Capybara.drivers[mode].call(app)
|
|
104
|
+
driver = Capybara.drivers[mode].call(app)
|
|
105
|
+
driver.session = self if driver.respond_to?(:session=)
|
|
106
|
+
driver
|
|
86
107
|
end
|
|
87
108
|
end
|
|
88
109
|
|
|
89
110
|
##
|
|
90
111
|
#
|
|
91
|
-
# Reset the session (i.e. remove cookies and navigate to blank page)
|
|
112
|
+
# Reset the session (i.e. remove cookies and navigate to blank page).
|
|
92
113
|
#
|
|
93
114
|
# This method does not:
|
|
94
115
|
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
116
|
+
# * accept modal dialogs if they are present (Selenium driver now does, others may not)
|
|
117
|
+
# * clear browser cache/HTML 5 local storage/IndexedDB/Web SQL database/etc.
|
|
118
|
+
# * modify state of the driver/underlying browser in any other way
|
|
98
119
|
#
|
|
99
120
|
# as doing so will result in performance downsides and it's not needed to do everything from the list above for most apps.
|
|
100
121
|
#
|
|
101
122
|
# If you want to do anything from the list above on a general basis you can:
|
|
102
123
|
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
124
|
+
# * write RSpec/Cucumber/etc. after hook
|
|
125
|
+
# * monkeypatch this method
|
|
126
|
+
# * use Ruby's `prepend` method
|
|
106
127
|
#
|
|
107
128
|
def reset!
|
|
108
129
|
if @touched
|
|
109
130
|
driver.reset!
|
|
110
131
|
@touched = false
|
|
111
132
|
end
|
|
112
|
-
@server
|
|
133
|
+
@server&.wait_for_pending_requests
|
|
113
134
|
raise_server_error!
|
|
114
135
|
end
|
|
115
136
|
alias_method :cleanup!, :reset!
|
|
@@ -117,19 +138,40 @@ module Capybara
|
|
|
117
138
|
|
|
118
139
|
##
|
|
119
140
|
#
|
|
120
|
-
#
|
|
141
|
+
# Disconnect from the current driver. A new driver will be instantiated on the next interaction.
|
|
142
|
+
#
|
|
143
|
+
def quit
|
|
144
|
+
@driver.quit if @driver.respond_to? :quit
|
|
145
|
+
@document = @driver = nil
|
|
146
|
+
@touched = false
|
|
147
|
+
@server&.reset_error!
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
##
|
|
151
|
+
#
|
|
152
|
+
# Raise errors encountered in the server.
|
|
121
153
|
#
|
|
122
154
|
def raise_server_error!
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
155
|
+
return unless @server&.error
|
|
156
|
+
|
|
157
|
+
# Force an explanation for the error being raised as the exception cause
|
|
158
|
+
begin
|
|
159
|
+
if config.raise_server_errors
|
|
160
|
+
raise CapybaraError, 'Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true'
|
|
161
|
+
end
|
|
162
|
+
rescue CapybaraError
|
|
163
|
+
# needed to get the cause set correctly in JRuby -- otherwise we could just do raise @server.error
|
|
164
|
+
raise @server.error, @server.error.message, @server.error.backtrace
|
|
165
|
+
ensure
|
|
166
|
+
@server.reset_error!
|
|
167
|
+
end
|
|
126
168
|
end
|
|
127
169
|
|
|
128
170
|
##
|
|
129
171
|
#
|
|
130
|
-
# Returns a hash of response headers. Not supported by all drivers (e.g. Selenium)
|
|
172
|
+
# Returns a hash of response headers. Not supported by all drivers (e.g. Selenium).
|
|
131
173
|
#
|
|
132
|
-
# @return [Hash
|
|
174
|
+
# @return [Hash<String, String>] A hash of response headers.
|
|
133
175
|
#
|
|
134
176
|
def response_headers
|
|
135
177
|
driver.response_headers
|
|
@@ -137,7 +179,7 @@ module Capybara
|
|
|
137
179
|
|
|
138
180
|
##
|
|
139
181
|
#
|
|
140
|
-
# Returns the current HTTP status code as an
|
|
182
|
+
# Returns the current HTTP status code as an integer. Not supported by all drivers (e.g. Selenium).
|
|
141
183
|
#
|
|
142
184
|
# @return [Integer] Current HTTP status code
|
|
143
185
|
#
|
|
@@ -150,7 +192,7 @@ module Capybara
|
|
|
150
192
|
# @return [String] A snapshot of the DOM of the current document, as it looks right now (potentially modified by JavaScript).
|
|
151
193
|
#
|
|
152
194
|
def html
|
|
153
|
-
driver.html
|
|
195
|
+
driver.html || ''
|
|
154
196
|
end
|
|
155
197
|
alias_method :body, :html
|
|
156
198
|
alias_method :source, :html
|
|
@@ -160,8 +202,14 @@ module Capybara
|
|
|
160
202
|
# @return [String] Path of the current page, without any domain information
|
|
161
203
|
#
|
|
162
204
|
def current_path
|
|
163
|
-
|
|
164
|
-
|
|
205
|
+
# Addressable parsing is more lenient than URI
|
|
206
|
+
uri = ::Addressable::URI.parse(current_url)
|
|
207
|
+
|
|
208
|
+
# Addressable doesn't support opaque URIs - we want nil here
|
|
209
|
+
return nil if uri&.scheme == 'about'
|
|
210
|
+
|
|
211
|
+
path = uri&.path
|
|
212
|
+
path unless path&.empty?
|
|
165
213
|
end
|
|
166
214
|
|
|
167
215
|
##
|
|
@@ -191,13 +239,13 @@ module Capybara
|
|
|
191
239
|
#
|
|
192
240
|
# For drivers which can run against an external application, such as the selenium driver
|
|
193
241
|
# giving an absolute URL will navigate to that page. This allows testing applications
|
|
194
|
-
# running on remote servers. For these drivers, setting {Capybara.app_host} will make the
|
|
242
|
+
# running on remote servers. For these drivers, setting {Capybara.configure app_host} will make the
|
|
195
243
|
# remote server the default. For example:
|
|
196
244
|
#
|
|
197
245
|
# Capybara.app_host = 'http://google.com'
|
|
198
246
|
# session.visit('/') # visits the google homepage
|
|
199
247
|
#
|
|
200
|
-
# If {Capybara.always_include_port} is set to true and this session is running against
|
|
248
|
+
# If {Capybara.configure always_include_port} is set to `true` and this session is running against
|
|
201
249
|
# a rack application, then the port that the rack application is running on will automatically
|
|
202
250
|
# be inserted into the URL. Supposing the app is running on port `4567`, doing something like:
|
|
203
251
|
#
|
|
@@ -211,28 +259,34 @@ module Capybara
|
|
|
211
259
|
raise_server_error!
|
|
212
260
|
@touched = true
|
|
213
261
|
|
|
214
|
-
visit_uri = URI.parse(visit_uri.to_s)
|
|
262
|
+
visit_uri = ::Addressable::URI.parse(visit_uri.to_s)
|
|
263
|
+
base_uri = ::Addressable::URI.parse(config.app_host || server_url)
|
|
215
264
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
else
|
|
220
|
-
Capybara.app_host && URI.parse(Capybara.app_host)
|
|
221
|
-
end
|
|
265
|
+
if base_uri && [nil, 'http', 'https'].include?(visit_uri.scheme)
|
|
266
|
+
if visit_uri.relative?
|
|
267
|
+
visit_uri_parts = visit_uri.to_hash.compact
|
|
222
268
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
# deploying to a subdirectory and/or single page apps where only the url fragment changes
|
|
227
|
-
if visit_uri.scheme.nil? && uri_base
|
|
228
|
-
visit_uri.path = uri_base.path + visit_uri.path
|
|
229
|
-
end
|
|
269
|
+
# Useful to people deploying to a subdirectory
|
|
270
|
+
# and/or single page apps where only the url fragment changes
|
|
271
|
+
visit_uri_parts[:path] = base_uri.path + visit_uri.path
|
|
230
272
|
|
|
231
|
-
|
|
273
|
+
visit_uri = base_uri.merge(visit_uri_parts)
|
|
274
|
+
end
|
|
275
|
+
adjust_server_port(visit_uri)
|
|
276
|
+
end
|
|
232
277
|
|
|
233
278
|
driver.visit(visit_uri.to_s)
|
|
234
279
|
end
|
|
235
280
|
|
|
281
|
+
##
|
|
282
|
+
#
|
|
283
|
+
# Refresh the page.
|
|
284
|
+
#
|
|
285
|
+
def refresh
|
|
286
|
+
raise_server_error!
|
|
287
|
+
driver.refresh
|
|
288
|
+
end
|
|
289
|
+
|
|
236
290
|
##
|
|
237
291
|
#
|
|
238
292
|
# Move back a single entry in the browser's history.
|
|
@@ -249,32 +303,40 @@ module Capybara
|
|
|
249
303
|
driver.go_forward
|
|
250
304
|
end
|
|
251
305
|
|
|
306
|
+
##
|
|
307
|
+
# @!method send_keys
|
|
308
|
+
# @see Capybara::Node::Element#send_keys
|
|
309
|
+
#
|
|
310
|
+
def send_keys(*args, **kw_args)
|
|
311
|
+
driver.send_keys(*args, **kw_args)
|
|
312
|
+
end
|
|
313
|
+
|
|
252
314
|
##
|
|
253
315
|
#
|
|
254
|
-
# Executes the given block within the context of a node.
|
|
255
|
-
# same options as
|
|
316
|
+
# Executes the given block within the context of a node. {#within} takes the
|
|
317
|
+
# same options as {Capybara::Node::Finders#find #find}, as well as a block. For the duration of the
|
|
256
318
|
# block, any command to Capybara will be handled as though it were scoped
|
|
257
319
|
# to the given element.
|
|
258
320
|
#
|
|
259
|
-
# within(:xpath, '
|
|
260
|
-
# fill_in('Street', :
|
|
321
|
+
# within(:xpath, './/div[@id="delivery-address"]') do
|
|
322
|
+
# fill_in('Street', with: '12 Main Street')
|
|
261
323
|
# end
|
|
262
324
|
#
|
|
263
|
-
# Just as with
|
|
264
|
-
#
|
|
325
|
+
# Just as with `#find`, if multiple elements match the selector given to
|
|
326
|
+
# {#within}, an error will be raised, and just as with `#find`, this
|
|
265
327
|
# behaviour can be controlled through the `:match` and `:exact` options.
|
|
266
328
|
#
|
|
267
329
|
# It is possible to omit the first parameter, in that case, the selector is
|
|
268
|
-
# assumed to be of the type set in Capybara.default_selector.
|
|
330
|
+
# assumed to be of the type set in {Capybara.configure default_selector}.
|
|
269
331
|
#
|
|
270
332
|
# within('div#delivery-address') do
|
|
271
|
-
# fill_in('Street', :
|
|
333
|
+
# fill_in('Street', with: '12 Main Street')
|
|
272
334
|
# end
|
|
273
335
|
#
|
|
274
|
-
# Note that a lot of uses of
|
|
336
|
+
# Note that a lot of uses of {#within} can be replaced more succinctly with
|
|
275
337
|
# chaining:
|
|
276
338
|
#
|
|
277
|
-
# find('div#delivery-address').fill_in('Street', :
|
|
339
|
+
# find('div#delivery-address').fill_in('Street', with: '12 Main Street')
|
|
278
340
|
#
|
|
279
341
|
# @overload within(*find_args)
|
|
280
342
|
# @param (see Capybara::Node::Finders#all)
|
|
@@ -284,15 +346,16 @@ module Capybara
|
|
|
284
346
|
#
|
|
285
347
|
# @raise [Capybara::ElementNotFound] If the scope can't be found before time expires
|
|
286
348
|
#
|
|
287
|
-
def within(*args)
|
|
288
|
-
new_scope =
|
|
349
|
+
def within(*args, **kw_args)
|
|
350
|
+
new_scope = args.first.respond_to?(:to_capybara_node) ? args.first.to_capybara_node : find(*args, **kw_args)
|
|
289
351
|
begin
|
|
290
352
|
scopes.push(new_scope)
|
|
291
|
-
yield
|
|
353
|
+
yield if block_given?
|
|
292
354
|
ensure
|
|
293
355
|
scopes.pop
|
|
294
356
|
end
|
|
295
357
|
end
|
|
358
|
+
alias_method :within_element, :within
|
|
296
359
|
|
|
297
360
|
##
|
|
298
361
|
#
|
|
@@ -300,10 +363,8 @@ module Capybara
|
|
|
300
363
|
#
|
|
301
364
|
# @param [String] locator Id or legend of the fieldset
|
|
302
365
|
#
|
|
303
|
-
def within_fieldset(locator)
|
|
304
|
-
within
|
|
305
|
-
yield
|
|
306
|
-
end
|
|
366
|
+
def within_fieldset(locator, &block)
|
|
367
|
+
within(:fieldset, locator, &block)
|
|
307
368
|
end
|
|
308
369
|
|
|
309
370
|
##
|
|
@@ -312,29 +373,72 @@ module Capybara
|
|
|
312
373
|
#
|
|
313
374
|
# @param [String] locator Id or caption of the table
|
|
314
375
|
#
|
|
315
|
-
def within_table(locator)
|
|
316
|
-
within
|
|
317
|
-
|
|
376
|
+
def within_table(locator, &block)
|
|
377
|
+
within(:table, locator, &block)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
##
|
|
381
|
+
#
|
|
382
|
+
# Switch to the given frame.
|
|
383
|
+
#
|
|
384
|
+
# If you use this method you are responsible for making sure you switch back to the parent frame when done in the frame changed to.
|
|
385
|
+
# {#within_frame} is preferred over this method and should be used when possible.
|
|
386
|
+
# May not be supported by all drivers.
|
|
387
|
+
#
|
|
388
|
+
# @overload switch_to_frame(element)
|
|
389
|
+
# @param [Capybara::Node::Element] element iframe/frame element to switch to
|
|
390
|
+
# @overload switch_to_frame(location)
|
|
391
|
+
# @param [Symbol] location relative location of the frame to switch to
|
|
392
|
+
# * :parent - the parent frame
|
|
393
|
+
# * :top - the top level document
|
|
394
|
+
#
|
|
395
|
+
def switch_to_frame(frame)
|
|
396
|
+
case frame
|
|
397
|
+
when Capybara::Node::Element
|
|
398
|
+
driver.switch_to_frame(frame)
|
|
399
|
+
scopes.push(:frame)
|
|
400
|
+
when :parent
|
|
401
|
+
if scopes.last != :frame
|
|
402
|
+
raise Capybara::ScopeError, "`switch_to_frame(:parent)` cannot be called from inside a descendant frame's "\
|
|
403
|
+
'`within` block.'
|
|
404
|
+
end
|
|
405
|
+
scopes.pop
|
|
406
|
+
driver.switch_to_frame(:parent)
|
|
407
|
+
when :top
|
|
408
|
+
idx = scopes.index(:frame)
|
|
409
|
+
top_level_scopes = [:frame, nil]
|
|
410
|
+
if idx
|
|
411
|
+
if scopes.slice(idx..-1).any? { |scope| !top_level_scopes.include?(scope) }
|
|
412
|
+
raise Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's "\
|
|
413
|
+
'`within` block.'
|
|
414
|
+
end
|
|
415
|
+
scopes.slice!(idx..-1)
|
|
416
|
+
driver.switch_to_frame(:top)
|
|
417
|
+
end
|
|
418
|
+
else
|
|
419
|
+
raise ArgumentError, 'You must provide a frame element, :parent, or :top when calling switch_to_frame'
|
|
318
420
|
end
|
|
319
421
|
end
|
|
320
422
|
|
|
321
423
|
##
|
|
322
424
|
#
|
|
323
|
-
# Execute the given block within the given iframe using given frame name or index.
|
|
324
|
-
# May be supported by
|
|
425
|
+
# Execute the given block within the given iframe using given frame, frame name/id or index.
|
|
426
|
+
# May not be supported by all drivers.
|
|
325
427
|
#
|
|
428
|
+
# @overload within_frame(element)
|
|
429
|
+
# @param [Capybara::Node::Element] frame element
|
|
430
|
+
# @overload within_frame([kind = :frame], locator, **options)
|
|
431
|
+
# @param [Symbol] kind Optional selector type (:frame, :css, :xpath, etc.) - Defaults to :frame
|
|
432
|
+
# @param [String] locator The locator for the given selector kind. For :frame this is the name/id of a frame/iframe element
|
|
326
433
|
# @overload within_frame(index)
|
|
327
|
-
# @param [Integer] index index of a frame
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
yield
|
|
434
|
+
# @param [Integer] index index of a frame (0 based)
|
|
435
|
+
def within_frame(*args, **kw_args)
|
|
436
|
+
switch_to_frame(_find_frame(*args, **kw_args))
|
|
437
|
+
begin
|
|
438
|
+
yield if block_given?
|
|
439
|
+
ensure
|
|
440
|
+
switch_to_frame(:parent)
|
|
335
441
|
end
|
|
336
|
-
ensure
|
|
337
|
-
scopes.pop
|
|
338
442
|
end
|
|
339
443
|
|
|
340
444
|
##
|
|
@@ -358,70 +462,50 @@ module Capybara
|
|
|
358
462
|
end
|
|
359
463
|
|
|
360
464
|
##
|
|
361
|
-
# Open new window.
|
|
362
|
-
#
|
|
465
|
+
# Open a new window.
|
|
466
|
+
# The current window doesn't change as the result of this call.
|
|
363
467
|
# It should be switched to explicitly.
|
|
364
468
|
#
|
|
365
469
|
# @return [Capybara::Window] window that has been opened
|
|
366
470
|
#
|
|
367
|
-
def open_new_window
|
|
471
|
+
def open_new_window(kind = :tab)
|
|
368
472
|
window_opened_by do
|
|
369
|
-
driver.open_new_window
|
|
473
|
+
if driver.method(:open_new_window).arity.zero?
|
|
474
|
+
driver.open_new_window
|
|
475
|
+
else
|
|
476
|
+
driver.open_new_window(kind)
|
|
477
|
+
end
|
|
370
478
|
end
|
|
371
479
|
end
|
|
372
480
|
|
|
373
481
|
##
|
|
482
|
+
# Switch to the given window.
|
|
483
|
+
#
|
|
374
484
|
# @overload switch_to_window(&block)
|
|
375
485
|
# Switches to the first window for which given block returns a value other than false or nil.
|
|
376
|
-
# If window that matches block can't be found, the window will be switched back and
|
|
486
|
+
# If window that matches block can't be found, the window will be switched back and {Capybara::WindowError} will be raised.
|
|
377
487
|
# @example
|
|
378
488
|
# window = switch_to_window { title == 'Page title' }
|
|
379
489
|
# @raise [Capybara::WindowError] if no window matches given block
|
|
380
490
|
# @overload switch_to_window(window)
|
|
381
491
|
# @param window [Capybara::Window] window that should be switched to
|
|
382
|
-
# @raise [Capybara::Driver::Base#no_such_window_error] if
|
|
492
|
+
# @raise [Capybara::Driver::Base#no_such_window_error] if nonexistent (e.g. closed) window was passed
|
|
383
493
|
#
|
|
384
494
|
# @return [Capybara::Window] window that has been switched to
|
|
385
|
-
# @raise [Capybara::ScopeError] if this method is invoked inside
|
|
386
|
-
#
|
|
495
|
+
# @raise [Capybara::ScopeError] if this method is invoked inside {#within} or
|
|
496
|
+
# {#within_frame} methods
|
|
387
497
|
# @raise [ArgumentError] if both or neither arguments were provided
|
|
388
498
|
#
|
|
389
|
-
def switch_to_window(window = nil, options
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
block_given = block_given?
|
|
393
|
-
if window && block_given
|
|
394
|
-
raise ArgumentError, "`switch_to_window` can take either a block or a window, not both"
|
|
395
|
-
elsif !window && !block_given
|
|
396
|
-
raise ArgumentError, "`switch_to_window`: either window or block should be provided"
|
|
397
|
-
elsif scopes.size > 1
|
|
398
|
-
raise Capybara::ScopeError, "`switch_to_window` is not supposed to be invoked from "\
|
|
399
|
-
"`within`'s, `within_frame`'s' or `within_window`'s' block."
|
|
400
|
-
end
|
|
499
|
+
def switch_to_window(window = nil, **options, &window_locator)
|
|
500
|
+
raise ArgumentError, '`switch_to_window` can take either a block or a window, not both' if window && window_locator
|
|
501
|
+
raise ArgumentError, '`switch_to_window`: either window or block should be provided' if !window && !window_locator
|
|
401
502
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
else
|
|
406
|
-
wait_time = Capybara::Queries::SelectorQuery.new(options).wait
|
|
407
|
-
document.synchronize(wait_time, errors: [Capybara::WindowError]) do
|
|
408
|
-
original_window_handle = driver.current_window_handle
|
|
409
|
-
begin
|
|
410
|
-
driver.window_handles.each do |handle|
|
|
411
|
-
driver.switch_to_window handle
|
|
412
|
-
if yield
|
|
413
|
-
return Window.new(self, handle)
|
|
414
|
-
end
|
|
415
|
-
end
|
|
416
|
-
rescue => e
|
|
417
|
-
driver.switch_to_window(original_window_handle)
|
|
418
|
-
raise e
|
|
419
|
-
else
|
|
420
|
-
driver.switch_to_window(original_window_handle)
|
|
421
|
-
raise Capybara::WindowError, "Could not find a window matching block/lambda"
|
|
422
|
-
end
|
|
423
|
-
end
|
|
503
|
+
unless scopes.last.nil?
|
|
504
|
+
raise Capybara::ScopeError, '`switch_to_window` is not supposed to be invoked from '\
|
|
505
|
+
'`within` or `within_frame` blocks.'
|
|
424
506
|
end
|
|
507
|
+
|
|
508
|
+
_switch_to_window(window, **options, &window_locator)
|
|
425
509
|
end
|
|
426
510
|
|
|
427
511
|
##
|
|
@@ -429,58 +513,42 @@ module Capybara
|
|
|
429
513
|
#
|
|
430
514
|
# 1. Switches to the given window (it can be located by window instance/lambda/string).
|
|
431
515
|
# 2. Executes the given block (within window located at previous step).
|
|
432
|
-
# 3. Switches back (this step will be invoked even if exception
|
|
516
|
+
# 3. Switches back (this step will be invoked even if an exception occurs at the second step).
|
|
433
517
|
#
|
|
434
518
|
# @overload within_window(window) { do_something }
|
|
435
|
-
# @param window [Capybara::Window] instance of
|
|
519
|
+
# @param window [Capybara::Window] instance of {Capybara::Window} class
|
|
436
520
|
# that will be switched to
|
|
437
|
-
# @raise [driver#no_such_window_error] if
|
|
521
|
+
# @raise [driver#no_such_window_error] if nonexistent (e.g. closed) window was passed
|
|
438
522
|
# @overload within_window(proc_or_lambda) { do_something }
|
|
439
|
-
# @param lambda [Proc]
|
|
523
|
+
# @param lambda [Proc] First window for which lambda
|
|
440
524
|
# returns a value other than false or nil will be switched to.
|
|
441
525
|
# @example
|
|
442
526
|
# within_window(->{ page.title == 'Page title' }) { click_button 'Submit' }
|
|
443
527
|
# @raise [Capybara::WindowError] if no window matching lambda was found
|
|
444
|
-
# @overload within_window(string) { do_something }
|
|
445
|
-
# @deprecated Pass window or lambda instead
|
|
446
|
-
# @param [String] handle, name, url or title of the window
|
|
447
528
|
#
|
|
448
|
-
# @raise [Capybara::ScopeError] if this method is invoked inside
|
|
449
|
-
# `within_frame` or `within_window` methods
|
|
529
|
+
# @raise [Capybara::ScopeError] if this method is invoked inside {#within_frame} method
|
|
450
530
|
# @return value returned by the block
|
|
451
531
|
#
|
|
452
|
-
def within_window(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
elsif window_or_handle.is_a?(Proc)
|
|
464
|
-
original = current_window
|
|
465
|
-
switch_to_window { window_or_handle.call }
|
|
466
|
-
scopes << nil
|
|
467
|
-
begin
|
|
468
|
-
yield
|
|
469
|
-
ensure
|
|
470
|
-
@scopes.pop
|
|
471
|
-
switch_to_window(original)
|
|
532
|
+
def within_window(window_or_proc)
|
|
533
|
+
original = current_window
|
|
534
|
+
scopes << nil
|
|
535
|
+
begin
|
|
536
|
+
case window_or_proc
|
|
537
|
+
when Capybara::Window
|
|
538
|
+
_switch_to_window(window_or_proc) unless original == window_or_proc
|
|
539
|
+
when Proc
|
|
540
|
+
_switch_to_window { window_or_proc.call }
|
|
541
|
+
else
|
|
542
|
+
raise ArgumentError, '`#within_window` requires a `Capybara::Window` instance or a lambda'
|
|
472
543
|
end
|
|
473
|
-
|
|
474
|
-
offending_line = caller.first
|
|
475
|
-
file_line = offending_line.match(/^(.+?):(\d+)/)[0]
|
|
476
|
-
warn "DEPRECATION WARNING: Passing string argument to #within_window is deprecated. "\
|
|
477
|
-
"Pass window object or lambda. (called from #{file_line})"
|
|
544
|
+
|
|
478
545
|
begin
|
|
479
|
-
|
|
480
|
-
driver.within_window(window_or_handle) { yield }
|
|
546
|
+
yield if block_given?
|
|
481
547
|
ensure
|
|
482
|
-
|
|
548
|
+
_switch_to_window(original) unless original == window_or_proc
|
|
483
549
|
end
|
|
550
|
+
ensure
|
|
551
|
+
scopes.pop
|
|
484
552
|
end
|
|
485
553
|
end
|
|
486
554
|
|
|
@@ -488,23 +556,23 @@ module Capybara
|
|
|
488
556
|
# Get the window that has been opened by the passed block.
|
|
489
557
|
# It will wait for it to be opened (in the same way as other Capybara methods wait).
|
|
490
558
|
# It's better to use this method than `windows.last`
|
|
491
|
-
# {https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html#h_note_10 as order of windows isn't defined in some drivers}
|
|
559
|
+
# {https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html#h_note_10 as order of windows isn't defined in some drivers}.
|
|
492
560
|
#
|
|
493
|
-
# @
|
|
494
|
-
#
|
|
495
|
-
#
|
|
496
|
-
#
|
|
497
|
-
#
|
|
561
|
+
# @overload window_opened_by(**options, &block)
|
|
562
|
+
# @param options [Hash]
|
|
563
|
+
# @option options [Numeric] :wait maximum wait time. Defaults to {Capybara.configure default_max_wait_time}
|
|
564
|
+
# @return [Capybara::Window] the window that has been opened within a block
|
|
565
|
+
# @raise [Capybara::WindowError] if block passed to window hasn't opened window
|
|
566
|
+
# or opened more than one window
|
|
498
567
|
#
|
|
499
|
-
def window_opened_by(options
|
|
568
|
+
def window_opened_by(**options)
|
|
500
569
|
old_handles = driver.window_handles
|
|
501
|
-
|
|
570
|
+
yield
|
|
502
571
|
|
|
503
|
-
|
|
504
|
-
document.synchronize(wait_time, errors: [Capybara::WindowError]) do
|
|
572
|
+
synchronize_windows(options) do
|
|
505
573
|
opened_handles = (driver.window_handles - old_handles)
|
|
506
574
|
if opened_handles.size != 1
|
|
507
|
-
raise Capybara::WindowError,
|
|
575
|
+
raise Capybara::WindowError, 'block passed to #window_opened_by '\
|
|
508
576
|
"opened #{opened_handles.size} windows instead of 1"
|
|
509
577
|
end
|
|
510
578
|
Window.new(self, opened_handles.first)
|
|
@@ -514,28 +582,45 @@ module Capybara
|
|
|
514
582
|
##
|
|
515
583
|
#
|
|
516
584
|
# Execute the given script, not returning a result. This is useful for scripts that return
|
|
517
|
-
# complex objects, such as jQuery statements.
|
|
518
|
-
#
|
|
585
|
+
# complex objects, such as jQuery statements. {#execute_script} should be used over
|
|
586
|
+
# {#evaluate_script} whenever possible.
|
|
519
587
|
#
|
|
520
588
|
# @param [String] script A string of JavaScript to execute
|
|
589
|
+
# @param args Optional arguments that will be passed to the script. Driver support for this is optional and types of objects supported may differ between drivers
|
|
521
590
|
#
|
|
522
|
-
def execute_script(script)
|
|
591
|
+
def execute_script(script, *args)
|
|
523
592
|
@touched = true
|
|
524
|
-
driver.execute_script(script)
|
|
593
|
+
driver.execute_script(script, *driver_args(args))
|
|
525
594
|
end
|
|
526
595
|
|
|
527
596
|
##
|
|
528
597
|
#
|
|
529
598
|
# Evaluate the given JavaScript and return the result. Be careful when using this with
|
|
530
|
-
# scripts that return complex objects, such as jQuery statements.
|
|
599
|
+
# scripts that return complex objects, such as jQuery statements. {#execute_script} might
|
|
531
600
|
# be a better alternative.
|
|
532
601
|
#
|
|
533
602
|
# @param [String] script A string of JavaScript to evaluate
|
|
603
|
+
# @param args Optional arguments that will be passed to the script
|
|
604
|
+
# @return [Object] The result of the evaluated JavaScript (may be driver specific)
|
|
605
|
+
#
|
|
606
|
+
def evaluate_script(script, *args)
|
|
607
|
+
@touched = true
|
|
608
|
+
result = driver.evaluate_script(script.strip, *driver_args(args))
|
|
609
|
+
element_script_result(result)
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
##
|
|
613
|
+
#
|
|
614
|
+
# Evaluate the given JavaScript and obtain the result from a callback function which will be passed as the last argument to the script.
|
|
615
|
+
#
|
|
616
|
+
# @param [String] script A string of JavaScript to evaluate
|
|
617
|
+
# @param args Optional arguments that will be passed to the script
|
|
534
618
|
# @return [Object] The result of the evaluated JavaScript (may be driver specific)
|
|
535
619
|
#
|
|
536
|
-
def
|
|
620
|
+
def evaluate_async_script(script, *args)
|
|
537
621
|
@touched = true
|
|
538
|
-
driver.
|
|
622
|
+
result = driver.evaluate_async_script(script, *driver_args(args))
|
|
623
|
+
element_script_result(result)
|
|
539
624
|
end
|
|
540
625
|
|
|
541
626
|
##
|
|
@@ -543,19 +628,23 @@ module Capybara
|
|
|
543
628
|
# Execute the block, accepting a alert.
|
|
544
629
|
#
|
|
545
630
|
# @!macro modal_params
|
|
546
|
-
#
|
|
547
|
-
#
|
|
548
|
-
#
|
|
549
|
-
#
|
|
631
|
+
# Expects a block whose actions will trigger the display modal to appear.
|
|
632
|
+
# @example
|
|
633
|
+
# $0 do
|
|
634
|
+
# click_link('link that triggers appearance of system modal')
|
|
635
|
+
# end
|
|
636
|
+
# @overload $0(text, **options, &blk)
|
|
637
|
+
# @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any modal is matched.
|
|
638
|
+
# @option options [Numeric] :wait Maximum time to wait for the modal to appear after executing the block. Defaults to {Capybara.configure default_max_wait_time}.
|
|
639
|
+
# @yield Block whose actions will trigger the system modal
|
|
640
|
+
# @overload $0(**options, &blk)
|
|
641
|
+
# @option options [Numeric] :wait Maximum time to wait for the modal to appear after executing the block. Defaults to {Capybara.configure default_max_wait_time}.
|
|
642
|
+
# @yield Block whose actions will trigger the system modal
|
|
550
643
|
# @return [String] the message shown in the modal
|
|
551
644
|
# @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
|
|
552
645
|
#
|
|
553
|
-
def accept_alert(
|
|
554
|
-
|
|
555
|
-
options[:text] ||= text_or_options unless text_or_options.nil?
|
|
556
|
-
options[:wait] ||= Capybara.default_max_wait_time
|
|
557
|
-
|
|
558
|
-
driver.accept_modal(:alert, options, &blk)
|
|
646
|
+
def accept_alert(text = nil, **options, &blk)
|
|
647
|
+
accept_modal(:alert, text, options, &blk)
|
|
559
648
|
end
|
|
560
649
|
|
|
561
650
|
##
|
|
@@ -564,12 +653,8 @@ module Capybara
|
|
|
564
653
|
#
|
|
565
654
|
# @macro modal_params
|
|
566
655
|
#
|
|
567
|
-
def accept_confirm(
|
|
568
|
-
|
|
569
|
-
options[:text] ||= text_or_options unless text_or_options.nil?
|
|
570
|
-
options[:wait] ||= Capybara.default_max_wait_time
|
|
571
|
-
|
|
572
|
-
driver.accept_modal(:confirm, options, &blk)
|
|
656
|
+
def accept_confirm(text = nil, **options, &blk)
|
|
657
|
+
accept_modal(:confirm, text, options, &blk)
|
|
573
658
|
end
|
|
574
659
|
|
|
575
660
|
##
|
|
@@ -578,12 +663,8 @@ module Capybara
|
|
|
578
663
|
#
|
|
579
664
|
# @macro modal_params
|
|
580
665
|
#
|
|
581
|
-
def dismiss_confirm(
|
|
582
|
-
|
|
583
|
-
options[:text] ||= text_or_options unless text_or_options.nil?
|
|
584
|
-
options[:wait] ||= Capybara.default_max_wait_time
|
|
585
|
-
|
|
586
|
-
driver.dismiss_modal(:confirm, options, &blk)
|
|
666
|
+
def dismiss_confirm(text = nil, **options, &blk)
|
|
667
|
+
dismiss_modal(:confirm, text, options, &blk)
|
|
587
668
|
end
|
|
588
669
|
|
|
589
670
|
##
|
|
@@ -593,12 +674,8 @@ module Capybara
|
|
|
593
674
|
# @macro modal_params
|
|
594
675
|
# @option options [String] :with Response to provide to the prompt
|
|
595
676
|
#
|
|
596
|
-
def accept_prompt(
|
|
597
|
-
|
|
598
|
-
options[:text] ||= text_or_options unless text_or_options.nil?
|
|
599
|
-
options[:wait] ||= Capybara.default_max_wait_time
|
|
600
|
-
|
|
601
|
-
driver.accept_modal(:prompt, options, &blk)
|
|
677
|
+
def accept_prompt(text = nil, **options, &blk)
|
|
678
|
+
accept_modal(:prompt, text, options, &blk)
|
|
602
679
|
end
|
|
603
680
|
|
|
604
681
|
##
|
|
@@ -607,86 +684,70 @@ module Capybara
|
|
|
607
684
|
#
|
|
608
685
|
# @macro modal_params
|
|
609
686
|
#
|
|
610
|
-
def dismiss_prompt(
|
|
611
|
-
|
|
612
|
-
options[:text] ||= text_or_options unless text_or_options.nil?
|
|
613
|
-
options[:wait] ||= Capybara.default_max_wait_time
|
|
614
|
-
|
|
615
|
-
driver.dismiss_modal(:prompt, options, &blk)
|
|
687
|
+
def dismiss_prompt(text = nil, **options, &blk)
|
|
688
|
+
dismiss_modal(:prompt, text, options, &blk)
|
|
616
689
|
end
|
|
617
690
|
|
|
618
691
|
##
|
|
619
692
|
#
|
|
620
|
-
# Save a snapshot of the page. If
|
|
621
|
-
#
|
|
693
|
+
# Save a snapshot of the page. If {Capybara.configure asset_host} is set it will inject `base` tag
|
|
694
|
+
# pointing to {Capybara.configure asset_host}.
|
|
622
695
|
#
|
|
623
|
-
# If invoked without arguments it will save file to
|
|
624
|
-
#
|
|
625
|
-
#
|
|
626
|
-
# the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
|
|
627
|
-
# relative to Dir.pwd
|
|
696
|
+
# If invoked without arguments it will save file to {Capybara.configure save_path}
|
|
697
|
+
# and file will be given randomly generated filename. If invoked with a relative path
|
|
698
|
+
# the path will be relative to {Capybara.configure save_path}.
|
|
628
699
|
#
|
|
629
700
|
# @param [String] path the path to where it should be saved
|
|
630
701
|
# @return [String] the path to which the file was saved
|
|
631
702
|
#
|
|
632
703
|
def save_page(path = nil)
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
704
|
+
prepare_path(path, 'html').tap do |p_path|
|
|
705
|
+
File.write(p_path, Capybara::Helpers.inject_asset_host(body, host: config.asset_host), mode: 'wb')
|
|
706
|
+
end
|
|
636
707
|
end
|
|
637
708
|
|
|
638
709
|
##
|
|
639
710
|
#
|
|
640
711
|
# Save a snapshot of the page and open it in a browser for inspection.
|
|
641
712
|
#
|
|
642
|
-
# If invoked without arguments it will save file to
|
|
643
|
-
#
|
|
644
|
-
#
|
|
645
|
-
# the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
|
|
646
|
-
# relative to Dir.pwd
|
|
713
|
+
# If invoked without arguments it will save file to {Capybara.configure save_path}
|
|
714
|
+
# and file will be given randomly generated filename. If invoked with a relative path
|
|
715
|
+
# the path will be relative to {Capybara.configure save_path}.
|
|
647
716
|
#
|
|
648
717
|
# @param [String] path the path to where it should be saved
|
|
649
718
|
#
|
|
650
719
|
def save_and_open_page(path = nil)
|
|
651
|
-
path
|
|
652
|
-
open_file(path)
|
|
720
|
+
save_page(path).tap { |s_path| open_file(s_path) }
|
|
653
721
|
end
|
|
654
722
|
|
|
655
723
|
##
|
|
656
724
|
#
|
|
657
725
|
# Save a screenshot of page.
|
|
658
726
|
#
|
|
659
|
-
# If invoked without arguments it will save file to
|
|
660
|
-
#
|
|
661
|
-
#
|
|
662
|
-
# the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
|
|
663
|
-
# relative to Dir.pwd
|
|
727
|
+
# If invoked without arguments it will save file to {Capybara.configure save_path}
|
|
728
|
+
# and file will be given randomly generated filename. If invoked with a relative path
|
|
729
|
+
# the path will be relative to {Capybara.configure save_path}.
|
|
664
730
|
#
|
|
665
731
|
# @param [String] path the path to where it should be saved
|
|
666
732
|
# @param [Hash] options a customizable set of options
|
|
667
733
|
# @return [String] the path to which the file was saved
|
|
668
|
-
def save_screenshot(path = nil, options
|
|
669
|
-
|
|
670
|
-
driver.save_screenshot(path, options)
|
|
671
|
-
path
|
|
734
|
+
def save_screenshot(path = nil, **options)
|
|
735
|
+
prepare_path(path, 'png').tap { |p_path| driver.save_screenshot(p_path, **options) }
|
|
672
736
|
end
|
|
673
737
|
|
|
674
738
|
##
|
|
675
739
|
#
|
|
676
740
|
# Save a screenshot of the page and open it for inspection.
|
|
677
741
|
#
|
|
678
|
-
# If invoked without arguments it will save file to
|
|
679
|
-
#
|
|
680
|
-
#
|
|
681
|
-
# the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
|
|
682
|
-
# relative to Dir.pwd
|
|
742
|
+
# If invoked without arguments it will save file to {Capybara.configure save_path}
|
|
743
|
+
# and file will be given randomly generated filename. If invoked with a relative path
|
|
744
|
+
# the path will be relative to {Capybara.configure save_path}.
|
|
683
745
|
#
|
|
684
746
|
# @param [String] path the path to where it should be saved
|
|
685
747
|
# @param [Hash] options a customizable set of options
|
|
686
748
|
#
|
|
687
|
-
def save_and_open_screenshot(path = nil, options
|
|
688
|
-
|
|
689
|
-
open_file(path)
|
|
749
|
+
def save_and_open_screenshot(path = nil, **options)
|
|
750
|
+
save_screenshot(path, **options).tap { |s_path| open_file(s_path) }
|
|
690
751
|
end
|
|
691
752
|
|
|
692
753
|
def document
|
|
@@ -694,15 +755,32 @@ module Capybara
|
|
|
694
755
|
end
|
|
695
756
|
|
|
696
757
|
NODE_METHODS.each do |method|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
758
|
+
if RUBY_VERSION >= '2.7'
|
|
759
|
+
class_eval <<~METHOD, __FILE__, __LINE__ + 1
|
|
760
|
+
def #{method}(...)
|
|
761
|
+
@touched = true
|
|
762
|
+
current_scope.#{method}(...)
|
|
763
|
+
end
|
|
764
|
+
METHOD
|
|
765
|
+
else
|
|
766
|
+
define_method method do |*args, &block|
|
|
767
|
+
@touched = true
|
|
768
|
+
current_scope.send(method, *args, &block)
|
|
769
|
+
end
|
|
700
770
|
end
|
|
701
771
|
end
|
|
702
772
|
|
|
703
773
|
DOCUMENT_METHODS.each do |method|
|
|
704
|
-
|
|
705
|
-
|
|
774
|
+
if RUBY_VERSION >= '2.7'
|
|
775
|
+
class_eval <<~METHOD, __FILE__, __LINE__ + 1
|
|
776
|
+
def #{method}(...)
|
|
777
|
+
document.#{method}(...)
|
|
778
|
+
end
|
|
779
|
+
METHOD
|
|
780
|
+
else
|
|
781
|
+
define_method method do |*args, &block|
|
|
782
|
+
document.send(method, *args, &block)
|
|
783
|
+
end
|
|
706
784
|
end
|
|
707
785
|
end
|
|
708
786
|
|
|
@@ -711,38 +789,163 @@ module Capybara
|
|
|
711
789
|
end
|
|
712
790
|
|
|
713
791
|
def current_scope
|
|
714
|
-
scopes.last
|
|
792
|
+
scope = scopes.last
|
|
793
|
+
[nil, :frame].include?(scope) ? document : scope
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
##
|
|
797
|
+
#
|
|
798
|
+
# Yield a block using a specific maximum wait time.
|
|
799
|
+
#
|
|
800
|
+
def using_wait_time(seconds, &block)
|
|
801
|
+
if Capybara.threadsafe
|
|
802
|
+
begin
|
|
803
|
+
previous_wait_time = config.default_max_wait_time
|
|
804
|
+
config.default_max_wait_time = seconds
|
|
805
|
+
yield
|
|
806
|
+
ensure
|
|
807
|
+
config.default_max_wait_time = previous_wait_time
|
|
808
|
+
end
|
|
809
|
+
else
|
|
810
|
+
Capybara.using_wait_time(seconds, &block)
|
|
811
|
+
end
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
##
|
|
815
|
+
#
|
|
816
|
+
# Accepts a block to set the configuration options if {Capybara.configure threadsafe} is `true`. Note that some options only have an effect
|
|
817
|
+
# if set at initialization time, so look at the configuration block that can be passed to the initializer too.
|
|
818
|
+
#
|
|
819
|
+
def configure
|
|
820
|
+
raise 'Session configuration is only supported when Capybara.threadsafe == true' unless Capybara.threadsafe
|
|
821
|
+
|
|
822
|
+
yield config
|
|
823
|
+
end
|
|
824
|
+
|
|
825
|
+
def self.instance_created?
|
|
826
|
+
@@instance_created
|
|
827
|
+
end
|
|
828
|
+
|
|
829
|
+
def config
|
|
830
|
+
@config ||= if Capybara.threadsafe
|
|
831
|
+
Capybara.session_options.dup
|
|
832
|
+
else
|
|
833
|
+
Capybara::ReadOnlySessionConfig.new(Capybara.session_options)
|
|
834
|
+
end
|
|
835
|
+
end
|
|
836
|
+
|
|
837
|
+
def server_url
|
|
838
|
+
@server&.base_url
|
|
715
839
|
end
|
|
716
840
|
|
|
717
841
|
private
|
|
718
842
|
|
|
843
|
+
@@instance_created = false # rubocop:disable Style/ClassVars
|
|
844
|
+
|
|
845
|
+
def driver_args(args)
|
|
846
|
+
args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg }
|
|
847
|
+
end
|
|
848
|
+
|
|
849
|
+
def accept_modal(type, text_or_options, options, &blk)
|
|
850
|
+
driver.accept_modal(type, **modal_options(text_or_options, **options), &blk)
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
def dismiss_modal(type, text_or_options, options, &blk)
|
|
854
|
+
driver.dismiss_modal(type, **modal_options(text_or_options, **options), &blk)
|
|
855
|
+
end
|
|
856
|
+
|
|
857
|
+
def modal_options(text = nil, **options)
|
|
858
|
+
options[:text] ||= text unless text.nil?
|
|
859
|
+
options[:wait] ||= config.default_max_wait_time
|
|
860
|
+
options
|
|
861
|
+
end
|
|
862
|
+
|
|
719
863
|
def open_file(path)
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
warn "File saved to #{path}."
|
|
725
|
-
warn "Please install the launchy gem to open the file automatically."
|
|
726
|
-
end
|
|
864
|
+
require 'launchy'
|
|
865
|
+
Launchy.open(path)
|
|
866
|
+
rescue LoadError
|
|
867
|
+
warn "File saved to #{path}.\nPlease install the launchy gem to open the file automatically."
|
|
727
868
|
end
|
|
728
869
|
|
|
729
870
|
def prepare_path(path, extension)
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
else
|
|
733
|
-
path = File.expand_path(default_fn(extension), Capybara.save_and_open_page_path) if path.nil?
|
|
871
|
+
File.expand_path(path || default_fn(extension), config.save_path).tap do |p_path|
|
|
872
|
+
FileUtils.mkdir_p(File.dirname(p_path))
|
|
734
873
|
end
|
|
735
|
-
FileUtils.mkdir_p(File.dirname(path))
|
|
736
|
-
path
|
|
737
874
|
end
|
|
738
875
|
|
|
739
876
|
def default_fn(extension)
|
|
740
|
-
timestamp = Time.new.strftime(
|
|
741
|
-
|
|
877
|
+
timestamp = Time.new.strftime('%Y%m%d%H%M%S')
|
|
878
|
+
"capybara-#{timestamp}#{rand(10**10)}.#{extension}"
|
|
742
879
|
end
|
|
743
880
|
|
|
744
881
|
def scopes
|
|
745
882
|
@scopes ||= [nil]
|
|
746
883
|
end
|
|
884
|
+
|
|
885
|
+
def element_script_result(arg)
|
|
886
|
+
case arg
|
|
887
|
+
when Array
|
|
888
|
+
arg.map { |subarg| element_script_result(subarg) }
|
|
889
|
+
when Hash
|
|
890
|
+
arg.transform_values! { |value| element_script_result(value) }
|
|
891
|
+
when Capybara::Driver::Node
|
|
892
|
+
Capybara::Node::Element.new(self, arg, nil, nil)
|
|
893
|
+
else
|
|
894
|
+
arg
|
|
895
|
+
end
|
|
896
|
+
end
|
|
897
|
+
|
|
898
|
+
def adjust_server_port(uri)
|
|
899
|
+
uri.port ||= @server.port if @server && config.always_include_port
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
def _find_frame(*args, **kw_args)
|
|
903
|
+
case args[0]
|
|
904
|
+
when Capybara::Node::Element
|
|
905
|
+
args[0]
|
|
906
|
+
when String, nil
|
|
907
|
+
find(:frame, *args, **kw_args)
|
|
908
|
+
when Symbol
|
|
909
|
+
find(*args, **kw_args)
|
|
910
|
+
when Integer
|
|
911
|
+
idx = args[0]
|
|
912
|
+
all(:frame, minimum: idx + 1)[idx]
|
|
913
|
+
else
|
|
914
|
+
raise TypeError
|
|
915
|
+
end
|
|
916
|
+
end
|
|
917
|
+
|
|
918
|
+
def _switch_to_window(window = nil, **options, &window_locator)
|
|
919
|
+
raise Capybara::ScopeError, 'Window cannot be switched inside a `within_frame` block' if scopes.include?(:frame)
|
|
920
|
+
raise Capybara::ScopeError, 'Window cannot be switched inside a `within` block' unless scopes.last.nil?
|
|
921
|
+
|
|
922
|
+
if window
|
|
923
|
+
driver.switch_to_window(window.handle)
|
|
924
|
+
window
|
|
925
|
+
else
|
|
926
|
+
synchronize_windows(options) do
|
|
927
|
+
original_window_handle = driver.current_window_handle
|
|
928
|
+
begin
|
|
929
|
+
_switch_to_window_by_locator(&window_locator)
|
|
930
|
+
rescue StandardError
|
|
931
|
+
driver.switch_to_window(original_window_handle)
|
|
932
|
+
raise
|
|
933
|
+
end
|
|
934
|
+
end
|
|
935
|
+
end
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
def _switch_to_window_by_locator
|
|
939
|
+
driver.window_handles.each do |handle|
|
|
940
|
+
driver.switch_to_window handle
|
|
941
|
+
return Window.new(self, handle) if yield
|
|
942
|
+
end
|
|
943
|
+
raise Capybara::WindowError, 'Could not find a window matching block/lambda'
|
|
944
|
+
end
|
|
945
|
+
|
|
946
|
+
def synchronize_windows(options, &block)
|
|
947
|
+
wait_time = Capybara::Queries::BaseQuery.wait(options, config.default_max_wait_time)
|
|
948
|
+
document.synchronize(wait_time, errors: [Capybara::WindowError], &block)
|
|
949
|
+
end
|
|
747
950
|
end
|
|
748
951
|
end
|