yannp-capybara 0.3.9
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +112 -0
- data/README.rdoc +442 -0
- data/lib/capybara/cucumber.rb +32 -0
- data/lib/capybara/driver/base.rb +56 -0
- data/lib/capybara/driver/celerity_driver.rb +154 -0
- data/lib/capybara/driver/culerity_driver.rb +26 -0
- data/lib/capybara/driver/node.rb +66 -0
- data/lib/capybara/driver/rack_test_driver.rb +279 -0
- data/lib/capybara/driver/selenium_driver.rb +157 -0
- data/lib/capybara/dsl.rb +98 -0
- data/lib/capybara/node/actions.rb +156 -0
- data/lib/capybara/node/finders.rb +155 -0
- data/lib/capybara/node/matchers.rb +97 -0
- data/lib/capybara/node.rb +209 -0
- data/lib/capybara/rails.rb +17 -0
- data/lib/capybara/server.rb +101 -0
- data/lib/capybara/session.rb +278 -0
- data/lib/capybara/spec/driver.rb +190 -0
- data/lib/capybara/spec/fixtures/capybara.jpg +0 -0
- data/lib/capybara/spec/fixtures/test_file.txt +1 -0
- data/lib/capybara/spec/public/jquery-ui.js +35 -0
- data/lib/capybara/spec/public/jquery.js +19 -0
- data/lib/capybara/spec/public/test.js +33 -0
- data/lib/capybara/spec/session/all_spec.rb +73 -0
- data/lib/capybara/spec/session/attach_file_spec.rb +64 -0
- data/lib/capybara/spec/session/check_spec.rb +67 -0
- data/lib/capybara/spec/session/choose_spec.rb +26 -0
- data/lib/capybara/spec/session/click_button_spec.rb +243 -0
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +30 -0
- data/lib/capybara/spec/session/click_link_spec.rb +108 -0
- data/lib/capybara/spec/session/current_url_spec.rb +15 -0
- data/lib/capybara/spec/session/fill_in_spec.rb +119 -0
- data/lib/capybara/spec/session/find_button_spec.rb +18 -0
- data/lib/capybara/spec/session/find_by_id_spec.rb +18 -0
- data/lib/capybara/spec/session/find_field_spec.rb +26 -0
- data/lib/capybara/spec/session/find_link_spec.rb +19 -0
- data/lib/capybara/spec/session/find_spec.rb +91 -0
- data/lib/capybara/spec/session/has_button_spec.rb +32 -0
- data/lib/capybara/spec/session/has_content_spec.rb +106 -0
- data/lib/capybara/spec/session/has_css_spec.rb +107 -0
- data/lib/capybara/spec/session/has_field_spec.rb +96 -0
- data/lib/capybara/spec/session/has_link_spec.rb +33 -0
- data/lib/capybara/spec/session/has_select_spec.rb +89 -0
- data/lib/capybara/spec/session/has_table_spec.rb +96 -0
- data/lib/capybara/spec/session/has_xpath_spec.rb +123 -0
- data/lib/capybara/spec/session/headers.rb +19 -0
- data/lib/capybara/spec/session/javascript.rb +232 -0
- data/lib/capybara/spec/session/response_code.rb +19 -0
- data/lib/capybara/spec/session/select_spec.rb +91 -0
- data/lib/capybara/spec/session/uncheck_spec.rb +21 -0
- data/lib/capybara/spec/session/unselect_spec.rb +54 -0
- data/lib/capybara/spec/session/within_spec.rb +153 -0
- data/lib/capybara/spec/session.rb +78 -0
- data/lib/capybara/spec/test_app.rb +94 -0
- data/lib/capybara/spec/views/buttons.erb +4 -0
- data/lib/capybara/spec/views/fieldsets.erb +29 -0
- data/lib/capybara/spec/views/form.erb +245 -0
- data/lib/capybara/spec/views/frame_one.erb +8 -0
- data/lib/capybara/spec/views/frame_two.erb +8 -0
- data/lib/capybara/spec/views/postback.erb +13 -0
- data/lib/capybara/spec/views/tables.erb +122 -0
- data/lib/capybara/spec/views/with_html.erb +43 -0
- data/lib/capybara/spec/views/with_js.erb +39 -0
- data/lib/capybara/spec/views/with_scope.erb +36 -0
- data/lib/capybara/spec/views/with_simple_html.erb +1 -0
- data/lib/capybara/spec/views/within_frames.erb +10 -0
- data/lib/capybara/util/save_and_open_page.rb +36 -0
- data/lib/capybara/util/timeout.rb +27 -0
- data/lib/capybara/version.rb +3 -0
- data/lib/capybara/xpath.rb +182 -0
- data/lib/capybara.rb +73 -0
- data/spec/capybara_spec.rb +18 -0
- data/spec/driver/celerity_driver_spec.rb +17 -0
- data/spec/driver/culerity_driver_spec.rb +13 -0
- data/spec/driver/rack_test_driver_spec.rb +19 -0
- data/spec/driver/remote_culerity_driver_spec.rb +24 -0
- data/spec/driver/remote_selenium_driver_spec.rb +19 -0
- data/spec/driver/selenium_driver_spec.rb +13 -0
- data/spec/dsl_spec.rb +140 -0
- data/spec/save_and_open_page_spec.rb +83 -0
- data/spec/server_spec.rb +53 -0
- data/spec/session/celerity_session_spec.rb +28 -0
- data/spec/session/culerity_session_spec.rb +26 -0
- data/spec/session/rack_test_session_spec.rb +34 -0
- data/spec/session/selenium_session_spec.rb +26 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/timeout_spec.rb +28 -0
- data/spec/xpath_spec.rb +173 -0
- metadata +314 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
<form action="/form" method="post">
|
2
|
+
<table id="agent_table">
|
3
|
+
<caption>Agent</caption>
|
4
|
+
|
5
|
+
<tr>
|
6
|
+
<td>
|
7
|
+
<label for="form_agent_name">Name</label>
|
8
|
+
</td>
|
9
|
+
<td>
|
10
|
+
<input type="text" name="form[agent_name]" value="James" id="form_agent_name"/>
|
11
|
+
</td>
|
12
|
+
</tr>
|
13
|
+
|
14
|
+
<tr>
|
15
|
+
<td colspan="2">
|
16
|
+
<input type="submit" value="Create"/>
|
17
|
+
</td>
|
18
|
+
</tr>
|
19
|
+
</table>
|
20
|
+
</form>
|
21
|
+
|
22
|
+
<form action="/form" method="post">
|
23
|
+
<table id="girl_table">
|
24
|
+
<caption>Girl</caption>
|
25
|
+
|
26
|
+
<tr>
|
27
|
+
<td>
|
28
|
+
<label for="form_girl_name">Name</label>
|
29
|
+
</td>
|
30
|
+
<td>
|
31
|
+
<input type="text" name="form[girl_name]" value="Vesper" id="form_girl_name"/>
|
32
|
+
</td>
|
33
|
+
</tr>
|
34
|
+
|
35
|
+
<tr>
|
36
|
+
<td colspan="2">
|
37
|
+
<input type="submit" value="Create"/>
|
38
|
+
</td>
|
39
|
+
</tr>
|
40
|
+
</table>
|
41
|
+
</form>
|
42
|
+
|
43
|
+
<form action="/form" method="post">
|
44
|
+
<table id="villain_table">
|
45
|
+
<caption>Villain</caption>
|
46
|
+
|
47
|
+
<tr>
|
48
|
+
<td>
|
49
|
+
<label for="form_villain_name">Name</label>
|
50
|
+
</td>
|
51
|
+
<td>
|
52
|
+
<input type="text" name="form[villain_name]" value="Ernst" id="form_villain_name"/>
|
53
|
+
</td>
|
54
|
+
</tr>
|
55
|
+
|
56
|
+
<tr>
|
57
|
+
<td colspan="2">
|
58
|
+
<input type="submit" value="Create"/>
|
59
|
+
</td>
|
60
|
+
</tr>
|
61
|
+
</table>
|
62
|
+
</form>
|
63
|
+
|
64
|
+
<table>
|
65
|
+
<caption>Ransom</caption>
|
66
|
+
|
67
|
+
<thead>
|
68
|
+
<tr>
|
69
|
+
<th>Year</th>
|
70
|
+
<th>Governmental</th>
|
71
|
+
<th>Private</th>
|
72
|
+
</tr>
|
73
|
+
</thead>
|
74
|
+
|
75
|
+
<tbody>
|
76
|
+
<tr>
|
77
|
+
<th scope="row">2007</th>
|
78
|
+
<td>$300</td>
|
79
|
+
<td>$100</td>
|
80
|
+
</tr>
|
81
|
+
<tr>
|
82
|
+
<th scope="row">2008</th>
|
83
|
+
<td>$123</td>
|
84
|
+
<td>$897</td>
|
85
|
+
</tr>
|
86
|
+
<tr>
|
87
|
+
<th scope="row">2009</th>
|
88
|
+
<td>$543</td>
|
89
|
+
<td>$99</td>
|
90
|
+
</tr>
|
91
|
+
</tbody>
|
92
|
+
</table>
|
93
|
+
|
94
|
+
<table>
|
95
|
+
<caption>Deaths</caption>
|
96
|
+
|
97
|
+
<thead>
|
98
|
+
<tr>
|
99
|
+
<th>Year</th>
|
100
|
+
<th>Sharks with lasers</th>
|
101
|
+
<th>Flaming volcano</th>
|
102
|
+
</tr>
|
103
|
+
</thead>
|
104
|
+
|
105
|
+
<tbody>
|
106
|
+
<tr>
|
107
|
+
<th scope="row">2007</th>
|
108
|
+
<td>66</td>
|
109
|
+
<td>7</td>
|
110
|
+
</tr>
|
111
|
+
<tr>
|
112
|
+
<th scope="row">2008</th>
|
113
|
+
<td>123</td>
|
114
|
+
<td>12</td>
|
115
|
+
</tr>
|
116
|
+
<tr>
|
117
|
+
<th scope="row">2009</th>
|
118
|
+
<td>913</td>
|
119
|
+
<td>13</td>
|
120
|
+
</tr>
|
121
|
+
</tbody>
|
122
|
+
</table>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
<h1>This is a test</h1>
|
2
|
+
|
3
|
+
<p id="first">
|
4
|
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
|
5
|
+
tempor incididunt ut <a href="/with_simple_html" title="awesome title" class="simple">labore</a>
|
6
|
+
et dolore magna aliqua. Ut enim ad minim veniam,
|
7
|
+
quis nostrud exercitation <a href="/foo" id="foo">ullamco</a> laboris nisi
|
8
|
+
ut aliquip ex ea commodo consequat.
|
9
|
+
<a href="/with_simple_html"><img src="http://www.foobar.sun/dummy_image.jpg" width="20" height="20" alt="awesome image" /></a>
|
10
|
+
</p>
|
11
|
+
|
12
|
+
<p id="second">
|
13
|
+
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
|
14
|
+
dolore eu fugiat <a href="/redirect" id="red">Redirect</a> pariatur. Excepteur sint occaecat cupidatat non proident,
|
15
|
+
sunt in culpa qui officia
|
16
|
+
text with
|
17
|
+
whitespace
|
18
|
+
id est laborum.
|
19
|
+
</p>
|
20
|
+
|
21
|
+
<p>
|
22
|
+
<input type="text" id="test_field" value="monkey"/>
|
23
|
+
<textarea>banana</textarea>
|
24
|
+
<a href="/redirect_back">BackToMyself</a>
|
25
|
+
<a title="twas a fine link" href="/redirect">A link came first</a>
|
26
|
+
<a title="a fine link" href="/with_simple_html">A link</a>
|
27
|
+
<a title="a fine link with data method" data-method="delete" href="/delete">A link with data-method</a>
|
28
|
+
<a>No Href</a>
|
29
|
+
<a href="">Blank Href</a>
|
30
|
+
<a href="#">Blank Anchor</a>
|
31
|
+
<a href="#anchor">Anchor</a>
|
32
|
+
<a href="/with_simple_html#anchor">Anchor on different page</a>
|
33
|
+
<a href="/with_html#anchor">Anchor on same page</a>
|
34
|
+
<input type="text" value="" id="test_field">
|
35
|
+
<input type="text" checked="checked" id="checked_field">
|
36
|
+
<a href="/redirect"><img src="http://www.foobar.sun/dummy_image.jpg" width="20" height="20" alt="very fine image" /></a>
|
37
|
+
<a href="/with_simple_html"><img src="http://www.foobar.sun/dummy_image.jpg" width="20" height="20" alt="fine image" /></a>
|
38
|
+
</p>
|
39
|
+
|
40
|
+
<div id="hidden" style="display: none;">
|
41
|
+
<div id="hidden_via_ancestor">Inside element with hidden ancestor</div>
|
42
|
+
<a href="/with_simple_html" title="awesome title" class="simple">hidden link</a>
|
43
|
+
</div>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
2
|
+
<head>
|
3
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
|
4
|
+
<title>with_js</title>
|
5
|
+
<script src="/jquery.js" type="text/javascript" charset="utf-8"></script>
|
6
|
+
<script src="/jquery-ui.js" type="text/javascript" charset="utf-8"></script>
|
7
|
+
<script src="/test.js" type="text/javascript" charset="utf-8"></script>
|
8
|
+
</head>
|
9
|
+
|
10
|
+
<body id="with_js">
|
11
|
+
<h1>FooBar</h1>
|
12
|
+
|
13
|
+
<p id="change">This is text</p>
|
14
|
+
<div id="drag">
|
15
|
+
<p>This is a draggable element.</p>
|
16
|
+
</div>
|
17
|
+
<div id="drop">
|
18
|
+
<p>It should be dropped here.</p>
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<p><a href="#" id="clickable">Click me</a></p>
|
22
|
+
|
23
|
+
<p>
|
24
|
+
<select id="waiter">
|
25
|
+
<option>Foo</option>
|
26
|
+
<option>My Waiting Option</option>
|
27
|
+
</select>
|
28
|
+
</p>
|
29
|
+
|
30
|
+
<p>
|
31
|
+
<input type="text" name="with_focus_event" value="" id="with_focus_event"/>
|
32
|
+
</p>
|
33
|
+
|
34
|
+
<p>
|
35
|
+
<input type="checkbox" id="checkbox_with_event"/>
|
36
|
+
</p>
|
37
|
+
</body>
|
38
|
+
</html>
|
39
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<h1>This page is used for testing various scopes</h1>
|
2
|
+
|
3
|
+
<p id="for_foo">
|
4
|
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
|
5
|
+
incididunt ut labore et dolore magna aliqua. Ut enim ad minim venia.
|
6
|
+
<a href="/redirect">Go</a>
|
7
|
+
</p>
|
8
|
+
|
9
|
+
<div id="for_bar">
|
10
|
+
<ul>
|
11
|
+
<li>With Simple HTML: <a href="/with_simple_html">Go</a>
|
12
|
+
<form action="/redirect" method="post" accept-charset="utf-8">
|
13
|
+
<p>
|
14
|
+
<label for="simple_first_name">First Name</label>
|
15
|
+
<input type="text" name="first_name" value="John" id="simple_first_name"/>
|
16
|
+
</p>
|
17
|
+
<p><input type="submit" value="Go"/></p>
|
18
|
+
</form>
|
19
|
+
</li>
|
20
|
+
<li>Bar: <a href="/foo">Go</a>
|
21
|
+
<form action="/form" method="post" accept-charset="utf-8">
|
22
|
+
<p>
|
23
|
+
<label for="bar_first_name">First Name</label>
|
24
|
+
<input type="text" name="form[first_name]" value="Peter" id="bar_first_name"/>
|
25
|
+
</p>
|
26
|
+
<p><input type="submit" value="Go"/></p>
|
27
|
+
</form>
|
28
|
+
</li>
|
29
|
+
</ul>
|
30
|
+
</div>
|
31
|
+
|
32
|
+
<div id="another_foo">
|
33
|
+
<ul>
|
34
|
+
<li>With Simple HTML: <a href="/">Go</a>
|
35
|
+
</ul>
|
36
|
+
</div>
|
@@ -0,0 +1 @@
|
|
1
|
+
Bar
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Capybara
|
2
|
+
class << self
|
3
|
+
def save_and_open_page(html)
|
4
|
+
name = File.join(*[Capybara.save_and_open_page_path, "capybara-#{Time.new.strftime("%Y%m%d%H%M%S")}.html"].compact)
|
5
|
+
|
6
|
+
unless Capybara.save_and_open_page_path.nil? || File.directory?(Capybara.save_and_open_page_path )
|
7
|
+
FileUtils.mkdir_p(Capybara.save_and_open_page_path)
|
8
|
+
end
|
9
|
+
FileUtils.touch(name) unless File.exist?(name)
|
10
|
+
|
11
|
+
tempfile = File.new(name,'w')
|
12
|
+
tempfile.write(rewrite_css_and_image_references(html))
|
13
|
+
tempfile.close
|
14
|
+
|
15
|
+
open_in_browser(tempfile.path)
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def open_in_browser(path) # :nodoc
|
21
|
+
require "launchy"
|
22
|
+
Launchy::Browser.run(path)
|
23
|
+
rescue LoadError
|
24
|
+
warn "Sorry, you need to install launchy to open pages: `gem install launchy`"
|
25
|
+
end
|
26
|
+
|
27
|
+
def rewrite_css_and_image_references(response_html) # :nodoc:
|
28
|
+
return response_html unless Capybara.asset_root
|
29
|
+
directories = Dir.new(Capybara.asset_root).entries.inject([]) do |list, name|
|
30
|
+
list << name if File.directory?(name) and not name.to_s =~ /^\./
|
31
|
+
list
|
32
|
+
end
|
33
|
+
response_html.gsub(/("|')\/(#{directories.join('|')})/, '\1' + Capybara.asset_root.to_s + '/\2')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Capybara
|
2
|
+
class << self
|
3
|
+
|
4
|
+
##
|
5
|
+
# Provides timeout similar to standard library Timeout, but avoids threads
|
6
|
+
#
|
7
|
+
def timeout(seconds = 1, driver = nil, &block)
|
8
|
+
start_time = Time.now
|
9
|
+
|
10
|
+
result = nil
|
11
|
+
|
12
|
+
until result
|
13
|
+
return result if result = yield
|
14
|
+
|
15
|
+
delay = seconds - (Time.now - start_time)
|
16
|
+
if delay <= 0
|
17
|
+
raise TimeoutError
|
18
|
+
end
|
19
|
+
|
20
|
+
driver && driver.wait_until(delay)
|
21
|
+
|
22
|
+
sleep(0.05)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
module Capybara
|
2
|
+
|
3
|
+
##
|
4
|
+
#
|
5
|
+
# This is a class for generating XPath queries, use it like this:
|
6
|
+
#
|
7
|
+
# Xpath.text_field('foo').link('blah').to_s
|
8
|
+
#
|
9
|
+
# This will generate an XPath that matches either a text field or a link.
|
10
|
+
#
|
11
|
+
class XPath
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def escape(string)
|
15
|
+
if string.include?("'")
|
16
|
+
string = string.split("'", -1).map do |substr|
|
17
|
+
"'#{substr}'"
|
18
|
+
end.join(%q{,"'",})
|
19
|
+
"concat(#{string})"
|
20
|
+
else
|
21
|
+
"'#{string}'"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def wrap(path)
|
26
|
+
if path.is_a?(self)
|
27
|
+
path
|
28
|
+
else
|
29
|
+
new(path.to_s)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def respond_to?(method)
|
34
|
+
new.respond_to?(method)
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_missing(*args)
|
38
|
+
new.send(*args)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :paths
|
43
|
+
|
44
|
+
def initialize(*paths)
|
45
|
+
@paths = paths
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
@paths.join(' | ')
|
50
|
+
end
|
51
|
+
|
52
|
+
def append(path)
|
53
|
+
XPath.new(*[@paths, XPath.wrap(path).paths].flatten)
|
54
|
+
end
|
55
|
+
|
56
|
+
def prepend(path)
|
57
|
+
XPath.new(*[XPath.wrap(path).paths, @paths].flatten)
|
58
|
+
end
|
59
|
+
|
60
|
+
def from_css(css)
|
61
|
+
XPath.new(*[@paths, Nokogiri::CSS.xpath_for(css).map { |selector| '.' + selector }].flatten)
|
62
|
+
end
|
63
|
+
alias_method :for_css, :from_css
|
64
|
+
|
65
|
+
def field(locator, options={})
|
66
|
+
if options[:with]
|
67
|
+
fillable_field(locator, options)
|
68
|
+
else
|
69
|
+
xpath = fillable_field(locator)
|
70
|
+
xpath = xpath.input_field(:file, locator, options)
|
71
|
+
xpath = xpath.checkbox(locator, options)
|
72
|
+
xpath = xpath.radio_button(locator, options)
|
73
|
+
xpath.select(locator, options)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def fillable_field(locator, options={})
|
78
|
+
text_area(locator, options).text_field(locator, options)
|
79
|
+
end
|
80
|
+
|
81
|
+
def content(locator)
|
82
|
+
append("./descendant-or-self::*[contains(normalize-space(.),#{s(locator)})]")
|
83
|
+
end
|
84
|
+
|
85
|
+
def table(locator, options={})
|
86
|
+
conditions = ""
|
87
|
+
if options[:rows]
|
88
|
+
row_conditions = options[:rows].map do |row|
|
89
|
+
row = row.map { |column| "*[self::td or self::th][text()=#{s(column)}]" }.join(sibling)
|
90
|
+
"tr[./#{row}]"
|
91
|
+
end.join(sibling)
|
92
|
+
conditions << "[.//#{row_conditions}]"
|
93
|
+
end
|
94
|
+
append(".//table[@id=#{s(locator)} or contains(caption,#{s(locator)})]#{conditions}")
|
95
|
+
end
|
96
|
+
|
97
|
+
def fieldset(locator)
|
98
|
+
append(".//fieldset[@id=#{s(locator)} or contains(legend,#{s(locator)})]")
|
99
|
+
end
|
100
|
+
|
101
|
+
def link(locator)
|
102
|
+
xpath = append(".//a[@href][@id=#{s(locator)} or contains(.,#{s(locator)}) or contains(@title,#{s(locator)}) or img[contains(@alt,#{s(locator)})]]")
|
103
|
+
xpath.prepend(".//a[@href][text()=#{s(locator)} or @title=#{s(locator)} or img[@alt=#{s(locator)}]]")
|
104
|
+
end
|
105
|
+
|
106
|
+
def button(locator)
|
107
|
+
xpath = append(".//input[@type='submit' or @type='image' or @type='button'][@id=#{s(locator)} or contains(@value,#{s(locator)})]")
|
108
|
+
xpath = xpath.append(".//button[@id=#{s(locator)} or contains(@value,#{s(locator)}) or contains(.,#{s(locator)})]")
|
109
|
+
xpath = xpath.prepend(".//input[@type='submit' or @type='image' or @type='button'][@value=#{s(locator)}]")
|
110
|
+
xpath = xpath.prepend(".//input[@type='image'][@alt=#{s(locator)} or contains(@alt,#{s(locator)})]")
|
111
|
+
xpath = xpath.prepend(".//button[@value=#{s(locator)} or text()=#{s(locator)}]")
|
112
|
+
end
|
113
|
+
|
114
|
+
def text_field(locator, options={})
|
115
|
+
options = options.merge(:value => options[:with]) if options.has_key?(:with)
|
116
|
+
add_field(locator, ".//input[not(@type) or (@type!='radio' and @type!='checkbox' and @type!='hidden')]", options)
|
117
|
+
end
|
118
|
+
|
119
|
+
def text_area(locator, options={})
|
120
|
+
options = options.merge(:text => options[:with]) if options.has_key?(:with)
|
121
|
+
add_field(locator, ".//textarea", options)
|
122
|
+
end
|
123
|
+
|
124
|
+
def select(locator, options={})
|
125
|
+
add_field(locator, ".//select", options)
|
126
|
+
end
|
127
|
+
|
128
|
+
def checkbox(locator, options={})
|
129
|
+
input_field(:checkbox, locator, options)
|
130
|
+
end
|
131
|
+
|
132
|
+
def radio_button(locator, options={})
|
133
|
+
input_field(:radio, locator, options)
|
134
|
+
end
|
135
|
+
|
136
|
+
def file_field(locator, options={})
|
137
|
+
input_field(:file, locator, options)
|
138
|
+
end
|
139
|
+
|
140
|
+
protected
|
141
|
+
|
142
|
+
def input_field(type, locator, options={})
|
143
|
+
options = options.merge(:value => options[:with]) if options.has_key?(:with)
|
144
|
+
add_field(locator, ".//input[@type='#{type}']", options)
|
145
|
+
end
|
146
|
+
|
147
|
+
# place this between to nodes to indicate that they should be siblings
|
148
|
+
def sibling
|
149
|
+
'/following-sibling::*[1]/self::'
|
150
|
+
end
|
151
|
+
|
152
|
+
def add_field(locator, field, options={})
|
153
|
+
postfix = extract_postfix(options)
|
154
|
+
xpath = append("#{field}[@id=#{s(locator)}]#{postfix}")
|
155
|
+
xpath = xpath.append("#{field}[@name=#{s(locator)}]#{postfix}")
|
156
|
+
xpath = xpath.append("#{field}[@id=//label[contains(.,#{s(locator)})]/@for]#{postfix}")
|
157
|
+
# FIXME: Label should not be scoped to node, temporary workaround!!!
|
158
|
+
xpath = xpath.append(".//label[contains(.,#{s(locator)})]/#{field}#{postfix}")
|
159
|
+
xpath.prepend("#{field}[@id=//label[text()=#{s(locator)}]/@for]#{postfix}")
|
160
|
+
end
|
161
|
+
|
162
|
+
def extract_postfix(options)
|
163
|
+
options.inject("") do |postfix, (key, value)|
|
164
|
+
case key
|
165
|
+
when :value then postfix += "[@value=#{s(value)}]"
|
166
|
+
when :text then postfix += "[text()=#{s(value)}]"
|
167
|
+
when :checked then postfix += "[@checked]"
|
168
|
+
when :unchecked then postfix += "[not(@checked)]"
|
169
|
+
when :options then postfix += value.map { |o| "[.//option/text()=#{s(o)}]" }.join
|
170
|
+
when :selected then postfix += [value].flatten.map { |o| "[.//option[@selected]/text()=#{s(o)}]" }.join
|
171
|
+
end
|
172
|
+
postfix
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Sanitize a String for putting it into an xpath query
|
177
|
+
def s(string)
|
178
|
+
XPath.escape(string)
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
end
|
data/lib/capybara.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module Capybara
|
5
|
+
class CapybaraError < StandardError; end
|
6
|
+
class DriverNotFoundError < CapybaraError; end
|
7
|
+
class ElementNotFound < CapybaraError; end
|
8
|
+
class OptionNotFound < ElementNotFound; end
|
9
|
+
class UnselectNotAllowed < CapybaraError; end
|
10
|
+
class NotSupportedByDriverError < CapybaraError; end
|
11
|
+
class TimeoutError < CapybaraError; end
|
12
|
+
class LocateHiddenElementError < CapybaraError; end
|
13
|
+
class InfiniteRedirectError < TimeoutError; end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_accessor :asset_root, :app_host, :run_server, :default_host
|
17
|
+
attr_accessor :default_selector, :default_wait_time, :ignore_hidden_elements
|
18
|
+
attr_accessor :save_and_open_page_path
|
19
|
+
|
20
|
+
##
|
21
|
+
#
|
22
|
+
# Configure Capybara to suit your needs.
|
23
|
+
#
|
24
|
+
# Capybara.configure do |config|
|
25
|
+
# config.run_server = false
|
26
|
+
# config.app_host = 'http://www.google.com'
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# === Configurable options
|
30
|
+
#
|
31
|
+
# [asset_root = String] Where static assets are located, used by save_and_open_page
|
32
|
+
# [app_host = String] The default host to use when giving a relative URL to visit
|
33
|
+
# [run_server = Boolean] Whether to start a Rack server for the given Rack app (Default: true)
|
34
|
+
# [default_selector = :css/:xpath] Methods which take a selector use the given type by default (Default: CSS)
|
35
|
+
# [default_wait_time = Integer] The number of seconds to wait for asynchronous processes to finish (Default: 2)
|
36
|
+
# [ignore_hidden_elements = Boolean] Whether to ignore hidden elements on the page (Default: false)
|
37
|
+
#
|
38
|
+
# === DSL Options
|
39
|
+
#
|
40
|
+
# when using capybara/dsl, the following options are also available:
|
41
|
+
#
|
42
|
+
# [default_driver = Symbol] The name of the driver to use by default. (Default: :rack_test)
|
43
|
+
# [javascript_driver = Symbol] The name of a driver to use for JavaScript enabled tests. (Default: :selenium)
|
44
|
+
#
|
45
|
+
def configure
|
46
|
+
yield self
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
autoload :Server, 'capybara/server'
|
51
|
+
autoload :Session, 'capybara/session'
|
52
|
+
autoload :XPath, 'capybara/xpath'
|
53
|
+
autoload :Node, 'capybara/node'
|
54
|
+
autoload :Document, 'capybara/node'
|
55
|
+
autoload :Element, 'capybara/node'
|
56
|
+
autoload :VERSION, 'capybara/version'
|
57
|
+
|
58
|
+
module Driver
|
59
|
+
autoload :Base, 'capybara/driver/base'
|
60
|
+
autoload :Node, 'capybara/driver/node'
|
61
|
+
autoload :RackTest, 'capybara/driver/rack_test_driver'
|
62
|
+
autoload :Celerity, 'capybara/driver/celerity_driver'
|
63
|
+
autoload :Culerity, 'capybara/driver/culerity_driver'
|
64
|
+
autoload :Selenium, 'capybara/driver/selenium_driver'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
Capybara.configure do |config|
|
69
|
+
config.run_server = true
|
70
|
+
config.default_selector = :css
|
71
|
+
config.default_wait_time = 2
|
72
|
+
config.ignore_hidden_elements = false
|
73
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'capybara'
|
4
|
+
|
5
|
+
describe Capybara do
|
6
|
+
|
7
|
+
describe 'default_wait_time' do
|
8
|
+
after do
|
9
|
+
Capybara.default_wait_time = 2
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be changeable" do
|
13
|
+
Capybara.default_wait_time = 5
|
14
|
+
Capybara.default_wait_time.should == 5
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
if RUBY_PLATFORM =~ /java/
|
4
|
+
describe Capybara::Driver::Celerity do
|
5
|
+
before(:all) do
|
6
|
+
@driver = Capybara::Driver::Celerity.new(TestApp)
|
7
|
+
end
|
8
|
+
|
9
|
+
it_should_behave_like "driver"
|
10
|
+
it_should_behave_like "driver with javascript support"
|
11
|
+
it_should_behave_like "driver with header support"
|
12
|
+
it_should_behave_like "driver with status code support"
|
13
|
+
it_should_behave_like "driver with cookies support"
|
14
|
+
end
|
15
|
+
else
|
16
|
+
puts "#{File.basename(__FILE__)} requires JRuby; skipping.."
|
17
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe Capybara::Driver::Culerity do
|
4
|
+
before(:all) do
|
5
|
+
@driver = Capybara::Driver::Culerity.new(TestApp)
|
6
|
+
end
|
7
|
+
|
8
|
+
it_should_behave_like "driver"
|
9
|
+
it_should_behave_like "driver with javascript support"
|
10
|
+
it_should_behave_like "driver with header support"
|
11
|
+
it_should_behave_like "driver with status code support"
|
12
|
+
it_should_behave_like "driver with cookies support"
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe Capybara::Driver::RackTest do
|
4
|
+
before do
|
5
|
+
@driver = Capybara::Driver::RackTest.new(TestApp)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should throw an error when no rack app is given" do
|
9
|
+
running do
|
10
|
+
Capybara::Driver::RackTest.new(nil)
|
11
|
+
end.should raise_error(ArgumentError)
|
12
|
+
end
|
13
|
+
|
14
|
+
it_should_behave_like "driver"
|
15
|
+
it_should_behave_like "driver with header support"
|
16
|
+
it_should_behave_like "driver with status code support"
|
17
|
+
it_should_behave_like "driver with cookies support"
|
18
|
+
it_should_behave_like "driver with infinite redirect detection"
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe Capybara::Driver::Culerity do
|
4
|
+
before(:all) do
|
5
|
+
Capybara.app_host = "http://capybara-testapp.heroku.com"
|
6
|
+
Capybara.run_server = false
|
7
|
+
@driver = Capybara::Driver::Culerity.new(TestApp)
|
8
|
+
end
|
9
|
+
|
10
|
+
after(:all) do
|
11
|
+
Capybara.app_host = nil
|
12
|
+
Capybara.run_server = true
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should navigate to a fully qualified remote page" do
|
16
|
+
@driver.visit('http://capybara-testapp.heroku.com/foo')
|
17
|
+
@driver.body.should include('Another World')
|
18
|
+
end
|
19
|
+
|
20
|
+
it_should_behave_like "driver"
|
21
|
+
it_should_behave_like "driver with javascript support"
|
22
|
+
it_should_behave_like "driver with header support"
|
23
|
+
it_should_behave_like "driver with status code support"
|
24
|
+
end
|