capybara 1.1.4 → 2.0.0.beta2
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.
- data/History.txt +100 -0
- data/License.txt +22 -0
- data/README.md +829 -0
- data/lib/capybara.rb +124 -6
- data/lib/capybara/cucumber.rb +2 -5
- data/lib/capybara/driver/base.rb +5 -5
- data/lib/capybara/driver/node.rb +2 -2
- data/lib/capybara/dsl.rb +3 -121
- data/lib/capybara/node/actions.rb +12 -28
- data/lib/capybara/node/base.rb +5 -13
- data/lib/capybara/node/element.rb +21 -21
- data/lib/capybara/node/finders.rb +27 -89
- data/lib/capybara/node/matchers.rb +107 -69
- data/lib/capybara/node/simple.rb +11 -13
- data/lib/capybara/query.rb +78 -0
- data/lib/capybara/rack_test/browser.rb +16 -27
- data/lib/capybara/rack_test/driver.rb +11 -1
- data/lib/capybara/rack_test/node.rb +17 -1
- data/lib/capybara/result.rb +84 -0
- data/lib/capybara/rspec/matchers.rb +28 -63
- data/lib/capybara/selector.rb +97 -33
- data/lib/capybara/selenium/driver.rb +14 -61
- data/lib/capybara/selenium/node.rb +6 -15
- data/lib/capybara/server.rb +32 -27
- data/lib/capybara/session.rb +54 -30
- data/lib/capybara/spec/public/jquery-ui.js +791 -0
- data/lib/capybara/spec/public/jquery.js +9046 -0
- data/lib/capybara/spec/public/test.js +4 -1
- data/lib/capybara/spec/session.rb +56 -27
- data/lib/capybara/spec/session/all_spec.rb +8 -4
- data/lib/capybara/spec/session/attach_file_spec.rb +12 -9
- data/lib/capybara/spec/session/check_spec.rb +6 -3
- data/lib/capybara/spec/session/choose_spec.rb +4 -1
- data/lib/capybara/spec/session/click_button_spec.rb +5 -14
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +2 -1
- data/lib/capybara/spec/session/click_link_spec.rb +3 -17
- data/lib/capybara/spec/session/current_url_spec.rb +77 -9
- data/lib/capybara/spec/session/fill_in_spec.rb +8 -18
- data/lib/capybara/spec/session/find_spec.rb +19 -46
- data/lib/capybara/spec/session/first_spec.rb +2 -34
- data/lib/capybara/spec/session/has_css_spec.rb +1 -1
- data/lib/capybara/spec/session/has_field_spec.rb +28 -0
- data/lib/capybara/spec/session/has_select_spec.rb +84 -31
- data/lib/capybara/spec/session/has_table_spec.rb +7 -69
- data/lib/capybara/spec/session/has_text_spec.rb +168 -0
- data/lib/capybara/spec/session/javascript.rb +65 -81
- data/lib/capybara/spec/session/node_spec.rb +115 -0
- data/lib/capybara/spec/session/screenshot.rb +29 -0
- data/lib/capybara/spec/session/select_spec.rb +12 -12
- data/lib/capybara/spec/session/text_spec.rb +9 -4
- data/lib/capybara/spec/session/unselect_spec.rb +12 -6
- data/lib/capybara/spec/session/visit_spec.rb +76 -0
- data/lib/capybara/spec/session/within_frame_spec.rb +33 -0
- data/lib/capybara/spec/session/within_spec.rb +47 -58
- data/lib/capybara/spec/session/within_window_spec.rb +40 -0
- data/lib/capybara/spec/test_app.rb +27 -3
- data/lib/capybara/spec/views/form.erb +11 -10
- data/lib/capybara/spec/views/host_links.erb +2 -2
- data/lib/capybara/spec/views/tables.erb +6 -66
- data/lib/capybara/spec/views/with_html.erb +3 -3
- data/lib/capybara/spec/views/with_js.erb +11 -8
- data/lib/capybara/util/save_and_open_page.rb +4 -3
- data/lib/capybara/version.rb +1 -1
- data/spec/basic_node_spec.rb +15 -3
- data/spec/dsl_spec.rb +12 -10
- data/spec/rack_test_spec.rb +152 -0
- data/spec/rspec/features_spec.rb +0 -2
- data/spec/rspec/matchers_spec.rb +164 -89
- data/spec/rspec_spec.rb +0 -2
- data/spec/selenium_spec.rb +67 -0
- data/spec/server_spec.rb +35 -23
- data/spec/spec_helper.rb +18 -2
- metadata +30 -30
- data/README.rdoc +0 -722
- data/lib/capybara/spec/driver.rb +0 -301
- data/lib/capybara/spec/session/current_host_spec.rb +0 -68
- data/lib/capybara/spec/session/has_content_spec.rb +0 -106
- data/lib/capybara/util/timeout.rb +0 -27
- data/spec/driver/rack_test_driver_spec.rb +0 -89
- data/spec/driver/selenium_driver_spec.rb +0 -37
- data/spec/session/rack_test_session_spec.rb +0 -55
- data/spec/session/selenium_session_spec.rb +0 -26
- data/spec/string_spec.rb +0 -77
- data/spec/timeout_spec.rb +0 -28
data/lib/capybara/selector.rb
CHANGED
@@ -1,13 +1,7 @@
|
|
1
1
|
module Capybara
|
2
2
|
class Selector
|
3
|
-
attr_reader :name
|
3
|
+
attr_reader :name, :custom_filters
|
4
4
|
|
5
|
-
class Normalized
|
6
|
-
attr_accessor :selector, :locator, :options, :xpaths
|
7
|
-
|
8
|
-
def failure_message; selector.failure_message; end
|
9
|
-
def name; selector.name; end
|
10
|
-
end
|
11
5
|
|
12
6
|
class << self
|
13
7
|
def all
|
@@ -21,32 +15,13 @@ module Capybara
|
|
21
15
|
def remove(name)
|
22
16
|
all.delete(name.to_sym)
|
23
17
|
end
|
24
|
-
|
25
|
-
def normalize(*args)
|
26
|
-
normalized = Normalized.new
|
27
|
-
normalized.options = if args.last.is_a?(Hash) then args.pop else {} end
|
28
|
-
|
29
|
-
if args[1]
|
30
|
-
normalized.selector = all[args[0]]
|
31
|
-
normalized.locator = args[1]
|
32
|
-
else
|
33
|
-
normalized.selector = all.values.find { |s| s.match?(args[0]) }
|
34
|
-
normalized.locator = args[0]
|
35
|
-
end
|
36
|
-
normalized.selector ||= all[Capybara.default_selector]
|
37
|
-
|
38
|
-
xpath = normalized.selector.call(normalized.locator)
|
39
|
-
if xpath.respond_to?(:to_xpaths)
|
40
|
-
normalized.xpaths = xpath.to_xpaths
|
41
|
-
else
|
42
|
-
normalized.xpaths = [xpath.to_s].flatten
|
43
|
-
end
|
44
|
-
normalized
|
45
|
-
end
|
46
18
|
end
|
47
19
|
|
48
20
|
def initialize(name, &block)
|
49
21
|
@name = name
|
22
|
+
@custom_filters = {}
|
23
|
+
@match = nil
|
24
|
+
@failure_message = nil
|
50
25
|
instance_eval(&block)
|
51
26
|
end
|
52
27
|
|
@@ -55,14 +30,22 @@ module Capybara
|
|
55
30
|
@xpath
|
56
31
|
end
|
57
32
|
|
33
|
+
# Same as xpath, but wrap in XPath.css().
|
34
|
+
def css(&block)
|
35
|
+
if block
|
36
|
+
@xpath = xpath { |*args| XPath.css(block.call(*args)) }
|
37
|
+
end
|
38
|
+
@xpath
|
39
|
+
end
|
40
|
+
|
58
41
|
def match(&block)
|
59
42
|
@match = block if block
|
60
43
|
@match
|
61
44
|
end
|
62
45
|
|
63
|
-
def
|
64
|
-
@
|
65
|
-
@
|
46
|
+
def label(label=nil)
|
47
|
+
@label = label if label
|
48
|
+
@label
|
66
49
|
end
|
67
50
|
|
68
51
|
def call(locator)
|
@@ -72,6 +55,10 @@ module Capybara
|
|
72
55
|
def match?(locator)
|
73
56
|
@match and @match.call(locator)
|
74
57
|
end
|
58
|
+
|
59
|
+
def filter(name, &block)
|
60
|
+
@custom_filters[name] = block
|
61
|
+
end
|
75
62
|
end
|
76
63
|
end
|
77
64
|
|
@@ -80,10 +67,87 @@ Capybara.add_selector(:xpath) do
|
|
80
67
|
end
|
81
68
|
|
82
69
|
Capybara.add_selector(:css) do
|
83
|
-
|
70
|
+
css { |css| css }
|
84
71
|
end
|
85
72
|
|
86
73
|
Capybara.add_selector(:id) do
|
87
74
|
xpath { |id| XPath.descendant[XPath.attr(:id) == id.to_s] }
|
88
75
|
match { |value| value.is_a?(Symbol) }
|
89
76
|
end
|
77
|
+
|
78
|
+
Capybara.add_selector(:field) do
|
79
|
+
xpath { |locator| XPath::HTML.field(locator) }
|
80
|
+
filter(:checked) { |node, value| not(value ^ node.checked?) }
|
81
|
+
filter(:unchecked) { |node, value| (value ^ node.checked?) }
|
82
|
+
filter(:with) { |node, with| node.value == with }
|
83
|
+
filter(:type) { |node, type| node[:type] == type }
|
84
|
+
end
|
85
|
+
|
86
|
+
Capybara.add_selector(:fieldset) do
|
87
|
+
xpath { |locator| XPath::HTML.fieldset(locator) }
|
88
|
+
end
|
89
|
+
|
90
|
+
Capybara.add_selector(:link_or_button) do
|
91
|
+
label "link or button"
|
92
|
+
xpath { |locator| XPath::HTML.link_or_button(locator) }
|
93
|
+
end
|
94
|
+
|
95
|
+
Capybara.add_selector(:link) do
|
96
|
+
xpath { |locator| XPath::HTML.link(locator) }
|
97
|
+
filter(:href) do |node, href|
|
98
|
+
node.first(:xpath, XPath.axis(:self)[XPath.attr(:href).equals(href)])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
Capybara.add_selector(:button) do
|
103
|
+
xpath { |locator| XPath::HTML.button(locator) }
|
104
|
+
end
|
105
|
+
|
106
|
+
Capybara.add_selector(:fillable_field) do
|
107
|
+
label "field"
|
108
|
+
xpath { |locator| XPath::HTML.fillable_field(locator) }
|
109
|
+
end
|
110
|
+
|
111
|
+
Capybara.add_selector(:radio_button) do
|
112
|
+
label "radio button"
|
113
|
+
xpath { |locator| XPath::HTML.radio_button(locator) }
|
114
|
+
filter(:checked) { |node, value| not(value ^ node.checked?) }
|
115
|
+
filter(:unchecked) { |node, value| (value ^ node.checked?) }
|
116
|
+
end
|
117
|
+
|
118
|
+
Capybara.add_selector(:checkbox) do
|
119
|
+
xpath { |locator| XPath::HTML.checkbox(locator) }
|
120
|
+
filter(:checked) { |node, value| not(value ^ node.checked?) }
|
121
|
+
filter(:unchecked) { |node, value| (value ^ node.checked?) }
|
122
|
+
end
|
123
|
+
|
124
|
+
Capybara.add_selector(:select) do
|
125
|
+
label "select box"
|
126
|
+
xpath { |locator| XPath::HTML.select(locator) }
|
127
|
+
filter(:options) do |node, options|
|
128
|
+
actual = node.all(:xpath, './/option').map { |option| option.text }
|
129
|
+
options.sort == actual.sort
|
130
|
+
end
|
131
|
+
filter(:with_options) { |node, options| options.all? { |option| node.first(:option, option) } }
|
132
|
+
filter(:selected) do |node, selected|
|
133
|
+
actual = node.all(:xpath, './/option').select { |option| option.selected? }.map { |option| option.text }
|
134
|
+
[selected].flatten.sort == actual.sort
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
Capybara.add_selector(:option) do
|
139
|
+
xpath { |locator| XPath::HTML.option(locator) }
|
140
|
+
end
|
141
|
+
|
142
|
+
Capybara.add_selector(:file_field) do
|
143
|
+
label "file field"
|
144
|
+
xpath { |locator| XPath::HTML.file_field(locator) }
|
145
|
+
end
|
146
|
+
|
147
|
+
Capybara.add_selector(:content) do
|
148
|
+
xpath { |content| XPath::HTML.content(content) }
|
149
|
+
end
|
150
|
+
|
151
|
+
Capybara.add_selector(:table) do
|
152
|
+
xpath { |locator| XPath::HTML.table(locator) }
|
153
|
+
end
|
@@ -2,13 +2,11 @@ require 'selenium-webdriver'
|
|
2
2
|
|
3
3
|
class Capybara::Selenium::Driver < Capybara::Driver::Base
|
4
4
|
DEFAULT_OPTIONS = {
|
5
|
-
:resynchronize => false,
|
6
|
-
:resynchronization_timeout => 10,
|
7
5
|
:browser => :firefox
|
8
6
|
}
|
9
|
-
SPECIAL_OPTIONS = [:browser
|
7
|
+
SPECIAL_OPTIONS = [:browser]
|
10
8
|
|
11
|
-
attr_reader :app, :
|
9
|
+
attr_reader :app, :options
|
12
10
|
|
13
11
|
def browser
|
14
12
|
unless @browser
|
@@ -27,13 +25,13 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
27
25
|
|
28
26
|
def initialize(app, options={})
|
29
27
|
@app = app
|
28
|
+
@browser = nil
|
29
|
+
@exit_status = nil
|
30
30
|
@options = DEFAULT_OPTIONS.merge(options)
|
31
|
-
@rack_server = Capybara::Server.new(@app)
|
32
|
-
@rack_server.boot if Capybara.run_server
|
33
31
|
end
|
34
32
|
|
35
33
|
def visit(path)
|
36
|
-
browser.navigate.to(
|
34
|
+
browser.navigate.to(path)
|
37
35
|
end
|
38
36
|
|
39
37
|
def source
|
@@ -53,18 +51,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
53
51
|
end
|
54
52
|
|
55
53
|
def wait?; true; end
|
56
|
-
|
57
|
-
def resynchronize
|
58
|
-
if options[:resynchronize]
|
59
|
-
load_wait_for_ajax_support
|
60
|
-
yield
|
61
|
-
Capybara.timeout(options[:resynchronization_timeout], self, "failed to resynchronize, ajax request timed out") do
|
62
|
-
evaluate_script("!window.capybaraRequestsOutstanding")
|
63
|
-
end
|
64
|
-
else
|
65
|
-
yield
|
66
|
-
end
|
67
|
-
end
|
54
|
+
def needs_server?; true; end
|
68
55
|
|
69
56
|
def execute_script(script)
|
70
57
|
browser.execute_script script
|
@@ -74,12 +61,15 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
74
61
|
browser.execute_script "return #{script}"
|
75
62
|
end
|
76
63
|
|
64
|
+
def save_screenshot(path, options={})
|
65
|
+
browser.save_screenshot(path)
|
66
|
+
end
|
67
|
+
|
77
68
|
def reset!
|
78
69
|
# Use instance variable directly so we avoid starting the browser just to reset the session
|
79
70
|
if @browser
|
80
|
-
begin
|
81
|
-
|
82
|
-
rescue Selenium::WebDriver::Error::UnhandledError => e
|
71
|
+
begin @browser.manage.delete_all_cookies
|
72
|
+
rescue Selenium::WebDriver::Error::UnhandledError
|
83
73
|
# delete_all_cookies fails when we've previously gone
|
84
74
|
# to about:blank, so we rescue this error and do nothing
|
85
75
|
# instead.
|
@@ -92,6 +82,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
92
82
|
old_window = browser.window_handle
|
93
83
|
browser.switch_to.frame(frame_id)
|
94
84
|
yield
|
85
|
+
ensure
|
95
86
|
browser.switch_to.window old_window
|
96
87
|
end
|
97
88
|
|
@@ -122,44 +113,6 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
122
113
|
end
|
123
114
|
|
124
115
|
def invalid_element_errors
|
125
|
-
[ Selenium::WebDriver::Error::
|
126
|
-
Selenium::WebDriver::Error::InvalidSelectorError,
|
127
|
-
Selenium::WebDriver::Error::UnknownError ]
|
128
|
-
end
|
129
|
-
|
130
|
-
private
|
131
|
-
|
132
|
-
def load_wait_for_ajax_support
|
133
|
-
browser.execute_script <<-JS
|
134
|
-
window.capybaraRequestsOutstanding = 0;
|
135
|
-
(function() { // Overriding XMLHttpRequest
|
136
|
-
var oldXHR = window.XMLHttpRequest;
|
137
|
-
|
138
|
-
function newXHR() {
|
139
|
-
var realXHR = new oldXHR();
|
140
|
-
|
141
|
-
window.capybaraRequestsOutstanding++;
|
142
|
-
realXHR.addEventListener("readystatechange", function() {
|
143
|
-
if( realXHR.readyState == 4 ) {
|
144
|
-
setTimeout( function() {
|
145
|
-
window.capybaraRequestsOutstanding--;
|
146
|
-
if(window.capybaraRequestsOutstanding < 0) {
|
147
|
-
window.capybaraRequestsOutstanding = 0;
|
148
|
-
}
|
149
|
-
}, 500 );
|
150
|
-
}
|
151
|
-
}, false);
|
152
|
-
|
153
|
-
return realXHR;
|
154
|
-
}
|
155
|
-
|
156
|
-
window.XMLHttpRequest = newXHR;
|
157
|
-
})();
|
158
|
-
JS
|
159
|
-
end
|
160
|
-
|
161
|
-
def url(path)
|
162
|
-
rack_server.url(path)
|
116
|
+
[Selenium::WebDriver::Error::ObsoleteElementError, Selenium::WebDriver::Error::UnhandledError]
|
163
117
|
end
|
164
|
-
|
165
118
|
end
|
@@ -23,34 +23,29 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
23
23
|
elsif tag_name == 'input' and type == 'checkbox'
|
24
24
|
click if value ^ native.attribute('checked').to_s.eql?("true")
|
25
25
|
elsif tag_name == 'input' and type == 'file'
|
26
|
-
|
27
|
-
native.send_keys(value.to_s)
|
28
|
-
end
|
26
|
+
native.send_keys(value.to_s)
|
29
27
|
elsif tag_name == 'textarea' or tag_name == 'input'
|
30
|
-
|
31
|
-
native.clear
|
32
|
-
native.send_keys(value.to_s)
|
33
|
-
end
|
28
|
+
native.send_keys(("\b" * native[:value].size) + value.to_s)
|
34
29
|
end
|
35
30
|
end
|
36
31
|
|
37
32
|
def select_option
|
38
|
-
|
33
|
+
native.click unless selected?
|
39
34
|
end
|
40
35
|
|
41
36
|
def unselect_option
|
42
37
|
if select_node['multiple'] != 'multiple' and select_node['multiple'] != 'true'
|
43
38
|
raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
|
44
39
|
end
|
45
|
-
|
40
|
+
native.click if selected?
|
46
41
|
end
|
47
42
|
|
48
43
|
def click
|
49
|
-
|
44
|
+
native.click
|
50
45
|
end
|
51
46
|
|
52
47
|
def drag_to(element)
|
53
|
-
|
48
|
+
driver.browser.action.drag_and_drop(native, element.native).perform
|
54
49
|
end
|
55
50
|
|
56
51
|
def tag_name
|
@@ -75,10 +70,6 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
75
70
|
|
76
71
|
private
|
77
72
|
|
78
|
-
def resynchronize
|
79
|
-
driver.resynchronize { yield }
|
80
|
-
end
|
81
|
-
|
82
73
|
# a reference to the select node if this is an option node
|
83
74
|
def select_node
|
84
75
|
find('./ancestor::select').first
|
data/lib/capybara/server.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
require 'uri'
|
2
2
|
require 'net/http'
|
3
3
|
require 'rack'
|
4
|
-
require 'capybara/util/timeout'
|
5
4
|
|
6
5
|
module Capybara
|
7
6
|
class Server
|
8
|
-
class
|
7
|
+
class Middleware
|
8
|
+
attr_accessor :error
|
9
|
+
|
9
10
|
def initialize(app)
|
10
11
|
@app = app
|
11
12
|
end
|
@@ -14,7 +15,12 @@ module Capybara
|
|
14
15
|
if env["PATH_INFO"] == "/__identify__"
|
15
16
|
[200, {}, [@app.object_id.to_s]]
|
16
17
|
else
|
17
|
-
|
18
|
+
begin
|
19
|
+
@app.call(env)
|
20
|
+
rescue StandardError => e
|
21
|
+
@error = e unless @error
|
22
|
+
raise e
|
23
|
+
end
|
18
24
|
end
|
19
25
|
end
|
20
26
|
end
|
@@ -27,23 +33,30 @@ module Capybara
|
|
27
33
|
|
28
34
|
attr_reader :app, :port
|
29
35
|
|
30
|
-
def initialize(app)
|
36
|
+
def initialize(app, port=Capybara.server_port)
|
31
37
|
@app = app
|
38
|
+
@middleware = Middleware.new(@app)
|
39
|
+
@server_thread = nil # supress warnings
|
40
|
+
@port = port
|
41
|
+
@port ||= Capybara::Server.ports[@app.object_id]
|
42
|
+
@port ||= find_available_port
|
32
43
|
end
|
33
44
|
|
34
|
-
def
|
35
|
-
|
45
|
+
def reset_error!
|
46
|
+
@middleware.error = nil
|
36
47
|
end
|
37
48
|
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
49
|
+
def error
|
50
|
+
@middleware.error
|
51
|
+
end
|
52
|
+
|
53
|
+
def host
|
54
|
+
Capybara.server_host || "127.0.0.1"
|
44
55
|
end
|
45
56
|
|
46
57
|
def responsive?
|
58
|
+
return false if @server_thread && @server_thread.join(0)
|
59
|
+
|
47
60
|
res = Net::HTTP.start(host, @port) { |http| http.get('/__identify__') }
|
48
61
|
|
49
62
|
if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
|
@@ -54,25 +67,17 @@ module Capybara
|
|
54
67
|
end
|
55
68
|
|
56
69
|
def boot
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
if not @port or not responsive?
|
61
|
-
@port = Capybara.server_port || find_available_port
|
62
|
-
Capybara::Server.ports[@app.object_id] = @port
|
63
|
-
|
64
|
-
Thread.new do
|
65
|
-
Capybara.server.call(Identify.new(@app), @port)
|
66
|
-
end
|
70
|
+
unless responsive?
|
71
|
+
Capybara::Server.ports[@app.object_id] = @port
|
67
72
|
|
68
|
-
|
69
|
-
|
70
|
-
end
|
73
|
+
@server_thread = Thread.new do
|
74
|
+
Capybara.server.call(@middleware, @port)
|
71
75
|
end
|
76
|
+
|
77
|
+
Timeout.timeout(60) { @server_thread.join(0.1) until responsive? }
|
72
78
|
end
|
73
79
|
rescue TimeoutError
|
74
|
-
|
75
|
-
exit
|
80
|
+
raise "Rack application timed out during boot"
|
76
81
|
else
|
77
82
|
self
|
78
83
|
end
|