Skip to content

Commit 6da0e49

Browse files
committed
Add GUI e2e tests for camera dialog
Add two end-to-end GUI tests for camera configuration dialog to prevent regressions: - test_remove_active_camera_works_while_scan_running: Verifies that removing the active camera still works while a discovery scan is running. The test slows CameraFactory.detect_cameras via monkeypatch to keep the scan running, ensures the remove button is enabled during scan, removes the selected camera, and cleans up by cancelling the scan. - test_ok_updates_internal_multicamera_settings: Ensures that after adding a second camera and accepting the dialog (OK), the settings_changed signal emits the updated MultiCameraSettings and the dialog's internal _multi_camera_settings is updated to match the accepted settings. These tests guard against regressions where scan-running state blocked structure edits (remove/move) and where dialog acceptance did not update the dialog's internal settings.
1 parent d0601ee commit 6da0e49

File tree

1 file changed

+96
-0
lines changed

1 file changed

+96
-0
lines changed

tests/gui/camera_config/test_cam_dialog_e2e.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,3 +424,99 @@ def slow_run(self):
424424

425425
qtbot.waitUntil(lambda: dialog._preview.loader is None and dialog._preview.state == PreviewState.IDLE, timeout=2000)
426426
assert dialog._preview.backend is None
427+
428+
429+
@pytest.mark.gui
430+
def test_remove_active_camera_works_while_scan_running(dialog, qtbot, monkeypatch):
431+
"""
432+
Regression test for:
433+
- 'When coming back to camera config after choosing a camera, it cannot be removed'
434+
Root cause: scan_running disabled structure edits (Remove/Move).
435+
Expected: Remove works even while discovery scan is running.
436+
"""
437+
438+
# Slow down camera detection so scan stays RUNNING long enough for interaction
439+
def slow_detect(backend, max_devices=10, should_cancel=None, progress_cb=None, **kwargs):
440+
for i in range(50):
441+
if should_cancel and should_cancel():
442+
break
443+
if progress_cb:
444+
progress_cb(f"Scanning… {i}")
445+
time.sleep(0.02)
446+
return [
447+
DetectedCamera(index=0, label=f"{backend}-X"),
448+
DetectedCamera(index=1, label=f"{backend}-Y"),
449+
]
450+
451+
monkeypatch.setattr(CameraFactory, "detect_cameras", staticmethod(slow_detect))
452+
453+
# Ensure an active row is selected
454+
dialog.active_cameras_list.setCurrentRow(0)
455+
qtbot.waitUntil(lambda: dialog.active_cameras_list.currentRow() == 0, timeout=1000)
456+
457+
initial_active = dialog.active_cameras_list.count()
458+
initial_model = len(dialog._working_settings.cameras)
459+
assert initial_active == initial_model == 1
460+
461+
# Trigger scan; wait until scan controls indicate it's running
462+
qtbot.mouseClick(dialog.refresh_btn, Qt.LeftButton)
463+
qtbot.waitUntil(lambda: dialog._is_scan_running(), timeout=1000)
464+
qtbot.waitUntil(lambda: dialog.scan_cancel_btn.isVisible(), timeout=1000)
465+
466+
# EXPECTATION: remove button should be enabled even during scan
467+
# (This will fail until _update_button_states is changed to not block remove/move during scan)
468+
qtbot.waitUntil(lambda: dialog.remove_camera_btn.isEnabled(), timeout=1000)
469+
470+
# Remove the selected active camera during scan
471+
qtbot.mouseClick(dialog.remove_camera_btn, Qt.LeftButton)
472+
473+
assert dialog.active_cameras_list.count() == initial_active - 1
474+
assert len(dialog._working_settings.cameras) == initial_model - 1
475+
476+
# Clean up: cancel scan so teardown doesn't hang waiting for scan completion
477+
if dialog.scan_cancel_btn.isVisible() and dialog.scan_cancel_btn.isEnabled():
478+
qtbot.mouseClick(dialog.scan_cancel_btn, Qt.LeftButton)
479+
480+
qtbot.waitUntil(lambda: not dialog._is_scan_running(), timeout=3000)
481+
482+
483+
@pytest.mark.gui
484+
def test_ok_updates_internal_multicamera_settings(dialog, qtbot):
485+
"""
486+
Regression test for:
487+
- 'adding another camera and hitting OK does not add the new extra camera'
488+
when caller reads dialog._multi_camera_settings after closing.
489+
490+
Expected:
491+
- OK emits updated settings
492+
- dialog._multi_camera_settings is updated to match accepted settings
493+
"""
494+
495+
# Ensure backend combo matches the active camera backend, so duplicate logic behaves consistently
496+
_select_backend_for_active_cam(dialog, cam_row=0)
497+
498+
# Scan and add a non-duplicate camera (index 1)
499+
_run_scan_and_wait(dialog, qtbot, timeout=2000)
500+
dialog.available_cameras_list.setCurrentRow(1)
501+
qtbot.mouseClick(dialog.add_camera_btn, Qt.LeftButton)
502+
503+
qtbot.waitUntil(lambda: dialog.active_cameras_list.count() == 2, timeout=1000)
504+
assert len(dialog._working_settings.cameras) == 2
505+
506+
# Click OK and capture emitted settings
507+
with qtbot.waitSignal(dialog.settings_changed, timeout=2000) as sig:
508+
qtbot.mouseClick(dialog.ok_btn, Qt.LeftButton)
509+
510+
emitted = sig.args[0]
511+
assert isinstance(emitted, MultiCameraSettings)
512+
assert len(emitted.cameras) == 2
513+
514+
# Check: internal source-of-truth must match accepted state
515+
# (This will fail until _on_ok_clicked updates self._multi_camera_settings)
516+
assert dialog._multi_camera_settings is not None
517+
assert len(dialog._multi_camera_settings.cameras) == 2
518+
519+
# Optional: ensure camera identities match (names/index/backend)
520+
assert [(c.backend, int(c.index)) for c in dialog._multi_camera_settings.cameras] == [
521+
(c.backend, int(c.index)) for c in emitted.cameras
522+
]

0 commit comments

Comments
 (0)