{"record":{"id":"bca03ce4deb7a3f0","repo":"D4Vinci/Scrapling","slug":"the-pseudo-element-pseudo-element-is-unknown","errorCode":null,"errorMessage":"The pseudo-element ::{pseudo_element} is unknown","messagePattern":"The pseudo-element ::(.+?) is unknown","errorType":"exception","errorClass":"ExpressionError","httpStatus":null,"severity":"error","filePath":"scrapling/core/translator.py","lineNumber":105,"sourceCode":"        # https://github.com/python/mypy/issues/14757\n        xpath = super().xpath_element(selector)  # type: ignore[safe-super]\n        return XPathExpr.from_xpath(xpath)\n\n    def xpath_pseudo_element(self, xpath: OriginalXPathExpr, pseudo_element: PseudoElement) -> OriginalXPathExpr:\n        \"\"\"\n        Dispatch method that transforms XPath to support the pseudo-element.\n        \"\"\"\n        if isinstance(pseudo_element, FunctionalPseudoElement):\n            method_name = f\"xpath_{pseudo_element.name.replace('-', '_')}_functional_pseudo_element\"\n            method = getattr(self, method_name, None)\n            if not method:  # pragma: no cover\n                raise ExpressionError(f\"The functional pseudo-element ::{pseudo_element.name}() is unknown\")\n            xpath = method(xpath, pseudo_element)\n        else:\n            method_name = f\"xpath_{pseudo_element.replace('-', '_')}_simple_pseudo_element\"\n            method = getattr(self, method_name, None)\n            if not method:  # pragma: no cover\n                raise ExpressionError(f\"The pseudo-element ::{pseudo_element} is unknown\")\n            xpath = method(xpath)\n        return xpath\n\n    @staticmethod\n    def xpath_attr_functional_pseudo_element(xpath: OriginalXPathExpr, function: FunctionalPseudoElement) -> XPathExpr:\n        \"\"\"Support selecting attribute values using ::attr() pseudo-element\"\"\"\n        if function.argument_types() not in ([\"STRING\"], [\"IDENT\"]):  # pragma: no cover\n            raise ExpressionError(f\"Expected a single string or ident for ::attr(), got {function.arguments!r}\")\n        return XPathExpr.from_xpath(xpath, attribute=function.arguments[0].value)\n\n    @staticmethod\n    def xpath_text_simple_pseudo_element(xpath: OriginalXPathExpr) -> XPathExpr:\n        \"\"\"Support selecting text nodes using ::text pseudo-element\"\"\"\n        return XPathExpr.from_xpath(xpath, textnode=True)\n\n\nclass HTMLTranslator(TranslatorMixin, OriginalHTMLTranslator):\n    def css_to_xpath(self, css: str, prefix: str = \"descendant-or-self::\") -> str:","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/D4Vinci/Scrapling/blob/5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f/scrapling/core/translator.py#L87-L123","documentation":"Raised by the non-functional branch of `xpath_pseudo_element` in scrapling/core/translator.py:105. Simple pseudo-elements (no parentheses) are dispatched to `xpath_<name>_simple_pseudo_element`; Scrapling only registers `::text` (plus cssselect's built-ins where applicable). Using any other simple pseudo-element such as `::before`, `::after`, `::first-line`, `::placeholder`, or `::marker` raises `ExpressionError` because pseudo-elements that exist only in browser rendering have no XPath representation.","triggerScenarios":"`selector.css('p::first-line')`, `selector.css('input::placeholder')`, `selector.css('q::before')` — any `::<name>` without parentheses other than `::text` (and `::attr()` which is functional).","commonSituations":"Selectors copied from CSS stylesheets or DevTools that target visual pseudo-elements; expecting `::before`/`::after` content to be extractable (it lives in CSS, not the DOM); migrating from BeautifulSoup/lxml code that silently ignored unknown pseudo-elements.","solutions":["Remove rendering-only pseudo-elements (`::before`, `::after`, `::placeholder`, `::first-line`, ...) from the selector — their content is not in the HTML tree.","To get `::before/::after` content, read the page's CSS (or rendered styles via the browser engine) instead of the DOM.","Use `::text` for text nodes and `::attr(name)` for attributes — these are the two Scrapling adds.","Test selectors in isolation (`Selector('<html>...</html>').css(sel)`) to find the offending one quickly."],"exampleFix":"# before\npage.css('button::before')  # ExpressionError: unknown pseudo-element\n\n# after\nbtn = page.css_first('button')\ncss_content = btn.pseudo_before_content  # or inspect stylesheet / use browser evaluation","handlingStrategy":"validation","validationCode":"import re\n\nSUPPORTED_SIMPLE = {'text'}\n\ndef check_simple_pseudos(css: str) -> list[str]:\n    return [name for name in re.findall(r'::([a-zA-Z-]+)(?!\\()', css) if name not in SUPPORTED_SIMPLE]","typeGuard":null,"tryCatchPattern":"try:\n    txt = page.css('p::first-line')\nexcept Exception as e:\n    if 'unknown' in str(e) and '::' in str(e):\n        txt = page.css('p::text')  # fall back to the full text node\n    else:\n        raise","preventionTips":["Remember ::before/::after/::placeholder are rendering constructs, absent from the DOM.","Validate selectors against the supported set before passing them to .css().","Prefer element.attrib and text extraction over pseudo-elements in generated code."],"tags":["css-selectors","translator","expression-error","scrapling"],"backgroundTag":null,"analyzedSha":"5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f","analyzedAt":"2026-08-14T22:23:09.440Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}