e2e tests added. Core & Authentication

This commit is contained in:
AG
2025-12-08 10:18:42 +02:00
parent d284563301
commit 615c3a0cb7
16 changed files with 740 additions and 133 deletions

View File

@@ -0,0 +1,44 @@
---
description: Standard workflow for implementing new features, including testing, verification, and documentation updates.
---
# Feature Implementation & Verification Workflow
Follow this process whenever a new feature is requested.
## 1. Implementation Phase
1. **Analyze Request**: Understand the user's requirements.
2. **Plan Changes**: Create or update the `implementation_plan.md`.
3. **Execute**: Write the code to implement the feature.
## 2. Verification Phase (E2E Testing)
Once the feature is implemented, you must verify it works as expected.
1. **Run Existing Tests**: Ensure no regressions.
```powershell
npx playwright test
```
2. **Manual Verification**: If Playwright tests are not sufficient or applicable yet, perform manual verification steps (or use the browser tool to simulate it).
3. **Fix Bugs**: If any issues are found (either in automated tests or manual verification), fix them immediately. Repeat this step until the feature is stable.
## 3. Documentation Phase
After the feature is stable and working:
1. **Update Requirements**: Modify `specs/requirements.md` to reflect the newly added functionality and any changes to existing logic. This is the source of truth.
2. **Update Test Plan**:
* **File**: `specs/gymflow-test-plan.md`
* **Action**: Update this file with new test scenarios. Use the **Playwright-test-generator** agent to help identify gaps or create new detailed scenarios based on the updated requirements.
## 4. Test Generation Phase
1. **Generate New Tests**:
* **Agent**: Use the `playwright-test-generator` agent.
* **Workflow**:
* Invoke the agent.
* Provide it with the updated `specs/requirements.md` and `specs/gymflow-test-plan.md`.
* Ask it to generate Playwright tests for the new scenarios.
* **File Path Convention**: `tests/<feature-name>/<scenario>.spec.ts`.
* **Verify**: Ensure the new tests pass reliably.
## 5. Finalize
1. **Cleanup**: Remove any temporary debug logs or unused files.
2. **Notify User**: Inform the user that the feature is complete, verified, and documented.

View File

@@ -0,0 +1,95 @@
---
name: playwright-test-generator
description: 'Use this agent when you need to create automated browser tests
using Playwright Examples: <example>Context: User wants to generate a test for
the test plan item. <test-suite><!-- Verbatim name of the test spec group w/o
ordinal like "Multiplication tests" --></test-suite> <test-name><!-- Name of
the test case without the ordinal like "should add two numbers"
--></test-name> <test-file><!-- Name of the file to save the test into, like
tests/multiplication/should-add-two-numbers.spec.ts --></test-file>
<seed-file><!-- Seed file path from test plan --></seed-file> <body><!-- Test
case content including steps and expectations --></body></example>'
tools:
- search
- playwright-test/browser_click
- playwright-test/browser_drag
- playwright-test/browser_evaluate
- playwright-test/browser_file_upload
- playwright-test/browser_handle_dialog
- playwright-test/browser_hover
- playwright-test/browser_navigate
- playwright-test/browser_press_key
- playwright-test/browser_select_option
- playwright-test/browser_snapshot
- playwright-test/browser_type
- playwright-test/browser_verify_element_visible
- playwright-test/browser_verify_list_visible
- playwright-test/browser_verify_text_visible
- playwright-test/browser_verify_value
- playwright-test/browser_wait_for
- playwright-test/generator_read_log
- playwright-test/generator_setup_page
- playwright-test/generator_write_test
model: Claude Sonnet 4
mcp-servers:
playwright-test:
type: stdio
command: npx
args:
- playwright
- run-test-mcp-server
tools:
- "*"
---
You are a Playwright Test Generator, an expert in browser automation and end-to-end testing.
Your specialty is creating robust, reliable Playwright tests that accurately simulate user interactions and validate
application behavior.
# For each test you generate
- Obtain the test plan with all the steps and verification specification
- Run the `generator_setup_page` tool to set up page for the scenario
- For each step and verification in the scenario, do the following:
- Use Playwright tool to manually execute it in real-time.
- Use the step description as the intent for each Playwright tool call.
- Retrieve generator log via `generator_read_log`
- Immediately after reading the test log, invoke `generator_write_test` with the generated source code
- File should contain single test
- File name must be fs-friendly scenario name
- Test must be placed in a describe matching the top-level test plan item
- Test title must match the scenario name
- Includes a comment with the step text before each step execution. Do not duplicate comments if step requires
multiple actions.
- Always use best practices from the log when generating tests.
<example-generation>
For following plan:
```markdown file=specs/plan.md
### 1. Adding New Todos
**Seed:** `tests/seed.spec.ts`
#### 1.1 Add Valid Todo
**Steps:**
1. Click in the "What needs to be done?" input field
#### 1.2 Add Multiple Todos
...
```
Following file is generated:
```ts file=add-valid-todo.spec.ts
// spec: specs/plan.md
// seed: tests/seed.spec.ts
test.describe('Adding New Todos', () => {
test('Add Valid Todo', async { page } => {
// 1. Click in the "What needs to be done?" input field
await page.click(...);
...
});
});
```
</example-generation>

