Class: WatirNokogiri::ElementLocator

Inherits:
Object
  • Object
show all
Includes:
Exception
Defined in:
lib/watir-nokogiri/locators/element_locator.rb

Constant Summary collapse

VALID_WHATS =
[String, Regexp]

Instance Method Summary collapse

Constructor Details

#initialize(nokogiri, selector, valid_attributes) ⇒ ElementLocator

Returns a new instance of ElementLocator.



6
7
8
9
10
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 6

def initialize(nokogiri, selector, valid_attributes)
  @nokogiri = nokogiri
  @selector = selector.dup
  @valid_attributes = valid_attributes
end

Instance Method Details

#assert_valid_as_attribute(attribute) ⇒ Object



83
84
85
86
87
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 83

def assert_valid_as_attribute(attribute)     
  unless valid_attribute? attribute or attribute.to_s =~ /^data_.+$/
    raise MissingWayOfFindingObjectException, "invalid attribute: #{attribute.inspect}"
  end
end

#attribute_expression(selectors) ⇒ Object



241
242
243
244
245
246
247
248
249
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 241

def attribute_expression(selectors)
  selectors.map do |key, val|
    if val.kind_of?(Array)
      "(" + val.map { |v| equal_pair(key, v) }.join(" or ") + ")"
    else
      equal_pair(key, val)
    end
  end.join(" and ")
end

#build_nokogiri_selector(selectors) ⇒ Object



215
216
217
218
219
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 215

def build_nokogiri_selector(selectors)
  unless selectors.values.any? { |e| e.kind_of? Regexp }
    build_xpath(selectors)
  end
end

#build_xpath(selectors) ⇒ Object



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 221

def build_xpath(selectors)
  xpath = ".//"
  xpath << (selectors.delete(:tag_name) || '*').to_s

  idx = selectors.delete :index

  # the remaining entries should be attributes
  unless selectors.empty?
    xpath << "[" << attribute_expression(selectors) << "]"
  end

  if idx
    xpath << "[#{idx + 1}]"
  end

  p :xpath => xpath, :selectors => selectors if $DEBUG

  [:xpath, xpath]
end

#can_be_combined_with_css?(selector) ⇒ Boolean

Returns:

  • (Boolean)


203
204
205
206
207
208
209
210
211
212
213
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 203

def can_be_combined_with_css?(selector)
  # ouch - is this worth it?
  keys = selector.keys
  return true if keys == [:tag_name]

  if selector[:tag_name] == "input"
    return keys == [:tag_name, :type] || keys == [:type, :tag_name]
  end

  false
end

#can_be_combined_with_xpath?(selector) ⇒ Boolean

Returns:

  • (Boolean)


181
182
183
184
185
186
187
188
189
190
191
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 181

def can_be_combined_with_xpath?(selector)
  # ouch - is this worth it?
  keys = selector.keys
  return true if keys == [:tag_name]

  if selector[:tag_name] == "input"
    return keys == [:tag_name, :type] || keys == [:type, :tag_name]
  end

  false
end

#check_type(how, what) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 158

def check_type(how, what)
  case how
  when :index
    unless what.kind_of?(Fixnum)
      raise TypeError, "expected Fixnum, got #{what.inspect}:#{what.class}"
    end
  else
    unless VALID_WHATS.any? { |t| what.kind_of? t }
      raise TypeError, "expected one of #{VALID_WHATS.inspect}, got #{what.inspect}:#{what.class}"
    end
  end
end

#delete_regexps_from(selector) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 71

def delete_regexps_from(selector)
  rx_selector = {}

  selector.dup.each do |how, what|
  next unless what.kind_of?(Regexp)
    rx_selector[how] = what
    selector.delete how
  end

  rx_selector
end

#equal_pair(key, value) ⇒ Object



251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 251

def equal_pair(key, value)
  if key == :class
    klass = XpathSupport.escape " #{value} "
    "contains(concat(' ', @class, ' '), #{klass})"
  elsif key == :label && should_use_label_element?
    # we assume :label means a corresponding label element, not the attribute
    text = "normalize-space()=#{XpathSupport.escape value}"
    "(@id=//label[#{text}]/@for or parent::label[#{text}])"
  else
    "#{lhs_for(key)}=#{XpathSupport.escape value}"
  end
end

#fetch_value(element, how) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 110

def fetch_value(element, how)
  case how
  when :text
    element.text
  when :tag_name
    element.node_name.downcase
  when :href
    (href = element.get_attribute('href')) && href.strip
  else
    element.get_attribute(how.to_s.gsub("_", "-"))
  end
end

#find_by_regexp_selector(selector, method = :find) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 48

