Pytest plugin that changes the default look and feel of pytest with progress bar and enhanced visual output
npx @tessl/cli install tessl/pypi-pytest-sugar@1.1.0A pytest plugin that changes the default visual appearance of pytest test execution. It provides a progress bar during test runs, displays test failures instantly as they occur rather than at the end, improves overall test result formatting with colored output, and adds visual enhancements to make test output more readable and informative.
pip install pytest-sugarpytest-sugar is automatically activated when installed via pytest's plugin system:
# No explicit imports required - plugin auto-activates
# Test your code normally with pytestFor programmatic access to components:
from pytest_sugar import SugarTerminalReporter, Theme, strip_colorspytest-sugar works automatically once installed. Simply run your tests:
# Basic test execution with pytest-sugar enabled
pytest
# Verbose output (one test per line)
pytest --verbose
# Disable pytest-sugar
pytest -p no:sugar
# Force pytest-sugar in non-terminal environments
pytest --force-sugarConfiguration via config files:
# pytest-sugar.conf or ~/.pytest-sugar.conf
[theme]
success = green
fail = red
skipped = blue
progressbar = green
[sugar]
progressbar_length = 20%pytest-sugar integrates with pytest's plugin architecture through:
pytest11 entry pointTerminalReporter with SugarTerminalReporterCore plugin configuration and initialization functions that integrate with pytest's hook system.
def pytest_addoption(parser: Parser) -> None:
"""
Add command-line options to pytest.
Adds pytest-sugar specific options:
--old-summary: Show full tracebacks instead of one-line summaries
--force-sugar: Force sugar output even when not in terminal
--sugar-trace-dir: Directory for Playwright trace files
--sugar-no-trace: Disable Playwright trace file detection
Parameters:
- parser: Parser - pytest argument parser
"""
def pytest_configure(config) -> None:
"""
Configure pytest-sugar plugin (trylast=True).
Replaces default terminal reporter with SugarTerminalReporter
when running in terminal or when forced.
Parameters:
- config: Config - pytest configuration object
"""
def pytest_sessionstart(session: Session) -> None:
"""
Called when pytest session starts.
Loads theme configuration from config files and initializes
global theme settings.
Parameters:
- session: Session - pytest session object
"""Functions that handle test collection completion and maintain test count for progress tracking.
def pytest_collection_finish(session: Session) -> None:
"""
Called when test collection is finished.
Sets the total test count on the terminal reporter for
progress bar calculations.
Parameters:
- session: Session - pytest session object
"""
def pytest_deselected(items: Sequence[Item]) -> None:
"""
Called when tests are deselected.
Updates the test count to exclude deselected tests from
progress calculations.
Parameters:
- items: Sequence[Item] - deselected test items
"""Customizes how test results are visually represented with colored symbols and status indicators.
def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]:
"""
Customize test status reporting with colored symbols.
Returns custom status representation with colors and symbols
based on test outcome (passed, failed, skipped, etc.).
Parameters:
- report: BaseReport - test execution report
Returns:
Optional[Tuple[str, str, str]] - (category, letter, word) or None
"""Enhanced terminal reporter that provides real-time progress visualization and improved test output formatting.
class SugarTerminalReporter:
"""
Custom terminal reporter that replaces pytest's default TerminalReporter.
Provides enhanced visual output with progress bar, instant failure display,
and improved formatting.
"""
def __init__(self, config: Config, file: Union[TextIO, None] = None) -> None:
"""
Initialize the sugar terminal reporter.
Parameters:
- config: Config - pytest configuration object
- file: Union[TextIO, None] - output file stream (optional)
"""
def reset_tracked_lines(self) -> None:
"""Reset line tracking state for display management."""
def pytest_collectreport(self, report: CollectReport) -> None:
"""
Handle collection reports and display collection failures.
Parameters:
- report: CollectReport - collection report from pytest
"""
def pytest_sessionstart(self, session: Session) -> None:
"""
Handle session start and display header information.
Parameters:
- session: Session - pytest session object
"""
def write_fspath_result(self, nodeid: str, res, **markup: bool) -> None:
"""
Override to disable default file path result writing (no-op).
pytest-sugar handles its own file path and result display formatting,
so this method is overridden to prevent default behavior.
Parameters:
- nodeid: str - test node identifier
- res: test result
- **markup: bool - markup options (ignored)
"""
def insert_progress(self, report: Union[CollectReport, TestReport]) -> None:
"""
Insert progress bar into current terminal line.
Creates and displays the progress bar with current test completion
percentage and visual indicators for passed/failed tests.
Parameters:
- report: Union[CollectReport, TestReport] - test or collection report
"""
def overwrite(self, line: str, rel_line_num: int) -> None:
"""
Overwrite terminal line at relative position.
Parameters:
- line: str - text to write
- rel_line_num: int - relative line number offset
"""
def get_max_column_for_test_status(self) -> int:
"""
Get maximum column width available for test status display.
Returns:
int - maximum column width for test status
"""
def begin_new_line(self, report: Union[CollectReport, TestReport], print_filename: bool) -> None:
"""
Start a new display line for test output.
Parameters:
- report: Union[CollectReport, TestReport] - test or collection report
- print_filename: bool - whether to print the filename
"""
def reached_last_column_for_test_status(self, report: Union[CollectReport, TestReport]) -> bool:
"""
Check if the current line has reached maximum width.
Parameters:
- report: Union[CollectReport, TestReport] - test or collection report
Returns:
bool - True if line is at maximum width
"""
def report_key(self, report: Union[CollectReport, TestReport]) -> Any:
"""
Get key to identify which line the report should write to.
Parameters:
- report: Union[CollectReport, TestReport] - test or collection report
Returns:
Any - key for grouping reports by display line
"""
def pytest_runtest_logstart(self, nodeid, location) -> None:
"""
Handle test execution start logging (override to disable default behavior).
Prevents pytest's default location line printing since pytest-sugar
already displays module names and test names in verbose mode.
Parameters:
- nodeid: test node identifier
- location: test location information
"""
def pytest_runtest_logfinish(self, nodeid: str) -> None:
"""
Handle test execution finish logging (override to disable default progress).
Prevents pytest's default progress display since pytest-sugar
provides its own progress bar implementation.
Parameters:
- nodeid: str - test node identifier
"""
def pytest_runtest_logreport(self, report: TestReport) -> None:
"""
Handle test execution reports and update display.
Processes test results, updates progress bar, and displays
test status with appropriate symbols and colors.
Parameters:
- report: TestReport - test execution report
"""
def count(self, key: str, when: tuple = ("call",)) -> int:
"""
Count test results by status and execution phase.
Parameters:
- key: str - result status key (passed, failed, skipped, etc.)
- when: tuple - execution phases to include (default: ("call",))
Returns:
int - count of matching results
"""
def summary_stats(self) -> None:
"""Display final test result summary with counts and Playwright trace information."""
def _get_decoded_crashline(self, report: CollectReport) -> str:
"""
Get decoded crash line from test report.
Handles encoding issues when extracting crash line information
from test failure reports.
Parameters:
- report: CollectReport - collection or test report
Returns:
str - decoded crash line text
"""
def _get_lineno_from_report(self, report: CollectReport) -> int:
"""
Extract line number from test report.
Attempts multiple strategies to extract the line number where
a test failure occurred, including doctest and regular test failures.
Parameters:
- report: CollectReport - collection or test report
Returns:
int - line number where failure occurred
"""
def _find_playwright_trace(self, report: TestReport) -> Optional[str]:
"""
Find Playwright trace file associated with a specific test report.
Identifies the location of trace files using the test report's node ID
and configuration options. Provides commands to view traces for failed tests.
Parameters:
- report: TestReport - test report containing node ID and failure details
Returns:
Optional[str] - command to view Playwright trace or None if not found/disabled
"""
@staticmethod
def _convert_node_to_trace_name(nodeid: str) -> str:
"""
Convert pytest node ID to trace directory name format.
Transforms pytest node identifiers into the expected trace directory
naming convention used by Playwright.
Parameters:
- nodeid: str - pytest node identifier
Returns:
str - trace directory name
"""
def summary_failures(self) -> None:
"""Override to prevent default failure summary (handled via instant display)."""
def summary_errors(self) -> None:
"""Override to prevent default error summary (handled via instant display)."""
def print_failure(self, report: Union[CollectReport, TestReport]) -> None:
"""
Print test failure information immediately when failure occurs.
Parameters:
- report: Union[CollectReport, TestReport] - failed test report
"""Configurable theme system for customizing visual appearance of test output.
class Theme:
"""
Data class for customizing visual appearance and symbols.
Configurable through pytest-sugar.conf files.
"""
# Color configuration
header: Optional[str] = "magenta"
skipped: Optional[str] = "blue"
success: Optional[str] = "green"
warning: Optional[str] = "yellow"
fail: Optional[str] = "red"
error: Optional[str] = "red"
xfailed: Optional[str] = "green"
xpassed: Optional[str] = "red"
progressbar: Optional[str] = "green"
progressbar_fail: Optional[str] = "red"
progressbar_background: Optional[str] = "grey"
path: Optional[str] = "cyan"
name: Optional[str] = None
unknown: Optional[str] = "blue"
rerun: Optional[str] = "blue"
# Symbol configuration
symbol_passed: str = "✓"
symbol_skipped: str = "s"
symbol_failed: str = "⨯"
symbol_failed_not_call: str = "ₓ"
symbol_xfailed_skipped: str = "x"
symbol_xfailed_failed: str = "X"
symbol_unknown: str = "?"
symbol_rerun: Optional[str] = "R"
def __getitem__(self, x):
"""Access theme attributes by key for dynamic lookup."""Support for pytest-xdist distributed testing plugin.
class DeferredXdistPlugin:
"""Plugin to handle pytest-xdist integration for distributed testing."""
def pytest_xdist_node_collection_finished(self, node, ids) -> None:
"""
Handle distributed collection completion.
Updates test count when using pytest-xdist for parallel test execution.
Parameters:
- node: xdist node object
- ids: list of test IDs collected by the node
"""Helper functions for text processing and display calculations.
def flatten(seq) -> Generator[Any, None, None]:
"""
Flatten nested sequences into a single generator.
Recursively flattens lists and tuples within the input sequence.
Parameters:
- seq: sequence to flatten (can contain nested lists/tuples)
Yields:
Any - flattened items from the input sequence
"""
def strip_colors(text: str) -> str:
"""
Remove ANSI color codes from text strings.
Uses regex to remove ANSI escape sequences for color formatting,
useful for text processing and length calculations.
Parameters:
- text: str - text containing ANSI color codes
Returns:
str - text with color codes removed
"""
def real_string_length(string: str) -> int:
"""
Calculate the real display length of a string after removing color codes.
Determines actual character width for proper terminal alignment
by first stripping ANSI color codes.
Parameters:
- string: str - string potentially containing color codes
Returns:
int - actual display length without color codes
"""pytest-sugar adds several command-line options to customize its behavior:
# Show detailed test failures instead of one-line tracebacks
pytest --old-summary
# Force pytest-sugar output even if not in a real terminal
pytest --force-sugar
# Specify directory for Playwright trace files (default: test-results)
pytest --sugar-trace-dir custom-traces
# Disable Playwright trace file detection and display
pytest --sugar-no-traceCreate pytest-sugar.conf in your project root or ~/.pytest-sugar.conf in your home directory:
[theme]
# Color customization (use color names or None to disable)
success = green
fail = red
skipped = blue
warning = yellow
error = red
xfailed = green
xpassed = red
progressbar = green
progressbar_fail = red
progressbar_background = grey
path = cyan
name =
unknown = blue
rerun = blue
# Symbol customization
symbol_passed = ✓
symbol_skipped = s
symbol_failed = ⨯
symbol_failed_not_call = ₓ
symbol_xfailed_skipped = x
symbol_xfailed_failed = X
symbol_unknown = ?
symbol_rerun = R
[sugar]
# Progress bar length (integer or percentage)
progressbar_length = 20%pytest-sugar automatically detects and displays Playwright trace files for failed tests:
test-results)--sugar-no-trace option# Type definitions from typing module used in API signatures
from typing import Any, Dict, Generator, List, Optional, Sequence, TextIO, Tuple, Union
# pytest imports for type annotations
from _pytest.config import Config
from _pytest.config.argparsing import Parser
from _pytest.main import Session
from _pytest.nodes import Item
from _pytest.reports import BaseReport, CollectReport, TestReport
from _pytest.terminal import TerminalReporter
# External dependencies
from termcolor import colored__version__: str = "1.1.1"
LEN_RIGHT_MARGIN: int = 0
LEN_PROGRESS_PERCENTAGE: int = 5
LEN_PROGRESS_BAR_SETTING: str = "10"
LEN_PROGRESS_BAR: Optional[int] = None
THEME: Theme # Global theme instance
PROGRESS_BAR_BLOCKS: List[str] # Unicode progress bar characters
IS_SUGAR_ENABLED: bool = False # Global enable flag