View File

@@ -0,0 +1,63 @@
---
name: playwright-test-healer
description: Use this agent when you need to debug and fix failing Playwright tests
tools:
- search
- edit
- playwright-test/browser_console_messages
- playwright-test/browser_evaluate
- playwright-test/browser_generate_locator
- playwright-test/browser_network_requests
- playwright-test/browser_snapshot
- playwright-test/test_debug
- playwright-test/test_list
- playwright-test/test_run
model: Claude Sonnet 4
mcp-servers:
playwright-test:
type: stdio
command: npx
args:
- playwright
- run-test-mcp-server
tools:
- "*"
---
You are the Playwright Test Healer, an expert test automation engineer specializing in debugging and
resolving Playwright test failures. Your mission is to systematically identify, diagnose, and fix
broken Playwright tests using a methodical approach.
Your workflow:
1. **Initial Execution**: Run all tests using `test_run` tool to identify failing tests
2. **Debug failed tests**: For each failing test run `test_debug`.
3. **Error Investigation**: When the test pauses on errors, use available Playwright MCP tools to:
- Examine the error details
- Capture page snapshot to understand the context
- Analyze selectors, timing issues, or assertion failures
4. **Root Cause Analysis**: Determine the underlying cause of the failure by examining:
- Element selectors that may have changed
- Timing and synchronization issues
- Data dependencies or test environment problems
- Application changes that broke test assumptions
5. **Code Remediation**: Edit the test code to address identified issues, focusing on:
- Updating selectors to match current application state
- Fixing assertions and expected values
- Improving test reliability and maintainability
- For inherently dynamic data, utilize regular expressions to produce resilient locators
6. **Verification**: Restart the test after each fix to validate the changes
7. **Iteration**: Repeat the investigation and fixing process until the test passes cleanly
Key principles:
- Be systematic and thorough in your debugging approach
- Document your findings and reasoning for each fix
- Prefer robust, maintainable solutions over quick hacks
- Use Playwright best practices for reliable test automation
- If multiple errors exist, fix them one at a time and retest
- Provide clear explanations of what was broken and how you fixed it
- You will continue this process until the test runs successfully without any failures or errors.
- If the error persists and you have high level of confidence that the test is correct, mark this test as test.fixme()
so that it is skipped during the execution. Add a comment before the failing step explaining what is happening instead
of the expected behavior.
- Do not ask user questions, you are not interactive tool, do the most reasonable thing possible to pass the test.
- Never wait for networkidle or use other discouraged or deprecated apis

View File

