Add comprehensive Playwright tests for calendar functionality

- Implemented tests for debugging browser cache vs server response.
- Verified calendar displays correct dates for November 2025.
- Added visual inspection screenshot for calendar layout.
- Created tests to ensure calendar fits within its container.
- Checked for correct styling of available and blocked dates.
- Debugged login form behavior and cookie handling.
- Added tests for forced browser refresh and cache verification.
- Conducted visual tests to compare actual calendar layout with expected.
- Enhanced logging for better traceability during test execution.
This commit is contained in:
Alex Polo
2025-11-07 14:09:22 +01:00
parent 4dec4745ae
commit 570c41f465
53 changed files with 3206 additions and 173 deletions

View File

@@ -0,0 +1,54 @@
import { test } from '@playwright/test';
test('Debug browser cache vs server response', async ({ page }) => {
// Completely clear cache and storage
await page.context().clearCookies();
// Set basic auth header
await page.setExtraHTTPHeaders({
'Authorization': 'Basic ' + Buffer.from('brotha:makefood1').toString('base64'),
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0'
});
console.log('1. Going to page with cache disabled...');
await page.goto('http://localhost:3000/admin/availability', {
waitUntil: 'networkidle'
});
await page.waitForSelector('.cook-calendar', { timeout: 10000 });
await page.waitForTimeout(3000);
// Check what we get immediately
const headers1 = await page.locator('.cook-calendar thead th').allTextContents();
const firstRow1 = await page.locator('.cook-calendar tbody tr:first-child td button').allTextContents();
console.log('2. Initial load:');
console.log(' Headers:', headers1.join(' '));
console.log(' First row:', firstRow1.join(' '));
// Force another reload with cache busting
console.log('3. Hard reload...');
await page.reload({ waitUntil: 'networkidle' });
await page.waitForTimeout(2000);
const headers2 = await page.locator('.cook-calendar thead th').allTextContents();
const firstRow2 = await page.locator('.cook-calendar tbody tr:first-child td button').allTextContents();
console.log('4. After reload:');
console.log(' Headers:', headers2.join(' '));
console.log(' First row:', firstRow2.join(' '));
// Check if there's a difference between SSR and client hydration
const pageSource = await page.content();
if (pageSource.includes('27</button></td><td class="rdp-cell"')) {
console.log('5. Server-side rendered content found in HTML');
} else {
console.log('5. Content appears to be client-side only');
}
// Take final screenshot
await page.screenshot({ path: 'final-calendar-debug.png' });
console.log('6. Screenshot saved as final-calendar-debug.png');
});

View File

@@ -0,0 +1,89 @@
import { test, expect } from '@playwright/test';
test('Verify calendar shows correct dates for November 2025', async ({ page }) => {
// Set basic auth header
await page.setExtraHTTPHeaders({
'Authorization': 'Basic ' + Buffer.from('brotha:makefood1').toString('base64')
});
await page.goto('http://localhost:3000/admin/availability');
await page.waitForSelector('.cook-calendar');
// Tjek månedsnavn
const monthHeader = await page.locator('text=november 2025');
await expect(monthHeader).toBeVisible();
console.log('✅ Month header shows November 2025');
// Find alle dage i kalenderen
const dayElements = await page.locator('.cook-calendar .rdp-day').all();
console.log(`Found ${dayElements.length} day elements`);
// Extract dag numre og deres position
const dayData = [];
for (let i = 0; i < dayElements.length; i++) {
const dayText = await dayElements[i].textContent();
const classes = await dayElements[i].getAttribute('class');
const isOutside = classes?.includes('rdp-day_outside') || false;
dayData.push({
index: i,
text: dayText?.trim(),
isOutside: isOutside
});
}
console.log('Calendar days:', dayData.slice(0, 10)); // First 10 days
// Find første dag i november (1)
const firstNovemberDay = dayData.find(day => day.text === '1' && !day.isOutside);
if (firstNovemberDay) {
console.log(`✅ First day of November (1) found at position ${firstNovemberDay.index}`);
// November 1, 2025 er en lørdag (weekday 6, men vi starter ugen på mandag så position 5)
// Så 1. november skulle være på position 5 (if we count from 0: Ma=0, Ti=1, On=2, To=3, Fr=4, Lø=5)
const expectedPosition = 5; // Saturday in week starting Monday
const actualPosition = firstNovemberDay.index % 7;
console.log(`November 1st is at grid position ${actualPosition}, expected ${expectedPosition}`);
if (actualPosition === expectedPosition) {
console.log('✅ November 1st is correctly positioned on Saturday');
} else {
console.log(`❌ November 1st position is wrong. Expected ${expectedPosition}, got ${actualPosition}`);
}
} else {
console.log('❌ Could not find November 1st');
}
// Find 5. november (today)
const fifthNovemberDay = dayData.find(day => day.text === '5' && !day.isOutside);
if (fifthNovemberDay) {
console.log(`✅ November 5th (today) found at position ${fifthNovemberDay.index}`);
// November 5, 2025 er en onsdag (position 2 if week starts Monday)
const expectedPosition = 2; // Wednesday
const actualPosition = fifthNovemberDay.index % 7;
console.log(`November 5th is at grid position ${actualPosition}, expected ${expectedPosition}`);
if (actualPosition === expectedPosition) {
console.log('✅ November 5th is correctly positioned on Wednesday');
} else {
console.log(`❌ November 5th position is wrong. Expected ${expectedPosition}, got ${actualPosition}`);
}
}
// Tjek at vi har 30 dage i november
const novemberDays = dayData.filter(day => !day.isOutside);
console.log(`November has ${novemberDays.length} days visible`);
// Vis de første par uger for debugging
console.log('\nFirst two weeks layout:');
for (let week = 0; week < 2; week++) {
const weekDays = dayData.slice(week * 7, (week + 1) * 7);
const weekString = weekDays.map(day =>
(day.isOutside ? `(${day.text})` : day.text || 'X').padStart(4, ' ')
).join(' ');
console.log(`Week ${week + 1}: ${weekString}`);
}
});

