xpath 1.0.0.beta1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/{README.rdoc → README.md} +59 -36
- data/lib/xpath/dsl.rb +8 -4
- data/lib/xpath/html.rb +29 -7
- data/lib/xpath/renderer.rb +10 -0
- data/lib/xpath/version.rb +1 -1
- data/spec/fixtures/form.html +15 -2
- data/spec/fixtures/simple.html +7 -0
- data/spec/fixtures/stuff.html +11 -0
- data/spec/html_spec.rb +50 -17
- data/spec/xpath_spec.rb +10 -0
- metadata +9 -9
data/{README.rdoc → README.md}
RENAMED
@@ -1,53 +1,65 @@
|
|
1
|
-
|
1
|
+
# XPath
|
2
2
|
|
3
3
|
XPath is a Ruby DSL around a subset of XPath 1.0. Its primary purpose is to
|
4
4
|
facilitate writing complex XPath queries from Ruby code.
|
5
5
|
|
6
|
-
|
6
|
+
[![Build Status](https://secure.travis-ci.org/jnicklas/xpath.png?branch=master)](http://travis-ci.org/jnicklas/xpath)
|
7
7
|
|
8
|
-
|
8
|
+
## Generating expressions
|
9
9
|
|
10
|
-
To create quick, one-off expressions,
|
10
|
+
To create quick, one-off expressions, `XPath.generate` can be used:
|
11
11
|
|
12
|
-
|
12
|
+
``` ruby
|
13
|
+
XPath.generate { |x| x.descendant(:ul)[x.attr(:id) == 'foo'] }
|
14
|
+
```
|
13
15
|
|
14
|
-
You can also call expression methods directly on the XPath module:
|
16
|
+
You can also call expression methods directly on the `XPath` module:
|
15
17
|
|
16
|
-
|
18
|
+
``` ruby
|
19
|
+
XPath.descendant(:ul)[XPath.attr(:id) == 'foo'] }
|
20
|
+
```
|
17
21
|
|
18
22
|
However for more complex expressions, it is probably more convenient to include
|
19
|
-
the XPath module into your own class or module:
|
23
|
+
the `XPath` module into your own class or module:
|
20
24
|
|
21
|
-
|
22
|
-
|
25
|
+
``` ruby
|
26
|
+
module MyXPaths
|
27
|
+
include XPath
|
23
28
|
|
24
|
-
|
25
|
-
|
26
|
-
|
29
|
+
def foo_ul
|
30
|
+
descendant(:ul)[attr(:id) == 'foo']
|
31
|
+
end
|
27
32
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
33
|
+
def password_field(id)
|
34
|
+
descendant(:input)[attr(:type) == 'password'][attr(:id) == id]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
```
|
32
38
|
|
33
|
-
Both ways return an
|
34
|
-
|
35
|
-
|
39
|
+
Both ways return an
|
40
|
+
[`XPath::Expression`](http://rdoc.info/github/jnicklas/xpath/XPath/Expression)
|
41
|
+
instance, which can be further modified. To convert the expression to a
|
42
|
+
string, just call `#to_s` on it. All available expressions are defined in
|
43
|
+
[`XPath::DSL`](http://rdoc.info/github/jnicklas/xpath/XPath/DSL).
|
36
44
|
|
37
|
-
|
45
|
+
## String, Hashes and Symbols
|
38
46
|
|
39
47
|
When you send a string as an argument to any XPath function, XPath assumes this
|
40
48
|
to be a string literal. On the other hand if you send in Symbol, XPath assumes
|
41
49
|
this to be an XPath literal. Thus the following two statements are not
|
42
50
|
equivalent:
|
43
51
|
|
44
|
-
|
45
|
-
|
52
|
+
``` ruby
|
53
|
+
XPath.descendant(:p)[XPath.attr(:id) == 'foo']
|
54
|
+
XPath.descendant(:p)[XPath.attr(:id) == :foo]
|
55
|
+
```
|
46
56
|
|
47
57
|
These are the XPath expressions that these would be translated to:
|
48
58
|
|
49
|
-
|
50
|
-
|
59
|
+
```
|
60
|
+
.//p[@id = 'foo']
|
61
|
+
.//p[@id = foo]
|
62
|
+
```
|
51
63
|
|
52
64
|
The second expression would match any p tag whose id attribute matches a 'foo'
|
53
65
|
tag it contains. Most likely this is not what you want.
|
@@ -55,7 +67,9 @@ tag it contains. Most likely this is not what you want.
|
|
55
67
|
In fact anything other than a String is treated as a literal. Thus the
|
56
68
|
following works as expected:
|
57
69
|
|
58
|
-
|
70
|
+
``` ruby
|
71
|
+
XPath.descendant(:p)[1]
|
72
|
+
```
|
59
73
|
|
60
74
|
Keep in mind that XPath is 1-indexed and not 0-indexed like most other
|
61
75
|
programming languages, including Ruby.
|
@@ -63,32 +77,41 @@ programming languages, including Ruby.
|
|
63
77
|
Hashes are automatically converted to equality expressions, so the above
|
64
78
|
example could be written as:
|
65
79
|
|
66
|
-
|
80
|
+
``` ruby
|
81
|
+
XPath.descendant(:p)[:@id => 'foo']
|
82
|
+
```
|
67
83
|
|
68
84
|
Which would generate the same expression:
|
69
85
|
|
70
|
-
|
86
|
+
```
|
87
|
+
.//p[@id = 'foo']
|
88
|
+
```
|
71
89
|
|
72
90
|
Note that the same rules apply here, both the keys and values in the hash are
|
73
91
|
treated the same way as any other expression in XPath. Thus the following are
|
74
92
|
not equivalent:
|
75
93
|
|
76
|
-
|
77
|
-
|
78
|
-
|
94
|
+
``` ruby
|
95
|
+
XPath.descendant(:p)[:@id => 'foo'] # => .//p[@id = 'foo']
|
96
|
+
XPath.descendant(:p)[:id => 'foo'] # => .//p[id = 'foo']
|
97
|
+
XPath.descendant(:p)['id' => 'foo'] # => .//p['id' = 'foo']
|
98
|
+
```
|
79
99
|
|
80
|
-
|
100
|
+
## HTML
|
81
101
|
|
82
102
|
XPath comes with a set of premade XPaths for use with HTML documents.
|
83
103
|
|
84
104
|
You can generate these like this:
|
85
105
|
|
86
|
-
|
87
|
-
|
106
|
+
``` ruby
|
107
|
+
XPath::HTML.link('Home')
|
108
|
+
XPath::HTML.field('Name')
|
109
|
+
```
|
88
110
|
|
89
|
-
See XPath::HTML for all
|
111
|
+
See [`XPath::HTML`](http://rdoc.info/github/jnicklas/xpath/XPath/HTML) for all
|
112
|
+
available matchers.
|
90
113
|
|
91
|
-
|
114
|
+
## License
|
92
115
|
|
93
116
|
(The MIT License)
|
94
117
|
|
data/lib/xpath/dsl.rb
CHANGED
@@ -21,6 +21,14 @@ module XPath
|
|
21
21
|
Expression.new(:axis, current, name, tag_name)
|
22
22
|
end
|
23
23
|
|
24
|
+
def next_sibling(*expressions)
|
25
|
+
Expression.new(:next_sibling, current, expressions)
|
26
|
+
end
|
27
|
+
|
28
|
+
def previous_sibling(*expressions)
|
29
|
+
Expression.new(:previous_sibling, current, expressions)
|
30
|
+
end
|
31
|
+
|
24
32
|
def anywhere(expression)
|
25
33
|
Expression.new(:anywhere, expression)
|
26
34
|
end
|
@@ -58,10 +66,6 @@ module XPath
|
|
58
66
|
end
|
59
67
|
alias_method :[], :where
|
60
68
|
|
61
|
-
def next_sibling(*expressions)
|
62
|
-
Expression.new(:next_sibling, current, expressions)
|
63
|
-
end
|
64
|
-
|
65
69
|
def one_of(*expressions)
|
66
70
|
Expression.new(:one_of, current, expressions)
|
67
71
|
end
|
data/lib/xpath/html.rb
CHANGED
@@ -9,6 +9,7 @@ module XPath
|
|
9
9
|
# Text, id, title, or image alt attribute of the link
|
10
10
|
#
|
11
11
|
def link(locator)
|
12
|
+
locator = locator.to_s
|
12
13
|
link = descendant(:a)[attr(:href)]
|
13
14
|
link[attr(:id).equals(locator) | string.n.contains(locator) | attr(:title).contains(locator) | descendant(:img)[attr(:alt).contains(locator)]]
|
14
15
|
end
|
@@ -19,9 +20,10 @@ module XPath
|
|
19
20
|
# Value, title, id, or image alt attribute of the button
|
20
21
|
#
|
21
22
|
def button(locator)
|
22
|
-
|
23
|
-
button
|
24
|
-
button += descendant(:
|
23
|
+
locator = locator.to_s
|
24
|
+
button = descendant(:input)[~attr(:disabled)][attr(:type).one_of('submit', 'reset', 'image', 'button')][attr(:id).equals(locator) | attr(:value).contains(locator) | attr(:title).contains(locator)]
|
25
|
+
button += descendant(:button)[~attr(:disabled)][attr(:id).equals(locator) | attr(:value).contains(locator) | string.n.contains(locator) | attr(:title).contains(locator)]
|
26
|
+
button += descendant(:input)[~attr(:disabled)][attr(:type).equals('image')][attr(:alt).contains(locator)]
|
25
27
|
end
|
26
28
|
|
27
29
|
|
@@ -41,6 +43,7 @@ module XPath
|
|
41
43
|
# Legend or id of the fieldset
|
42
44
|
#
|
43
45
|
def fieldset(locator)
|
46
|
+
locator = locator.to_s
|
44
47
|
descendant(:fieldset)[attr(:id).equals(locator) | child(:legend)[string.n.contains(locator)]]
|
45
48
|
end
|
46
49
|
|
@@ -52,6 +55,7 @@ module XPath
|
|
52
55
|
# Label, id, or name of field to match
|
53
56
|
#
|
54
57
|
def field(locator)
|
58
|
+
locator = locator.to_s
|
55
59
|
xpath = descendant(:input, :textarea, :select)[~attr(:type).one_of('submit', 'image', 'hidden')]
|
56
60
|
xpath = locate_field(xpath, locator)
|
57
61
|
xpath
|
@@ -66,6 +70,7 @@ module XPath
|
|
66
70
|
# Label, id, or name of field to match
|
67
71
|
#
|
68
72
|
def fillable_field(locator)
|
73
|
+
locator = locator.to_s
|
69
74
|
xpath = descendant(:input, :textarea)[~attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')]
|
70
75
|
xpath = locate_field(xpath, locator)
|
71
76
|
xpath
|
@@ -78,6 +83,7 @@ module XPath
|
|
78
83
|
# Label, id, or name of the field to match
|
79
84
|
#
|
80
85
|
def select(locator)
|
86
|
+
locator = locator.to_s
|
81
87
|
locate_field(descendant(:select), locator)
|
82
88
|
end
|
83
89
|
|
@@ -88,6 +94,7 @@ module XPath
|
|
88
94
|
# Label, id, or name of the checkbox to match
|
89
95
|
#
|
90
96
|
def checkbox(locator)
|
97
|
+
locator = locator.to_s
|
91
98
|
locate_field(descendant(:input)[attr(:type).equals('checkbox')], locator)
|
92
99
|
end
|
93
100
|
|
@@ -98,6 +105,7 @@ module XPath
|
|
98
105
|
# Label, id, or name of the radio button to match
|
99
106
|
#
|
100
107
|
def radio_button(locator)
|
108
|
+
locator = locator.to_s
|
101
109
|
locate_field(descendant(:input)[attr(:type).equals('radio')], locator)
|
102
110
|
end
|
103
111
|
|
@@ -108,6 +116,7 @@ module XPath
|
|
108
116
|
# Label, id, or name of the file field to match
|
109
117
|
#
|
110
118
|
def file_field(locator)
|
119
|
+
locator = locator.to_s
|
111
120
|
locate_field(descendant(:input)[attr(:type).equals('file')], locator)
|
112
121
|
end
|
113
122
|
|
@@ -117,8 +126,9 @@ module XPath
|
|
117
126
|
# @param [String] name
|
118
127
|
# Label for the option group
|
119
128
|
#
|
120
|
-
def optgroup(
|
121
|
-
|
129
|
+
def optgroup(locator)
|
130
|
+
locator = locator.to_s
|
131
|
+
descendant(:optgroup)[attr(:label).contains(locator)]
|
122
132
|
end
|
123
133
|
|
124
134
|
|
@@ -127,8 +137,9 @@ module XPath
|
|
127
137
|
# @param [String] name
|
128
138
|
# Visible text of the option
|
129
139
|
#
|
130
|
-
def option(
|
131
|
-
|
140
|
+
def option(locator)
|
141
|
+
locator = locator.to_s
|
142
|
+
descendant(:option)[string.n.equals(locator)]
|
132
143
|
end
|
133
144
|
|
134
145
|
|
@@ -140,14 +151,25 @@ module XPath
|
|
140
151
|
# Content of each cell in each row to match
|
141
152
|
#
|
142
153
|
def table(locator)
|
154
|
+
locator = locator.to_s
|
143
155
|
descendant(:table)[attr(:id).equals(locator) | descendant(:caption).contains(locator)]
|
144
156
|
end
|
145
157
|
|
158
|
+
# Match any 'dd' element.
|
159
|
+
#
|
160
|
+
# @param [String] locator
|
161
|
+
# Id of the 'dd' element or text from preciding 'dt' element content
|
162
|
+
def definition_description(locator)
|
163
|
+
locator = locator.to_s
|
164
|
+
descendant(:dd)[attr(:id).equals(locator) | previous_sibling(:dt)[string.n.equals(locator)] ]
|
165
|
+
end
|
166
|
+
|
146
167
|
protected
|
147
168
|
|
148
169
|
def locate_field(xpath, locator)
|
149
170
|
locate_field = xpath[attr(:id).equals(locator) | attr(:name).equals(locator) | attr(:placeholder).equals(locator) | attr(:id).equals(anywhere(:label)[string.n.contains(locator)].attr(:for))]
|
150
171
|
locate_field += descendant(:label)[string.n.contains(locator)].descendant(xpath)
|
172
|
+
locate_field[~attr(:disabled)]
|
151
173
|
end
|
152
174
|
end
|
153
175
|
end
|
data/lib/xpath/renderer.rb
CHANGED
@@ -135,6 +135,16 @@ module XPath
|
|
135
135
|
end
|
136
136
|
end
|
137
137
|
|
138
|
+
def previous_sibling(current, element_names)
|
139
|
+
if element_names.length == 1
|
140
|
+
"#{current}/preceding-sibling::*[1]/self::#{element_names.first}"
|
141
|
+
elsif element_names.length > 1
|
142
|
+
"#{current}/preceding-sibling::*[1]/self::*[#{element_names.map { |e| "self::#{e}" }.join(" | ")}]"
|
143
|
+
else
|
144
|
+
"#{current}/preceding-sibling::*[1]/self::*"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
138
148
|
def inverse(current)
|
139
149
|
"not(#{current})"
|
140
150
|
end
|
data/lib/xpath/version.rb
CHANGED
data/spec/fixtures/form.html
CHANGED
@@ -28,7 +28,7 @@
|
|
28
28
|
<input type="submit" title="My submit title" value="submit-with-title" data="title-submit">
|
29
29
|
<input type="submit" title="Exact submit title" value="exact title submit" data="exact-title-submit">
|
30
30
|
<input type="submit" title="Not Exact submit title" value="exact title submit" data="not-exact-title-submit">
|
31
|
-
|
31
|
+
|
32
32
|
<input type="reset" id="reset-with-id" data="id-reset" value="Has ID"/>
|
33
33
|
<input type="reset" value="reset-with-value" data="value-reset"/>
|
34
34
|
<input type="reset" value="not exact value reset" data="not-exact-value-reset"/>
|
@@ -36,7 +36,7 @@
|
|
36
36
|
<input type="reset" title="My reset title" value="reset-with-title" data="title-reset">
|
37
37
|
<input type="reset" title="Exact reset title" value="exact title reset" data="exact-title-reset">
|
38
38
|
<input type="reset" title="Not Exact reset title" value="exact title reset" data="not-exact-title-reset">
|
39
|
-
|
39
|
+
|
40
40
|
<input type="button" id="button-with-id" data="id-button" value="Has ID"/>
|
41
41
|
<input type="button" value="button-with-value" data="value-button"/>
|
42
42
|
<input type="button" value="not exact value button" data="not-exact-value-button"/>
|
@@ -77,6 +77,8 @@
|
|
77
77
|
<input type="schmoo" value="schmoo button" data="schmoo"/>
|
78
78
|
<label for="text-with-id">Label text <input type="text" id="text-with-id" data="id-text" value="monkey"/></label>
|
79
79
|
<label for="problem-text-with-id">Label text's got an apostrophe <input type="text" id="problem-text-with-id" data="id-problem-text" value="monkey"/></label>
|
80
|
+
|
81
|
+
<input disabled type="submit" id="disabled-submit" data="submit-disabled" value=""/>
|
80
82
|
</p>
|
81
83
|
|
82
84
|
<p>
|
@@ -185,4 +187,15 @@
|
|
185
187
|
<label>Input hidden with parent label<input type="hidden" data="input-hidden-with-parent-label-data"/></label>
|
186
188
|
<label>Input checkbox with parent label<input type="checkbox" data="input-checkbox-with-parent-label-data"/></label>
|
187
189
|
<label>Input radio with parent label<input type="radio" data="input-radio-with-parent-label-data"/></label>
|
190
|
+
|
191
|
+
<h4>Disabled</h4>
|
192
|
+
<input disabled id="input-disabled" value="correct-value" data="input-data"/>
|
193
|
+
<input disabled type="text" id="input-text-disabled" data="input-text-data"/>
|
194
|
+
<input disabled type="file" id="input-file-disabled" data="input-file-data"/>
|
195
|
+
<input disabled type="password" id="input-password-disabled" data="input-password-data"/>
|
196
|
+
<input disabled type="custom" id="input-custom-disabled" data="input-custom-data"/>
|
197
|
+
<textarea disabled id="textarea-with-id-data-disabled">Correct value</textarea>
|
198
|
+
<select disabled id="select-with-id-data-disabled"></select>
|
199
|
+
<input disabled type="checkbox" id="input-checkbox-disabled" data="input-checkbox-data"/>
|
200
|
+
<input disabled type="radio" id="input-radio-disabled" data="input-radio-data"/>
|
188
201
|
</p>
|
data/spec/fixtures/simple.html
CHANGED
@@ -11,6 +11,13 @@
|
|
11
11
|
<ul><li>A list</li></ul>
|
12
12
|
</div>
|
13
13
|
|
14
|
+
<div id="woo" title="wooDiv" data="id">
|
15
|
+
<ul><li>A list</li></ul>
|
16
|
+
<p title="gorilla">Bax</p>
|
17
|
+
<p>Bax</p>
|
18
|
+
<p id="wooDiv">Blah</p>
|
19
|
+
</div>
|
20
|
+
|
14
21
|
<div id="baz" title="bazDiv"></div>
|
15
22
|
|
16
23
|
<div id="preference">
|
data/spec/fixtures/stuff.html
CHANGED
@@ -41,3 +41,14 @@
|
|
41
41
|
<div id="hidden_via_ancestor">Inside element with hidden ancestor</div>
|
42
42
|
<a href="/with_simple_html" title="awesome title" class="simple">hidden link</a>
|
43
43
|
</div>
|
44
|
+
|
45
|
+
<div>
|
46
|
+
<dl>
|
47
|
+
<dt>Coffee</dt>
|
48
|
+
<dd id="latte" data="with-id">black hot drink</dd>
|
49
|
+
|
50
|
+
<dt>Milk</dt>
|
51
|
+
<dd data="with-dt">white cold drink</dd>
|
52
|
+
</dl>
|
53
|
+
</div>
|
54
|
+
|
data/spec/html_spec.rb
CHANGED
@@ -28,6 +28,7 @@ describe XPath::HTML do
|
|
28
28
|
it("finds links by image's alt attribute") { get('Alt link').should == 'link-img' }
|
29
29
|
it("finds links by image's approximate alt attribute") { get('Alt').should == 'link-img' }
|
30
30
|
it("does not find links without href attriutes") { get('Wrong Link').should be_nil }
|
31
|
+
it("casts to string") { get(:'some-id').should == 'link-id' }
|
31
32
|
end
|
32
33
|
|
33
34
|
describe '#button' do
|
@@ -82,6 +83,9 @@ describe XPath::HTML do
|
|
82
83
|
context "with unkown type" do
|
83
84
|
it("does not find the button") { get('schmoo button').should be_nil }
|
84
85
|
end
|
86
|
+
|
87
|
+
it("") { get('disabled-submit').should be_nil }
|
88
|
+
it("casts to string") { get(:'tag-with-tex').should == 'text-btag' }
|
85
89
|
end
|
86
90
|
|
87
91
|
describe '#fieldset' do
|
@@ -92,6 +96,7 @@ describe XPath::HTML do
|
|
92
96
|
it("finds fieldsets by legend child tags") { get('Span Legend').should == 'fieldset-legend-span' }
|
93
97
|
it("accepts approximate legends") { get('Legend').should == 'fieldset-legend' }
|
94
98
|
it("finds nested fieldsets by legend") { get('Inner legend').should == 'fieldset-inner' }
|
99
|
+
it("casts to string") { get(:'Inner legend').should == 'fieldset-inner' }
|
95
100
|
end
|
96
101
|
|
97
102
|
describe '#field' do
|
@@ -153,6 +158,17 @@ describe XPath::HTML do
|
|
153
158
|
it("does not find image buttons") { get('Input image with parent label').should be_nil }
|
154
159
|
it("does not find hidden fields") { get('Input hidden with parent label').should be_nil }
|
155
160
|
end
|
161
|
+
|
162
|
+
context "is diabled" do
|
163
|
+
it("does not find inputs with no type") { get('input-disabled').should be_nil }
|
164
|
+
it("does not find inputs with text type") { get('input-text-disabled').should be_nil }
|
165
|
+
it("does not find inputs with password type") { get('input-password-disabled').should be_nil }
|
166
|
+
it("does not find inputs with custom type") { get('input-custom-disabled').should be_nil }
|
167
|
+
it("does not find textareas") { get('textarea-disabled').should be_nil }
|
168
|
+
it("does not find select boxes") { get('select-disabled').should be_nil }
|
169
|
+
end
|
170
|
+
|
171
|
+
it("casts to string") { get(:'select-with-id').should == 'select-with-id-data' }
|
156
172
|
end
|
157
173
|
|
158
174
|
describe '#fillable_field' do
|
@@ -170,48 +186,65 @@ describe XPath::HTML do
|
|
170
186
|
it("finds selects by name") { get('select-with-name').should == 'select-with-name-data' }
|
171
187
|
it("finds selects by label") { get('Select with label').should == 'select-with-label-data' }
|
172
188
|
it("finds selects by parent label") { get('Select with parent label').should == 'select-with-parent-label-data' }
|
189
|
+
it("does not find disabled selects") { get('select-disabled').should be_nil }
|
190
|
+
it("casts to string") { get(:'Select with parent label').should == 'select-with-parent-label-data' }
|
173
191
|
end
|
174
192
|
|
175
193
|
describe '#checkbox' do
|
176
194
|
subject{ :checkbox }
|
177
|
-
it("finds checkboxes by id")
|
178
|
-
it("finds checkboxes by name")
|
179
|
-
it("finds checkboxes by label")
|
195
|
+
it("finds checkboxes by id") { get('input-checkbox-with-id').should == 'input-checkbox-with-id-data' }
|
196
|
+
it("finds checkboxes by name") { get('input-checkbox-with-name').should == 'input-checkbox-with-name-data' }
|
197
|
+
it("finds checkboxes by label") { get('Input checkbox with label').should == 'input-checkbox-with-label-data' }
|
180
198
|
it("finds checkboxes by parent label") { get('Input checkbox with parent label').should == 'input-checkbox-with-parent-label-data' }
|
199
|
+
it("does not find disabled") { get('input-checkbox-disabled').should be_nil }
|
200
|
+
it("casts to string") { get(:'Input checkbox with parent label').should == 'input-checkbox-with-parent-label-data' }
|
181
201
|
end
|
182
202
|
|
183
203
|
describe '#radio_button' do
|
184
204
|
subject{ :radio_button }
|
185
|
-
it("finds radio buttons by id")
|
186
|
-
it("finds radio buttons by name")
|
187
|
-
it("finds radio buttons by label")
|
205
|
+
it("finds radio buttons by id") { get('input-radio-with-id').should == 'input-radio-with-id-data' }
|
206
|
+
it("finds radio buttons by name") { get('input-radio-with-name').should == 'input-radio-with-name-data' }
|
207
|
+
it("finds radio buttons by label") { get('Input radio with label').should == 'input-radio-with-label-data' }
|
188
208
|
it("finds radio buttons by parent label") { get('Input radio with parent label').should == 'input-radio-with-parent-label-data' }
|
189
|
-
|
209
|
+
it("does not find disabled") { get('input-radio-disabled').should be_nil }
|
210
|
+
it("casts to string") { get(:'Input radio with parent label').should == 'input-radio-with-parent-label-data' }
|
190
211
|
end
|
191
212
|
|
192
213
|
describe '#file_field' do
|
193
214
|
subject{ :file_field }
|
194
|
-
it("finds file fields by id")
|
195
|
-
it("finds file fields by name")
|
196
|
-
it("finds file fields by label")
|
215
|
+
it("finds file fields by id") { get('input-file-with-id').should == 'input-file-with-id-data' }
|
216
|
+
it("finds file fields by name") { get('input-file-with-name').should == 'input-file-with-name-data' }
|
217
|
+
it("finds file fields by label") { get('Input file with label').should == 'input-file-with-label-data' }
|
197
218
|
it("finds file fields by parent label") { get('Input file with parent label').should == 'input-file-with-parent-label-data' }
|
198
|
-
|
199
|
-
|
200
|
-
describe '#option' do
|
201
|
-
subject{ :option }
|
202
|
-
it("finds options by text") { get('Option with text').should == 'option-with-text-data' }
|
203
|
-
it("does not find option by partial text") { get('Option with').should be_nil }
|
219
|
+
it("does not find disabled") { get('input-file-disabled').should be_nil }
|
220
|
+
it("casts to string") { get(:'Input file with parent label').should == 'input-file-with-parent-label-data' }
|
204
221
|
end
|
205
222
|
|
206
223
|
describe "#optgroup" do
|
207
224
|
subject { :optgroup }
|
208
225
|
it("finds optgroups by label") { get('Group A').should == 'optgroup-a' }
|
226
|
+
it("casts to string") { get(:'Group A').should == 'optgroup-a' }
|
227
|
+
end
|
228
|
+
|
229
|
+
describe '#option' do
|
230
|
+
subject{ :option }
|
231
|
+
it("finds options by text") { get('Option with text').should == 'option-with-text-data' }
|
232
|
+
it("does not find option by partial text") { get('Option with').should be_nil }
|
233
|
+
it("casts to string") { get(:'Option with text').should == 'option-with-text-data' }
|
209
234
|
end
|
210
235
|
|
211
236
|
describe "#table" do
|
212
237
|
subject {:table}
|
213
|
-
it("finds tables by id")
|
238
|
+
it("finds tables by id") { get('table-with-id').should == 'table-with-id-data' }
|
214
239
|
it("finds tables by caption") { get('Table with caption').should == 'table-with-caption-data' }
|
240
|
+
it("casts to string") { get(:'Table with caption').should == 'table-with-caption-data' }
|
215
241
|
end
|
216
242
|
|
243
|
+
describe "#definition_description" do
|
244
|
+
subject {:definition_description}
|
245
|
+
let(:template) {'stuff'}
|
246
|
+
it("find definition description by id") { get('latte').should == "with-id" }
|
247
|
+
it("find definition description by term") { get("Milk").should == "with-dt" }
|
248
|
+
it("casts to string") { get(:"Milk").should == "with-dt" }
|
249
|
+
end
|
217
250
|
end
|
data/spec/xpath_spec.rb
CHANGED
@@ -98,6 +98,16 @@ describe XPath do
|
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
|
+
describe '#previous_sibling' do
|
102
|
+
it "should find nodes which are exactly preceding the current node" do
|
103
|
+
xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling(:p) }.first.text.should == 'Bax'
|
104
|
+
xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling(:ul, :p) }.first.text.should == 'Bax'
|
105
|
+
xpath { |x| x.descendant(:p)[x.attr(:title) == 'gorilla'].previous_sibling(:ul, :p) }.first.text.should == 'A list'
|
106
|
+
xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling(:ul, :li) }.first.should be_nil
|
107
|
+
xpath { |x| x.descendant(:p)[x.attr(:id) == 'wooDiv'].previous_sibling }.first.text.should == 'Bax'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
101
111
|
describe '#anywhere' do
|
102
112
|
it "should find nodes regardless of the context" do
|
103
113
|
@results = xpath do |x|
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xpath
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Jonas Nicklas
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-11-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: nokogiri
|
@@ -81,7 +81,7 @@ email:
|
|
81
81
|
executables: []
|
82
82
|
extensions: []
|
83
83
|
extra_rdoc_files:
|
84
|
-
- README.
|
84
|
+
- README.md
|
85
85
|
files:
|
86
86
|
- lib/xpath/dsl.rb
|
87
87
|
- lib/xpath/expression.rb
|
@@ -98,13 +98,13 @@ files:
|
|
98
98
|
- spec/spec_helper.rb
|
99
99
|
- spec/union_spec.rb
|
100
100
|
- spec/xpath_spec.rb
|
101
|
-
- README.
|
101
|
+
- README.md
|
102
102
|
homepage: http://github.com/jnicklas/xpath
|
103
103
|
licenses: []
|
104
104
|
post_install_message:
|
105
105
|
rdoc_options:
|
106
106
|
- --main
|
107
|
-
- README.
|
107
|
+
- README.md
|
108
108
|
require_paths:
|
109
109
|
- lib
|
110
110
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -116,12 +116,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
116
116
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
117
|
none: false
|
118
118
|
requirements:
|
119
|
-
- - ! '
|
119
|
+
- - ! '>='
|
120
120
|
- !ruby/object:Gem::Version
|
121
|
-
version:
|
121
|
+
version: '0'
|
122
122
|
requirements: []
|
123
123
|
rubyforge_project: xpath
|
124
|
-
rubygems_version: 1.8.
|
124
|
+
rubygems_version: 1.8.24
|
125
125
|
signing_key:
|
126
126
|
specification_version: 3
|
127
127
|
summary: Generate XPath expressions from Ruby
|