xpath 1.0.0.beta1 → 1.0.0
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/{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
|
+
[](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
|