import { test, expect } from '@playwright/test'; test.describe('Tritri Frontend Tests', () => { test.beforeEach(async ({ page }) => { // Clear cookies before each test await page.context().clearCookies(); }); test('Home page loads correctly', async ({ page }) => { // Use create=true to prevent redirect to dashboard await page.goto('http://localhost:3002?create=true'); // Check if the page loads with the right title await expect(page).toHaveTitle(/Tritri/); // Check for main elements await expect(page.locator('h1')).toContainText('Tritri'); await expect(page.locator('text=Split expenses fairly with your group')).toBeVisible(); // Check for create trip form await expect(page.locator('label:has-text("Trip Name")')).toBeVisible(); await expect(page.locator('label:has-text("Your Name")')).toBeVisible(); await expect(page.locator('label:has-text("Default Currency")')).toBeVisible(); // Check for join trip section - using actual text from the UI await expect(page.locator('text=Join Existing Trip')).toBeVisible(); await expect(page.locator('text=Join Trip with Code')).toBeVisible(); }); test('Create a new trip', async ({ page }) => { // Use create=true to prevent redirect to dashboard await page.goto('http://localhost:3002?create=true'); // Fill out the trip creation form await page.fill('input#trip-name', 'Weekend in Paris'); await page.fill('input#creator-name', 'John Doe'); await page.selectOption('select#currency', 'USD'); // Submit the form await page.click('button:has-text("Create Trip")'); // Wait for success state await expect(page.locator('text=Trip Created!')).toBeVisible(); await expect(page.locator('text=Your trip "Weekend in Paris" is ready')).toBeVisible(); // Check for share code section await expect(page.locator('text=Share this code with your group:')).toBeVisible(); // Look for the share code display (it's in a div with gray background) await expect(page.locator('.bg-gray-100 .text-2xl')).toBeVisible(); // Check navigation buttons await expect(page.locator('button:has-text("Go to Trip Dashboard")')).toBeVisible(); await expect(page.locator('button:has-text("Copy Share Code")')).toBeVisible(); }); test('Join an existing trip with valid share code', async ({ page }) => { // First, create a trip to get a share code await page.goto('http://localhost:3002?create=true'); await page.fill('input#trip-name', 'Join Test Trip'); await page.fill('input#creator-name', 'Alice'); await page.selectOption('select#currency', 'EUR'); await page.click('button:has-text("Create Trip")'); // Wait for trip creation success await expect(page.locator('text=Trip Created!')).toBeVisible(); // Extract the share code from the success page // The share code is displayed in a gray box with large text const shareCodeElement = page.locator('.bg-gray-100 .text-2xl, [class*="bg-gray"] .text-2xl').first(); await expect(shareCodeElement).toBeVisible({ timeout: 5000 }); const shareCode = await shareCodeElement.textContent(); expect(shareCode).toBeTruthy(); expect(shareCode?.trim()).toHaveLength(6); // Clear localStorage to simulate a different user joining await page.evaluate(() => { localStorage.clear(); }); // Navigate to join page await page.goto('http://localhost:3002/join'); // Verify join page loads await expect(page.locator('h1:has-text("Join Trip")')).toBeVisible(); await expect(page.locator('text=Enter the 6-character code to join your group')).toBeVisible(); await expect(page.locator('input#share-code')).toBeVisible(); // Enter the share code await page.fill('input#share-code', shareCode?.trim() || ''); await page.click('button:has-text("Find Trip")'); // Wait for trip to be found and participant selection to appear await expect(page.locator('text=Welcome to')).toBeVisible({ timeout: 10000 }); await expect(page.locator('text=Select your name to join this trip')).toBeVisible(); // Verify the trip name is displayed await expect(page.locator(`text=Join Test Trip`)).toBeVisible(); // Verify participants are shown await expect(page.locator('text=Who are you?')).toBeVisible(); await expect(page.locator('text=Alice')).toBeVisible(); // Select the participant (Alice, the creator) await page.click('button:has-text("Alice")'); // Verify the participant is selected (button should have selected styling) const selectedButton = page.locator('button:has-text("Alice")').first(); await expect(selectedButton).toHaveClass(/border-blue-500|bg-blue-50/); // Click join trip button await page.click('button:has-text("Join Trip")'); // Wait for navigation to trip page await page.waitForURL(/\/trip\/\d+/, { timeout: 10000 }); // Verify we're on the trip page await expect(page.locator('text=Join Test Trip')).toBeVisible(); await expect(page.locator('text=Welcome back,')).toBeVisible(); await expect(page.locator('text=Alice')).toBeVisible(); }); test('Join trip with invalid share code', async ({ page }) => { await page.goto('http://localhost:3002/join'); // Try with invalid code (too short) await page.fill('input#share-code', 'ABC'); await page.click('button:has-text("Find Trip")'); // Button should be disabled or show error // The button should be disabled for codes less than 6 characters const findButton = page.locator('button:has-text("Find Trip")'); await expect(findButton).toBeDisabled(); // Try with invalid code (wrong format) await page.fill('input#share-code', 'INVALID'); await page.click('button:has-text("Find Trip")'); // Wait for error message await expect(page.locator('text=Trip not found')).toBeVisible({ timeout: 10000 }); }); test('Join trip with URL parameter code', async ({ page }) => { // First, create a trip to get a share code await page.goto('http://localhost:3002?create=true'); await page.fill('input#trip-name', 'URL Join Test'); await page.fill('input#creator-name', 'Bob'); await page.selectOption('select#currency', 'USD'); await page.click('button:has-text("Create Trip")'); // Wait for trip creation await expect(page.locator('text=Trip Created!')).toBeVisible(); // Extract share code const shareCodeElement = page.locator('.bg-gray-100 .text-2xl, [class*="bg-gray"] .text-2xl').first(); await expect(shareCodeElement).toBeVisible({ timeout: 5000 }); const shareCode = await shareCodeElement.textContent(); expect(shareCode?.trim()).toHaveLength(6); // Clear localStorage await page.evaluate(() => { localStorage.clear(); }); // Navigate to join page with code in URL await page.goto(`http://localhost:3002/join?code=${shareCode?.trim()}`); // The code should be auto-filled and trip should be found automatically await expect(page.locator('text=Welcome to')).toBeVisible({ timeout: 10000 }); await expect(page.locator('text=URL Join Test')).toBeVisible(); await expect(page.locator('text=Bob')).toBeVisible(); // Select participant and join await page.click('button:has-text("Bob")'); await page.click('button:has-text("Join Trip")'); // Verify navigation to trip page await page.waitForURL(/\/trip\/\d+/, { timeout: 10000 }); await expect(page.locator('text=URL Join Test')).toBeVisible(); }); test('Join trip - back button functionality', async ({ page }) => { // Create a trip first await page.goto('http://localhost:3002?create=true'); await page.fill('input#trip-name', 'Back Button Test'); await page.fill('input#creator-name', 'Charlie'); await page.click('button:has-text("Create Trip")'); await expect(page.locator('text=Trip Created!')).toBeVisible(); const shareCodeElement = page.locator('.bg-gray-100 .text-2xl, [class*="bg-gray"] .text-2xl').first(); await expect(shareCodeElement).toBeVisible({ timeout: 5000 }); const shareCode = await shareCodeElement.textContent(); // Clear localStorage await page.evaluate(() => { localStorage.clear(); }); // Navigate to join page await page.goto('http://localhost:3002/join'); await page.fill('input#share-code', shareCode?.trim() || ''); await page.click('button:has-text("Find Trip")'); // Wait for trip to be found await expect(page.locator('text=Welcome to')).toBeVisible({ timeout: 10000 }); // Click back button await page.click('button:has-text("Back")'); // Should return to the join form await expect(page.locator('h1:has-text("Join Trip")')).toBeVisible(); await expect(page.locator('input#share-code')).toBeVisible(); // Share code should be cleared const inputValue = await page.locator('input#share-code').inputValue(); expect(inputValue).toBe(''); }); test('Multi-trip identity management', async ({ page }) => { // Create first trip await page.goto('http://localhost:3002'); await page.fill('input#trip-name', 'Trip 1 - Paris'); await page.fill('input#creator-name', 'Multi User'); await page.click('button:has-text("Create Trip")'); // Go to dashboard await page.click('button:has-text("Go to Trip Dashboard")'); // Should redirect to trip page, then navigate to dashboard await page.waitForTimeout(1000); // Navigate back to home to create second trip await page.goto('http://localhost:3002'); // Since user has existing trips, should redirect to dashboard // Use heading selector to avoid strict mode violation await expect(page.locator('h1:has-text("My Trips")')).toBeVisible(); // Check that the first trip is in the dashboard await expect(page.locator('text=Trip 1 - Paris')).toBeVisible(); // Clear localStorage and create second trip by using create=true parameter await page.goto('http://localhost:3002?create=true'); await page.fill('input#trip-name', 'Trip 2 - Tokyo'); await page.fill('input#creator-name', 'Multi User'); await page.click('button:has-text("Create Trip")'); // Go to dashboard await page.click('button:has-text("Go to Trip Dashboard")'); await page.waitForTimeout(1000); // Now check localStorage for multiple trips const tripsData = await page.evaluate(() => { const trips = []; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key && key.startsWith('tritri_trip_')) { const tripData = localStorage.getItem(key); if (tripData) { trips.push(JSON.parse(tripData)); } } } return trips; }); // Should have trip data stored expect(tripsData.length).toBeGreaterThan(0); // Check if trips have the required structure tripsData.forEach(trip => { expect(trip).toHaveProperty('tripId'); expect(trip).toHaveProperty('tripName'); expect(trip).toHaveProperty('participantName'); expect(trip).toHaveProperty('shareCode'); }); }); test('Trip navigation and switching', async ({ page }) => { // Create and access a trip await page.goto('http://localhost:3002'); await page.fill('input#trip-name', 'Navigation Test Trip'); await page.fill('input#creator-name', 'Test User'); await page.click('button:has-text("Create Trip")'); await page.click('button:has-text("Go to Trip Dashboard")'); // Should be on trip page await expect(page.locator('text=Navigation Test Trip')).toBeVisible(); // Test bottom navigation await expect(page.locator('text=My Trips')).toBeVisible(); // Use specific selector for Add button to avoid strict mode violation await expect(page.locator('span:has-text("Add")')).toBeVisible(); await expect(page.locator('text=People')).toBeVisible(); // Click on "My Trips" to go to dashboard await page.click('text=My Trips'); // Should be on dashboard page await expect(page.locator('text=My Trips')).toBeVisible(); await expect(page.locator('text=Navigation Test Trip')).toBeVisible(); // Click on the trip to go back await page.click('text=Navigation Test Trip'); // Should be back on trip page await expect(page.locator('text=Navigation Test Trip')).toBeVisible(); // Test the hamburger menu button await page.click('button[title="View all trips"]'); await expect(page.locator('text=My Trips')).toBeVisible(); }); test('Add participant functionality', async ({ page }) => { // Create a trip await page.goto('http://localhost:3002'); await page.fill('input#trip-name', 'Participant Test Trip'); await page.fill('input#creator-name', 'Creator'); await page.click('button:has-text("Create Trip")'); await page.click('button:has-text("Go to Trip Dashboard")'); // Click on "Add Person" button await page.click('text=People'); // Should prompt for participant name await expect(page.locator('text=Enter participant name:')).toBeVisible(); // Handle the prompt (this might need to be adjusted based on actual implementation) page.on('dialog', async dialog => { await dialog.accept('New Participant'); }); // Wait for the participant to be added await page.waitForTimeout(1000); // Check if participant was added (this would need verification in the UI) // This is a placeholder - actual implementation would check the participant list }); test('Leave trip functionality', async ({ page }) => { // Create a trip await page.goto('http://localhost:3002'); await page.fill('input#trip-name', 'Leave Test Trip'); await page.fill('input#creator-name', 'Test User'); await page.click('button:has-text("Create Trip")'); await page.click('button:has-text("Go to Trip Dashboard")'); // Click on leave trip button page.on('dialog', async dialog => { await dialog.accept(); }); await page.click('button[title="Leave trip"]'); // Should redirect to dashboard await expect(page.locator('text=My Trips')).toBeVisible(); // The trip should no longer be accessible (would need verification) }); test('Responsive design', async ({ page }) => { // Test mobile viewport await page.setViewportSize({ width: 375, height: 667 }); // iPhone SE await page.goto('http://localhost:3002'); // Check mobile layout await expect(page.locator('h1')).toBeVisible(); await expect(page.locator('text=Split expenses fairly with your group')).toBeVisible(); // Test tablet viewport await page.setViewportSize({ width: 768, height: 1024 }); // iPad await expect(page.locator('h1')).toBeVisible(); // Test desktop viewport await page.setViewportSize({ width: 1200, height: 800 }); await expect(page.locator('h1')).toBeVisible(); }); }); // End-to-end test for complete user journey test.describe('Complete User Journey', () => { test('New user creates trip and manages multiple trips', async ({ page }) => { // Start as new user await page.goto('http://localhost:3002'); await expect(page.locator('h1')).toContainText('Tritri'); // Create first trip await page.fill('input#trip-name', 'Family Vacation'); await page.fill('input#creator-name', 'Dad'); await page.selectOption('select#currency', 'USD'); await page.click('button:has-text("Create Trip")'); // Verify trip creation await expect(page.locator('text=Trip Created!')).toBeVisible(); const shareCode = await page.locator('[data-testid="share-code"]').textContent(); expect(shareCode).toHaveLength(6); // Navigate to trip await page.click('button:has-text("Go to Trip Dashboard")'); await expect(page.locator('text=Family Vacation')).toBeVisible(); // Verify user welcome await expect(page.locator('text=Welcome back,')).toBeVisible(); await expect(page.locator('text=Dad')).toBeVisible(); // Test navigation to dashboard await page.click('text=My Trips'); // Create second trip await page.click('text=Create Trip'); await page.fill('input#trip-name', 'Business Trip'); await page.fill('input#creator-name', 'Dad'); await page.click('button:has-text("Create Trip")'); await page.click('button:has-text("Go to Trip Dashboard")'); // Navigate between trips await page.click('text=My Trips'); await expect(page.locator('text=Family Vacation')).toBeVisible(); await expect(page.locator('text=Business Trip')).toBeVisible(); // Switch to first trip await page.click('text=Family Vacation'); await expect(page.locator('text=Family Vacation')).toBeVisible(); // Switch to second trip await page.click('text=My Trips'); await page.click('text=Business Trip'); await expect(page.locator('text=Business Trip')).toBeVisible(); console.log('✅ Complete user journey test passed'); }); });