xpeditis2.0/apps/frontend/e2e/booking-workflow.spec.ts
2025-11-04 07:30:15 +01:00

261 lines
9.5 KiB
TypeScript

/**
* 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)/);
});
});