View File

@@ -0,0 +1,27 @@
import { test } from '@playwright/test';
test('Take screenshot of calendar for visual inspection', async ({ page }) => {
// Set basic auth header
await page.setExtraHTTPHeaders({
'Authorization': 'Basic ' + Buffer.from('brotha:makefood1').toString('base64')
});
await page.goto('http://localhost:3000/admin/availability');
await page.waitForSelector('.cook-calendar');
// Wait a bit more to ensure everything is loaded
await page.waitForTimeout(2000);
// Take screenshot of just the calendar
const calendar = page.locator('.cook-calendar');
await calendar.screenshot({ path: 'calendar-debug.png' });
// Also log the actual HTML structure
const calendarHTML = await calendar.innerHTML();
console.log('=== FULL CALENDAR HTML ===');
console.log(calendarHTML);
// Check actual header text
const headers = await page.locator('.cook-calendar th, .cook-calendar .rdp-head_cell').allTextContents();
console.log('Week headers:', headers);
});

138
tests/calendar-test.spec.ts Normal file
View File

@@ -0,0 +1,138 @@
import { test, expect } from '@playwright/test';
test.describe('Admin Availability Calendar', () => {
test.beforeEach(async ({ page }) => {
// Set basic auth header
await page.setExtraHTTPHeaders({
'Authorization': 'Basic ' + Buffer.from('brotha:makefood1').toString('base64')
});
// Gå til admin availability siden
await page.goto('http://localhost:3000/admin/availability');
// Vent på siden loader
await page.waitForSelector('.cook-calendar');
});
test('should display calendar with proper layout', async ({ page }) => {
// Tjek at kalenderen vises
const calendar = await page.locator('.cook-calendar');
await expect(calendar).toBeVisible();
// Tjek at månedsnavn vises
const monthHeader = await page.locator('text=november 2025');
await expect(monthHeader).toBeVisible();
// Tjek at weekday headers vises
const weekdays = await page.locator('[class*="rdp"]');
await expect(weekdays.first()).toBeVisible();
console.log('✅ Calendar layout looks good');
});
test('should have proper day styling', async ({ page }) => {
// Find en dag i kalenderen
const dayButtons = await page.locator('.cook-calendar .rdp-day');
const firstDay = dayButtons.first();
// Tjek at dagen er synlig
await expect(firstDay).toBeVisible();
// Tjek background farve (should be green for available or red for blocked)
const styles = await firstDay.evaluate((el) => {
const computed = window.getComputedStyle(el);
return {
backgroundColor: computed.backgroundColor,
color: computed.color,
border: computed.border
};
});
console.log('Day styling:', styles);
// Tjek at der er enten grøn eller rød background
const isGreenOrRed = styles.backgroundColor.includes('240, 253, 244') || // green-50
styles.backgroundColor.includes('220, 38, 38'); // red-600
expect(isGreenOrRed).toBeTruthy();
console.log('✅ Day colors are correct');
});
test('should handle day clicks', async ({ page }) => {
// Find en klikkbar dag
const dayButtons = await page.locator('.cook-calendar .rdp-day');
// Vent på at dage er loaded
await expect(dayButtons.first()).toBeVisible();
// Klik på første dag
await dayButtons.first().click();
// Vent kort på API response
await page.waitForTimeout(1000);
// Tjek at der er en toast besked eller loading state
const possibleMessages = [
page.locator('text=✅'),
page.locator('text=🚫'),
page.locator('text=Gemmer ændring'),
page.locator('text=Der skete en fejl')
];
let messageFound = false;
for (const message of possibleMessages) {
if (await message.isVisible()) {
messageFound = true;
console.log('✅ Click interaction working - message found');
break;
}
}
// Hvis ingen besked, så tjek om API kaldet skete via network requests
if (!messageFound) {
console.log('⚠️ No visible message, but click may have worked');
}
});
test('should fit within container', async ({ page }) => {
// Tjek calendar container størrelse
const calendar = await page.locator('.cook-calendar');
const calendarBox = await calendar.boundingBox();
// Tjek container
const container = await page.locator('div.max-w-md');
const containerBox = await container.boundingBox();
if (calendarBox && containerBox) {
// Kalenderen skal passe inden i containeren
expect(calendarBox.width).toBeLessThanOrEqual(containerBox.width + 20); // 20px tolerance
console.log(`✅ Calendar fits: ${calendarBox.width}px ≤ ${containerBox.width}px`);
}
});
test('should show correct blocked dates', async ({ page }) => {
// Tjek API data
const response = await page.request.get('http://localhost:3000/api/availability/blocked');
const blockedDates = await response.json();
console.log('Blocked dates from API:', blockedDates.length);
if (blockedDates.length > 0) {
// Find røde dage i kalenderen
const redDays = await page.locator('.cook-calendar .rdp-day').evaluateAll((buttons) => {
return buttons.filter(btn => {
const styles = window.getComputedStyle(btn);
return styles.backgroundColor.includes('220, 38, 38'); // red-600
}).length;
});
console.log(`Found ${redDays} red days in calendar for ${blockedDates.length} blocked dates`);
// Der skal være mindst nogle røde dage hvis der er blokerede datoer
if (blockedDates.length > 0) {
expect(redDays).toBeGreaterThan(0);
console.log('✅ Blocked dates show as red');
}
}
});
});

