3d4b427168
- Renamed both `css_selector` and `xpath_selector` both to `generate_css_selector` and `generate_xpath_selector` for clarity and not interrupting the auto-completion while coding. - Added `generate_full_css_selector` and `generate_full_xpath_selector` methods
90 lines
3.4 KiB
Python
90 lines
3.4 KiB
Python
|
|
class SelectorsGeneration:
|
|
"""Selectors generation functions
|
|
Trying to generate selectors like Firefox or maybe cleaner ones!? Ehm
|
|
Inspiration: https://searchfox.org/mozilla-central/source/devtools/shared/inspector/css-logic.js#591"""
|
|
|
|
def __general_selection(self, selection: str = 'css', full_path=False) -> str:
|
|
"""Generate a selector for the current element.
|
|
:return: A string of the generated selector.
|
|
"""
|
|
selectorPath = []
|
|
target = self
|
|
css = selection.lower() == 'css'
|
|
while target is not None:
|
|
if target.parent:
|
|
if target.attrib.get('id'):
|
|
# id is enough
|
|
part = (
|
|
f'#{target.attrib["id"]}' if css
|
|
else f"[@id='{target.attrib['id']}']"
|
|
)
|
|
selectorPath.append(part)
|
|
if not full_path:
|
|
return (
|
|
" > ".join(reversed(selectorPath)) if css
|
|
else '//*' + "/".join(reversed(selectorPath))
|
|
)
|
|
else:
|
|
part = f'{target.tag}'
|
|
# We won't use classes anymore because I some websites share exact classes between elements
|
|
# classes = target.attrib.get('class', '').split()
|
|
# if classes and css:
|
|
# part += f".{'.'.join(classes)}"
|
|
# else:
|
|
counter = {}
|
|
for child in target.parent.children:
|
|
counter.setdefault(child.tag, 0)
|
|
counter[child.tag] += 1
|
|
if child._root == target._root:
|
|
break
|
|
|
|
if counter[target.tag] > 1:
|
|
part += (
|
|
f":nth-of-type({counter[target.tag]})" if css
|
|
else f"[{counter[target.tag]}]"
|
|
)
|
|
|
|
selectorPath.append(part)
|
|
target = target.parent
|
|
if target is None or target.tag == 'html':
|
|
return (
|
|
" > ".join(reversed(selectorPath)) if css
|
|
else '//' + "/".join(reversed(selectorPath))
|
|
)
|
|
else:
|
|
break
|
|
|
|
return (
|
|
" > ".join(reversed(selectorPath)) if css
|
|
else '//' + "/".join(reversed(selectorPath))
|
|
)
|
|
|
|
@property
|
|
def generate_css_selector(self) -> str:
|
|
"""Generate a CSS selector for the current element
|
|
:return: A string of the generated selector.
|
|
"""
|
|
return self.__general_selection()
|
|
|
|
@property
|
|
def generate_full_css_selector(self) -> str:
|
|
"""Generate a complete CSS selector for the current element
|
|
:return: A string of the generated selector.
|
|
"""
|
|
return self.__general_selection(full_path=True)
|
|
|
|
@property
|
|
def generate_xpath_selector(self) -> str:
|
|
"""Generate a XPath selector for the current element
|
|
:return: A string of the generated selector.
|
|
"""
|
|
return self.__general_selection('xpath')
|
|
|
|
@property
|
|
def generate_full_xpath_selector(self) -> str:
|
|
"""Generate a complete XPath selector for the current element
|
|
:return: A string of the generated selector.
|
|
"""
|
|
return self.__general_selection('xpath', full_path=True)
|