@@ -0,0 +1,81 @@
---
name: playwright-test-planner
description: Use this agent when you need to create comprehensive test plan for
a web application or website
tools:
- search
- playwright-test/browser_click
- playwright-test/browser_close
- playwright-test/browser_console_messages
- playwright-test/browser_drag
- playwright-test/browser_evaluate
- playwright-test/browser_file_upload
- playwright-test/browser_handle_dialog
- playwright-test/browser_hover
- playwright-test/browser_navigate
- playwright-test/browser_navigate_back
- playwright-test/browser_network_requests
- playwright-test/browser_press_key
- playwright-test/browser_select_option
- playwright-test/browser_snapshot
- playwright-test/browser_take_screenshot
- playwright-test/browser_type
- playwright-test/browser_wait_for
- playwright-test/planner_setup_page
- playwright-test/planner_save_plan
model: Claude Sonnet 4
mcp-servers:
playwright-test:
type: stdio
command: npx
args:
- playwright
- run-test-mcp-server
tools:
- "*"
---
You are an expert web test planner with extensive experience in quality assurance, user experience testing, and test
scenario design. Your expertise includes functional testing, edge case identification, and comprehensive test coverage
planning.
You will:
1. **Navigate and Explore**
- Invoke the `planner_setup_page` tool once to set up page before using any other tools
- Explore the browser snapshot
- Do not take screenshots unless absolutely necessary
- Use `browser_*` tools to navigate and discover interface
- Thoroughly explore the interface, identifying all interactive elements, forms, navigation paths, and functionality
2. **Analyze User Flows**
- Map out the primary user journeys and identify critical paths through the application
- Consider different user types and their typical behaviors
3. **Design Comprehensive Scenarios**
Create detailed test scenarios that cover:
- Happy path scenarios (normal user behavior)
- Edge cases and boundary conditions
- Error handling and validation
4. **Structure Test Plans**
Each scenario must include:
- Clear, descriptive title
- Detailed step-by-step instructions
- Expected outcomes where appropriate
- Assumptions about starting state (always assume blank/fresh state)
- Success criteria and failure conditions
5. **Create Documentation**
Submit your test plan using `planner_save_plan` tool.
**Quality Standards**:
- Write steps that are specific enough for any tester to follow
- Include negative testing scenarios
- Ensure scenarios are independent and can be run in any order
**Output Format**: Always save the complete test plan as a markdown file with clear headings, numbered steps, and
professional formatting suitable for sharing with development and QA teams.

View File

@@ -0,0 +1,34 @@
name: "Copilot Setup Steps"
on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml
jobs:
copilot-setup-steps:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
# Customize this step as needed
- name: Build application
run: npx run build

View File

@@ -9,8 +9,9 @@
"preview": "vite preview", "preview": "vite preview",
"dev:full": "npm-run-all --parallel dev \"dev --prefix server\"", "dev:full": "npm-run-all --parallel dev \"dev --prefix server\"",
"dev:full:bg": "npm-run-all --parallel dev \"dev --prefix server\" &", "dev:full:bg": "npm-run-all --parallel dev \"dev --prefix server\" &",
"test:full": "npm-run-all --parallel dev \"start:test --prefix server\"", "prod:full": "npm-run-all --parallel preview \"start:prod --prefix server\"",
"prod:full": "npm-run-all --parallel preview \"start:prod --prefix server\"" "server:test": "npm run start:test --prefix server",
"test:full": "npm-run-all --parallel dev server:test"
}, },
"dependencies": { "dependencies": {
"@google/genai": "^1.30.0", "@google/genai": "^1.30.0",
@@ -34,4 +35,4 @@
"typescript": "~5.8.2", "typescript": "~5.8.2",
"vite": "^6.2.0" "vite": "^6.2.0"
} }
} }

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@@ -302,6 +302,37 @@ Comprehensive test plan for the GymFlow web application, covering authentication
**Expected Results:** **Expected Results:**
- The 'Name' input field for new exercises correctly prompts for capitalization on mobile keyboards, enhancing user experience. - The 'Name' input field for new exercises correctly prompts for capitalization on mobile keyboards, enhancing user experience.
#### 2.12. B. Exercise Library - Create Unilateral Exercise
**File:** `tests/workout-management.spec.ts`
**Steps:**
1. Log in as a regular user.
2. Navigate to the 'Profile' section -> 'Exercise Manager'.
3. Click 'Create Exercise'.
4. Enter name 'One Arm Row'.
5. Select 'Strength' type.
6. Toggle the 'Unilateral' switch to ON.
7. Click 'Create'.
**Expected Results:**
- The new exercise is created.
- It is marked as unilateral in the database/UI logic (though visual indicator might differ).
#### 2.13. B. Exercise Library - Create Special Type Exercises
**File:** `tests/workout-management.spec.ts`
**Steps:**
1. Log in as a regular user.
2. Create a 'Static' exercise (e.g., 'Plank').
3. Create a 'High Jump' exercise (e.g., 'Box Jump').
4. Create a 'Long Jump' exercise (e.g., 'Broad Jump').
5. Create a 'Plyometric' exercise (e.g., 'Burpees').
**Expected Results:**
- All exercises are created successfully with their respective types.
### 3. III. Workout Tracking ### 3. III. Workout Tracking
**Seed:** `tests/workout-tracking.spec.ts` **Seed:** `tests/workout-tracking.spec.ts`
@@ -497,6 +528,52 @@ Comprehensive test plan for the GymFlow web application, covering authentication
- The exercise list filters dynamically as the user types. - The exercise list filters dynamically as the user types.
- The search field content is cleared on focus. - The search field content is cleared on focus.
#### 3.14. C. Active Session - Log Unilateral Set
**File:** `tests/workout-tracking.spec.ts`
**Steps:**
1. Start a Free Workout (or Plan with unilateral exercise).
2. Select a Unilateral exercise (created in 2.12).
3. Enter Weight/Reps.
4. Select 'Left' from the Side selector.
5. Click 'Log Set'.
6. Repeat for 'Right' side.
**Expected Results:**
- Sets are logged with the correct 'Left'/'Right' indicators visible in the history.
#### 3.15. C. Active Session - Log Special Type Set
**File:** `tests/workout-tracking.spec.ts`
**Steps:**
1. Start a Free Workout.
2. Select a Static exercise -> Enter Duration.
3. Select a High Jump exercise -> Enter Height.
4. Select a Long Jump exercise -> Enter Distance.
5. Select a Plyometric exercise -> Enter Reps.
6. Log a set for each.
**Expected Results:**
- Each set is logged with the correct specific metric (Height, Distance, Duration, etc.).
#### 3.16. C. Active Session - Smart Plan Matching
**File:** `tests/workout-tracking.spec.ts`
**Steps:**
1. Start a Plan with 2 exercises (Ex A, Ex B).
2. Log a set for Ex A (matching plan). Verify it counts towards plan progress.
3. Manually search and select Ex B (skipping Ex A).
4. Log a set for Ex B.
**Expected Results:**
- The system detects the mismatch or allows it.
- If "Smart Matching" is strict, it might warn or just log it as an extra set.
- If "Smart Matching" is flexible, it might advance progress for Ex B (depending on spec).
- *Assumption based on Requirements*: "System attempts to match... activeExerciseId returned". Verify the UI updates focus to the relevant step if matched.
### 4. IV. Data & Progress ### 4. IV. Data & Progress
**Seed:** `tests/data-progress.spec.ts` **Seed:** `tests/data-progress.spec.ts`

