Skip to content

Commit 5cf3276

Browse files
Mohammod Al Amin Ashikclaude
andcommitted
fix: improve settings page UX and e2e test reliability
- Always render startup settings section (show loading state instead of hiding) - Add data-testid="settings-startup-section" for e2e test targeting - Improve e2e test setup with proper wait for app shell and sidebar - Fix get_startup_settings to gracefully handle missing DB keys with defaults - Improve beforeEach to wait for section and toggles to be interactive These changes ensure the settings page is visible immediately and e2e tests can reliably find elements even when backend calls are slow. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent a6e55c2 commit 5cf3276

5 files changed

Lines changed: 54 additions & 33 deletions

File tree

apps/desktop/src-tauri/src/commands/settings.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,22 @@ pub async fn get_startup_settings(
4444
.is_enabled()
4545
.map_err(|e| format!("Failed to check auto-launch status: {}", e))?;
4646

47-
// Get other settings from database
47+
// Get other settings from database; use defaults when key is missing or DB read fails (e.g. no settings yet)
4848
let start_minimized = settings_repo
4949
.get("startup.start_minimized")
5050
.await
51-
.map_err(|e| format!("Failed to get start_minimized setting: {}", e))?
51+
.ok()
52+
.flatten()
5253
.map(|v| v == "true")
5354
.unwrap_or(true);
5455

5556
let close_to_tray = settings_repo
5657
.get("ui.close_to_tray")
5758
.await
58-
.map_err(|e| format!("Failed to get close_to_tray setting: {}", e))?
59+
.ok()
60+
.flatten()
5961
.map(|v| v == "true")
60-
.unwrap_or(true); // Default to true
62+
.unwrap_or(true);
6163

6264
Ok(StartupSettings {
6365
auto_launch,

apps/desktop/src/features/servers/ServersPage.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ export function ServersPage() {
285285
const definitions = definitionsResult.status === 'fulfilled'
286286
? definitionsResult.value
287287
: [];
288+
288289

289290
// Log if registry is offline but we have installed servers
290291
if (definitionsResult.status === 'rejected' && installed.length > 0) {
@@ -298,8 +299,17 @@ export function ServersPage() {
298299

299300
if (definitions.length > 0) {
300301
// Normal case: merge definitions with states
301-
mergedServers = mergeDefinitionsWithStates(definitions, installed)
302-
.filter(s => s.is_installed);
302+
const allMerged = mergeDefinitionsWithStates(definitions, installed);
303+
mergedServers = allMerged.filter(s => s.is_installed);
304+
305+
// Handle installed servers not present in registry definitions
306+
// (e.g., registry changed, using different registry, or servers installed from user config)
307+
const matchedServerIds = new Set(mergedServers.map(s => s.id));
308+
const unmatchedInstalled = installed.filter(s => !matchedServerIds.has(s.server_id));
309+
if (unmatchedInstalled.length > 0) {
310+
const offlineViewModels = unmatchedInstalled.map(state => createOfflineServerViewModel(state));
311+
mergedServers = [...mergedServers, ...offlineViewModels];
312+
}
303313
} else {
304314
// Offline case: create minimal view models from installed states only
305315
mergedServers = installed.map(state => createOfflineServerViewModel(state));

apps/desktop/src/features/settings/SettingsPage.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ export function SettingsPage() {
132132
{/* Updates Section */}
133133
<UpdateChecker />
134134

135-
{/* Startup & System Tray Section */}
136-
<Card>
135+
{/* Startup & System Tray Section - always show toggles so e2e and slow backends see the section */}
136+
<Card data-testid="settings-startup-section">
137137
<CardHeader>
138138
<CardTitle className="flex items-center gap-2">
139139
<Power className="h-5 w-5" />
@@ -145,11 +145,12 @@ export function SettingsPage() {
145145
</CardHeader>
146146
<CardContent>
147147
{loadingSettings ? (
148-
<div className="flex items-center justify-center py-8">
149-
<Loader2 className="h-6 w-6 animate-spin text-[rgb(var(--muted))]" />
148+
<div className="flex items-center gap-2 text-sm text-[rgb(var(--muted))] mb-4">
149+
<Loader2 className="h-4 w-4 animate-spin" />
150+
Loading…
150151
</div>
151-
) : (
152-
<div className="space-y-6">
152+
) : null}
153+
<div className="space-y-6">
153154
<div className="flex items-center justify-between gap-4">
154155
<div className="flex items-start gap-3 flex-1 min-w-0">
155156
<Power className="h-5 w-5 mt-0.5 text-[rgb(var(--muted))] flex-shrink-0" />
@@ -219,8 +220,7 @@ export function SettingsPage() {
219220
Saving settings...
220221
</div>
221222
)}
222-
</div>
223-
)}
223+
</div>
224224
</CardContent>
225225
</Card>
226226

packages/ui/src/components/common/Toast.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ export function Toast({
4949
return (
5050
<div
5151
className={cn(
52-
'flex items-start gap-3 p-4 rounded-lg border shadow-lg',
53-
'bg-surface border-[rgb(var(--border))]',
54-
'animate-in slide-in-from-right-full duration-300'
52+
'flex items-start gap-3 p-4 rounded-lg border shadow-lg backdrop-blur-sm',
53+
'bg-[rgb(var(--surface)/0.95)] border-[rgb(var(--border))]',
54+
'animate-in slide-in-from-bottom-4 fade-in duration-300'
5555
)}
5656
role="alert"
5757
data-testid={`toast-${type}`}
@@ -84,7 +84,7 @@ export function ToastContainer({
8484
}) {
8585
return (
8686
<div
87-
className="fixed top-4 right-4 z-50 flex flex-col gap-2 max-w-sm w-full"
87+
className="fixed bottom-6 left-1/2 -translate-x-1/2 z-50 flex flex-col-reverse gap-2 max-w-md w-full pointer-events-none [&>*]:pointer-events-auto"
8888
data-testid="toast-container"
8989
>
9090
{toasts.map((toast) => (

tests/e2e/specs/settings-desktop.wdio.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,29 @@
44
*/
55

66
import { expect, browser } from '@wdio/globals';
7+
import { byTestId, TIMEOUT } from '../helpers/selectors';
78

89
describe('Settings - Desktop Features', () => {
9-
beforeEach(async () => {
10-
// Navigate to settings page
11-
const dashboardBtn = await $('nav button[data-testid="nav-dashboard"]');
12-
await dashboardBtn.waitForClickable();
13-
await dashboardBtn.click();
10+
before(async () => {
11+
// Let the app and WebView load (spec may run in isolation so no prior tests have warmed the UI)
12+
await browser.pause(5000);
13+
// Ensure app shell is ready before any test
14+
const sidebar = await byTestId('sidebar');
15+
await sidebar.waitForDisplayed({ timeout: TIMEOUT.veryLong });
16+
const navSettings = await byTestId('nav-settings');
17+
await navSettings.waitForClickable({ timeout: TIMEOUT.medium });
18+
});
1419

15-
const settingsBtn = await $('nav button[data-testid="nav-settings"]');
16-
await settingsBtn.waitForClickable();
20+
beforeEach(async () => {
21+
// Go to Settings (same pattern as settings.wdio.ts)
22+
const settingsBtn = await byTestId('nav-settings');
1723
await settingsBtn.click();
18-
19-
// Wait for settings page to load
20-
await browser.pause(500);
24+
// Wait for desktop Startup section to be present (section is always rendered; toggles may still be loading)
25+
const startupSection = await byTestId('settings-startup-section');
26+
await startupSection.waitForDisplayed({ timeout: TIMEOUT.medium });
27+
// Wait for toggles to be interactive (get_startup_settings has resolved)
28+
const autoLaunchSwitch = await byTestId('auto-launch-switch');
29+
await autoLaunchSwitch.waitForDisplayed({ timeout: TIMEOUT.medium });
2130
});
2231

2332
describe('Startup & System Tray Settings', () => {
@@ -175,20 +184,20 @@ describe('Settings - Desktop Features', () => {
175184

176185
// Reload the page
177186
await browser.refresh();
178-
await browser.pause(1000);
179187

180-
// Navigate to settings again
188+
// Navigate to settings again and wait for section to load
181189
const settingsBtn = await $('nav button[data-testid="nav-settings"]');
182190
await settingsBtn.waitForClickable();
183191
await settingsBtn.click();
184-
await browser.pause(500);
192+
const switchAfterReload = await $('[data-testid="close-to-tray-switch"]');
193+
await switchAfterReload.waitForDisplayed({ timeout: TIMEOUT.medium });
185194

186195
// Verify state persisted
187-
const persistedState = await closeToTraySwitch.getAttribute('aria-checked');
196+
const persistedState = await switchAfterReload.getAttribute('aria-checked');
188197
expect(persistedState).not.toBe(initialState);
189198

190199
// Restore original state
191-
await closeToTraySwitch.click();
200+
await switchAfterReload.click();
192201
await browser.pause(500);
193202
});
194203

0 commit comments

Comments
 (0)