pytest-dev/pytest · error · TypeError

Expected id to be a string or a `pytest.HIDDEN_PARAM`…

Error message

Expected id to be a string or a `pytest.HIDDEN_PARAM` sentinel, got {type(id)}: {id!r}

What it means

A TypeError raised by ParameterSet.param when the `id` argument is not None, not a str, and not the HIDDEN_PARAM sentinel. pytest.param accepts only a string id (the displayed test id) or the special pytest.HIDDEN_PARAM marker to hide a parameter row. Any other type (int, float, list, None mismatch) fails this isinstance chain.

Solutions

  1. Convert the id to a string: `id=str(value)`.
  2. Omit the id argument entirely to let pytest generate one, or pass None explicitly.
  3. Use `pytest.HIDDEN_PARAM` (newer pytest) only if you intend to hide the row.

Example fix

# before
pytest.param(1, id=42)
# after
pytest.param(1, id='42')
Defensive patterns

Strategy: type-guard

Validate before calling

def coerce_param_id(id_value):
    if id_value is None:
        return None
    return str(id_value) if not isinstance(id_value, str) else id_value

Type guard

def is_legal_param_id(id_value) -> bool:
    return id_value is None or isinstance(id_value, str)

Prevention

When it happens

Trigger: Calling `pytest.param(1, id=42)`, `pytest.param(1, id=['a'])`, or `pytest.param(1, id=object())`. The guard `not isinstance(id, str) and id is not HIDDEN_PARAM` catches everything that is neither.

Common situations: Passing a numeric id expecting automatic string conversion, passing a list/tuple of id components, or passing None when meaning to omit the argument (None is allowed and means 'auto').

Related errors


AI-assisted analysis of pytest-dev/pytest@0d6fbdeffa (2026-08-11). Data as JSON: /api/errors/29a707b4bf4fb156. Report an issue: GitHub.

Appendix: source

Thrown at src/_pytest/mark/structures.py:134

    def param(
        cls,
        *values: object,
        marks: MarkDecorator | Collection[MarkDecorator | Mark] = (),
        id: str | _HiddenParam | None = None,
    ) -> ParameterSet:
        if isinstance(marks, MarkDecorator):
            marks = (marks,)
        else:
            assert isinstance(marks, collections.abc.Collection)
        if any(i.name == "usefixtures" for i in marks):
            raise ValueError(
                "pytest.param cannot add pytest.mark.usefixtures; see "
                "https://docs.pytest.org/en/stable/reference/reference.html#pytest-param"
            )

        if id is not None:
            if not isinstance(id, str) and id is not HIDDEN_PARAM:
                raise TypeError(
                    "Expected id to be a string or a `pytest.HIDDEN_PARAM` sentinel, "
                    f"got {type(id)}: {id!r}",
                )
        return cls(values, marks, id)

    @classmethod
    def extract_from(
        cls,
        parameterset: ParameterSet | Sequence[object] | object,
        force_tuple: bool = False,
    ) -> ParameterSet:
        """Extract from an object or objects.

        :param parameterset:
            A legacy style parameterset that may or may not be a tuple,
            and may or may not be wrapped into a mess of mark objects.

        :param force_tuple:

View on GitHub (pinned to 0d6fbdeffa)