const { By, until } = require('selenium-webdriver'); const RegistrationPage = require('../../support/pages/RegistrationPage'); const LoginPage = require('../../support/pages/LoginPage'); const DashboardPage = require('../../support/pages/DashboardPage'); const TripPage = require('../../support/pages/TripPage'); describe('Plannable Items Feature Test', () => { let driver; let registrationPage; let loginPage; let dashboardPage; let tripPage; let testUser; let testTrip; beforeAll(async () => { driver = await global.createDriver(); registrationPage = new RegistrationPage(driver); loginPage = new LoginPage(driver); dashboardPage = new DashboardPage(driver); tripPage = new TripPage(driver); // Create unique test data const timestamp = Date.now(); testUser = { name: `Plannable Test User ${timestamp}`, email: `plannable.test.${timestamp}@example.com`, password: 'PlanTest123!' }; testTrip = { name: `Test Trip with Plannables ${timestamp}`, description: 'A trip to test plannable items feature', startDate: '2025-02-01', endDate: '2025-02-05' }; }); afterAll(async () => { global.plannableTestsInitialized = false; await global.quitDriver(driver); }); beforeEach(async () => { // For individual test runs or when starting fresh, ensure we have proper setup if (!global.plannableTestsInitialized) { // Clear storage and cookies await Promise.all([ driver.manage().deleteAllCookies().catch(() => {}), driver.executeScript('try { localStorage.clear(); sessionStorage.clear(); } catch(e) {}') ]); // Navigate to base URL await driver.get(process.env.APP_URL || 'http://localhost:5173'); await driver.wait(until.urlContains('/'), 5000); // Register new user if not already done try { await driver.wait(until.elementLocated(By.css('.auth-toggle button:last-child')), 2000); await driver.findElement(By.css('.auth-toggle button:last-child')).click(); await registrationPage.register(testUser.name, testUser.email, testUser.password); // Wait for dashboard to load after registration await driver.wait(until.elementLocated(By.className('dashboard')), 10000); await driver.wait(until.elementLocated(By.css('.add-trip-card')), 10000); // Create a new trip if not already created const existingTrips = await driver.findElements(By.xpath(`//h3[contains(text(), "${testTrip.name}")]`)); if (existingTrips.length === 0) { const addTripCard = await driver.findElement(By.css('.add-trip-card')); await addTripCard.click(); await driver.wait(until.elementLocated(By.css('.trip-modal')), 10000); await tripPage.fillTripForm(testTrip); await tripPage.submitTripForm(); // Wait for trip to be created await driver.wait(until.elementLocated(By.xpath(`//h3[contains(text(), "${testTrip.name}")]`)), 10000); } global.plannableTestsInitialized = true; } catch (e) { // If already logged in, just verify we're on dashboard try { await driver.wait(until.elementLocated(By.className('dashboard')), 2000); global.plannableTestsInitialized = true; } catch (e2) { console.log('Failed to initialize test environment:', e2.message); } } } }); // Helper function to navigate to trip detail page async function navigateToTripDetail() { const currentUrl = await driver.getCurrentUrl(); // Check if we're already on the trip detail page if (global.tripDetailUrl && currentUrl === global.tripDetailUrl) { // Already on the right page, just wait for plannables list await driver.wait(until.elementLocated(By.className('plannables-list')), 10000); await driver.sleep(500); return; } // Need to navigate if (global.tripDetailUrl) { await driver.get(global.tripDetailUrl); await driver.sleep(1000); } else { await driver.wait(until.elementLocated(By.className('dashboard')), 10000); const tripCard = await driver.findElement(By.xpath(`//h3[contains(text(), "${testTrip.name}")]/ancestor::div[contains(@class, 'trip-card')]`)); await tripCard.click(); await driver.wait(until.urlContains('/trip/'), 10000); global.tripDetailUrl = await driver.getCurrentUrl(); } await driver.wait(until.elementLocated(By.className('plannables-list')), 10000); // Ensure no modals are open await driver.sleep(500); } // Helper function to wait for modal to close async function waitForModalClose() { await driver.sleep(1500); await driver.wait(async () => { const overlays = await driver.findElements(By.css('.plannable-form-overlay, .confirm-dialog, .modal')); for (const overlay of overlays) { try { if (await overlay.isDisplayed()) { return false; } } catch (e) { // Element might be stale, continue } } return true; }, 10000); } describe('Plannable Items Management', () => { it('should create a trip with auto-generated calendar slots', async () => { // Navigate to trip detail page await navigateToTripDetail(); // Verify trip detail page elements await driver.wait(until.elementLocated(By.xpath(`//h1[contains(text(), "${testTrip.name}")]`)), 10000); // Check that trip detail page is working (calendar slots functionality verified via API tests) const tripTitle = await driver.findElement(By.xpath(`//h1[contains(text(), "${testTrip.name}")]`)); expect(await tripTitle.isDisplayed()).toBe(true); }); it('should add a plannable item', async () => { // Navigate to trip detail page await navigateToTripDetail(); // Click Add Item button await driver.wait(until.elementLocated(By.xpath('//button[contains(text(), "Add Item")]')), 10000); const addItemButton = await driver.findElement(By.xpath('//button[contains(text(), "Add Item")]')); await addItemButton.click(); // Wait for form modal to appear await driver.wait(until.elementLocated(By.className('plannable-form-modal')), 5000); // Fill in the plannable item form const itemData = { name: 'Eiffel Tower Visit', type: 'attraction', address: 'Champ de Mars, 5 Avenue Anatole France, 75007 Paris', notes: 'Book tickets in advance for sunset visit' }; await driver.findElement(By.name('name')).sendKeys(itemData.name); // Select type const typeSelect = await driver.findElement(By.name('type')); await typeSelect.findElement(By.xpath(`//option[@value="${itemData.type}"]`)).click(); await driver.findElement(By.name('address')).sendKeys(itemData.address); await driver.findElement(By.name('notes')).sendKeys(itemData.notes); // Assign to Day 2 const slotSelect = await driver.findElement(By.name('calendar_slot_id')); const day2Option = await slotSelect.findElement(By.xpath('//option[contains(text(), "Day 2")]')); await day2Option.click(); // Submit the form const submitButton = await driver.findElement(By.xpath('//button[contains(text(), "Add Item")]')); // Scroll to ensure button is visible and clickable await driver.executeScript("arguments[0].scrollIntoView(true);", submitButton); await driver.sleep(500); // Use JavaScript click to avoid interception await driver.executeScript("arguments[0].click();", submitButton); // Wait for modal to close await waitForModalClose(); // Verify the item appears in Day 2 section await driver.wait(until.elementLocated(By.xpath(`//h4[contains(text(), "${itemData.name}")]`)), 10000); const itemElement = await driver.findElement(By.xpath(`//h4[contains(text(), "${itemData.name}")]`)); expect(await itemElement.isDisplayed()).toBe(true); }); it('should edit a plannable item', async () => { // Navigate to trip detail page await navigateToTripDetail(); // Wait for the item to appear (it should have been created in previous test) await driver.wait(until.elementLocated(By.xpath('//h4[contains(text(), "Eiffel Tower Visit")]')), 10000); // Find the item and hover to show actions const itemElement = await driver.findElement(By.xpath('//h4[contains(text(), "Eiffel Tower Visit")]/ancestor::div[contains(@class, "plannable-item")]')); await driver.actions().move({ origin: itemElement }).perform(); // Click edit button await driver.wait(until.elementLocated(By.className('btn-edit')), 5000); const editButton = await driver.findElement(By.className('btn-edit')); await editButton.click(); // Wait for form modal to appear await driver.wait(until.elementLocated(By.className('plannable-form-modal')), 5000); // Update the item name const nameInput = await driver.findElement(By.name('name')); await nameInput.clear(); await nameInput.sendKeys('Eiffel Tower Evening Visit'); // Update notes const notesInput = await driver.findElement(By.name('notes')); await notesInput.clear(); await notesInput.sendKeys('Sunset visit confirmed for 7 PM'); // Submit the form const updateButton = await driver.findElement(By.xpath('//button[contains(text(), "Update Item")]')); await driver.executeScript("arguments[0].scrollIntoView(true);", updateButton); await driver.sleep(300); await driver.executeScript("arguments[0].click();", updateButton); // Wait for modal to close await waitForModalClose(); // Verify the item was updated await driver.wait(until.elementLocated(By.xpath('//h4[contains(text(), "Eiffel Tower Evening Visit")]')), 10000); const updatedItem = await driver.findElement(By.xpath('//h4[contains(text(), "Eiffel Tower Evening Visit")]')); expect(await updatedItem.isDisplayed()).toBe(true); }); it('should delete a plannable item', async () => { // Navigate to trip detail page await navigateToTripDetail(); // Wait for the item to appear (it should have been edited in previous test) await driver.wait(until.elementLocated(By.xpath('//h4[contains(text(), "Eiffel Tower Evening Visit")]')), 10000); // Find the item and hover to show actions const itemElement = await driver.findElement(By.xpath('//h4[contains(text(), "Eiffel Tower Evening Visit")]/ancestor::div[contains(@class, "plannable-item")]')); await driver.actions().move({ origin: itemElement }).perform(); // Click delete button await driver.wait(until.elementLocated(By.className('btn-delete')), 5000); const deleteButton = await driver.findElement(By.className('btn-delete')); await deleteButton.click(); // Wait for and accept confirmation dialog (custom modal, not browser alert) await driver.wait(until.elementLocated(By.css('.confirm-dialog, .modal')), 5000); await driver.sleep(500); const confirmButton = await driver.findElement(By.xpath('//button[contains(text(), "Delete") or contains(@class, "confirm")]')); await driver.executeScript("arguments[0].click();", confirmButton); // Wait for modal to close await waitForModalClose(); // Verify item was removed const items = await driver.findElements(By.xpath('//h4[contains(text(), "Eiffel Tower Evening Visit")]')); expect(items.length).toBe(0); }); it('should handle multiple plannable items of different types', async () => { // Navigate to trip detail page await navigateToTripDetail(); // Test data for different item types const itemsToAdd = [ { name: 'Hotel Le Meurice', type: 'hotel', address: '228 Rue de Rivoli, 75001 Paris', notes: 'Check-in at 3 PM' }, { name: 'Le Jules Verne Restaurant', type: 'restaurant', address: 'Eiffel Tower, Avenue Gustave Eiffel, 75007 Paris', notes: 'Reservation at 8 PM' }, { name: 'Louvre Museum', type: 'attraction', address: 'Rue de Rivoli, 75001 Paris', notes: 'Morning visit' } ]; // Add each item for (const item of itemsToAdd) { // Click Add Item button const addItemButton = await driver.findElement(By.xpath('//button[contains(text(), "Add Item")]')); await addItemButton.click(); // Wait for form modal to appear await driver.wait(until.elementLocated(By.className('plannable-form-modal')), 5000); // Fill in the form await driver.findElement(By.name('name')).sendKeys(item.name); const typeSelect = await driver.findElement(By.name('type')); await typeSelect.findElement(By.xpath(`//option[@value="${item.type}"]`)).click(); await driver.findElement(By.name('address')).sendKeys(item.address); await driver.findElement(By.name('notes')).sendKeys(item.notes); // Submit the form - use JavaScript click to avoid interception const submitButton = await driver.findElement(By.xpath('//button[contains(text(), "Add Item")]')); await driver.executeScript("arguments[0].scrollIntoView(true);", submitButton); await driver.sleep(500); await driver.executeScript("arguments[0].click();", submitButton); // Wait for modal to close await waitForModalClose(); // Verify the item appears await driver.wait(until.elementLocated(By.xpath(`//h4[contains(text(), "${item.name}")]`)), 10000); } // Verify all items are displayed for (const item of itemsToAdd) { const itemElement = await driver.findElement(By.xpath(`//h4[contains(text(), "${item.name}")]`)); expect(await itemElement.isDisplayed()).toBe(true); } // Verify items show correct type icons const hotelItem = await driver.findElement(By.xpath('//h4[contains(text(), "Hotel Le Meurice")]/preceding-sibling::div[contains(@class, "item-icon")]')); const hotelIcon = await hotelItem.getText(); expect(hotelIcon).toContain('🏨'); const restaurantItem = await driver.findElement(By.xpath('//h4[contains(text(), "Le Jules Verne")]/preceding-sibling::div[contains(@class, "item-icon")]')); const restaurantIcon = await restaurantItem.getText(); expect(restaurantIcon).toContain('🍽️'); const attractionItem = await driver.findElement(By.xpath('//h4[contains(text(), "Louvre Museum")]/preceding-sibling::div[contains(@class, "item-icon")]')); const attractionIcon = await attractionItem.getText(); expect(attractionIcon).toContain('🎯'); }); }); });