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:
54
tests/cache-debug-test.spec.ts
Normal file
54
tests/cache-debug-test.spec.ts
Normal 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');
|
||||
});
|
||||
89
tests/calendar-date-test.spec.ts
Normal file
89
tests/calendar-date-test.spec.ts
Normal 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}`);
|
||||
}
|
||||
});
|
||||
27
tests/calendar-screenshot.spec.ts
Normal file
27
tests/calendar-screenshot.spec.ts
Normal 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
138
tests/calendar-test.spec.ts
Normal 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
39
tests/cookie-test.spec.ts
Normal 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');
|
||||
}
|
||||
});
|
||||
41
tests/debug-calendar.spec.ts
Normal file
41
tests/debug-calendar.spec.ts
Normal 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
40
tests/debug-login.spec.ts
Normal 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');
|
||||
});
|
||||
40
tests/force-refresh-test.spec.ts
Normal file
40
tests/force-refresh-test.spec.ts
Normal 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);
|
||||
});
|
||||
42
tests/visual-calendar-test.spec.ts
Normal file
42
tests/visual-calendar-test.spec.ts
Normal 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'));
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user