capybara 0.1.3 → 0.1.4
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/Manifest.txt +4 -4
- data/README.rdoc +9 -2
- data/lib/capybara.rb +12 -6
- data/lib/capybara/driver/base.rb +17 -0
- data/lib/capybara/driver/culerity_driver.rb +22 -10
- data/lib/capybara/driver/rack_test_driver.rb +41 -36
- data/lib/capybara/driver/selenium_driver.rb +12 -5
- data/lib/capybara/dsl.rb +6 -6
- data/lib/capybara/node.rb +9 -5
- data/lib/capybara/rails.rb +0 -4
- data/lib/capybara/server.rb +3 -0
- data/lib/capybara/session.rb +69 -81
- data/lib/capybara/xpath.rb +124 -0
- data/spec/driver/selenium_driver_spec.rb +1 -1
- data/spec/drivers_spec.rb +20 -3
- data/spec/dsl_spec.rb +4 -3
- data/spec/public/jquery-ui.js +35 -0
- data/spec/session/culerity_session_spec.rb +4 -3
- data/spec/session/rack_test_session_spec.rb +4 -3
- data/spec/session/selenium_session_spec.rb +5 -4
- data/spec/session_spec.rb +248 -49
- data/spec/test_app.rb +1 -1
- data/spec/views/form.erb +26 -3
- data/spec/views/with_js.erb +25 -0
- data/spec/xpath_spec.rb +195 -0
- metadata +6 -6
- data/lib/capybara/driver/firewatir_driver.rb +0 -66
- data/lib/capybara/driver/safariwatir_driver.rb +0 -67
- data/spec/driver/firewatir_driver_spec.rb +0 -10
- data/spec/driver/safariwarit_driver_spec.rb +0 -10
data/lib/capybara/rails.rb
CHANGED
data/lib/capybara/server.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
require 'uri'
|
1
2
|
require 'net/http'
|
2
3
|
require 'rack'
|
4
|
+
require 'rack/handler/mongrel'
|
3
5
|
|
4
6
|
class Capybara::Server
|
5
7
|
attr_reader :app
|
@@ -17,6 +19,7 @@ class Capybara::Server
|
|
17
19
|
end
|
18
20
|
|
19
21
|
def url(path)
|
22
|
+
path = URI.parse(path).request_uri if path =~ /^http/
|
20
23
|
"http://#{host}:#{port}#{path}"
|
21
24
|
end
|
22
25
|
|
data/lib/capybara/session.rb
CHANGED
@@ -1,26 +1,6 @@
|
|
1
1
|
module Capybara
|
2
|
-
|
3
|
-
class << self
|
4
|
-
attr_writer :default_selector
|
5
|
-
|
6
|
-
def default_selector
|
7
|
-
@default_selector ||= :xpath
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
2
|
class Session
|
12
3
|
|
13
|
-
FIELDS_PATHS = {
|
14
|
-
:text_field => proc { |id| "//input[@type='text'][@id='#{id}']" },
|
15
|
-
:text_area => proc { |id| "//textarea[@id='#{id}']" },
|
16
|
-
:password_field => proc { |id| "//input[@type='password'][@id='#{id}']" },
|
17
|
-
:radio => proc { |id| "//input[@type='radio'][@id='#{id}']" },
|
18
|
-
:hidden_field => proc { |id| "//input[@type='hidden'][@id='#{id}']" },
|
19
|
-
:checkbox => proc { |id| "//input[@type='checkbox'][@id='#{id}']" },
|
20
|
-
:select => proc { |id| "//select[@id='#{id}']" },
|
21
|
-
:file_field => proc { |id| "//input[@type='file'][@id='#{id}']" }
|
22
|
-
}
|
23
|
-
|
24
4
|
attr_reader :mode, :app
|
25
5
|
|
26
6
|
def initialize(mode, app)
|
@@ -46,35 +26,59 @@ module Capybara
|
|
46
26
|
end
|
47
27
|
|
48
28
|
def click_link(locator)
|
49
|
-
|
29
|
+
link = wait_for(XPath.link(locator))
|
30
|
+
raise Capybara::ElementNotFound, "no link with title, id or text '#{locator}' found" unless link
|
31
|
+
link.click
|
50
32
|
end
|
51
33
|
|
52
34
|
def click_button(locator)
|
53
|
-
|
35
|
+
button = wait_for(XPath.button(locator))
|
36
|
+
raise Capybara::ElementNotFound, "no button with value or id or text '#{locator}' found" unless button
|
37
|
+
button.click
|
38
|
+
end
|
39
|
+
|
40
|
+
def drag(source_locator, target_locator)
|
41
|
+
source = find(source_locator)
|
42
|
+
raise Capybara::ElementNotFound, "drag source '#{source_locator}' not found on page" unless source
|
43
|
+
target = find(target_locator)
|
44
|
+
raise Capybara::ElementNotFound, "drag target '#{target_locator}' not found on page" unless target
|
45
|
+
source.drag_to(target)
|
54
46
|
end
|
55
47
|
|
56
48
|
def fill_in(locator, options={})
|
57
|
-
|
49
|
+
field = wait_for(XPath.fillable_field(locator))
|
50
|
+
raise Capybara::ElementNotFound, "cannot fill in, no text field, text area or password field with id or label '#{locator}' found" unless field
|
51
|
+
field.set(options[:with])
|
58
52
|
end
|
59
53
|
|
60
54
|
def choose(locator)
|
61
|
-
|
55
|
+
field = wait_for(XPath.radio_button(locator))
|
56
|
+
raise Capybara::ElementNotFound, "cannot choose field, no radio button with id or label '#{locator}' found" unless field
|
57
|
+
field.set(true)
|
62
58
|
end
|
63
59
|
|
64
60
|
def check(locator)
|
65
|
-
|
61
|
+
field = wait_for(XPath.checkbox(locator))
|
62
|
+
raise Capybara::ElementNotFound, "cannot check field, no checkbox with id or label '#{locator}' found" unless field
|
63
|
+
field.set(true)
|
66
64
|
end
|
67
65
|
|
68
66
|
def uncheck(locator)
|
69
|
-
|
67
|
+
field = wait_for(XPath.checkbox(locator))
|
68
|
+
raise Capybara::ElementNotFound, "cannot uncheck field, no checkbox with id or label '#{locator}' found" unless field
|
69
|
+
field.set(false)
|
70
70
|
end
|
71
71
|
|
72
72
|
def select(value, options={})
|
73
|
-
|
73
|
+
field = wait_for(XPath.select(options[:from]))
|
74
|
+
raise Capybara::ElementNotFound, "cannot select option, no select box with id or label '#{options[:from]}' found" unless field
|
75
|
+
field.select(value)
|
74
76
|
end
|
75
77
|
|
76
78
|
def attach_file(locator, path)
|
77
|
-
|
79
|
+
field = wait_for(XPath.file_field(locator))
|
80
|
+
raise Capybara::ElementNotFound, "cannot attach file, no file field with id or label '#{locator}' found" unless field
|
81
|
+
field.set(path)
|
78
82
|
end
|
79
83
|
|
80
84
|
def body
|
@@ -82,11 +86,11 @@ module Capybara
|
|
82
86
|
end
|
83
87
|
|
84
88
|
def has_content?(content)
|
85
|
-
has_xpath?(
|
89
|
+
has_xpath?(XPath.content(content).to_s)
|
86
90
|
end
|
87
91
|
|
88
92
|
def has_xpath?(path, options={})
|
89
|
-
results =
|
93
|
+
results = all(path)
|
90
94
|
if options[:text]
|
91
95
|
results = filter_by_text(results, options[:text])
|
92
96
|
end
|
@@ -104,20 +108,20 @@ module Capybara
|
|
104
108
|
def within(kind, scope=nil)
|
105
109
|
kind, scope = Capybara.default_selector, kind unless scope
|
106
110
|
scope = css_to_xpath(scope) if kind == :css
|
107
|
-
raise Capybara::ElementNotFound, "scope '#{scope}' not found on page"
|
111
|
+
raise Capybara::ElementNotFound, "scope '#{scope}' not found on page" unless wait_for(scope)
|
108
112
|
scopes.push(scope)
|
109
113
|
yield
|
110
114
|
scopes.pop
|
111
115
|
end
|
112
116
|
|
113
117
|
def within_fieldset(locator)
|
114
|
-
within
|
118
|
+
within XPath.fieldset(locator) do
|
115
119
|
yield
|
116
120
|
end
|
117
121
|
end
|
118
122
|
|
119
123
|
def within_table(locator)
|
120
|
-
within
|
124
|
+
within XPath.table(locator) do
|
121
125
|
yield
|
122
126
|
end
|
123
127
|
end
|
@@ -127,25 +131,45 @@ module Capybara
|
|
127
131
|
Capybara::SaveAndOpenPage.save_and_open_page(body)
|
128
132
|
end
|
129
133
|
|
130
|
-
def
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
134
|
+
def all(locator)
|
135
|
+
XPath.wrap(locator).scope(current_scope).paths.map do |path|
|
136
|
+
driver.find(path)
|
137
|
+
end.flatten
|
138
|
+
end
|
139
|
+
|
140
|
+
def find(locator)
|
141
|
+
all(locator).first
|
142
|
+
end
|
143
|
+
|
144
|
+
def wait_for(locator)
|
145
|
+
return find(locator) unless driver.wait?
|
146
|
+
8.times do
|
147
|
+
result = find(locator)
|
148
|
+
return result if result
|
149
|
+
sleep(0.1)
|
150
|
+
end
|
151
|
+
nil
|
152
|
+
end
|
153
|
+
|
154
|
+
def find_field(locator)
|
155
|
+
find(XPath.field(locator))
|
135
156
|
end
|
136
157
|
alias_method :field_labeled, :find_field
|
137
158
|
|
138
159
|
def find_link(locator)
|
139
|
-
|
140
|
-
raise Capybara::ElementNotFound, "no link with title, id or text '#{locator}' found" unless link
|
141
|
-
link
|
160
|
+
find(XPath.link(locator))
|
142
161
|
end
|
143
162
|
|
144
163
|
def find_button(locator)
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
164
|
+
find(XPath.button(locator))
|
165
|
+
end
|
166
|
+
|
167
|
+
def evaluate_script(script)
|
168
|
+
begin
|
169
|
+
driver.evaluate_script(script)
|
170
|
+
rescue NoMethodError
|
171
|
+
raise NotSupportedByDriverError
|
172
|
+
end
|
149
173
|
end
|
150
174
|
|
151
175
|
private
|
@@ -172,41 +196,5 @@ module Capybara
|
|
172
196
|
def scopes
|
173
197
|
@scopes ||= []
|
174
198
|
end
|
175
|
-
|
176
|
-
def find_field_by_id(locator, *kinds)
|
177
|
-
kinds.each do |kind|
|
178
|
-
path = FIELDS_PATHS[kind]
|
179
|
-
element = find(path.call(locator)).first
|
180
|
-
return element if element
|
181
|
-
end
|
182
|
-
return nil
|
183
|
-
end
|
184
|
-
|
185
|
-
def find_field_by_label(locator, *kinds)
|
186
|
-
kinds.each do |kind|
|
187
|
-
label = find("//label[contains(.,'#{locator}')]").first
|
188
|
-
if label
|
189
|
-
element = find_field_by_id(label[:for], kind)
|
190
|
-
return element if element
|
191
|
-
end
|
192
|
-
end
|
193
|
-
return nil
|
194
|
-
end
|
195
|
-
|
196
|
-
def find(locator)
|
197
|
-
locator = current_scope.to_s + locator
|
198
|
-
driver.find(locator)
|
199
|
-
end
|
200
|
-
|
201
|
-
def sanitized_xpath_string(string)
|
202
|
-
if string =~ /'/
|
203
|
-
string = string.split("'", -1).map do |substr|
|
204
|
-
"'#{substr}'"
|
205
|
-
end.join(%q{,"'",})
|
206
|
-
"concat(#{string})"
|
207
|
-
else
|
208
|
-
"'#{string}'"
|
209
|
-
end
|
210
|
-
end
|
211
199
|
end
|
212
200
|
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Capybara
|
2
|
+
# this is a class for generating XPath queries, use it like this:
|
3
|
+
# Xpath.text_field('foo').link('blah').to_s
|
4
|
+
# this will generate an XPath that matches either a text field or a link
|
5
|
+
class XPath
|
6
|
+
class << self
|
7
|
+
def wrap(path)
|
8
|
+
if path.is_a?(self)
|
9
|
+
path
|
10
|
+
else
|
11
|
+
new(path.to_s)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def respond_to?(method)
|
16
|
+
new.respond_to?(method)
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(*args)
|
20
|
+
new.send(*args)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :paths
|
25
|
+
|
26
|
+
def initialize(*paths)
|
27
|
+
@paths = paths
|
28
|
+
end
|
29
|
+
|
30
|
+
def field(locator)
|
31
|
+
fillable_field(locator).file_field(locator).checkbox(locator).radio_button(locator).select(locator)
|
32
|
+
end
|
33
|
+
|
34
|
+
def fillable_field(locator)
|
35
|
+
text_field(locator).password_field(locator).text_area(locator)
|
36
|
+
end
|
37
|
+
|
38
|
+
def content(locator)
|
39
|
+
append("/descendant-or-self::*[contains(.,#{s(locator)})]")
|
40
|
+
end
|
41
|
+
|
42
|
+
def table(locator)
|
43
|
+
append("//table[@id=#{s(locator)} or contains(caption,#{s(locator)})]")
|
44
|
+
end
|
45
|
+
|
46
|
+
def fieldset(locator)
|
47
|
+
append("//fieldset[@id=#{s(locator)} or contains(legend,#{s(locator)})]")
|
48
|
+
end
|
49
|
+
|
50
|
+
def link(locator)
|
51
|
+
append("//a[@id=#{s(locator)} or contains(.,#{s(locator)}) or @title=#{s(locator)}]")
|
52
|
+
end
|
53
|
+
|
54
|
+
def button(locator)
|
55
|
+
xpath = append("//input[@type='submit' or @type='image'][@id=#{s(locator)} or @value=#{s(locator)}]")
|
56
|
+
xpath.append("//button[@id=#{s(locator)} or @value=#{s(locator)} or contains(.,#{s(locator)})]")
|
57
|
+
end
|
58
|
+
|
59
|
+
def text_field(locator)
|
60
|
+
add_field(locator) { |id| "//input[@type='text'][@id=#{id}]" }
|
61
|
+
end
|
62
|
+
|
63
|
+
def password_field(locator)
|
64
|
+
add_field(locator) { |id| "//input[@type='password'][@id=#{id}]" }
|
65
|
+
end
|
66
|
+
|
67
|
+
def text_area(locator)
|
68
|
+
add_field(locator) { |id| "//textarea[@id=#{id}]" }
|
69
|
+
end
|
70
|
+
|
71
|
+
def radio_button(locator)
|
72
|
+
add_field(locator) { |id| "//input[@type='radio'][@id=#{id}]" }
|
73
|
+
end
|
74
|
+
|
75
|
+
def checkbox(locator)
|
76
|
+
add_field(locator) { |id| "//input[@type='checkbox'][@id=#{id}]" }
|
77
|
+
end
|
78
|
+
|
79
|
+
def select(locator)
|
80
|
+
add_field(locator) { |id| "//select[@id=#{id}]" }
|
81
|
+
end
|
82
|
+
|
83
|
+
def file_field(locator)
|
84
|
+
add_field(locator) { |id| "//input[@type='file'][@id=#{id}]" }
|
85
|
+
end
|
86
|
+
|
87
|
+
def scope(scope)
|
88
|
+
XPath.new(*paths.map { |p| scope + p })
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_s
|
92
|
+
@paths.join(' | ')
|
93
|
+
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
|
97
|
+
def add_field(locator)
|
98
|
+
xpath = append(yield(s(locator)))
|
99
|
+
xpath = xpath.append(yield("//label[contains(.,#{s(locator)})]/@for"))
|
100
|
+
xpath.prepend(yield("//label[text()=#{s(locator)}]/@for"))
|
101
|
+
end
|
102
|
+
|
103
|
+
# Sanitize a String for putting it into an xpath query
|
104
|
+
def s(string)
|
105
|
+
if string.include?("'")
|
106
|
+
string = string.split("'", -1).map do |substr|
|
107
|
+
"'#{substr}'"
|
108
|
+
end.join(%q{,"'",})
|
109
|
+
"concat(#{string})"
|
110
|
+
else
|
111
|
+
"'#{string}'"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def prepend(path)
|
116
|
+
XPath.new(*[path, @paths].flatten)
|
117
|
+
end
|
118
|
+
|
119
|
+
def append(path)
|
120
|
+
XPath.new(*[@paths, path].flatten)
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
data/spec/drivers_spec.rb
CHANGED
@@ -37,7 +37,7 @@ shared_examples_for 'driver' do
|
|
37
37
|
@driver.find('//a')[0].text.should == 'labore'
|
38
38
|
@driver.find('//a')[1].text.should == 'ullamco'
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
it "should extract node attributes" do
|
42
42
|
@driver.find('//a')[0][:href].should == '/with_simple_html'
|
43
43
|
@driver.find('//a')[0][:class].should == 'simple'
|
@@ -51,7 +51,7 @@ shared_examples_for 'driver' do
|
|
51
51
|
@driver.find('//input').first.set('gorilla')
|
52
52
|
@driver.find('//input').first.value.should == 'gorilla'
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
it "should extract node tag name" do
|
56
56
|
@driver.find('//a')[0].tag_name.should == 'a'
|
57
57
|
@driver.find('//a')[1].tag_name.should == 'a'
|
@@ -63,10 +63,27 @@ shared_examples_for 'driver' do
|
|
63
63
|
end
|
64
64
|
|
65
65
|
shared_examples_for "driver with javascript support" do
|
66
|
+
before { @driver.visit('/with_js') }
|
67
|
+
|
66
68
|
describe '#find' do
|
67
69
|
it "should find dynamically changed nodes" do
|
68
|
-
@driver.visit('/with_js')
|
69
70
|
@driver.find('//p').first.text.should == 'I changed it'
|
70
71
|
end
|
71
72
|
end
|
73
|
+
|
74
|
+
describe '#drag_to' do
|
75
|
+
it "should drag and drop an object" do
|
76
|
+
draggable = @driver.find('//div[@id="drag"]').first
|
77
|
+
droppable = @driver.find('//div[@id="drop"]').first
|
78
|
+
draggable.drag_to(droppable)
|
79
|
+
@driver.find('//div[contains(., "Dropped!")]').should_not be_nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "#evaluate_script" do
|
84
|
+
it "should return the value of the executed script" do
|
85
|
+
@driver.evaluate_script('1+1').should == 2
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
72
89
|
end
|
data/spec/dsl_spec.rb
CHANGED
@@ -36,7 +36,7 @@ describe Capybara do
|
|
36
36
|
Capybara.current_driver.should == :culerity
|
37
37
|
end
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
describe '#javascript_driver' do
|
41
41
|
it "should default to selenium" do
|
42
42
|
Capybara.javascript_driver.should == :selenium
|
@@ -98,7 +98,7 @@ describe Capybara do
|
|
98
98
|
Capybara.current_session.app.should == Capybara.app
|
99
99
|
end
|
100
100
|
end
|
101
|
-
|
101
|
+
|
102
102
|
describe '.reset_sessions!' do
|
103
103
|
it "should clear any persisted sessions" do
|
104
104
|
object_id = Capybara.current_session.object_id
|
@@ -114,6 +114,7 @@ describe Capybara do
|
|
114
114
|
end
|
115
115
|
|
116
116
|
it_should_behave_like "session"
|
117
|
+
it_should_behave_like "session without javascript support"
|
117
118
|
|
118
119
|
it "should be possible to include it in another class" do
|
119
120
|
klass = Class.new do
|
@@ -124,7 +125,7 @@ describe Capybara do
|
|
124
125
|
foo.click_link('ullamco')
|
125
126
|
foo.body.should include('Another World')
|
126
127
|
end
|
127
|
-
|
128
|
+
|
128
129
|
it "should provide a 'page' shortcut for more expressive tests" do
|
129
130
|
klass = Class.new do
|
130
131
|
include Capybara
|