View File

@@ -1,143 +1,133 @@
# Application Requirements # GymFlow Application Requirements Specification
This document outlines the functional requirements of the GymFlow application, derived from an analysis of its React components and backend services. ## 1. Introduction
This document serves as the authoritative source of truth for the GymFlow application functionality. It is derived from a deep analysis of the production codebase (React frontend + Node.js/Prisma backend). These requirements are intended to guide future development and serve as the basis for automated test generation (Playwright).
## I. Core & Authentication ## 2. System Actors
* **Guest**: An unauthenticated user (restricted to Login/Register).
* **User**: A standard authenticated user with access to tracking, plans, and capabilities.
* **Admin**: A super-user with additional privileges to manage other users (block, delete, reset passwords).
### A. Login (`Login.tsx`) ---
- **User Authentication:** Users must be able to log in using their email and password.
- **First-Time Password Change:** On their initial login, users are required to change their temporary password.
- **Language Selection:** The interface language can be toggled between English and Russian directly on the login screen.
### B. Navigation (`Navbar.tsx`) ## 3. Functional Requirements
- **Primary Navigation:** A persistent navigation bar (bottom on mobile, side rail on desktop) allows access to the main sections of the application:
- Tracker
- Plans
- History
- Stats
- AI Coach
- Profile
## II. Workout Management ### 3.1. Authentication & Integrity
The system relies on JWT-based authentication.
### A. Workout Plans (`Plans.tsx`) * **3.1.1 Login**
- **CRUD Operations:** Users can create, view, update, and delete their workout plans. * **Input**: Email, Password.
- **Plan Composition:** A plan consists of a name, a descriptive text (for preparation/instructions), and an ordered list of exercises. * **Logic**:
- **Exercises List Reordering:** Exercises within a plan can be reordered via a drag-and-drop interface. * Must validate email existence.
- **Starting a Session:** Users can initiate a workout session from a selected plan in the list. * Must validate password matches hash.
* **Block Check**: Must prevent login if `User.isBlocked` is true, returning a 403 error.
* **UI**:
* Show "Invalid credentials" on failure.
* Redirect to Tracker upon success.
* **Language Toggle**: User must be able to switch between English (EN) and Russian (RU) before logging in.
* **3.1.2 Registration**
* **Input**: Email, Password.
* **Logic**:
* Email must be unique.
* Creates a `UserProfile` with default values (e.g., default weight 70kg) upon account creation.
* Sets `isFirstLogin` to true.
* **3.1.3 First-Time Setup (Password Change)**
* **Trigger**: If `User.isFirstLogin` is true.
* **Requirement**: User must be forced to change password immediately after initial login.
* **Effect**: Updates `User.password` and sets `isFirstLogin` to false.
* **3.1.4 Profile Management**
* **Fields**: Weight, Height, Gender, Birth Date.
* **Logic**:
* Updates to `UserProfile` table.
* Dates are handled as ISO strings or timestamps.
### B. Exercise Library (`Plans.tsx`, `Profile.tsx`) ### 3.2. Workout Management (Plans)
- **Custom Exercise Creation:** Users can define new exercises, specifying: Users can structure their training via Plans.
- Name: The input field should suggest capitalizing each word for mobile users.
- Type (e.g., Strength, Bodyweight, Cardio)
- For bodyweight exercises, a percentage of body weight to be used in calculations.
- **Exercise Management:** From the profile section, users can view all their created exercises, edit their names, and archive or unarchive them.
## III. Workout Tracking * **3.2.1 Plan Creation/Editing**
* **Data**: Name (required), Description (optional).
* **Exercises**:
* User selects exercises from the global/personal library.
* **Ordering**: Exercises must be ordered (0-indexed).
* **Weighted Flag**: specific exercises in a plan can be marked as `isWeighted` (visual indicator).
* **Logic**: Supports reordering capabilities via drag-and-drop in UI.
* **3.2.2 Plan Deletion**
* Standard soft or hard delete (Cascades to PlanExercises).
### A. Tracker Hub (`Tracker/index.tsx`) ### 3.3. Exercise Library
- **Central Control:** This component orchestrates the tracking experience, displaying the appropriate view based on the current state (idle, in-session, or sporadic logging). * **3.3.1 Exercise Types**
The system supports distinct exercise types which dictate valid data entry fields:
* `STRENGTH`: Requires **Weight (kg)** and **Reps**.
* `BODYWEIGHT`: Requires **Reps** (and optional added Weight).
* `CARDIO`: Requires **Duration (s)** and **Distance (m)**.
* `STATIC`: Requires **Duration (s)**.
* `HIGH_JUMP`: Requires **Height (cm)**.
* `LONG_JUMP`: Requires **Distance (m)**.
* `PLYOMETRIC`: Requires **Reps**.
* **3.3.2 Custom Exercises**
* User can create new exercises.
* **Unilateral Flag**: Boolean flag `isUnilateral`. If true, sets recorded for this exercise can specify a `side` (LEFT/RIGHT).
* **Bodyweight %**: For bodyweight-based calculations.
### B. Idle State (`Tracker/IdleView.tsx`) ### 3.4. Workout Tracking (The "Tracker")
- **Session Initiation:** From the idle screen, users can: The core feature. States: **Idle**, **Active Session**, **Sporadic Mode**.
- Start a "Free Workout" without a predefined plan.
- Begin a "Quick Log" session for sporadic, one-off sets.
- Select and start a workout from their list of saved plans.
- **Session Body Weight Entry:** Users can input their current body weight before starting a session; the default value comes from user profile; this weight is associated with the active session for accurate calculations.
- **Plan Preview:** If a plan includes a description, it is displayed to the user for review before the session begins.
### C. Active Session (`Tracker/ActiveSessionView.tsx`) * **3.4.1 Active Session (Standard)**
- **Real-Time Timer:** A running timer displays the elapsed time for the current workout. * **Initiation**:
- **Exercise Selection:** Users can search for and select the exercise they are performing. The search field filters available exercises as the user types and clears its content automatically when focused. New exercises can be created on-the-fly. * Can start "Free Workout" (no plan).
- **Set Logging:** Users can log completed sets, with input fields dynamically adjusting based on the selected exercise's type (e.g., weight/reps for strength, duration/distance for cardio). * Can start from a "Plan".
- **Session Body Weight:** The body weight entered before the session is displayed and used for calculations within the active session. * **Input**: User confirms current Body Weight before starting.
- **Session History:** A chronological list of sets logged during the current session is displayed. * **Constraint**: User cannot have more than one active standard session (where `endTime` is null). API returns 400 if one exists.
- **Set Management:** Logged sets can be edited to correct data or deleted entirely. * **The "Active" State**:
- **Plan Progression:** When following a plan, the interface highlights the current exercise. Users can view the full plan and jump to different steps if needed. * **Persistence**: Session remains active in DB if browser is closed. `GET /sessions/active` restores state.
- **Session Control:** Users can finish the session (saving the data) or quit without saving. * **Timer**: Client-side logic calculates `now - startTime`.
* **Logging Sets**:
* **Input**: Exercise, Metrics (Weight/Reps/etc. based on Type), Side (if unilateral).
* **Smart Matching**: If a Plan is active, the system attempts to match the logged set to the current step in the plan based on the exercise ID and the number of sets already performed vs. planned.
* **Response**: Returns the created set and the `activeExerciseId` (for UI to auto-advance focus in plan).
* **Session Termination**:
* **Finish**: Updates `endTime` to now.
* **Profile Sync**: If the session had a `userBodyWeight` set, it updates the `UserProfile.weight`.
* **Quit (No Save)**:
* **Destructive Action**: Hard deletes the `WorkoutSession` and all associated `WorkoutSets` from the DB.
* **3.4.2 Active Session (Quick Log)**
* **Concept**: A special session type (`QUICK_LOG`) that aggregates all sporadic sets for a single day.
* **Logic**:
* `GET /sessions/quick-log`: Finds strictly one session for the current calendar day (00:00-23:59 local server time) of type `QUICK_LOG`.
* `POST /sessions/quick-log/set`:
* Finds OR Creates the daily Quick Log session.
* Appends the set.
* **UI**: Separate "Sporadic Mode" view specialized for fast, one-off entries without a timer or plan context.
### D. Sporadic Logging (`Tracker/SporadicView.tsx`) ### 3.5. History & Analysis
- **Ad-Hoc Tracking:** Enables logging of individual sets outside of a structured workout session. * **3.5.1 Session History**
- **Quick Access:** Optimized for quickly selecting an exercise, entering metrics, and logging the set. The exercise selection field filters available exercises as the user types and clears its content automatically when focused. * Displays all finished sessions.
- **Daily History:** Displays a list of all sporadic sets logged on the current day. * Groups by Date.
* **Quick Logs**: Displayed alongside standard workouts, identifiable by type.
* **3.5.2 Statistics**
* Visualizes progress over time.
* **Key Metrics**: Volume (Weight * Reps), Frequency, Body Weight trends.
## IV. Data & Progress ### 3.6. User Interface Logic
* **3.6.1 Navigation**
* **Mobile**: Bottom navigation bar.
* **Desktop**: Side rail navigation.
* **Responsiveness**: Main layout container adapts padding/margins based on viewport breakpoint.
* **3.6.2 Input Behavior**
* **Search**: Exercise selectors must support text search filtering.
* **Auto-Clear**: Search inputs should clear/select text on focus for rapid entry.
### A. Session History (`History.tsx`) ### 3.7. Admin Capabilities
- **Workout Review:** Provides a comprehensive list of all past workout sessions and sporadic sets, grouped by day. Accessible only if `User.role === 'ADMIN'`.
- **Detailed View:** Users can inspect the details of each session, including all sets performed.
- **Data Correction:** Past sessions and their individual sets can be edited or deleted.
### B. Performance Statistics (`Stats.tsx`) * **User List**: View all registered users (showing Block status, First Login status).
- **Visualizations:** The application generates and displays charts to track progress over time, including: * **Actions**:
- **Total Volume:** The total weight lifted in each session. * **Toggle Block**: Ban/Unban access.
- **Set Count:** The number of sets performed per session. * **Delete User**: Permanent removal.
- **Body Weight:** A line graph of the user's body weight over time. * **Reset Password**: Admin can manually trigger password reset flows.
## V. User & System Management ## 4. Technical Constants & Constraints
* **Database**: SQLite (via Prisma).
### A. User Profile (`Profile.tsx`) * **API Schema**: REST-like (JSON).
- **Personal Information:** Users can view and update their personal data, including weight, height, birth date, and gender. * **Timezones**: Dates stored as UTC in DB, displayed in Local Time on client.
- **Dedicated Daily Weight Logging:** Users can log their body weight daily, and view a history of these records. * **Offline Mode**: Currently NOT supported (requires active connection for mutations).
- **Account Management:** Users can change their password and (if not an admin) delete their own account.
- **Language Preference:** The application language can be changed.
### B. AI Coach (`AICoach.tsx`)
- **Conversational AI:** Provides an interactive chat interface with an AI fitness expert.
- **Contextual Awareness:** The AI is primed with the user's workout history, plans, and profile data to provide personalized advice and analysis.
### C. Admin Panel (within `Profile.tsx`)
- **User Administration:** Admin-level users have access to a special panel to manage the user base.
- **User CRUD:** Admins can create new users, view a list of all users, block/unblock, and delete users.
- **Password Resets:** Admins can reset the passwords for non-admin users.
## VI. User Interface & Experience
### A. Adaptive GUI
- **Responsive Navigation:**
- On mobile devices, the application displays a bottom navigation bar.
- On desktop devices, a vertical navigation rail is displayed on the left side.
- **Fluid Layout:** The application's content layout adapts to different screen sizes, ensuring usability across a wide range of devices.
- **Responsive Charts:** Data visualizations in the statistics section are responsive and scale to fit the available screen space.
## VII. Backend API Endpoints
The frontend components interact with a backend service through the following conceptual endpoints:
- **Authentication:**
- `POST /auth/login`
- `POST /auth/change-password`
- `GET /auth/me`
- **Users (Admin):**
- `GET /users`
- `POST /users/create`
- `DELETE /users/:id`
- `PUT /users/toggle-block/:id`
- `POST /users/admin-reset-password`
- **User Profile:**
- `PUT /users/profile/:id`
- **Workout Plans:**
- `GET /plans`
- `POST /plans`
- `DELETE /plans/:id`
- **Exercises:**
- `GET /exercises`
- `POST /exercises`
- **Workout Sessions:**
- `GET /sessions`
- `POST /sessions/start`
- `POST /sessions/end`
- `POST /sessions/quit`
- `POST /sessions/active/log-set`
- `PUT /sessions/:id`
- `DELETE /sessions/:id`
- **Sporadic Sets:**
- `GET /sporadic-sets`
- `POST /sporadic-sets`
- `PUT /sporadic-sets/:id`
- `DELETE /sporadic-sets/:id`
- **Weight Logging:**
- `GET /weight`
- `POST /weight`
- **AI Service:**
- `POST /ai/chat`

