Skip to content

Conversation

@JanZachmann
Copy link
Contributor

@JanZachmann JanZachmann commented Jan 17, 2026

Completes the Crux Core migration by achieving 100% E2E test pass rate (51/51 tests) through Core/Shell synchronization fixes, network logic refactoring, and comprehensive test coverage for all device operations.

Key Changes:

  • Upgrade Crux framework to 0.17 with updated Core/Shell architecture
  • Add missing ViewModel fields for proper Core/Shell synchronization
  • Refactor network configuration logic into modular structure
  • Fix rollback modal and connection detection issues
  • Add E2E tests for reboot, factory reset, and firmware updates
  • Improve test infrastructure with configurable polling intervals

…r detection

Add test case to verify Core correctly identifies current connection adapter when
browser hostname is 'localhost' and network adapter has matching 'localhost' IP.

This test confirms the Core logic works correctly for E2E test scenarios where
the test environment uses 'localhost' as both the browser hostname and adapter IP.

The test validates that the direct IP matching logic in current_connection_adapter()
properly handles non-standard hostnames like 'localhost' that aren't valid IPv4
addresses but still match adapter IPs exactly via string comparison.

Related to E2E test failures where timing issues prevent network status from being
fully processed before UI assertions, but Core logic itself is verified correct.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
The Vue synchronization layer was missing 5 fields that were added to
the Crux Core's Model during migration, causing e2e test failures where
computed values like current_connection_adapter weren't propagating to
the UI.

Root Cause:
- sync.ts didn't deserialize fields added in Phase 2 of the migration
- This broke "(current connection)" detection and rollback modal logic

Changes:
- types.ts: Add missing fields to ViewModel interface
  - browser_hostname, current_connection_adapter
  - device_went_offline
  - should_show_rollback_modal, default_rollback_enabled
- state.ts: Initialize new fields with proper defaults
- sync.ts: Synchronize new fields in updateViewModelFromCore()
- Test files: Add mouse.click() workaround for Save button viewport
  issue with Playwright + Vuetify
- Test files: Skip 8 tests with unrelated issues for investigation

Test Results:
- Before: 5/33 passed (15%)
- After: 16/33 passed (48%), 8 skipped, 9 did not run
- All originally failing migration tests now pass

Fixes omnect#89

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
The rollback confirmation modal was remaining visible after clicking
"Apply Changes" because the should_show_rollback_modal flag wasn't
being cleared in handle_set_network_config_response.

Changes:
- Clear should_show_rollback_modal flag after successful network
  config application in both code paths (IP change and non-IP change)
- Unskip "shows rollback timer on configuration change" test which
  now passes
- Skip flaky "tab switching with unsaved changes" test

Test results: 17/33 tests passing (52%), up from 16/33 (48%)

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Fixed current_connection_adapter() to return None for non-IP hostnames
instead of incorrectly returning the first online adapter. This ensures
the rollback modal only appears when actually changing the current
connection adapter.

Previous behavior:
- Hostname like "localhost" would match first online adapter even when
  adapter IP didn't match (e.g., 192.168.1.200 != localhost)
- Caused rollback modal to appear for non-current-connection adapters

New behavior:
- Only matches adapters with exact IP address match
- Returns None for domain names without direct match
- Correctly prevents rollback modal for other adapters

Test results: 18/33 tests passing (55%), up from 17/33 (52%)
New passing test: "static IP on non-server adapter - no rollback modal"

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Unskipped and fixed two DHCP-related E2E tests:

1. "DHCP on non-server adapter" - Now passes with hostname matching fix
   - Non-server adapter (IP 192.168.1.200) correctly identified
   - No rollback modal appears as expected

2. "DHCP on server adapter with rollback enabled"
   - Fixed test order: publish network status before navigation
   - Corrected assertion: checkbox is unchecked by default for DHCP
   - Added checkbox check action to enable rollback
   - Properly tests DHCP with rollback enabled scenario

The fix to current_connection_adapter() in the previous commit ensures
these tests work correctly - adapters are only matched by direct IP
comparison, preventing false positives.

Test results: 20/33 tests passing (61%), up from 18/33 (55%)

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Unskipped and fixed 2 e2e tests to align with actual UI behavior:

1. Gateway field readonly when DHCP enabled:
   - Added proper assertions to verify gateway field is editable in Static mode
   - Verified gateway field becomes readonly when switching to DHCP
   - Verified gateway field becomes editable again when switching back to Static
   - Used getByRole('textbox', { name: /Gateways/i }) to avoid ambiguity with copy button

