capybara 2.5.0 → 2.18.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 +5 -5
- data/.yard/templates_custom/default/class/html/selectors.erb +38 -0
- data/.yard/templates_custom/default/class/html/setup.rb +17 -0
- data/.yard/yard_extensions.rb +78 -0
- data/.yardopts +1 -0
- data/History.md +413 -10
- data/License.txt +1 -1
- data/README.md +237 -130
- data/lib/capybara/config.rb +132 -0
- data/lib/capybara/cucumber.rb +3 -1
- data/lib/capybara/driver/base.rb +27 -6
- data/lib/capybara/driver/node.rb +14 -5
- data/lib/capybara/dsl.rb +2 -3
- data/lib/capybara/helpers.rb +13 -65
- data/lib/capybara/minitest/spec.rb +177 -0
- data/lib/capybara/minitest.rb +278 -0
- data/lib/capybara/node/actions.rb +180 -24
- data/lib/capybara/node/base.rb +17 -5
- data/lib/capybara/node/document.rb +5 -0
- data/lib/capybara/node/document_matchers.rb +15 -14
- data/lib/capybara/node/element.rb +55 -7
- data/lib/capybara/node/finders.rb +179 -67
- data/lib/capybara/node/matchers.rb +301 -105
- data/lib/capybara/node/simple.rb +15 -4
- data/lib/capybara/queries/ancestor_query.rb +25 -0
- data/lib/capybara/queries/base_query.rb +69 -3
- data/lib/capybara/queries/current_path_query.rb +17 -8
- data/lib/capybara/queries/match_query.rb +19 -0
- data/lib/capybara/queries/selector_query.rb +251 -0
- data/lib/capybara/queries/sibling_query.rb +25 -0
- data/lib/capybara/queries/text_query.rb +67 -16
- data/lib/capybara/queries/title_query.rb +4 -2
- data/lib/capybara/query.rb +3 -131
- data/lib/capybara/rack_test/browser.rb +14 -5
- data/lib/capybara/rack_test/css_handlers.rb +1 -0
- data/lib/capybara/rack_test/driver.rb +15 -8
- data/lib/capybara/rack_test/form.rb +34 -12
- data/lib/capybara/rack_test/node.rb +29 -12
- data/lib/capybara/rails.rb +3 -3
- data/lib/capybara/result.rb +104 -9
- data/lib/capybara/rspec/compound.rb +95 -0
- data/lib/capybara/rspec/features.rb +17 -6
- data/lib/capybara/rspec/matcher_proxies.rb +45 -0
- data/lib/capybara/rspec/matchers.rb +199 -80
- data/lib/capybara/rspec.rb +4 -2
- data/lib/capybara/selector/css.rb +30 -0
- data/lib/capybara/selector/filter.rb +20 -0
- data/lib/capybara/selector/filter_set.rb +74 -0
- data/lib/capybara/selector/filters/base.rb +33 -0
- data/lib/capybara/selector/filters/expression_filter.rb +40 -0
- data/lib/capybara/selector/filters/node_filter.rb +27 -0
- data/lib/capybara/selector/selector.rb +276 -0
- data/lib/capybara/selector.rb +452 -157
- data/lib/capybara/selenium/driver.rb +282 -81
- data/lib/capybara/selenium/node.rb +144 -46
- data/lib/capybara/server.rb +59 -16
- data/lib/capybara/session/config.rb +114 -0
- data/lib/capybara/session/matchers.rb +29 -19
- data/lib/capybara/session.rb +378 -143
- data/lib/capybara/spec/fixtures/no_extension +1 -0
- data/lib/capybara/spec/public/jquery-ui.js +13 -791
- data/lib/capybara/spec/public/jquery.js +4 -9045
- data/lib/capybara/spec/public/test.js +45 -11
- data/lib/capybara/spec/session/accept_alert_spec.rb +30 -7
- data/lib/capybara/spec/session/accept_confirm_spec.rb +14 -2
- data/lib/capybara/spec/session/accept_prompt_spec.rb +35 -6
- data/lib/capybara/spec/session/all_spec.rb +45 -32
- data/lib/capybara/spec/session/ancestor_spec.rb +85 -0
- data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +110 -0
- data/lib/capybara/spec/session/assert_current_path.rb +15 -2
- data/lib/capybara/spec/session/assert_selector.rb +29 -28
- data/lib/capybara/spec/session/assert_text.rb +59 -20
- data/lib/capybara/spec/session/assert_title.rb +25 -11
- data/lib/capybara/spec/session/attach_file_spec.rb +42 -4
- data/lib/capybara/spec/session/body_spec.rb +1 -0
- data/lib/capybara/spec/session/check_spec.rb +90 -14
- data/lib/capybara/spec/session/choose_spec.rb +31 -5
- data/lib/capybara/spec/session/click_button_spec.rb +20 -9
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +15 -9
- data/lib/capybara/spec/session/click_link_spec.rb +39 -15
- data/lib/capybara/spec/session/current_scope_spec.rb +2 -1
- data/lib/capybara/spec/session/current_url_spec.rb +12 -3
- data/lib/capybara/spec/session/dismiss_confirm_spec.rb +6 -5
- data/lib/capybara/spec/session/dismiss_prompt_spec.rb +4 -3
- data/lib/capybara/spec/session/element/assert_match_selector.rb +36 -0
- data/lib/capybara/spec/session/element/match_css_spec.rb +23 -0
- data/lib/capybara/spec/session/element/match_xpath_spec.rb +23 -0
- data/lib/capybara/spec/session/element/matches_selector_spec.rb +106 -0
- data/lib/capybara/spec/session/evaluate_async_script_spec.rb +22 -0
- data/lib/capybara/spec/session/evaluate_script_spec.rb +23 -1
- data/lib/capybara/spec/session/execute_script_spec.rb +22 -3
- data/lib/capybara/spec/session/fill_in_spec.rb +50 -32
- data/lib/capybara/spec/session/find_button_spec.rb +43 -2
- data/lib/capybara/spec/session/find_by_id_spec.rb +3 -2
- data/lib/capybara/spec/session/find_field_spec.rb +42 -6
- data/lib/capybara/spec/session/find_link_spec.rb +22 -3
- data/lib/capybara/spec/session/find_spec.rb +103 -57
- data/lib/capybara/spec/session/first_spec.rb +34 -18
- data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +103 -0
- data/lib/capybara/spec/session/{within_frame_spec.rb → frame/within_frame_spec.rb} +44 -2
- 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 +69 -0
- data/lib/capybara/spec/session/has_button_spec.rb +17 -8
- data/lib/capybara/spec/session/has_css_spec.rb +85 -73
- data/lib/capybara/spec/session/has_current_path_spec.rb +91 -7
- data/lib/capybara/spec/session/has_field_spec.rb +93 -58
- data/lib/capybara/spec/session/has_link_spec.rb +9 -8
- data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
- data/lib/capybara/spec/session/has_select_spec.rb +159 -59
- data/lib/capybara/spec/session/has_selector_spec.rb +64 -28
- data/lib/capybara/spec/session/has_table_spec.rb +1 -0
- data/lib/capybara/spec/session/has_text_spec.rb +27 -12
- data/lib/capybara/spec/session/has_title_spec.rb +22 -4
- data/lib/capybara/spec/session/has_xpath_spec.rb +32 -29
- data/lib/capybara/spec/session/headers.rb +2 -1
- data/lib/capybara/spec/session/html_spec.rb +4 -3
- data/lib/capybara/spec/session/node_spec.rb +198 -38
- data/lib/capybara/spec/session/refresh_spec.rb +28 -0
- data/lib/capybara/spec/session/reset_session_spec.rb +46 -5
- data/lib/capybara/spec/session/response_code.rb +2 -1
- data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +6 -5
- data/lib/capybara/spec/session/save_page_spec.rb +34 -2
- data/lib/capybara/spec/session/save_screenshot_spec.rb +31 -1
- data/lib/capybara/spec/session/screenshot_spec.rb +4 -2
- data/lib/capybara/spec/session/select_spec.rb +34 -32
- data/lib/capybara/spec/session/selectors_spec.rb +65 -0
- data/lib/capybara/spec/session/sibling_spec.rb +52 -0
- data/lib/capybara/spec/session/text_spec.rb +4 -4
- data/lib/capybara/spec/session/title_spec.rb +2 -1
- data/lib/capybara/spec/session/uncheck_spec.rb +42 -2
- data/lib/capybara/spec/session/unselect_spec.rb +17 -16
- data/lib/capybara/spec/session/visit_spec.rb +77 -2
- data/lib/capybara/spec/session/window/become_closed_spec.rb +12 -11
- data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
- data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +16 -11
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +7 -4
- data/lib/capybara/spec/session/window/window_spec.rb +36 -29
- data/lib/capybara/spec/session/window/windows_spec.rb +1 -0
- data/lib/capybara/spec/session/window/within_window_spec.rb +31 -7
- data/lib/capybara/spec/session/within_spec.rb +14 -6
- data/lib/capybara/spec/spec_helper.rb +37 -4
- data/lib/capybara/spec/test_app.rb +15 -3
- data/lib/capybara/spec/views/buttons.erb +1 -0
- data/lib/capybara/spec/views/fieldsets.erb +2 -1
- data/lib/capybara/spec/views/form.erb +169 -9
- 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 +3 -2
- data/lib/capybara/spec/views/frame_two.erb +2 -1
- data/lib/capybara/spec/views/header_links.erb +1 -0
- data/lib/capybara/spec/views/host_links.erb +1 -0
- data/lib/capybara/spec/views/initial_alert.erb +10 -0
- data/lib/capybara/spec/views/path.erb +1 -0
- data/lib/capybara/spec/views/popup_one.erb +1 -0
- data/lib/capybara/spec/views/popup_two.erb +1 -0
- data/lib/capybara/spec/views/postback.erb +2 -1
- data/lib/capybara/spec/views/tables.erb +1 -0
- data/lib/capybara/spec/views/with_base_tag.erb +1 -0
- data/lib/capybara/spec/views/with_count.erb +2 -1
- 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_html.erb +40 -2
- data/lib/capybara/spec/views/with_html_entities.erb +1 -0
- data/lib/capybara/spec/views/with_js.erb +32 -1
- data/lib/capybara/spec/views/with_scope.erb +1 -0
- data/lib/capybara/spec/views/with_simple_html.erb +2 -1
- data/lib/capybara/spec/views/with_slow_unload.erb +17 -0
- data/lib/capybara/spec/views/with_title.erb +2 -1
- data/lib/capybara/spec/views/with_unload_alert.erb +14 -0
- data/lib/capybara/spec/views/with_windows.erb +7 -0
- data/lib/capybara/spec/views/within_frames.erb +3 -2
- data/lib/capybara/version.rb +2 -1
- data/lib/capybara/window.rb +20 -3
- data/lib/capybara.rb +189 -93
- data/spec/basic_node_spec.rb +7 -6
- data/spec/capybara_spec.rb +90 -4
- data/spec/dsl_spec.rb +3 -1
- data/spec/filter_set_spec.rb +28 -0
- data/spec/fixtures/capybara.csv +1 -0
- data/spec/fixtures/selenium_driver_rspec_failure.rb +5 -1
- data/spec/fixtures/selenium_driver_rspec_success.rb +5 -1
- data/spec/minitest_spec.rb +130 -0
- data/spec/minitest_spec_spec.rb +135 -0
- data/spec/per_session_config_spec.rb +67 -0
- data/spec/rack_test_spec.rb +50 -7
- data/spec/result_spec.rb +76 -0
- data/spec/rspec/features_spec.rb +21 -8
- data/spec/rspec/scenarios_spec.rb +21 -0
- data/spec/rspec/{matchers_spec.rb → shared_spec_matchers.rb} +160 -54
- data/spec/rspec/views_spec.rb +5 -0
- data/spec/rspec_matchers_spec.rb +46 -0
- data/spec/rspec_spec.rb +79 -1
- data/spec/selector_spec.rb +199 -0
- data/spec/selenium_spec_chrome.rb +54 -9
- data/spec/selenium_spec_firefox.rb +68 -0
- data/spec/selenium_spec_marionette.rb +127 -0
- data/spec/server_spec.rb +102 -14
- data/spec/session_spec.rb +54 -0
- data/spec/shared_selenium_session.rb +215 -0
- data/spec/spec_helper.rb +7 -0
- metadata +140 -15
- data/spec/selenium_spec.rb +0 -128
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
3
|
+
|
|
2
4
|
def visible_text
|
|
3
5
|
# Selenium doesn't normalize Unicode whitespace.
|
|
4
6
|
Capybara::Helpers.normalize_whitespace(native.text)
|
|
5
7
|
end
|
|
6
8
|
|
|
7
9
|
def all_text
|
|
8
|
-
text = driver.
|
|
10
|
+
text = driver.execute_script("return arguments[0].textContent", self)
|
|
9
11
|
Capybara::Helpers.normalize_whitespace(text)
|
|
10
12
|
end
|
|
11
13
|
|
|
@@ -16,77 +18,110 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def value
|
|
19
|
-
if tag_name == "select" and
|
|
20
|
-
native.find_elements(:
|
|
21
|
+
if tag_name == "select" and multiple?
|
|
22
|
+
native.find_elements(:css, "option:checked").map { |n| n[:value] || n.text }
|
|
21
23
|
else
|
|
22
24
|
native[:value]
|
|
23
25
|
end
|
|
24
26
|
end
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
##
|
|
29
|
+
#
|
|
30
|
+
# Set the value of the form element to the given value.
|
|
31
|
+
#
|
|
32
|
+
# @param [String] value The new value
|
|
33
|
+
# @param [Hash{}] options Driver specific options for how to set the value
|
|
34
|
+
# @option options [Symbol,Array] :clear (nil) The method used to clear the previous value <br/>
|
|
35
|
+
# nil => clear via javascript <br/>
|
|
36
|
+
# :none => append the new value to the existing value <br/>
|
|
37
|
+
# :backspace => send backspace keystrokes to clear the field <br/>
|
|
38
|
+
# Array => an array of keys to send before the value being set, e.g. [[:command, 'a'], :backspace]
|
|
39
|
+
def set(value, options={})
|
|
27
40
|
tag_name = self.tag_name
|
|
28
41
|
type = self[:type]
|
|
29
|
-
|
|
42
|
+
|
|
43
|
+
if (Array === value) && !multiple?
|
|
30
44
|
raise ArgumentError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
|
|
31
45
|
end
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
|
|
47
|
+
case tag_name
|
|
48
|
+
when 'input'
|
|
49
|
+
case type
|
|
50
|
+
when 'radio'
|
|
51
|
+
click
|
|
52
|
+
when 'checkbox'
|
|
53
|
+
click if value ^ native.attribute('checked').to_s.eql?("true")
|
|
54
|
+
when 'file'
|
|
55
|
+
path_names = value.to_s.empty? ? [] : value
|
|
56
|
+
if driver.chrome?
|
|
57
|
+
native.send_keys(Array(path_names).join("\n"))
|
|
58
|
+
else
|
|
59
|
+
native.send_keys(*path_names)
|
|
60
|
+
end
|
|
44
61
|
else
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
62
|
+
set_text(value, options)
|
|
63
|
+
end
|
|
64
|
+
when 'textarea'
|
|
65
|
+
set_text(value, options)
|
|
66
|
+
else
|
|
67
|
+
if content_editable?
|
|
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
|
|
49
85
|
else
|
|
50
|
-
#
|
|
51
|
-
# Script can change a readonly element which user input cannot, so
|
|
52
|
-
# don't execute if readonly.
|
|
53
|
-
driver.browser.execute_script "arguments[0].value = ''", native
|
|
86
|
+
# action api is really slow here just use native.send_keys
|
|
54
87
|
native.send_keys(value.to_s)
|
|
55
88
|
end
|
|
56
89
|
end
|
|
57
|
-
elsif native.attribute('isContentEditable')
|
|
58
|
-
#ensure we are focused on the element
|
|
59
|
-
script = <<-JS
|
|
60
|
-
var range = document.createRange();
|
|
61
|
-
range.selectNodeContents(arguments[0]);
|
|
62
|
-
window.getSelection().addRange(range);
|
|
63
|
-
JS
|
|
64
|
-
driver.browser.execute_script script, native
|
|
65
|
-
native.send_keys(value.to_s)
|
|
66
90
|
end
|
|
67
91
|
end
|
|
68
92
|
|
|
69
93
|
def select_option
|
|
70
|
-
native.click unless selected?
|
|
94
|
+
native.click unless selected? || disabled?
|
|
71
95
|
end
|
|
72
96
|
|
|
73
97
|
def unselect_option
|
|
74
|
-
|
|
75
|
-
raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
|
|
76
|
-
end
|
|
98
|
+
raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box." if !select_node.multiple?
|
|
77
99
|
native.click if selected?
|
|
78
100
|
end
|
|
79
101
|
|
|
80
102
|
def click
|
|
81
103
|
native.click
|
|
104
|
+
rescue => e
|
|
105
|
+
if e.is_a?(::Selenium::WebDriver::Error::ElementClickInterceptedError) ||
|
|
106
|
+
e.message =~ /Other element would receive the click/
|
|
107
|
+
begin
|
|
108
|
+
driver.execute_script("arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'})", self)
|
|
109
|
+
rescue
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
raise e
|
|
82
113
|
end
|
|
83
114
|
|
|
84
115
|
def right_click
|
|
85
|
-
|
|
116
|
+
scroll_if_needed do
|
|
117
|
+
driver.browser.action.context_click(native).perform
|
|
118
|
+
end
|
|
86
119
|
end
|
|
87
120
|
|
|
88
121
|
def double_click
|
|
89
|
-
|
|
122
|
+
scroll_if_needed do
|
|
123
|
+
driver.browser.action.double_click(native).perform
|
|
124
|
+
end
|
|
90
125
|
end
|
|
91
126
|
|
|
92
127
|
def send_keys(*args)
|
|
@@ -94,11 +129,15 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
94
129
|
end
|
|
95
130
|
|
|
96
131
|
def hover
|
|
97
|
-
|
|
132
|
+
scroll_if_needed do
|
|
133
|
+
driver.browser.action.move_to(native).perform
|
|
134
|
+
end
|
|
98
135
|
end
|
|
99
136
|
|
|
100
137
|
def drag_to(element)
|
|
101
|
-
|
|
138
|
+
scroll_if_needed do
|
|
139
|
+
driver.browser.action.drag_and_drop(native, element.native).perform
|
|
140
|
+
end
|
|
102
141
|
end
|
|
103
142
|
|
|
104
143
|
def tag_name
|
|
@@ -114,12 +153,34 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
114
153
|
selected = native.selected?
|
|
115
154
|
selected and selected != "false"
|
|
116
155
|
end
|
|
156
|
+
alias :checked? :selected?
|
|
117
157
|
|
|
118
158
|
def disabled?
|
|
119
|
-
|
|
159
|
+
# workaround for selenium-webdriver/geckodriver reporting elements as enabled when they are nested in disabling elements
|
|
160
|
+
if driver.marionette?
|
|
161
|
+
if %w(option optgroup).include? tag_name
|
|
162
|
+
!native.enabled? || find_xpath("parent::*[self::optgroup or self::select]")[0].disabled?
|
|
163
|
+
else
|
|
164
|
+
!native.enabled? || !find_xpath("parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]").empty?
|
|
165
|
+
end
|
|
166
|
+
else
|
|
167
|
+
!native.enabled?
|
|
168
|
+
end
|
|
120
169
|
end
|
|
121
170
|
|
|
122
|
-
|
|
171
|
+
def readonly?
|
|
172
|
+
readonly = self[:readonly]
|
|
173
|
+
readonly and readonly != "false"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def multiple?
|
|
177
|
+
multiple = self[:multiple]
|
|
178
|
+
multiple and multiple != "false"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def content_editable?
|
|
182
|
+
native.attribute('isContentEditable')
|
|
183
|
+
end
|
|
123
184
|
|
|
124
185
|
def find_xpath(locator)
|
|
125
186
|
native.find_elements(:xpath, locator).map { |n| self.class.new(driver, n) }
|
|
@@ -158,9 +219,46 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
158
219
|
end
|
|
159
220
|
|
|
160
221
|
private
|
|
161
|
-
|
|
162
222
|
# a reference to the select node if this is an option node
|
|
163
223
|
def select_node
|
|
164
|
-
find_xpath('./ancestor::select').first
|
|
224
|
+
find_xpath('./ancestor::select[1]').first
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def set_text(value, options)
|
|
228
|
+
if readonly?
|
|
229
|
+
warn "Attempt to set readonly element with value: #{value} \n *This will raise an exception in a future version of Capybara"
|
|
230
|
+
elsif value.to_s.empty? && options[:clear].nil?
|
|
231
|
+
native.clear
|
|
232
|
+
else
|
|
233
|
+
if options[:clear] == :backspace
|
|
234
|
+
# Clear field by sending the correct number of backspace keys.
|
|
235
|
+
backspaces = [:backspace] * self.value.to_s.length
|
|
236
|
+
native.send_keys(*(backspaces + [value.to_s]))
|
|
237
|
+
elsif options[:clear] == :none
|
|
238
|
+
native.send_keys(value.to_s)
|
|
239
|
+
elsif options[:clear].is_a? Array
|
|
240
|
+
native.send_keys(*options[:clear], value.to_s)
|
|
241
|
+
else
|
|
242
|
+
# Clear field by JavaScript assignment of the value property.
|
|
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)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def scroll_if_needed(&block)
|
|
252
|
+
block.call
|
|
253
|
+
rescue ::Selenium::WebDriver::Error::MoveTargetOutOfBoundsError
|
|
254
|
+
script = <<-JS
|
|
255
|
+
try {
|
|
256
|
+
arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
|
|
257
|
+
} catch(e) {
|
|
258
|
+
arguments[0].scrollIntoView(true);
|
|
259
|
+
}
|
|
260
|
+
JS
|
|
261
|
+
driver.execute_script(script, self)
|
|
262
|
+
block.call
|
|
165
263
|
end
|
|
166
264
|
end
|
data/lib/capybara/server.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'uri'
|
|
2
3
|
require 'net/http'
|
|
3
4
|
require 'rack'
|
|
@@ -5,21 +6,47 @@ require 'rack'
|
|
|
5
6
|
module Capybara
|
|
6
7
|
class Server
|
|
7
8
|
class Middleware
|
|
9
|
+
class Counter
|
|
10
|
+
attr_reader :value
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@value = 0
|
|
14
|
+
@mutex = Mutex.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def increment
|
|
18
|
+
@mutex.synchronize { @value += 1 }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def decrement
|
|
22
|
+
@mutex.synchronize { @value -= 1 }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
8
26
|
attr_accessor :error
|
|
9
27
|
|
|
10
|
-
def initialize(app)
|
|
28
|
+
def initialize(app, server_errors)
|
|
11
29
|
@app = app
|
|
30
|
+
@counter = Counter.new
|
|
31
|
+
@server_errors = server_errors
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def pending_requests?
|
|
35
|
+
@counter.value > 0
|
|
12
36
|
end
|
|
13
37
|
|
|
14
38
|
def call(env)
|
|
15
39
|
if env["PATH_INFO"] == "/__identify__"
|
|
16
40
|
[200, {}, [@app.object_id.to_s]]
|
|
17
41
|
else
|
|
42
|
+
@counter.increment
|
|
18
43
|
begin
|
|
19
44
|
@app.call(env)
|
|
20
|
-
rescue
|
|
45
|
+
rescue *@server_errors => e
|
|
21
46
|
@error = e unless @error
|
|
22
47
|
raise e
|
|
48
|
+
ensure
|
|
49
|
+
@counter.decrement
|
|
23
50
|
end
|
|
24
51
|
end
|
|
25
52
|
end
|
|
@@ -33,41 +60,46 @@ module Capybara
|
|
|
33
60
|
|
|
34
61
|
attr_reader :app, :port, :host
|
|
35
62
|
|
|
36
|
-
def initialize(app, port=Capybara.server_port, host=Capybara.server_host)
|
|
63
|
+
def initialize(app, port=Capybara.server_port, host=Capybara.server_host, server_errors=Capybara.server_errors)
|
|
37
64
|
@app = app
|
|
38
|
-
@middleware = Middleware.new(@app)
|
|
39
65
|
@server_thread = nil # suppress warnings
|
|
40
|
-
@host, @port = host, port
|
|
41
|
-
@port ||= Capybara::Server.ports[
|
|
42
|
-
@port ||= find_available_port
|
|
66
|
+
@host, @port, @server_errors = host, port, server_errors
|
|
67
|
+
@port ||= Capybara::Server.ports[port_key]
|
|
68
|
+
@port ||= find_available_port(host)
|
|
43
69
|
end
|
|
44
70
|
|
|
45
71
|
def reset_error!
|
|
46
|
-
|
|
72
|
+
middleware.error = nil
|
|
47
73
|
end
|
|
48
74
|
|
|
49
75
|
def error
|
|
50
|
-
|
|
76
|
+
middleware.error
|
|
51
77
|
end
|
|
52
78
|
|
|
53
79
|
def responsive?
|
|
54
80
|
return false if @server_thread && @server_thread.join(0)
|
|
55
81
|
|
|
56
|
-
res = Net::HTTP.start(host,
|
|
82
|
+
res = Net::HTTP.start(host, port) { |http| http.get('/__identify__') }
|
|
57
83
|
|
|
58
84
|
if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
|
|
59
|
-
return res.body ==
|
|
85
|
+
return res.body == app.object_id.to_s
|
|
60
86
|
end
|
|
61
87
|
rescue SystemCallError
|
|
62
88
|
return false
|
|
63
89
|
end
|
|
64
90
|
|
|
91
|
+
def wait_for_pending_requests
|
|
92
|
+
Timeout.timeout(60) { sleep(0.01) while pending_requests? }
|
|
93
|
+
rescue Timeout::Error
|
|
94
|
+
raise "Requests did not finish in 60 seconds"
|
|
95
|
+
end
|
|
96
|
+
|
|
65
97
|
def boot
|
|
66
98
|
unless responsive?
|
|
67
|
-
Capybara::Server.ports[
|
|
99
|
+
Capybara::Server.ports[port_key] = port
|
|
68
100
|
|
|
69
101
|
@server_thread = Thread.new do
|
|
70
|
-
Capybara.server.call(
|
|
102
|
+
Capybara.server.call(middleware, port, host)
|
|
71
103
|
end
|
|
72
104
|
|
|
73
105
|
Timeout.timeout(60) { @server_thread.join(0.1) until responsive? }
|
|
@@ -80,12 +112,23 @@ module Capybara
|
|
|
80
112
|
|
|
81
113
|
private
|
|
82
114
|
|
|
83
|
-
def
|
|
84
|
-
|
|
115
|
+
def middleware
|
|
116
|
+
@middleware ||= Middleware.new(app, @server_errors)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def port_key
|
|
120
|
+
Capybara.reuse_server ? app.object_id : middleware.object_id
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def pending_requests?
|
|
124
|
+
middleware.pending_requests?
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def find_available_port(host)
|
|
128
|
+
server = TCPServer.new(host, 0)
|
|
85
129
|
server.addr[1]
|
|
86
130
|
ensure
|
|
87
131
|
server.close if server
|
|
88
132
|
end
|
|
89
|
-
|
|
90
133
|
end
|
|
91
134
|
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'delegate'
|
|
3
|
+
|
|
4
|
+
module Capybara
|
|
5
|
+
class SessionConfig
|
|
6
|
+
OPTIONS = [:always_include_port, :run_server, :default_selector, :default_max_wait_time, :ignore_hidden_elements,
|
|
7
|
+
:automatic_reload, :match, :exact, :exact_text, :raise_server_errors, :visible_text_only, :wait_on_first_by_default,
|
|
8
|
+
:automatic_label_click, :enable_aria_label, :save_path, :exact_options, :asset_host, :default_host, :app_host,
|
|
9
|
+
:save_and_open_page_path, :server_host, :server_port, :server_errors]
|
|
10
|
+
|
|
11
|
+
attr_accessor(*OPTIONS)
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
#@!method always_include_port
|
|
15
|
+
# See {Capybara.configure}
|
|
16
|
+
#@!method run_server
|
|
17
|
+
# See {Capybara.configure}
|
|
18
|
+
#@!method default_selector
|
|
19
|
+
# See {Capybara.configure}
|
|
20
|
+
#@!method default_max_wait_time
|
|
21
|
+
# See {Capybara.configure}
|
|
22
|
+
#@!method ignore_hidden_elements
|
|
23
|
+
# See {Capybara.configure}
|
|
24
|
+
#@!method automatic_reload
|
|
25
|
+
# See {Capybara.configure}
|
|
26
|
+
#@!method match
|
|
27
|
+
# See {Capybara.configure}
|
|
28
|
+
#@!method exact
|
|
29
|
+
# See {Capybara.configure}
|
|
30
|
+
#@!method raise_server_errors
|
|
31
|
+
# See {Capybara.configure}
|
|
32
|
+
#@!method visible_text_only
|
|
33
|
+
# See {Capybara.configure}
|
|
34
|
+
#@!method wait_on_first_by_default
|
|
35
|
+
# See {Capybara.configure}
|
|
36
|
+
#@!method automatic_label_click
|
|
37
|
+
# See {Capybara.configure}
|
|
38
|
+
#@!method enable_aria_label
|
|
39
|
+
# See {Capybara.configure}
|
|
40
|
+
#@!method save_path
|
|
41
|
+
# See {Capybara.configure}
|
|
42
|
+
#@deprecated
|
|
43
|
+
#@!method exact_options
|
|
44
|
+
# See {Capybara.configure}
|
|
45
|
+
#@!method asset_host
|
|
46
|
+
# See {Capybara.configure}
|
|
47
|
+
#@!method default_host
|
|
48
|
+
# See {Capybara.configure}
|
|
49
|
+
#@!method app_host
|
|
50
|
+
# See {Capybara.configure}
|
|
51
|
+
#@!method save_and_open_page_path
|
|
52
|
+
# See {Capybara.configure}
|
|
53
|
+
#@!method server_host
|
|
54
|
+
# See {Capybara.configure}
|
|
55
|
+
#@!method server_port
|
|
56
|
+
# See {Capybara.configure}
|
|
57
|
+
#@!method server_errors
|
|
58
|
+
# See {Capybara.configure}
|
|
59
|
+
|
|
60
|
+
remove_method :server_host
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
#
|
|
64
|
+
# @return [String] The IP address bound by default server
|
|
65
|
+
#
|
|
66
|
+
def server_host
|
|
67
|
+
@server_host || '127.0.0.1'
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
remove_method :server_errors=
|
|
71
|
+
def server_errors=(errors)
|
|
72
|
+
(@server_errors ||= []).replace(errors.dup)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
remove_method :app_host=
|
|
76
|
+
def app_host=(url)
|
|
77
|
+
raise ArgumentError.new("Capybara.app_host should be set to a url (http://www.example.com)") unless url.nil? || (url =~ URI::Parser.new.make_regexp)
|
|
78
|
+
@app_host = url
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
remove_method :default_host=
|
|
82
|
+
def default_host=(url)
|
|
83
|
+
raise ArgumentError.new("Capybara.default_host should be set to a url (http://www.example.com)") unless url.nil? || (url =~ URI::Parser.new.make_regexp)
|
|
84
|
+
@default_host = url
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
remove_method :save_and_open_page_path=
|
|
88
|
+
def save_and_open_page_path=(path)
|
|
89
|
+
warn "DEPRECATED: #save_and_open_page_path is deprecated, please use #save_path instead. \n"\
|
|
90
|
+
"Note: Behavior is slightly different with relative paths - see documentation" unless path.nil?
|
|
91
|
+
@save_and_open_page_path = path
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
remove_method :exact_options=
|
|
95
|
+
def exact_options=(opt)
|
|
96
|
+
@exact_options = opt
|
|
97
|
+
warn "DEPRECATED: #exact_options is deprecated, please scope your findes/actions and use the `:exact` "\
|
|
98
|
+
"option if similar functionality is needed."
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def initialize_copy(other)
|
|
102
|
+
super
|
|
103
|
+
@server_errors = @server_errors.dup
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
class ReadOnlySessionConfig < SimpleDelegator
|
|
108
|
+
SessionConfig::OPTIONS.each do |m|
|
|
109
|
+
define_method "#{m}=" do |val|
|
|
110
|
+
raise "Per session settings are only supported when Capybara.threadsafe == true"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -1,49 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module Capybara
|
|
2
3
|
module SessionMatchers
|
|
3
4
|
##
|
|
4
5
|
# Asserts that the page has the given path.
|
|
5
|
-
# By default this will compare against the
|
|
6
|
+
# By default, if passed a full url this will compare against the full url,
|
|
7
|
+
# if passed a path only the path+query portion will be compared, if passed a regexp
|
|
8
|
+
# the comparison will depend on the :url option
|
|
6
9
|
#
|
|
7
10
|
# @!macro current_path_query_params
|
|
8
11
|
# @overload $0(string, options = {})
|
|
9
12
|
# @param string [String] The string that the current 'path' should equal
|
|
10
13
|
# @overload $0(regexp, options = {})
|
|
11
14
|
# @param regexp [Regexp] The regexp that the current 'path' should match to
|
|
12
|
-
# @option options [
|
|
13
|
-
# @option options [Boolean] :
|
|
14
|
-
# @option options [
|
|
15
|
+
# @option options [Boolean] :url (true if `string` ia a full url, otherwise false) Whether the compare should be done against the full current url or just the path
|
|
16
|
+
# @option options [Boolean] :ignore_query (false) Whether the query portion of the current url/path should be ignored
|
|
17
|
+
# @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
|
|
15
18
|
# @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
|
|
16
19
|
# @return [true]
|
|
17
20
|
#
|
|
18
21
|
def assert_current_path(path, options={})
|
|
19
|
-
query
|
|
20
|
-
document.synchronize(query.wait) do
|
|
21
|
-
unless query.resolves_for?(self)
|
|
22
|
-
raise Capybara::ExpectationNotMet, query.failure_message
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
return true
|
|
22
|
+
_verify_current_path(path,options) { |query| raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self) }
|
|
26
23
|
end
|
|
27
24
|
|
|
28
25
|
##
|
|
29
26
|
# Asserts that the page doesn't have the given path.
|
|
27
|
+
# By default, if passed a full url this will compare against the full url,
|
|
28
|
+
# if passed a path only the path+query portion will be compared, if passed a regexp
|
|
29
|
+
# the comparison will depend on the :url option
|
|
30
30
|
#
|
|
31
31
|
# @macro current_path_query_params
|
|
32
32
|
# @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
|
|
33
33
|
# @return [true]
|
|
34
34
|
#
|
|
35
35
|
def assert_no_current_path(path, options={})
|
|
36
|
-
query
|
|
37
|
-
document.synchronize(query.wait) do
|
|
38
|
-
if query.resolves_for?(self)
|
|
39
|
-
raise Capybara::ExpectationNotMet, query.negative_failure_message
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
return true
|
|
36
|
+
_verify_current_path(path,options) { |query| raise Capybara::ExpectationNotMet, query.negative_failure_message if query.resolves_for?(self) }
|
|
43
37
|
end
|
|
44
38
|
|
|
45
39
|
##
|
|
46
40
|
# Checks if the page has the given path.
|
|
41
|
+
# By default, if passed a full url this will compare against the full url,
|
|
42
|
+
# if passed a path only the path+query portion will be compared, if passed a regexp
|
|
43
|
+
# the comparison will depend on the :url option
|
|
47
44
|
#
|
|
48
45
|
# @macro current_path_query_params
|
|
49
46
|
# @return [Boolean]
|
|
@@ -56,6 +53,9 @@ module Capybara
|
|
|
56
53
|
|
|
57
54
|
##
|
|
58
55
|
# Checks if the page doesn't have the given path.
|
|
56
|
+
# By default, if passed a full url this will compare against the full url,
|
|
57
|
+
# if passed a path only the path+query portion will be compared, if passed a regexp
|
|
58
|
+
# the comparison will depend on the :url option
|
|
59
59
|
#
|
|
60
60
|
# @macro current_path_query_params
|
|
61
61
|
# @return [Boolean]
|
|
@@ -65,5 +65,15 @@ module Capybara
|
|
|
65
65
|
rescue Capybara::ExpectationNotMet
|
|
66
66
|
return false
|
|
67
67
|
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def _verify_current_path(path, options)
|
|
72
|
+
query = Capybara::Queries::CurrentPathQuery.new(path, options)
|
|
73
|
+
document.synchronize(query.wait) do
|
|
74
|
+
yield(query)
|
|
75
|
+
end
|
|
76
|
+
return true
|
|
77
|
+
end
|
|
68
78
|
end
|
|
69
|
-
end
|
|
79
|
+
end
|