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)