2. Current connection detection - hostname not matching any IP:
   - Fixed incorrect test expectation that assumed fallback to first online adapter
   - Corrected to expect NO "(current connection)" indicator when hostname doesn't match any adapter IP
   - This aligns with the fix in e3be467 that removed the problematic fallback logic
   - When browser hostname is a domain name (not an IP), we can't determine current connection without DNS resolution

Test progress: 29/33 passing (88%)

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Unskipped e2e test that verifies the unsaved changes dialog appears when
switching tabs with a dirty form and that clicking "Discard Changes"
successfully switches to the target tab.

The test was already correct and passes without modification. The UI
properly implements the unsaved changes dialog via network_form_dirty
flag and DeviceNetworks.vue tab watcher.

Test progress: 30/33 passing (91%)

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Unskipped e2e test that verifies the Reset button discards unsaved changes
and reverts form fields to their original values from the network adapter.

The test was already correct and passes without modification. The UI properly
implements reset functionality via restoreSettings() which calls both
networkFormReset() (to clear Core state) and resetFormFields() (to reset
local form values).

Test progress: 31/33 passing (94%)

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Unskipped regression test that verifies form fields don't get reset while
the user is typing, which would cause the caret to jump and user input to
be lost.

The test uses pressSequentially() to simulate character-by-character typing
and verifies values are preserved during:
1. Typing into IP address field
2. Switching between DHCP and Static modes
3. Typing after mode switches

The UI properly prevents form resets during editing via the
isSyncingFromWebSocket flag that gates the resetFormFields() calls in the
network_form_dirty watcher.

Test progress: 32/33 passing (97%)

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Unskipped and fixed the DHCP rollback timeout e2e test that verifies
automatic rollback and redirect to login when switching to DHCP with
rollback enabled.

Key fixes:
1. Enable rollback checkbox (DHCP defaults to rollback disabled)
2. Call harness.simulateRollbackTimeout() to simulate backend rollback
3. Re-enable healthcheck after rollback (similar to static IP rollback test)
4. Adjusted wait times to match test flow

The test now properly verifies:
- Rollback modal appears when switching to DHCP on server adapter
- User can enable rollback (unchecked by default for DHCP)
- Overlay appears with countdown
- Timeout triggers rollback after 5 seconds
- Spinner text changes to "Automatic rollback initiated"
- Healthcheck succeeds on old IP after rollback
- Session is invalidated and redirects to login

Test progress: 33/33 passing (100%)

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Replaced the verbose boundingBox + mouse.click workaround with the cleaner
dispatchEvent('click') approach in network E2E tests. This avoids issues where
Playwright's auto-scrolling triggers a v-overlay scrim that blocks standard clicks.

Also increased default viewport height in playwright.config.ts to 1024px to
better accommodate the network configuration form and reduce the need for scrolling.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Added an E2E test to verify that the UI correctly displays a persistent
fullscreen error dialog when the backend reports a version mismatch
via the /healthcheck endpoint (HTTP 503).

The test verifies:
- The error dialog appears with the correct versions and instructions.
- The dialog is persistent and blocks the UI.
- No error dialog is shown when the version matches.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Fixed a UX issue where the network rollback confirmation modal would appear
immediately upon changing form settings (e.g. switching to DHCP) instead of
waiting for the user to click 'Save'.

Changes:
- Decoupled modal visibility from the Core's 'should_show_rollback_modal' state.
- 'should_show_rollback_modal' is now used as 'isRollbackRequired' to check if confirmation is needed.
- Introduced local 'confirmationModalOpen' state to control the dialog.
- Updated 'submit()' handler to check 'isRollbackRequired' and open the modal only then.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Replaced 'dispatchEvent('click')' workaround with standard '.click()' in
network rollback E2E tests. This verifies that the 'Save' button is now
clickable and not obscured by the rollback modal, confirming the fix for
the modal timing issue.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Removed 'dispatchEvent('click')' workarounds in 'network.spec.ts' and
'network-config-comprehensive.spec.ts'. The underlying issue (rollback
confirmation modal appearing prematurely and blocking the UI) was fixed in
the previous commit, allowing standard Playwright '.click()' interactions
to function correctly.

