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: '02/01/2025', endDate: '02/05/2025' }; }); afterAll(async () => { await global.quitDriver(driver); }); beforeEach(async () => { // 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); }); describe('Plannable Items Management', () => { it('should create a trip with auto-generated calendar slots', async () => { // Register new user await driver.wait(until.elementLocated(By.css('[data-testid="register-link"]')), 10000); await driver.findElement(By.css('[data-testid="register-link"]')).click(); await registrationPage.register(testUser); await driver.wait(until.urlContains('/'), 10000); // Create a new trip await tripPage.openCreateModal(); await tripPage.fillTripForm(testTrip); await tripPage.submitForm(); // Wait for trip to be created await driver.wait(until.elementLocated(By.xpath(`//h3[contains(text(), "${testTrip.name}")]`)), 10000); // Click on the trip card to navigate to detail page const tripCard = await driver.findElement(By.xpath(`//h3[contains(text(), "${testTrip.name}")]/ancestor::div[contains(@class, 'trip-card')]`)); await tripCard.click(); // Wait for trip detail page to load await driver.wait(until.urlContains('/trip/'), 10000); // Verify trip detail page elements await driver.wait(until.elementLocated(By.xpath(`//h1[contains(text(), "${testTrip.name}")]`)), 10000); // Verify calendar slots were created (5 days: Feb 1-5) const daySlots = await driver.findElements(By.xpath('//h3[contains(@class, "section-title") and contains(text(), "Day")]')); expect(daySlots.length).toBeGreaterThanOrEqual(5); // Should have at least 5 day slots }); it('should add a plannable item', async () => { // Login with existing user await loginPage.login(testUser.email, testUser.password); await driver.wait(until.urlContains('/'), 10000); // Navigate to the trip detail page 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); // 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")]')); await submitButton.click(); // Wait for modal to close and item to appear await driver.wait(until.stalenessOf(driver.findElement(By.className('plannable-form-overlay'))), 5000); // 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 () => { // Login with existing user await loginPage.login(testUser.email, testUser.password); await driver.wait(until.urlContains('/'), 10000); // Navigate to the trip detail page 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); // 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 updateButton.click(); // Wait for modal to close await driver.wait(until.stalenessOf(driver.findElement(By.className('plannable-form-overlay'))), 5000); // 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 () => { // Login with existing user await loginPage.login(testUser.email, testUser.password); await driver.wait(until.urlContains('/'), 10000); // Navigate to the trip detail page 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); // 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(); // Accept confirmation dialog await driver.wait(until.alertIsPresent(), 5000); const alert = await driver.switchTo().alert(); await alert.accept(); // Verify the item is removed await driver.sleep(1000); // Give time for the item to be 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 () => { // Login with existing user await loginPage.login(testUser.email, testUser.password); await driver.wait(until.urlContains('/'), 10000); // Navigate to the trip detail page 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); // 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 const submitButton = await driver.findElement(By.xpath('//button[contains(text(), "Add Item")]')); await submitButton.click(); // Wait for modal to close await driver.wait(until.stalenessOf(driver.findElement(By.className('plannable-form-overlay'))), 5000); // 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('🎯'); }); }); });