{"record":{"id":"d80ae45611c2622c","repo":"SeleniumHQ/selenium","slug":"mutation-types-must-name-at-least-one-mutation-typ","errorCode":null,"errorMessage":"mutation_types must name at least one mutation type","messagePattern":"mutation_types must name at least one mutation type","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"py/private/_script_handlers.py","lineNumber":541,"sourceCode":"        self._channel: str | None = None\n        self._subscription_id: str | None = None\n        self._handlers: dict[int, frozenset[str]] = {}\n        self._preload_script_ids: list[str] = []\n        self._active_types: set[str] = set()\n\n    def _normalize_types(self, mutation_types: str | Iterable[str] | None) -> frozenset[str]:\n        if mutation_types is None:\n            return frozenset(self.DEFAULT_MUTATION_TYPES)\n        if isinstance(mutation_types, str):\n            mutation_types = (mutation_types,)\n        types = frozenset(mutation_types)\n        unknown = types - set(self.MUTATION_TYPES)\n        if unknown:\n            raise ValueError(\n                f\"Unsupported DOM mutation type(s) {sorted(unknown)}; expected a subset of {self.MUTATION_TYPES}\"\n            )\n        if not types:\n            raise ValueError(\"mutation_types must name at least one mutation type\")\n        return types\n\n    def _channel_argument(self) -> dict:\n        if self._channel is None:\n            # Stable, namespaced channel to avoid collisions with user scripts.\n            self._channel = f\"selenium.domMutation.{uuid.uuid4().hex}\"\n        return {\"type\": \"channel\", \"value\": {\"channel\": self._channel}}\n\n    def _listener_declaration(self, types: set[str]) -> str:\n        # script.addPreloadScript arguments may only be channels, so the\n        # observation options are inlined into the function declaration.\n        options = json.dumps({name: True for name in sorted(types)})\n        return \"function(channel) { return (\" + DOM_MUTATION_LISTENER_JS + \")(channel, \" + options + \"); }\"\n\n    def _observe_types(self, channel_arg: dict, types: set[str]) -> None:\n        declaration = self._listener_declaration(types)\n        preload_script_id = self._script._add_preload_script(declaration, arguments=[channel_arg])\n        self._preload_script_ids.append(preload_script_id)","sourceCodeStart":523,"sourceCodeEnd":559,"githubUrl":"https://github.com/SeleniumHQ/selenium/blob/aa36b38e696a0909e973bdf5e2f9031ffe842c4b/py/private/_script_handlers.py#L523-L559","documentation":"Raised as a ValueError by _DOMMutationHandler._normalize_types() when the normalized set of mutation types is empty. After converting the input to a frozenset and validating that all values are in MUTATION_TYPES, the code checks `if not types` and throws if the frozenset has zero elements. This is distinct from the unknown-type error (110) — here all values were valid but the collection was empty.","triggerScenarios":"Calling _normalize_types with an empty list/tuple/set: mutation_types=[] or mutation_types=set(). The frozenset of the empty iterable has zero elements, passing the unknown-type check but failing the `if not types` check.","commonSituations":"Programmatically constructing the mutation_types list from a filter or configuration that can produce an empty set; passing an empty list by accident; a default parameter resolution that yields [] in an edge case.","solutions":["Pass None instead of an empty list to accept the default mutation types (attributes).","Ensure the mutation_types collection always contains at least one valid value before passing it.","Add a guard: if your code computes the list, check `if not types: types = None` before the call."],"exampleFix":"# before\nhandler.add_callback(callback, mutation_types=[])\n\n# after\nhandler.add_callback(callback, mutation_types=None)  # uses default 'attributes'\n# or\nhandler.add_callback(callback, mutation_types=['attributes'])","handlingStrategy":"validation","validationCode":"if not mutation_types:\n    mutation_types = None  # accept default\nhandler.add_callback(callback, mutation_types=mutation_types)","typeGuard":null,"tryCatchPattern":"try:\n    handler.add_callback(callback, mutation_types=types)\nexcept ValueError as e:\n    if 'must name at least one' in str(e):\n        types = ['attributes']\n        handler.add_callback(callback, mutation_types=types)\n    else:\n        raise","preventionTips":["Pass None instead of an empty list to use defaults.","Ensure computed mutation_types lists are non-empty before passing.","Guard with: if not types: types = None"],"tags":["python","bidi","dom-mutation","validation","mutation-observer"],"backgroundTag":null,"analyzedSha":"aa36b38e696a0909e973bdf5e2f9031ffe842c4b","analyzedAt":"2026-08-14T02:32:32.244Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}