capybara 1.1.4 → 2.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|