capybara 3.3.1 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +16 -0
- data/README.md +5 -7
- data/lib/capybara.rb +7 -6
- data/lib/capybara/config.rb +1 -1
- data/lib/capybara/dsl.rb +2 -2
- data/lib/capybara/helpers.rb +3 -3
- data/lib/capybara/minitest/spec.rb +3 -3
- data/lib/capybara/node/actions.rb +18 -18
- data/lib/capybara/node/base.rb +1 -1
- data/lib/capybara/node/element.rb +2 -2
- data/lib/capybara/node/finders.rb +6 -6
- data/lib/capybara/node/matchers.rb +5 -5
- data/lib/capybara/node/simple.rb +2 -2
- data/lib/capybara/queries/ancestor_query.rb +1 -1
- data/lib/capybara/queries/base_query.rb +12 -11
- data/lib/capybara/queries/current_path_query.rb +1 -1
- data/lib/capybara/queries/selector_query.rb +39 -15
- data/lib/capybara/queries/sibling_query.rb +1 -1
- data/lib/capybara/queries/text_query.rb +1 -1
- data/lib/capybara/rack_test/browser.rb +7 -7
- data/lib/capybara/rack_test/driver.rb +1 -1
- data/lib/capybara/rack_test/form.rb +7 -7
- data/lib/capybara/rack_test/node.rb +16 -16
- data/lib/capybara/rails.rb +1 -1
- data/lib/capybara/result.rb +8 -4
- data/lib/capybara/rspec/features.rb +4 -4
- data/lib/capybara/rspec/matchers.rb +6 -6
- data/lib/capybara/selector.rb +106 -90
- data/lib/capybara/selector/css.rb +4 -4
- data/lib/capybara/selector/filter_set.rb +52 -8
- data/lib/capybara/selector/selector.rb +39 -15
- data/lib/capybara/selenium/driver.rb +10 -10
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +8 -0
- data/lib/capybara/selenium/node.rb +9 -10
- data/lib/capybara/selenium/nodes/chrome_node.rb +18 -0
- data/lib/capybara/selenium/nodes/marionette_node.rb +32 -7
- data/lib/capybara/server.rb +3 -3
- data/lib/capybara/server/animation_disabler.rb +1 -1
- data/lib/capybara/server/middleware.rb +1 -1
- data/lib/capybara/session.rb +23 -19
- data/lib/capybara/session/config.rb +18 -3
- data/lib/capybara/spec/public/test.js +1 -1
- data/lib/capybara/spec/session/accept_alert_spec.rb +10 -10
- data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -3
- data/lib/capybara/spec/session/accept_prompt_spec.rb +9 -10
- data/lib/capybara/spec/session/all_spec.rb +33 -32
- data/lib/capybara/spec/session/ancestor_spec.rb +19 -19
- data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +38 -38
- data/lib/capybara/spec/session/assert_current_path_spec.rb +16 -16
- data/lib/capybara/spec/session/assert_selector_spec.rb +53 -53
- data/lib/capybara/spec/session/assert_style_spec.rb +3 -3
- data/lib/capybara/spec/session/assert_text_spec.rb +31 -30
- data/lib/capybara/spec/session/assert_title_spec.rb +12 -12
- data/lib/capybara/spec/session/attach_file_spec.rb +51 -52
- data/lib/capybara/spec/session/body_spec.rb +6 -6
- data/lib/capybara/spec/session/check_spec.rb +52 -47
- data/lib/capybara/spec/session/choose_spec.rb +32 -32
- data/lib/capybara/spec/session/click_button_spec.rb +103 -103
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +24 -23
- data/lib/capybara/spec/session/click_link_spec.rb +49 -48
- data/lib/capybara/spec/session/current_scope_spec.rb +7 -7
- data/lib/capybara/spec/session/current_url_spec.rb +26 -27
- data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
- data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -2
- data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +8 -8
- data/lib/capybara/spec/session/element/match_css_spec.rb +10 -10
- data/lib/capybara/spec/session/element/match_xpath_spec.rb +6 -6
- data/lib/capybara/spec/session/element/matches_selector_spec.rb +51 -51
- data/lib/capybara/spec/session/evaluate_async_script_spec.rb +7 -7
- data/lib/capybara/spec/session/evaluate_script_spec.rb +15 -8
- data/lib/capybara/spec/session/execute_script_spec.rb +7 -7
- data/lib/capybara/spec/session/fill_in_spec.rb +43 -42
- data/lib/capybara/spec/session/find_button_spec.rb +23 -23
- data/lib/capybara/spec/session/find_by_id_spec.rb +7 -7
- data/lib/capybara/spec/session/find_field_spec.rb +32 -30
- data/lib/capybara/spec/session/find_link_spec.rb +21 -21
- data/lib/capybara/spec/session/find_spec.rb +153 -135
- data/lib/capybara/spec/session/first_spec.rb +41 -41
- data/lib/capybara/spec/session/frame/frame_title_spec.rb +5 -5
- data/lib/capybara/spec/session/frame/frame_url_spec.rb +5 -5
- data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +17 -17
- data/lib/capybara/spec/session/frame/within_frame_spec.rb +31 -17
- data/lib/capybara/spec/session/go_back_spec.rb +1 -1
- data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
- data/lib/capybara/spec/session/has_all_selectors_spec.rb +17 -17
- data/lib/capybara/spec/session/has_button_spec.rb +13 -13
- data/lib/capybara/spec/session/has_css_spec.rb +133 -131
- data/lib/capybara/spec/session/has_current_path_spec.rb +29 -29
- data/lib/capybara/spec/session/has_field_spec.rb +58 -58
- data/lib/capybara/spec/session/has_link_spec.rb +4 -4
- data/lib/capybara/spec/session/has_none_selectors_spec.rb +24 -24
- data/lib/capybara/spec/session/has_select_spec.rb +43 -43
- data/lib/capybara/spec/session/has_selector_spec.rb +71 -71
- data/lib/capybara/spec/session/has_style_spec.rb +3 -3
- data/lib/capybara/spec/session/has_table_spec.rb +4 -4
- data/lib/capybara/spec/session/has_text_spec.rb +53 -52
- data/lib/capybara/spec/session/has_title_spec.rb +14 -14
- data/lib/capybara/spec/session/has_xpath_spec.rb +39 -38
- data/lib/capybara/spec/session/headers_spec.rb +1 -1
- data/lib/capybara/spec/session/html_spec.rb +6 -6
- data/lib/capybara/spec/session/node_spec.rb +129 -123
- data/lib/capybara/spec/session/node_wrapper_spec.rb +10 -7
- data/lib/capybara/spec/session/refresh_spec.rb +4 -7
- data/lib/capybara/spec/session/reset_session_spec.rb +28 -28
- data/lib/capybara/spec/session/response_code_spec.rb +1 -1
- data/lib/capybara/spec/session/save_and_open_page_spec.rb +2 -2
- data/lib/capybara/spec/session/save_page_spec.rb +37 -37
- data/lib/capybara/spec/session/save_screenshot_spec.rb +6 -6
- data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
- data/lib/capybara/spec/session/select_spec.rb +81 -81
- data/lib/capybara/spec/session/selectors_spec.rb +17 -17
- data/lib/capybara/spec/session/sibling_spec.rb +9 -9
- data/lib/capybara/spec/session/text_spec.rb +23 -23
- data/lib/capybara/spec/session/title_spec.rb +5 -5
- data/lib/capybara/spec/session/uncheck_spec.rb +24 -20
- data/lib/capybara/spec/session/unselect_spec.rb +37 -37
- data/lib/capybara/spec/session/visit_spec.rb +48 -49
- data/lib/capybara/spec/session/window/current_window_spec.rb +1 -1
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +16 -16
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +2 -2
- data/lib/capybara/spec/session/window/window_spec.rb +4 -4
- data/lib/capybara/spec/session/window/within_window_spec.rb +14 -14
- data/lib/capybara/spec/session/within_spec.rb +41 -41
- data/lib/capybara/spec/spec_helper.rb +11 -9
- data/lib/capybara/spec/test_app.rb +18 -17
- data/lib/capybara/spec/views/form.erb +29 -31
- data/lib/capybara/spec/views/with_html.erb +2 -2
- data/lib/capybara/version.rb +1 -1
- data/spec/basic_node_spec.rb +23 -23
- data/spec/capybara_spec.rb +20 -20
- data/spec/css_splitter_spec.rb +7 -7
- data/spec/dsl_spec.rb +37 -32
- data/spec/filter_set_spec.rb +4 -4
- data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
- data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
- data/spec/minitest_spec.rb +4 -4
- data/spec/minitest_spec_spec.rb +23 -23
- data/spec/per_session_config_spec.rb +5 -5
- data/spec/rack_test_spec.rb +44 -44
- data/spec/result_spec.rb +14 -14
- data/spec/rspec/features_spec.rb +13 -13
- data/spec/rspec/scenarios_spec.rb +4 -4
- data/spec/rspec/shared_spec_matchers.rb +282 -281
- data/spec/rspec/views_spec.rb +3 -3
- data/spec/rspec_matchers_spec.rb +10 -10
- data/spec/rspec_spec.rb +29 -29
- data/spec/selector_spec.rb +64 -64
- data/spec/selenium_spec_chrome.rb +14 -22
- data/spec/selenium_spec_chrome_remote.rb +28 -8
- data/spec/selenium_spec_edge.rb +9 -4
- data/spec/selenium_spec_firefox_remote.rb +87 -0
- data/spec/selenium_spec_ie.rb +9 -4
- data/spec/selenium_spec_marionette.rb +42 -18
- data/spec/server_spec.rb +29 -27
- data/spec/session_spec.rb +17 -17
- data/spec/shared_selenium_session.rb +70 -52
- data/spec/spec_helper.rb +1 -1
- metadata +4 -2
@@ -19,7 +19,7 @@ module Capybara
|
|
19
19
|
# * Locator: The id of the element to match
|
20
20
|
#
|
21
21
|
# * **:field** - Select field elements (input [not of type submit, image, or hidden], textarea, select)
|
22
|
-
# * Locator: Matches against the id, name, or placeholder
|
22
|
+
# * Locator: Matches against the id, Capybara.test_id attribute, name, or placeholder
|
23
23
|
# * Filters:
|
24
24
|
# * :id (String) — Matches the id attribute
|
25
25
|
# * :name (String) — Matches the name attribute
|
@@ -50,7 +50,7 @@ module Capybara
|
|
50
50
|
# * :href (String, Regexp, nil) — Matches the normalized href of the link, if nil will find <a> elements with no href attribute
|
51
51
|
#
|
52
52
|
# * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
|
53
|
-
# * Locator: Matches the id, value, or title attributes, string content of a button, or the alt attribute of an image type button or of a descendant image of a button
|
53
|
+
# * Locator: Matches the id, Capybara.test_id attribute, value, or title attributes, string content of a button, or the alt attribute of an image type button or of a descendant image of a button
|
54
54
|
# * Filters:
|
55
55
|
# * :id (String) — Matches the id attribute
|
56
56
|
# * :title (String) — Matches the title attribute
|
@@ -62,7 +62,7 @@ module Capybara
|
|
62
62
|
# * Locator: See :link and :button selectors
|
63
63
|
#
|
64
64
|
# * **:fillable_field** - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
|
65
|
-
# * Locator: Matches against the id, name, or placeholder
|
65
|
+
# * Locator: Matches against the id, Capybara.test_id attribute, name, or placeholder
|
66
66
|
# * Filters:
|
67
67
|
# * :id (String) — Matches the id attribute
|
68
68
|
# * :name (String) — Matches the name attribute
|
@@ -74,7 +74,7 @@ module Capybara
|
|
74
74
|
# * :multiple (Boolean) — Match fields that accept multiple values
|
75
75
|
#
|
76
76
|
# * **:radio_button** - Find radio buttons
|
77
|
-
# * Locator: Match id, name, or associated label text
|
77
|
+
# * Locator: Match id, Capybara.test_id attribute, name, or associated label text
|
78
78
|
# * Filters:
|
79
79
|
# * :id (String) — Matches the id attribute
|
80
80
|
# * :name (String) — Matches the name attribute
|
@@ -85,7 +85,7 @@ module Capybara
|
|
85
85
|
# * :option (String) — Match the value
|
86
86
|
#
|
87
87
|
# * **:checkbox** - Find checkboxes
|
88
|
-
# * Locator: Match id, name, or associated label text
|
88
|
+
# * Locator: Match id, Capybara.test_id attribute, name, or associated label text
|
89
89
|
# * Filters:
|
90
90
|
# * *:id (String) — Matches the id attribute
|
91
91
|
# * *:name (String) — Matches the name attribute
|
@@ -96,7 +96,7 @@ module Capybara
|
|
96
96
|
# * *:option (String) — Match the value
|
97
97
|
#
|
98
98
|
# * **:select** - Find select elements
|
99
|
-
# * Locator: Match id, name, placeholder, or associated label text
|
99
|
+
# * Locator: Match id, Capybara.test_id attribute, name, placeholder, or associated label text
|
100
100
|
# * Filters:
|
101
101
|
# * :id (String) — Matches the id attribute
|
102
102
|
# * :name (String) — Matches the name attribute
|
@@ -126,7 +126,7 @@ module Capybara
|
|
126
126
|
# * Locator:
|
127
127
|
#
|
128
128
|
# * **:file_field** - Find file input elements
|
129
|
-
# * Locator: Match id, name, or associated label text
|
129
|
+
# * Locator: Match id, Capybara.test_id attribute, name, or associated label text
|
130
130
|
# * Filters:
|
131
131
|
# * :id (String) — Matches the id attribute
|
132
132
|
# * :name (String) — Matches the name attribute
|
@@ -190,6 +190,10 @@ module Capybara
|
|
190
190
|
@expression = nil
|
191
191
|
@expression_filters = {}
|
192
192
|
@default_visibility = nil
|
193
|
+
@config = {
|
194
|
+
enable_aria_label: false,
|
195
|
+
test_id: nil
|
196
|
+
}
|
193
197
|
instance_eval(&block)
|
194
198
|
end
|
195
199
|
|
@@ -287,11 +291,12 @@ module Capybara
|
|
287
291
|
# @return [String] Description of the selector when used with the options passed
|
288
292
|
def_delegator :@filter_set, :description
|
289
293
|
|
290
|
-
def call(locator, **options)
|
294
|
+
def call(locator, selector_config: {}, **options)
|
295
|
+
@config.merge! selector_config
|
291
296
|
if format
|
292
297
|
@expression.call(locator, options)
|
293
298
|
else
|
294
|
-
warn
|
299
|
+
warn 'Selector has no format'
|
295
300
|
end
|
296
301
|
end
|
297
302
|
|
@@ -349,15 +354,25 @@ module Capybara
|
|
349
354
|
def_delegators :@filter_set, :node_filter, :expression_filter, :filter
|
350
355
|
|
351
356
|
def filter_set(name, filters_to_use = nil)
|
352
|
-
|
353
|
-
filter_selector = filters_to_use.nil? ? ->(*) { true } : ->(n, _) { filters_to_use.include? n }
|
354
|
-
@filter_set.expression_filters.merge!(f_set.expression_filters.select(&filter_selector))
|
355
|
-
@filter_set.node_filters.merge!(f_set.node_filters.select(&filter_selector))
|
356
|
-
f_set.descriptions.each { |desc| @filter_set.describe(&desc) }
|
357
|
+
@filter_set.import(name, filters_to_use)
|
357
358
|
end
|
358
359
|
|
359
360
|
def_delegator :@filter_set, :describe
|
360
361
|
|
362
|
+
def describe_expression_filters(&block)
|
363
|
+
if block_given?
|
364
|
+
describe(:expression_filters, &block)
|
365
|
+
else
|
366
|
+
describe(:expression_filters) do |**options|
|
367
|
+
describe_all_expression_filters(options)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def describe_node_filters(&block)
|
373
|
+
describe(:node_filters, &block)
|
374
|
+
end
|
375
|
+
|
361
376
|
##
|
362
377
|
#
|
363
378
|
# Set the default visibility mode that shouble be used if no visibile option is passed when using the selector.
|
@@ -378,7 +393,15 @@ module Capybara
|
|
378
393
|
|
379
394
|
private
|
380
395
|
|
381
|
-
def
|
396
|
+
def enable_aria_label
|
397
|
+
@config[:enable_aria_label]
|
398
|
+
end
|
399
|
+
|
400
|
+
def test_id
|
401
|
+
@config[:test_id]
|
402
|
+
end
|
403
|
+
|
404
|
+
def locate_field(xpath, locator, **_options)
|
382
405
|
return xpath if locator.nil?
|
383
406
|
locate_xpath = xpath # Need to save original xpath for the label wrap
|
384
407
|
locator = locator.to_s
|
@@ -387,6 +410,7 @@ module Capybara
|
|
387
410
|
XPath.attr(:placeholder) == locator,
|
388
411
|
XPath.attr(:id) == XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)].reduce(:|)
|
389
412
|
attr_matchers |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
|
413
|
+
attr_matchers |= XPath.attr(test_id) == locator if test_id
|
390
414
|
|
391
415
|
locate_xpath = locate_xpath[attr_matchers]
|
392
416
|
locate_xpath + XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
|
@@ -14,7 +14,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
14
14
|
|
15
15
|
def self.load_selenium
|
16
16
|
require 'selenium-webdriver'
|
17
|
-
warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade." if Gem.loaded_specs[
|
17
|
+
warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade." if Gem.loaded_specs['selenium-webdriver'].version < Gem::Version.new('3.5.0')
|
18
18
|
rescue LoadError => e
|
19
19
|
raise e if e.message !~ /selenium-webdriver/
|
20
20
|
raise LoadError, "Capybara's selenium driver is unable to load `selenium-webdriver`, please install the gem and add `gem 'selenium-webdriver'` to your Gemfile if you are using bundler."
|
@@ -99,7 +99,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
99
99
|
end
|
100
100
|
|
101
101
|
def evaluate_script(script, *args)
|
102
|
-
result = execute_script("return #{script}", *args)
|
102
|
+
result = execute_script("return #{script.strip}", *args)
|
103
103
|
unwrap_script_result(result)
|
104
104
|
end
|
105
105
|
|
@@ -135,12 +135,12 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
135
135
|
# to about:blank, so we rescue this error and do nothing
|
136
136
|
# instead.
|
137
137
|
end
|
138
|
-
@browser.navigate.to(
|
138
|
+
@browser.navigate.to('about:blank')
|
139
139
|
end
|
140
140
|
navigated = true
|
141
141
|
|
142
142
|
# Ensure the page is empty and trigger an UnhandledAlertError for any modals that appear during unload
|
143
|
-
until find_xpath(
|
143
|
+
until find_xpath('/html/body/*').empty?
|
144
144
|
raise Capybara::ExpectationNotMet, 'Timed out waiting for Selenium session reset' if timer.expired?
|
145
145
|
sleep 0.05
|
146
146
|
end
|
@@ -153,11 +153,11 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
153
153
|
sleep 0.25 # allow time for the modal to be handled
|
154
154
|
rescue modal_error
|
155
155
|
# The alert is now gone
|
156
|
-
if current_url !=
|
156
|
+
if current_url != 'about:blank'
|
157
157
|
begin
|
158
158
|
# If navigation has not occurred attempt again and accept alert
|
159
159
|
# since FF may have dismissed the alert at first attempt
|
160
|
-
@browser.navigate.to(
|
160
|
+
@browser.navigate.to('about:blank')
|
161
161
|
sleep 0.1 # slight wait for alert
|
162
162
|
@browser.switch_to.alert.accept
|
163
163
|
rescue modal_error # rubocop:disable Metrics/BlockNesting, Lint/HandleExceptions
|
@@ -219,7 +219,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
219
219
|
end
|
220
220
|
|
221
221
|
def close_window(handle)
|
222
|
-
raise ArgumentError,
|
222
|
+
raise ArgumentError, 'Not allowed to close the primary window' if handle == window_handles.first
|
223
223
|
within_given_window(handle) do
|
224
224
|
browser.close
|
225
225
|
end
|
@@ -328,15 +328,15 @@ private
|
|
328
328
|
if @browser.respond_to? :session_storage
|
329
329
|
@browser.session_storage.clear
|
330
330
|
else
|
331
|
-
warn
|
331
|
+
warn 'sessionStorage clear requested but is not available for this driver'
|
332
332
|
end
|
333
333
|
end
|
334
334
|
|
335
|
-
if options[:clear_local_storage]
|
335
|
+
if options[:clear_local_storage] # rubocop:disable Style/GuardClause
|
336
336
|
if @browser.respond_to? :local_storage
|
337
337
|
@browser.local_storage.clear
|
338
338
|
else
|
339
|
-
warn
|
339
|
+
warn 'localStorage clear requested but is not available for this driver'
|
340
340
|
end
|
341
341
|
end
|
342
342
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'capybara/selenium/nodes/chrome_node'
|
4
|
+
|
3
5
|
module Capybara::Selenium::Driver::ChromeDriver
|
4
6
|
def fullscreen_window(handle)
|
5
7
|
within_given_window(handle) do
|
@@ -32,4 +34,10 @@ module Capybara::Selenium::Driver::ChromeDriver
|
|
32
34
|
window_handles.slice(1..-1).each { |win| close_window(win) }
|
33
35
|
super
|
34
36
|
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def build_node(native_node)
|
41
|
+
::Capybara::Selenium::ChromeNode.new(self, native_node)
|
42
|
+
end
|
35
43
|
end
|
@@ -6,11 +6,11 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def all_text
|
9
|
-
text = driver.execute_script(
|
9
|
+
text = driver.execute_script('return arguments[0].textContent', self)
|
10
10
|
text.gsub(/[\u200b\u200e\u200f]/, '')
|
11
11
|
.gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
|
12
|
-
.gsub(/\A[[:space:]&&[^\u00a0]]+/,
|
13
|
-
.gsub(/[[:space:]&&[^\u00a0]]+\z/,
|
12
|
+
.gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
|
13
|
+
.gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
|
14
14
|
.tr("\u00a0", ' ')
|
15
15
|
end
|
16
16
|
|
@@ -21,8 +21,8 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def value
|
24
|
-
if tag_name ==
|
25
|
-
native.find_elements(:css,
|
24
|
+
if tag_name == 'select' && multiple?
|
25
|
+
native.find_elements(:css, 'option:checked').map { |n| n[:value] || n.text }
|
26
26
|
else
|
27
27
|
native[:value]
|
28
28
|
end
|
@@ -78,7 +78,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def unselect_option
|
81
|
-
raise Capybara::UnselectNotAllowed,
|
81
|
+
raise Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.' unless select_node.multiple?
|
82
82
|
native.click if selected?
|
83
83
|
end
|
84
84
|
|
@@ -142,8 +142,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
142
142
|
def disabled?
|
143
143
|
return true unless native.enabled?
|
144
144
|
# WebDriver only defines `disabled?` for form controls but fieldset makes sense too
|
145
|
-
|
146
|
-
false
|
145
|
+
tag_name == 'fieldset' && find_xpath('ancestor-or-self::fieldset[@disabled]').any?
|
147
146
|
end
|
148
147
|
|
149
148
|
def content_editable?
|
@@ -192,7 +191,7 @@ private
|
|
192
191
|
end
|
193
192
|
|
194
193
|
def boolean_attr(val)
|
195
|
-
val && (val !=
|
194
|
+
val && (val != 'false')
|
196
195
|
end
|
197
196
|
|
198
197
|
# a reference to the select node if this is an option node
|
@@ -206,7 +205,7 @@ private
|
|
206
205
|
elsif clear == :backspace
|
207
206
|
# Clear field by sending the correct number of backspace keys.
|
208
207
|
backspaces = [:backspace] * self.value.to_s.length
|
209
|
-
native.send_keys(*(backspaces + [value.to_s]))
|
208
|
+
native.send_keys(*([:end] + backspaces + [value.to_s]))
|
210
209
|
elsif clear == :none
|
211
210
|
native.send_keys(value.to_s)
|
212
211
|
elsif clear.is_a? Array
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
|
4
|
+
def set_file(value) # rubocop:disable Naming/AccessorMethodName
|
5
|
+
super(value)
|
6
|
+
rescue ::Selenium::WebDriver::Error::ExpectedError => e
|
7
|
+
if e.message =~ /File not found : .+\n.+/m
|
8
|
+
raise ArgumentError, "Selenium with remote Chrome doesn't currently support multiple file upload"
|
9
|
+
end
|
10
|
+
raise
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def bridge
|
16
|
+
driver.browser.send(:bridge)
|
17
|
+
end
|
18
|
+
end
|
@@ -4,28 +4,53 @@ class Capybara::Selenium::MarionetteNode < Capybara::Selenium::Node
|
|
4
4
|
def click(keys = [], **options)
|
5
5
|
super
|
6
6
|
rescue ::Selenium::WebDriver::Error::ElementNotInteractableError
|
7
|
-
if tag_name ==
|
8
|
-
warn
|
9
|
-
|
7
|
+
if tag_name == 'tr'
|
8
|
+
warn 'You are attempting to click a table row which has issues in geckodriver/marionette - see https://github.com/mozilla/geckodriver/issues/1228. ' \
|
9
|
+
'Your test should probably be clicking on a table cell like a user would. Clicking the first cell in the row instead.'
|
10
10
|
return find_css('th:first-child,td:first-child')[0].click
|
11
11
|
end
|
12
12
|
raise
|
13
13
|
end
|
14
14
|
|
15
15
|
def disabled?
|
16
|
-
|
16
|
+
# Not sure exactly what version of FF fixed the below issue, but it is definitely fixed in 61+
|
17
|
+
return super unless driver.browser.capabilities[:browser_version].to_f < 61.0
|
17
18
|
|
19
|
+
return true if super
|
18
20
|
# workaround for selenium-webdriver/geckodriver reporting elements as enabled when they are nested in disabling elements
|
19
21
|
if %w[option optgroup].include? tag_name
|
20
|
-
find_xpath(
|
22
|
+
find_xpath('parent::*[self::optgroup or self::select]')[0].disabled?
|
21
23
|
else
|
22
|
-
!find_xpath(
|
24
|
+
!find_xpath('parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]').empty?
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
26
28
|
def set_file(value) # rubocop:disable Naming/AccessorMethodName
|
27
29
|
path_names = value.to_s.empty? ? [] : value
|
28
30
|
native.clear
|
29
|
-
Array(path_names).each
|
31
|
+
Array(path_names).each do |path|
|
32
|
+
unless driver.browser.respond_to?(:upload)
|
33
|
+
if (fd = bridge.file_detector)
|
34
|
+
local_file = fd.call([path])
|
35
|
+
path = upload(local_file) if local_file
|
36
|
+
end
|
37
|
+
end
|
38
|
+
native.send_keys(path)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def bridge
|
45
|
+
driver.browser.send(:bridge)
|
46
|
+
end
|
47
|
+
|
48
|
+
def upload(local_file)
|
49
|
+
unless File.file?(local_file)
|
50
|
+
raise Error::WebDriverError, "you may only upload files: #{local_file.inspect}"
|
51
|
+
end
|
52
|
+
|
53
|
+
result = bridge.http.call(:post, "session/#{bridge.session_id}/file", file: Selenium::WebDriver::Zipper.zip_file(local_file))
|
54
|
+
result['value']
|
30
55
|
end
|
31
56
|
end
|
data/lib/capybara/server.rb
CHANGED
@@ -18,7 +18,7 @@ module Capybara
|
|
18
18
|
attr_reader :app, :port, :host
|
19
19
|
|
20
20
|
def initialize(app, *deprecated_options, port: Capybara.server_port, host: Capybara.server_host, reportable_errors: Capybara.server_errors, extra_middleware: [])
|
21
|
-
warn
|
21
|
+
warn 'Positional arguments, other than the application, to Server#new are deprecated, please use keyword arguments' unless deprecated_options.empty?
|
22
22
|
@app = app
|
23
23
|
@extra_middleware = extra_middleware
|
24
24
|
@server_thread = nil # suppress warnings
|
@@ -56,7 +56,7 @@ module Capybara
|
|
56
56
|
def wait_for_pending_requests
|
57
57
|
timer = Capybara::Helpers.timer(expire_in: 60)
|
58
58
|
while pending_requests?
|
59
|
-
raise
|
59
|
+
raise 'Requests did not finish in 60 seconds' if timer.expired?
|
60
60
|
sleep 0.01
|
61
61
|
end
|
62
62
|
end
|
@@ -71,7 +71,7 @@ module Capybara
|
|
71
71
|
|
72
72
|
timer = Capybara::Helpers.timer(expire_in: 60)
|
73
73
|
until responsive?
|
74
|
-
raise
|
74
|
+
raise 'Rack application timed out during boot' if timer.expired?
|
75
75
|
@server_thread.join(0.1)
|
76
76
|
end
|
77
77
|
end
|
data/lib/capybara/session.rb
CHANGED
@@ -75,12 +75,12 @@ module Capybara
|
|
75
75
|
attr_accessor :synchronized
|
76
76
|
|
77
77
|
def initialize(mode, app = nil)
|
78
|
-
raise TypeError,
|
78
|
+
raise TypeError, 'The second parameter to Session::new should be a rack app if passed.' if app && !app.respond_to?(:call)
|
79
79
|
@@instance_created = true
|
80
80
|
@mode = mode
|
81
81
|
@app = app
|
82
82
|
if block_given?
|
83
|
-
raise
|
83
|
+
raise 'A configuration block is only accepted when Capybara.threadsafe == true' unless Capybara.threadsafe
|
84
84
|
yield config
|
85
85
|
end
|
86
86
|
@server = if config.run_server && @app && driver.needs_server?
|
@@ -141,7 +141,7 @@ module Capybara
|
|
141
141
|
# Force an explanation for the error being raised as the exception cause
|
142
142
|
begin
|
143
143
|
if config.raise_server_errors
|
144
|
-
raise CapybaraError,
|
144
|
+
raise CapybaraError, 'Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true'
|
145
145
|
end
|
146
146
|
rescue CapybaraError
|
147
147
|
# needed to get the cause set correctly in JRuby -- otherwise we could just do raise @server.error
|
@@ -190,7 +190,7 @@ module Capybara
|
|
190
190
|
uri = ::Addressable::URI.parse(current_url)
|
191
191
|
|
192
192
|
# Addressable doesn't support opaque URIs - we want nil here
|
193
|
-
return nil if uri&.scheme ==
|
193
|
+
return nil if uri&.scheme == 'about'
|
194
194
|
|
195
195
|
path = uri&.path
|
196
196
|
path unless path&.empty?
|
@@ -383,7 +383,7 @@ module Capybara
|
|
383
383
|
when :parent
|
384
384
|
if scopes.last != :frame
|
385
385
|
raise Capybara::ScopeError, "`switch_to_frame(:parent)` cannot be called from inside a descendant frame's "\
|
386
|
-
|
386
|
+
'`within` block.'
|
387
387
|
end
|
388
388
|
scopes.pop
|
389
389
|
driver.switch_to_frame(:parent)
|
@@ -392,11 +392,13 @@ module Capybara
|
|
392
392
|
if idx
|
393
393
|
if scopes.slice(idx..-1).any? { |scope| ![:frame, nil].include?(scope) }
|
394
394
|
raise Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's "\
|
395
|
-
|
395
|
+
'`within` block.'
|
396
396
|
end
|
397
397
|
scopes.slice!(idx..-1)
|
398
398
|
driver.switch_to_frame(:top)
|
399
399
|
end
|
400
|
+
else
|
401
|
+
raise ArgumentError, 'You must provide a frame element, :parent, or :top when calling switch_to_frame'
|
400
402
|
end
|
401
403
|
end
|
402
404
|
|
@@ -408,7 +410,7 @@ module Capybara
|
|
408
410
|
# @overload within_frame(element)
|
409
411
|
# @param [Capybara::Node::Element] frame element
|
410
412
|
# @overload within_frame([kind = :frame], locator, **options)
|
411
|
-
# @param [Symbol] kind Optional selector type (:
|
413
|
+
# @param [Symbol] kind Optional selector type (:frame, :css, :xpath, etc.) - Defaults to :frame
|
412
414
|
# @param [String] locator The locator for the given selector kind. For :frame this is the name/id of a frame/iframe element
|
413
415
|
# @overload within_frame(index)
|
414
416
|
# @param [Integer] index index of a frame (0 based)
|
@@ -471,11 +473,11 @@ module Capybara
|
|
471
473
|
# @raise [ArgumentError] if both or neither arguments were provided
|
472
474
|
#
|
473
475
|
def switch_to_window(window = nil, **options, &window_locator)
|
474
|
-
raise ArgumentError,
|
475
|
-
raise ArgumentError,
|
476
|
+
raise ArgumentError, '`switch_to_window` can take either a block or a window, not both' if window && block_given?
|
477
|
+
raise ArgumentError, '`switch_to_window`: either window or block should be provided' if !window && !block_given?
|
476
478
|
unless scopes.last.nil?
|
477
|
-
raise Capybara::ScopeError,
|
478
|
-
|
479
|
+
raise Capybara::ScopeError, '`switch_to_window` is not supposed to be invoked from '\
|
480
|
+
'`within` or `within_frame` blocks.'
|
479
481
|
end
|
480
482
|
|
481
483
|
_switch_to_window(window, options, &window_locator)
|
@@ -512,7 +514,7 @@ module Capybara
|
|
512
514
|
when Proc
|
513
515
|
_switch_to_window { window_or_proc.call }
|
514
516
|
else
|
515
|
-
raise ArgumentError(
|
517
|
+
raise ArgumentError('`#within_window` requires a `Capybara::Window` instance or a lambda')
|
516
518
|
end
|
517
519
|
|
518
520
|
begin
|
@@ -546,7 +548,7 @@ module Capybara
|
|
546
548
|
document.synchronize(wait_time, errors: [Capybara::WindowError]) do
|
547
549
|
opened_handles = (driver.window_handles - old_handles)
|
548
550
|
if opened_handles.size != 1
|
549
|
-
raise Capybara::WindowError,
|
551
|
+
raise Capybara::WindowError, 'block passed to #window_opened_by '\
|
550
552
|
"opened #{opened_handles.size} windows instead of 1"
|
551
553
|
end
|
552
554
|
Window.new(self, opened_handles.first)
|
@@ -774,7 +776,7 @@ module Capybara
|
|
774
776
|
# if set at initialization time, so look at the configuration block that can be passed to the initializer too
|
775
777
|
#
|
776
778
|
def configure
|
777
|
-
raise
|
779
|
+
raise 'Session configuration is only supported when Capybara.threadsafe == true' unless Capybara.threadsafe
|
778
780
|
yield config
|
779
781
|
end
|
780
782
|
|
@@ -813,7 +815,7 @@ module Capybara
|
|
813
815
|
end
|
814
816
|
|
815
817
|
def open_file(path)
|
816
|
-
require
|
818
|
+
require 'launchy'
|
817
819
|
Launchy.open(path)
|
818
820
|
rescue LoadError
|
819
821
|
warn "File saved to #{path}.\nPlease install the launchy gem to open the file automatically."
|
@@ -824,7 +826,7 @@ module Capybara
|
|
824
826
|
end
|
825
827
|
|
826
828
|
def default_fn(extension)
|
827
|
-
timestamp = Time.new.strftime(
|
829
|
+
timestamp = Time.new.strftime('%Y%m%d%H%M%S')
|
828
830
|
"capybara-#{timestamp}#{rand(10**10)}.#{extension}"
|
829
831
|
end
|
830
832
|
|
@@ -846,6 +848,8 @@ module Capybara
|
|
846
848
|
end
|
847
849
|
|
848
850
|
def _find_frame(*args)
|
851
|
+
return find(:frame) if args.length.zero?
|
852
|
+
|
849
853
|
case args[0]
|
850
854
|
when Capybara::Node::Element
|
851
855
|
args[0]
|
@@ -862,8 +866,8 @@ module Capybara
|
|
862
866
|
end
|
863
867
|
|
864
868
|
def _switch_to_window(window = nil, **options)
|
865
|
-
raise Capybara::ScopeError,
|
866
|
-
raise Capybara::ScopeError,
|
869
|
+
raise Capybara::ScopeError, 'Window cannot be switched inside a `within_frame` block' if scopes.include?(:frame)
|
870
|
+
raise Capybara::ScopeError, 'Window cannot be switch inside a `within` block' unless scopes.last.nil?
|
867
871
|
|
868
872
|
if window
|
869
873
|
driver.switch_to_window(window.handle)
|
@@ -882,7 +886,7 @@ module Capybara
|
|
882
886
|
raise e
|
883
887
|
else
|
884
888
|
driver.switch_to_window(original_window_handle)
|
885
|
-
raise Capybara::WindowError,
|
889
|
+
raise Capybara::WindowError, 'Could not find a window matching block/lambda'
|
886
890
|
end
|
887
891
|
end
|
888
892
|
end
|