View File

@@ -1,4 +1,4 @@
import React from 'react'; import React, { useId } from 'react';
import { X } from 'lucide-react'; import { X } from 'lucide-react';
interface FilledInputProps { interface FilledInputProps {
@@ -19,6 +19,8 @@ interface FilledInputProps {
} }
const FilledInput: React.FC<FilledInputProps> = ({ label, value, onChange, onClear, onFocus, onBlur, type = "number", icon, autoFocus, step, inputMode, autocapitalize, autoComplete, rightElement }) => { const FilledInput: React.FC<FilledInputProps> = ({ label, value, onChange, onClear, onFocus, onBlur, type = "number", icon, autoFocus, step, inputMode, autocapitalize, autoComplete, rightElement }) => {
const id = useId();
const handleClear = () => { const handleClear = () => {
const syntheticEvent = { const syntheticEvent = {
target: { value: '' } target: { value: '' }
@@ -29,10 +31,11 @@ const FilledInput: React.FC<FilledInputProps> = ({ label, value, onChange, onCle
return ( return (
<div className="relative group bg-surface-container-high rounded-t-lg border-b border-outline-variant hover:bg-white/5 focus-within:border-primary transition-colors"> <div className="relative group bg-surface-container-high rounded-t-lg border-b border-outline-variant hover:bg-white/5 focus-within:border-primary transition-colors">
<label className="absolute top-2 left-4 text-[10px] font-medium text-on-surface-variant flex items-center gap-1"> <label htmlFor={id} className="absolute top-2 left-4 text-[10px] font-medium text-on-surface-variant flex items-center gap-1">
{icon} {label} {icon} {label}
</label> </label>
<input <input
id={id}
type={type} type={type}
step={step} step={step}
inputMode={inputMode || (type === 'number' ? 'decimal' : 'text')} inputMode={inputMode || (type === 'number' ? 'decimal' : 'text')}

View File

@@ -58,6 +58,8 @@ const Login: React.FC<LoginProps> = ({ onLogin, language, onLanguageChange }) =>
<h2 className="text-2xl text-on-surface mb-2">{t('change_pass_title', language)}</h2> <h2 className="text-2xl text-on-surface mb-2">{t('change_pass_title', language)}</h2>
<p className="text-sm text-on-surface-variant mb-6">{t('change_pass_desc', language)}</p> <p className="text-sm text-on-surface-variant mb-6">{t('change_pass_desc', language)}</p>
{error && <div className="text-error text-sm text-center bg-error-container/10 p-2 rounded-lg mb-4">{error}</div>}
<div className="space-y-4"> <div className="space-y-4">
<FilledInput <FilledInput
label={t('change_pass_new', language)} label={t('change_pass_new', language)}

155
tests/core-auth.spec.ts Normal file
View File

@@ -0,0 +1,155 @@
import { test, expect } from './fixtures';
test.describe('I. Core & Authentication', () => {
test.beforeEach(async ({ page }) => {
// Console logs for debugging
page.on('console', msg => console.log('PAGE LOG:', msg.text()));
page.on('pageerror', exception => console.log(`PAGE ERROR: ${exception}`));
await page.goto('/');
});
// 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' });
// 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.');
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()}`);
}
console.log('Failed to handle first login. Dumping page content...');
console.log(await page.content());
throw e;
}
}
// 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 handleFirstLogin(page);
// Expect redirection to dashboard
await expect(page).not.toHaveURL(/\/login/);
await expect(page.getByText('Free Workout')).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 expect(page.getByText('Invalid credentials')).toBeVisible();
await expect(page.getByRole('button', { name: 'Login' })).toBeVisible();
});
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 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();
// 1.3 Test successful change
await page.getByLabel('New Password').fill('StrongNewPass123!');
await page.getByRole('button', { name: /Save|Change/i }).click();
// Now we should be logged in
await expect(page.getByText('Free Workout')).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();
});
// 1.6. A. Login - Language Selection (Russian)
test('1.6 Login - Language Selection (Russian)', async ({ page }) => {
await page.getByRole('combobox').selectOption('ru');
await expect(page.getByRole('button', { name: 'Войти' })).toBeVisible();
});
// 1.7. B. Navigation - Desktop Navigation Rail
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 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();
});
// 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 handleFirstLogin(page);
// Set viewport to mobile
await page.setViewportSize({ width: 375, height: 667 });
await page.waitForTimeout(500); // Allow layout transition
// Verify visibility of mobile nav items
await expect(page.getByRole('button', { name: 'Tracker' }).last()).toBeVisible();
});
});

55
tests/fixtures.ts Normal file
View File

@@ -0,0 +1,55 @@
import { test as base, expect } from '@playwright/test';
import { request } from '@playwright/test';
// Define the type for our custom fixtures
type MyFixtures = {
createUniqueUser: () => Promise<{ email: string, password: string, id: string }>;
};
// Extend the base test with our custom fixture
export const test = base.extend<MyFixtures>({
createUniqueUser: async ({ }, use) => {
// We use a new API context for setup to avoid polluting request history,
// although setup requests are usually separate anyway.
const apiContext = await request.newContext({
baseURL: 'http://localhost:3001' // Direct access to backend
});
// Setup: Helper function to create a user
const createUser = async () => {
const uniqueId = Math.random().toString(36).substring(7);
const email = `test.user.${uniqueId}@example.com`;
const password = 'StrongPassword123!';
const response = await apiContext.post('/api/auth/register', {
data: {
email,
password
}
});
const body = await response.json();
// If registration fails because we hit a collision (unlikely) or other error, fail the test
if (!response.ok()) {
console.error(`REGISTRATION FAILED: ${response.status()} ${response.statusText()}`);
console.error(`RESPONSE BODY: ${JSON.stringify(body, null, 2)}`);
throw new Error(`Failed to register user: ${JSON.stringify(body)}`);
}
return { email, password, id: body.user.id };
};
// Use the fixture
await use(createUser);
// Cleanup: In a real "test:full" env with ephemeral db, cleanup might not be needed.
// But if we want to be clean, we can delete the user.
// Requires admin privileges usually, or specific delete-me endpoint.
// Given the requirements "delete own account" exists (5.6), we could theoretically login and delete.
// For now we skip auto-cleanup to keep it simple, assuming test DB is reset periodically.
},
});
export { expect };

7
tests/seed.spec.ts Normal file
View File

@@ -0,0 +1,7 @@
import { test, expect } from '@playwright/test';
test.describe('Test group', () => {
test('seed', async ({ page }) => {
// generate code here.
});
});