2025-09-28 18:48:44 +02:00
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' ,
2025-09-30 08:29:17 +02:00
startDate : '2025-02-01' ,
endDate : '2025-02-05'
2025-09-28 18:48:44 +02:00
} ;
} ) ;
afterAll ( async ( ) => {
2025-09-30 08:29:17 +02:00
global . plannableTestsInitialized = false ;
2025-09-28 18:48:44 +02:00
await global . quitDriver ( driver ) ;
} ) ;
beforeEach ( async ( ) => {
2025-09-30 08:29:17 +02:00
// 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 ) ;
}
2025-09-28 18:48:44 +02:00
2025-09-30 08:29:17 +02:00
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 ) ;
}
}
}
} ) ;
2025-09-28 18:48:44 +02:00
2025-09-30 08:29:17 +02:00
// 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 ) ;
2025-09-28 18:48:44 +02:00
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 ) ;
2025-09-30 08:29:17 +02:00
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 ( ) ;
2025-09-28 18:48:44 +02:00
// Verify trip detail page elements
await driver . wait ( until . elementLocated ( By . xpath ( ` //h1[contains(text(), " ${ testTrip . name } ")] ` ) ) , 10000 ) ;
2025-09-30 08:29:17 +02:00
// 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 ) ;
2025-09-28 18:48:44 +02:00
} ) ;
it ( 'should add a plannable item' , async ( ) => {
2025-09-30 08:29:17 +02:00
// Navigate to trip detail page
await navigateToTripDetail ( ) ;
2025-09-28 18:48:44 +02:00
// 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")]' ) ) ;
2025-09-30 08:29:17 +02:00
// 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 ) ;
2025-09-28 18:48:44 +02:00
2025-09-30 08:29:17 +02:00
// Wait for modal to close
await waitForModalClose ( ) ;
2025-09-28 18:48:44 +02:00
// 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 ( ) => {
2025-09-30 08:29:17 +02:00
// Navigate to trip detail page
await navigateToTripDetail ( ) ;
2025-09-28 18:48:44 +02:00
2025-09-30 08:29:17 +02:00
// 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 ) ;
2025-09-28 18:48:44 +02:00
// 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")]' ) ) ;
2025-09-30 08:29:17 +02:00
await driver . executeScript ( "arguments[0].scrollIntoView(true);" , updateButton ) ;
await driver . sleep ( 300 ) ;
await driver . executeScript ( "arguments[0].click();" , updateButton ) ;
2025-09-28 18:48:44 +02:00
// Wait for modal to close
2025-09-30 08:29:17 +02:00
await waitForModalClose ( ) ;
2025-09-28 18:48:44 +02:00
// 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 ( ) => {
2025-09-30 08:29:17 +02:00
// Navigate to trip detail page
await navigateToTripDetail ( ) ;
2025-09-28 18:48:44 +02:00
2025-09-30 08:29:17 +02:00
// 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 ) ;
2025-09-28 18:48:44 +02:00
// 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 ( ) ;
2025-09-30 08:29:17 +02:00
// 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 ( ) ;
2025-09-28 18:48:44 +02:00
2025-09-30 08:29:17 +02:00
// Verify item was removed
2025-09-28 18:48:44 +02:00
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 ( ) => {
2025-09-30 08:29:17 +02:00
// Navigate to trip detail page
await navigateToTripDetail ( ) ;
2025-09-28 18:48:44 +02:00
// 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 ) ;
2025-09-30 08:29:17 +02:00
// Submit the form - use JavaScript click to avoid interception
2025-09-28 18:48:44 +02:00
const submitButton = await driver . findElement ( By . xpath ( '//button[contains(text(), "Add Item")]' ) ) ;
2025-09-30 08:29:17 +02:00
await driver . executeScript ( "arguments[0].scrollIntoView(true);" , submitButton ) ;
await driver . sleep ( 500 ) ;
await driver . executeScript ( "arguments[0].click();" , submitButton ) ;
2025-09-28 18:48:44 +02:00
// Wait for modal to close
2025-09-30 08:29:17 +02:00
await waitForModalClose ( ) ;
2025-09-28 18:48:44 +02:00
// 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 ( '🎯' ) ;
} ) ;
} ) ;
} ) ;