def find_by_regexp_selector(selector, method = :find)
  parent = @nokogiri
  rx_selector = delete_regexps_from(selector)

  if rx_selector.has_key?(:label) && should_use_label_element?
    label = label_from_text(rx_selector.delete(:label)) || (return [])
    if (id = label.get_attribute('for'))
      selector[:id] = id
    else
      parent = label
    end
  end

  how, what = build_nokogiri_selector(selector)

  unless how
    raise Error, "internal error: unable to build WebDriver selector from #{selector.inspect}"
  end

  elements = parent.xpath(what)
  elements.__send__(method) { |el| matches_selector?(el, rx_selector) }
end

#given_css(selector) ⇒ Object



193
194
195
196
197
198
199
200
201
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 193

def given_css(selector)  
  return unless css = selector.delete(:css)

  unless selector.empty? || can_be_combined_with_css?(selector)
    raise ArgumentError, ":css cannot be combined with other selectors (#{selector.inspect})"
  end

  [:css, xpath]
end

#given_xpath(selector) ⇒ Object



171
172
173
174
175
176
177
178
179
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 171

def given_xpath(selector)
  return unless xpath = selector.delete(:xpath)

  unless selector.empty? || can_be_combined_with_xpath?(selector)
    raise ArgumentError, ":xpath cannot be combined with other selectors (#{selector.inspect})"
  end

  [:xpath, xpath]
end

#label_from_text(label_exp) ⇒ Object



97
98
99
100
101
102
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 97

def label_from_text(label_exp)
  # TODO: this won't work correctly if @nokogiri is a sub-element
  @nokogiri.search('label').find do |el|
    matches_selector?(el, :text => label_exp)
  end
end

#lhs_for(key) ⇒ Object



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 264

def lhs_for(key)
  case key
  when :text, 'text'
    'normalize-space()'
  when :href
    # TODO: change this behaviour?
    'normalize-space(@href)'
  when :type
    # type attributes can be upper case - downcase them
    # https://github.com/watir/watir-webdriver/issues/72
    XpathSupport.downcase('@type')
  else
    "@#{key.to_s.gsub("_", "-")}"
  end
end

#locateObject



12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 12

def locate()	
  idx = @selector.delete(:index)
  if idx
    element = locate_all[idx]
  else
    element = locate_all.first
  end

  # This actually only applies when finding by xpath - browser.text_field(:xpath, "//input[@type='radio']")
  # We don't need to validate the element if we built the xpath ourselves.
  # It is also used to alter behavior of methods locating more than one type of element
  # (e.g. text_field locates both input and textarea)
  validate_element(element) if element
end

#locate_allObject



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 27

def locate_all()
  selector = normalized_selector

  if selector.has_key? :index
    raise ArgumentError, "can't locate all elements by :index"
  end

  how, what = given_css(selector) ||
                    given_xpath(selector) || 
                    build_nokogiri_selector(selector)
                
  case how
    when :css
      @nokogiri.css(what)
    when :xpath
      @nokogiri.xpath(what)
    else
      find_by_regexp_selector(selector, :select)
  end				
end

#matches_selector?(element, selector) ⇒ Boolean

Returns:

  • (Boolean)


104
105
106
107
108
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 104

def matches_selector?(element, selector)
  selector.all? do |how, what|
    what === fetch_value(element, how)
  end
end

#normalize_selector(how, what) ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 136

def normalize_selector(how, what)
  case how
  when :tag_name, :text, :xpath, :index, :class, :label, :css
    # include :class since the valid attribute is 'class_name'
    # include :for since the valid attribute is 'html_for'
    [how, what]
  when :class_name
    [:class, what]
  when :caption
    [:text, what]
  when :for
    assert_valid_as_attribute :html_for
    [how, what]
  else
    assert_valid_as_attribute how
    [how, what]
  end
end

#normalized_selectorObject



123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 123

def normalized_selector
  selector = {}

  @selector.each do |how, what|
    check_type(how, what)

    how, what = normalize_selector(how, what)
    selector[how] = what
  end

  selector
end

#should_use_label_element?Boolean

Returns:

  • (Boolean)


93
94
95
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 93

def should_use_label_element?
  @selector[:tag_name] != "option"
end

#tag_name_matches?(element_tag_name, tag_name) ⇒ Boolean

Returns:

  • (Boolean)


293
294
295
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 293

def tag_name_matches?(element_tag_name, tag_name)
  tag_name === element_tag_name
end

#valid_attribute?(attribute) ⇒ Boolean

Returns:

  • (Boolean)


89
90
91
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 89

def valid_attribute?(attribute)
  @valid_attributes && @valid_attributes.include?(attribute)
end

#validate_element(element) ⇒ Object



280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/watir-nokogiri/locators/element_locator.rb', line 280

def validate_element(element)
  tn = @selector[:tag_name]
  element_tag_name = element.node_name.downcase

  return if tn && !tag_name_matches?(element_tag_name, tn)

  if element_tag_name == 'input'
    return if @selector[:type] && @selector[:type] != element.get_attribute(:type).downcase
  end

  element
end