SeleniumHQ/selenium · error · InvalidSelectorException

Compound class names are not allowed.

Error message

Compound class names are not allowed.

What it means

Raised inside `ShadowRoot.find_element` when `by == By.CLASS_NAME` and the value contains internal whitespace after stripping (e.g. 'foo bar'). The shadow-root API converts CLASS_NAME into a CSS selector (`.value`), but a multi-class string is not a valid single CSS class, so it rejects it preemptively. It is an InvalidSelectorException.

Source

Thrown at py/selenium/webdriver/remote/shadowroot.py:81

                - By.CSS_SELECTOR: Locate by a CSS selector.
                - By.CLASS_NAME: Locate by the `class` attribute.
                - By.TAG_NAME: Locate by the tag name (e.g., "input", "button").
                - By.LINK_TEXT: Locate a link element by its exact text.
                - By.PARTIAL_LINK_TEXT: Locate a link element by partial text match.
            value: The locator value to use with the specified `by` strategy.

        Returns:
            The first matching `WebElement` found on the page.

        Example:
            >>> element = driver.find_element(By.ID, "foo")
        """
        if by == By.ID:
            by = By.CSS_SELECTOR
            value = f'[id="{value}"]'
        elif by == By.CLASS_NAME:
            if value and any(char.isspace() for char in value.strip()):
                raise InvalidSelectorException("Compound class names are not allowed.")
            by = By.CSS_SELECTOR
            value = f".{value}"
        elif by == By.NAME:
            by = By.CSS_SELECTOR
            value = f'[name="{value}"]'

        return self._execute(Command.FIND_ELEMENT_FROM_SHADOW_ROOT, {"using": by, "value": value})["value"]

    def find_elements(self, by: str | By = By.ID, value: str | None = None) -> list[WebElement]:
        """Find elements inside a shadow root given a By strategy and locator.

        Args:
            by: The locating strategy to use. Default is `By.ID`. Supported values include:
                - By.ID: Locate by element ID.
                - By.NAME: Locate by the `name` attribute.
                - By.XPATH: Locate by an XPath expression.
                - By.CSS_SELECTOR: Locate by a CSS selector.
                - By.CLASS_NAME: Locate by the `class` attribute.

View on GitHub (pinned to aa36b38e69)

Solutions

  1. Use By.CSS_SELECTOR with a compound selector: '.btn.primary'.
  2. If you only need one distinguishing class, pass that single class name to CLASS_NAME.
  3. Switch to By.XPATH if the multi-class match is complex.

Example fix

# before
shadow_root.find_element(By.CLASS_NAME, 'btn primary')

# after
shadow_root.find_element(By.CSS_SELECTOR, '.btn.primary')
Defensive patterns

Strategy: validation

Validate before calling

if by == By.CLASS_NAME and value and any(c.isspace() for c in value.strip()):
    raise InvalidSelectorException('use By.CSS_SELECTOR for compound classes, e.g. .a.b')

Type guard

def is_single_class_name(value: str) -> bool:
    return not (value and any(c.isspace() for c in value.strip()))

Prevention

When it happens

Trigger: Calling `shadow_root.find_element(By.CLASS_NAME, 'btn primary')` or any value with embedded spaces/tabs. Whitespace at the ends is stripped first, so only inner whitespace triggers it.

Common situations: Selecting elements that carry several classes by passing the full class attribute string; copying the className straight from HTML into the locator.

Related errors


AI-assisted analysis of SeleniumHQ/selenium@aa36b38e69 (2026-08-14). Data as JSON: /api/errors/038de26a4f2f2591. Report an issue: GitHub.