Summary
The #878 SPA aim-axis wizard shipped a ~180 LOC client-side state machine in desktop/shared/spa/js/setup-ui.js (_gyroAimWizardOpen at :2910, _gyroAimWizardRender at :2928, capture/retry/cancel/submit handlers at :2967-3066). Server math _aim_wizard_compute (parent_server.py:12330-12486) is well-covered from #826 reuse, but the SPA wizard itself has no Playwright or integration test. The audit surfaced this on 2026-05-11.
The closing comment on #878 explicitly notes "Math layer reused (no new tests)." Reused math is fine to leave; the new UI layer is not.
Why it matters
The wizard has multiple independent failure modes the operator could hit:
- Step-state corruption (Retry from step 2 → step 0 → forgets earlier captures vs preserves them — current code keeps state in a closure, but no regression test pins the contract)
- Diagnostic endpoint returns
rawQuat=null (gyro not streaming yet) — UI surfaces "No quaternion received…" at :2996 but no test verifies the message renders, the retry path is reachable, and Submit is correctly disabled
- Validation error rendering (
r.err → modal at :3034-3046) — the four error codes from _aim_wizard_compute (bad_quaternion / insufficient_pitch/yaw / degenerate_axes / non_orthogonal_frame) need to surface specific strings, currently untested
- Double-click on Capture fires two
ra('GET'…) to /diagnostic — second can land first and capture a frame from a different orientation. No debounce, no race test.
- Success path renders derived
forward + up axes at :3050-3066 — no test verifies the numbers are correctly extracted from the server response
Proposed tests
Unit-style (tests/test_878_spa_wizard.py, Flask test_client + jsdom)
Drive the wizard from the server's perspective:
def test_aim_wizard_happy_path_persists_axes():
# Pre-register gyro-192.168.10.250 with last_quat_world set
# POST {poses: [neutral, pitch_forward, yaw_left]} → 200
# GET /api/remotes/<id> → forward_local + up_local match cross-product math
def test_aim_wizard_validation_errors_each_case():
# 4 cases, one per error code; assert err string + detail returned
def test_aim_wizard_unknown_remote_auto_registers():
# POST with deviceId=gyro-1.2.3.4 (not pre-registered)
# → 200, remote auto-registered, axes persisted
# (Note: this is also the regression for #(new KIND_GYRO bug issue))
Playwright (tests/test_878_spa_wizard_ui.py, headless Chromium)
Build on tests/capture_manual_screenshots_v1_7_119.py for browser lifecycle. Drive the actual SPA:
def test_calibrate_button_only_visible_for_gyro_fixture():
# Open Setup → DMX fixture edit modal → no Calibrate button
# Open Setup → Gyro fixture edit modal → Calibrate button visible
def test_wizard_three_step_navigation():
# Click Calibrate → step 0 visible (Neutral prompt)
# Click Capture → step 1 visible (Pitch forward prompt)
# Click Capture → step 2 visible (Yaw left prompt)
# Click Capture → submit fires → success modal with axes
def test_wizard_retry_per_step():
# Capture step 0 → step 1 → click Retry-last → back to step 0
# Capture step 0 again → step 1, verify captures buffer was reset correctly
def test_wizard_cancel_at_any_step():
# Open wizard, capture step 0, click Cancel → modal closes
# Open again → state is fresh (no leaked step counter)
def test_wizard_renders_no_quat_error():
# Stop the gyro orient stream → diagnostic.rawQuat=null
# Click Capture → "No quaternion received from the gyro yet" surfaced
def test_wizard_double_click_debounce():
# Click Capture twice within 100 ms → only one /diagnostic GET fires
# (Regression for the double-fire concern from the audit)
def test_wizard_validation_error_rendering():
# Capture three poses with axes too parallel (degenerate)
# Submit → modal shows "those gestures looked the same, please retry"
# Retry-from-start button visible and resets the wizard
def test_wizard_success_displays_derived_axes():
# Capture three valid orthogonal poses → success modal
# Assert displayed forward_local + up_local strings match server response
Add to manual smoke-test list
Append to docs/help-smoke-test.md (created in #881 if/when it lands, or as a new file):
[ ] Setup → Gyro fixture → Calibrate button visible
[ ] Click Calibrate → three-step wizard renders
[ ] Capture each pose → final modal shows derived forward + up axes
[ ] Re-open Setup → Gyro fixture → button now says "Re-calibrate"
[ ] Force a validation failure (capture three near-parallel poses)
→ specific error message + Retry-from-start works
Acceptance
Related
Summary
The #878 SPA aim-axis wizard shipped a ~180 LOC client-side state machine in
desktop/shared/spa/js/setup-ui.js(_gyroAimWizardOpenat:2910,_gyroAimWizardRenderat:2928, capture/retry/cancel/submit handlers at:2967-3066). Server math_aim_wizard_compute(parent_server.py:12330-12486) is well-covered from #826 reuse, but the SPA wizard itself has no Playwright or integration test. The audit surfaced this on 2026-05-11.The closing comment on #878 explicitly notes "Math layer reused (no new tests)." Reused math is fine to leave; the new UI layer is not.
Why it matters
The wizard has multiple independent failure modes the operator could hit:
rawQuat=null(gyro not streaming yet) — UI surfaces "No quaternion received…" at:2996but no test verifies the message renders, the retry path is reachable, and Submit is correctly disabledr.err→ modal at:3034-3046) — the four error codes from_aim_wizard_compute(bad_quaternion/insufficient_pitch/yaw/degenerate_axes/non_orthogonal_frame) need to surface specific strings, currently untestedra('GET'…)to/diagnostic— second can land first and capture a frame from a different orientation. No debounce, no race test.forward+upaxes at:3050-3066— no test verifies the numbers are correctly extracted from the server responseProposed tests
Unit-style (
tests/test_878_spa_wizard.py, Flask test_client + jsdom)Drive the wizard from the server's perspective:
Playwright (
tests/test_878_spa_wizard_ui.py, headless Chromium)Build on
tests/capture_manual_screenshots_v1_7_119.pyfor browser lifecycle. Drive the actual SPA:Add to manual smoke-test list
Append to
docs/help-smoke-test.md(created in #881 if/when it lands, or as a new file):Acceptance
tests/test_878_spa_wizard.pyexists with the unit-style cases above.tests/test_878_spa_wizard_ui.pyexists with the Playwright cases above; all pass on a CI runner.tests/test_orient_contract.py:651-661ortests/test_parent.py:3490-3530(which exercise the underlying endpoint).Related
_aim_wizard_computethat the SPA path reuses for free.