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
- Convert the id to a string: `id=str(value)`.
- Omit the id argument entirely to let pytest generate one, or pass None explicitly.
- 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
- Always pass strings as `id`; convert non-strings with str().
- Omit `id` to use pytest's auto-generated ids.
- Type-check ids built from dynamic data.
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
- Empty parameter set in
- got instead of Mark
- ids must be a callable or an iterable
- must be absolute import path string, not
- pytest.param cannot add pytest.mark.usefixtures; see…
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)