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