capybara 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/History.md +52 -15
- data/README.md +45 -38
- data/lib/capybara/cucumber.rb +1 -0
- data/lib/capybara/driver/base.rb +5 -2
- data/lib/capybara/driver/node.rb +1 -0
- data/lib/capybara/dsl.rb +1 -0
- data/lib/capybara/helpers.rb +3 -2
- data/lib/capybara/node/actions.rb +6 -0
- data/lib/capybara/node/base.rb +1 -0
- data/lib/capybara/node/document.rb +1 -0
- data/lib/capybara/node/document_matchers.rb +1 -0
- data/lib/capybara/node/element.rb +1 -0
- data/lib/capybara/node/finders.rb +3 -2
- data/lib/capybara/node/matchers.rb +72 -2
- data/lib/capybara/node/simple.rb +6 -3
- data/lib/capybara/queries/base_query.rb +1 -0
- data/lib/capybara/queries/current_path_query.rb +6 -3
- data/lib/capybara/queries/match_query.rb +21 -0
- data/lib/capybara/queries/selector_query.rb +138 -0
- data/lib/capybara/queries/text_query.rb +1 -0
- data/lib/capybara/queries/title_query.rb +1 -0
- data/lib/capybara/query.rb +3 -131
- data/lib/capybara/rack_test/browser.rb +1 -0
- data/lib/capybara/rack_test/css_handlers.rb +1 -0
- data/lib/capybara/rack_test/driver.rb +6 -4
- data/lib/capybara/rack_test/form.rb +1 -0
- data/lib/capybara/rack_test/node.rb +1 -0
- data/lib/capybara/rails.rb +2 -1
- data/lib/capybara/result.rb +1 -0
- data/lib/capybara/rspec/features.rb +1 -0
- data/lib/capybara/rspec/matchers.rb +54 -3
- data/lib/capybara/rspec.rb +1 -0
- data/lib/capybara/selector/filter.rb +48 -0
- data/lib/capybara/selector.rb +175 -75
- data/lib/capybara/selenium/driver.rb +24 -7
- data/lib/capybara/selenium/node.rb +1 -0
- data/lib/capybara/server.rb +38 -6
- data/lib/capybara/session/matchers.rb +2 -1
- data/lib/capybara/session.rb +30 -14
- data/lib/capybara/spec/public/test.js +6 -0
- data/lib/capybara/spec/session/accept_alert_spec.rb +2 -1
- data/lib/capybara/spec/session/accept_confirm_spec.rb +2 -1
- data/lib/capybara/spec/session/accept_prompt_spec.rb +2 -1
- data/lib/capybara/spec/session/all_spec.rb +1 -0
- data/lib/capybara/spec/session/assert_current_path.rb +1 -0
- data/lib/capybara/spec/session/assert_selector.rb +1 -0
- data/lib/capybara/spec/session/assert_text.rb +1 -0
- data/lib/capybara/spec/session/assert_title.rb +1 -0
- data/lib/capybara/spec/session/attach_file_spec.rb +1 -0
- data/lib/capybara/spec/session/body_spec.rb +1 -0
- data/lib/capybara/spec/session/check_spec.rb +1 -0
- data/lib/capybara/spec/session/choose_spec.rb +1 -0
- data/lib/capybara/spec/session/click_button_spec.rb +1 -0
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +1 -0
- data/lib/capybara/spec/session/click_link_spec.rb +1 -0
- data/lib/capybara/spec/session/current_scope_spec.rb +2 -1
- data/lib/capybara/spec/session/current_url_spec.rb +1 -0
- data/lib/capybara/spec/session/dismiss_confirm_spec.rb +2 -1
- data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -1
- data/lib/capybara/spec/session/element/assert_match_selector.rb +31 -0
- data/lib/capybara/spec/session/element/match_css_spec.rb +17 -0
- data/lib/capybara/spec/session/element/match_xpath_spec.rb +23 -0
- data/lib/capybara/spec/session/element/matches_selector_spec.rb +63 -0
- data/lib/capybara/spec/session/evaluate_script_spec.rb +1 -0
- data/lib/capybara/spec/session/execute_script_spec.rb +1 -0
- data/lib/capybara/spec/session/fill_in_spec.rb +1 -0
- data/lib/capybara/spec/session/find_button_spec.rb +1 -0
- data/lib/capybara/spec/session/find_by_id_spec.rb +1 -0
- data/lib/capybara/spec/session/find_field_spec.rb +1 -0
- data/lib/capybara/spec/session/find_link_spec.rb +1 -0
- data/lib/capybara/spec/session/find_spec.rb +1 -0
- data/lib/capybara/spec/session/first_spec.rb +1 -0
- data/lib/capybara/spec/session/go_back_spec.rb +1 -0
- data/lib/capybara/spec/session/go_forward_spec.rb +1 -0
- data/lib/capybara/spec/session/has_button_spec.rb +1 -0
- data/lib/capybara/spec/session/has_css_spec.rb +1 -0
- data/lib/capybara/spec/session/has_current_path_spec.rb +1 -0
- data/lib/capybara/spec/session/has_field_spec.rb +13 -0
- data/lib/capybara/spec/session/has_link_spec.rb +1 -0
- data/lib/capybara/spec/session/has_select_spec.rb +19 -0
- data/lib/capybara/spec/session/has_selector_spec.rb +6 -0
- data/lib/capybara/spec/session/has_table_spec.rb +1 -0
- data/lib/capybara/spec/session/has_text_spec.rb +1 -0
- data/lib/capybara/spec/session/has_title_spec.rb +1 -0
- data/lib/capybara/spec/session/has_xpath_spec.rb +1 -0
- data/lib/capybara/spec/session/headers.rb +1 -0
- data/lib/capybara/spec/session/html_spec.rb +1 -0
- data/lib/capybara/spec/session/node_spec.rb +29 -0
- data/lib/capybara/spec/session/reset_session_spec.rb +35 -1
- data/lib/capybara/spec/session/response_code.rb +1 -0
- data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +3 -2
- 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 +3 -1
- data/lib/capybara/spec/session/select_spec.rb +1 -0
- data/lib/capybara/spec/session/selectors_spec.rb +35 -0
- data/lib/capybara/spec/session/text_spec.rb +1 -0
- data/lib/capybara/spec/session/title_spec.rb +2 -1
- data/lib/capybara/spec/session/uncheck_spec.rb +1 -0
- data/lib/capybara/spec/session/unselect_spec.rb +1 -0
- data/lib/capybara/spec/session/visit_spec.rb +1 -0
- data/lib/capybara/spec/session/window/become_closed_spec.rb +1 -0
- 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 +1 -0
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -0
- data/lib/capybara/spec/session/window/window_spec.rb +1 -8
- data/lib/capybara/spec/session/window/windows_spec.rb +1 -0
- data/lib/capybara/spec/session/window/within_window_spec.rb +2 -3
- data/lib/capybara/spec/session/within_frame_spec.rb +1 -0
- data/lib/capybara/spec/session/within_spec.rb +1 -0
- data/lib/capybara/spec/spec_helper.rb +2 -0
- data/lib/capybara/spec/test_app.rb +1 -0
- 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 +24 -0
- data/lib/capybara/spec/views/frame_child.erb +2 -1
- data/lib/capybara/spec/views/frame_one.erb +2 -1
- data/lib/capybara/spec/views/frame_parent.erb +2 -1
- 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/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_hover.erb +2 -1
- data/lib/capybara/spec/views/with_html.erb +3 -0
- data/lib/capybara/spec/views/with_html_entities.erb +1 -0
- data/lib/capybara/spec/views/with_js.erb +5 -0
- 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 +12 -0
- data/lib/capybara/spec/views/with_windows.erb +1 -0
- data/lib/capybara/spec/views/within_frames.erb +2 -1
- data/lib/capybara/version.rb +2 -1
- data/lib/capybara/window.rb +1 -0
- data/lib/capybara.rb +86 -10
- data/spec/basic_node_spec.rb +1 -0
- data/spec/capybara_spec.rb +28 -1
- data/spec/dsl_spec.rb +1 -0
- data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -0
- data/spec/fixtures/selenium_driver_rspec_success.rb +1 -0
- data/spec/rack_test_spec.rb +1 -0
- data/spec/result_spec.rb +1 -0
- data/spec/rspec/features_spec.rb +1 -0
- data/spec/rspec/matchers_spec.rb +1 -0
- data/spec/rspec/scenarios_spec.rb +1 -0
- data/spec/rspec/views_spec.rb +1 -0
- data/spec/rspec_spec.rb +1 -0
- data/spec/selector_spec.rb +55 -1
- data/spec/selenium_spec.rb +6 -2
- data/spec/selenium_spec_chrome.rb +1 -0
- data/spec/server_spec.rb +101 -13
- data/spec/spec_helper.rb +1 -0
- metadata +16 -6
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Capybara
|
|
3
|
+
class Selector
|
|
4
|
+
class Filter
|
|
5
|
+
def initialize(name, block, options={})
|
|
6
|
+
@name = name
|
|
7
|
+
@block = block
|
|
8
|
+
@options = options
|
|
9
|
+
@options[:valid_values] = [true,false] if options[:boolean]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def default?
|
|
13
|
+
@options.has_key?(:default)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def default
|
|
17
|
+
@options[:default]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def matches?(node, value)
|
|
21
|
+
return true if skip?(value)
|
|
22
|
+
|
|
23
|
+
if !valid_value?(value)
|
|
24
|
+
msg = "Invalid value #{value.inspect} passed to filter #{@name} - "
|
|
25
|
+
if default?
|
|
26
|
+
warn msg + "defaulting to #{default}"
|
|
27
|
+
value = default
|
|
28
|
+
else
|
|
29
|
+
warn msg + "skipping"
|
|
30
|
+
return true
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@block.call(node, value)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def skip?(value)
|
|
38
|
+
@options.has_key?(:skip_if) && value == @options[:skip_if]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def valid_value?(value)
|
|
44
|
+
!@options.has_key?(:valid_values) || Array(@options[:valid_values]).include?(value)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/capybara/selector.rb
CHANGED
|
@@ -1,42 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'capybara/selector/filter'
|
|
3
|
+
|
|
1
4
|
module Capybara
|
|
2
5
|
class Selector
|
|
3
|
-
class Filter
|
|
4
|
-
def initialize(name, block, options={})
|
|
5
|
-
@name = name
|
|
6
|
-
@block = block
|
|
7
|
-
@options = options
|
|
8
|
-
@options[:valid_values] = [true,false] if options[:boolean]
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def default?
|
|
12
|
-
@options.has_key?(:default)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def default
|
|
16
|
-
@options[:default]
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def matches?(node, value)
|
|
20
|
-
return true if skip?(value)
|
|
21
|
-
|
|
22
|
-
if @options.has_key?(:valid_values) && !Array(@options[:valid_values]).include?(value)
|
|
23
|
-
msg = "Invalid value #{value.inspect} passed to filter #{@name} - "
|
|
24
|
-
if default?
|
|
25
|
-
warn msg + "defaulting to #{default}"
|
|
26
|
-
value = default
|
|
27
|
-
else
|
|
28
|
-
warn msg + "skipping"
|
|
29
|
-
return true
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
@block.call(node, value)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def skip?(value)
|
|
37
|
-
@options.has_key?(:skip_if) && value == @options[:skip_if]
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
6
|
|
|
41
7
|
attr_reader :name, :custom_filters, :format
|
|
42
8
|
|
|
@@ -65,24 +31,19 @@ module Capybara
|
|
|
65
31
|
@label = nil
|
|
66
32
|
@failure_message = nil
|
|
67
33
|
@description = nil
|
|
34
|
+
@format = nil
|
|
35
|
+
@expression = nil
|
|
68
36
|
instance_eval(&block)
|
|
69
37
|
end
|
|
70
38
|
|
|
71
39
|
def xpath(&block)
|
|
72
|
-
if block
|
|
73
|
-
|
|
74
|
-
@xpath, @css = block, nil
|
|
75
|
-
end
|
|
76
|
-
@xpath
|
|
40
|
+
@format, @expression = :xpath, block if block
|
|
41
|
+
format == :xpath ? @expression : nil
|
|
77
42
|
end
|
|
78
43
|
|
|
79
|
-
# Same as xpath, but wrap in XPath.css().
|
|
80
44
|
def css(&block)
|
|
81
|
-
if block
|
|
82
|
-
|
|
83
|
-
@css, @xpath = block, nil
|
|
84
|
-
end
|
|
85
|
-
@css
|
|
45
|
+
@format, @expression = :css, block if block
|
|
46
|
+
format == :css ? @expression : nil
|
|
86
47
|
end
|
|
87
48
|
|
|
88
49
|
def match(&block)
|
|
@@ -100,10 +61,10 @@ module Capybara
|
|
|
100
61
|
end
|
|
101
62
|
|
|
102
63
|
def call(locator)
|
|
103
|
-
if
|
|
104
|
-
@
|
|
64
|
+
if format
|
|
65
|
+
@expression.call(locator)
|
|
105
66
|
else
|
|
106
|
-
|
|
67
|
+
warn "Selector has no format"
|
|
107
68
|
end
|
|
108
69
|
end
|
|
109
70
|
|
|
@@ -118,6 +79,17 @@ module Capybara
|
|
|
118
79
|
def describe &block
|
|
119
80
|
@description = block
|
|
120
81
|
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def locate_field(xpath, locator)
|
|
86
|
+
locate_field = xpath[XPath.attr(:id).equals(locator) |
|
|
87
|
+
XPath.attr(:name).equals(locator) |
|
|
88
|
+
XPath.attr(:placeholder).equals(locator) |
|
|
89
|
+
XPath.attr(:id).equals(XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for))]
|
|
90
|
+
locate_field += XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
|
|
91
|
+
locate_field
|
|
92
|
+
end
|
|
121
93
|
end
|
|
122
94
|
end
|
|
123
95
|
|
|
@@ -134,7 +106,11 @@ Capybara.add_selector(:id) do
|
|
|
134
106
|
end
|
|
135
107
|
|
|
136
108
|
Capybara.add_selector(:field) do
|
|
137
|
-
xpath
|
|
109
|
+
xpath do |locator|
|
|
110
|
+
xpath = XPath.descendant(:input, :textarea, :select)[~XPath.attr(:type).one_of('submit', 'image', 'hidden')]
|
|
111
|
+
xpath = locate_field(xpath, locator.to_s) unless locator.nil?
|
|
112
|
+
xpath
|
|
113
|
+
end
|
|
138
114
|
filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
|
|
139
115
|
filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
|
|
140
116
|
filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
|
|
@@ -147,31 +123,42 @@ Capybara.add_selector(:field) do
|
|
|
147
123
|
node[:type] == type
|
|
148
124
|
end
|
|
149
125
|
end
|
|
126
|
+
filter(:multiple, boolean: true) { |node, value| !(value ^ node[:multiple]) }
|
|
150
127
|
describe do |options|
|
|
151
|
-
desc, states =
|
|
128
|
+
desc, states = String.new, []
|
|
152
129
|
desc << " of type #{options[:type].inspect}" if options[:type]
|
|
153
130
|
desc << " with value #{options[:with].to_s.inspect}" if options.has_key?(:with)
|
|
154
131
|
states << 'checked' if options[:checked] || (options.has_key?(:unchecked) && !options[:unchecked])
|
|
155
132
|
states << 'not checked' if options[:unchecked] || (options.has_key?(:checked) && !options[:checked])
|
|
156
133
|
states << 'disabled' if options[:disabled] == true
|
|
157
134
|
desc << " that is #{states.join(' and ')}" unless states.empty?
|
|
135
|
+
desc << " with the multiple attribute" if options[:multiple] == true
|
|
136
|
+
desc << " without the multiple attribute" if options[:multiple] === false
|
|
158
137
|
desc
|
|
159
138
|
end
|
|
160
139
|
end
|
|
161
140
|
|
|
162
141
|
Capybara.add_selector(:fieldset) do
|
|
163
|
-
xpath
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
xpath { |locator| XPath::HTML.link_or_button(locator) }
|
|
169
|
-
filter(:disabled, default: false, boolean: true) { |node, value| node.tag_name == "a" or not(value ^ node.disabled?) }
|
|
170
|
-
describe { |options| " that is disabled" if options[:disabled] }
|
|
142
|
+
xpath do |locator|
|
|
143
|
+
xpath = XPath.descendant(:fieldset)
|
|
144
|
+
xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.child(:legend)[XPath.string.n.is(locator.to_s)]] unless locator.nil?
|
|
145
|
+
xpath
|
|
146
|
+
end
|
|
171
147
|
end
|
|
172
148
|
|
|
173
149
|
Capybara.add_selector(:link) do
|
|
174
|
-
xpath
|
|
150
|
+
xpath do |locator|
|
|
151
|
+
xpath = XPath.descendant(:a)[XPath.attr(:href)]
|
|
152
|
+
unless locator.nil?
|
|
153
|
+
locator = locator.to_s
|
|
154
|
+
xpath = xpath[XPath.attr(:id).equals(locator) |
|
|
155
|
+
XPath.string.n.is(locator) |
|
|
156
|
+
XPath.attr(:title).is(locator) |
|
|
157
|
+
XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
|
|
158
|
+
end
|
|
159
|
+
xpath
|
|
160
|
+
end
|
|
161
|
+
|
|
175
162
|
filter(:href) do |node, href|
|
|
176
163
|
if href.is_a? Regexp
|
|
177
164
|
node[:href].match href
|
|
@@ -179,31 +166,77 @@ Capybara.add_selector(:link) do
|
|
|
179
166
|
node.first(:xpath, XPath.axis(:self)[XPath.attr(:href).equals(href.to_s)], minimum: 0)
|
|
180
167
|
end
|
|
181
168
|
end
|
|
169
|
+
|
|
182
170
|
describe { |options| " with href #{options[:href].inspect}" if options[:href] }
|
|
183
171
|
end
|
|
184
172
|
|
|
185
173
|
Capybara.add_selector(:button) do
|
|
186
|
-
xpath
|
|
174
|
+
xpath do |locator|
|
|
175
|
+
input_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')]
|
|
176
|
+
btn_xpath = XPath.descendant(:button)
|
|
177
|
+
image_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).equals('image')]
|
|
178
|
+
|
|
179
|
+
unless locator.nil?
|
|
180
|
+
locator = locator.to_s
|
|
181
|
+
input_btn_xpath = input_btn_xpath[XPath.attr(:id).equals(locator) | XPath.attr(:value).is(locator) | XPath.attr(:title).is(locator)]
|
|
182
|
+
btn_xpath = btn_xpath[XPath.attr(:id).equals(locator) | XPath.attr(:value).is(locator) | XPath.string.n.is(locator) | XPath.attr(:title).is(locator)]
|
|
183
|
+
image_btn_xpath = image_btn_xpath[XPath.attr(:alt).is(locator)]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
input_btn_xpath + btn_xpath + image_btn_xpath
|
|
187
|
+
end
|
|
188
|
+
|
|
187
189
|
filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
|
|
190
|
+
|
|
188
191
|
describe { |options| " that is disabled" if options[:disabled] == true }
|
|
189
192
|
end
|
|
190
193
|
|
|
194
|
+
Capybara.add_selector(:link_or_button) do
|
|
195
|
+
label "link or button"
|
|
196
|
+
xpath do |locator|
|
|
197
|
+
self.class.all.values_at(:link, :button).map {|selector| selector.xpath.call(locator)}.reduce(:+)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
filter(:disabled, default: false, boolean: true) { |node, value| node.tag_name == "a" or not(value ^ node.disabled?) }
|
|
201
|
+
|
|
202
|
+
describe { |options| " that is disabled" if options[:disabled] }
|
|
203
|
+
end
|
|
204
|
+
|
|
191
205
|
Capybara.add_selector(:fillable_field) do
|
|
192
206
|
label "field"
|
|
193
|
-
xpath
|
|
207
|
+
xpath do |locator|
|
|
208
|
+
xpath = XPath.descendant(:input, :textarea)[~XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')]
|
|
209
|
+
xpath = locate_field(xpath, locator.to_s) unless locator.nil?
|
|
210
|
+
xpath
|
|
211
|
+
end
|
|
212
|
+
|
|
194
213
|
filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
|
|
195
|
-
|
|
214
|
+
filter(:multiple, boolean: true) { |node, value| !(value ^ node[:multiple]) }
|
|
215
|
+
|
|
216
|
+
describe do |options|
|
|
217
|
+
desc = String.new
|
|
218
|
+
desc << " that is disabled" if options[:disabled] == true
|
|
219
|
+
desc << " with the multiple attribute" if options[:multiple] == true
|
|
220
|
+
desc << " without the multiple attribute" if options[:multiple] === false
|
|
221
|
+
desc
|
|
222
|
+
end
|
|
196
223
|
end
|
|
197
224
|
|
|
198
225
|
Capybara.add_selector(:radio_button) do
|
|
199
226
|
label "radio button"
|
|
200
|
-
xpath
|
|
227
|
+
xpath do |locator|
|
|
228
|
+
xpath = XPath.descendant(:input)[XPath.attr(:type).equals('radio')]
|
|
229
|
+
xpath = locate_field(xpath, locator.to_s) unless locator.nil?
|
|
230
|
+
xpath
|
|
231
|
+
end
|
|
232
|
+
|
|
201
233
|
filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
|
|
202
234
|
filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
|
|
203
235
|
filter(:option) { |node, value| node.value == value.to_s }
|
|
204
236
|
filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
|
|
237
|
+
|
|
205
238
|
describe do |options|
|
|
206
|
-
desc, states =
|
|
239
|
+
desc, states = String.new, []
|
|
207
240
|
desc << " with value #{options[:option].inspect}" if options[:option]
|
|
208
241
|
states << 'checked' if options[:checked] || (options.has_key?(:unchecked) && !options[:unchecked])
|
|
209
242
|
states << 'not checked' if options[:unchecked] || (options.has_key?(:checked) && !options[:checked])
|
|
@@ -214,13 +247,19 @@ Capybara.add_selector(:radio_button) do
|
|
|
214
247
|
end
|
|
215
248
|
|
|
216
249
|
Capybara.add_selector(:checkbox) do
|
|
217
|
-
xpath
|
|
250
|
+
xpath do |locator|
|
|
251
|
+
xpath = XPath.descendant(:input)[XPath.attr(:type).equals('checkbox')]
|
|
252
|
+
xpath = locate_field(xpath, locator.to_s) unless locator.nil?
|
|
253
|
+
xpath
|
|
254
|
+
end
|
|
255
|
+
|
|
218
256
|
filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
|
|
219
257
|
filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
|
|
220
258
|
filter(:option) { |node, value| node.value == value.to_s }
|
|
221
259
|
filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
|
|
260
|
+
|
|
222
261
|
describe do |options|
|
|
223
|
-
desc, states =
|
|
262
|
+
desc, states = String.new, []
|
|
224
263
|
desc << " with value #{options[:option].inspect}" if options[:option]
|
|
225
264
|
states << 'checked' if options[:checked] || (options.has_key?(:unchecked) && !options[:unchecked])
|
|
226
265
|
states << 'not checked' if options[:unchecked] || (options.has_key?(:checked) && !options[:checked])
|
|
@@ -232,7 +271,12 @@ end
|
|
|
232
271
|
|
|
233
272
|
Capybara.add_selector(:select) do
|
|
234
273
|
label "select box"
|
|
235
|
-
xpath
|
|
274
|
+
xpath do |locator|
|
|
275
|
+
xpath = XPath.descendant(:select)
|
|
276
|
+
xpath = locate_field(xpath, locator.to_s) unless locator.nil?
|
|
277
|
+
xpath
|
|
278
|
+
end
|
|
279
|
+
|
|
236
280
|
filter(:options) do |node, options|
|
|
237
281
|
if node.visible?
|
|
238
282
|
actual = node.all(:xpath, './/option').map { |option| option.text }
|
|
@@ -253,27 +297,83 @@ Capybara.add_selector(:select) do
|
|
|
253
297
|
[selected].flatten.sort == actual.sort
|
|
254
298
|
end
|
|
255
299
|
filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
|
|
300
|
+
filter(:multiple, boolean: true) { |node, value| !(value ^ node[:multiple]) }
|
|
301
|
+
|
|
256
302
|
describe do |options|
|
|
257
|
-
desc =
|
|
303
|
+
desc = String.new
|
|
258
304
|
desc << " with options #{options[:options].inspect}" if options[:options]
|
|
259
305
|
desc << " with at least options #{options[:with_options].inspect}" if options[:with_options]
|
|
260
306
|
desc << " with #{options[:selected].inspect} selected" if options[:selected]
|
|
261
307
|
desc << " that is disabled" if options[:disabled] == true
|
|
308
|
+
desc << " that allows multiple selection" if options[:multiple] == true
|
|
309
|
+
desc << " that only allows single selection" if options[:multiple] === false
|
|
262
310
|
desc
|
|
263
311
|
end
|
|
264
312
|
end
|
|
265
313
|
|
|
266
314
|
Capybara.add_selector(:option) do
|
|
267
|
-
xpath
|
|
315
|
+
xpath do |locator|
|
|
316
|
+
xpath = XPath.descendant(:option)
|
|
317
|
+
xpath = xpath[XPath.string.n.is(locator.to_s)] unless locator.nil?
|
|
318
|
+
xpath
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
filter(:disabled, boolean: true) { |node, value| not(value ^ node.disabled?) }
|
|
322
|
+
filter(:selected, boolean: true) { |node, value| not(value ^ node.selected?) }
|
|
323
|
+
|
|
324
|
+
describe do |options|
|
|
325
|
+
desc = String.new
|
|
326
|
+
desc << " that is#{' not' unless options[:disabled]} disabled" if options.has_key?(:disabled)
|
|
327
|
+
desc << " that is#{' not' unless options[:selected]} selected" if options.has_key?(:selected)
|
|
328
|
+
desc
|
|
329
|
+
end
|
|
268
330
|
end
|
|
269
331
|
|
|
270
332
|
Capybara.add_selector(:file_field) do
|
|
271
333
|
label "file field"
|
|
272
|
-
xpath
|
|
334
|
+
xpath do |locator|
|
|
335
|
+
xpath = XPath.descendant(:input)[XPath.attr(:type).equals('file')]
|
|
336
|
+
xpath = locate_field(xpath, locator.to_s) unless locator.nil?
|
|
337
|
+
xpath
|
|
338
|
+
end
|
|
339
|
+
|
|
273
340
|
filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
|
|
274
|
-
|
|
341
|
+
filter(:multiple, boolean: true) { |node, value| !(value ^ node[:multiple]) }
|
|
342
|
+
|
|
343
|
+
describe do |options|
|
|
344
|
+
desc = String.new
|
|
345
|
+
desc << " that is disabled" if options[:disabled] == true
|
|
346
|
+
desc << " that allows multiple files" if options[:multiple] == true
|
|
347
|
+
desc << " that only allows a single file" if options[:multiple] === false
|
|
348
|
+
desc
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
Capybara.add_selector(:label) do
|
|
353
|
+
label "label"
|
|
354
|
+
xpath do |locator|
|
|
355
|
+
xpath = XPath.descendant(:label)
|
|
356
|
+
xpath = xpath[XPath.string.n.is(locator.to_s) | XPath.attr(:id).equals(locator.to_s)] unless locator.nil?
|
|
357
|
+
xpath
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
filter(:for) do |node, field_or_value|
|
|
361
|
+
if field_or_value.is_a? Capybara::Node::Element
|
|
362
|
+
if field_or_value[:id] && (field_or_value[:id] == node[:for])
|
|
363
|
+
true
|
|
364
|
+
else
|
|
365
|
+
field_or_value.find_xpath('./ancestor::label[1]').include? node.base
|
|
366
|
+
end
|
|
367
|
+
else
|
|
368
|
+
node[:for] == field_or_value.to_s
|
|
369
|
+
end
|
|
370
|
+
end
|
|
275
371
|
end
|
|
276
372
|
|
|
277
373
|
Capybara.add_selector(:table) do
|
|
278
|
-
xpath
|
|
374
|
+
xpath do |locator|
|
|
375
|
+
xpath = XPath.descendant(:table)
|
|
376
|
+
xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.descendant(:caption).is(locator.to_s)] unless locator.nil?
|
|
377
|
+
xpath
|
|
378
|
+
end
|
|
279
379
|
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require "uri"
|
|
2
3
|
|
|
3
4
|
class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
@@ -91,20 +92,35 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
|
91
92
|
def reset!
|
|
92
93
|
# Use instance variable directly so we avoid starting the browser just to reset the session
|
|
93
94
|
if @browser
|
|
95
|
+
navigated = false
|
|
96
|
+
start_time = Capybara::Helpers.monotonic_time
|
|
94
97
|
begin
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
#
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
if !navigated
|
|
99
|
+
# Only trigger a navigation if we haven't done it already, otherwise it
|
|
100
|
+
# can trigger an endless series of unload modals
|
|
101
|
+
begin
|
|
102
|
+
@browser.manage.delete_all_cookies
|
|
103
|
+
rescue Selenium::WebDriver::Error::UnhandledError
|
|
104
|
+
# delete_all_cookies fails when we've previously gone
|
|
105
|
+
# to about:blank, so we rescue this error and do nothing
|
|
106
|
+
# instead.
|
|
107
|
+
end
|
|
108
|
+
@browser.navigate.to("about:blank")
|
|
109
|
+
end
|
|
110
|
+
navigated = true
|
|
111
|
+
|
|
112
|
+
#Ensure the page is empty and trigger an UnhandledAlertError for any modals that appear during unload
|
|
113
|
+
until find_xpath("/html/body/*").empty? do
|
|
114
|
+
raise Capybara::ExpectationNotMet.new('Timed out waiting for Selenium session reset') if (Capybara::Helpers.monotonic_time - start_time) >= 10
|
|
115
|
+
sleep 0.05
|
|
100
116
|
end
|
|
101
|
-
@browser.navigate.to("about:blank")
|
|
102
117
|
rescue Selenium::WebDriver::Error::UnhandledAlertError
|
|
103
118
|
# This error is thrown if an unhandled alert is on the page
|
|
104
119
|
# Firefox appears to automatically dismiss this alert, chrome does not
|
|
105
120
|
# We'll try to accept it
|
|
106
121
|
begin
|
|
107
122
|
@browser.switch_to.alert.accept
|
|
123
|
+
sleep 0.25 # allow time for the modal to be handled
|
|
108
124
|
rescue Selenium::WebDriver::Error::NoAlertPresentError
|
|
109
125
|
# The alert is now gone - nothing to do
|
|
110
126
|
end
|
|
@@ -247,8 +263,9 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
|
247
263
|
Selenium::WebDriver::Error::NoSuchWindowError
|
|
248
264
|
end
|
|
249
265
|
|
|
266
|
+
# @deprecated This method is being removed
|
|
250
267
|
def browser_initialized?
|
|
251
|
-
!@browser.nil?
|
|
268
|
+
super && !@browser.nil?
|
|
252
269
|
end
|
|
253
270
|
|
|
254
271
|
private
|
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,46 @@ 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
28
|
def initialize(app)
|
|
11
29
|
@app = app
|
|
30
|
+
@counter = Counter.new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def pending_requests?
|
|
34
|
+
@counter.value > 0
|
|
12
35
|
end
|
|
13
36
|
|
|
14
37
|
def call(env)
|
|
15
38
|
if env["PATH_INFO"] == "/__identify__"
|
|
16
39
|
[200, {}, [@app.object_id.to_s]]
|
|
17
40
|
else
|
|
41
|
+
@counter.increment
|
|
18
42
|
begin
|
|
19
43
|
@app.call(env)
|
|
20
44
|
rescue *Capybara.server_errors => e
|
|
21
45
|
@error = e unless @error
|
|
22
46
|
raise e
|
|
47
|
+
ensure
|
|
48
|
+
@counter.decrement
|
|
23
49
|
end
|
|
24
50
|
end
|
|
25
51
|
end
|
|
@@ -38,8 +64,8 @@ module Capybara
|
|
|
38
64
|
@middleware = Middleware.new(@app)
|
|
39
65
|
@server_thread = nil # suppress warnings
|
|
40
66
|
@host, @port = host, port
|
|
41
|
-
@port ||= Capybara::Server.ports[@app.object_id]
|
|
42
|
-
@port ||= find_available_port
|
|
67
|
+
@port ||= Capybara::Server.ports[Capybara.reuse_server ? @app.object_id : @middleware.object_id]
|
|
68
|
+
@port ||= find_available_port(host)
|
|
43
69
|
end
|
|
44
70
|
|
|
45
71
|
def reset_error!
|
|
@@ -62,12 +88,18 @@ module Capybara
|
|
|
62
88
|
return false
|
|
63
89
|
end
|
|
64
90
|
|
|
91
|
+
def wait_for_pending_requests
|
|
92
|
+
Timeout.timeout(60) { sleep(0.01) while @middleware.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[@app.object_id] = @port
|
|
99
|
+
Capybara::Server.ports[Capybara.reuse_server ? @app.object_id : @middleware.object_id] = @port
|
|
68
100
|
|
|
69
101
|
@server_thread = Thread.new do
|
|
70
|
-
Capybara.server.call(@middleware, @port)
|
|
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,8 +112,8 @@ module Capybara
|
|
|
80
112
|
|
|
81
113
|
private
|
|
82
114
|
|
|
83
|
-
def find_available_port
|
|
84
|
-
server = TCPServer.new(
|
|
115
|
+
def find_available_port(host)
|
|
116
|
+
server = TCPServer.new(host, 0)
|
|
85
117
|
server.addr[1]
|
|
86
118
|
ensure
|
|
87
119
|
server.close if server
|