/** * Test: Appointment disappears when dragged past 8 PM * * This test reproduces the bug where dragging an appointment * past 8 PM on the scheduler causes it to disappear. */ import { test, expect } from '@playwright/test'; test.describe('Scheduler Drag Past 8 PM Bug', () => { test.beforeEach(async ({ page, context }) => { // Clear cookies to avoid stale auth issues await context.clearCookies(); // Login as acme business owner await page.goto('http://acme.lvh.me:5173/login'); await page.fill('input[name="username"], input[placeholder*="username" i], input[type="text"]', 'acme_owner'); await page.fill('input[name="password"], input[placeholder*="password" i], input[type="password"]', 'password123'); await page.click('button[type="submit"]'); // Wait for dashboard to load await page.waitForLoadState('networkidle'); await page.waitForTimeout(1000); // Click on Scheduler in the sidebar await page.getByRole('link', { name: 'Scheduler' }).click(); await page.waitForLoadState('networkidle'); await page.waitForTimeout(500); }); test('should not disappear when dragging appointment past 8 PM', async ({ page }) => { // Wait for scheduler to load await page.waitForSelector('.timeline-scroll', { timeout: 10000 }); // Find an appointment on the scheduler const appointments = page.locator('[draggable="true"]').filter({ hasText: /min/ }); const appointmentCount = await appointments.count(); console.log(`Found ${appointmentCount} appointments`); if (appointmentCount === 0) { test.skip('No appointments found to test with'); return; } // Get the first appointment const appointment = appointments.first(); const appointmentText = await appointment.textContent(); console.log(`Testing with appointment: ${appointmentText}`); // Get initial bounding box const initialBox = await appointment.boundingBox(); if (!initialBox) { test.skip('Could not get appointment bounding box'); return; } console.log(`Initial position: x=${initialBox.x}, y=${initialBox.y}`); // Find the timeline container const timeline = page.locator('.timeline-scroll'); const timelineBox = await timeline.boundingBox(); if (!timelineBox) { throw new Error('Could not get timeline bounding box'); } // Calculate 8 PM position (20 hours * 60 minutes * 2.5 pixels per minute = 3000 pixels from start) // Plus we need to scroll and find the exact position const pixelsPerMinute = 2.5; const eightPMMinutes = 20 * 60; // 1200 minutes const tenPMMinutes = 22 * 60; // 1320 minutes - drag past 8 PM const targetX = tenPMMinutes * pixelsPerMinute; // 3300 pixels from midnight console.log(`Target X position for 10 PM: ${targetX}`); // First, scroll the timeline to show the 8 PM area await timeline.evaluate((el, scrollTo) => { el.scrollLeft = scrollTo - 500; // Scroll to show 8 PM area with some padding }, targetX); await page.waitForTimeout(500); // Wait for scroll to complete // Get the scroll position const scrollLeft = await timeline.evaluate(el => el.scrollLeft); console.log(`Scrolled to: ${scrollLeft}`); // Now drag the appointment to 10 PM position (past 8 PM) // The target position relative to the viewport const targetXViewport = timelineBox.x + (targetX - scrollLeft); const targetY = initialBox.y + initialBox.height / 2; console.log(`Dragging to viewport position: x=${targetXViewport}, y=${targetY}`); // Perform the drag await appointment.hover(); await page.mouse.down(); await page.mouse.move(targetXViewport, targetY, { steps: 20 }); await page.waitForTimeout(100); await page.mouse.up(); // Wait for any updates await page.waitForTimeout(1000); // Take a screenshot for debugging await page.screenshot({ path: 'scheduler-after-drag-to-10pm.png', fullPage: true }); // Check if the appointment still exists // First, look for it in the timeline const appointmentsAfterDrag = page.locator('[draggable="true"]').filter({ hasText: /min/ }); const countAfterDrag = await appointmentsAfterDrag.count(); console.log(`Appointments after drag: ${countAfterDrag}`); // The appointment should still be visible (either in timeline or pending) expect(countAfterDrag).toBeGreaterThanOrEqual(appointmentCount); // Also check console for any errors const consoleErrors: string[] = []; page.on('console', msg => { if (msg.type() === 'error') { consoleErrors.push(msg.text()); } }); // Check network requests for failed appointment updates const failedRequests: string[] = []; page.on('response', response => { if (response.url().includes('/appointments/') && response.status() >= 400) { failedRequests.push(`${response.url()} - ${response.status()}`); } }); await page.waitForTimeout(500); if (consoleErrors.length > 0) { console.log('Console errors:', consoleErrors); } if (failedRequests.length > 0) { console.log('Failed requests:', failedRequests); } }); test('debug: log scheduler time range and appointments', async ({ page }) => { // Wait for scheduler to load await page.waitForSelector('.timeline-scroll', { timeout: 10000 }); // Take initial screenshot await page.screenshot({ path: 'scheduler-initial.png', fullPage: true }); // Log timeline info const timeline = page.locator('.timeline-scroll'); const timelineInfo = await timeline.evaluate(el => ({ scrollWidth: el.scrollWidth, clientWidth: el.clientWidth, scrollLeft: el.scrollLeft, })); console.log('Timeline info:', timelineInfo); // Log all appointments const appointments = page.locator('[draggable="true"]').filter({ hasText: /min/ }); const count = await appointments.count(); console.log(`Total appointments: ${count}`); for (let i = 0; i < count; i++) { const apt = appointments.nth(i); const text = await apt.textContent(); const box = await apt.boundingBox(); console.log(`Appointment ${i}: "${text}" at position x=${box?.x}, y=${box?.y}, width=${box?.width}`); } // Scroll to different times and check visibility const timesToCheck = [ { hour: 8, name: '8 AM' }, { hour: 12, name: '12 PM' }, { hour: 17, name: '5 PM' }, { hour: 20, name: '8 PM' }, { hour: 22, name: '10 PM' }, ]; for (const { hour, name } of timesToCheck) { const scrollPosition = hour * 60 * 2.5 - 200; // pixels from midnight, minus padding await timeline.evaluate((el, pos) => { el.scrollLeft = pos; }, scrollPosition); await page.waitForTimeout(300); const visibleApts = await appointments.count(); console.log(`At ${name}: ${visibleApts} appointments visible, scrollLeft=${await timeline.evaluate(el => el.scrollLeft)}`); await page.screenshot({ path: `scheduler-at-${hour}h.png` }); } }); test('debug: monitor network during drag operation', async ({ page }) => { // Set up network logging const networkLog: { method: string; url: string; status?: number; body?: any }[] = []; page.on('request', request => { if (request.url().includes('/api/')) { networkLog.push({ method: request.method(), url: request.url(), body: request.postData() ? JSON.parse(request.postData() || '{}') : null, }); } }); page.on('response', async response => { if (response.url().includes('/api/')) { const entry = networkLog.find(e => e.url === response.url() && !e.status); if (entry) { entry.status = response.status(); } } }); // Wait for scheduler await page.waitForSelector('.timeline-scroll', { timeout: 10000 }); await page.waitForTimeout(1000); // Clear log after initial load networkLog.length = 0; // Find first appointment const appointments = page.locator('[draggable="true"]').filter({ hasText: /min/ }); const appointment = appointments.first(); if (await appointment.count() === 0) { test.skip('No appointments found'); return; } const initialBox = await appointment.boundingBox(); if (!initialBox) return; // Scroll to 8 PM area const timeline = page.locator('.timeline-scroll'); await timeline.evaluate(el => { el.scrollLeft = 2800; }); // Near 8 PM await page.waitForTimeout(500); const timelineBox = await timeline.boundingBox(); if (!timelineBox) return; // Drag to 9 PM (21 * 60 * 2.5 = 3150, minus scroll) const scrollLeft = await timeline.evaluate(el => el.scrollLeft); const targetX = timelineBox.x + (3150 - scrollLeft); console.log('Starting drag to 9 PM...'); await appointment.hover(); await page.mouse.down(); await page.mouse.move(targetX, initialBox.y + 50, { steps: 30 }); await page.waitForTimeout(200); await page.mouse.up(); // Wait for network requests to complete await page.waitForTimeout(2000); console.log('Network requests during drag:', JSON.stringify(networkLog, null, 2)); // Check if there were any PATCH requests and their responses const patchRequests = networkLog.filter(r => r.method === 'PATCH'); console.log(`PATCH requests: ${patchRequests.length}`); for (const req of patchRequests) { console.log(`PATCH ${req.url}: status=${req.status}, body=${JSON.stringify(req.body)}`); } await page.screenshot({ path: 'scheduler-after-drag-debug.png' }); }); });