pytest-dev/pytest · error · TypeError

{e} - maybe you meant pytest.mark.skipif?

Error message

{e} - maybe you meant pytest.mark.skipif?

What it means

pytest.mark.skip accepts only reason= and allow_module_level=; it does not take a condition. evaluate_skip_marks() constructs Skip(*mark.args, **mark.kwargs), so passing positional condition arguments (the skipif signature) makes Skip()'s dataclass constructor raise TypeError. pytest catches that TypeError and re-raises it with a hint pointing to skipif, since the most common cause is confusing the two decorators.

Source

Thrown at src/_pytest/skipping.py:192

        else:
            conditions = (mark.kwargs["condition"],)

        # Unconditional.
        if not conditions:
            reason = mark.kwargs.get("reason", "")
            return Skip(reason)

        # If any of the conditions are true.
        for condition in conditions:
            result, reason = evaluate_condition(item, mark, condition)
            if result:
                return Skip(reason)

    for mark in item.iter_markers(name="skip"):
        try:
            return Skip(*mark.args, **mark.kwargs)
        except TypeError as e:
            raise TypeError(str(e) + " - maybe you meant pytest.mark.skipif?") from None

    return None


@dataclasses.dataclass(frozen=True)
class Xfail:
    """The result of evaluate_xfail_marks()."""

    __slots__ = ("raises", "reason", "run", "strict")

    reason: str
    run: bool
    strict: bool
    raises: (
        type[BaseException]
        | tuple[type[BaseException], ...]
        | AbstractRaises[BaseException]
        | None

View on GitHub (pinned to 0d6fbdeffa)

Solutions

  1. Switch the decorator to pytest.mark.skipif for conditional skipping: @pytest.mark.skipif(sys.platform == 'win32', reason='...').
  2. If you want unconditional skip, drop the condition arg: @pytest.mark.skip(reason='...').
  3. Pass reason only as a keyword: @pytest.mark.skip(reason='why') — never positional condition args.

Example fix

# before
@pytest.mark.skip(sys.platform == "win32", reason="windows-only")
def test_x(): ...

# after
@pytest.mark.skipif(sys.platform == "win32", reason="windows-only")
def test_x(): ...
Defensive patterns

Strategy: validation

Validate before calling

# In conftest.py — collect-time check that surfaces the misuse early.
import pytest

def pytest_collectstart(collector):
    # Inspect skip marks for positional args (the skipif signature).
    for mark in getattr(collector, "iter_markers", lambda **k: [])() or []:
        if getattr(mark, "name", None) == "skip" and mark.args:
            raise pytest.UsageError(
                "pytest.mark.skip got positional args; did you mean skipif?"
            )

Type guard

# Detect the misuse statically in a lint/check script:
import ast

def skip_has_positional_args(decorator: ast.AST) -> bool:
    return (
        isinstance(decorator, ast.Call)
        and isinstance(decorator.func, ast.Attribute)
        and decorator.func.attr == "skip"
        and len(decorator.args) >= 1
    )

Try / catch

# Not recommended to catch — fix the decorator. If migrating many tests:
import re, pathlib
for p in pathlib.Path("tests").rglob("*.py"):
    t = p.read_text()
    t2 = re.sub(r"@pytest\.mark\.skip\(([^)]+),\s*reason=", r"@pytest.mark.skipif(\1, reason=", t)
    p.write_text(t2)

Prevention

When it happens

Trigger: Writing @pytest.mark.skip(sys.platform == 'win32', reason='windows-only') or @pytest.mark.skip('some condition') — i.e. giving skip the positional args that belong to skipif. Also triggered by passing an unexpected kwarg to skip.

Common situations: Copy-pasting a skipif example and changing the decorator name to skip; reading docs for skipif and applying them to skip; migrating a conditional skip written as `if cond: pytest.skip()` into a decorator form and getting the marker wrong.

Related errors


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