This completes the cleanup of these test workarounds.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
Completed the transition from 'dispatchEvent('click')' to standard '.click()'
in 'network-rollback.spec.ts'. This was missed in the previous cleanup commit.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
    Fixed an issue where the network rollback confirmation modal was not displayed
    when switching from DHCP to Static IP if the IP address remained unchanged (e.g.,
    auto-filled). The logic in the core has been updated to trigger the modal if
    *any* field in the network configuration form has changed on the active adapter,
    not just the IP address.

    Additionally, this commit:
    - Exports 'NetworkConfigRequest' and 'NetworkFormData' from shared_types to
      the UI for better type safety.
    - Updates 'NetworkSettings.vue' to use the strongly-typed 'NetworkConfigRequest'
      when constructing the configuration payload.
    - Adds a regression test case 'DHCP -> Static (Same IP)' to 'network-rollback.spec.ts'
      to verify the fix and prevent future regressions.

    Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
    Fixed an issue where a 'rollback successful' snackbar was shown instead of the
    'Network Settings Rolled Back' modal when the device automatically rolled back
    network settings while the UI was still active (dynamic rollback).

    Changes:
    - Backend: Replaced 'http_get_silent!' with 'http_get!' in the 'WaitingForOldIp'
      state to ensure the healthcheck response body is parsed, allowing the
      'network_rollback_occurred' flag to be detected.
    - Backend: Removed the automatic 'success_message' when rollback succeeds,
      preventing the misleading snackbar.
    - Frontend: Updated 'sync.ts' to include 'network_rollback_occurred' in the
      ViewModel synchronization.
    - Frontend: Added a watcher in 'App.vue' to trigger the rollback notification
      modal when the 'network_rollback_occurred' flag becomes true in the ViewModel.
    - Tests: Added a regression test in 'network-rollback.spec.ts' to verify that
      the modal appears and the snackbar does not during a simulated rollback.

    Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
    Refactored 'network-rollback.spec.ts' to improve readability and reduce
    duplication:
    - Introduced 'navigateToAdapter' and 'setupAdapter' helper functions.
    - Consolidated setup logic for 'Static -> DHCP' and 'DHCP -> Static' tests.
    - Standardized test steps across scenarios.
    - Removed redundant comments and improved organization.

    Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
…ant file

    Refactored 'network-config-comprehensive.spec.ts' to use helper functions
    for adapter setup and navigation, improving readability and maintainability.
    Updated assertions to match the recent fix (expecting rollback modal instead
    of snackbar).

    Deleted 'network.spec.ts' as its single test case is fully covered by the
    comprehensive test suite.

    Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
    Fixed an issue where the network rollback countdown overlay was not displayed
    when switching from DHCP to Static IP (with the same IP) even when rollback
    was explicitly enabled. The core logic previously only triggered the waiting
    state if the IP changed or if switching to DHCP, ignoring the 'enableRollback'
    flag in other cases.

    The logic has been updated to always enter the waiting state if
    'enable_rollback' is true, regardless of whether the configuration parameters
    changed significantly.

    Extended the E2E test 'DHCP -> Static (Same IP)' to verify the overlay
    appearance.

    Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
    Fixed a bug where the network configuration confirmation modal would
    remain open after applying changes for the second time during
    consecutive updates (e.g., after a rollback and re-login).

    The root cause was two-fold:
    1. The UI relied on a watcher on 'success_message'. If consecutive
       operations returned the same message, the watcher wouldn't trigger.
    2. The modal wasn't explicitly closed in 'submitNetworkConfig'.

    Changes:
    - UI: Explicitly close confirmation modal on 'Apply Changes'.
    - UI: Clear 'success_message' and 'error_message' in Core before
      submitting and after displaying notifications to ensure watchers
      always trigger on new events.
    - Core: Reset 'should_show_rollback_modal' and clear 'success_message'
      when configuration is applied.
    - Core: Enhanced 'current_connection_adapter' detection to correctly
      match 'localhost' hostnames.
    - Test: Exposed 'window.setBrowserHostname' to allow E2E tests to
      reliably spoof the current connection.
    - Test: Added regression test in 'network-rollback.spec.ts' covering
      the consecutive update scenario.

    Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
…E2E tests

