fix(gui): Flatpak Show Results opens the log via host xdg-open

fix(gui): keep Show Results enabled during a run

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 11:54:11 +02:00
parent 7edfc25a1f
commit c313e1431b
4 changed files with 23 additions and 2 deletions

View File

@@ -323,6 +323,7 @@ The `testium_assist` editor extension is a thin LSP client that spawns `testium
Both Flatpak and AppImage export `TESTIUM_VERSION` from a launcher (Flatpak: launcher script in `org.testium.Testium.yaml`; AppImage: `runtime.env` in `AppImageBuilder.yml`). `get_testium_version()` checks `/.flatpak-info` / `APPIMAGE` and reads `TESTIUM_VERSION` rather than relying on package metadata or repo introspection. Both Flatpak and AppImage export `TESTIUM_VERSION` from a launcher (Flatpak: launcher script in `org.testium.Testium.yaml`; AppImage: `runtime.env` in `AppImageBuilder.yml`). `get_testium_version()` checks `/.flatpak-info` / `APPIMAGE` and reads `TESTIUM_VERSION` rather than relying on package metadata or repo introspection.
## Recent fixes / notable changes ## Recent fixes / notable changes
- Show Results (GUI): the toolbar action stays enabled during a run (the log grows live, so it is useful mid-test), not just after. In Flatpak `QDesktopServices.openUrl` routes through the OpenURI portal and often opens no editor for a `.log`; `bins.host_open_path()` now spawns `xdg-open` on the host via `flatpak-spawn --host` (returns False outside Flatpak so the caller falls back to `openUrl`).
- Test-tree search (GUI): a Ctrl+F find bar highlights + navigates matching items, with Name/Type/Doc field checkboxes. Search modifications run under `blockSignals` (else `setBackground``itemChanged``on_testChecked` storms the controller), and the search/run highlights share one flag-driven `_refresh_highlight()` (run > search > default) so overlapping layers never leave a stale colour. See "## Test-tree search (GUI)". - Test-tree search (GUI): a Ctrl+F find bar highlights + navigates matching items, with Name/Type/Doc field checkboxes. Search modifications run under `blockSignals` (else `setBackground``itemChanged``on_testChecked` storms the controller), and the search/run highlights share one flag-driven `_refresh_highlight()` (run > search > default) so overlapping layers never leave a stale colour. See "## Test-tree search (GUI)".
- `pytest` item: pytest analogue of `unittest`, but runs on the **host interpreter in a subprocess** (`bins.python_bin()`, like `py_func`) so it works across every packaging channel. A stdlib-only pytest plugin streams collected node-ids + per-test results back over stdout via sentinels; each test becomes a child item with its own PASS/FAIL/SKIP, duration and failure message. Params: `test_file`, `test_method`. Validation item: `test/validation/items/pytest/` (the validation venv now pip-installs `pytest`). See "### `pytest` item". - `pytest` item: pytest analogue of `unittest`, but runs on the **host interpreter in a subprocess** (`bins.python_bin()`, like `py_func`) so it works across every packaging channel. A stdlib-only pytest plugin streams collected node-ids + per-test results back over stdout via sentinels; each test becomes a child item with its own PASS/FAIL/SKIP, duration and failure message. Params: `test_file`, `test_method`. Validation item: `test/validation/items/pytest/` (the validation venv now pip-installs `pytest`). See "### `pytest` item".
- Graceful item load: a self-loading item that fails to load its module/file (e.g. a `unittest` test file importing a missing module, or `pytest` not installed on the host) no longer aborts the **whole** test load. `TestSet._load_item()` wraps the item's `load()`, emits a `tm.print_warn(...)` at load time and records the reason in `item._load_error`; the `@test_run` wrapper turns a non-None `_load_error` into a clean run-time `FAILURE` (message printed once via `write_footer`). The rest of the campaign loads and runs normally. Applies to module-loading items (`unittest`, `pytest`); structural action loading stays fail-fast. - Graceful item load: a self-loading item that fails to load its module/file (e.g. a `unittest` test file importing a missing module, or `pytest` not installed on the host) no longer aborts the **whole** test load. `TestSet._load_item()` wraps the item's `load()`, emits a `tm.print_warn(...)` at load time and records the reason in `item._load_error`; the `@test_run` wrapper turns a non-None `_load_error` into a clean run-time `FAILURE` (message printed once via `write_footer`). The rest of the campaign loads and runs normally. Applies to module-loading items (`unittest`, `pytest`); structural action loading stays fail-fast.

View File

@@ -199,6 +199,23 @@ def host_console_command(shell_cmd, cwd):
return ["flatpak-spawn", "--host", f"--directory={cwd}", *argv] return ["flatpak-spawn", "--host", f"--directory={cwd}", *argv]
def host_open_path(path):
"""Open *path* with the host default application (Flatpak only).
QDesktopServices/openUrl routes through the OpenURI portal inside Flatpak,
which often fails to open a plain editor for a log file. Spawn xdg-open on
the host so the user's real default app is used. Returns True on dispatch;
False (incl. outside Flatpak) so the caller can fall back to openUrl.
"""
if not _in_flatpak():
return False
try:
subprocess.Popen(["flatpak-spawn", "--host", "xdg-open", path])
return True
except (FileNotFoundError, PermissionError):
return False
def _which_host_flatpak(name): def _which_host_flatpak(name):
"""Resolve a binary name (or absolute path) on the host via flatpak-spawn. """Resolve a binary name (or absolute path) on the host via flatpak-spawn.

View File

@@ -181,7 +181,8 @@ class TestRunner:
w.actionStart_test.setText("Pause test") w.actionStart_test.setText("Pause test")
w.actionPreferences.setDisabled(True) w.actionPreferences.setDisabled(True)
w.actionRefresh_test.setDisabled(True) w.actionRefresh_test.setDisabled(True)
w.actionShow_Results.setDisabled(True) # Show Results stays available during the run (log grows live).
w.actionShow_Results.setEnabled(True)
w.actionSave_report.setDisabled(True) w.actionSave_report.setDisabled(True)
w.logSettingsBox.setDisabled(True) w.logSettingsBox.setDisabled(True)
w.actionStop_test.setEnabled(True) w.actionStop_test.setEnabled(True)

View File

@@ -40,6 +40,7 @@ from runtime.string_queue import StringQueue
from interpreter.process import TestProcess from interpreter.process import TestProcess
from interpreter.utils.test_ctrl import TestSetController from interpreter.utils.test_ctrl import TestSetController
from interpreter.utils.icons import icon_prefix from interpreter.utils.icons import icon_prefix
from interpreter.utils import bins
from main_win.test_run.outlog import OutLog from main_win.test_run.outlog import OutLog
from main_win.test_run.test_run import ThreadTestStatus from main_win.test_run.test_run import ThreadTestStatus
@@ -639,6 +640,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.statusBar().showMessage( self.statusBar().showMessage(
"Opening the logfile (" + s + "): " + self.logFileName, 100000 "Opening the logfile (" + s + "): " + self.logFileName, 100000
) )
if not bins.host_open_path(self.logFileName):
QDesktopServices.openUrl(QUrl.fromLocalFile(self.logFileName)) QDesktopServices.openUrl(QUrl.fromLocalFile(self.logFileName))
@Slot() @Slot()