diff --git a/contracts/lib/self b/contracts/lib/self
new file mode 160000
index 0000000..03e471c
--- /dev/null
+++ b/contracts/lib/self
@@ -0,0 +1 @@
+Subproject commit 03e471c5bfa5a6dfe5e69324cbf060f1545cf679
diff --git a/next-frontend/.github/workflows/e2e-tests.yml b/next-frontend/.github/workflows/e2e-tests.yml
new file mode 100644
index 0000000..fe00683
--- /dev/null
+++ b/next-frontend/.github/workflows/e2e-tests.yml
@@ -0,0 +1,211 @@
+name: E2E Tests
+
+on:
+ push:
+ branches: [ main, develop ]
+ pull_request:
+ branches: [ main, develop ]
+
+jobs:
+ e2e-tests:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [18.x, 20.x]
+ browser: [chromium, firefox, webkit]
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Install Playwright browsers
+ run: npx playwright install --with-deps ${{ matrix.browser }}
+
+ - name: Build application
+ run: npm run build
+ env:
+ NEXT_PUBLIC_TEST_MODE: true
+ NEXT_PUBLIC_MOCK_CONTRACTS: true
+
+ - name: Run Playwright tests
+ run: npx playwright test --project=${{ matrix.browser }}
+ env:
+ CI: true
+ NEXT_PUBLIC_TEST_MODE: true
+ NEXT_PUBLIC_E2E_TESTING: true
+
+ - name: Upload test results
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: playwright-results-${{ matrix.browser }}-node-${{ matrix.node-version }}
+ path: |
+ test-results/
+ playwright-report/
+ playbackwright-report/
+ retention-days: 30
+
+ security-tests:
+ runs-on: ubuntu-latest
+ needs: e2e-tests
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20.x
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run security-focused E2E tests
+ run: npx playwright test tests/security/
+ env:
+ CI: true
+ NEXT_PUBLIC_TEST_MODE: true
+
+ performance-tests:
+ runs-on: ubuntu-latest
+ needs: e2e-tests
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20.x
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Install Playwright browsers
+ run: npx playwright install --with-deps
+
+ - name: Run performance E2E tests
+ run: npx playwright test --project=chromium --grep="performance|load"
+ env:
+ CI: true
+ NEXT_PUBLIC_TEST_MODE: true
+
+ - name: Upload performance results
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: performance-results
+ path: |
+ test-results/
+ playwright-report/
+ retention-days: 7
+
+ mobile-tests:
+ runs-on: ubuntu-latest
+ needs: e2e-tests
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20.x
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Install Playwright browsers
+ run: npx playwright install --with-deps
+
+ - name: Run mobile E2E tests
+ run: npx playwright test --project="Mobile Chrome" --project="Mobile Safari"
+ env:
+ CI: true
+ NEXT_PUBLIC_TEST_MODE: true
+
+ cross-browser-tests:
+ runs-on: ubuntu-latest
+ needs: e2e-tests
+
+ strategy:
+ matrix:
+ browser: [chromium, firefox, webkit]
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20.x
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Install Playwright browsers
+ run: npx playwright install --with-deps ${{ matrix.browser }}
+
+ - name: Run cross-browser compatibility tests
+ run: npx playwright test --project=${{ matrix.browser }} --grep="cross-browser|compatibility"
+ env:
+ CI: true
+ NEXT_PUBLIC_TEST_MODE: true
+
+ test-summary:
+ runs-on: ubuntu-latest
+ needs: [e2e-tests, security-tests, performance-tests, mobile-tests, cross-browser-tests]
+ if: always()
+
+ steps:
+ - name: Download all test results
+ uses: actions/download-artifact@v4
+ with:
+ path: all-results/
+
+ - name: Generate test summary
+ run: |
+ echo "# E2E Test Results Summary" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ # Check if tests passed
+ if [ -f "all-results/playwright-report/index.html" ]; then
+ echo "โ E2E tests completed successfully" >> $GITHUB_STEP_SUMMARY
+ echo "- Cross-browser testing passed" >> $GITHUB_STEP_SUMMARY
+ echo "- Security tests completed" >> $GITHUB_STEP_SUMMARY
+ echo "- Performance tests executed" >> $GITHUB_STEP_SUMMARY
+ echo "- Mobile compatibility verified" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "โ E2E tests failed" >> $GITHUB_STEP_SUMMARY
+ echo "Please check the test artifacts for detailed failure information." >> $GITHUB_STEP_SUMMARY
+ fi
+
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "## Test Artifacts" >> $GITHUB_STEP_SUMMARY
+ echo "- [Playwright Report](./artifacts)" >> $GITHUB_STEP_SUMMARY
+ echo "- [Test Results](./test-results/)" >> $GITHUB_STEP_SUMMARY
+
+ - name: Upload all artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: all-test-results
+ path: all-results/
+ retention-days: 30
\ No newline at end of file
diff --git a/next-frontend/ENS_INTEGRATION_SUMMARY.md b/next-frontend/ENS_INTEGRATION_SUMMARY.md
deleted file mode 100644
index d07ef3b..0000000
--- a/next-frontend/ENS_INTEGRATION_SUMMARY.md
+++ /dev/null
@@ -1,214 +0,0 @@
-# ENS Profile Integration - Implementation Summary
-
-## Overview
-This pull request implements ENS (Ethereum Name Service) user profile display functionality for BlockBelle's chat interface. The implementation provides a comprehensive solution for fetching, displaying, and managing ENS profiles with graceful fallbacks and real-time updates.
-
-## ๐ฏ Features Implemented
-
-### โ Core Functionality
-- **ENS Profile Fetching**: Retrieve comprehensive profile data including avatar, bio, website, Twitter, and GitHub
-- **Avatar Support**: Handle IPFS URLs and automatic conversion to accessible URLs
-- **Profile Display**: Show ENS names, display names, bios, and verification badges
-- **Fallback Handling**: Graceful degradation to default profiles when ENS data is unavailable
-- **Real-time Updates**: Monitor and update profiles when ENS records change
-
-### โ User Interface Integration
-- **UserList Enhancement**: Updated user list to display ENS profiles with avatars and verification
-- **Chat Interface**: Integrated ENS profiles in chat headers and message displays
-- **Notification System**: Updated to use ENS display names for better user experience
-- **Multiple Display Modes**: Support for compact and full profile views
-
-### โ Technical Implementation
-- **ENS Service Layer**: Comprehensive service for profile management and caching
-- **React Hooks**: Custom hooks for individual and batch profile management
-- **Component Library**: Reusable ENS profile components with multiple size options
-- **Change Detection**: Smart polling mechanism for detecting profile updates
-
-## ๐ Files Modified/Created
-
-### New Files
-- `src/lib/ensService.ts` - Core ENS service with fetching, caching, and subscription logic
-- `src/hooks/useENSProfile.ts` - React hooks for profile management
-- `src/components/ENSProfile.tsx` - Reusable ENS profile component
-- `src/__tests__/ens.test.ts` - Comprehensive service layer tests
-- `src/__tests__/ens-hooks.test.tsx` - React hooks test suite
-- `src/__tests__/ens-profile-component.test.tsx` - Component integration tests
-
-### Modified Files
-- `package.json` - Added ENS SDK dependencies
-- `src/components/UserList.tsx` - Integrated ENS profile display
-- `src/components/ChatInterface.tsx` - Added ENS profile integration in chat
-
-## ๐ง Technical Details
-
-### ENS Service Features
-```typescript
-// Key service functions implemented:
-- fetchENSProfile(address) - Fetch individual profile
-- fetchMultipleENSProfiles(addresses[]) - Batch profile fetching
-- subscribeToENSChanges(address, callback) - Real-time updates
-- subscribeToMultipleENSChanges() - Batch subscription management
-- detectProfileChanges(address) - Change detection with field comparison
-- Cache management with TTL and invalidation
-```
-
-### React Hooks
-```typescript
-// Hooks implemented:
-- useENSProfile(address) - Individual profile management
-- useENSProfiles(addresses[]) - Batch profile management
-- useENSDisplayInfo(address) - Formatted display data
-```
-
-### Component Features
-```typescript
-// ENSProfile component props:
-- size: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
-- showBadge: boolean
-- showFullProfile: boolean
-- fallbackToAddress: boolean
-- Custom className support
-```
-
-## ๐งช Testing Coverage
-
-### Service Layer Tests
-- Profile fetching with mock data
-- Cache management (get/set/clear)
-- Subscription lifecycle
-- Error handling and edge cases
-- Performance testing with batch operations
-
-### Hook Tests
-- Individual and batch profile fetching
-- Loading states and error handling
-- Integration with ENS service
-- Cleanup and subscription management
-
-### Component Tests
-- Rendering with various profile states
-- Avatar handling and fallback behavior
-- Social link integration
-- Size and prop variations
-- External link security
-
-## ๐จ UI/UX Enhancements
-
-### Profile Display
-- **Avatar System**: ENS avatars with fallback to initials
-- **Verification Badges**: Visual indicators for verified users
-- **Bio Integration**: Display user bios when available
-- **Social Links**: Direct links to website, Twitter, and GitHub
-- **ENS Badges**: Special indicators for users with ENS profiles
-
-### User Experience
-- **Loading States**: Skeleton screens during profile fetch
-- **Error Handling**: Graceful fallbacks for missing data
-- **Real-time Updates**: Automatic profile updates
-- **Responsive Design**: Works across all device sizes
-
-## ๐ Performance Optimizations
-
-### Caching Strategy
-- **In-memory Cache**: 5-minute TTL for profile data
-- **Batch Operations**: Process multiple addresses efficiently
-- **Lazy Loading**: Load profiles on demand
-- **Subscription Cleanup**: Proper cleanup to prevent memory leaks
-
-### Network Optimization
-- **Batch Fetching**: Process up to 5 addresses simultaneously
-- **Error Recovery**: Retry mechanisms for failed requests
-- **Minimal Re-renders**: Optimized React hooks for performance
-
-## ๐ Security & Privacy
-
-### Data Handling
-- **Client-side Only**: All profile fetching happens client-side
-- **No Data Storage**: Profiles not stored in database
-- **External Links**: Proper `rel="noopener noreferrer"` attributes
-- **Input Validation**: Address validation and sanitization
-
-## ๐ฑ Mobile Responsiveness
-
-### Responsive Design
-- **Adaptive Sizing**: Profile components scale appropriately
-- **Touch-Friendly**: Proper touch targets for mobile devices
-- **Collapsible Info**: Bio and social links adapt to screen size
-- **Loading States**: Mobile-optimized loading indicators
-
-## ๐ฎ Future Enhancements
-
-### Ready for Production
-- **ENS SDK Integration**: Mock implementation ready for real ENS SDK
-- **Database Integration**: Easy transition to persistent storage
-- **Real-time Subscriptions**: Foundation for WebSocket-based updates
-- **Advanced Caching**: Redis integration ready
-
-### Potential Additions
-- **Profile Editing**: Allow users to update their ENS records
-- **Bulk Operations**: Admin tools for managing multiple profiles
-- **Analytics**: Track profile usage and performance metrics
-- **Social Features**: Follow/block users based on ENS data
-
-## ๐ Acceptance Criteria Met
-
-### โ Fetch and display ENS avatar and profile for authenticated users
-- Implemented comprehensive profile fetching service
-- Created reusable React components with avatar support
-- Integrated into chat interface and user list
-
-### โ Fallback gracefully to default profile details if ENS data is incomplete
-- Default profiles for addresses without ENS
-- Fallback to initials when avatars fail to load
-- Error handling with user-friendly messages
-
-### โ Ensure profiles are updated if ENS records change
-- Real-time monitoring with configurable polling intervals
-- Automatic UI updates when profile data changes
-- Smart change detection with field comparison
-
-### โ Add tests to verify ENS profile integration
-- Comprehensive test suite covering all components
-- Service layer testing with mock data
-- React component integration testing
-- Edge case and error handling tests
-
-## ๐ ๏ธ Technical Stack
-
-### Dependencies
-- **@ensdomains/ensjs** - ENS SDK for profile operations
-- **@ensdomains/ens-contracts** - ENS contract interfaces
-- **React 19** - Modern React with concurrent features
-- **Wagmi** - Ethereum interaction library
-- **TypeScript** - Type-safe development
-
-### Development Tools
-- **Vitest** - Fast unit testing framework
-- **Testing Library** - React component testing utilities
-- **msw** - API mocking for tests
-
-## ๐ Code Quality
-
-### Metrics
-- **Test Coverage**: 95%+ across all modules
-- **Type Safety**: 100% TypeScript coverage
-- **Documentation**: Comprehensive inline documentation
-- **Error Handling**: Robust error boundaries and fallbacks
-
-### Architecture
-- **Separation of Concerns**: Service layer, hooks, and components
-- **Reusability**: Modular design for easy extension
-- **Performance**: Optimized for real-world usage patterns
-- **Maintainability**: Clean, well-documented code structure
-
-## ๐ Summary
-
-This implementation successfully delivers a production-ready ENS profile integration for BlockBelle's chat interface. The solution provides:
-
-1. **Complete Feature Set**: All requirements implemented with additional enhancements
-2. **Robust Testing**: Comprehensive test suite ensuring reliability
-3. **Performance Optimized**: Efficient caching and batch operations
-4. **User-Friendly**: Intuitive interface with proper fallbacks
-5. **Future-Ready**: Extensible architecture for additional features
-
-The implementation is ready for production deployment and can be easily extended with additional ENS features or migrated to use the real ENS SDK when dependencies are available.
\ No newline at end of file
diff --git a/next-frontend/I18N_CONTRIBUTING.md b/next-frontend/I18N_CONTRIBUTING.md
deleted file mode 100644
index 0e32bf0..0000000
--- a/next-frontend/I18N_CONTRIBUTING.md
+++ /dev/null
@@ -1,422 +0,0 @@
-# Internationalization Contributing Guide
-
-This guide helps developers contribute to BlockBelle's internationalization (i18n) system.
-
-## Quick Start for Developers
-
-### Adding New Text
-
-When you add new text to the application, **always use translation keys**:
-
-```typescript
-// โ Don't do this
-
Welcome to BlockBelle
-
-// โ Always do this
-
{t('homepage.welcome')}
-```
-
-### Before You Start Development
-
-1. **Install dependencies**: `npm install` (this will install `next-intl`)
-2. **Check existing translations**: Look at `src/i18n/locales/` for reference
-3. **Understand the structure**: Review `INTERNATIONALIZATION.md`
-
-## Translation Workflow
-
-### Step 1: Identify Translation Needs
-
-When adding new features or text:
-
-1. **Scan for hardcoded strings** in your new components
-2. **Identify text types**: Buttons, labels, error messages, notifications
-3. **Plan translation keys**: Choose descriptive, hierarchical names
-
-### Step 2: Add Translation Keys
-
-**Update all three language files:**
-
-#### English (`src/i18n/locales/en.json`)
-```json
-{
- "feature": {
- "newFeature": "New Feature",
- "description": "This is a new feature description"
- }
-}
-```
-
-#### Spanish (`src/i18n/locales/es.json`)
-```json
-{
- "feature": {
- "newFeature": "Nueva Caracterรญstica",
- "description": "Esta es una descripciรณn de nueva caracterรญstica"
- }
-}
-```
-
-#### French (`src/i18n/locales/fr.json`)
-```json
-{
- "feature": {
- "newFeature": "Nouvelle Fonctionnalitรฉ",
- "description": "Ceci est une description de nouvelle fonctionnalitรฉ"
- }
-}
-```
-
-### Step 3: Update Components
-
-```typescript
-'use client';
-
-import { useTranslations } from 'next-intl';
-
-export default function NewComponent() {
- const t = useTranslations();
-
- return (
-
-
{t('feature.newFeature')}
-
{t('feature.description')}
-
-
- );
-}
-```
-
-## Translation Key Naming Guidelines
-
-### 1. Use Descriptive Names
-
-```typescript
-// โ Poor naming
-t('msg1')
-
-// โ Good naming
-t('chat.message.private.placeholder')
-```
-
-### 2. Organize Hierarchically
-
-```json
-{
- "common": {
- "actions": {
- "save": "Save",
- "cancel": "Cancel",
- "delete": "Delete"
- },
- "status": {
- "loading": "Loading...",
- "error": "Error",
- "success": "Success"
- }
- },
- "navigation": {
- "main": {
- "home": "Home",
- "chat": "Chat",
- "groups": "Groups"
- },
- "footer": {
- "about": "About",
- "contact": "Contact"
- }
- }
-}
-```
-
-### 3. Be Context-Aware
-
-```json
-{
- "chat": {
- "message": {
- "placeholder": "Type your message...",
- "send": "Send",
- "attach": "Attach File"
- },
- "status": {
- "online": "Online",
- "offline": "Offline",
- "typing": "Typing..."
- }
- }
-}
-```
-
-## Common Translation Patterns
-
-### Form Labels
-
-```json
-{
- "forms": {
- "login": {
- "email": "Email Address",
- "password": "Password",
- "submit": "Sign In"
- },
- "registration": {
- "username": "Choose Username",
- "email": "Email Address",
- "password": "Create Password",
- "submit": "Create Account"
- }
- }
-}
-```
-
-### Error Messages
-
-```json
-{
- "errors": {
- "validation": {
- "required": "This field is required",
- "email": "Please enter a valid email address",
- "password": "Password must be at least 8 characters"
- },
- "network": {
- "connection": "Network connection error",
- "timeout": "Request timed out",
- "server": "Server error occurred"
- }
- }
-}
-```
-
-### Success Messages
-
-```json
-{
- "success": {
- "registration": "Account created successfully!",
- "login": "Welcome back!",
- "profile": "Profile updated successfully"
- }
-}
-```
-
-## Testing Your Changes
-
-### 1. Development Testing
-
-```bash
-# Start development server
-npm run dev
-
-# Test in different languages:
-# - http://localhost:3000/en
-# - http://localhost:3000/es
-# - http://localhost:3000/fr
-```
-
-### 2. Language Toggle Testing
-
-1. Click the language toggle in the header
-2. Verify the URL changes (e.g., `/en` โ `/es`)
-3. Check that all text translates correctly
-4. Ensure language preference persists on page reload
-
-### 3. Component Testing
-
-```typescript
-import { render, screen } from '@testing-library/react';
-import { NextIntlClientProvider } from 'next-intl';
-import MyComponent from './MyComponent';
-
-test('translates text correctly', () => {
- const messages = require('../i18n/locales/es.json');
-
- render(
-
-
-
- );
-
- expect(screen.getByText('Cargando...')).toBeInTheDocument();
-});
-```
-
-## Adding New Languages
-
-### Step-by-Step Process
-
-1. **Update Routing**: Edit `src/i18n/routing.ts`
- ```typescript
- export const routing = defineRouting({
- locales: ['en', 'es', 'fr', 'de'], // Add new language
- defaultLocale: 'en'
- });
- ```
-
-2. **Create Translation File**: Create `src/i18n/locales/de.json`
-
-3. **Update Language Toggle**: Add language to `src/components/LanguageToggle.tsx`
-
-4. **Test**: Verify new language works correctly
-
-## Best Practices Checklist
-
-### Before Submitting Changes
-
-- [ ] All new text uses translation keys (no hardcoded strings)
-- [ ] Translation keys exist in all three language files (en, es, fr)
-- [ ] Translation keys follow naming conventions
-- [ ] Text is contextually appropriate for each language
-- [ ] Components render correctly in all languages
-- [ ] Language toggle works properly
-- [ ] URLs reflect selected language
-- [ ] No console errors in browser
-
-### Code Review Checklist
-
-- [ ] Translation files are properly formatted JSON
-- [ ] Translation keys are descriptive and organized
-- [ ] No missing translations in any language
-- [ ] Components use `useTranslations` hook correctly
-- [ ] Language switching functionality works
-- [ ] Tests cover translation functionality
-
-## Common Pitfalls to Avoid
-
-### 1. Missing Translations
-
-```typescript
-// โ This will cause errors if key doesn't exist
-t('feature.nonexistent')
-
-// โ Check if key exists first
-t('feature.available') || 'Default Text'
-```
-
-### 2. Inconsistent Naming
-
-```json
-// โ Inconsistent
-{
- "userProfile": "User Profile",
- "account_settings": "Account Settings",
- "notificationSystem": "Notifications"
-}
-
-// โ Consistent camelCase
-{
- "userProfile": "User Profile",
- "accountSettings": "Account Settings",
- "notificationSystem": "Notifications"
-}
-```
-
-### 3. Not Considering Text Length
-
-Some languages (like German) produce longer text:
-
-```json
-{
- "button": {
- "save": "Save",
- "cancel": "Cancel"
- }
-}
-```
-
-German: "Speichern" (longer than "Save")
-
-**Solution**: Design UI components to handle text expansion gracefully.
-
-### 4. Ignoring Cultural Context
-
-```json
-// โ English-centric
-{
- "greeting": "Hello {name}!"
-}
-
-// โ Culturally appropriate
-{
- "greeting": {
- "morning": "Good morning {name}!",
- "afternoon": "Good afternoon {name}!",
- "evening": "Good evening {name}!"
- }
-}
-```
-
-## Troubleshooting
-
-### Translation Not Showing
-
-1. Check key exists in all language files
-2. Verify key name spelling
-3. Ensure component is a client component
-4. Check browser console for errors
-
-### Language Toggle Not Working
-
-1. Verify routing configuration
-2. Check middleware setup
-3. Ensure language files are valid JSON
-4. Test with `npm run dev`
-
-### JSON Validation Errors
-
-```bash
-# Validate JSON syntax
-node -e "JSON.parse(require('fs').readFileSync('src/i18n/locales/en.json'))"
-```
-
-## Getting Help
-
-### Resources
-
-1. **Next.js i18n Documentation**: https://nextjs.org/docs/app/building-your-application/routing/internationalization
-2. **next-intl Documentation**: https://next-intl-docs.vercel.app/
-3. **Project Documentation**: `INTERNATIONALIZATION.md`
-
-### When to Ask for Help
-
-- Complex translation scenarios
-- RTL language support
-- Performance issues
-- Testing strategies
-- Integration problems
-
-### Code Review
-
-For significant i18n changes, request review from:
-
-- Team members familiar with i18n
-- Design team (for UI/UX considerations)
-- Product team (for user experience)
-
-## Continuous Improvement
-
-### Monitor Translation Usage
-
-- Track which keys are most used
-- Identify missing translations
-- Monitor translation completeness
-
-### Update Documentation
-
-When adding new patterns or fixing issues:
-
-1. Update `INTERNATIONALIZATION.md`
-2. Add examples to this guide
-3. Document any new processes
-
-### Performance Monitoring
-
-- Track translation file loading times
-- Monitor bundle size impact
-- Check for unnecessary translations
-
----
-
-**Remember**: Good internationalization is an ongoing process. Continuously improve translations and user experience across all supported languages.
-
-*Happy translating! ๐*
\ No newline at end of file
diff --git a/next-frontend/INTERNATIONALIZATION.md b/next-frontend/INTERNATIONALIZATION.md
deleted file mode 100644
index 38306b3..0000000
--- a/next-frontend/INTERNATIONALIZATION.md
+++ /dev/null
@@ -1,147 +0,0 @@
-# Internationalization (i18n) Implementation Guide
-
-This document provides comprehensive information about the internationalization (i18n) implementation in BlockBelle.
-
-## Overview
-
-BlockBelle uses **next-intl** as the primary i18n library for React and Next.js applications. The implementation supports:
-
-- **Languages**: English (default), Spanish, and French
-- **Dynamic Language Switching**: Users can switch languages without page reload
-- **URL-based Localization**: URLs include locale prefixes (`/en`, `/es`, `/fr`)
-- **Server-side Rendering**: Proper server-side translation support
-- **Client-side Hydration**: Smooth client-side language switching
-
-## File Structure
-
-```
-src/
-โโโ i18n/
-โ โโโ locales/
-โ โ โโโ en.json # English translations
-โ โ โโโ es.json # Spanish translations
-โ โ โโโ fr.json # French translations
-โ โโโ index.ts # i18n configuration
-โ โโโ routing.ts # Routing configuration
-โโโ middleware.ts # Internationalized routing middleware
-โโโ app/
- โโโ [locale]/
- โ โโโ layout.tsx # Locale-specific layout
- โ โโโ page.tsx # Locale-specific page
- โโโ page.tsx # Root redirect to default locale
-```
-
-## Translation Files
-
-Translation files are located in `src/i18n/locales/` and use JSON format. Each file contains translation keys organized by sections.
-
-## Usage in Components
-
-### Basic Translation Usage
-
-```typescript
-'use client';
-
-import { useTranslations } from 'next-intl';
-
-export default function MyComponent() {
- const t = useTranslations();
-
- return (
-
-
{t('homepage.welcome')}
-
{t('common.loading')}
-
- );
-}
-```
-
-## Language Toggle Component
-
-The `LanguageToggle` component provides a dropdown interface for switching between languages:
-
-```typescript
-import LanguageToggle from '@/components/LanguageToggle';
-
-// In your component
-
-
-
-```
-
-## Adding New Languages
-
-### 1. Add Language to Routing
-
-Edit `src/i18n/routing.ts`:
-
-```typescript
-export const routing = defineRouting({
- locales: ['en', 'es', 'fr', 'de'], // Add new language code
- defaultLocale: 'en'
-});
-```
-
-### 2. Create Translation File
-
-Create `src/i18n/locales/de.json` with translation keys.
-
-### 3. Update Language Toggle
-
-Add the new language to the `LanguageToggle` component.
-
-## Best Practices
-
-### 1. Key Naming Conventions
-
-- Use **camelCase** for keys: `navigation.home`
-- Group related keys: `navigation.home`, `navigation.chat`
-- Be descriptive: `error.networkConnection` instead of `error.1`
-
-### 2. Avoiding Hardcoded Text
-
-Always use translation keys instead of hardcoded strings:
-
-```typescript
-// โ Bad
-
Welcome to BlockBelle
-
-// โ Good
-
{t('homepage.welcome')}
-```
-
-## URL Structure
-
-- **English**: `https://yourdomain.com/` (default)
-- **Spanish**: `https://yourdomain.com/es/`
-- **French**: `https://yourdomain.com/fr/`
-- **German**: `https://yourdomain.com/de/` (when added)
-
-## Troubleshooting
-
-### Common Issues
-
-1. **Translation Key Not Found**
- - Ensure the key exists in all translation files
- - Check for typos in key names
-
-2. **Locale Not Working**
- - Verify the locale is added to `routing.ts`
- - Check middleware configuration
-
-3. **Component Not Re-rendering**
- - Ensure `useTranslations` hook is used correctly
- - Check that the component is a client component
-
-## Contributing
-
-When adding new text to the application:
-
-1. **Always use translation keys** - never hardcode text
-2. **Update all language files** - ensure translations exist for EN, ES, and FR
-3. **Follow naming conventions** - use descriptive, hierarchical keys
-4. **Test in all languages** - verify translations display correctly
-
----
-
-*This documentation is maintained as part of the BlockBelle project.*
\ No newline at end of file
diff --git a/next-frontend/e2e/.env.example b/next-frontend/e2e/.env.example
new file mode 100644
index 0000000..0e50f38
--- /dev/null
+++ b/next-frontend/e2e/.env.example
@@ -0,0 +1,88 @@
+# E2E Test Environment Configuration
+# Copy this file to .env and update the values as needed
+
+# Test Mode Configuration
+NEXT_PUBLIC_TEST_MODE=true
+NEXT_PUBLIC_E2E_TESTING=true
+NEXT_PUBLIC_MOCK_CONTRACTS=true
+
+# Mock Contract Addresses for Testing
+NEXT_PUBLIC_REGISTRY_ADDRESS=0xA72B585c6b2293177dd485Ec0A607A471976771B
+NEXT_PUBLIC_CHAT_ADDRESS=0x562456dBF6F21d40C96D392Ef6eD1de2e921bF2C
+
+# Test Database (if using separate test database)
+DATABASE_URL=postgresql://test:test@localhost:5432/blockbelle_test
+TEST_DATABASE_URL=postgresql://test:test@localhost:5432/blockbelle_e2e_test
+
+# Network Configuration
+NEXT_PUBLIC_CHAIN_ID=0xa4ec
+NEXT_PUBLIC_RPC_URL=https://forno.celo.org
+
+# Authentication Test Configuration
+TEST_WALLET_ADDRESS=0x742d35cc6cF6B4633F82c9B7C7C31E7c7B6C8F9A
+TEST_PRIVATE_KEY=mock_private_key_for_testing_only
+
+# Mock API Responses
+MOCK_API_DELAY=500
+MOCK_CONTRACT_RESPONSE_DELAY=1000
+
+# Test Performance Configuration
+TEST_TIMEOUT=30000
+TEST_RETRIES=2
+TEST_WORKERS=3
+
+# Security Test Configuration
+ENABLE_SECURITY_TESTS=true
+ENABLE_RATE_LIMIT_TESTS=true
+ENABLE_XSS_TESTS=true
+
+# Mobile Testing Configuration
+MOBILE_VIEWPORT_WIDTH=375
+MOBILE_VIEWPORT_HEIGHT=667
+TABLET_VIEWPORT_WIDTH=768
+TABLET_VIEWPORT_HEIGHT=1024
+
+# Browser Configuration
+DEFAULT_BROWSER=chromium
+HEADLESS_MODE=true
+VIDEO_RECORDING=true
+SCREENSHOT_ON_FAILURE=true
+
+# CI/CD Configuration
+CI=true
+GITHUB_ACTIONS=true
+ARTIFACT_RETENTION_DAYS=30
+
+# Logging Configuration
+DEBUG=pw:api
+VERBOSE_LOGGING=false
+CONSOLE_LOGGING=true
+
+# Mock Data Configuration
+MOCK_USER_COUNT=10
+MOCK_MESSAGE_COUNT=50
+MOCK_GROUP_COUNT=5
+
+# External Service Mocking
+MOCK_ENS_RESOLVER=true
+MOCK_IPFS_UPLOADS=true
+MOCK_NOTIFICATION_SERVICE=true
+
+# Rate Limiting Test Configuration
+RATE_LIMIT_TEST_MESSAGES=10
+RATE_LIMIT_TEST_INTERVAL=100
+
+# Performance Test Thresholds
+MAX_PAGE_LOAD_TIME=5000
+MAX_MESSAGE_SEND_TIME=3000
+MAX_CHAT_LOAD_TIME=4000
+
+# Accessibility Testing
+ENABLE_A11Y_TESTS=true
+CONTRAST_CHECKS=true
+SCREEN_READER_SIMULATION=true
+
+# Network Conditions Testing
+SLOW_NETWORK_SIMULATION=true
+OFFLINE_SIMULATION=true
+INTERMITTENT_CONNECTION_SIMULATION=true
\ No newline at end of file
diff --git a/next-frontend/e2e/CHANGELOG.md b/next-frontend/e2e/CHANGELOG.md
new file mode 100644
index 0000000..1943ab9
--- /dev/null
+++ b/next-frontend/e2e/CHANGELOG.md
@@ -0,0 +1,167 @@
+# E2E Test Suite Changelog
+
+All notable changes to the BlockBelle E2E test suite will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [1.0.0] - 2025-12-02
+
+### Added
+- **Initial E2E Test Suite** - Comprehensive end-to-end testing for BlockBelle chat functionality
+- **Playwright Framework** - Multi-browser testing support (Chromium, Firefox, WebKit, Mobile)
+- **ENS-Verified User Tests** - Complete test coverage for verified users with full feature access
+- **Non-Verified User Tests** - Tests for users with limited access and upgrade prompts
+- **Message Delivery Tests** - Verification of successful and failed message scenarios
+- **Group Chat Tests** - Group creation, joining, management, and messaging functionality
+- **Authentication Tests** - Login/logout flows, session management, and error handling
+- **Security Tests** - Route protection, input validation, rate limiting, and CSRF protection
+- **Test Fixtures** - Reusable test utilities, mock data, and blockchain simulation
+- **CI/CD Integration** - GitHub Actions workflow for automated testing
+- **Test Documentation** - Comprehensive README with setup and usage instructions
+- **Environment Configuration** - Example environment file with all necessary settings
+- **Test Runner Script** - Shell script for convenient test execution
+
+### Test Coverage
+
+#### Chat Functionality
+- โ Private messaging between users
+- โ Message delivery confirmation
+- โ Message history and timestamps
+- โ User presence indicators
+- โ Message search functionality
+- โ Real-time message updates
+
+#### User Types
+- โ ENS-verified users with full features
+- โ Non-verified users with limited access
+- โ Verification status display
+- โ Upgrade prompts and restrictions
+
+#### Group Features
+- โ Group creation and management
+- โ Group joining (public and private)
+- โ Member management and permissions
+- โ Group messaging and mentions
+- โ Group admin controls
+
+#### Authentication
+- โ Wallet connection/disconnection
+- โ Session persistence
+- โ Authentication state management
+- โ Error handling and recovery
+- โ Multi-account handling
+
+#### Security
+- โ Route access control
+- โ Input validation and sanitization
+- โ XSS protection
+- โ CSRF token validation
+- โ Rate limiting
+- โ Message encryption verification
+
+#### Browser Compatibility
+- โ Desktop Chrome (Chromium)
+- โ Desktop Firefox
+- โ Desktop Safari (WebKit)
+- โ Mobile Chrome (Pixel 5)
+- โ Mobile Safari (iPhone 12)
+
+### Technical Implementation
+
+#### Mock Infrastructure
+- Blockchain contract simulation
+- Wallet provider mocking
+- Network request interception
+- Realistic user data simulation
+
+#### Test Utilities
+- Custom Playwright fixtures
+- Blockchain network mocking
+- Contract response simulation
+- Realistic user scenario creation
+
+#### CI/CD Pipeline
+- Multi-matrix testing (Node.js versions ร browsers)
+- Artifact collection and reporting
+- Performance and security test isolation
+- Automated test summary generation
+
+### Development Tools
+
+#### Test Runner
+- Shell script with colored output
+- Environment checking
+- Multiple execution modes
+- Dependency management
+
+#### Documentation
+- Comprehensive README
+- Setup and configuration guides
+- Troubleshooting documentation
+- Contributing guidelines
+
+### Dependencies
+- Playwright 1.48.0+
+- Node.js 18+
+- TypeScript 5.9+
+
+### Configuration
+- Multi-browser testing setup
+- Environment variable configuration
+- Mobile viewport testing
+- Performance threshold settings
+
+### Future Enhancements
+- Visual regression testing
+- Performance benchmarking
+- Accessibility testing expansion
+- Multi-language testing
+- API contract testing
+
+---
+
+## Test Execution Examples
+
+### Run All Tests
+```bash
+npm run test:e2e
+```
+
+### Run Specific Test Category
+```bash
+./run-tests.sh --category chat
+./run-tests.sh --security
+```
+
+### Run with UI Mode
+```bash
+npm run test:e2e:ui
+./run-tests.sh --ui
+```
+
+### Environment Setup
+```bash
+# Install dependencies
+./run-tests.sh --install-deps
+
+# Install browsers
+./run-tests.sh --install-browsers
+
+# Check environment
+./run-tests.sh --check
+```
+
+### CI/CD Integration
+Tests automatically run on:
+- Push to main/develop branches
+- Pull requests
+- Multi-browser matrix
+- Security-focused jobs
+
+### Test Reports
+After test execution:
+- HTML reports in `playwright-report/`
+- Screenshots on failure
+- Video recordings
+- Console logs and traces
\ No newline at end of file
diff --git a/next-frontend/e2e/README.md b/next-frontend/e2e/README.md
new file mode 100644
index 0000000..c629598
--- /dev/null
+++ b/next-frontend/e2e/README.md
@@ -0,0 +1,347 @@
+# E2E Tests for BlockBelle Chat
+
+This directory contains comprehensive end-to-end (E2E) tests for BlockBelle's chat functionality, built with [Playwright](https://playwright.dev/).
+
+## Overview
+
+The E2E tests verify real user interactions across different scenarios:
+
+- **ENS-Verified User Chat Flows** - Tests for users with completed ENS verification
+- **Non-Verified User Chat Flows** - Tests for users without verification
+- **Message Delivery Scenarios** - Tests for successful/failed message delivery
+- **Group Chat Functionality** - Tests for creating and managing group chats
+- **Login/Logout Flows** - Tests for authentication and session management
+- **Security Protections** - Tests for security measures and route protection
+
+## Test Structure
+
+```
+e2e/
+โโโ fixtures/
+โ โโโ chat-fixtures.ts # Shared test fixtures and utilities
+โโโ tests/
+โ โโโ chat/
+โ โ โโโ ens-verified-chat.spec.ts
+โ โ โโโ non-verified-chat.spec.ts
+โ โ โโโ message-delivery.spec.ts
+โ โ โโโ group-chat.spec.ts
+โ โโโ auth/
+โ โ โโโ login-logout.spec.ts
+โ โโโ security/
+โ โโโ chat-security.spec.ts
+โโโ README.md # This file
+โโโ test-utils.ts # Additional test utilities
+```
+
+## Getting Started
+
+### Prerequisites
+
+1. **Node.js** (v18 or higher)
+2. **npm** or **yarn**
+3. **Playwright browsers** (will be installed automatically)
+
+### Installation
+
+1. **Install Playwright dependencies:**
+ ```bash
+ cd next-frontend
+ npm run e2e:install
+ ```
+
+2. **Install project dependencies:**
+ ```bash
+ npm install
+ ```
+
+### Running Tests
+
+#### Run all E2E tests:
+```bash
+npm run test:e2e
+```
+
+#### Run tests with UI mode:
+```bash
+npm run test:e2e:ui
+```
+
+#### Run tests in headed mode (see browser):
+```bash
+npm run test:e2e:headed
+```
+
+#### Run specific test file:
+```bash
+npx playwright test tests/chat/ens-verified-chat.spec.ts
+```
+
+#### Run tests for specific user type:
+```bash
+npx playwright test --grep "ENS-Verified"
+```
+
+#### Run security tests only:
+```bash
+npx playwright test tests/security/
+```
+
+### Test Reports
+
+After running tests, view the HTML report:
+```bash
+npm run e2e:show-report
+```
+
+## Test Configuration
+
+### Playwright Configuration (`playwright.config.ts`)
+
+The tests are configured to run on multiple browsers:
+- **Chromium** (Desktop Chrome)
+- **Firefox** (Desktop Firefox)
+- **WebKit** (Desktop Safari)
+- **Mobile Chrome** (Pixel 5)
+- **Mobile Safari** (iPhone 12)
+
+### Environment Variables
+
+Create a `.env` file in the project root:
+
+```env
+# Test environment configuration
+NEXT_PUBLIC_TEST_MODE=true
+NEXT_PUBLIC_MOCK_CONTRACTS=true
+NEXT_PUBLIC_E2E_TESTING=true
+
+# Mock contract addresses for testing
+NEXT_PUBLIC_REGISTRY_ADDRESS=0xTestRegistry123
+NEXT_PUBLIC_CHAT_ADDRESS=0xTestChat123
+
+# Test database (if using test database)
+DATABASE_URL=postgresql://test:test@localhost:5432/blockbelle_test
+```
+
+## Test Fixtures
+
+### Mock Users
+
+The tests use predefined mock users:
+
+- **Alice Johnson** (`0x742d35cc6cF6B4633F82c9B7C7C31E7c7B6C8F9A`) - ENS verified user
+- **Bob Smith** (`0x8ba1f109551bD432803012645Hac136c32c3c0c4`) - ENS verified user
+- **Charlie User** (`0x1234567890123456789012345678901234567890`) - Non-verified user
+- **Dave Developer** (`0x9876543210987654321098765432109876543210`) - ENS verified user
+
+### Custom Test Fixtures
+
+Available fixtures in `fixtures/chat-fixtures.ts`:
+
+- `mockWalletConnection(user)` - Mock wallet connection for a user
+- `setupMockContract()` - Mock smart contract responses
+- `waitForChatLoad()` - Wait for chat interface to load
+- `sendMessage(content)` - Send a message with verification
+- `selectChat(userAddress)` - Select a specific chat
+- `verifyMessageDelivery(content)` - Verify message was delivered
+
+## Test Scenarios
+
+### 1. ENS-Verified User Chat Flows
+
+Tests for users who have completed ENS verification:
+- โ Full feature access
+- โ Verification badges display
+- โ Enhanced messaging capabilities
+- โ Priority support features
+
+### 2. Non-Verified User Chat Flows
+
+Tests for users without ENS verification:
+- โ ๏ธ Limited feature access
+- โ ๏ธ Upgrade prompts and restrictions
+- โ ๏ธ Basic messaging only
+- โ ๏ธ Rate limiting and restrictions
+
+### 3. Message Delivery Scenarios
+
+Tests for message handling:
+- โ Successful message delivery
+- โ Failed delivery and error handling
+- ๐ Network issues and retry mechanisms
+- ๐ Message ordering and timestamps
+
+### 4. Group Chat Functionality
+
+Tests for group features:
+- ๐ฅ Group creation and joining
+- ๐ Private vs public groups
+- ๐ Group messaging and mentions
+- โ๏ธ Group management and permissions
+
+### 5. Authentication Flows
+
+Tests for login/logout:
+- ๐ Wallet connection/disconnection
+- ๐ฑ Session management
+- ๐ Authentication state persistence
+- โ Error handling and recovery
+
+### 6. Security Protections
+
+Tests for security measures:
+- ๐ก๏ธ Route access control
+- ๐งน Input validation and sanitization
+- ๐ซ Rate limiting and abuse prevention
+- ๐ Message encryption and privacy
+- ๐ก๏ธ CSRF protection
+
+## Mock Data and Utilities
+
+### Contract Mocking
+
+Tests mock smart contract responses for:
+- `getAllUsers()` - Returns list of registered users
+- `getUserDetails()` - Returns user profile information
+- `getConversation()` - Returns message history
+- `sendMessage()` - Simulates message sending
+
+### Network Simulation
+
+Tests simulate various network conditions:
+- Successful requests
+- Network timeouts
+- Server errors
+- Rate limiting responses
+
+## Continuous Integration
+
+### GitHub Actions
+
+Tests can be run in CI using the included workflow configuration:
+
+```yaml
+name: E2E Tests
+on: [push, pull_request]
+jobs:
+ e2e-tests:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 18
+ - run: npm ci
+ - run: npm run e2e:install
+ - run: npm run test:e2e
+```
+
+### Environment Setup for CI
+
+1. **Install Playwright browsers:**
+ ```bash
+ npx playwright install --with-deps
+ ```
+
+2. **Set environment variables:**
+ ```bash
+ export NEXT_PUBLIC_TEST_MODE=true
+ export CI=true
+ ```
+
+3. **Run tests:**
+ ```bash
+ npm run test:e2e
+ ```
+
+## Debugging Tests
+
+### Common Issues
+
+1. **Tests failing due to timeouts:**
+ - Increase timeout values in test configuration
+ - Check if mock data is being loaded correctly
+
+2. **Element not found errors:**
+ - Verify selectors match actual DOM elements
+ - Check if page is fully loaded before interacting
+
+3. **Mock data not working:**
+ - Ensure mock scripts are loaded before page interactions
+ - Verify mock contract addresses match test configuration
+
+### Debug Commands
+
+```bash
+# Run tests with debug output
+DEBUG=pw:api npx playwright test
+
+# Record test execution
+npx playwright test --record-video
+
+# Take screenshots on failure
+npx playwright test --screenshot=only-on-failure
+
+# Generate test artifacts
+npx playwright test --tracing=on
+```
+
+## Contributing
+
+### Writing New Tests
+
+1. **Create test file** in appropriate directory:
+ ```
+ e2e/tests/chat/new-feature.spec.ts
+ ```
+
+2. **Use existing fixtures** and mock data where possible
+
+3. **Follow naming conventions:**
+ - Test files: `*.spec.ts`
+ - Test suites: `describe('Feature Name')`
+ - Test cases: `it('should do something')`
+
+4. **Add test data attributes** to components for reliable selection:
+ ```tsx
+
+ ```
+
+### Test Best Practices
+
+1. **Use semantic test names** that describe the scenario
+2. **Mock external dependencies** (contracts, APIs)
+3. **Clean up test data** between tests
+4. **Test both success and failure cases**
+5. **Use realistic user scenarios**
+6. **Add assertions for UI state changes**
+
+## Troubleshooting
+
+### Common Problems
+
+1. **Playwright browsers not installed:**
+ ```bash
+ npx playwright install --with-deps
+ ```
+
+2. **Tests timing out:**
+ - Increase timeout in `playwright.config.ts`
+ - Check network conditions in tests
+
+3. **Mock data not working:**
+ - Verify mock scripts are injected correctly
+ - Check console for JavaScript errors
+
+4. **Authentication issues:**
+ - Ensure wallet mocking is complete
+ - Check session management in tests
+
+### Getting Help
+
+- ๐ [Playwright Documentation](https://playwright.dev/)
+- ๐ Report issues in the project repository
+- ๐ฌ Check existing test patterns in the codebase
+
+## License
+
+These E2E tests are part of the BlockBelle project and follow the same license terms.
\ No newline at end of file
diff --git a/next-frontend/e2e/fixtures/chat-fixtures.ts b/next-frontend/e2e/fixtures/chat-fixtures.ts
new file mode 100644
index 0000000..1fa2ccd
--- /dev/null
+++ b/next-frontend/e2e/fixtures/chat-fixtures.ts
@@ -0,0 +1,262 @@
+/**
+ * E2E Test Fixtures for BlockBelle Chat Application
+ *
+ * This file contains shared test fixtures and utilities for E2E testing.
+ * It provides mock data, helper functions, and setup/teardown utilities.
+ */
+
+import { test as base, Page } from '@playwright/test'
+
+export interface MockUser {
+ address: string
+ ensName?: string
+ displayName: string
+ isVerified: boolean
+ avatar?: string
+ bio?: string
+}
+
+export interface MockMessage {
+ sender: string
+ receiver: string
+ content: string
+ timestamp: number
+ type: 'private' | 'group'
+}
+
+export const MOCK_USERS: MockUser[] = [
+ {
+ address: '0x742d35cc6cF6B4633F82c9B7C7C31E7c7B6C8F9A',
+ ensName: 'alice.eth',
+ displayName: 'Alice Johnson',
+ isVerified: true,
+ avatar: 'https://example.com/avatars/alice.jpg',
+ bio: 'Blockchain developer and ENS enthusiast'
+ },
+ {
+ address: '0x8ba1f109551bD432803012645Hac136c32c3c0c4',
+ ensName: 'bob.smith',
+ displayName: 'Bob Smith',
+ isVerified: true,
+ avatar: 'https://example.com/avatars/bob.jpg',
+ bio: 'Web3 designer'
+ },
+ {
+ address: '0x1234567890123456789012345678901234567890',
+ displayName: 'Charlie User',
+ isVerified: false,
+ bio: 'New user exploring the platform'
+ },
+ {
+ address: '0x9876543210987654321098765432109876543210',
+ ensName: 'dave.dev',
+ displayName: 'Dave Developer',
+ isVerified: true,
+ avatar: 'https://example.com/avatars/dave.jpg'
+ }
+]
+
+export const MOCK_MESSAGES: MockMessage[] = [
+ {
+ sender: '0x742d35cc6cF6B4633F82c9B7C7C31E7c7B6C8F9A',
+ receiver: '0x8ba1f109551bD432803012645Hac136c32c3c0c4',
+ content: 'Hey Bob! How is the new project going?',
+ timestamp: Date.now() - 3600000,
+ type: 'private'
+ },
+ {
+ sender: '0x8ba1f109551bD432803012645Hac136c32c3c0c4',
+ receiver: '0x742d35cc6cF6B4633F82c9B7C7C31E7c7B6C8F9A',
+ content: 'Hi Alice! It\'s going great! Just finished the smart contract.',
+ timestamp: Date.now() - 3500000,
+ type: 'private'
+ },
+ {
+ sender: '0x1234567890123456789012345678901234567890',
+ receiver: '0x742d35cc6cF6B4633F82c9B7C7C31E7c7B6C8F9A',
+ content: 'Hi Alice! I\'m new here and would love to connect.',
+ timestamp: Date.now() - 1800000,
+ type: 'private'
+ }
+]
+
+export interface ChatTestFixtures {
+ mockWalletConnection: (user: MockUser) => Promise
+ setupMockContract: () => Promise
+ waitForChatLoad: () => Promise
+ sendMessage: (content: string) => Promise
+ selectChat: (userAddress: string) => Promise
+ verifyMessageDelivery: (content: string) => Promise
+}
+
+// Custom test fixtures
+export const test = base.extend({
+ mockWalletConnection: async ({ page }, use) => {
+ await use(async (user: MockUser) => {
+ // Mock wallet connection
+ await page.addInitScript((user) => {
+ // Mock ethereum provider
+ window.ethereum = {
+ request: async ({ method, params }: any) => {
+ switch (method) {
+ case 'eth_accounts':
+ return [user.address]
+ case 'eth_chainId':
+ return '0xa4ec' // Celo mainnet
+ case 'personal_sign':
+ return '0xmockedSignature'
+ default:
+ return null
+ }
+ },
+ isMetaMask: true,
+ selectedAddress: user.address
+ }
+
+ // Mock localStorage for user preferences
+ localStorage.setItem('connectedAddress', user.address)
+ localStorage.setItem('userProfile', JSON.stringify({
+ address: user.address,
+ displayName: user.displayName,
+ isVerified: user.isVerified,
+ ensName: user.ensName
+ }))
+ }, user)
+ })
+ },
+
+ setupMockContract: async ({ page }, use) => {
+ await use(async () => {
+ // Mock contract responses
+ await page.addInitScript(() => {
+ // Mock wagmi readContract
+ window.readContractMock = async (contractAddress: string, functionName: string, args?: any[]) => {
+ switch (functionName) {
+ case 'getAllUsers':
+ return [
+ '0x742d35cc6cF6B4633F82c9B7C7C31E7c7B6C8F9A',
+ '0x8ba1f109551bD432803012645Hac136c32c3c0c4',
+ '0x1234567890123456789012345678901234567890',
+ '0x9876543210987654321098765432109876543210'
+ ]
+ case 'getUserDetails':
+ const address = args[0].toLowerCase()
+ const mockUsers: Record = {
+ '0x742d35cc6cF6b4633f82c9b7c7c31e7c7b6c8f9a': {
+ ensName: 'alice.eth',
+ avatarHash: 'QmAlice123',
+ registered: true
+ },
+ '0x8ba1f109551bd432803012645hac136c32c3c0c4': {
+ ensName: 'bob.smith',
+ avatarHash: 'QmBob456',
+ registered: true
+ },
+ '0x1234567890123456789012345678901234567890': {
+ ensName: '',
+ avatarHash: '',
+ registered: false
+ },
+ '0x9876543210987654321098765432109876543210': {
+ ensName: 'dave.dev',
+ avatarHash: 'QmDave789',
+ registered: true
+ }
+ }
+ return mockUsers[address] || { ensName: '', avatarHash: '', registered: false }
+ case 'getConversation':
+ // Return mock conversation data
+ return [
+ {
+ sender: args[0],
+ receiver: args[1],
+ content: 'Hey there!',
+ timestamp: Math.floor(Date.now() / 1000) - 3600
+ }
+ ]
+ default:
+ return null
+ }
+ }
+
+ // Mock wagmi writeContract
+ window.writeContractMock = async (contractAddress: string, functionName: string, args?: any[]) => {
+ if (functionName === 'sendMessage') {
+ // Simulate successful message send
+ return '0xmockedTransactionHash'
+ }
+ return null
+ }
+
+ // Mock useReadContract hook
+ window.mockUseReadContract = (config: any) => ({
+ data: window.readContractMock?.(config.address, config.functionName, config.args),
+ isLoading: false,
+ error: null,
+ refetch: () => Promise.resolve()
+ })
+
+ // Mock useWriteContract hook
+ window.mockUseWriteContract = () => ({
+ writeContract: window.writeContractMock,
+ data: '0xmockedHash',
+ isPending: false,
+ error: null
+ })
+ })
+ })
+ },
+
+ waitForChatLoad: async ({ page }, use) => {
+ await use(async () => {
+ await page.waitForSelector('[data-testid="main-chat"]', { timeout: 10000 })
+ await page.waitForFunction(() => {
+ const chatList = document.querySelector('[data-testid="chat-list"]')
+ return chatList && chatList.children.length > 0
+ }, { timeout: 5000 })
+ })
+ },
+
+ sendMessage: async ({ page }, use) => {
+ await use(async (content: string) => {
+ const messageInput = page.locator('[data-testid="message-input"]')
+ const sendButton = page.locator('[data-testid="send-button"]')
+
+ await messageInput.fill(content)
+ await sendButton.click()
+
+ // Wait for message to appear in the chat
+ await page.waitForFunction((msgContent) => {
+ const messages = document.querySelectorAll('[data-testid="message-bubble"]')
+ return Array.from(messages).some(message =>
+ message.textContent?.includes(msgContent)
+ )
+ }, content, { timeout: 5000 })
+ })
+ },
+
+ selectChat: async ({ page }, use) => {
+ await use(async (userAddress: string) => {
+ const chatItem = page.locator(`[data-testid="chat-item-${userAddress}"]`)
+ await chatItem.click()
+
+ // Wait for chat to load
+ await page.waitForSelector('[data-testid="chat-header"]', { timeout: 3000 })
+ })
+ },
+
+ verifyMessageDelivery: async ({ page }, use) => {
+ await use(async (content: string) => {
+ await page.waitForFunction((msgContent) => {
+ const messages = document.querySelectorAll('[data-testid="message-bubble"]')
+ return Array.from(messages).some(message => {
+ const bubbleText = message.textContent || ''
+ return bubbleText.includes(msgContent) &&
+ message.querySelector('[data-testid="message-timestamp"]')
+ })
+ }, content, { timeout: 8000 })
+ })
+ }
+})
+
+export { expect } from '@playwright/test'
\ No newline at end of file
diff --git a/next-frontend/e2e/run-tests.sh b/next-frontend/e2e/run-tests.sh
new file mode 100644
index 0000000..6b378a0
--- /dev/null
+++ b/next-frontend/e2e/run-tests.sh
@@ -0,0 +1,247 @@
+#!/bin/bash
+
+# E2E Test Runner Script for BlockBelle
+# This script provides convenient commands for running E2E tests
+
+set -e
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+# Function to print colored output
+print_status() {
+ echo -e "${BLUE}[INFO]${NC} $1"
+}
+
+print_success() {
+ echo -e "${GREEN}[SUCCESS]${NC} $1"
+}
+
+print_warning() {
+ echo -e "${YELLOW}[WARNING]${NC} $1"
+}
+
+print_error() {
+ echo -e "${RED}[ERROR]${NC} $1"
+}
+
+# Check if Node.js is installed
+if ! command -v node &> /dev/null; then
+ print_error "Node.js is not installed. Please install Node.js 18+ and try again."
+ exit 1
+fi
+
+# Check if npm is installed
+if ! command -v npm &> /dev/null; then
+ print_error "npm is not installed. Please install npm and try again."
+ exit 1
+fi
+
+print_status "Starting BlockBelle E2E Test Suite..."
+
+# Function to install dependencies
+install_deps() {
+ print_status "Installing dependencies..."
+ npm install
+ print_success "Dependencies installed"
+}
+
+# Function to install Playwright browsers
+install_browsers() {
+ print_status "Installing Playwright browsers..."
+ npx playwright install --with-deps
+ print_success "Playwright browsers installed"
+}
+
+# Function to run all tests
+run_all_tests() {
+ print_status "Running all E2E tests..."
+ npm run test:e2e
+}
+
+# Function to run tests with UI
+run_ui_tests() {
+ print_status "Running E2E tests with UI mode..."
+ npm run test:e2e:ui
+}
+
+# Function to run headed tests
+run_headed_tests() {
+ print_status "Running E2E tests in headed mode..."
+ npm run test:e2e:headed
+}
+
+# Function to run specific test category
+run_category_tests() {
+ local category=$1
+ if [ -z "$category" ]; then
+ print_error "Please specify a test category: chat, auth, security"
+ exit 1
+ fi
+
+ print_status "Running $category tests..."
+ npx playwright test tests/$category/
+}
+
+# Function to run single test file
+run_single_test() {
+ local test_file=$1
+ if [ -z "$test_file" ]; then
+ print_error "Please specify a test file path"
+ exit 1
+ fi
+
+ print_status "Running test: $test_file"
+ npx playwright test $test_file
+}
+
+# Function to show test report
+show_report() {
+ print_status "Generating test report..."
+ npm run e2e:show-report
+}
+
+# Function to run specific user type tests
+run_user_type_tests() {
+ local user_type=$1
+ if [ -z "$user_type" ]; then
+ print_error "Please specify user type: verified, unverified"
+ exit 1
+ fi
+
+ print_status "Running tests for $user_type users..."
+ npx playwright test --grep="$user_type"
+}
+
+# Function to run security tests only
+run_security_tests() {
+ print_status "Running security-focused tests..."
+ npx playwright test tests/security/
+}
+
+# Function to clean test artifacts
+clean_artifacts() {
+ print_status "Cleaning test artifacts..."
+ rm -rf test-results/
+ rm -rf playwright-report/
+ rm -rf playbackwright-report/
+ print_success "Test artifacts cleaned"
+}
+
+# Function to check test environment
+check_environment() {
+ print_status "Checking test environment..."
+
+ # Check Node.js version
+ node_version=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
+ if [ "$node_version" -lt 18 ]; then
+ print_warning "Node.js version $node_version detected. Recommended: Node.js 18+"
+ else
+ print_success "Node.js version: $(node -v)"
+ fi
+
+ # Check if Playwright is installed
+ if npx playwright --version &> /dev/null; then
+ print_success "Playwright version: $(npx playwright --version)"
+ else
+ print_warning "Playwright not found. Run with --install-browsers to install."
+ fi
+
+ # Check for test environment file
+ if [ -f ".env" ]; then
+ print_success "Environment file found"
+ else
+ print_warning "No .env file found. Copy .env.example to .env and configure."
+ fi
+}
+
+# Function to show usage
+show_usage() {
+ echo "BlockBelle E2E Test Runner"
+ echo ""
+ echo "Usage: $0 [command] [options]"
+ echo ""
+ echo "Commands:"
+ echo " --install-deps Install all dependencies"
+ echo " --install-browsers Install Playwright browsers"
+ echo " --all Run all E2E tests"
+ echo " --ui Run tests with UI mode"
+ echo " --headed Run tests in headed mode"
+ echo " --category Run tests for specific category (chat|auth|security)"
+ echo " --single Run single test file"
+ echo " --verified Run tests for verified users"
+ echo " --unverified Run tests for unverified users"
+ echo " --security Run security-focused tests"
+ echo " --report Show test report"
+ echo " --clean Clean test artifacts"
+ echo " --check Check test environment"
+ echo " --help Show this help message"
+ echo ""
+ echo "Examples:"
+ echo " $0 --install-deps"
+ echo " $0 --install-browsers"
+ echo " $0 --all"
+ echo " $0 --category chat"
+ echo " $0 --verified"
+ echo " $0 --security"
+ echo " $0 --single tests/chat/ens-verified-chat.spec.ts"
+}
+
+# Parse command line arguments
+case "${1:-}" in
+ --install-deps)
+ install_deps
+ ;;
+ --install-browsers)
+ install_browsers
+ ;;
+ --all)
+ run_all_tests
+ ;;
+ --ui)
+ run_ui_tests
+ ;;
+ --headed)
+ run_headed_tests
+ ;;
+ --category)
+ run_category_tests "$2"
+ ;;
+ --single)
+ run_single_test "$2"
+ ;;
+ --verified)
+ run_user_type_tests "verified"
+ ;;
+ --unverified)
+ run_user_type_tests "unverified"
+ ;;
+ --security)
+ run_security_tests
+ ;;
+ --report)
+ show_report
+ ;;
+ --clean)
+ clean_artifacts
+ ;;
+ --check)
+ check_environment
+ ;;
+ --help|-h)
+ show_usage
+ ;;
+ "")
+ print_warning "No command specified. Run with --help for usage information."
+ show_usage
+ ;;
+ *)
+ print_error "Unknown command: $1"
+ show_usage
+ exit 1
+ ;;
+esac
\ No newline at end of file
diff --git a/next-frontend/e2e/test-utils.ts b/next-frontend/e2e/test-utils.ts
new file mode 100644
index 0000000..076bf59
--- /dev/null
+++ b/next-frontend/e2e/test-utils.ts
@@ -0,0 +1,350 @@
+/**
+ * Additional Test Utilities for BlockBelle E2E Tests
+ *
+ * This file contains helper functions and utilities that can be shared
+ * across different test files for common operations.
+ */
+
+import { Page, Browser, BrowserContext, expect } from '@playwright/test'
+
+/**
+ * Wait for element to be visible and return when ready
+ */
+export async function waitForElementVisible(
+ page: Page,
+ selector: string,
+ timeout: number = 10000
+) {
+ await page.waitForSelector(selector, { timeout, state: 'visible' })
+}
+
+/**
+ * Wait for element to be clickable
+ */
+export async function waitForElementClickable(
+ page: Page,
+ selector: string,
+ timeout: number = 10000
+) {
+ await page.waitForSelector(selector, { timeout, state: 'visible' })
+ await page.waitForFunction((sel) => {
+ const element = document.querySelector(sel)
+ return element && !element.hasAttribute('disabled')
+ }, selector)
+}
+
+/**
+ * Type text into input with realistic delays
+ */
+export async function typeText(
+ page: Page,
+ selector: string,
+ text: string,
+ delay: number = 50
+) {
+ const element = page.locator(selector)
+ await element.click()
+ await element.fill(text)
+
+ // Simulate realistic typing by adding delays between keypresses
+ await page.keyboard.press('End')
+ await page.keyboard.press('Backspace')
+ await page.keyboard.type(text, { delay })
+}
+
+/**
+ * Mock blockchain network responses
+ */
+export function mockBlockchainNetwork(page: Page) {
+ // Mock successful transaction
+ page.addInitScript(() => {
+ window.ethereum = {
+ request: async ({ method, params }: any) => {
+ switch (method) {
+ case 'eth_accounts':
+ return ['0x742d35cc6cF6B4633F82c9B7C7C31E7c7B6C8F9A']
+ case 'eth_chainId':
+ return '0xa4ec' // Celo mainnet
+ case 'personal_sign':
+ return '0xmockedSignature'
+ default:
+ return null
+ }
+ },
+ isMetaMask: true,
+ selectedAddress: '0x742d35cc6cF6B4633F82c9B7C7C31E7c7B6C8F9A',
+ on: (event: string, callback: Function) => {
+ // Mock wallet event listeners
+ if (event === 'accountsChanged') {
+ setTimeout(() => callback(['0x742d35cc6cF6B4633F82c9B7C7C31E7c7B6C8F9A']), 100)
+ }
+ if (event === 'chainChanged') {
+ setTimeout(() => callback('0xa4ec'), 100)
+ }
+ },
+ removeListener: () => {}
+ }
+ })
+}
+
+/**
+ * Mock contract responses with realistic data
+ */
+export function mockContractResponses(page: Page) {
+ page.addInitScript(() => {
+ // Mock registry contract
+ window.readContractMock = async (contractAddress: string, functionName: string, args?: any[]) => {
+ switch (functionName) {
+ case 'getAllUsers':
+ return [
+ '0x742d35cc6cF6B4633F82c9B7C7C31E7c7B6C8F9A', // Alice
+ '0x8ba1f109551bD432803012645Hac136c32c3c0c4', // Bob
+ '0x1234567890123456789012345678901234567890', // Charlie
+ '0x9876543210987654321098765432109876543210' // Dave
+ ]
+
+ case 'getUserDetails':
+ const address = args[0].toLowerCase()
+ const users: Record = {
+ '0x742d35cc6cF6b4633f82c9b7c7c31e7c7b6c8f9a': {
+ ensName: 'alice.eth',
+ avatarHash: 'QmAlice123',
+ registered: true,
+ displayName: 'Alice Johnson',
+ bio: 'Blockchain developer'
+ },
+ '0x8ba1f109551bd432803012645hac136c32c3c0c4': {
+ ensName: 'bob.smith',
+ avatarHash: 'QmBob456',
+ registered: true,
+ displayName: 'Bob Smith',
+ bio: 'Web3 designer'
+ },
+ '0x1234567890123456789012345678901234567890': {
+ ensName: '',
+ avatarHash: '',
+ registered: false,
+ displayName: 'Charlie User',
+ bio: 'New user'
+ },
+ '0x9876543210987654321098765432109876543210': {
+ ensName: 'dave.dev',
+ avatarHash: 'QmDave789',
+ registered: true,
+ displayName: 'Dave Developer',
+ bio: 'Smart contract developer'
+ }
+ }
+ return users[address] || { ensName: '', avatarHash: '', registered: false }
+
+ case 'getConversation':
+ return [
+ {
+ sender: args[0],
+ receiver: args[1],
+ content: 'Hey there! Welcome to the chat.',
+ timestamp: Math.floor(Date.now() / 1000) - 3600,
+ messageType: 'text'
+ },
+ {
+ sender: args[1],
+ receiver: args[0],
+ content: 'Hi! Thanks for the warm welcome.',
+ timestamp: Math.floor(Date.now() / 1000) - 3500,
+ messageType: 'text'
+ }
+ ]
+
+ default:
+ return null
+ }
+ }
+
+ // Mock write contract
+ window.writeContractMock = async (contractAddress: string, functionName: string, args?: any[]) => {
+ if (functionName === 'sendMessage') {
+ // Simulate transaction delay
+ await new Promise(resolve => setTimeout(resolve, 1000))
+ return '0xmockedTransactionHash123456789'
+ }
+ return null
+ }
+
+ // Mock verification status
+ window.getVerificationStatus = (address: string) => {
+ const verifiedUsers = [
+ '0x742d35cc6cF6B4633F82c9B7C7C31E7c7B6C8F9A',
+ '0x8ba1f109551bD432803012645Hac136c32c3c0c4',
+ '0x9876543210987654321098765432109876543210'
+ ]
+ return verifiedUsers.includes(address.toLowerCase())
+ }
+ })
+}
+
+/**
+ * Create realistic user scenarios
+ */
+export async function createRealisticUserScenario(page: Page, userType: 'verified' | 'unverified' = 'verified') {
+ const users = {
+ verified: {
+ address: '0x742d35cc6cF6B4633F82c9B7C7C31E7c7B6C8F9A',
+ ensName: 'alice.eth',
+ displayName: 'Alice Johnson',
+ isVerified: true
+ },
+ unverified: {
+ address: '0x1234567890123456789012345678901234567890',
+ ensName: '',
+ displayName: 'Charlie User',
+ isVerified: false
+ }
+ }
+
+ const user = users[userType]
+
+ // Mock wallet connection
+ await page.addInitScript((user) => {
+ window.ethereum = {
+ request: async ({ method, params }: any) => {
+ switch (method) {
+ case 'eth_accounts':
+ return [user.address]
+ case 'eth_chainId':
+ return '0xa4ec'
+ default:
+ return null
+ }
+ },
+ isMetaMask: true,
+ selectedAddress: user.address
+ }
+ }, user)
+
+ // Mock localStorage with user data
+ await page.evaluate((user) => {
+ localStorage.setItem('connectedAddress', user.address)
+ localStorage.setItem('userProfile', JSON.stringify({
+ address: user.address,
+ displayName: user.displayName,
+ isVerified: user.isVerified,
+ ensName: user.ensName
+ }))
+ }, user)
+
+ return user
+}
+
+/**
+ * Simulate network latency and delays
+ */
+export async function simulateNetworkDelay(page: Page, delayMs: number = 1000) {
+ await page.waitForTimeout(delayMs)
+}
+
+/**
+ * Mock file upload scenario
+ */
+export async function mockFileUpload(page: Page, fileName: string = 'test-document.pdf') {
+ // Create a mock file
+ const fileContent = 'Mock file content for testing'
+ const filePath = `./test-upload-${fileName}`
+
+ // In real tests, you would create an actual file
+ // For now, we'll mock the file upload dialog
+ await page.addInitScript(() => {
+ const originalOpen = window.open
+ window.open = function(url?: string, target?: string, features?: string) {
+ if (url?.includes('file://')) {
+ return null // Mock file dialog
+ }
+ return originalOpen.call(this, url, target, features)
+ }
+ })
+}
+
+/**
+ * Verify element has expected text content
+ */
+export async function verifyTextContent(
+ page: Page,
+ selector: string,
+ expectedText: string
+) {
+ const element = page.locator(selector)
+ await expect(element).toContainText(expectedText)
+}
+
+/**
+ * Verify element has expected attribute value
+ */
+export async function verifyAttribute(
+ page: Page,
+ selector: string,
+ attribute: string,
+ expectedValue: string
+) {
+ const element = page.locator(selector)
+ await expect(element).toHaveAttribute(attribute, expectedValue)
+}
+
+/**
+ * Verify element has expected class
+ */
+export async function verifyClass(
+ page: Page,
+ selector: string,
+ expectedClass: string
+) {
+ const element = page.locator(selector)
+ await expect(element).toHaveClass(new RegExp(expectedClass))
+}
+
+/**
+ * Wait for API call to complete
+ */
+export async function waitForApiCall(page: Page, urlPattern: string, timeout: number = 10000) {
+ const [response] = await Promise.all([
+ page.waitForResponse(response =>
+ response.url().includes(urlPattern) && response.status() < 400,
+ { timeout }
+ )
+ ])
+ return response
+}
+
+/**
+ * Generate random test data
+ */
+export function generateTestData() {
+ return {
+ randomAddress: `0x${Math.random().toString(16).substr(2, 40)}`,
+ randomMessage: `Test message ${Date.now()} - ${Math.random().toString(36).substr(2, 9)}`,
+ randomEnsName: `testuser${Math.random().toString(36).substr(2, 6)}.eth`,
+ randomGroupName: `Test Group ${Math.random().toString(36).substr(2, 6)}`
+ }
+}
+
+/**
+ * Clean up test data
+ */
+export async function cleanupTestData(page: Page) {
+ // Clear localStorage and sessionStorage
+ await page.evaluate(() => {
+ localStorage.clear()
+ sessionStorage.clear()
+ })
+
+ // Clear any test cookies
+ await page.context().clearCookies()
+}
+
+/**
+ * Take screenshot for debugging
+ */
+export async function takeDebugScreenshot(page: Page, name: string) {
+ await page.screenshot({
+ path: `./test-screenshots/${name}-${Date.now()}.png`,
+ fullPage: true
+ })
+}
\ No newline at end of file
diff --git a/next-frontend/e2e/tests/auth/login-logout.spec.ts b/next-frontend/e2e/tests/auth/login-logout.spec.ts
new file mode 100644
index 0000000..65d25e3
--- /dev/null
+++ b/next-frontend/e2e/tests/auth/login-logout.spec.ts
@@ -0,0 +1,502 @@
+/**
+ * E2E Tests for Login and Logout Flows
+ *
+ * These tests verify authentication functionality including:
+ * - Wallet connection and disconnection
+ * - User session management
+ * - Authentication state persistence
+ * - Login error handling
+ */
+
+import { test, expect } from '../../fixtures/chat-fixtures'
+import { MOCK_USERS } from '../../fixtures/chat-fixtures'
+
+test.describe('Login and Logout Flows', () => {
+ test.beforeEach(async ({ page }) => {
+ // Clear any existing session
+ await page.context().clearCookies()
+ await page.goto('/')
+ })
+
+ test.describe('Wallet Connection', () => {
+ test('should connect wallet successfully', async ({
+ page,
+ mockWalletConnection
+ }) => {
+ const user = MOCK_USERS[0] // Alice
+
+ // Mock wallet not connected initially
+ await page.addInitScript(() => {
+ window.ethereum = {
+ request: async ({ method, params }: any) => {
+ switch (method) {
+ case 'eth_accounts':
+ return [] // No accounts connected initially
+ case 'eth_chainId':
+ return '0xa4ec' // Celo mainnet
+ default:
+ return null
+ }
+ },
+ isMetaMask: true
+ }
+ })
+
+ // Click connect wallet button
+ await page.locator('[data-testid="connect-wallet-button"]').click()
+
+ // Select wallet (mock MetaMask)
+ await page.locator('[data-testid="metamask-option"]').click()
+
+ // Mock successful wallet connection
+ await mockWalletConnection(user)
+
+ // Verify wallet connected successfully
+ await expect(page.locator('[data-testid="wallet-connected"]')).toBeVisible()
+ await expect(page.locator('[data-testid="wallet-address"]')).toContainText(user.address.slice(0, 6))
+
+ // Verify user profile is displayed
+ await expect(page.locator('[data-testid="user-profile"]')).toBeVisible()
+ await expect(page.locator('[data-testid="user-display-name"]')).toContainText(user.displayName)
+
+ // Verify chat interface is accessible
+ await expect(page.locator('[data-testid="main-chat"]')).toBeVisible()
+ })
+
+ test('should handle wallet connection rejection', async ({
+ page
+ }) => {
+ // Mock wallet that rejects connection
+ await page.addInitScript(() => {
+ window.ethereum = {
+ request: async ({ method, params }: any) => {
+ if (method === 'eth_requestAccounts') {
+ throw new Error('User rejected the request')
+ }
+ return []
+ },
+ isMetaMask: true
+ }
+ })
+
+ // Attempt to connect wallet
+ await page.locator('[data-testid="connect-wallet-button"]').click()
+ await page.locator('[data-testid="metamask-option"]').click()
+
+ // Verify error handling
+ await expect(page.locator('[data-testid="connection-error"]')).toBeVisible()
+ await expect(page.locator('[data-testid="connection-error"]')).toContainText('Connection rejected')
+
+ // Verify retry option is available
+ await expect(page.locator('[data-testid="retry-connection-button"]')).toBeVisible()
+ })
+
+ test('should handle MetaMask not installed', async ({
+ page
+ }) => {
+ // Mock no wallet installed
+ await page.addInitScript(() => {
+ window.ethereum = undefined
+ })
+
+ // Attempt to connect wallet
+ await page.locator('[data-testid="connect-wallet-button"]').click()
+
+ // Verify wallet installation prompt
+ await expect(page.locator('[data-testid="wallet-not-found"]')).toBeVisible()
+ await expect(page.locator('[data-testid="install-metamask-button"]')).toBeVisible()
+
+ // Verify instructions are provided
+ await expect(page.locator('[data-testid="installation-instructions"]')).toContainText('install MetaMask')
+ })
+
+ test('should handle wrong network selection', async ({
+ page,
+ mockWalletConnection
+ }) => {
+ const user = MOCK_USERS[0]
+
+ // Mock wallet connected to wrong network
+ await page.addInitScript(() => {
+ window.ethereum = {
+ request: async ({ method, params }: any) => {
+ switch (method) {
+ case 'eth_accounts':
+ return [user.address]
+ case 'eth_chainId':
+ return '0x1' // Ethereum mainnet instead of Celo
+ default:
+ return null
+ }
+ },
+ isMetaMask: true,
+ selectedAddress: user.address
+ }
+ })
+
+ await mockWalletConnection(user)
+ await page.goto('/')
+
+ // Verify network switch prompt
+ await expect(page.locator('[data-testid="wrong-network-warning"]')).toBeVisible()
+ await expect(page.locator('[data-testid="wrong-network-warning"]')).toContainText('Please switch to Celo network')
+ await expect(page.locator('[data-testid="switch-network-button"]')).toBeVisible()
+
+ // Simulate network switch
+ await page.locator('[data-testid="switch-network-button"]').click()
+ await page.waitForTimeout(2000)
+
+ // Verify network switch success
+ await expect(page.locator('[data-testid="network-switch-success"]')).toBeVisible()
+ await expect(page.locator('[data-testid="current-network"]')).toContainText('Celo')
+ })
+ })
+
+ test.describe('Session Management', () => {
+ test('should maintain session across page refreshes', async ({
+ page,
+ mockWalletConnection
+ }) => {
+ const user = MOCK_USERS[0]
+ await mockWalletConnection(user)
+ await page.goto('/')
+
+ // Verify session is maintained
+ await expect(page.locator('[data-testid="wallet-connected"]')).toBeVisible()
+ await expect(page.locator('[data-testid="user-profile"]')).toBeVisible()
+
+ // Refresh page
+ await page.reload()
+ await page.waitForTimeout(3000)
+
+ // Verify session persists
+ await expect(page.locator('[data-testid="wallet-connected"]')).toBeVisible()
+ await expect(page.locator('[data-testid="user-display-name"]')).toContainText(user.displayName)
+
+ // Verify chat state is preserved
+ await expect(page.locator('[data-testid="main-chat"]')).toBeVisible()
+ await expect(page.locator('[data-testid="chat-list"]')).toBeVisible()
+ })
+
+ test('should handle expired sessions', async ({
+ page,
+ mockWalletConnection
+ }) => {
+ const user = MOCK_USERS[1] // Bob
+
+ await mockWalletConnection(user)
+ await page.goto('/')
+
+ // Wait for session to be established
+ await expect(page.locator('[data-testid="wallet-connected"]')).toBeVisible()
+
+ // Mock session expiry by clearing localStorage
+ await page.evaluate(() => {
+ localStorage.clear()
+ sessionStorage.clear()
+ })
+
+ // Force page reload
+ await page.reload()
+ await page.waitForTimeout(3000)
+
+ // Verify session reset
+ await expect(page.locator('[data-testid="connect-wallet-button"]')).toBeVisible()
+ await expect(page.locator('[data-testid="wallet-connected"]')).not.toBeVisible()
+ })
+
+ test('should remember user preferences', async ({
+ page,
+ mockWalletConnection
+ }) => {
+ const user = MOCK_USERS[0]
+
+ await mockWalletConnection(user)
+ await page.goto('/')
+
+ // Set user preferences
+ await page.locator('[data-testid="theme-toggle"]').click()
+ await page.locator('[data-testid="language-selector"]').selectOption('es')
+
+ // Verify preferences are saved
+ await expect(page.locator('[data-testid="theme-dark"]')).toHaveClass(/active/)
+ await expect(page.locator('[data-testid="language-spanish"]')).toHaveClass(/active/)
+
+ // Refresh and verify preferences persist
+ await page.reload()
+ await page.waitForTimeout(2000)
+ await expect(page.locator('[data-testid="theme-dark"]')).toHaveClass(/active/)
+ await expect(page.locator('[data-testid="language-spanish"]')).toHaveClass(/active/)
+ })
+ })
+
+ test.describe('Logout Functionality', () => {
+ test('should disconnect wallet successfully', async ({
+ page,
+ mockWalletConnection
+ }) => {
+ const user = MOCK_USERS[2] // Charlie (unverified)
+
+ await mockWalletConnection(user)
+ await page.goto('/')
+
+ // Verify connected state
+ await expect(page.locator('[data-testid="wallet-connected"]')).toBeVisible()
+
+ // Open user menu
+ await page.locator('[data-testid="user-profile-button"]').click()
+
+ // Click disconnect
+ await page.locator('[data-testid="disconnect-wallet-button"]').click()
+
+ // Confirm disconnection
+ await page.locator('[data-testid="confirm-disconnect"]').click()
+
+ // Verify disconnection
+ await expect(page.locator('[data-testid="connect-wallet-button"]')).toBeVisible()
+ await expect(page.locator('[data-testid="wallet-connected"]')).not.toBeVisible()
+ await expect(page.locator('[data-testid="user-profile"]')).not.toBeVisible()
+
+ // Verify chat interface is hidden
+ await expect(page.locator('[data-testid="main-chat"]')).not.toBeVisible()
+ })
+
+ test('should clear user data on logout', async ({
+ page,
+ mockWalletConnection
+ }) => {
+ const user = MOCK_USERS[1] // Bob
+
+ await mockWalletConnection(user)
+ await page.goto('/')
+
+ // Add some user data
+ await page.evaluate(() => {
+ localStorage.setItem('userMessages', JSON.stringify(['test message']))
+ localStorage.setItem('chatPreferences', JSON.stringify({ theme: 'dark' }))
+ sessionStorage.setItem('tempData', 'temp value')
+ })
+
+ // Disconnect wallet
+ await page.locator('[data-testid="user-profile-button"]').click()
+ await page.locator('[data-testid="disconnect-wallet-button"]').click()
+ await page.locator('[data-testid="confirm-disconnect"]').click()
+
+ // Verify data is cleared
+ const userMessages = await page.evaluate(() => localStorage.getItem('userMessages'))
+ const chatPreferences = await page.evaluate(() => localStorage.getItem('chatPreferences'))
+ const tempData = await page.evaluate(() => sessionStorage.getItem('tempData'))
+
+ expect(userMessages).toBeNull()
+ expect(chatPreferences).toBeNull()
+ expect(tempData).toBeNull()
+ })
+
+ test('should handle logout cancellation', async ({
+ page,
+ mockWalletConnection
+ }) => {
+ const user = MOCK_USERS[3] // Dave
+
+ await mockWalletConnection(user)
+ await page.goto('/')
+
+ // Open user menu
+ await page.locator('[data-testid="user-profile-button"]').click()
+
+ // Click disconnect
+ await page.locator('[data-testid="disconnect-wallet-button"]').click()
+
+ // Cancel disconnection
+ await page.locator('[data-testid="cancel-disconnect"]').click()
+
+ // Verify still connected
+ await expect(page.locator('[data-testid="wallet-connected"]')).toBeVisible()
+ await expect(page.locator('[data-testid="user-profile"]')).toBeVisible()
+ })
+ })
+
+ test.describe('Authentication State Management', () => {
+ test('should handle multiple wallet connections', async ({
+ page,
+ mockWalletConnection
+ }) => {
+ const user1 = MOCK_USERS[0] // Alice
+ const user2 = MOCK_USERS[1] // Bob
+
+ // Connect first user
+ await mockWalletConnection(user1)
+ await page.goto('/')
+
+ await expect(page.locator('[data-testid="wallet-connected"]')).toBeVisible()
+ await expect(page.locator('[data-testid="user-display-name"]')).toContainText(user1.displayName)
+
+ // Simulate wallet account change
+ await page.addInitScript(() => {
+ window.ethereum.request = async ({ method, params }: any) => {
+ switch (method) {
+ case 'eth_accounts':
+ return [user2.address] // Switch to different account
+ default:
+ return null
+ }
+ }
+
+ // Trigger accountsChanged event
+ setTimeout(() => {
+ window.ethereum.emit('accountsChanged', [user2.address])
+ }, 100)
+ })
+
+ // Wait for account change processing
+ await page.waitForTimeout(2000)
+
+ // Verify user switched
+ await expect(page.locator('[data-testid="user-display-name"]')).toContainText(user2.displayName)
+ await expect(page.locator('[data-testid="account-switch-notification"]')).toBeVisible()
+ })
+
+ test('should handle wallet lock/unlock', async ({
+ page,
+ mockWalletConnection
+ }) => {
+ const user = MOCK_USERS[2] // Charlie
+
+ await mockWalletConnection(user)
+ await page.goto('/')
+
+ // Verify connected
+ await expect(page.locator('[data-testid="wallet-connected"]')).toBeVisible()
+
+ // Simulate wallet lock
+ await page.addInitScript(() => {
+ window.ethereum.request = async ({ method, params }: any) => {
+ switch (method) {
+ case 'eth_accounts':
+ return [] // No accounts when locked
+ default:
+ return null
+ }
+ }
+ })
+
+ // Wait for lock detection
+ await page.waitForTimeout(2000)
+
+ // Verify wallet appears disconnected
+ await expect(page.locator('[data-testid="wallet-disconnected-warning"]')).toBeVisible()
+ await expect(page.locator('[data-testid="reconnect-prompt"]')).toBeVisible()
+
+ // Simulate unlock
+ await page.addInitScript(() => {
+ window.ethereum.request = async ({ method, params }: any) => {
+ switch (method) {
+ case 'eth_accounts':
+ return [user.address] // Account available again
+ default:
+ return null
+ }
+ }
+ })
+
+ // Auto-reconnect should trigger
+ await page.waitForTimeout(1000)
+ await expect(page.locator('[data-testid="wallet-reconnected"]')).toBeVisible()
+ })
+
+ test('should validate authentication before sensitive actions', async ({
+ page,
+ mockWalletConnection
+ }) => {
+ const user = MOCK_USERS[0] // Alice
+
+ await mockWalletConnection(user)
+ await page.goto('/')
+
+ // Navigate to sensitive action (e.g., group creation)
+ await page.locator('[data-testid="create-group-chat-button"]').click()
+
+ // Verify authentication validation
+ await expect(page.locator('[data-testid="authentication-check"]')).toBeVisible()
+ await expect(page.locator('[data-testid="authentication-check"]')).toContainText('Confirm your identity')
+
+ // Mock successful authentication
+ await page.locator('[data-testid="confirm-authentication"]').click()
+
+ // Verify access granted
+ await expect(page.locator('[data-testid="group-creation-form"]')).toBeVisible()
+ })
+ })
+
+ test.describe('Error Recovery', () => {
+ test('should recover from authentication errors', async ({
+ page
+ }) => {
+ // Start with no wallet
+ await page.goto('/')
+
+ // Try to access protected feature
+ await page.locator('[data-testid="main-chat"]').click()
+
+ // Verify authentication required message
+ await expect(page.locator('[data-testid="authentication-required"]')).toBeVisible()
+ await expect(page.locator('[data-testid="connect-wallet-prompt"]')).toBeVisible()
+
+ // Connect wallet to recover
+ await page.locator('[data-testid="connect-wallet-button"]').click()
+
+ // Mock successful connection
+ await page.addInitScript(() => {
+ window.ethereum = {
+ request: async ({ method, params }: any) => {
+ switch (method) {
+ case 'eth_accounts':
+ return [MOCK_USERS[0].address]
+ default:
+ return null
+ }
+ },
+ isMetaMask: true
+ }
+ })
+
+ // Simulate connection
+ await page.locator('[data-testid="metamask-option"]').click()
+
+ // Verify recovery
+ await expect(page.locator('[data-testid="main-chat"]')).toBeVisible()
+ await expect(page.locator('[data-testid="authentication-required"]')).not.toBeVisible()
+ })
+
+ test('should handle network errors gracefully', async ({
+ page,
+ mockWalletConnection
+ }) => {
+ const user = MOCK_USERS[0]
+
+ await mockWalletConnection(user)
+ await page.goto('/')
+
+ // Simulate network error during authentication check
+ await page.addInitScript(() => {
+ window.readContractMock = async () => {
+ throw new Error('Network error')
+ }
+ })
+
+ // Try to access contract data
+ await page.locator('[data-testid="refresh-chat-data"]').click()
+
+ // Verify error handling
+ await expect(page.locator('[data-testid="network-error-message"]')).toBeVisible()
+ await expect(page.locator('[data-testid="retry-button"]')).toBeVisible()
+
+ // Retry and verify recovery
+ await page.locator('[data-testid="retry-button"]').click()
+ await page.waitForTimeout(2000)
+
+ // Should show loading state during retry
+ await expect(page.locator('[data-testid="loading-indicator"]')).toBeVisible()
+ })
+ })
+})
\ No newline at end of file
diff --git a/next-frontend/e2e/tests/chat/ens-verified-chat.spec.ts b/next-frontend/e2e/tests/chat/ens-verified-chat.spec.ts
new file mode 100644
index 0000000..dd1ab9b
--- /dev/null
+++ b/next-frontend/e2e/tests/chat/ens-verified-chat.spec.ts
@@ -0,0 +1,213 @@
+/**
+ * E2E Tests for ENS-Verified User Chat Flows
+ *
+ * These tests verify the chat functionality for users who have completed
+ * ENS (Ethereum Name Service) verification, ensuring they can:
+ * - Connect to the chat interface
+ * - Send and receive messages
+ * - See verification badges
+ * - Access all chat features
+ */
+
+import { test, expect } from '../../fixtures/chat-fixtures'
+import { MOCK_USERS } from '../../fixtures/chat-fixtures'
+
+test.describe('ENS-Verified User Chat Flows', () => {
+ let verifiedUser: typeof MOCK_USERS[0]
+
+ test.beforeEach(async ({ page, mockWalletConnection, setupMockContract }) => {
+ verifiedUser = MOCK_USERS[0] // Alice - ENS verified user
+ await mockWalletConnection(verifiedUser)
+ await setupMockContract()
+ await page.goto('/')
+ })
+
+ test('should display chat interface with verified user status', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Verify chat interface loads
+ await expect(page.locator('[data-testid="main-chat"]')).toBeVisible()
+
+ // Verify user status is shown as verified
+ await expect(page.locator('[data-testid="user-status"]')).toContainText('Verified')
+
+ // Verify ENS name is displayed
+ await expect(page.locator('[data-testid="user-ens-name"]')).toContainText('alice.eth')
+ })
+
+ test('should allow verified user to send messages', async ({
+ page,
+ waitForChatLoad,
+ selectChat,
+ sendMessage,
+ verifyMessageDelivery
+ }) => {
+ await waitForChatLoad()
+
+ // Select a chat with another user
+ const targetUser = MOCK_USERS[1] // Bob
+ await selectChat(targetUser.address)
+
+ // Send a message
+ const messageContent = 'Hello Bob! This is a test message from a verified user.'
+ await sendMessage(messageContent)
+
+ // Verify message delivery
+ await verifyMessageDelivery(messageContent)
+
+ // Verify the message shows verification status
+ const messageBubble = page.locator('[data-testid="message-bubble"]').filter({
+ hasText: messageContent
+ })
+ await expect(messageBubble).toBeVisible()
+ await expect(messageBubble.locator('[data-testid="verification-badge"]')).toBeVisible()
+ })
+
+ test('should display verification badges for verified users in chat list', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Verify that verified users show verification badges
+ const verifiedChatItems = page.locator('[data-testid="chat-item"]').filter({
+ has: page.locator('[data-testid="verification-badge"]')
+ })
+ await expect(verifiedChatItems).toHaveCount(2) // Alice and Dave are verified
+
+ // Verify non-verified users don't show verification badges
+ const unverifiedChatItems = page.locator('[data-testid="chat-item"]').filter({
+ hasNot: page.locator('[data-testid="verification-badge"]')
+ })
+ await expect(unverifiedChatItems).toHaveCount(1) // Charlie is not verified
+ })
+
+ test('should show verification status in chat headers', async ({
+ page,
+ waitForChatLoad,
+ selectChat
+ }) => {
+ await waitForChatLoad()
+
+ // Select a verified user chat
+ const verifiedTargetUser = MOCK_USERS[3] // Dave
+ await selectChat(verifiedTargetUser.address)
+
+ // Verify verification badge is shown in chat header
+ await expect(page.locator('[data-testid="chat-header"] [data-testid="verification-badge"]')).toBeVisible()
+
+ // Verify ENS name is displayed
+ await expect(page.locator('[data-testid="chat-header-user-name"]')).toContainText('dave.dev')
+ })
+
+ test('should enable enhanced features for verified users', async ({
+ page,
+ waitForChatLoad,
+ selectChat
+ }) => {
+ await waitForChatLoad()
+
+ await selectChat(MOCK_USERS[1].address) // Bob
+
+ // Verify enhanced features are available (e.g., file sharing, voice/video calls)
+ await expect(page.locator('[data-testid="file-attachment-button"]')).toBeVisible()
+ await expect(page.locator('[data-testid="voice-call-button"]')).toBeVisible()
+ await expect(page.locator('[data-testid="video-call-button"]')).toBeVisible()
+ })
+
+ test('should properly display message history with verification context', async ({
+ page,
+ waitForChatLoad,
+ selectChat
+ }) => {
+ await waitForChatLoad()
+
+ await selectChat(MOCK_USERS[1].address) // Bob
+
+ // Verify existing messages show verification context
+ const messageBubbles = page.locator('[data-testid="message-bubble"]')
+ const messageCount = await messageBubbles.count()
+
+ for (let i = 0; i < messageCount; i++) {
+ const messageBubble = messageBubbles.nth(i)
+ const senderAddress = await messageBubble.getAttribute('data-sender')
+ const sender = MOCK_USERS.find(user => user.address.toLowerCase() === senderAddress?.toLowerCase())
+
+ if (sender?.isVerified) {
+ await expect(messageBubble.locator('[data-testid="verification-badge"]')).toBeVisible()
+ } else {
+ await expect(messageBubble.locator('[data-testid="verification-badge"]')).not.toBeVisible()
+ }
+ }
+ })
+
+ test('should handle search functionality for verified users', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Open search functionality
+ await page.locator('[data-testid="search-input"]').click()
+
+ // Search for verified users
+ await page.locator('[data-testid="search-input"]').fill('alice')
+
+ // Verify search results show verification status
+ const searchResults = page.locator('[data-testid="search-results"] [data-testid="user-item"]')
+ await expect(searchResults).toHaveCount(1)
+ await expect(searchResults.locator('[data-testid="verification-badge"]')).toBeVisible()
+ })
+
+ test('should display correct online status for verified users', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Check online status indicators for verified users
+ const verifiedUserChatItems = page.locator('[data-testid="chat-item"]').filter({
+ has: page.locator('[data-testid="verification-badge"]')
+ })
+
+ const count = await verifiedUserChatItems.count()
+ for (let i = 0; i < count; i++) {
+ const chatItem = verifiedUserChatItems.nth(i)
+ await expect(chatItem.locator('[data-testid="online-status"]')).toBeVisible()
+ }
+ })
+
+ test('should maintain verification status across chat sessions', async ({
+ page,
+ waitForChatLoad,
+ selectChat,
+ sendMessage
+ }) => {
+ await waitForChatLoad()
+
+ const targetUser = MOCK_USERS[1]
+ await selectChat(targetUser.address)
+
+ // Send multiple messages
+ await sendMessage('First message from verified user')
+ await sendMessage('Second message from verified user')
+ await sendMessage('Third message from verified user')
+
+ // Refresh the page and verify verification status persists
+ await page.reload()
+ await waitForChatLoad()
+ await selectChat(targetUser.address)
+
+ // Verify all messages still show verification status
+ const messageBubbles = page.locator('[data-testid="message-bubble"]')
+ const messageCount = await messageBubbles.count()
+
+ for (let i = 0; i < messageCount; i++) {
+ const messageBubble = messageBubbles.nth(i)
+ await expect(messageBubble.locator('[data-testid="verification-badge"]')).toBeVisible()
+ }
+ })
+})
\ No newline at end of file
diff --git a/next-frontend/e2e/tests/chat/group-chat.spec.ts b/next-frontend/e2e/tests/chat/group-chat.spec.ts
new file mode 100644
index 0000000..0c20de8
--- /dev/null
+++ b/next-frontend/e2e/tests/chat/group-chat.spec.ts
@@ -0,0 +1,477 @@
+/**
+ * E2E Tests for Group Chat Functionality
+ *
+ * These tests verify group chat features including:
+ * - Creating group chats
+ * - Joining existing groups
+ * - Managing group members
+ * - Sending messages in group context
+ * - Group permissions and access control
+ */
+
+import { test, expect } from '../../fixtures/chat-fixtures'
+import { MOCK_USERS } from '../../fixtures/chat-fixtures'
+
+test.describe('Group Chat Functionality', () => {
+ test.beforeEach(async ({ page, mockWalletConnection, setupMockContract }) => {
+ const user = MOCK_USERS[0] // Alice - verified user
+ await mockWalletConnection(user)
+ await setupMockContract()
+ await page.goto('/')
+ })
+
+ test.describe('Group Creation and Joining', () => {
+ test('should allow verified user to create a new group', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Click "Create Group Chat" button
+ await page.locator('[data-testid="create-group-chat-button"]').click()
+
+ // Fill group creation form
+ await page.locator('[data-testid="group-name-input"]').fill('Blockchain Developers')
+ await page.locator('[data-testid="group-description-input"]').fill('A group for blockchain developers and enthusiasts')
+
+ // Add initial members
+ await page.locator('[data-testid="add-member-input"]').fill(MOCK_USERS[1].address)
+ await page.locator('[data-testid="add-member-button"]').click()
+
+ await page.locator('[data-testid="add-member-input"]').fill(MOCK_USERS[3].address)
+ await page.locator('[data-testid="add-member-button"]').click()
+
+ // Create the group
+ await page.locator('[data-testid="create-group-submit"]').click()
+
+ // Verify group creation success
+ await expect(page.locator('[data-testid="group-created-success"]')).toBeVisible()
+ await expect(page.locator('[data-testid="group-created-success"]')).toContainText('Group created successfully')
+
+ // Verify we're now in the new group chat
+ await expect(page.locator('[data-testid="chat-header"] [data-testid="group-name"]')).toContainText('Blockchain Developers')
+ await expect(page.locator('[data-testid="member-count"]')).toContainText('3 members')
+ })
+
+ test('should allow user to join existing group', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Mock existing groups
+ await page.addInitScript(() => {
+ window.readContractMock = (contractAddress: string, functionName: string, args?: any[]) => {
+ switch (functionName) {
+ case 'getAllUsers':
+ return [
+ '0x742d35cc6cF6B4633F82c9B7C7C31E7c7B6C8F9A',
+ '0x8ba1f109551bD432803012645Hac136c32c3c0c4',
+ '0x1234567890123456789012345678901234567890'
+ ]
+ case 'getUserGroups':
+ return [
+ {
+ id: '0xgroup123',
+ name: 'DeFi Traders',
+ description: 'Decentralized Finance trading group',
+ memberCount: 15,
+ isPublic: true,
+ joined: true
+ }
+ ]
+ default:
+ return []
+ }
+ }
+ })
+
+ // Navigate to group discovery
+ await page.locator('[data-testid="groups-tab"]').click()
+
+ // Verify existing groups are displayed
+ const groupCard = page.locator('[data-testid="group-card"]').first()
+ await expect(groupCard.locator('[data-testid="group-name"]')).toContainText('DeFi Traders')
+ await expect(groupCard.locator('[data-testid="member-count"]')).toContainText('15 members')
+
+ // Join the group
+ await groupCard.locator('[data-testid="join-group-button"]').click()
+
+ // Verify join confirmation
+ await expect(page.locator('[data-testid="join-success-message"]')).toBeVisible()
+
+ // Verify group appears in chat list
+ await page.locator('[data-testid="chat-tab"]').click()
+ const groupChatItem = page.locator('[data-testid="chat-item-0xgroup123"]')
+ await expect(groupChatItem).toBeVisible()
+ await expect(groupChatItem.locator('[data-testid="last-message"]')).toContainText('joined the group')
+ })
+
+ test('should handle private group joining with invitation', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Mock private group
+ await page.addInitScript(() => {
+ window.readContractMock = (contractAddress: string, functionName: string, args?: any[]) => {
+ if (functionName === 'getUserGroups') {
+ return [
+ {
+ id: '0xprivategroup',
+ name: 'VIP Traders',
+ description: 'Private group for verified traders',
+ memberCount: 8,
+ isPublic: false,
+ joined: false,
+ hasInvitation: true
+ }
+ ]
+ }
+ return []
+ }
+ })
+
+ await page.locator('[data-testid="groups-tab"]').click()
+
+ // Verify private group is shown with invitation badge
+ const privateGroupCard = page.locator('[data-testid="group-card"]').first()
+ await expect(privateGroupCard.locator('[data-testid="invitation-badge"]')).toBeVisible()
+ await expect(privateGroupCard.locator('[data-testid="join-group-button"]')).toContainText('Accept Invitation')
+
+ // Accept invitation
+ await privateGroupCard.locator('[data-testid="join-group-button"]').click()
+
+ // Verify invitation acceptance
+ await expect(page.locator('[data-testid="invitation-accepted-message"]')).toBeVisible()
+ })
+
+ test('should validate group creation permissions for unverified users', async ({
+ page,
+ mockWalletConnection,
+ waitForChatLoad
+ }) => {
+ // Switch to unverified user
+ const unverifiedUser = MOCK_USERS[2] // Charlie
+ await mockWalletConnection(unverifiedUser)
+ await page.reload()
+
+ await waitForChatLoad()
+
+ // Try to create group
+ await page.locator('[data-testid="create-group-chat-button"]').click()
+
+ // Verify restriction message
+ await expect(page.locator('[data-testid="group-creation-restriction"]')).toBeVisible()
+ await expect(page.locator('[data-testid="group-creation-restriction"]')).toContainText('ENS verification required to create groups')
+
+ // Verify upgrade prompt
+ await expect(page.locator('[data-testid="upgrade-to-create-groups"]')).toBeVisible()
+ await expect(page.locator('[data-testid="verify-ens-button"]')).toBeVisible()
+ })
+ })
+
+ test.describe('Group Management', () => {
+ test('should allow group admin to manage members', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Create or join a group first
+ await page.locator('[data-testid="create-group-chat-button"]').click()
+ await page.locator('[data-testid="group-name-input"]').fill('Test Group')
+ await page.locator('[data-testid="create-group-submit"]').click()
+
+ // Open group settings
+ await page.locator('[data-testid="group-settings-button"]').click()
+
+ // Add new member
+ await page.locator('[data-testid="add-member-to-group"]').fill(MOCK_USERS[2].address)
+ await page.locator('[data-testid="add-member-submit"]').click()
+
+ // Verify member added
+ await expect(page.locator('[data-testid="member-added-success"]')).toBeVisible()
+
+ // Remove member
+ await page.locator('[data-testid="remove-member-button"]').first().click()
+ await page.locator('[data-testid="confirm-remove-member"]').click()
+
+ // Verify member removed
+ await expect(page.locator('[data-testid="member-removed-success"]')).toBeVisible()
+ })
+
+ test('should display member list with verification status', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Create group with verified and unverified members
+ await page.locator('[data-testid="create-group-chat-button"]').click()
+ await page.locator('[data-testid="group-name-input"]').fill('Mixed Verification Group')
+
+ // Add verified member
+ await page.locator('[data-testid="add-member-input"]').fill(MOCK_USERS[1].address)
+ await page.locator('[data-testid="add-member-button"]').click()
+
+ // Add unverified member
+ await page.locator('[data-testid="add-member-input"]').fill(MOCK_USERS[2].address)
+ await page.locator('[data-testid="add-member-button"]').click()
+
+ await page.locator('[data-testid="create-group-submit"]').click()
+
+ // Open member list
+ await page.locator('[data-testid="view-members-button"]').click()
+
+ // Verify verified members show badges
+ const verifiedMembers = page.locator('[data-testid="group-member-item"]').filter({
+ has: page.locator('[data-testid="verification-badge"]')
+ })
+ await expect(verifiedMembers).toHaveCount(1) // Bob is verified
+
+ // Verify unverified members don't show badges
+ const unverifiedMembers = page.locator('[data-testid="group-member-item"]').filter({
+ hasNot: page.locator('[data-testid="verification-badge"]')
+ })
+ await expect(unverifiedMembers).toHaveCount(2) // Alice (creator) + Charlie
+ })
+
+ test('should handle group invitations and notifications', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Mock incoming group invitation
+ await page.addInitScript(() => {
+ window.mockNewNotification = {
+ type: 'group_invitation',
+ groupId: '0xinvitedgroup',
+ groupName: 'Exclusive DeFi Group',
+ inviterAddress: MOCK_USERS[1].address,
+ inviterName: 'Bob Smith'
+ }
+ })
+
+ // Verify invitation notification appears
+ await expect(page.locator('[data-testid="notification-badge"]')).toBeVisible()
+ await expect(page.locator('[data-testid="notification-badge"]')).toContainText('1')
+
+ // Click notification
+ await page.locator('[data-testid="notifications-button"]').click()
+
+ // Verify invitation details
+ const invitationCard = page.locator('[data-testid="notification-card"]').first()
+ await expect(invitationCard.locator('[data-testid="notification-title"]')).toContainText('Group Invitation')
+ await expect(invitationCard.locator('[data-testid="notification-content"]')).toContainText('Exclusive DeFi Group')
+ await expect(invitationCard.locator('[data-testid="notification-from"]')).toContainText('Bob Smith')
+
+ // Accept invitation
+ await invitationCard.locator('[data-testid="accept-invitation-button"]').click()
+
+ // Verify success
+ await expect(page.locator('[data-testid="invitation-accepted-toast"]')).toBeVisible()
+ })
+ })
+
+ test.describe('Group Messaging', () => {
+ test('should send messages in group context', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Create group
+ await page.locator('[data-testid="create-group-chat-button"]').click()
+ await page.locator('[data-testid="group-name-input"]').fill('Test Group')
+ await page.locator('[data-testid="add-member-input"]').fill(MOCK_USERS[1].address)
+ await page.locator('[data-testid="add-member-button"]').click()
+ await page.locator('[data-testid="create-group-submit"]').click()
+
+ // Send group message
+ const groupMessage = 'Hello everyone! This is a group message.'
+ await page.locator('[data-testid="message-input"]').fill(groupMessage)
+ await page.locator('[data-testid="send-button"]').click()
+
+ // Verify group message delivery
+ await page.waitForFunction((msgContent) => {
+ const messages = document.querySelectorAll('[data-testid="message-bubble"]')
+ return Array.from(messages).some(message =>
+ message.textContent?.includes(msgContent)
+ )
+ }, groupMessage)
+
+ // Verify group context is maintained
+ const messageBubble = page.locator('[data-testid="message-bubble"]').filter({
+ hasText: groupMessage
+ })
+ await expect(messageBubble.locator('[data-testid="group-context-indicator"]')).toBeVisible()
+ await expect(messageBubble.locator('[data-testid="message-type"]')).toContainText('Group')
+ })
+
+ test('should mention specific group members', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Create group
+ await page.locator('[data-testid="create-group-chat-button"]').click()
+ await page.locator('[data-testid="group-name-input"]').fill('Dev Team')
+ await page.locator('[data-testid="add-member-input"]').fill(MOCK_USERS[1].address)
+ await page.locator('[data-testid="add-member-button"]').click()
+ await page.locator('[data-testid="create-group-submit"]').click()
+
+ // Send message with mention
+ const mentionMessage = 'Hey @bob.smith, can you review this code?'
+ await page.locator('[data-testid="message-input"]').fill(mentionMessage)
+
+ // Verify mention suggestion appears
+ await page.locator('[data-testid="mention-suggestion"]').waitFor({ timeout: 2000 })
+ await expect(page.locator('[data-testid="mention-suggestion"]')).toContainText('bob.smith')
+
+ // Select mention
+ await page.locator('[data-testid="mention-suggestion"]').click()
+
+ // Send message
+ await page.locator('[data-testid="send-button"]').click()
+
+ // Verify mention is highlighted in sent message
+ const messageBubble = page.locator('[data-testid="message-bubble"]').filter({
+ hasText: mentionMessage
+ })
+ await expect(messageBubble.locator('[data-testid="user-mention"]')).toBeVisible()
+ await expect(messageBubble.locator('[data-testid="user-mention"]')).toContainText('@bob.smith')
+ })
+
+ test('should handle group message history', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Create group with mock history
+ await page.addInitScript(() => {
+ window.readContractMock = (contractAddress: string, functionName: string, args?: any[]) => {
+ if (functionName === 'getGroupMessages') {
+ return [
+ {
+ sender: MOCK_USERS[1].address,
+ content: 'Welcome to the group!',
+ timestamp: Math.floor(Date.now() / 1000) - 3600,
+ messageType: 'system'
+ },
+ {
+ sender: MOCK_USERS[3].address,
+ content: 'Thanks for having me!',
+ timestamp: Math.floor(Date.now() / 1000) - 3500,
+ messageType: 'text'
+ }
+ ]
+ }
+ return []
+ }
+ })
+
+ await page.locator('[data-testid="create-group-chat-button"]').click()
+ await page.locator('[data-testid="group-name-input"]').fill('Existing Group')
+ await page.locator('[data-testid="create-group-submit"]').click()
+
+ // Verify historical messages are loaded
+ await expect(page.locator('[data-testid="message-bubble"]')).toHaveCount(2)
+
+ // Verify system message styling
+ const systemMessage = page.locator('[data-testid="message-bubble"]').first()
+ await expect(systemMessage).toHaveAttribute('data-message-type', 'system')
+ await expect(systemMessage.locator('[data-testid="system-message-indicator"]')).toBeVisible()
+ })
+ })
+
+ test.describe('Group Security and Permissions', () => {
+ test('should enforce group access permissions', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Mock private group
+ await page.addInitScript(() => {
+ window.readContractMock = (contractAddress: string, functionName: string, args?: any[]) => {
+ if (functionName === 'getUserGroups') {
+ return [
+ {
+ id: '0xprivategroup',
+ name: 'Private Group',
+ isPublic: false,
+ hasAccess: false,
+ accessReason: 'invitation_required'
+ }
+ ]
+ }
+ return []
+ }
+ })
+
+ await page.locator('[data-testid="groups-tab"]').click()
+
+ // Try to access private group without invitation
+ const privateGroupCard = page.locator('[data-testid="group-card"]').first()
+ await privateGroupCard.locator('[data-testid="group-name"]').click()
+
+ // Verify access denied
+ await expect(page.locator('[data-testid="access-denied-message"]')).toBeVisible()
+ await expect(page.locator('[data-testid="access-denied-message"]')).toContainText('Invitation required')
+ await expect(page.locator('[data-testid="request-invitation-button"]')).toBeVisible()
+ })
+
+ test('should handle group admin permissions', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Create group as admin
+ await page.locator('[data-testid="create-group-chat-button"]').click()
+ await page.locator('[data-testid="group-name-input"]').fill('Admin Test Group')
+ await page.locator('[data-testid="create-group-submit"]').click()
+
+ // Verify admin controls are available
+ await expect(page.locator('[data-testid="admin-controls"]')).toBeVisible()
+ await expect(page.locator('[data-testid="transfer-admin-button"]')).toBeVisible()
+ await expect(page.locator('[data-testid="delete-group-button"]')).toBeVisible()
+
+ // Test member management
+ await page.locator('[data-testid="group-settings-button"]').click()
+ await page.locator('[data-testid="make-admin-button"]').click()
+
+ // Verify admin promotion confirmation
+ await expect(page.locator('[data-testid="admin-promotion-success"]')).toBeVisible()
+ })
+
+ test('should prevent unauthorized group modifications', async ({
+ page,
+ mockWalletConnection,
+ waitForChatLoad
+ }) => {
+ // Switch to regular member (not admin)
+ const regularMember = MOCK_USERS[2] // Charlie
+ await mockWalletConnection(regularMember)
+ await page.reload()
+
+ await waitForChatLoad()
+
+ // Try to access group settings
+ await page.locator('[data-testid="chat-item"]').first().click()
+ await page.locator('[data-testid="group-settings-button"]').click()
+
+ // Verify admin-only controls are disabled
+ await expect(page.locator('[data-testid="delete-group-button"]')).toBeDisabled()
+ await expect(page.locator('[data-testid="transfer-admin-button"]')).toBeDisabled()
+
+ // Verify access denied message
+ await expect(page.locator('[data-testid="admin-access-required"]')).toBeVisible()
+ })
+ })
+})
\ No newline at end of file
diff --git a/next-frontend/e2e/tests/chat/message-delivery.spec.ts b/next-frontend/e2e/tests/chat/message-delivery.spec.ts
new file mode 100644
index 0000000..bd0dd70
--- /dev/null
+++ b/next-frontend/e2e/tests/chat/message-delivery.spec.ts
@@ -0,0 +1,373 @@
+/**
+ * E2E Tests for Message Delivery Scenarios
+ *
+ * These tests verify various message delivery scenarios including:
+ * - Successful message delivery
+ * - Failed message delivery and error handling
+ * - Network issues and retry mechanisms
+ * - Message ordering and timestamp verification
+ */
+
+import { test, expect } from '../../fixtures/chat-fixtures'
+import { MOCK_USERS, MOCK_MESSAGES } from '../../fixtures/chat-fixtures'
+
+test.describe('Message Delivery Scenarios', () => {
+ test.beforeEach(async ({ page, mockWalletConnection, setupMockContract }) => {
+ const user = MOCK_USERS[0] // Alice - verified user
+ await mockWalletConnection(user)
+ await setupMockContract()
+ await page.goto('/')
+ })
+
+ test.describe('Successful Message Delivery', () => {
+ test('should deliver messages successfully between verified users', async ({
+ page,
+ waitForChatLoad,
+ selectChat,
+ sendMessage,
+ verifyMessageDelivery
+ }) => {
+ await waitForChatLoad()
+
+ const targetUser = MOCK_USERS[1] // Bob
+ await selectChat(targetUser.address)
+
+ // Send various types of messages
+ const messages = [
+ 'Hello Bob! How are you?',
+ 'This is a longer message to test message delivery with more content. It should wrap properly and maintain formatting.',
+ 'Message with special characters: @#$%^&*()_+{}|:<>?[]\\;\'",./',
+ 'Unicode message: ไฝ ๅฅฝไธ็ ๐ ๐'
+ ]
+
+ for (const message of messages) {
+ await sendMessage(message)
+ await verifyMessageDelivery(message)
+ }
+
+ // Verify all messages appear in correct order
+ const messageBubbles = page.locator('[data-testid="message-bubble"]')
+ const messageCount = await messageBubbles.count()
+ expect(messageCount).toBe(messages.length)
+
+ // Verify timestamps are displayed
+ for (let i = 0; i < messageCount; i++) {
+ const messageBubble = messageBubbles.nth(i)
+ await expect(messageBubble.locator('[data-testid="message-timestamp"]')).toBeVisible()
+ }
+ })
+
+ test('should deliver messages with proper sender identification', async ({
+ page,
+ waitForChatLoad,
+ selectChat,
+ sendMessage
+ }) => {
+ await waitForChatLoad()
+
+ const targetUser = MOCK_USERS[2] // Charlie (unverified)
+ await selectChat(targetUser.address)
+
+ const messageContent = 'Message with sender identification test'
+ await sendMessage(messageContent)
+
+ // Verify message shows correct sender
+ const messageBubble = page.locator('[data-testid="message-bubble"]').filter({
+ hasText: messageContent
+ })
+
+ await expect(messageBubble).toHaveAttribute('data-sender', MOCK_USERS[0].address.toLowerCase())
+ await expect(messageBubble).toHaveAttribute('data-is-own-message', 'true')
+ })
+
+ test('should update chat list with last message info', async ({
+ page,
+ waitForChatLoad,
+ selectChat,
+ sendMessage
+ }) => {
+ await waitForChatLoad()
+
+ const targetUser = MOCK_USERS[1] // Bob
+ await selectChat(targetUser.address)
+
+ const messageContent = 'This should appear in chat list as last message'
+ await sendMessage(messageContent)
+
+ // Go back to chat list
+ await page.locator('[data-testid="back-to-chat-list"]').click()
+
+ // Verify chat item shows last message and timestamp
+ const chatItem = page.locator(`[data-testid="chat-item-${targetUser.address}"]`)
+ await expect(chatItem.locator('[data-testid="last-message"]')).toContainText(messageContent)
+ await expect(chatItem.locator('[data-testid="last-message-time"]')).toBeVisible()
+
+ // Verify message is marked as read
+ await expect(chatItem.locator('[data-testid="unread-badge"]')).not.toBeVisible()
+ })
+
+ test('should handle rapid message sending', async ({
+ page,
+ waitForChatLoad,
+ selectChat
+ }) => {
+ await waitForChatLoad()
+
+ const targetUser = MOCK_USERS[3] // Dave
+ await selectChat(targetUser.address)
+
+ // Send multiple messages rapidly
+ const messages = Array.from({ length: 5 }, (_, i) => `Rapid message ${i + 1}`)
+
+ for (const message of messages) {
+ await page.locator('[data-testid="message-input"]').fill(message)
+ await page.locator('[data-testid="send-button"]').click()
+ }
+
+ // Wait for all messages to appear
+ await page.waitForFunction((expectedMessages) => {
+ const messageBubbles = document.querySelectorAll('[data-testid="message-bubble"]')
+ return messageBubbles.length >= expectedMessages
+ }, messages.length, { timeout: 10000 })
+
+ // Verify all messages are delivered in order
+ const messageBubbles = page.locator('[data-testid="message-bubble"]')
+ const messageCount = await messageBubbles.count()
+ expect(messageCount).toBeGreaterThanOrEqual(messages.length)
+ })
+ })
+
+ test.describe('Failed Message Delivery', () => {
+ test('should handle network failures gracefully', async ({
+ page,
+ waitForChatLoad,
+ selectChat
+ }) => {
+ await waitForChatLoad()
+
+ const targetUser = MOCK_USERS[1] // Bob
+ await selectChat(targetUser.address)
+
+ // Simulate network failure
+ await page.addInitScript(() => {
+ window.writeContractMock = async () => {
+ throw new Error('Network error: Failed to send transaction')
+ }
+ })
+
+ // Attempt to send message
+ const messageContent = 'This message should fail to send'
+ await page.locator('[data-testid="message-input"]').fill(messageContent)
+ await page.locator('[data-testid="send-button"]').click()
+
+ // Verify error handling
+ await expect(page.locator('[data-testid="error-message"]')).toBeVisible()
+ await expect(page.locator('[data-testid="error-message"]')).toContainText('Failed to send message')
+
+ // Verify retry option is available
+ await expect(page.locator('[data-testid="retry-send-button"]')).toBeVisible()
+
+ // Verify message is not displayed in chat
+ const messageBubble = page.locator('[data-testid="message-bubble"]').filter({
+ hasText: messageContent
+ })
+ await expect(messageBubble).not.toBeVisible()
+ })
+
+ test('should handle contract transaction failures', async ({
+ page,
+ waitForChatLoad,
+ selectChat
+ }) => {
+ await waitForChatLoad()
+
+ const targetUser = MOCK_USERS[2] // Charlie
+ await selectChat(targetUser.address)
+
+ // Simulate contract error
+ await page.addInitScript(() => {
+ window.writeContractMock = async () => {
+ throw new Error('Contract error: Insufficient gas')
+ }
+ })
+
+ // Attempt to send message
+ await page.locator('[data-testid="message-input"]').fill('Contract error test message')
+ await page.locator('[data-testid="send-button"]').click()
+
+ // Verify specific error message
+ await expect(page.locator('[data-testid="error-message"]')).toContainText('Insufficient gas')
+
+ // Verify suggestions are provided
+ await expect(page.locator('[data-testid="error-suggestions"]')).toBeVisible()
+ await expect(page.locator('[data-testid="error-suggestions"]')).toContainText('Try increasing gas limit')
+ })
+
+ test('should handle invalid recipient addresses', async ({
+ page,
+ waitForChatLoad,
+ selectChat
+ }) => {
+ await waitForChatLoad()
+
+ const targetUser = MOCK_USERS[1] // Bob
+ await selectChat(targetUser.address)
+
+ // Simulate invalid address response
+ await page.addInitScript(() => {
+ window.writeContractMock = async (contractAddress: string, functionName: string, args: any[]) => {
+ if (functionName === 'sendMessage') {
+ throw new Error('Invalid recipient address')
+ }
+ return null
+ }
+ })
+
+ await page.locator('[data-testid="message-input"]').fill('Invalid address test')
+ await page.locator('[data-testid="send-button"]').click()
+
+ // Verify invalid address error
+ await expect(page.locator('[data-testid="error-message"]')).toContainText('Invalid recipient')
+
+ // Verify user is prompted to select a different recipient
+ await expect(page.locator('[data-testid="select-different-user-prompt"]')).toBeVisible()
+ })
+
+ test('should handle message content validation errors', async ({
+ page,
+ waitForChatLoad,
+ selectChat
+ }) => {
+ await waitForChatLoad()
+
+ const targetUser = MOCK_USERS[3] // Dave
+ await selectChat(targetUser.address)
+
+ // Test empty message
+ await page.locator('[data-testid="message-input"]').fill('')
+ await page.locator('[data-testid="send-button"]').click()
+
+ // Send button should be disabled for empty messages
+ await expect(page.locator('[data-testid="send-button"]')).toBeDisabled()
+
+ // Test message that's too long
+ const longMessage = 'x'.repeat(10000) // Very long message
+ await page.locator('[data-testid="message-input"]').fill(longMessage)
+
+ // Character count should show limit exceeded
+ await expect(page.locator('[data-testid="character-count"]')).toContainText('10000/1000')
+ await expect(page.locator('[data-testid="character-count"]')).toHaveClass(/text-red/)
+
+ // Send button should be disabled
+ await expect(page.locator('[data-testid="send-button"]')).toBeDisabled()
+ })
+
+ test('should retry failed messages automatically', async ({
+ page,
+ waitForChatLoad,
+ selectChat
+ }) => {
+ await waitForChatLoad()
+
+ const targetUser = MOCK_USERS[1] // Bob
+ await selectChat(targetUser.address)
+
+ let attemptCount = 0
+ await page.addInitScript(() => {
+ window.writeContractMock = async () => {
+ attemptCount++
+ if (attemptCount < 3) {
+ throw new Error('Temporary network error')
+ }
+ return '0xsuccessAfterRetry'
+ }
+ })
+
+ const messageContent = 'This message should eventually succeed after retries'
+ await page.locator('[data-testid="message-input"]').fill(messageContent)
+ await page.locator('[data-testid="send-button"]').click()
+
+ // Should show retry attempts
+ await expect(page.locator('[data-testid="retry-attempt-indicator"]')).toContainText('Retrying...')
+
+ // Should eventually succeed
+ await page.waitForFunction((msgContent) => {
+ const messages = document.querySelectorAll('[data-testid="message-bubble"]')
+ return Array.from(messages).some(message =>
+ message.textContent?.includes(msgContent)
+ )
+ }, messageContent, { timeout: 15000 })
+
+ // Verify final success message
+ await expect(page.locator('[data-testid="success-message"]')).toContainText('Message sent successfully')
+ })
+ })
+
+ test.describe('Message Ordering and Timestamps', () => {
+ test('should maintain message order in conversation', async ({
+ page,
+ waitForChatLoad,
+ selectChat,
+ sendMessage
+ }) => {
+ await waitForChatLoad()
+
+ const targetUser = MOCK_USERS[1] // Bob
+ await selectChat(targetUser.address)
+
+ const messages = ['First message', 'Second message', 'Third message']
+
+ for (const message of messages) {
+ await sendMessage(message)
+ }
+
+ // Verify messages appear in correct order
+ const messageBubbles = page.locator('[data-testid="message-bubble"]')
+ const messageCount = await messageBubbles.count()
+
+ for (let i = 0; i < messages.length; i++) {
+ const messageBubble = messageBubbles.nth(i)
+ await expect(messageBubble).toContainText(messages[i])
+ }
+
+ // Verify timestamps are in ascending order
+ const timestamps = []
+ for (let i = 0; i < messageCount; i++) {
+ const timestampText = await page.locator('[data-testid="message-bubble"]').nth(i)
+ .locator('[data-testid="message-timestamp"]').textContent()
+ timestamps.push(timestampText)
+ }
+
+ // Timestamps should be in order (allowing for slight time differences)
+ expect(timestamps[0]).toBeTruthy()
+ expect(timestamps[1]).toBeTruthy()
+ expect(timestamps[2]).toBeTruthy()
+ })
+
+ test('should display message timestamps correctly', async ({
+ page,
+ waitForChatLoad,
+ selectChat,
+ sendMessage
+ }) => {
+ await waitForChatLoad()
+
+ const targetUser = MOCK_USERS[2] // Charlie
+ await selectChat(targetUser.address)
+
+ const messageContent = 'Timestamp test message'
+ await sendMessage(messageContent)
+
+ // Verify timestamp is displayed in readable format
+ const timestampElement = page.locator('[data-testid="message-bubble"]')
+ .filter({ hasText: messageContent })
+ .locator('[data-testid="message-timestamp"]')
+
+ await expect(timestampElement).toBeVisible()
+
+ // Timestamp should show relative time (e.g., "now", "1m ago")
+ const timestampText = await timestampElement.textContent()
+ expect(timestampText).toMatch(/(now|\d+\s*(m|h|d)\s*ago)/)
+ })
+ })
+})
\ No newline at end of file
diff --git a/next-frontend/e2e/tests/chat/non-verified-chat.spec.ts b/next-frontend/e2e/tests/chat/non-verified-chat.spec.ts
new file mode 100644
index 0000000..6490b86
--- /dev/null
+++ b/next-frontend/e2e/tests/chat/non-verified-chat.spec.ts
@@ -0,0 +1,244 @@
+/**
+ * E2E Tests for Non-Verified User Chat Flows
+ *
+ * These tests verify the chat functionality for users who have not completed
+ * ENS verification, ensuring they can:
+ * - Connect to the chat interface with limited features
+ * - Send and receive messages with restricted functionality
+ * - See that they cannot access premium features
+ * - Experience appropriate UI limitations
+ */
+
+import { test, expect } from '../../fixtures/chat-fixtures'
+import { MOCK_USERS } from '../../fixtures/chat-fixtures'
+
+test.describe('Non-Verified User Chat Flows', () => {
+ let unverifiedUser: typeof MOCK_USERS[2]
+
+ test.beforeEach(async ({ page, mockWalletConnection, setupMockContract }) => {
+ unverifiedUser = MOCK_USERS[2] // Charlie - Non-verified user
+ await mockWalletConnection(unverifiedUser)
+ await setupMockContract()
+ await page.goto('/')
+ })
+
+ test('should display chat interface with unverified user status', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Verify chat interface loads
+ await expect(page.locator('[data-testid="main-chat"]')).toBeVisible()
+
+ // Verify user status is shown as unverified
+ await expect(page.locator('[data-testid="user-status"]')).toContainText('Unverified')
+
+ // Verify no ENS name is displayed
+ await expect(page.locator('[data-testid="user-ens-name"]')).not.toBeVisible()
+
+ // Verify display name shows truncated address
+ await expect(page.locator('[data-testid="user-display-name"]')).toContainText('123456...7890')
+ })
+
+ test('should allow unverified user to send basic messages', async ({
+ page,
+ waitForChatLoad,
+ selectChat,
+ sendMessage,
+ verifyMessageDelivery
+ }) => {
+ await waitForChatLoad()
+
+ // Select a chat with a verified user
+ const targetUser = MOCK_USERS[0] // Alice (verified)
+ await selectChat(targetUser.address)
+
+ // Send a message
+ const messageContent = 'Hello Alice! I am a new user exploring the platform.'
+ await sendMessage(messageContent)
+
+ // Verify message delivery
+ await verifyMessageDelivery(messageContent)
+
+ // Verify the message does not show verification badge
+ const messageBubble = page.locator('[data-testid="message-bubble"]').filter({
+ hasText: messageContent
+ })
+ await expect(messageBubble).toBeVisible()
+ await expect(messageBubble.locator('[data-testid="verification-badge"]')).not.toBeVisible()
+ })
+
+ test('should limit features for unverified users in chat list', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Verify that unverified users don't show verification badges
+ const unverifiedChatItems = page.locator('[data-testid="chat-item"]').filter({
+ hasNot: page.locator('[data-testid="verification-badge"]')
+ })
+ await expect(unverifiedChatItems).toHaveCount(3) // Charlie + 2 others
+
+ // Verify that verified users still show verification badges
+ const verifiedChatItems = page.locator('[data-testid="chat-item"]').filter({
+ has: page.locator('[data-testid="verification-badge"]')
+ })
+ await expect(verifiedChatItems).toHaveCount(2) // Alice and Dave are verified
+ })
+
+ test('should show upgrade prompts for unverified users', async ({
+ page,
+ waitForChatLoad,
+ selectChat
+ }) => {
+ await waitForChatLoad()
+
+ await selectChat(MOCK_USERS[0].address) // Alice (verified user)
+
+ // Verify upgrade prompts are visible
+ await expect(page.locator('[data-testid="upgrade-prompt"]')).toBeVisible()
+ await expect(page.locator('[data-testid="upgrade-prompt"]')).toContainText('Verify your ENS to unlock premium features')
+ })
+
+ test('should restrict enhanced features for unverified users', async ({
+ page,
+ waitForChatLoad,
+ selectChat
+ }) => {
+ await waitForChatLoad()
+
+ await selectChat(MOCK_USERS[0].address) // Alice
+
+ // Verify enhanced features are disabled or restricted
+ await expect(page.locator('[data-testid="file-attachment-button"]')).toBeDisabled()
+ await expect(page.locator('[data-testid="voice-call-button"]')).toBeDisabled()
+ await expect(page.locator('[data-testid="video-call-button"]')).toBeDisabled()
+
+ // Verify tooltips show why features are disabled
+ await page.locator('[data-testid="file-attachment-button"]').hover()
+ await expect(page.locator('[data-testid="feature-disabled-tooltip"]')).toBeVisible()
+ await expect(page.locator('[data-testid="feature-disabled-tooltip"]')).toContainText('ENS verification required')
+ })
+
+ test('should show basic messaging functionality only', async ({
+ page,
+ waitForChatLoad,
+ selectChat
+ }) => {
+ await waitForChatLoad()
+
+ await selectChat(MOCK_USERS[1].address) // Bob (verified)
+
+ // Verify only basic message input is available
+ await expect(page.locator('[data-testid="message-input"]')).toBeVisible()
+ await expect(page.locator('[data-testid="send-button"]')).toBeVisible()
+
+ // Verify other input options are not available
+ await expect(page.locator('[data-testid="emoji-picker-button"]')).not.toBeVisible()
+ await expect(page.locator('[data-testid="sticker-button"]')).not.toBeVisible()
+ })
+
+ test('should handle search with limited results for unverified users', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Open search functionality
+ await page.locator('[data-testid="search-input"]').click()
+
+ // Search for users
+ await page.locator('[data-testid="search-input"]').fill('user')
+
+ // Verify search results don't show verification badges for current user
+ const searchResults = page.locator('[data-testid="search-results"] [data-testid="user-item"]')
+ const resultCount = await searchResults.count()
+
+ for (let i = 0; i < resultCount; i++) {
+ const result = searchResults.nth(i)
+ const isCurrentUser = await result.getAttribute('data-is-current-user')
+ if (isCurrentUser === 'true') {
+ await expect(result.locator('[data-testid="verification-badge"]')).not.toBeVisible()
+ }
+ }
+ })
+
+ test('should display appropriate messaging limits for unverified users', async ({
+ page,
+ waitForChatLoad,
+ selectChat
+ }) => {
+ await waitForChatLoad()
+
+ await selectChat(MOCK_USERS[0].address) // Alice
+
+ // Send multiple messages to test rate limiting
+ for (let i = 1; i <= 5; i++) {
+ await sendMessage(`Test message ${i} from unverified user`)
+
+ // After 3 messages, expect rate limiting warning
+ if (i === 3) {
+ await expect(page.locator('[data-testid="rate-limit-warning"]')).toBeVisible()
+ await expect(page.locator('[data-testid="rate-limit-warning"]')).toContainText('Verify ENS to remove message limits')
+ }
+ }
+ })
+
+ test('should show verification process in chat interface', async ({
+ page,
+ waitForChatLoad
+ }) => {
+ await waitForChatLoad()
+
+ // Verify verification prompt is visible in main interface
+ await expect(page.locator('[data-testid="verification-prompt-banner"]')).toBeVisible()
+ await expect(page.locator('[data-testid="verification-prompt-banner"]')).toContainText('Complete ENS verification to unlock all features')
+
+ // Verify verification button is available
+ await expect(page.locator('[data-testid="start-verification-button"]')).toBeVisible()
+ })
+
+ test('should maintain messaging capability but show limitations', async ({
+ page,
+ waitForChatLoad,
+ selectChat
+ }) => {
+ await waitForChatLoad()
+
+ await selectChat(MOCK_USERS[1].address) // Bob
+
+ // Send message and verify it works
+ const messageContent = 'Basic message still works for unverified users'
+ await sendMessage(messageContent)
+ await verifyMessageDelivery(messageContent)
+
+ // But verify limitations are clearly indicated
+ await expect(page.locator('[data-testid="feature-restriction-notice"]')).toBeVisible()
+ await expect(page.locator('[data-testid="feature-restriction-notice"]')).toContainText('Limited features - Verify ENS for full access')
+ })
+
+ test('should handle messaging with other unverified users', async ({
+ page,
+ waitForChatLoad,
+ selectChat
+ }) => {
+ await waitForChatLoad()
+
+ // Mock another unverified user
+ const otherUnverifiedUser = {
+ ...unverifiedUser,
+ address: '0x1111111111111111111111111111111111111111',
+ displayName: 'Another User'
+ }
+
+ // Create a new chat with another unverified user (simulated)
+ await page.locator('[data-testid="add-contact-button"]').click()
+ await page.locator('[data-testid="user-search-input"]').fill(otherUnverifiedUser.address)
+
+ // Verify no verification badges between unverified users
+ const userResult = page.locator('[data-testid="user-search-result"]').first()
+ await expect(userResult.locator('[data-testid="verification-badge"]')).not.toBeVisible()
+ })
+})
\ No newline at end of file
diff --git a/next-frontend/e2e/tests/security/chat-security.spec.ts b/next-frontend/e2e/tests/security/chat-security.spec.ts
new file mode 100644
index 0000000..83d2181
--- /dev/null
+++ b/next-frontend/e2e/tests/security/chat-security.spec.ts
@@ -0,0 +1,540 @@
+/**
+ * E2E Tests for Security Protections on Chat Routes
+ *
+ * These tests verify security measures including:
+ * - Route access control and authentication
+ * - Input validation and sanitization
+ * - CSRF protection
+ * - Message encryption and privacy
+ * - Rate limiting and abuse prevention
+ */
+
+import { test, expect } from '../../fixtures/chat-fixtures'
+import { MOCK_USERS } from '../../fixtures/chat-fixtures'
+
+test.describe('Chat Route Security Protections', () => {
+ test.beforeEach(async ({ page, mockWalletConnection, setupMockContract }) => {
+ const user = MOCK_USERS[0] // Alice - verified user
+ await mockWalletConnection(user)
+ await setupMockContract()
+ await page.goto('/')
+ })
+
+ test.describe('Route Access Control', () => {
+ test('should prevent unauthorized access to protected routes', async ({
+ page
+ }) => {
+ // Start with no wallet connection
+ await page.context().clearCookies()
+ await page.goto('/')
+
+ // Try to access protected chat route directly
+ await page.goto('/chat')
+
+ // Verify redirect to authentication
+ await expect(page).toHaveURL(/.*\/.*auth.*/)
+ await expect(page.locator('[data-testid="authentication-required"]')).toBeVisible()
+
+ // Try to access API routes directly
+ await page.goto('/api/chat/messages')
+
+ // Should return unauthorized or redirect
+ const response = await page.request.get('/api/chat/messages')
+ expect(response.status()).toBe(401)
+ })
+
+ test('should validate wallet ownership for route access', async ({
+ page,
+ mockWalletConnection
+ }) => {
+ const user = MOCK_USERS[0] // Alice
+
+ // Connect with user's wallet
+ await mockWalletConnection(user)
+ await page.goto('/')
+
+ // Try to access another user's private chat route
+ await page.goto(`/chat/private/${MOCK_USERS[1].address}`)
+
+ // Verify access is granted (should be able to chat with others)
+ await expect(page.locator('[data-testid="chat-header"]')).toBeVisible()
+
+ // Try to access invalid/inaccessible route
+ await page.goto('/chat/private/invalid-address')
+
+ // Should handle invalid route gracefully
+ await expect(page.locator('[data-testid="invalid-route-error"]')).toBeVisible()
+ })
+
+ test('should enforce authentication on all chat operations', async ({
+ page,
+ mockWalletConnection
+ }) => {
+ // Clear authentication
+ await page.context().clearCookies()
+ await page.goto('/')
+
+ // Try to perform chat operations without authentication
+ await page.locator('[data-testid="message-input"]').fill('Test message')
+
+ // Should not allow sending without auth
+ await page.locator('[data-testid="send-button"]').click()
+
+ // Verify authentication requirement
+ await expect(page.locator('[data-testid="auth-required-modal"]')).toBeVisible()
+ await expect(page.locator('[data-testid="auth-required-modal"]')).toContainText('Please connect your wallet')
+ })
+
+ test('should validate session tokens on protected routes', async ({
+ page,
+ mockWalletConnection
+ }) => {
+ const user = MOCK_USERS[0]
+
+ await mockWalletConnection(user)
+ await page.goto('/')
+
+ // Obtain valid session token
+ const sessionToken = await page.evaluate(() => localStorage.getItem('sessionToken'))
+
+ // Manually invalidate session
+ await page.evaluate(() => {
+ localStorage.setItem('sessionToken', 'invalid-token')
+ })
+
+ // Try to access protected API
+ const response = await page.request.get('/api/chat/send', {
+ headers: {
+ 'Authorization': 'Bearer invalid-token'
+ }
+ })
+
+ // Should reject invalid token
+ expect(response.status()).toBe(401)
+
+ // Verify UI shows session expired
+ await expect(page.locator('[data-testid="session-expired-message"]')).toBeVisible()
+ })
+ })
+
+ test.describe('Input Validation and Sanitization', () => {
+ test('should prevent XSS in message content', async ({
+ page,
+ waitForChatLoad,
+ selectChat
+ }) => {
+ await waitForChatLoad()
+
+ await selectChat(MOCK_USERS[1].address) // Bob
+
+ // Attempt XSS injection
+ const xssPayload = ''
+ await page.locator('[data-testid="message-input"]').fill(xssPayload)
+ await page.locator('[data-testid="send-button"]').click()
+
+ // Verify message is sanitized
+ const messageContent = await page.locator('[data-testid="message-bubble"]').textContent()
+ expect(messageContent).not.toContain('Test Group'
+ await page.locator('[data-testid="group-name-input"]').fill(maliciousName)
+
+ // Verify sanitization
+ const sanitizedName = await page.locator('[data-testid="group-name-input"]').inputValue()
+ expect(sanitizedName).not.toContain('