39
tests/cookie-test.spec.ts Normal file
View File

@@ -0,0 +1,39 @@
import { test } from '@playwright/test';
test('Check cookie and redirect after login', async ({ page }) => {
// Go to login page
await page.goto('http://localhost:3000/login');
// Fill and submit form
await page.fill('#username', 'brotha');
await page.fill('#password', 'makefood1');
await page.click('button[type="submit"]');
// Wait for response
await page.waitForTimeout(2000);
// Check cookies
const cookies = await page.context().cookies();
const authCookie = cookies.find(c => c.name === 'authToken');
console.log('Auth cookie:', authCookie ? 'Present' : 'Missing');
if (authCookie) {
console.log('Cookie value length:', authCookie.value.length);
console.log('Cookie secure:', authCookie.secure);
console.log('Cookie httpOnly:', authCookie.httpOnly);
}
// Try to navigate to admin manually
console.log('Trying to navigate to /admin...');
await page.goto('http://localhost:3000/admin');
await page.waitForTimeout(1000);
const finalUrl = page.url();
console.log('Final URL:', finalUrl);
if (finalUrl.includes('/login')) {
console.log('❌ Redirected back to login - middleware issue');
} else if (finalUrl.includes('/admin')) {
console.log('✅ Successfully accessed admin');
}
});

View File

@@ -0,0 +1,41 @@
import { test } from '@playwright/test';
test('Debug calendar DOM structure', async ({ page }) => {
// Set basic auth header
await page.setExtraHTTPHeaders({
'Authorization': 'Basic ' + Buffer.from('brotha:makefood1').toString('base64')
});
await page.goto('http://localhost:3000/admin/availability');
await page.waitForSelector('.cook-calendar');
// Log all classes i kalenderen
const calendarHTML = await page.locator('.cook-calendar').innerHTML();
console.log('Calendar HTML structure:');
console.log(calendarHTML.slice(0, 1000) + '...');
// Find alle elements med rdp classes
const rdpElements = await page.locator('[class*="rdp"]').all();
console.log(`Found ${rdpElements.length} elements with rdp classes`);
for (let i = 0; i < Math.min(10, rdpElements.length); i++) {
const className = await rdpElements[i].getAttribute('class');
const tagName = await rdpElements[i].evaluate(el => el.tagName);
console.log(`${i}: ${tagName}.${className}`);
}
// Tjek computed styles på nogle elements
const dayElements = await page.locator('.cook-calendar [class*="day"]').all();
console.log(`Found ${dayElements.length} day elements`);
if (dayElements.length > 0) {
const firstDayStyle = await dayElements[0].evaluate((el) => {
const computed = window.getComputedStyle(el);
return {
backgroundColor: computed.backgroundColor,
className: el.className
};
});
console.log('First day style:', firstDayStyle);
}
});

