Skip to content

Commit e0fd2b5

Browse files
author
Mohammod Al Amin Ashik
committed
Add data-testid attributes and comprehensive E2E tests (41 passing)
1 parent 33f71b2 commit e0fd2b5

23 files changed

Lines changed: 3466 additions & 100 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ playwright-report/
5858
blob-report/
5959
html-report/
6060
tests/e2e/reports/
61+
tests/e2e/screenshots/
62+
tests/e2e/videos/
6163

6264
# Tauri
6365
apps/desktop/src-tauri/target/

apps/desktop/src/App.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,18 +93,21 @@ function AppContent() {
9393
label="Dashboard"
9494
active={activeNav === 'home'}
9595
onClick={() => setActiveNav('home')}
96+
data-testid="nav-dashboard"
9697
/>
9798
<SidebarItem
9899
icon={<Zap className="h-4 w-4" />}
99100
label="My Servers"
100101
active={activeNav === 'servers'}
101102
onClick={() => setActiveNav('servers')}
103+
data-testid="nav-my-servers"
102104
/>
103105
<SidebarItem
104106
icon={<Server className="h-4 w-4" />}
105107
label="Discover"
106108
active={activeNav === 'registry'}
107109
onClick={() => setActiveNav('registry')}
110+
data-testid="nav-discover"
108111
/>
109112
</SidebarSection>
110113

@@ -114,12 +117,14 @@ function AppContent() {
114117
label="Spaces"
115118
active={activeNav === 'spaces'}
116119
onClick={() => setActiveNav('spaces')}
120+
data-testid="nav-spaces"
117121
/>
118122
<SidebarItem
119123
icon={<Wrench className="h-4 w-4" />}
120124
label="FeatureSets"
121125
active={activeNav === 'featuresets'}
122126
onClick={() => setActiveNav('featuresets')}
127+
data-testid="nav-featuresets"
123128
/>
124129
</SidebarSection>
125130

@@ -129,6 +134,7 @@ function AppContent() {
129134
label="Clients"
130135
active={activeNav === 'clients'}
131136
onClick={() => setActiveNav('clients')}
137+
data-testid="nav-clients"
132138
/>
133139
</SidebarSection>
134140

@@ -138,6 +144,7 @@ function AppContent() {
138144
label="Settings"
139145
active={activeNav === 'settings'}
140146
onClick={() => setActiveNav('settings')}
147+
data-testid="nav-settings"
141148
/>
142149
</SidebarSection>
143150
</Sidebar>

apps/desktop/src/features/registry/RegistryPage.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,11 @@ export function RegistryPage() {
9191
const hasActiveFilters = Object.values(activeFilters).some(v => v && v !== 'all');
9292

9393
return (
94-
<div className="h-full flex flex-col">
94+
<div className="h-full flex flex-col" data-testid="registry-page">
9595
{/* Header */}
9696
<div className="p-6 border-b border-[rgb(var(--border-subtle))]">
9797
<div className="flex items-center gap-3 mb-1">
98-
<h1 className="text-2xl font-bold">Discover Servers</h1>
98+
<h1 className="text-2xl font-bold" data-testid="registry-title">Discover Servers</h1>
9999
{isOffline && (
100100
<span className="px-2 py-0.5 text-xs font-medium bg-amber-500/20 text-amber-600 dark:text-amber-400 rounded-full">
101101
Offline
@@ -133,6 +133,7 @@ export function RegistryPage() {
133133
value={localSearch}
134134
onChange={(e) => setLocalSearch(e.target.value)}
135135
className="input w-full pl-10"
136+
data-testid="search-input"
136137
/>
137138
</div>
138139

@@ -225,7 +226,7 @@ export function RegistryPage() {
225226

226227
{/* Footer: Stats & Pagination */}
227228
<div className="p-4 border-t border-[rgb(var(--border-subtle))] flex items-center justify-between bg-[rgb(var(--surface))]">
228-
<div className="text-sm text-[rgb(var(--muted))]">
229+
<div className="text-sm text-[rgb(var(--muted))]" data-testid="server-count">
229230
{displayServers.length} server{displayServers.length !== 1 ? 's' : ''} found
230231
{servers.filter((s) => s.is_installed).length > 0 && (
231232
<span className="ml-2 border-l border-[rgb(var(--border-subtle))] pl-2">

apps/desktop/src/features/registry/ServerCard.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,14 @@ export function ServerCard({
6868
hover:border-[rgb(var(--primary))]/50 hover:shadow-lg
6969
transition-all duration-200 cursor-pointer shadow-sm"
7070
onClick={() => onViewDetails(server)}
71+
data-testid={`server-card-${server.id}`}
7172
>
7273
{/* Header */}
7374
<div className="flex items-start gap-3 mb-3">
7475
<div className="text-3xl flex-shrink-0">{server.icon || '📦'}</div>
7576
<div className="flex-1 min-w-0">
7677
<div className="flex items-center gap-1.5">
77-
<h3 className="font-semibold truncate" title={server.name}>
78+
<h3 className="font-semibold truncate" title={server.name} data-testid={`server-name-${server.id}`}>
7879
{server.name}
7980
</h3>
8081
{server.publisher?.verified && (
@@ -132,6 +133,7 @@ export function ServerCard({
132133
disabled={isLoading}
133134
className="px-4 py-1.5 text-sm rounded-lg border border-[rgb(var(--error))]/30 text-[rgb(var(--error))]
134135
hover:bg-[rgb(var(--error))]/10 transition-colors disabled:opacity-50"
136+
data-testid={`uninstall-btn-${server.id}`}
135137
>
136138
Uninstall
137139
</button>
@@ -144,6 +146,7 @@ export function ServerCard({
144146
disabled={isLoading}
145147
className="px-4 py-1.5 text-sm rounded-lg bg-[rgb(var(--primary))] text-[rgb(var(--primary-foreground))]
146148
hover:bg-[rgb(var(--primary-hover))] transition-colors disabled:opacity-50"
149+
data-testid={`install-btn-${server.id}`}
147150
>
148151
Install
149152
</button>

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -761,11 +761,11 @@ export function ServersPage() {
761761
}
762762

763763
return (
764-
<div className="space-y-6">
764+
<div className="space-y-6" data-testid="servers-page">
765765
{/* Header */}
766766
<div className="flex items-center justify-between">
767767
<div>
768-
<h1 className="text-2xl font-bold">My Servers</h1>
768+
<h1 className="text-2xl font-bold" data-testid="servers-title">My Servers</h1>
769769
<p className="text-sm text-[rgb(var(--muted))]">
770770
Manage your installed MCP servers
771771
</p>
@@ -843,6 +843,7 @@ export function ServersPage() {
843843
className={`bg-[rgb(var(--card))] border border-[rgb(var(--border-subtle))] rounded-xl shadow-sm transition-all ${
844844
!server.enabled && 'opacity-60'
845845
}`}
846+
data-testid={`installed-server-${server.id}`}
846847
>
847848
{/* Server Header */}
848849
<div className="p-4">

apps/desktop/src/features/spaces/SpacesPage.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export function SpacesPage() {
112112
Manage isolated environments with their own credentials and server configurations
113113
</p>
114114
</div>
115-
<Button variant="primary" size="md" onClick={() => setShowCreateModal(true)}>
115+
<Button variant="primary" size="md" onClick={() => setShowCreateModal(true)} data-testid="create-space-btn">
116116
<Plus className="h-4 w-4 mr-2" />
117117
Create Space
118118
</Button>
@@ -182,6 +182,7 @@ export function SpacesPage() {
182182
className={`transition-all hover:shadow-lg hover:scale-[1.01] ${
183183
isActive ? 'ring-2 ring-primary-500 shadow-lg' : ''
184184
}`}
185+
data-testid={`space-card-${space.id}`}
185186
>
186187
<CardContent className="p-5">
187188
{/* Header */}
@@ -209,6 +210,7 @@ export function SpacesPage() {
209210
disabled={isProcessing || isActive}
210211
className="p-1.5 text-[rgb(var(--muted))] hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
211212
title="Delete Space"
213+
data-testid={`delete-space-${space.id}`}
212214
>
213215
<Trash2 className="h-4 w-4" />
214216
</button>
@@ -224,6 +226,7 @@ export function SpacesPage() {
224226
variant="secondary"
225227
onClick={() => handleSetActive(space.id)}
226228
disabled={isProcessing}
229+
data-testid={`set-active-space-${space.id}`}
227230
>
228231
{isProcessing ? (
229232
<Loader2 className="h-3 w-3 animate-spin mr-2" />
@@ -247,8 +250,8 @@ export function SpacesPage() {
247250

248251
{/* Create Modal */}
249252
{showCreateModal && (
250-
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
251-
<Card className="w-full max-w-md mx-4 animate-in fade-in zoom-in-95 duration-200 shadow-2xl">
253+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50" data-testid="create-space-modal-overlay">
254+
<Card className="w-full max-w-md mx-4 animate-in fade-in zoom-in-95 duration-200 shadow-2xl" data-testid="create-space-modal">
252255
<CardHeader>
253256
<CardTitle className="flex items-center justify-between">
254257
<span className="flex items-center gap-2">
@@ -292,6 +295,7 @@ export function SpacesPage() {
292295
placeholder="e.g., Personal, Work, Project X"
293296
className="w-full px-3 py-2.5 rounded-lg border border-[rgb(var(--border))] bg-[rgb(var(--surface))] focus:outline-none focus:ring-2 focus:ring-primary-500"
294297
autoFocus
298+
data-testid="create-space-name-input"
295299
/>
296300
</div>
297301

@@ -300,6 +304,7 @@ export function SpacesPage() {
300304
variant="ghost"
301305
onClick={() => setShowCreateModal(false)}
302306
className="flex-1"
307+
data-testid="create-space-cancel-btn"
303308
>
304309
Cancel
305310
</Button>
@@ -308,6 +313,7 @@ export function SpacesPage() {
308313
onClick={handleCreate}
309314
disabled={isCreating || !newSpaceName.trim()}
310315
className="flex-1"
316+
data-testid="create-space-submit-btn"
311317
>
312318
{isCreating ? <Loader2 className="h-4 w-4 animate-spin mr-2" /> : 'Create Space'}
313319
</Button>

package.json

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
"test:rust:doc": "cargo test --workspace --doc",
1818
"test:ts": "vitest run -c tests/ts/vitest.config.ts",
1919
"test:ts:watch": "vitest -c tests/ts/vitest.config.ts",
20-
"test:e2e": "pnpm exec wdio run tests/e2e/wdio.conf.ts",
20+
"test:e2e": "cross-env MCPMUX_REGISTRY_URL=http://localhost:8787 pnpm exec wdio run tests/e2e/wdio.conf.ts",
21+
"test:e2e:grep": "cross-env MCPMUX_REGISTRY_URL=http://localhost:8787 pnpm exec wdio run tests/e2e/wdio.conf.ts --grep",
22+
"test:e2e:file": "cross-env MCPMUX_REGISTRY_URL=http://localhost:8787 pnpm exec wdio run tests/e2e/wdio.conf.ts --spec",
23+
"test:e2e:video": "cross-env MCPMUX_REGISTRY_URL=http://localhost:8787 SAVE_ALL_VIDEOS=true pnpm exec wdio run tests/e2e/wdio.conf.ts",
2124
"test:e2e:web": "pnpm exec playwright test -c tests/e2e/playwright.config.ts",
2225
"test:e2e:web:headed": "pnpm exec playwright test -c tests/e2e/playwright.config.ts --headed",
2326
"test:ci": "pnpm test",
@@ -31,24 +34,33 @@
3134
},
3235
"devDependencies": {
3336
"@changesets/cli": "^2.27.0",
37+
"@modelcontextprotocol/sdk": "^1.10.0",
3438
"@playwright/test": "^1.50.0",
3539
"@testing-library/jest-dom": "^6.6.0",
3640
"@testing-library/react": "^16.0.0",
3741
"@testing-library/user-event": "^14.5.0",
42+
"@types/cors": "^2.8.17",
43+
"@types/express": "^5.0.0",
3844
"@vitejs/plugin-react": "^4.6.0",
3945
"@vitest/coverage-v8": "^3.0.0",
4046
"@wdio/cli": "^9.0.0",
4147
"@wdio/local-runner": "^9.0.0",
4248
"@wdio/mocha-framework": "^9.0.0",
4349
"@wdio/spec-reporter": "^9.0.0",
50+
"cors": "^2.8.5",
51+
"cross-env": "^10.1.0",
52+
"express": "^5.0.1",
4453
"jsdom": "^25.0.0",
4554
"prettier": "^3.4.2",
4655
"prettier-plugin-tailwindcss": "^0.6.9",
4756
"react": "^19.1.0",
4857
"react-dom": "^19.1.0",
4958
"ts-node": "^10.9.0",
59+
"tsx": "^4.19.0",
5060
"typescript": "^5.7.2",
51-
"vitest": "^3.0.0"
61+
"vitest": "^3.0.0",
62+
"wdio-video-reporter": "^6.2.0",
63+
"zod": "^3.23.8"
5264
},
5365
"engines": {
5466
"node": ">=20.0.0",

packages/ui/src/components/layout/Sidebar.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@ interface SidebarItemProps {
3030
label: string;
3131
active?: boolean;
3232
onClick?: () => void;
33+
'data-testid'?: string;
3334
}
3435

35-
export function SidebarItem({ icon, label, active, onClick }: SidebarItemProps) {
36+
export function SidebarItem({ icon, label, active, onClick, 'data-testid': testId }: SidebarItemProps) {
3637
return (
3738
<button
3839
onClick={onClick}
40+
data-testid={testId}
3941
className={cn(
4042
'flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-150',
4143
'text-[rgb(var(--muted))] hover:text-[rgb(var(--foreground))] hover:bg-[rgb(var(--surface-hover))]',

0 commit comments

Comments
 (0)