Robot Framework SeleniumLibrary
elementfinder.py
Go to the documentation of this file.
1 # Copyright 2008-2011 Nokia Networks
2 # Copyright 2011-2016 Ryan Tomac, Ed Manlove and contributors
3 # Copyright 2016- Robot Framework Foundation
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 
17 from robot.api import logger
18 from robot.utils import NormalizedDict
19 from selenium.webdriver.remote.webelement import WebElement
20 
21 from SeleniumLibrary.base import ContextAware
22 from SeleniumLibrary.errors import ElementNotFound
23 from SeleniumLibrary.utils import escape_xpath_value, events, is_falsy
24 
25 from .customlocator import CustomLocator
26 
27 
29 
30  def __init__(self, ctx):
31  ContextAware.__init__(self, ctx)
32  strategies = {
33  'identifier': self._find_by_identifier_find_by_identifier,
34  'id': self._find_by_id_find_by_id,
35  'name': self._find_by_name_find_by_name,
36  'xpath': self._find_by_xpath_find_by_xpath,
37  'dom': self._find_by_dom_find_by_dom,
38  'link': self._find_by_link_text_find_by_link_text,
39  'partial link': self._find_by_partial_link_text_find_by_partial_link_text,
40  'css': self._find_by_css_selector_find_by_css_selector,
41  'class': self._find_by_class_name_find_by_class_name,
42  'jquery': self._find_by_jquery_selector_find_by_jquery_selector,
43  'sizzle': self._find_by_jquery_selector_find_by_jquery_selector,
44  'tag': self._find_by_tag_name_find_by_tag_name,
45  'scLocator': self._find_by_sc_locator_find_by_sc_locator,
46  'default': self._find_by_default_find_by_default
47  }
48  self._strategies_strategies = NormalizedDict(initial=strategies, caseless=True,
49  spaceless=True)
50  self._default_strategies_default_strategies = list(strategies)
51  self._key_attrs_key_attrs = {
52  None: ['@id', '@name'],
53  'a': ['@id', '@name', '@href',
54  'normalize-space(descendant-or-self::text())'],
55  'img': ['@id', '@name', '@src', '@alt'],
56  'input': ['@id', '@name', '@value', '@src'],
57  'button': ['@id', '@name', '@value',
58  'normalize-space(descendant-or-self::text())']
59  }
60 
61  def find(self, locator, tag=None, first_only=True, required=True,
62  parent=None):
63  element_type = 'Element' if not tag else tag.capitalize()
64  if parent and not self._is_webelement_is_webelement(parent):
65  raise ValueError('Parent must be Selenium WebElement but it '
66  'was {}.'.format(type(parent)))
67  if self._is_webelement_is_webelement(locator):
68  return locator
69  prefix, criteria = self._parse_locator_parse_locator(locator)
70  strategy = self._strategies_strategies[prefix]
71  tag, constraints = self._get_tag_and_constraints_get_tag_and_constraints(tag)
72  elements = strategy(criteria, tag, constraints,
73  parent=parent or self.driverdriverdriver)
74  if required and not elements:
75  raise ElementNotFound("%s with locator '%s' not found."
76  % (element_type, locator))
77  if first_only:
78  if not elements:
79  return None
80  return elements[0]
81  return elements
82 
83  def register(self, strategy_name, strategy_keyword, persist=False):
84  strategy = CustomLocator(self.ctxctx, strategy_name, strategy_keyword)
85  if strategy.name in self._strategies_strategies:
86  raise RuntimeError("The custom locator '%s' cannot be registered. "
87  "A locator of that name already exists."
88  % strategy.name)
89  self._strategies_strategies[strategy.name] = strategy.find
90  if is_falsy(persist):
91  # Unregister after current scope ends
92  events.on('scope_end', 'current', self.unregisterunregister, strategy.name)
93 
94  def unregister(self, strategy_name):
95  if strategy_name in self._default_strategies_default_strategies:
96  raise RuntimeError("Cannot unregister the default strategy '%s'."
97  % strategy_name)
98  if strategy_name not in self._strategies_strategies:
99  raise RuntimeError("Cannot unregister the non-registered strategy '%s'."
100  % strategy_name)
101  del self._strategies_strategies[strategy_name]
102 
103  def _is_webelement(self, element):
104  # Hook for unit tests
105  return isinstance(element, WebElement)
106 
107  def _disallow_webelement_parent(self, element):
108  if self._is_webelement_is_webelement(element):
109  raise ValueError('This method does not allow WebElement as parent')
110 
111  def _find_by_identifier(self, criteria, tag, constraints, parent):
112  elements = self._normalize_normalize(parent.find_elements_by_id(criteria)) \
113  + self._normalize_normalize(parent.find_elements_by_name(criteria))
114  return self._filter_elements_filter_elements(elements, tag, constraints)
115 
116  def _find_by_id(self, criteria, tag, constraints, parent):
117  return self._filter_elements_filter_elements(parent.find_elements_by_id(criteria),
118  tag, constraints)
119 
120  def _find_by_name(self, criteria, tag, constraints, parent):
121  return self._filter_elements_filter_elements(parent.find_elements_by_name(criteria),
122  tag, constraints)
123 
124  def _find_by_xpath(self, criteria, tag, constraints, parent):
125  return self._filter_elements_filter_elements(parent.find_elements_by_xpath(criteria),
126  tag, constraints)
127 
128  def _find_by_dom(self, criteria, tag, constraints, parent):
129  self._disallow_webelement_parent_disallow_webelement_parent(parent)
130  result = self.driverdriverdriver.execute_script("return %s;" % criteria)
131  if result is None:
132  return []
133  if not isinstance(result, list):
134  result = [result]
135  return self._filter_elements_filter_elements(result, tag, constraints)
136 
137  def _find_by_jquery_selector(self, criteria, tag, constraints, parent):
138  self._disallow_webelement_parent_disallow_webelement_parent(parent)
139  js = "return jQuery('%s').get();" % criteria.replace("'", "\\'")
140  return self._filter_elements_filter_elements(
141  self.driverdriverdriver.execute_script(js),
142  tag, constraints)
143 
144  def _find_by_link_text(self, criteria, tag, constraints, parent):
145  return self._filter_elements_filter_elements(
146  parent.find_elements_by_link_text(criteria),
147  tag, constraints)
148 
149  def _find_by_partial_link_text(self, criteria, tag, constraints, parent):
150  return self._filter_elements_filter_elements(
151  parent.find_elements_by_partial_link_text(criteria),
152  tag, constraints)
153 
154  def _find_by_css_selector(self, criteria, tag, constraints, parent):
155  return self._filter_elements_filter_elements(
156  parent.find_elements_by_css_selector(criteria),
157  tag, constraints)
158 
159  def _find_by_class_name(self, criteria, tag, constraints, parent):
160  return self._filter_elements_filter_elements(
161  parent.find_elements_by_class_name(criteria),
162  tag, constraints)
163 
164  def _find_by_tag_name(self, criteria, tag, constraints, parent):
165  return self._filter_elements_filter_elements(
166  parent.find_elements_by_tag_name(criteria),
167  tag, constraints)
168 
169  def _find_by_sc_locator(self, criteria, tag, constraints, parent):
170  logger.warn('scLocator is deprecated.')
171  self._disallow_webelement_parent_disallow_webelement_parent(parent)
172  js = "return isc.AutoTest.getElement('%s')" % criteria.replace("'", "\\'")
173  return self._filter_elements_filter_elements([self.driverdriverdriver.execute_script(js)],
174  tag, constraints)
175 
176  def _find_by_default(self, criteria, tag, constraints, parent):
177  if tag in self._key_attrs_key_attrs:
178  key_attrs = self._key_attrs_key_attrs[tag]
179  else:
180  key_attrs = self._key_attrs_key_attrs[None]
181  xpath_criteria = escape_xpath_value(criteria)
182  xpath_tag = tag if tag is not None else '*'
183  xpath_constraints = self._get_xpath_constraints_get_xpath_constraints(constraints)
184  xpath_searchers = ["%s=%s" % (attr, xpath_criteria) for attr in key_attrs]
185  xpath_searchers.extend(self._get_attrs_with_url_get_attrs_with_url(key_attrs, criteria))
186  xpath = "//%s[%s%s(%s)]" % (
187  xpath_tag,
188  ' and '.join(xpath_constraints),
189  ' and ' if xpath_constraints else '',
190  ' or '.join(xpath_searchers)
191  )
192  return self._normalize_normalize(parent.find_elements_by_xpath(xpath))
193 
194  def _get_xpath_constraints(self, constraints):
195  xpath_constraints = [self._get_xpath_constraint_get_xpath_constraint(name, value)
196  for name, value in constraints.items()]
197  return xpath_constraints
198 
199  def _get_xpath_constraint(self, name, value):
200  if isinstance(value, list):
201  return "@%s[. = '%s']" % (name, "' or . = '".join(value))
202  else:
203  return "@%s='%s'" % (name, value)
204 
205  def _get_tag_and_constraints(self, tag):
206  if tag is None:
207  return None, {}
208  tag = tag.lower()
209  constraints = {}
210  if tag == 'link':
211  tag = 'a'
212  if tag == 'partial link':
213  tag = 'a'
214  elif tag == 'image':
215  tag = 'img'
216  elif tag == 'list':
217  tag = 'select'
218  elif tag == 'radio button':
219  tag = 'input'
220  constraints['type'] = 'radio'
221  elif tag == 'checkbox':
222  tag = 'input'
223  constraints['type'] = 'checkbox'
224  elif tag == 'text field':
225  tag = 'input'
226  constraints['type'] = ['date', 'datetime-local', 'email', 'month',
227  'number', 'password', 'search', 'tel',
228  'text', 'time', 'url', 'week', 'file']
229  elif tag == 'file upload':
230  tag = 'input'
231  constraints['type'] = 'file'
232  elif tag == 'text area':
233  tag = 'textarea'
234  return tag, constraints
235 
236  def _parse_locator(self, locator):
237  if locator.startswith(('//', '(//')):
238  return 'xpath', locator
239  index = self._get_locator_separator_index_get_locator_separator_index(locator)
240  if index != -1:
241  prefix = locator[:index].strip()
242  if prefix in self._strategies_strategies:
243  return prefix, locator[index+1:].lstrip()
244  return 'default', locator
245 
246  def _get_locator_separator_index(self, locator):
247  if '=' not in locator:
248  return locator.find(':')
249  if ':' not in locator:
250  return locator.find('=')
251  return min(locator.find('='), locator.find(':'))
252 
253  def _element_matches(self, element, tag, constraints):
254  if not element.tag_name.lower() == tag:
255  return False
256  for name in constraints:
257  if isinstance(constraints[name], list):
258  if element.get_attribute(name) not in constraints[name]:
259  return False
260  elif element.get_attribute(name) != constraints[name]:
261  return False
262  return True
263 
264  def _filter_elements(self, elements, tag, constraints):
265  elements = self._normalize_normalize(elements)
266  if tag is None:
267  return elements
268  return [element for element in elements
269  if self._element_matches_element_matches(element, tag, constraints)]
270 
271  def _get_attrs_with_url(self, key_attrs, criteria):
272  attrs = []
273  url = None
274  xpath_url = None
275  for attr in ['@src', '@href']:
276  if attr in key_attrs:
277  if url is None or xpath_url is None:
278  url = self._get_base_url_get_base_url() + "/" + criteria
279  xpath_url = escape_xpath_value(url)
280  attrs.append("%s=%s" % (attr, xpath_url))
281  return attrs
282 
283  def _get_base_url(self):
284  url = self.driverdriverdriver.current_url
285  if '/' in url:
286  url = '/'.join(url.split('/')[:-1])
287  return url
288 
289  def _normalize(self, elements):
290  # Apparently IEDriver has returned invalid data earlier and recently
291  # ChromeDriver has done sometimes returned None:
292  # https://github.com/SeleniumHQ/selenium/issues/4555
293  if not isinstance(elements, list):
294  logger.debug("WebDriver find returned %s" % elements)
295  return []
296  return elements
def __init__(self, ctx)
Base class exposing attributes from the common context.
def _find_by_id(self, criteria, tag, constraints, parent)
def _find_by_class_name(self, criteria, tag, constraints, parent)
def _find_by_xpath(self, criteria, tag, constraints, parent)
def _filter_elements(self, elements, tag, constraints)
def find(self, locator, tag=None, first_only=True, required=True, parent=None)
def _element_matches(self, element, tag, constraints)
def _get_attrs_with_url(self, key_attrs, criteria)
def _find_by_name(self, criteria, tag, constraints, parent)
def register(self, strategy_name, strategy_keyword, persist=False)
def _find_by_default(self, criteria, tag, constraints, parent)
def _find_by_identifier(self, criteria, tag, constraints, parent)
def _find_by_dom(self, criteria, tag, constraints, parent)
def _find_by_link_text(self, criteria, tag, constraints, parent)
def _find_by_jquery_selector(self, criteria, tag, constraints, parent)
def _find_by_partial_link_text(self, criteria, tag, constraints, parent)
def _find_by_css_selector(self, criteria, tag, constraints, parent)
def _find_by_sc_locator(self, criteria, tag, constraints, parent)
def _find_by_tag_name(self, criteria, tag, constraints, parent)
def escape_xpath_value(value)
Definition: __init__.py:23