40
tests/debug-login.spec.ts Normal file
View File

@@ -0,0 +1,40 @@
import { test } from '@playwright/test';
test('Debug login form behavior', async ({ page }) => {
// Capture console messages
const messages: string[] = [];
page.on('console', msg => {
messages.push(`${msg.type()}: ${msg.text()}`);
});
// Go to login page
await page.goto('http://localhost:3000/login');
// Fill form
await page.fill('#username', 'brotha');
await page.fill('#password', 'makefood1');
// Submit form
await page.click('button[type="submit"]');
// Wait a bit
await page.waitForTimeout(3000);
// Check current URL
const currentUrl = page.url();
console.log('Current URL after login:', currentUrl);
// Print console messages
console.log('Console messages:');
messages.forEach(msg => console.log(' ', msg));
// Check if there are any error messages on page
const errorMessage = await page.locator('[class*="red"]').textContent();
if (errorMessage) {
console.log('Error message on page:', errorMessage);
}
// Check localStorage
const authToken = await page.evaluate(() => localStorage.getItem('authToken'));
console.log('Auth token in localStorage:', authToken ? 'Present' : 'Missing');
});

View File

@@ -0,0 +1,40 @@
import { test } from '@playwright/test';
test('Force browser refresh and check cache', async ({ page }) => {
// Set basic auth header
await page.setExtraHTTPHeaders({
'Authorization': 'Basic ' + Buffer.from('brotha:makefood1').toString('base64')
});
// Force hard reload
await page.goto('http://localhost:3000/admin/availability', {
waitUntil: 'networkidle'
});
// Bypass cache
await page.reload({ waitUntil: 'networkidle' });
await page.waitForSelector('.cook-calendar');
await page.waitForTimeout(3000);
// Take screenshot
await page.screenshot({ path: 'current-calendar.png', fullPage: true });
// Check if there are any console errors
const logs = [];
page.on('console', msg => {
logs.push(`${msg.type()}: ${msg.text()}`);
});
// Reload one more time and check
await page.reload({ waitUntil: 'networkidle' });
await page.waitForTimeout(2000);
const tableHTML = await page.locator('.cook-calendar table').innerHTML();
console.log('Table HTML (first 500 chars):');
console.log(tableHTML.substring(0, 500) + '...');
// Check actual layout one more time
const firstRowCells = await page.locator('.cook-calendar tbody tr:first-child td button').allTextContents();
console.log('After hard refresh - First row:', firstRowCells);
});

View File

@@ -0,0 +1,42 @@
import { test } from '@playwright/test';
test('Visual test - take screenshot and compare with expected layout', async ({ page }) => {
// Set basic auth header
await page.setExtraHTTPHeaders({
'Authorization': 'Basic ' + Buffer.from('brotha:makefood1').toString('base64')
});
await page.goto('http://localhost:3000/admin/availability');
await page.waitForSelector('.cook-calendar');
await page.waitForTimeout(2000);
// Get the actual table structure
const tableRows = await page.locator('.cook-calendar tbody tr').all();
console.log('=== ACTUAL CALENDAR LAYOUT ===');
for (let i = 0; i < tableRows.length; i++) {
const cells = await tableRows[i].locator('td button').allTextContents();
console.log(`Row ${i + 1}: ${cells.join(' ')}`);
}
// Get headers
const headers = await page.locator('.cook-calendar thead th').allTextContents();
console.log('Headers:', headers.join(' '));
// What we expect for November 2025:
// November 1, 2025 is Saturday, so in week starting Monday:
// ma ti on to fr lö sö
// Should be: 27 28 29 30 31 1 2
const firstRow = await tableRows[0].locator('td button').allTextContents();
console.log('\nFirst row actual:', firstRow);
console.log('First row expected: [27, 28, 29, 30, 31, 1, 2] (1 should be in 6th position - Saturday)');
if (firstRow[5] === '1') {
console.log('✅ November 1st is correctly in position 6 (Saturday)');
} else {
console.log(`❌ November 1st is in wrong position. Found "${firstRow[5]}" in position 6`);
console.log('Position of "1":', firstRow.indexOf('1'));
}
});