/** * E2E Test - Complete Booking Workflow * * Tests the complete booking flow from rate search to booking confirmation */ import { test, expect } from '@playwright/test'; // Test configuration const BASE_URL = process.env.BASE_URL || 'http://localhost:3000'; const API_URL = process.env.API_URL || 'http://localhost:4000/api/v1'; // Test user credentials (should be set via environment variables) const TEST_USER = { email: process.env.TEST_USER_EMAIL || 'test@example.com', password: process.env.TEST_USER_PASSWORD || 'TestPassword123!', }; test.describe('Complete Booking Workflow', () => { test.beforeEach(async ({ page }) => { // Navigate to homepage await page.goto(BASE_URL); }); test('should complete full booking flow', async ({ page }) => { // Step 1: Login await test.step('User Login', async () => { await page.click('text=Login'); await page.fill('input[name="email"]', TEST_USER.email); await page.fill('input[name="password"]', TEST_USER.password); await page.click('button[type="submit"]'); // Wait for redirect to dashboard await page.waitForURL('**/dashboard'); await expect(page).toHaveURL(/.*dashboard/); }); // Step 2: Navigate to Rate Search await test.step('Navigate to Rate Search', async () => { await page.click('text=Search Rates'); await expect(page).toHaveURL(/.*rates\/search/); }); // Step 3: Search for Rates await test.step('Search for Shipping Rates', async () => { // Fill search form await page.fill('input[name="origin"]', 'Rotterdam'); await page.waitForTimeout(500); // Wait for autocomplete await page.keyboard.press('ArrowDown'); await page.keyboard.press('Enter'); await page.fill('input[name="destination"]', 'Shanghai'); await page.waitForTimeout(500); await page.keyboard.press('ArrowDown'); await page.keyboard.press('Enter'); // Select departure date (2 weeks from now) const departureDate = new Date(); departureDate.setDate(departureDate.getDate() + 14); await page.fill('input[name="departureDate"]', departureDate.toISOString().split('T')[0]); // Select container type await page.selectOption('select[name="containerType"]', '40HC'); await page.fill('input[name="quantity"]', '1'); // Submit search await page.click('button[type="submit"]'); // Wait for results await page.waitForSelector('.rate-results', { timeout: 10000 }); // Verify results are displayed const resultsCount = await page.locator('.rate-card').count(); expect(resultsCount).toBeGreaterThan(0); }); // Step 4: Select a Rate and Create Booking await test.step('Select Rate and Create Booking', async () => { // Select first available rate await page.locator('.rate-card').first().click('button:has-text("Book")'); // Should navigate to booking form await expect(page).toHaveURL(/.*bookings\/create/); // Fill booking details await page.fill('input[name="shipperName"]', 'Test Shipper Inc.'); await page.fill('input[name="shipperAddress"]', '123 Test St'); await page.fill('input[name="shipperCity"]', 'Rotterdam'); await page.fill('input[name="shipperCountry"]', 'Netherlands'); await page.fill('input[name="shipperEmail"]', 'shipper@test.com'); await page.fill('input[name="shipperPhone"]', '+31612345678'); await page.fill('input[name="consigneeName"]', 'Test Consignee Ltd.'); await page.fill('input[name="consigneeAddress"]', '456 Dest Ave'); await page.fill('input[name="consigneeCity"]', 'Shanghai'); await page.fill('input[name="consigneeCountry"]', 'China'); await page.fill('input[name="consigneeEmail"]', 'consignee@test.com'); await page.fill('input[name="consigneePhone"]', '+8613812345678'); // Container details await page.fill('input[name="cargoDescription"]', 'Test Cargo - Electronics'); await page.fill('input[name="cargoWeight"]', '15000'); // kg await page.fill('input[name="cargoValue"]', '50000'); // USD // Submit booking await page.click('button:has-text("Create Booking")'); // Wait for confirmation await page.waitForSelector('.booking-confirmation', { timeout: 10000 }); }); // Step 5: Verify Booking in Dashboard await test.step('Verify Booking in Dashboard', async () => { // Navigate to dashboard await page.click('text=Dashboard'); await expect(page).toHaveURL(/.*dashboard/); // Verify new booking appears in list await page.waitForSelector('.bookings-table'); // Check that first row contains the booking const firstBooking = page.locator('.booking-row').first(); await expect(firstBooking).toBeVisible(); // Verify booking number format (WCM-YYYY-XXXXXX) const bookingNumber = await firstBooking.locator('.booking-number').textContent(); expect(bookingNumber).toMatch(/WCM-\d{4}-[A-Z0-9]{6}/); }); // Step 6: View Booking Details await test.step('View Booking Details', async () => { // Click on booking to view details await page.locator('.booking-row').first().click(); // Should navigate to booking details page await expect(page).toHaveURL(/.*bookings\/[a-f0-9-]+/); // Verify all details are displayed await expect(page.locator('text=Test Shipper Inc.')).toBeVisible(); await expect(page.locator('text=Test Consignee Ltd.')).toBeVisible(); await expect(page.locator('text=Rotterdam')).toBeVisible(); await expect(page.locator('text=Shanghai')).toBeVisible(); }); }); test('should handle rate search errors gracefully', async ({ page }) => { await test.step('Login', async () => { await page.goto(`${BASE_URL}/login`); await page.fill('input[name="email"]', TEST_USER.email); await page.fill('input[name="password"]', TEST_USER.password); await page.click('button[type="submit"]'); await page.waitForURL('**/dashboard'); }); await test.step('Test Invalid Search', async () => { await page.goto(`${BASE_URL}/rates/search`); // Try to search without filling required fields await page.click('button[type="submit"]'); // Should show validation errors await expect(page.locator('.error-message')).toBeVisible(); }); }); test('should filter bookings in dashboard', async ({ page }) => { await test.step('Login and Navigate to Dashboard', async () => { await page.goto(`${BASE_URL}/login`); await page.fill('input[name="email"]', TEST_USER.email); await page.fill('input[name="password"]', TEST_USER.password); await page.click('button[type="submit"]'); await page.waitForURL('**/dashboard'); }); await test.step('Apply Filters', async () => { // Open filter panel await page.click('button:has-text("Filters")'); // Filter by status await page.check('input[value="confirmed"]'); // Apply filters await page.click('button:has-text("Apply")'); // Wait for filtered results await page.waitForTimeout(1000); // Verify all visible bookings have confirmed status const bookings = page.locator('.booking-row'); const count = await bookings.count(); for (let i = 0; i < count; i++) { const status = await bookings.nth(i).locator('.status-badge').textContent(); expect(status?.toLowerCase()).toContain('confirmed'); } }); }); test('should export bookings', async ({ page }) => { await test.step('Login and Navigate to Dashboard', async () => { await page.goto(`${BASE_URL}/login`); await page.fill('input[name="email"]', TEST_USER.email); await page.fill('input[name="password"]', TEST_USER.password); await page.click('button[type="submit"]'); await page.waitForURL('**/dashboard'); }); await test.step('Export Bookings', async () => { // Wait for download event const downloadPromise = page.waitForEvent('download'); // Click export button await page.click('button:has-text("Export")'); await page.click('text=CSV'); // Wait for download const download = await downloadPromise; // Verify filename expect(download.suggestedFilename()).toMatch(/bookings.*\.csv/); }); }); }); test.describe('Authentication', () => { test('should prevent access to protected pages', async ({ page }) => { // Try to access dashboard without logging in await page.goto(`${BASE_URL}/dashboard`); // Should redirect to login await expect(page).toHaveURL(/.*login/); }); test('should show error for invalid credentials', async ({ page }) => { await page.goto(`${BASE_URL}/login`); await page.fill('input[name="email"]', 'wrong@example.com'); await page.fill('input[name="password"]', 'wrongpassword'); await page.click('button[type="submit"]'); // Should show error message await expect(page.locator('.error-message')).toBeVisible(); await expect(page.locator('text=Invalid credentials')).toBeVisible(); }); test('should logout successfully', async ({ page }) => { // Login first await page.goto(`${BASE_URL}/login`); await page.fill('input[name="email"]', TEST_USER.email); await page.fill('input[name="password"]', TEST_USER.password); await page.click('button[type="submit"]'); await page.waitForURL('**/dashboard'); // Logout await page.click('button:has-text("Logout")'); // Should redirect to home/login await expect(page).toHaveURL(/.*(\/$|\/login)/); }); });