From b75ddaefbe623754628842a27bb26b03be7124d5 Mon Sep 17 00:00:00 2001 From: JeffreyChen Date: Mon, 2 Nov 2026 12:05:10 +0800 Subject: [PATCH 1/2] Skip every agent GUI test when Qt cannot load, not only when it is absent The container image has PySide6 but no libEGL, so QtWidgets fails to import. These 13 modules guarded with importorskip("PySide6"), which succeeds there, and their tests failed with ImportError instead of skipping like the rest of the GUI suite. --- .../headless/test_agent_completion_authorization_gui.py | 2 +- test/unit_test/headless/test_agent_completion_handoff_gui.py | 2 +- test/unit_test/headless/test_agent_dag_background_gui.py | 4 ++-- test/unit_test/headless/test_agent_dag_batch_surfaces.py | 2 +- test/unit_test/headless/test_agent_dag_continuation_gui.py | 2 +- test/unit_test/headless/test_agent_evidence_handoff_gui.py | 2 +- .../headless/test_agent_skill_authorization_surfaces.py | 2 +- test/unit_test/headless/test_agent_skill_completion_gui.py | 2 +- test/unit_test/headless/test_agent_skill_execution_gui.py | 2 +- .../unit_test/headless/test_agent_skill_recording_surfaces.py | 2 +- test/unit_test/headless/test_agent_task_handoff_gui.py | 2 +- test/unit_test/headless/test_agent_transfer_gui.py | 2 +- .../headless/test_agent_trigger_authorization_surfaces.py | 2 +- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/test/unit_test/headless/test_agent_completion_authorization_gui.py b/test/unit_test/headless/test_agent_completion_authorization_gui.py index 0761463db..be07a2ea0 100644 --- a/test/unit_test/headless/test_agent_completion_authorization_gui.py +++ b/test/unit_test/headless/test_agent_completion_authorization_gui.py @@ -9,7 +9,7 @@ ('agent_completion_authorization_create', 'create_authorized_completion'), ]) def test_readonly_renewal_actions_use_the_shared_recovery_protocol(monkeypatch, action, operation): - pytest.importorskip('PySide6') + pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # also when Qt cannot load (no libEGL) from PySide6.QtWidgets import QApplication from je_auto_control.gui.agent_tasks_tab import AgentTasksTab import shiboken6 diff --git a/test/unit_test/headless/test_agent_completion_handoff_gui.py b/test/unit_test/headless/test_agent_completion_handoff_gui.py index c7789f737..d56bb93e5 100644 --- a/test/unit_test/headless/test_agent_completion_handoff_gui.py +++ b/test/unit_test/headless/test_agent_completion_handoff_gui.py @@ -6,7 +6,7 @@ def test_existing_handoff_action_and_result_keep_the_renewed_review_explicit(monkeypatch): - pytest.importorskip('PySide6') + pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # also when Qt cannot load (no libEGL) from PySide6.QtWidgets import QApplication from je_auto_control.gui.agent_tasks_tab import AgentTasksTab import shiboken6 diff --git a/test/unit_test/headless/test_agent_dag_background_gui.py b/test/unit_test/headless/test_agent_dag_background_gui.py index ae17f91db..e0a96ddac 100644 --- a/test/unit_test/headless/test_agent_dag_background_gui.py +++ b/test/unit_test/headless/test_agent_dag_background_gui.py @@ -11,7 +11,7 @@ def test_background_worker_forwards_initial_and_explicit_approved_continuation(monkeypatch): - pytest.importorskip("PySide6") + pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # also when Qt cannot load (no libEGL) from je_auto_control.gui.dag_tab import _DagWorker call = Mock(return_value={"approval_id": "approval"}) monkeypatch.setattr("je_auto_control.gui.dag_tab.manage_agent_dag", call) @@ -27,7 +27,7 @@ def test_background_worker_forwards_initial_and_explicit_approved_continuation(m def test_closing_the_view_does_not_stop_a_batch_already_accepted_by_the_service(service, monkeypatch): - pytest.importorskip("PySide6") + pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # also when Qt cannot load (no libEGL) import shiboken6 from PySide6.QtWidgets import QApplication from je_auto_control.gui.dag_tab import DagTab diff --git a/test/unit_test/headless/test_agent_dag_batch_surfaces.py b/test/unit_test/headless/test_agent_dag_batch_surfaces.py index 41b3968aa..b8fa259db 100644 --- a/test/unit_test/headless/test_agent_dag_batch_surfaces.py +++ b/test/unit_test/headless/test_agent_dag_batch_surfaces.py @@ -35,7 +35,7 @@ def test_versioned_api_and_catalog_expose_the_same_managed_batch_protocol(): def test_gui_registers_only_on_action_and_discards_graph_or_batch_selection_changes(monkeypatch): - pytest.importorskip("PySide6") + pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # also when Qt cannot load (no libEGL) import shiboken6 from PySide6.QtWidgets import QApplication from je_auto_control.gui.dag_tab import DagTab, _DagWorker diff --git a/test/unit_test/headless/test_agent_dag_continuation_gui.py b/test/unit_test/headless/test_agent_dag_continuation_gui.py index 98124746f..d9999bb88 100644 --- a/test/unit_test/headless/test_agent_dag_continuation_gui.py +++ b/test/unit_test/headless/test_agent_dag_continuation_gui.py @@ -9,7 +9,7 @@ def test_review_and_run_actions_preserve_source_selection_and_do_not_auto_execute(monkeypatch): - pytest.importorskip("PySide6") + pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # also when Qt cannot load (no libEGL) import shiboken6 from PySide6.QtWidgets import QApplication from je_auto_control.gui.dag_tab import DagTab, _DagWorker diff --git a/test/unit_test/headless/test_agent_evidence_handoff_gui.py b/test/unit_test/headless/test_agent_evidence_handoff_gui.py index b69c89fb3..0fcda9897 100644 --- a/test/unit_test/headless/test_agent_evidence_handoff_gui.py +++ b/test/unit_test/headless/test_agent_evidence_handoff_gui.py @@ -6,7 +6,7 @@ @pytest.mark.parametrize('operation', ['review_handoff', 'create_handoff', 'revoke_handoff']) def test_actions_only_prepare_explicit_handoff_requests(monkeypatch, operation): - pytest.importorskip('PySide6') + pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # also when Qt cannot load (no libEGL) from PySide6.QtWidgets import QApplication from je_auto_control.gui.agent_tasks_tab import AgentTasksTab import shiboken6 diff --git a/test/unit_test/headless/test_agent_skill_authorization_surfaces.py b/test/unit_test/headless/test_agent_skill_authorization_surfaces.py index 0d5475bba..ac166d25f 100644 --- a/test/unit_test/headless/test_agent_skill_authorization_surfaces.py +++ b/test/unit_test/headless/test_agent_skill_authorization_surfaces.py @@ -53,7 +53,7 @@ def test_authenticated_http_can_review_and_create_without_executing_or_publishin def test_gui_skill_selector_dispatches_review_and_creation_only_on_explicit_actions(monkeypatch): - pytest.importorskip("PySide6") + pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # also when Qt cannot load (no libEGL) from PySide6.QtWidgets import QApplication import shiboken6 from je_auto_control.gui.agent_tasks_tab import AgentTasksTab diff --git a/test/unit_test/headless/test_agent_skill_completion_gui.py b/test/unit_test/headless/test_agent_skill_completion_gui.py index e6b7e70bc..d61d59fb5 100644 --- a/test/unit_test/headless/test_agent_skill_completion_gui.py +++ b/test/unit_test/headless/test_agent_skill_completion_gui.py @@ -5,7 +5,7 @@ def test_gui_prepares_separate_trial_and_completion_fields_without_dispatch(monkeypatch): - pytest.importorskip('PySide6') + pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # also when Qt cannot load (no libEGL) from PySide6.QtWidgets import QApplication from je_auto_control.gui.agent_tasks_tab import AgentTasksTab import shiboken6 diff --git a/test/unit_test/headless/test_agent_skill_execution_gui.py b/test/unit_test/headless/test_agent_skill_execution_gui.py index 65fbc7439..063fead90 100644 --- a/test/unit_test/headless/test_agent_skill_execution_gui.py +++ b/test/unit_test/headless/test_agent_skill_execution_gui.py @@ -9,7 +9,7 @@ ('accept', 'agent_skill_execution_accept_example'), ]) def test_gui_prepares_original_trial_and_separate_execution_without_dispatch(monkeypatch, operation, key): - pytest.importorskip('PySide6') + pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # also when Qt cannot load (no libEGL) from PySide6.QtWidgets import QApplication from je_auto_control.gui.agent_tasks_tab import AgentTasksTab import shiboken6 diff --git a/test/unit_test/headless/test_agent_skill_recording_surfaces.py b/test/unit_test/headless/test_agent_skill_recording_surfaces.py index 29b8bb1ee..9a06240a2 100644 --- a/test/unit_test/headless/test_agent_skill_recording_surfaces.py +++ b/test/unit_test/headless/test_agent_skill_recording_surfaces.py @@ -72,7 +72,7 @@ def test_http_revoked_reviewer_refuses_recording_with_409(recording, tmp_path): def test_gui_prepares_original_source_and_reader_fields_without_recording(monkeypatch): - pytest.importorskip('PySide6') + pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # also when Qt cannot load (no libEGL) from PySide6.QtWidgets import QApplication from je_auto_control.gui.agent_tasks_tab import AgentTasksTab import shiboken6 diff --git a/test/unit_test/headless/test_agent_task_handoff_gui.py b/test/unit_test/headless/test_agent_task_handoff_gui.py index af64d52df..806bf6791 100644 --- a/test/unit_test/headless/test_agent_task_handoff_gui.py +++ b/test/unit_test/headless/test_agent_task_handoff_gui.py @@ -6,7 +6,7 @@ @pytest.fixture def tab(monkeypatch): - pytest.importorskip('PySide6') + pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # also when Qt cannot load (no libEGL) from PySide6.QtWidgets import QApplication from je_auto_control.gui.agent_tasks_tab import AgentTasksTab import shiboken6 diff --git a/test/unit_test/headless/test_agent_transfer_gui.py b/test/unit_test/headless/test_agent_transfer_gui.py index 53d6ebd74..29943a582 100644 --- a/test/unit_test/headless/test_agent_transfer_gui.py +++ b/test/unit_test/headless/test_agent_transfer_gui.py @@ -7,7 +7,7 @@ @pytest.fixture def tab(): - pytest.importorskip('PySide6') + pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # also when Qt cannot load (no libEGL) from PySide6.QtWidgets import QApplication from je_auto_control.gui.agent_tasks_tab import AgentTasksTab import shiboken6 diff --git a/test/unit_test/headless/test_agent_trigger_authorization_surfaces.py b/test/unit_test/headless/test_agent_trigger_authorization_surfaces.py index 58c980e38..7d630eeab 100644 --- a/test/unit_test/headless/test_agent_trigger_authorization_surfaces.py +++ b/test/unit_test/headless/test_agent_trigger_authorization_surfaces.py @@ -54,7 +54,7 @@ def test_authenticated_http_preserves_owner_and_does_not_dispatch_inherited_queu def test_gui_review_prepares_separate_explicit_request_and_refuses_wrong_selection(monkeypatch): - pytest.importorskip("PySide6") + pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # also when Qt cannot load (no libEGL) from PySide6.QtWidgets import QApplication import shiboken6 from je_auto_control.gui.agent_tasks_tab import AgentTasksTab From 0cd1fdfe06788afd8e065a9af2e33a11f7460e4c Mon Sep 17 00:00:00 2001 From: JeffreyChen Date: Mon, 2 Nov 2026 13:18:50 +0800 Subject: [PATCH 2/2] Give the grounding wall-deadline test room for a slow runner's setup The test gave the task a 2 s wall budget and expects it to run out while the helper's fourth observation sleeps. On the release PR's Windows 3.10 runner the helper start and the first observe/act alone took longer, so the budget ran out during setup instead. The budget is now 6 s under a 20 s action limit: enough for setup, and still spent during the 30 s sleep. --- test/unit_test/headless/test_agent_native_target.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/unit_test/headless/test_agent_native_target.py b/test/unit_test/headless/test_agent_native_target.py index e1706554e..1cdb2412e 100644 --- a/test/unit_test/headless/test_agent_native_target.py +++ b/test/unit_test/headless/test_agent_native_target.py @@ -132,7 +132,10 @@ def test_wall_deadline_during_grounding_retires_before_input_cleanup(tmp_path): runtime = AgentRuntime(tmp_path / "one.sqlite", targets={"fixture": adapter}) try: specification = spec() - specification["budget"] = {"wall_seconds": 2, "action_seconds": 5} + # The wall budget must outlast the helper start and the first observe/act on a slow + # runner, and still run out (before the 20 s action limit) while the fourth observation + # sleeps for 30 seconds. + specification["budget"] = {"wall_seconds": 6, "action_seconds": 20} task = runtime.submit(specification) frame = runtime.observe(task.task_id) runtime.act(task.task_id, {"tool": "AC_increment", "arguments": {"hold": True},