{%- set s = test_summary %} {%- set executed = s.total - s.skipped %} {%- set problems = s.failed + s.error %} {%- set status = 'empty' if not s.total else ('failed' if problems else 'passed') %} {%- set brand = branding or {} %} {%- set cmp = comparison %} {%- set groups = failure_groups or [] %} {%- set max_duration = slowest_tests[0].duration if slowest_tests else 0 %} {% if brand.favicon %}{% endif %} {{ '✕ ' if status == 'failed' else ('✓ ' if status == 'passed' else '') }}{{ test_report_title|e }}
{%- if brand.logo %}{% else %}{% endif %} pyrept test report
{% if status == 'failed' %}Failed{% elif status == 'passed' %}Passed{% else %}No tests ran{% endif %}

{{ test_report_title|e }}

{{ timestamp }} {% if duration is not none %}{{ '%.2f'|format(duration) }}s total{% endif %} {% if (environment or {}).get('Framework') %}{{ environment.Framework|e }}{% endif %} {% if (environment or {}).get('Python') %}Python {{ environment.Python|e }}{% endif %}
{%- set t = s.total if s.total else 1 %} {%- set p_pass = s.passed * 100 / t %}{% set p_fail = s.failed * 100 / t %}{% set p_err = s.error * 100 / t %}{% set p_skip = s.skipped * 100 / t %}
Total
{{ s.total }}
Passed
{{ s.passed }}
Failed
{{ s.failed }}
Errors
{{ s.error }}
Skipped
{{ s.skipped }}
Pass rate
{{ s.percentage }}%{% if cmp and cmp.percentage_delta is number %}{{ '%+.2f'|format(cmp.percentage_delta) }}{% endif %}
{% if cmp %} {%- set base = cmp.baseline or {} %} {%- macro name_list(title, names) -%} {% if names %}
{{ title }} ({{ names|length }})
    {% for n in names[:200] %}
  • {{ n|e }}
  • {% endfor %}{% if names|length > 200 %}
  • … {{ names|length - 200 }} more
  • {% endif %}
{% endif %} {%- endmacro %}

Compared with previous run

{{ (base.source or 'baseline')|e }}{% if base.timestamp %} · {{ base.timestamp|e }}{% endif %}{% if base.percentage is number %} · pass rate then {{ base.percentage|e }}%{% endif %}{% if cmp.percentage_delta is number %} · {{ '%+.2f'|format(cmp.percentage_delta) }} pts{% endif %}
New failures{{ cmp.new_failures|length }}
Fixed{{ cmp.fixed|length }}
Still failing{{ cmp.still_failing|length }}
New tests{{ cmp.new_tests|length }}
Removed tests{{ cmp.removed_tests|length }}
Slower{{ cmp.slower|length }}
{{ name_list('New failures', cmp.new_failures) }} {{ name_list('Fixed', cmp.fixed) }} {{ name_list('Still failing', cmp.still_failing) }} {{ name_list('New tests', cmp.new_tests) }} {{ name_list('Removed tests', cmp.removed_tests) }} {% if cmp.slower %}
Slower tests ({{ cmp.slower|length }})
    {% for t in cmp.slower %}
  • {{ t.name|e }}: {{ '%.3f'|format(t.before) }}s → {{ '%.3f'|format(t.after) }}s (×{{ t.ratio|e }})
  • {% endfor %}
{% endif %}
{% endif %} {% if groups %}

Common failure causes

Failures that share an error message, often one root cause
    {% for g in groups %}
  1. {{ g.count }} tests{{ g.message|e }}
    Show tests
      {% for n in g.tests[:200] %}
    • {{ n|e }}
    • {% endfor %}{% if g.tests|length > 200 %}
    • … {{ g.tests|length - 200 }} more
    • {% endif %}
  2. {% endfor %}
{% endif %}

Environment

{% for key, value in (environment or {}).items() %}{% endfor %}
{{ key|e }}{{ value|e }}
Executed{{ executed }} of {{ s.total }} (skipped excluded from pass rate)

Slowest tests

{% if slowest_tests %}
{% for t in slowest_tests %}
{{ t.name|e }}{{ '%.3f'|format(t.duration) }}s
{% endfor %}
{% else %}

No timing data recorded.

{% endif %}
{% if test_results %}

Test map

One square per test{% if test_results|length > 3000 %} (first 3000 of {{ test_results|length }}){% endif %}, click one to open it
{% for r in test_results[:3000] %}{% endfor %}
PassedFailedErrorSkipped
{% endif %}
{% for r in test_results %} {%- set md = r.metadata or {} %} {%- set change = (cmp.changes or {}).get(r.name) if cmp else none %} {%- set where, leaf = r.name|split_test_name %}
{% if r.result == 'passed' %}{% elif r.result == 'skipped' %}{% elif r.result == 'error' %}{% else %}{% endif %} {{ r.result }} {% if change %}{{ change|replace('-', ' ')|e }}{% endif %} {{ where|e }}{{ leaf|e }}{% for tag in md.get('tags') or [] %}{{ tag|e }}{% endfor %} {% if md.get('duration') is number %}{{ '%.3f'|format(md.duration) }}s{% if max_duration %}{% endif %}{% endif %}
{% if md.get('location') %}{{ md.location|e }}{% endif %} {% if md.get('project') %}{{ md.project|e }}{% endif %} {% if md.get('suite') %}{{ md.suite|e }}{% endif %} {% if md.get('retries') %}retries: {{ md.retries|e }}{% endif %} {% if md.get('flaky') %}flaky{% endif %}

Description

{% if r.description %}

{{ r.description|e }}

{% else %}

No description (add a docstring to the test).

{% endif %} {% if r.traceback %}

{% if r.result in ['failed', 'error'] %}Error{% else %}Details{% endif %}

{{ r.traceback|e }}
{% endif %} {% set attachments = md.get('attachments') or [] %} {% if attachments %}

Attachments

{% for a in attachments %} {%- set ctype = (a.content_type or 'application/octet-stream')|string %} {%- set safe_path = a.path and not (a.path|string|trim|lower).startswith(('javascript:', 'vbscript:', 'data:')) %} {% if ctype.startswith('image/') and a.data %}
{{ a.name|e }}
{{ a.name|e }}
{% elif a.text %}
{{ a.name|e }}
{{ a.text|e }}
{% elif a.data %}

{{ a.name|e }} ({{ ctype|e }})

{% elif safe_path %}

{{ a.name|e }} ({{ ctype|e }})

{% endif %} {% endfor %}
{% endif %}
{% endfor %}

No tests match the current filter.