diff --git a/server/default_exercises.csv b/server/default_exercises.csv
index 0f25f53..8a9ed92 100644
--- a/server/default_exercises.csv
+++ b/server/default_exercises.csv
@@ -1,43 +1,43 @@
-name,type,bodyWeightPercentage,isUnilateral
-Air Squats,BODYWEIGHT,1.0,false
-Barbell Row,STRENGTH,0,false
-Bench Press,STRENGTH,0,false
-Bicep Curl,STRENGTH,0,true
-Bulgarian Split-Squat Jumps,BODYWEIGHT,1.0,true
-Bulgarian Split-Squats,BODYWEIGHT,1.0,true
-Burpees,BODYWEIGHT,1.0,false
-Calf Raise,STRENGTH,0,true
-Chin-Ups,BODYWEIGHT,1.0,false
-Cycling,CARDIO,0,false
-Deadlift,STRENGTH,0,false
-Dips,BODYWEIGHT,1.0,false
-Dumbbell Curl,STRENGTH,0,true
-Dumbbell Shoulder Press,STRENGTH,0,true
-Face Pull,STRENGTH,0,false
-Front Squat,STRENGTH,0,false
-Hammer Curl,STRENGTH,0,true
-Handstand,BODYWEIGHT,1.0,false
-Hip Thrust,STRENGTH,0,false
-Jump Rope,CARDIO,0,false
-Lat Pulldown,STRENGTH,0,false
-Leg Extension,STRENGTH,0,true
-Leg Press,STRENGTH,0,false
-Lunges,BODYWEIGHT,1.0,true
-Mountain Climbers,CARDIO,0,false
-Muscle-Up,BODYWEIGHT,1.0,false
-Overhead Press,STRENGTH,0,false
-Plank,STATIC,0,false
-Pull-Ups,BODYWEIGHT,1.0,false
-Push-Ups,BODYWEIGHT,0.65,false
-Romanian Deadlift,STRENGTH,0,false
-Rowing,CARDIO,0,false
-Running,CARDIO,0,false
-Russian Twist,BODYWEIGHT,0,false
-Seated Cable Row,STRENGTH,0,false
-Side Plank,STATIC,0,true
-Sissy Squats,BODYWEIGHT,1.0,false
-Sprint,CARDIO,0,false
-Squat,STRENGTH,0,false
-Treadmill,CARDIO,0,false
-Tricep Extension,STRENGTH,0,false
-Wall-Sit,STATIC,0,false
+name,name_ru,type,bodyWeightPercentage,isUnilateral
+Air Squats,Приседания,BODYWEIGHT,1.0,false
+Barbell Row,Тяга штанги в наклоне,STRENGTH,0,false
+Bench Press,Жим лежа,STRENGTH,0,false
+Bicep Curl,Подъем на бицепс,STRENGTH,0,true
+Bulgarian Split-Squat Jumps,Болгарские сплит-прыжки,BODYWEIGHT,1.0,true
+Bulgarian Split-Squats,Болгарские сплит-приседания,BODYWEIGHT,1.0,true
+Burpees,Берпи,BODYWEIGHT,1.0,false
+Calf Raise,Подъем на носки,STRENGTH,0,true
+Chin-Ups,Подтягивания обратным хватом,BODYWEIGHT,1.0,false
+Cycling,Велосипед,CARDIO,0,false
+Deadlift,Становая тяга,STRENGTH,0,false
+Dips,Отжимания на брусьях,BODYWEIGHT,1.0,false
+Dumbbell Curl,Сгибания рук с гантелями,STRENGTH,0,true
+Dumbbell Shoulder Press,Жим гантелей сидя,STRENGTH,0,true
+Face Pull,Тяга к лицу,STRENGTH,0,false
+Front Squat,Фронтальный присед,STRENGTH,0,false
+Hammer Curl,Сгибания "Молот",STRENGTH,0,true
+Handstand,Стойка на руках,BODYWEIGHT,1.0,false
+Hip Thrust,Ягодичный мостик,STRENGTH,0,false
+Jump Rope,Скакалка,CARDIO,0,false
+Lat Pulldown,Тяга верхнего блока,STRENGTH,0,false
+Leg Extension,Разгибание ног в тренажере,STRENGTH,0,true
+Leg Press,Жим ногами,STRENGTH,0,false
+Lunges,Выпады,BODYWEIGHT,1.0,true
+Mountain Climbers,Альпинист,CARDIO,0,false
+Muscle-Up,Выход силой,BODYWEIGHT,1.0,false
+Overhead Press,Армейский жим,STRENGTH,0,false
+Plank,Планка,STATIC,0,false
+Pull-Ups,Подтягивания,BODYWEIGHT,1.0,false
+Push-Ups,Отжимания,BODYWEIGHT,0.65,false
+Romanian Deadlift,Румынская тяга,STRENGTH,0,false
+Rowing,Гребля,CARDIO,0,false
+Running,Бег,CARDIO,0,false
+Russian Twist,Русский твист,BODYWEIGHT,0,false
+Seated Cable Row,Тяга блока к поясу,STRENGTH,0,false
+Side Plank,Боковая планка,STATIC,0,true
+Sissy Squats,Сисси-приседания,BODYWEIGHT,1.0,false
+Sprint,Спринт,CARDIO,0,false
+Squat,Приседания со штангой,STRENGTH,0,false
+Treadmill,Беговая дорожка,CARDIO,0,false
+Tricep Extension,Разгибание рук на трицепс,STRENGTH,0,false
+Wall-Sit,Стульчик у стены,STATIC,0,false
diff --git a/server/prisma/test.db b/server/prisma/test.db
index b03caa9..df7a0ba 100644
Binary files a/server/prisma/test.db and b/server/prisma/test.db differ
diff --git a/server/src/controllers/auth.controller.ts b/server/src/controllers/auth.controller.ts
index ca474b4..0e7ab1b 100644
--- a/server/src/controllers/auth.controller.ts
+++ b/server/src/controllers/auth.controller.ts
@@ -83,6 +83,21 @@ export class AuthController {
}
}
+ static async initializeAccount(req: any, res: Response) {
+ try {
+ const userId = req.user.userId;
+ const { language } = req.body;
+ if (!language) {
+ return sendError(res, 'Language is required', 400);
+ }
+ const user = await AuthService.initializeUser(userId, language);
+ return sendSuccess(res, { user });
+ } catch (error: any) {
+ logger.error('Error in initializeAccount', { error });
+ return sendError(res, error.message || 'Server error', 500);
+ }
+ }
+
static async getAllUsers(req: any, res: Response) {
try {
if (req.user.role !== 'ADMIN') {
diff --git a/server/src/index.ts b/server/src/index.ts
index 62e1113..4cb76de 100644
--- a/server/src/index.ts
+++ b/server/src/index.ts
@@ -65,7 +65,7 @@ async function ensureAdminUser() {
console.info(`ℹ️ Admin user exists but with different email: ${existingAdmin.email}. Expected: ${adminEmail}`);
}
// Even if admin exists, ensure exercises are seeded (will skip if already has them)
- await AuthService.seedDefaultExercises(existingAdmin.id);
+ await AuthService.seedDefaultExercises(existingAdmin.id, 'en');
await prisma.$disconnect();
return;
}
@@ -77,12 +77,12 @@ async function ensureAdminUser() {
email: adminEmail,
password: hashed,
role: 'ADMIN',
- profile: { create: { weight: 70 } },
+ profile: { create: { weight: 70, language: 'en' } },
},
});
// Seed exercises for new admin
- await AuthService.seedDefaultExercises(admin.id);
+ await AuthService.seedDefaultExercises(admin.id, 'en');
console.info(`✅ Admin user created and exercises seeded (email: ${adminEmail})`);
await prisma.$disconnect();
diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts
index e538259..d6a98eb 100644
--- a/server/src/routes/auth.ts
+++ b/server/src/routes/auth.ts
@@ -16,6 +16,7 @@ router.use(authenticateToken); // Standard middleware now
router.get('/me', AuthController.getCurrentUser);
router.post('/change-password', validate(changePasswordSchema), AuthController.changePassword);
router.patch('/profile', validate(updateProfileSchema), AuthController.updateProfile);
+router.post('/initialize', AuthController.initializeAccount);
// Admin routes
router.get('/users', AuthController.getAllUsers);
diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts
index a934cf7..bcd5fdb 100644
--- a/server/src/services/auth.service.ts
+++ b/server/src/services/auth.service.ts
@@ -67,17 +67,13 @@ export class AuthService {
include: { profile: true }
});
- // Seed default exercises
- // Seed default exercises
- await this.seedDefaultExercises(user.id);
-
const token = jwt.sign({ userId: user.id, role: user.role }, JWT_SECRET);
const { password: _, ...userSafe } = user;
return { user: userSafe, token };
}
- static async seedDefaultExercises(userId: string) {
+ static async seedDefaultExercises(userId: string, language: string = 'en') {
try {
// Ensure env is loaded from root (in case server didn't restart)
if (!process.env.DEFAULT_EXERCISES_CSV_PATH) {
@@ -110,6 +106,8 @@ export class AuthService {
const headers = lines[0].split(',').map(h => h.trim());
const exercisesToCreate = [];
+ const nameColumn = language === 'ru' ? 'name_ru' : 'name';
+
for (let i = 1; i < lines.length; i++) {
const cols = lines[i].split(',').map(c => c.trim());
if (cols.length < headers.length) continue;
@@ -117,10 +115,12 @@ export class AuthService {
const row: any = {};
headers.forEach((h, idx) => row[h] = cols[idx]);
- if (row.name && row.type) {
+ const exerciseName = row[nameColumn] || row['name'];
+
+ if (exerciseName && row.type) {
exercisesToCreate.push({
userId,
- name: row.name,
+ name: exerciseName,
type: row.type,
bodyWeightPercentage: row.bodyWeightPercentage ? parseFloat(row.bodyWeightPercentage) : 0,
isUnilateral: row.isUnilateral === 'true',
@@ -153,14 +153,34 @@ export class AuthService {
}
}
+ static async initializeUser(userId: string, language: string) {
+ // Update profile language
+ await prisma.userProfile.upsert({
+ where: { userId },
+ update: { language },
+ create: { userId, language, weight: 70 }
+ });
+
+ // Seed exercises in that language
+ await this.seedDefaultExercises(userId, language);
+
+ // Mark as first login done
+ await prisma.user.update({
+ where: { id: userId },
+ data: { isFirstLogin: false }
+ });
+
+ // Return updated user
+ return this.getUser(userId);
+ }
+
static async changePassword(userId: string, newPassword: string) {
const hashed = await bcrypt.hash(newPassword, 10);
await prisma.user.update({
where: { id: userId },
data: {
- password: hashed,
- isFirstLogin: false
+ password: hashed
}
});
}
diff --git a/src/App.tsx b/src/App.tsx
index 764acf8..455f32d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -8,6 +8,7 @@ import AICoach from './components/AICoach';
import Plans from './components/Plans';
import Login from './components/Login';
import Profile from './components/Profile';
+import InitializeAccount from './components/InitializeAccount';
import { Language, User } from './types';
import { getSystemLanguage } from './services/i18n';
import { useAuth } from './context/AuthContext';
@@ -49,6 +50,10 @@ function App() {
return ;
}
+ if (currentUser?.isFirstLogin && location.pathname !== '/initialize') {
+ return ;
+ }
+
return (
{currentUser && (
@@ -66,6 +71,17 @@ function App() {
)
} />
+
+ ) : (
+
+ )
+ } />
} />
diff --git a/src/components/InitializeAccount.tsx b/src/components/InitializeAccount.tsx
new file mode 100644
index 0000000..a5f107b
--- /dev/null
+++ b/src/components/InitializeAccount.tsx
@@ -0,0 +1,99 @@
+import React, { useState } from 'react';
+import { initializeAccount } from '../services/auth';
+import { User, Language } from '../types';
+import { Globe, ArrowRight, Check } from 'lucide-react';
+import { t } from '../services/i18n';
+
+interface InitializeAccountProps {
+ onInitialized: (user: User) => void;
+ language: Language;
+ onLanguageChange: (lang: Language) => void;
+}
+
+const InitializeAccount: React.FC
= ({ onInitialized, language, onLanguageChange }) => {
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [error, setError] = useState('');
+
+ const handleInitialize = async () => {
+ setIsSubmitting(true);
+ setError('');
+ const res = await initializeAccount(language);
+ if (res.success && res.user) {
+ onInitialized(res.user);
+ } else {
+ setError(res.error || 'Failed to initialize account');
+ setIsSubmitting(false);
+ }
+ };
+
+ const languages: { code: Language; label: string; desc: string }[] = [
+ { code: 'en', label: 'English', desc: t('init_lang_en_desc', language) },
+ { code: 'ru', label: 'Русский', desc: t('init_lang_ru_desc', language) },
+ ];
+
+ return (
+
+
+
+
+
+
+
+ {t('init_title', language)}
+
+
+ {t('init_desc', language)}
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+ {languages.map((lang) => (
+
+ ))}
+
+
+
+
+
+ );
+};
+
+export default InitializeAccount;
diff --git a/src/components/Login.tsx b/src/components/Login.tsx
index 1041335..cb76869 100644
--- a/src/components/Login.tsx
+++ b/src/components/Login.tsx
@@ -41,7 +41,7 @@ const Login: React.FC = ({ onLogin, language, onLanguageChange }) =>
if (tempUser && newPassword.length >= 4) {
const res = await changePassword(tempUser.id, newPassword);
if (res.success) {
- const updatedUser = { ...tempUser, isFirstLogin: false };
+ const updatedUser = { ...tempUser };
onLogin(updatedUser);
} else {
setError(res.error || t('change_pass_error', language));
diff --git a/src/services/auth.ts b/src/services/auth.ts
index e5ccbe2..ed3c289 100644
--- a/src/services/auth.ts
+++ b/src/services/auth.ts
@@ -149,3 +149,19 @@ export const getMe = async (): Promise<{ success: boolean; user?: User; error?:
return { success: false, error: 'Failed to fetch user' };
}
};
+export const initializeAccount = async (language: string): Promise<{ success: boolean; user?: User; error?: string }> => {
+ try {
+ const res = await api.post>('/auth/initialize', { language });
+ if (res.success && res.data) {
+ return { success: true, user: res.data.user };
+ }
+ return { success: false, error: res.error };
+ } catch (e: any) {
+ try {
+ const err = JSON.parse(e.message);
+ return { success: false, error: err.error || 'Failed to initialize account' };
+ } catch {
+ return { success: false, error: 'Failed to initialize account' };
+ }
+ }
+};
diff --git a/src/services/i18n.ts b/src/services/i18n.ts
index d0e5ba4..038c15f 100644
--- a/src/services/i18n.ts
+++ b/src/services/i18n.ts
@@ -36,6 +36,12 @@ const translations = {
register_btn: 'Register',
have_account: 'Already have an account? Login',
need_account: 'Need an account? Register',
+ init_title: 'Setup Your Account',
+ init_desc: 'Welcome! Choose your preferred language.',
+ init_select_lang: 'Select Language',
+ init_start: 'Get Started',
+ init_lang_en_desc: 'GUI and default exercise names will be in English',
+ init_lang_ru_desc: 'GUI and default exercise names will be in Russian',
// General
date: 'Date',
@@ -253,6 +259,12 @@ const translations = {
register_btn: 'Зарегистрироваться',
have_account: 'Уже есть аккаунт? Войти',
need_account: 'Нет аккаунта? Регистрация',
+ init_title: 'Настройка аккаунта',
+ init_desc: 'Добро пожаловать! Выберите предпочтительный язык.',
+ init_select_lang: 'Выберите язык',
+ init_start: 'Начать работу',
+ init_lang_en_desc: 'Интерфейс и названия упражнений по умолчанию будут на английском',
+ init_lang_ru_desc: 'Интерфейс и названия упражнений по умолчанию будут на русском',
// General
date: 'Дата',
diff --git a/tests/01_core_auth.spec.ts b/tests/01_core_auth.spec.ts
index 9bcd3bf..ebb94b4 100644
--- a/tests/01_core_auth.spec.ts
+++ b/tests/01_core_auth.spec.ts
@@ -12,65 +12,61 @@ test.describe('I. Core & Authentication', () => {
// Helper to handle first login if needed
async function handleFirstLogin(page: any) {
- // Wait for either Free Workout (already logged in/not first time)
- // OR Change Password heading
- // OR Error message
- try {
- const heading = page.getByRole('heading', { name: /Change Password/i });
- const dashboard = page.getByText('Free Workout');
- const loginButton = page.getByRole('button', { name: 'Login' });
+ console.log('Starting handleFirstLogin helper...');
+ const dashboard = page.getByText(/Free Workout|Свободная тренировка/i).first();
+ const changePass = page.getByRole('heading', { name: /Change Password|Смена пароля/i });
+ const initAcc = page.getByRole('heading', { name: /Setup Your Account|Настройка аккаунта/i });
- // Race condition: wait for one of these to appear
- // We use a small polling or just wait logic.
- // Playwright doesn't have "race" for locators easily without Promise.race
-
- // Simple approach: Check if Change Password appears within 5s
- await expect(heading).toBeVisible({ timeout: 5000 });
-
- // If we are here, Change Password is visible
- console.log('Change Password screen detected. Handling...');
- await page.getByLabel('New Password').fill('StrongNewPass123!');
- await page.getByRole('button', { name: /Save|Change/i }).click();
-
- // Now expect dashboard
- await expect(dashboard).toBeVisible();
- console.log('Password changed. Dashboard visible.');
- } catch (e) {
- // If Change Password didn't appear, maybe we are already at dashboard?
- if (await page.getByText('Free Workout').isVisible()) {
- console.log('Already at Dashboard.');
+ for (let i = 0; i < 30; i++) {
+ if (await dashboard.isVisible()) {
+ console.log('Dashboard visible.');
return;
}
- // Check for login error
- const error = page.locator('.text-error');
- if (await error.isVisible()) {
- console.log('Login Error detected:', await error.textContent());
- throw new Error(`Login failed: ${await error.textContent()}`);
+
+ if (await changePass.isVisible()) {
+ console.log('Change Password screen detected. Handling...');
+ await page.getByLabel(/New Password|Новый пароль/i).fill('StrongNewPass123!');
+ await page.getByRole('button', { name: /Save|Change|Сохранить/i }).click();
+ // Wait a bit for transition
+ await page.waitForTimeout(1000);
+ continue;
}
- // Note: If none of the above, it might be a clean login that just worked fast or failed silently
+
+ if (await initAcc.isVisible()) {
+ console.log('Initialization screen detected. Handling...');
+ await page.getByRole('button', { name: /Get Started|Начать работу/i }).click();
+ // Wait a bit for transition
+ await page.waitForTimeout(1000);
+ continue;
+ }
+
+ await page.waitForTimeout(500);
}
+
+ // Final check with assertion to fail the test if not reached
+ await expect(dashboard).toBeVisible({ timeout: 5000 });
}
// 1.1. A. Login - Successful Authentication
test('1.1 Login - Successful Authentication', async ({ page, createUniqueUser }) => {
const user = await createUniqueUser();
- await page.getByLabel('Email').fill(user.email);
- await page.getByLabel('Password').fill(user.password);
- await page.getByRole('button', { name: 'Login' }).click();
+ await page.getByLabel(/Email/i).fill(user.email);
+ await page.getByLabel(/Password|Пароль/i).fill(user.password);
+ await page.getByRole('button', { name: /Login|Войти/i }).click();
await handleFirstLogin(page);
// Expect redirection to dashboard
await expect(page).not.toHaveURL(/\/login/);
- await expect(page.getByText('Free Workout')).toBeVisible();
+ await expect(page.getByText(/Free Workout|Свободная тренировка/i).first()).toBeVisible();
});
// 1.2. A. Login - Invalid Credentials
test('1.2 Login - Invalid Credentials', async ({ page }) => {
- await page.getByLabel('Email').fill('invalid@user.com');
- await page.getByLabel('Password').fill('wrongpassword');
- await page.getByRole('button', { name: 'Login' }).click();
+ await page.getByLabel(/Email/i).fill('invalid@user.com');
+ await page.getByLabel(/Password|Пароль/i).fill('wrongpassword');
+ await page.getByRole('button', { name: /Login|Войти/i }).click();
await expect(page.getByText('Invalid credentials')).toBeVisible();
await expect(page.getByRole('button', { name: 'Login' })).toBeVisible();
@@ -79,31 +75,34 @@ test.describe('I. Core & Authentication', () => {
test('1.3 & 1.4 Login - First-Time Password Change', async ({ page, createUniqueUser }) => {
const user = await createUniqueUser();
- await page.getByLabel('Email').fill(user.email);
- await page.getByLabel('Password').fill(user.password);
- await page.getByRole('button', { name: 'Login' }).click();
+ await page.getByLabel(/Email/i).fill(user.email);
+ await page.getByLabel(/Password|Пароль/i).fill(user.password);
+ await page.getByRole('button', { name: /Login|Войти/i }).click();
- await expect(page.getByRole('heading', { name: /Change Password/i }).first()).toBeVisible({ timeout: 10000 });
+ await expect(page.getByRole('heading', { name: /Change Password|Смена пароля/i }).first()).toBeVisible({ timeout: 10000 });
// 1.4 Test short password
- await page.getByLabel('New Password').fill('123');
- await page.getByRole('button', { name: /Save|Change/i }).click();
- await expect(page.getByText('Password too short')).toBeVisible();
+ await page.getByLabel(/New Password|Новый пароль/i).fill('123');
+ await page.getByRole('button', { name: /Save|Change|Сохранить/i }).click();
+ await expect(page.getByText(/Password too short|Пароль слишком короткий/i)).toBeVisible();
// 1.3 Test successful change
- await page.getByLabel('New Password').fill('StrongNewPass123!');
- await page.getByRole('button', { name: /Save|Change/i }).click();
+ await page.getByLabel(/New Password|Новый пароль/i).fill('StrongNewPass123!');
+ await page.getByRole('button', { name: /Save|Change|Сохранить/i }).click();
+
+ // Now we should be on Setup Account page
+ await expect(page.getByRole('heading', { name: /Setup Your Account|Настройка аккаунта/i })).toBeVisible();
+ await page.getByRole('button', { name: /Get Started|Начать работу/i }).click();
// Now we should be logged in
- await expect(page.getByText('Free Workout')).toBeVisible();
+ await expect(page.getByText(/Free Workout|Свободная тренировка/i).first()).toBeVisible();
});
- // 1.5. A. Login - Language Selection (English)
test('1.5 Login - Language Selection (English)', async ({ page }) => {
await page.getByRole('combobox').selectOption('en');
- await expect(page.getByLabel('Email')).toBeVisible();
- await expect(page.getByRole('button', { name: 'Login' })).toBeVisible();
+ await expect(page.getByLabel(/Email/i)).toBeVisible();
+ await expect(page.getByRole('button', { name: /Login|Войти/i })).toBeVisible();
});
// 1.6. A. Login - Language Selection (Russian)
@@ -116,26 +115,26 @@ test.describe('I. Core & Authentication', () => {
test('1.7 Navigation - Desktop Navigation Rail', async ({ page, createUniqueUser }) => {
const user = await createUniqueUser();
- await page.getByLabel('Email').fill(user.email);
- await page.getByLabel('Password').fill(user.password);
- await page.getByRole('button', { name: 'Login' }).click();
+ await page.getByLabel(/Email/i).fill(user.email);
+ await page.getByLabel(/Password|Пароль/i).fill(user.password);
+ await page.getByRole('button', { name: /Login|Войти/i }).click();
await handleFirstLogin(page);
// Set viewport to desktop
await page.setViewportSize({ width: 1280, height: 720 });
- await expect(page.getByRole('button', { name: 'Tracker' }).first()).toBeVisible();
- await expect(page.getByRole('button', { name: 'Plans' }).first()).toBeVisible();
+ await expect(page.getByRole('button', { name: /Tracker|Трекер/i }).first()).toBeVisible();
+ await expect(page.getByRole('button', { name: /Plans|Планы/i }).first()).toBeVisible();
});
// 1.8. B. Navigation - Mobile Bottom Navigation Bar
test('1.8 Navigation - Mobile Bottom Navigation Bar', async ({ page, createUniqueUser }) => {
const user = await createUniqueUser();
- await page.getByLabel('Email').fill(user.email);
- await page.getByLabel('Password').fill(user.password);
- await page.getByRole('button', { name: 'Login' }).click();
+ await page.getByLabel(/Email/i).fill(user.email);
+ await page.getByLabel(/Password|Пароль/i).fill(user.password);
+ await page.getByRole('button', { name: /Login|Войти/i }).click();
await handleFirstLogin(page);
@@ -145,7 +144,44 @@ test.describe('I. Core & Authentication', () => {
await page.waitForTimeout(500); // Allow layout transition
// Verify visibility of mobile nav items
- await expect(page.getByRole('button', { name: 'Tracker' }).last()).toBeVisible();
+ await expect(page.getByRole('button', { name: /Tracker|Трекер/i }).last()).toBeVisible();
+ });
+
+ // 1.9. C. Initialization - Russian Language Seeding
+ test('1.9 Initialization - Russian Language Seeding', async ({ page, createUniqueUser }) => {
+ const user = await createUniqueUser();
+
+ await page.getByLabel(/Email/i).fill(user.email);
+ await page.getByLabel(/Password|Пароль/i).fill(user.password);
+ await page.getByRole('button', { name: /Login|Войти/i }).click();
+
+ // Handle password change
+ await expect(page.getByRole('heading', { name: /Change Password|Смена пароля/i })).toBeVisible();
+ await page.getByLabel(/New Password|Новый пароль/i).fill('StrongNewPass123!');
+ await page.getByRole('button', { name: /Save|Change|Сохранить/i }).click();
+
+ // Handle initialization - Select Russian
+ await expect(page.getByRole('heading', { name: /Setup Your Account|Настройка аккаунта/i })).toBeVisible();
+ await page.getByText('Русский').click();
+ await page.getByRole('button', { name: /Начать работу|Get Started/i }).click();
+
+ // Expect dashboard
+ await expect(page.getByText('Свободная тренировка')).toBeVisible();
+
+ // Verify some exercise is in Russian
+ await page.getByText(/Свободная тренировка|Free Workout/i).first().click();
+ await page.getByLabel(/Выберите упражнение|Select Exercise/i).click();
+
+ // "Air Squats" should be "Приседания" in suggestions
+ await expect(page.getByRole('button', { name: 'Приседания', exact: true })).toBeVisible();
+ await page.getByRole('button', { name: 'Приседания', exact: true }).click();
+
+ // Verify it's selected in the input
+ const exerciseInput = page.getByLabel(/Выберите упражнение|Select Exercise/i);
+ await expect(exerciseInput).toHaveValue('Приседания');
+
+ // Verify "Log Set" button is now in Russian
+ await expect(page.getByRole('button', { name: /Записать подход|Log Set/i })).toBeVisible();
});
});