Skip to content

Commit d450dd9

Browse files
authored
🐛 fix: improve e2e server and complete i18n resources (lobehub#11678)
- Refactor webServer.ts with better process coordination and lock file mechanism - Add mock S3 env vars to prevent initialization errors - Complete missing i18n translations across all locales
1 parent e1666a5 commit d450dd9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+1114
-46
lines changed

e2e/src/support/webServer.ts

Lines changed: 95 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { type ChildProcess, exec } from 'node:child_process';
1+
import { type ChildProcess, spawn } from 'node:child_process';
22
import { existsSync, unlinkSync, writeFileSync } from 'node:fs';
3+
import { connect } from 'node:net';
34
import { resolve } from 'node:path';
45

56
let serverProcess: ChildProcess | null = null;
@@ -8,6 +9,21 @@ let serverStartPromise: Promise<void> | null = null;
89
// File-based lock to coordinate between parallel workers
910
const LOCK_FILE = resolve(__dirname, '../../.server-starting.lock');
1011

12+
export async function stopWebServer(): Promise<void> {
13+
if (serverProcess) {
14+
console.log('🛑 Stopping web server...');
15+
serverProcess.kill();
16+
serverProcess = null;
17+
serverStartPromise = null;
18+
}
19+
// Clean up lock file
20+
try {
21+
unlinkSync(LOCK_FILE);
22+
} catch {
23+
// Ignore if file doesn't exist
24+
}
25+
}
26+
1127
interface WebServerOptions {
1228
command: string;
1329
env?: Record<string, string>;
@@ -16,15 +32,36 @@ interface WebServerOptions {
1632
timeout?: number;
1733
}
1834

19-
async function isServerRunning(port: number): Promise<boolean> {
20-
try {
21-
const response = await fetch(`http://localhost:${port}/chat`, {
22-
method: 'HEAD',
35+
async function isServerRunning(port: number, timeoutMs = 2000): Promise<boolean> {
36+
const hosts = new Set(['127.0.0.1', '::1', 'localhost']);
37+
const envHost = process.env.HOST?.trim();
38+
if (envHost && envHost !== '0.0.0.0' && envHost !== '::') {
39+
hosts.add(envHost);
40+
}
41+
42+
const tryConnect = (host: string) =>
43+
new Promise<boolean>((resolve) => {
44+
const socket = connect({ host, port });
45+
const timeoutId = setTimeout(() => {
46+
socket.destroy();
47+
resolve(false);
48+
}, timeoutMs);
49+
50+
const finish = (result: boolean) => {
51+
clearTimeout(timeoutId);
52+
socket.destroy();
53+
resolve(result);
54+
};
55+
56+
socket.once('connect', () => finish(true));
57+
socket.once('error', () => finish(false));
2358
});
24-
return response.ok;
25-
} catch {
26-
return false;
59+
60+
for (const host of hosts) {
61+
if (await tryConnect(host)) return true;
2762
}
63+
64+
return false;
2865
}
2966

3067
export async function startWebServer(options: WebServerOptions): Promise<void> {
@@ -94,27 +131,41 @@ export async function startWebServer(options: WebServerOptions): Promise<void> {
94131
// Get the project root directory (parent of e2e folder)
95132
const projectRoot = resolve(__dirname, '../../..');
96133

97-
// Start the server process
98-
serverProcess = exec(command, {
134+
const serverEnv = {
135+
...process.env,
136+
// E2E test secret keys
137+
BETTER_AUTH_SECRET: 'e2e-test-secret-key-for-better-auth-32chars!',
138+
KEY_VAULTS_SECRET: 'LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s=',
139+
// Disable email verification for e2e
140+
NEXT_PUBLIC_AUTH_EMAIL_VERIFICATION: '0',
141+
// Enable Better Auth for e2e tests with real authentication
142+
NEXT_PUBLIC_ENABLE_BETTER_AUTH: '1',
143+
NODE_OPTIONS: '--max-old-space-size=6144',
144+
PORT: String(port),
145+
// Mock S3 env vars to prevent initialization errors
146+
S3_ACCESS_KEY_ID: 'e2e-mock-access-key',
147+
S3_BUCKET: 'e2e-mock-bucket',
148+
S3_ENDPOINT: 'https://e2e-mock-s3.localhost',
149+
S3_SECRET_ACCESS_KEY: 'e2e-mock-secret-key',
150+
...env,
151+
};
152+
153+
// Start the server process (spawn avoids maxBuffer limits)
154+
serverProcess = spawn(command, {
99155
cwd: projectRoot,
100-
env: {
101-
...process.env,
102-
// E2E test secret keys
103-
BETTER_AUTH_SECRET: 'e2e-test-secret-key-for-better-auth-32chars!',
104-
KEY_VAULTS_SECRET: 'LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s=',
105-
// Disable email verification for e2e
106-
NEXT_PUBLIC_AUTH_EMAIL_VERIFICATION: '0',
107-
// Enable Better Auth for e2e tests with real authentication
108-
NEXT_PUBLIC_ENABLE_BETTER_AUTH: '1',
109-
NODE_OPTIONS: '--max-old-space-size=6144',
110-
PORT: String(port),
111-
// Mock S3 env vars to prevent initialization errors
112-
S3_ACCESS_KEY_ID: 'e2e-mock-access-key',
113-
S3_BUCKET: 'e2e-mock-bucket',
114-
S3_ENDPOINT: 'https://e2e-mock-s3.localhost',
115-
S3_SECRET_ACCESS_KEY: 'e2e-mock-secret-key',
116-
...env,
117-
},
156+
env: serverEnv,
157+
shell: true,
158+
stdio: ['ignore', 'pipe', 'pipe'],
159+
});
160+
161+
let startupError: Error | null = null;
162+
serverProcess.once('error', (error) => {
163+
startupError = error;
164+
});
165+
serverProcess.once('exit', (code, signal) => {
166+
startupError = new Error(
167+
`Server exited before ready (code: ${code ?? 'unknown'}, signal: ${signal ?? 'none'})`,
168+
);
118169
});
119170

120171
// Forward server output to console for debugging
@@ -129,6 +180,9 @@ export async function startWebServer(options: WebServerOptions): Promise<void> {
129180
// Wait for server to be ready
130181
const startTime = Date.now();
131182
while (!(await isServerRunning(port))) {
183+
if (startupError) {
184+
throw startupError;
185+
}
132186
if (Date.now() - startTime > timeout) {
133187
throw new Error(`Server failed to start within ${timeout}ms`);
134188
}
@@ -138,22 +192,20 @@ export async function startWebServer(options: WebServerOptions): Promise<void> {
138192
}
139193

140194
console.log(`✅ Web server is ready on port ${port}`);
141-
})();
195+
})().catch(async (error) => {
196+
serverStartPromise = null;
197+
try {
198+
unlinkSync(LOCK_FILE);
199+
await stopWebServer();
200+
} catch {
201+
// Ignore if file doesn't exist
202+
}
203+
throw error;
204+
});
142205

143206
return serverStartPromise;
144207
}
145208

146-
export async function stopWebServer(): Promise<void> {
147-
if (serverProcess) {
148-
console.log('🛑 Stopping web server...');
149-
serverProcess.kill();
150-
serverProcess = null;
151-
serverStartPromise = null;
152-
}
153-
// Clean up lock file
154-
try {
155-
unlinkSync(LOCK_FILE);
156-
} catch {
157-
// Ignore if file doesn't exist
158-
}
159-
}
209+
process.on('exit', () => {
210+
void stopWebServer();
211+
});

locales/ar/chat.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@
204204
"noSelectedAgents": "لم يتم تحديد أي أعضاء بعد",
205205
"openInNewWindow": "فتح في نافذة جديدة",
206206
"operation.execAgentRuntime": "جارٍ تحضير الرد",
207+
"operation.execClientTask": "تنفيذ المهمة",
207208
"operation.sendMessage": "جارٍ إرسال الرسالة",
208209
"owner": "مالك المجموعة",
209210
"pageCopilot.title": "وكيل الصفحة",
@@ -322,11 +323,15 @@
322323
"tab.profile": "ملف الوكيل",
323324
"tab.search": "بحث",
324325
"task.activity.calling": "جارٍ استدعاء المهارة...",
326+
"task.activity.clientExecuting": "يتم التنفيذ محليًا...",
325327
"task.activity.generating": "جارٍ توليد الرد...",
326328
"task.activity.gotResult": "تم استلام نتيجة الأداة",
327329
"task.activity.toolCalling": "جارٍ استدعاء {{toolName}}...",
328330
"task.activity.toolResult": "تم استلام نتيجة {{toolName}}",
329331
"task.batchTasks": "{{count}} مهمة فرعية مجمعة",
332+
"task.instruction": "تعليمات المهمة",
333+
"task.intermediateSteps": "{{count}} خطوة وسيطة",
334+
"task.metrics.duration": "(استغرق {{duration}})",
330335
"task.metrics.stepsShort": "خطوات",
331336
"task.metrics.toolCallsShort": "استخدامات الأداة",
332337
"task.status.cancelled": "تم إلغاء المهمة",

locales/ar/desktop-onboarding.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,18 @@
5858
"screen4.title": "كيف تود مشاركة البيانات؟",
5959
"screen4.title2": "اختيارك يساعدنا على التحسين",
6060
"screen4.title3": "يمكنك تغيير ذلك في أي وقت من الإعدادات",
61+
"screen5.actions.cancel": "إلغاء",
6162
"screen5.actions.connectToServer": "الاتصال بالخادم",
6263
"screen5.actions.connecting": "جارٍ الاتصال...",
6364
"screen5.actions.signInCloud": "تسجيل الدخول إلى LobeHub Cloud",
6465
"screen5.actions.signOut": "تسجيل الخروج",
6566
"screen5.actions.signingIn": "جارٍ تسجيل الدخول...",
6667
"screen5.actions.signingOut": "جارٍ تسجيل الخروج...",
6768
"screen5.actions.tryAgain": "حاول مرة أخرى",
69+
"screen5.auth.phase.browserOpened": "تم فتح المتصفح، يرجى تسجيل الدخول...",
70+
"screen5.auth.phase.verifying": "جارٍ التحقق من بيانات الاعتماد...",
71+
"screen5.auth.phase.waitingForAuth": "في انتظار التفويض...",
72+
"screen5.auth.remaining": "المتبقي: {{time}}ث",
6873
"screen5.badge": "تسجيل الدخول",
6974
"screen5.description": "سجّل الدخول لمزامنة الوكلاء والمجموعات والإعدادات والسياق عبر جميع الأجهزة.",
7075
"screen5.errors.desktopOnlyOidc": "تفويض OIDC متاح فقط في تطبيق سطح المكتب.",

locales/ar/discover.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,16 @@
141141
"filterBy.timePeriod.year": "العام الماضي",
142142
"footer.desc": "تطوّر مع مستخدمي الذكاء الاصطناعي حول العالم. كن منشئًا وشارك وكلاءك ومهاراتك في مجتمع LobeHub.",
143143
"footer.title": "شارك إبداعاتك في مجتمع LobeHub اليوم",
144+
"fork.alreadyForked": "لقد قمت بالفعل بعمل نسخة من هذا الوكيل. يتم الآن الانتقال إلى نسختك...",
145+
"fork.failed": "فشل النسخ. يرجى المحاولة مرة أخرى.",
146+
"fork.forkAndChat": "انسخ وابدأ المحادثة",
147+
"fork.forkedFrom": "تم النسخ من",
148+
"fork.forks": "نسخ",
149+
"fork.forksCount": "{{count}} نسخة",
150+
"fork.forksCount_other": "{{count}} نسخ",
151+
"fork.success": "تم النسخ بنجاح!",
152+
"fork.viewAllForks": "عرض جميع النسخ",
153+
"groupAgents.tag": "مجموعة",
144154
"home.communityAgents": "وكلاء المجتمع",
145155
"home.featuredAssistants": "وكلاء مميزون",
146156
"home.featuredModels": "نماذج مميزة",
@@ -471,14 +481,19 @@
471481
"user.follow": "متابعة",
472482
"user.followers": "المتابعون",
473483
"user.following": "يتابع",
484+
"user.forkedAgentGroups": "مجموعات الوكلاء المنسوخة",
485+
"user.forkedAgents": "الوكلاء المنسوخون",
474486
"user.login": "كن منشئًا",
475487
"user.logout": "تسجيل الخروج",
476488
"user.myProfile": "ملفي الشخصي",
477489
"user.noAgents": "لم ينشر هذا المستخدم أي وكلاء بعد",
478490
"user.noFavoriteAgents": "لا يوجد وكلاء محفوظون بعد",
479491
"user.noFavoritePlugins": "لا توجد مهارات محفوظة بعد",
492+
"user.noForkedAgentGroups": "لا توجد مجموعات وكلاء منسوخة بعد",
493+
"user.noForkedAgents": "لا يوجد وكلاء منسوخون بعد",
480494
"user.publishedAgents": "الوكلاء المنشؤون",
481495
"user.tabs.favorites": "المفضلة",
496+
"user.tabs.forkedAgents": "المنسوخون",
482497
"user.tabs.publishedAgents": "تم الإنشاء",
483498
"user.unfavorite": "إلغاء الحفظ",
484499
"user.unfavoriteFailed": "فشل في إلغاء الحفظ",

0 commit comments

Comments
 (0)