mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Add debug info
This commit is contained in:
@@ -6,7 +6,7 @@ class CenterLayoutExample(App):
|
|||||||
CSS_PATH = "center_layout.css"
|
CSS_PATH = "center_layout.css"
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Static("One", id="bottom")
|
yield Static("Onee", id="bottom")
|
||||||
yield Static("Two", id="middle")
|
yield Static("Two", id="middle")
|
||||||
yield Static("Three", id="top")
|
yield Static("Three", id="top")
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import os
|
|||||||
import shlex
|
import shlex
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
|
from textual.app import App
|
||||||
from textual._import_app import import_app
|
from textual._import_app import import_app
|
||||||
|
|
||||||
# This module defines our "Custom Fences", powered by SuperFences
|
# This module defines our "Custom Fences", powered by SuperFences
|
||||||
@@ -25,7 +26,9 @@ def format_svg(source, language, css_class, options, md, attrs, **kwargs) -> str
|
|||||||
try:
|
try:
|
||||||
rows = int(attrs.get("lines", 24))
|
rows = int(attrs.get("lines", 24))
|
||||||
columns = int(attrs.get("columns", 80))
|
columns = int(attrs.get("columns", 80))
|
||||||
svg = take_svg_screenshot(path, press, title, terminal_size=(rows, columns))
|
svg = take_svg_screenshot(
|
||||||
|
None, path, press, title, terminal_size=(rows, columns)
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
|
|
||||||
@@ -39,8 +42,9 @@ def format_svg(source, language, css_class, options, md, attrs, **kwargs) -> str
|
|||||||
|
|
||||||
|
|
||||||
def take_svg_screenshot(
|
def take_svg_screenshot(
|
||||||
app_path: str,
|
app: App | None = None,
|
||||||
press: Iterable[str] = ("_", "_"),
|
app_path: str | None = None,
|
||||||
|
press: Iterable[str] = ("_",),
|
||||||
title: str | None = None,
|
title: str | None = None,
|
||||||
terminal_size: tuple[int, int] = (24, 80),
|
terminal_size: tuple[int, int] = (24, 80),
|
||||||
) -> str:
|
) -> str:
|
||||||
@@ -48,7 +52,10 @@ def take_svg_screenshot(
|
|||||||
|
|
||||||
os.environ["COLUMNS"] = str(columns)
|
os.environ["COLUMNS"] = str(columns)
|
||||||
os.environ["LINES"] = str(rows)
|
os.environ["LINES"] = str(rows)
|
||||||
app = import_app(app_path)
|
|
||||||
|
if app is None:
|
||||||
|
app = import_app(app_path)
|
||||||
|
|
||||||
app.console.legacy_windows = False
|
app.console.legacy_windows = False
|
||||||
if title is None:
|
if title is None:
|
||||||
title = app.title
|
title = app.title
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import difflib
|
import difflib
|
||||||
|
import os
|
||||||
|
import pprint
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import partial
|
from functools import partial
|
||||||
@@ -13,15 +15,19 @@ from _pytest.fixtures import FixtureRequest
|
|||||||
from _pytest.main import Session
|
from _pytest.main import Session
|
||||||
from _pytest.terminal import TerminalReporter
|
from _pytest.terminal import TerminalReporter
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
|
from rich import inspect
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
|
from textual.app import App
|
||||||
from textual._doc import take_svg_screenshot
|
from textual._doc import take_svg_screenshot
|
||||||
|
from textual._import_app import import_app
|
||||||
|
|
||||||
snapshot_svg_key = pytest.StashKey[str]()
|
TEXTUAL_SNAPSHOT_SVG_KEY = pytest.StashKey[str]()
|
||||||
actual_svg_key = pytest.StashKey[str]()
|
TEXTUAL_ACTUAL_SVG_KEY = pytest.StashKey[str]()
|
||||||
snapshot_pass = pytest.StashKey[bool]()
|
TEXTUAL_SNAPSHOT_PASS = pytest.StashKey[bool]()
|
||||||
|
TEXTUAL_APP_KEY = pytest.StashKey[App]()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -36,15 +42,17 @@ def snap_compare(
|
|||||||
|
|
||||||
def compare(app_path: str, snapshot: SnapshotAssertion) -> bool:
|
def compare(app_path: str, snapshot: SnapshotAssertion) -> bool:
|
||||||
node = request.node
|
node = request.node
|
||||||
actual_screenshot = take_svg_screenshot(app_path)
|
app = import_app(app_path)
|
||||||
|
actual_screenshot = take_svg_screenshot(app=app)
|
||||||
result = snapshot == actual_screenshot
|
result = snapshot == actual_screenshot
|
||||||
|
|
||||||
if result is False:
|
if result is False:
|
||||||
# The split and join below is a mad hack, sorry...
|
# The split and join below is a mad hack, sorry...
|
||||||
node.stash[snapshot_svg_key] = "\n".join(str(snapshot).splitlines()[1:-1])
|
node.stash[TEXTUAL_SNAPSHOT_SVG_KEY] = "\n".join(str(snapshot).splitlines()[1:-1])
|
||||||
node.stash[actual_svg_key] = actual_screenshot
|
node.stash[TEXTUAL_ACTUAL_SVG_KEY] = actual_screenshot
|
||||||
|
node.stash[TEXTUAL_APP_KEY] = app
|
||||||
else:
|
else:
|
||||||
node.stash[snapshot_pass] = True
|
node.stash[TEXTUAL_SNAPSHOT_PASS] = True
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -59,6 +67,8 @@ class SvgSnapshotDiff:
|
|||||||
file_similarity: float
|
file_similarity: float
|
||||||
path: PathLike
|
path: PathLike
|
||||||
line_number: int
|
line_number: int
|
||||||
|
app: App
|
||||||
|
environment: dict
|
||||||
|
|
||||||
|
|
||||||
def pytest_sessionfinish(
|
def pytest_sessionfinish(
|
||||||
@@ -73,22 +83,28 @@ def pytest_sessionfinish(
|
|||||||
diffs: List[SvgSnapshotDiff] = []
|
diffs: List[SvgSnapshotDiff] = []
|
||||||
num_snapshots_passing = 0
|
num_snapshots_passing = 0
|
||||||
for item in session.items:
|
for item in session.items:
|
||||||
num_snapshots_passing += int(item.stash.get(snapshot_pass, False))
|
|
||||||
snapshot_svg = item.stash.get(snapshot_svg_key, None)
|
# Grab the data our fixture attached to the pytest node
|
||||||
actual_svg = item.stash.get(actual_svg_key, None)
|
num_snapshots_passing += int(item.stash.get(TEXTUAL_SNAPSHOT_PASS, False))
|
||||||
if snapshot_svg and actual_svg:
|
snapshot_svg = item.stash.get(TEXTUAL_SNAPSHOT_SVG_KEY, None)
|
||||||
|
actual_svg = item.stash.get(TEXTUAL_ACTUAL_SVG_KEY, None)
|
||||||
|
app = item.stash.get(TEXTUAL_APP_KEY, None)
|
||||||
|
|
||||||
|
if snapshot_svg and actual_svg and app:
|
||||||
path, line_index, name = item.reportinfo()
|
path, line_index, name = item.reportinfo()
|
||||||
diffs.append(
|
diffs.append(
|
||||||
SvgSnapshotDiff(
|
SvgSnapshotDiff(
|
||||||
snapshot=str(snapshot_svg),
|
snapshot=str(snapshot_svg),
|
||||||
actual=str(actual_svg),
|
actual=str(actual_svg),
|
||||||
file_similarity=100
|
file_similarity=100
|
||||||
* difflib.SequenceMatcher(
|
* difflib.SequenceMatcher(
|
||||||
a=str(snapshot_svg), b=str(actual_svg)
|
a=str(snapshot_svg), b=str(actual_svg)
|
||||||
).ratio(),
|
).ratio(),
|
||||||
test_name=name,
|
test_name=name,
|
||||||
path=path,
|
path=path,
|
||||||
line_number=line_index + 1,
|
line_number=line_index + 1,
|
||||||
|
app=app,
|
||||||
|
environment=dict(os.environ),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
|
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row mb-4" style="background-color:#F4F8F7;">
|
<div class="row mb-4" style="background-color:#F4F8F7;">
|
||||||
<div class="col-8 p-4">
|
<div class="col-8 p-4">
|
||||||
@@ -51,14 +53,18 @@
|
|||||||
</strong>
|
</strong>
|
||||||
<span class="text-muted">({{ "%.2f"|format(diff.file_similarity) }}% source similarity)</span>
|
<span class="text-muted">({{ "%.2f"|format(diff.file_similarity) }}% source similarity)</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-muted">{{ diff.path }}:{{ diff.line_number }}</span>
|
<div class="text-muted">
|
||||||
|
{{ diff.path }}:{{ diff.line_number }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{{ diff.actual }}
|
{{ diff.actual }}
|
||||||
<div class="w-100 d-flex justify-content-center mt-1">
|
<div class="w-100 d-flex justify-content-center mt-1">
|
||||||
<span class="small">Output from test</span>
|
<span class="small">Output from test (<a href="#" class="link-primary mb-0"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#environmentModal">More info</a>)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
@@ -69,6 +75,63 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal modal-lg fade" id="environmentModal" tabindex="-1"
|
||||||
|
aria-labelledby="environmentModalLabel"
|
||||||
|
aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="environmentModalLabel">More info for <span
|
||||||
|
class="font-monospace">{{ diff.test_name }}</span></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
||||||
|
aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body overflow-auto">
|
||||||
|
<h5>Textual App State</h5>
|
||||||
|
<table class="table mb-4">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Variable</th>
|
||||||
|
<th scope="col">Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="font-monospace">app.console.legacy_windows</td>
|
||||||
|
<td class="font-monospace">{{ diff.app.console.legacy_windows }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="font-monospace">app.console.size</td>
|
||||||
|
<td class="font-monospace">{{ diff.app.console.size }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h5>Environment (<span class="font-monospace">os.environ</span>)</h5>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Variable</th>
|
||||||
|
<th scope="col">Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for key, value in diff.environment.items() %}
|
||||||
|
<tr>
|
||||||
|
<td class="font-monospace">{{ key }}</td>
|
||||||
|
<td class="font-monospace">{{ value }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,21 +141,28 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card bg-light">
|
<div class="card bg-light">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="card-text">If you're happy with the change, run pytest with the <span class="font-monospace text-primary">--snapshot-update</span> flag to update the snapshot.</p>
|
<p class="card-text">If you're happy with the test output, run pytest with the <span
|
||||||
|
class="font-monospace text-primary">--snapshot-update</span> flag to update the snapshot.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="w-100 d-flex p-4 justify-content-center">
|
<div class="w-100 d-flex p-4 justify-content-center">
|
||||||
<p class="text-muted">Report generated at UTC {{ now }}.</p>
|
<p class="text-muted">Report generated at UTC {{ now }}.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user