- Fixed a bug where saving a non-current network adapter would show an endless loading animation.
- Core: Ensure 'success_message' and 'error_message' are cleared when starting a network config request.
- Core: Updated 'NetworkFormState::to_submitting' to validate adapter name, preventing state corruption.
- UI: Removed invalid direct 'viewModel' mutations and added a robust watcher for 'network_form_state' to reset the save button's loading state.
- Test: Centralized network configuration helpers in 'NetworkTestHarness'.
- Test: Refactored 'network-config-comprehensive.spec.ts' and 'network-rollback.spec.ts' to use the improved harness, reducing boilerplate and hardcoded delays.
- Test: Added regression test for the endless progress bug.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
- UI: Read healthcheck and reconnection poll intervals from environment variables (VITE_RECONNECTION_POLL_INTERVAL_MS, VITE_NEW_IP_POLL_INTERVAL_MS).
- UI: Update rollback countdown immediately when starting polling to improve UX and test stability.
- Test: Configured E2E tests to use 500ms polling intervals, significantly reducing wait times.
- Test: Optimized 'network-config-comprehensive.spec.ts' by reducing hardcoded timeouts and wait periods.
- Test: Updated 'NetworkTestHarness' to use 'route.abort()' for healthcheck failures to bypass status code masking.
- Core: Refactored network config response handling and added regression tests for non-server adapter updates.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
- Reflected the new modular network structure (src/app/src/update/device/network/) in GEMINI.md, CLAUDE.md, and src/app/README.md.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
- Added 'src/ui/tests/reboot.spec.ts' covering reboot initiation, dialog cancellation, and timeout scenarios.
- UI: Made 'REBOOT_TIMEOUT_MS' configurable via environment variable 'VITE_REBOOT_TIMEOUT_MS' to allow faster E2E testing.
- Test: Configured E2E test environment to use a 500ms reboot timeout.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
- Added 'src/ui/tests/factory-reset.spec.ts' covering factory reset initiation, dialog cancellation, and timeout scenarios.
- UI: Made 'FACTORY_RESET_TIMEOUT_MS' configurable via environment variable 'VITE_FACTORY_RESET_TIMEOUT_MS' to allow faster E2E testing.
- Test: Configured E2E test environment to use a 500ms factory reset timeout.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
- Added 'src/ui/tests/update.spec.ts' to verify firmware upload, manifest validation, and successful update installation.
- Test: Mocked healthcheck sequence to simulate device reboot (offline -> online) during update.
- Test: Increased 'VITE_REBOOT_TIMEOUT_MS' to 2000ms in 'scripts/run-e2e-tests.sh' to allow multiple polling ticks for update simulation.
- Test: Updated 'src/ui/tests/reboot.spec.ts' to align with the increased reboot timeout.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
- Added 'src/ui/tests/fixtures/test-setup.ts' with 'setupAndLogin' helper to standardize common API mocking and authentication steps.
- Refactored 'reboot.spec.ts', 'factory-reset.spec.ts', 'update.spec.ts', and 'device.spec.ts' to use the new helper, reducing code duplication.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
- Upgraded 'crux_core' to 0.17.0-rc2 and 'crux_http' to 0.16.0-rc2.
- Refactored 'src/app/src/lib.rs' to use the new 'effect' macro and 'Effect' enum instead of the legacy 'Capabilities' struct.
- Updated 'App::update' signature to match Crux 0.17 changes.
- Updated WASM FFI bindings in 'src/app/src/wasm.rs' to use the new 'Bridge' API (update, view, resolve).
- Enabled 'wasm_js' feature for 'getrandom' in 'src/app/Cargo.toml' to support WASM targets.
- Updated UI shell ('src/ui/src/composables/core/effects.ts' and 'centrifugo.ts') to handle the new 'Request' structure and correctly resolve effects by passing 'requestId' back to Core.
- Updated 'GEMINI.md' with the new version information and architectural notes.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
- Upgraded 'crux_http' from 0.16.0-rc1 to 0.16.0-rc2.
- Removed the 'x-original-status' workaround in 'src/ui/src/composables/core/http.ts' as crux_http v0.16 correctly handles error response bodies.
- Refactored 'src/app/src/http_helpers.rs' to remove status masking logic and updated version references in comments.
- Cleaned up 'src/app/src/macros.rs' and 'src/app/README.md' to reflect the removal of the workaround.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
These files are used for agent context and should be ignored by git. They are already covered by the existing *.md ignore pattern in .gitignore.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
- Switched 'oidc-client-ts' user store from 'InMemoryWebStorage' to 'window.localStorage' to persist mock users across reloads.
- Added 'mockPortalAuth' fixture to inject a mock OIDC user into 'localStorage'.
- Fixed 'redirects to set-password if required' test by mocking portal authentication.
- Added missing success message mocks for password setup and updates.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
- Reverted 'setTimeout' workaround in 'useMessageWatchers.ts'.
- Refactored 'SetPassword.vue' to trigger auto-login by watching 'viewModel.requires_password_set' state transition instead of relying on the ephemeral 'success_message'.
- This eliminates the race condition between the global snackbar watcher (App.vue) and the local logic watcher.
- Verified 'can set initial password successfully' test passes reliably.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
@JanZachmann JanZachmann force-pushed the refactor/ui-to-core-migration branch from a37f18b to 47b2e2d Compare January 20, 2026 17:57
Move setup-centrifugo.sh from tools/ to scripts/ for better organization.
All scripts now navigate to repository root automatically, allowing them
to be executed from any directory. Centrifugo setup is simplified to
always use ./scripts/setup-centrifugo.sh with tools/ directory created
on-demand.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
@JanZachmann JanZachmann changed the title fix: complete E2E test suite - achieve 100% pass rate feat: complete Crux Core migration with full E2E test coverage Jan 20, 2026
@JanZachmann JanZachmann merged commit 3181b5a into omnect:main Jan 20, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant