ScreenshotNeo

BlogHow-to

How to Skip Tests in pytest

Skip pytest tests unconditionally, by condition, at runtime, or when an optional dependency is missing. Learn when to use skip versus xfail and how to inspect skip reasons.

By the ScreenshotNeo team4 October 20269 min read

Use @pytest.mark.skip(reason="...") to skip a test every time, @pytest.mark.skipif(condition, reason="...") when a known condition makes it inapplicable, and pytest.skip(reason) when the decision can only be made at runtime. For an optional dependency, use pytest.importorskip(). Use xfail when the test should run even though failure is expected.

This guide covers pytest’s built-in ways to skip tests, how to apply them to functions, classes, and modules, how to see skip reasons, and how to handle common mistakes.

1. Choose the right way to skip

Need Use When the decision is made Does the test run?
Always skip a test @pytest.mark.skip(reason=...) During collection No
Skip if a known condition holds @pytest.mark.skipif(condition, reason=...) During collection No, if the condition is true
Decide based on runtime state pytest.skip(reason) During setup or test execution It may start, then be reported as skipped
Skip when an optional import is unavailable pytest.importorskip(name, ...) When the import is attempted No, if the import cannot be made
Exclude files or directories from test discovery Collection configuration or hooks During collection The files’ tests are not collected
Run a test whose failure is expected @pytest.mark.xfail(...) The test runs by default Yes, unless run=False

A skip says a test does not apply under current conditions. An expected failure (xfail) says the test is still meaningful to run, but it is expected to fail—for example, because of a known bug. Keeping these meanings distinct preserves useful test results. See the pytest skip and xfail guide and pytest API reference.

2. Skip one test unconditionally

Mark the test with pytest.mark.skip. Give a short reason so that the skip is understandable in test output and during code review.

import pytest

@pytest.mark.skip(reason="waiting for the service endpoint")
def test_service_endpoint():
    assert check_service_endpoint()

The test is collected, but its body does not run. Remove or revise the marker when the condition that justified it has changed; otherwise, the test can remain silently out of coverage.

3. Skip when a condition is true

Use @pytest.mark.skipif for conditions that can be checked as pytest collects tests, such as the operating system or Python version. The test is skipped when the expression evaluates to true.

import sys
import pytest

@pytest.mark.skipif(sys.platform != "win32", reason="requires Windows")
def test_windows_feature():
    assert windows_only_behavior()

Apply a condition to a class or module

Place the marker on a class to apply it to the class’s tests. To apply it to every test in a module, assign a marker to pytestmark at module scope:

# test_windows.py
import sys
import pytest

pytestmark = pytest.mark.skipif(
    sys.platform != "win32",
    reason="tests in this module require Windows",
)

def test_registry_access():
    assert can_read_registry()

Multiple applicable skipif conditions combine: if any condition is true, pytest skips the test. Prefer boolean expressions. String conditions remain supported mainly for backward compatibility.

4. Skip after discovering a runtime condition

Use pytest.skip() when the reason only becomes known during fixture setup or test execution—for example, after checking whether an external service or configuration is available.

import pytest

def test_feature():
    if not valid_config():
        pytest.skip("required configuration is unavailable")

    assert run_feature()

For a check that several tests share, put the check in a fixture and call pytest.skip() there. That avoids repeating the same runtime decision in each test. A skip raised in setup also reports the affected test as skipped.

Skip a whole module at runtime

If a module-level condition is only discovered as the module is imported, pass allow_module_level=True. Without it, a module-level call to pytest.skip() is an error.

# test_optional_service.py
import pytest

if not service_configured():
    pytest.skip(
        "service is not configured",
        allow_module_level=True,
    )

def test_service_feature():
    assert call_service()

For a condition that can be evaluated directly during collection, a module-level pytestmark = pytest.mark.skipif(...) is usually clearer.

5. Skip when an optional dependency is missing

Use pytest.importorskip() when a test or module needs a dependency that is optional for the project. It returns the imported module if the import succeeds; if it cannot import the module, pytest skips the test or module.

import pytest

optional_lib = pytest.importorskip("optional_lib")

def test_optional_parser():
    result = optional_lib.parse("value")
    assert result is not None

You can request a minimum module version with minversion:

import pytest

optional_lib = pytest.importorskip(
    "optional_lib",
    minversion="2.0",
)

The current API uses ModuleNotFoundError as the default exception type. To skip on other ImportError exceptions too, use exc_type=ImportError where supported by the pytest version installed in your project. This behavior has changed across pytest versions, so check the deprecations documentation and the documentation matching your installed version before relying on it.

6. Skip a test module or exclude files from collection

There are two different goals that are easy to confuse:

  • Keep a test in collection but report it as skipped: mark it with skip or skipif, or call pytest.skip() with allow_module_level=True when the module-level condition is discovered at runtime.
  • Prevent pytest from collecting the file or directory: configure test discovery or use a collection hook. A skip marker acts on collected test items; it is not the mechanism for excluding a directory.

Choose skipping when you want the test to remain visible in the test report. Choose collection exclusion when those tests should not be discovered for that run or project configuration. The exact collection settings depend on the project’s layout and setup.

7. See why pytest skipped a test

Run pytest with -rs to include skip reasons in the short test summary:

pytest -rs

To include details for skipped, xfailed, and xpassed tests, use:

pytest -rxXs

The -r option controls which outcomes appear in the short summary report. Pytest reports skipped and xfailed tests separately, so a skip does not look like a passing assertion.

8. Complete example

Save this as test_platform.py and run pytest -rs. It demonstrates an unconditional skip, a collection-time condition, a runtime decision, and an optional import. The example’s placeholder checks are deliberately simple; replace them with the project’s real conditions and assertions.

import sys
import pytest


@pytest.mark.skip(reason="example of a test that is temporarily disabled")
def test_disabled_example():
    assert True


@pytest.mark.skipif(
    sys.platform != "win32",
    reason="this behavior requires Windows",
)
def test_windows_behavior():
    assert sys.platform == "win32"


def test_runtime_configuration():
    configured = False  # Replace with a real runtime check.
    if not configured:
        pytest.skip("required configuration is unavailable")
    assert configured


optional_lib = pytest.importorskip("optional_lib")


def test_optional_library():
    assert optional_lib is not None

9. Troubleshooting common skip problems

Symptom Likely cause Fix
Every run skips the test An unconditional skip marker is still attached, or a skipif expression is always true. Check the marker and condition, including platform or version comparisons. Remove or update a temporary skip when it is no longer needed.
The test runs even though you expected it to skip The skipif expression is false, or the runtime check is not reached. Check the condition’s value and the code path. Use pytest -rs to inspect skips that do occur.
pytest.skip() at module scope raises an error Module-level skips require explicit permission. Pass allow_module_level=True, or use a collection-time module skipif marker if the condition is already known.
An optional import error fails collection instead of skipping The import was attempted directly before pytest could handle it, or the exception differs from the configured exc_type. Use pytest.importorskip() at the appropriate scope. Check the installed pytest version and its exc_type behavior.
A test with a known bug appears as a skip skip was used for a test that should still execute and expose whether the bug remains. Consider xfail. Pytest runs xfailed tests by default and reports an unexpected pass as XPASS.
A directory’s tests still appear in collection A skip marker marks collected tests; it does not exclude the directory. Adjust collection configuration or a collection hook for the desired file or directory exclusion.
Skip reasons do not appear in the output The default report may not show the summary detail you need. Run pytest -rs, or pytest -rxXs for skip and xfail details.

10. Reliability and maintenance

  • Use a specific reason that explains the requirement or missing resource. This makes a skip actionable in the summary.
  • Keep conditions close to the tests they govern, or centralize shared conditions in a module marker or fixture.
  • Prefer collection-time markers for stable facts such as platform support, and runtime skips for state that may vary between runs.
  • Review skips when the platform matrix, dependencies, or external resources change. A skip prevents the test body from checking behavior in that run.
  • Use xfail for known failures when running the test still provides useful signal. Consider strict=True so an unexpected pass fails the suite, or configure xfail_strict as a project default.

Skipping an inapplicable test can make a run reliable across environments, but an overly broad condition can hide regressions. Tie the condition to the actual prerequisite, and keep the skip reason specific enough to audit.

11. Performance and cost

Pytest markers such as skip and skipif let pytest avoid running the marked test body when the test is skipped. Runtime checks necessarily execute enough setup or test code to discover the condition. Optional-import checks also attempt the import. These mechanisms are primarily about applicability and accurate reporting; choose the earliest point at which the condition can be determined without making the condition misleading.

For test-suite cost, measure the work your suite actually performs: a test skipped during collection avoids its body, while setup and import work may still happen depending on where the check is placed. Do not skip tests solely to reduce runtime if doing so removes coverage your project needs.

12. Frequently asked questions

How do I skip a test in pytest?

Add @pytest.mark.skip(reason="...") above the test function.

How do I skip a test if a condition is true?

Use @pytest.mark.skipif(condition, reason="..."). When the condition is only known at runtime, call pytest.skip(reason) instead.

How do I skip a whole test module?

Set a module-level pytestmark to a skip or skipif marker. For a runtime decision during module import, call pytest.skip(reason, allow_module_level=True).

How do I see why pytest skipped a test?

Run pytest -rs to show skip reasons in the summary.

Should I skip or xfail a test for a known bug?

Use xfail if the test should still run and demonstrate the expected failure. Use skip when the test should not execute under the current conditions.

13. Or skip the browser setup

This pytest guide is about test control flow; if your test workflow also needs website screenshots, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF. The DIY approach is to install and manage a browser, its dependencies, and capture code. ScreenshotNeo lets you request the capture directly; its API documentation covers the available parameters.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers identify the page verdict and billing status.
  • An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
  • The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo and get 1,000 screenshots a month free, with no card.