{"record":{"id":"03c39c316c466cef","repo":"D4Vinci/Scrapling","slug":"expressions-of-type-name-xpathexpr-can-ony-j","errorCode":null,"errorMessage":"Expressions of type {__name__}.XPathExpr can ony join expressions of the same type (or its descendants), got {type(other)}","messagePattern":"Expressions of type (.+?)\\.XPathExpr can ony join expressions of the same type \\(or its descendants\\), got (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"scrapling/core/translator.py","lineNumber":61,"sourceCode":"            else:\n                path += \"/text()\"\n\n        if self.attribute is not None:\n            if path.endswith(\"::*/*\"):  # pragma: no cover\n                path = path[:-2]\n            path += f\"/@{self.attribute}\"\n\n        return path\n\n    def join(\n        self: Self,\n        combiner: str,\n        other: OriginalXPathExpr,\n        *args: Any,\n        **kwargs: Any,\n    ) -> Self:\n        if not isinstance(other, XPathExpr):\n            raise ValueError(  # pragma: no cover\n                f\"Expressions of type {__name__}.XPathExpr can ony join expressions\"\n                f\" of the same type (or its descendants), got {type(other)}\"\n            )\n        super().join(combiner, other, *args, **kwargs)\n        self.textnode = other.textnode\n        self.attribute = other.attribute\n        return self\n\n\n# e.g. cssselect.GenericTranslator, cssselect.HTMLTranslator\nclass TranslatorProtocol(Protocol):\n    def xpath_element(self, selector: Element) -> OriginalXPathExpr:  # pyright: ignore # pragma: no cover\n        pass\n\n    def css_to_xpath(self, css: str, prefix: str = ...) -> str:  # pyright: ignore # pragma: no cover\n        pass\n\n","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/D4Vinci/Scrapling/blob/5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f/scrapling/core/translator.py#L43-L79","documentation":"Raised by `XPathExpr.join` in scrapling/core/translator.py, Scrapling's cssselect extension that adds `::text` and `::attr()` pseudo-elements. When two CSS selector expressions are combined (comma grouping inside a selector like `div a, div b::text`), `join` requires `other` to be an `XPathExpr` (or subclass) so the `textnode`/`attribute` state carries over correctly. Passing a plain `cssselect.GenericTranslator` XPathExpr — which happens if a different translator or a raw expression object is mixed in — triggers this ValueError.","triggerScenarios":"Combining selector expressions where one side was produced by a non-Scrapling translator (plain `GenericTranslator().selector_to_xpath(...)` result fed into a Scrapling CSS query chain), or calling `XPathExpr.join(combiner, other)` manually with a `cssselect.xpath.XPathExpr` instance. Typical user-facing selector: grouping with `::text`/`::attr()` across differently-translated branches.","commonSituations":"Mixing Scrapling's `HTMLTranslator` with stock cssselect translation output; code that pre-compiles part of a selector with `cssselect` directly and concatenates it into a `Selector.css()` query; upgrading Scrapling versions where the translator internals changed shape.","solutions":["Let Scrapling translate the whole CSS selector itself — pass the full selector string to `page.css('a::attr(href), a::text')` instead of pre-translating parts with cssselect.","If you must combine programmatically, wrap the foreign expression first: `XPathExpr.from_xpath(raw_expr)` before joining.","Check for stray `GenericTranslator`/`HTMLTranslator` usage in your code and replace with Scrapling's translator classes.","Reproduce with a minimal selector to confirm which branch produces the non-Scrapling expression."],"exampleFix":"# before\nfrom cssselect import GenericTranslator\npartial = GenericTranslator().css_to_xpath('div a')\nselector.css(partial + '|//span')  # mixing translators\n\n# after\nselector.css('div a, span')  # one selector, one translator","handlingStrategy":"validation","validationCode":"from scrapling.core.translator import XPathExpr\nfrom cssselect.xpath import XPathExpr as OriginalXPathExpr\n\ndef joinable(expr) -> bool:\n    return isinstance(expr, XPathExpr)","typeGuard":"from scrapling.core.translator import XPathExpr\n\ndef is_scrapling_xpath_expr(expr) -> bool:\n    \"\"\"True if expr can be joined with Scrapling XPathExpr instances.\"\"\"\n    return isinstance(expr, XPathExpr)","tryCatchPattern":"try:\n    combined = expr_a.join(',', expr_b)\nexcept ValueError as e:\n    # wrap foreign expressions first, then retry once\n    from scrapling.core.translator import XPathExpr\n    combined = expr_a.join(',', XPathExpr.from_xpath(expr_b))","preventionTips":["Never mix stock cssselect translation output into Scrapling selector strings.","Pass whole CSS selector strings to .css(); let Scrapling own the translation.","Wrap foreign expressions with XPathExpr.from_xpath() before any join."],"tags":["css-selectors","xpath","translator","scrapling"],"backgroundTag":null,"analyzedSha":"5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f","analyzedAt":"2026-08-14T22:23:09.440Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}